Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) 2004-2026 ZNC, see the NOTICE file for details. |
3 | | * |
4 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | | * you may not use this file except in compliance with the License. |
6 | | * You may obtain a copy of the License at |
7 | | * |
8 | | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | | * |
10 | | * Unless required by applicable law or agreed to in writing, software |
11 | | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | * See the License for the specific language governing permissions and |
14 | | * limitations under the License. |
15 | | */ |
16 | | |
17 | | #include <znc/IRCSock.h> |
18 | | #include <znc/Chan.h> |
19 | | #include <znc/User.h> |
20 | | #include <znc/IRCNetwork.h> |
21 | | #include <znc/Server.h> |
22 | | #include <znc/Query.h> |
23 | | #include <znc/ZNCDebug.h> |
24 | | #include <time.h> |
25 | | |
26 | | using std::set; |
27 | | using std::vector; |
28 | | using std::map; |
29 | | |
30 | | #define IRCSOCKMODULECALL(macFUNC, macEXITER) \ |
31 | 0 | NETWORKMODULECALL(macFUNC, m_pNetwork->GetUser(), m_pNetwork, nullptr, \ |
32 | 0 | macEXITER) |
33 | | // These are used in OnGeneralCTCP() |
34 | | const unsigned long long CIRCSock::m_uCTCPFloodTime = 5000; |
35 | | const unsigned int CIRCSock::m_uCTCPFloodCount = 5; |
36 | | |
37 | | // It will be bad if user sets it to 0.00000000000001 |
38 | | // If you want no flood protection, set network's flood rate to -1 |
39 | | // TODO move this constant to CIRCNetwork? |
40 | | static const double FLOOD_MINIMAL_RATE = 0.3; |
41 | | |
42 | | class CIRCFloodTimer : public CCron { |
43 | | CIRCSock* m_pSock; |
44 | | |
45 | | public: |
46 | 0 | CIRCFloodTimer(CIRCSock* pSock) : m_pSock(pSock) { |
47 | 0 | StartMaxCycles(m_pSock->m_fFloodRate, 0); |
48 | 0 | } |
49 | | CIRCFloodTimer(const CIRCFloodTimer&) = delete; |
50 | | CIRCFloodTimer& operator=(const CIRCFloodTimer&) = delete; |
51 | 0 | void RunJob() override { |
52 | 0 | if (m_pSock->m_iSendsAllowed < m_pSock->m_uFloodBurst) { |
53 | 0 | m_pSock->m_iSendsAllowed++; |
54 | 0 | } |
55 | 0 | m_pSock->TrySend(); |
56 | 0 | } |
57 | | }; |
58 | | |
59 | 0 | bool CIRCSock::IsFloodProtected(double fRate) { |
60 | 0 | return fRate > FLOOD_MINIMAL_RATE; |
61 | 0 | } |
62 | | |
63 | | CIRCSock::CIRCSock(CIRCNetwork* pNetwork) |
64 | 0 | : CIRCSocket(), |
65 | 0 | m_bAuthed(false), |
66 | 0 | m_bNamesx(false), |
67 | 0 | m_bUHNames(false), |
68 | 0 | m_bAwayNotify(false), |
69 | 0 | m_bAccountNotify(false), |
70 | 0 | m_bExtendedJoin(false), |
71 | 0 | m_bServerTime(false), |
72 | 0 | m_bMessageTagCap(false), |
73 | 0 | m_sPerms("*!@%+"), |
74 | 0 | m_sPermModes("qaohv"), |
75 | 0 | m_scUserModes(), |
76 | 0 | m_mceChanModes(), |
77 | 0 | m_pNetwork(pNetwork), |
78 | 0 | m_Nick(), |
79 | 0 | m_sPass(""), |
80 | 0 | m_msChans(), |
81 | 0 | m_uMaxNickLen(9), |
82 | 0 | m_uCapPaused(0), |
83 | 0 | m_ssAcceptedCaps(), |
84 | 0 | m_ssPendingCaps(), |
85 | 0 | m_lastCTCP(0), |
86 | 0 | m_uNumCTCP(0), |
87 | 0 | m_mISupport(), |
88 | 0 | m_vSendQueue(), |
89 | 0 | m_iSendsAllowed(pNetwork->GetFloodBurst()), |
90 | 0 | m_uFloodBurst(pNetwork->GetFloodBurst()), |
91 | 0 | m_fFloodRate(pNetwork->GetFloodRate()), |
92 | 0 | m_bFloodProtection(IsFloodProtected(pNetwork->GetFloodRate())), |
93 | 0 | m_lastFloodWarned(0) { |
94 | 0 | EnableReadLine(); |
95 | 0 | m_Nick.SetIdent(m_pNetwork->GetIdent()); |
96 | 0 | m_Nick.SetHost(m_pNetwork->GetBindHost()); |
97 | 0 | SetEncoding(m_pNetwork->GetEncoding()); |
98 | |
|
99 | 0 | m_mceChanModes['b'] = ListArg; |
100 | 0 | m_mceChanModes['e'] = ListArg; |
101 | 0 | m_mceChanModes['I'] = ListArg; |
102 | 0 | m_mceChanModes['k'] = HasArg; |
103 | 0 | m_mceChanModes['l'] = ArgWhenSet; |
104 | 0 | m_mceChanModes['p'] = NoArg; |
105 | 0 | m_mceChanModes['s'] = NoArg; |
106 | 0 | m_mceChanModes['t'] = NoArg; |
107 | 0 | m_mceChanModes['i'] = NoArg; |
108 | 0 | m_mceChanModes['n'] = NoArg; |
109 | |
|
110 | 0 | pNetwork->SetIRCSocket(this); |
111 | | |
112 | | // RFC says a line can have 512 chars max + 512 chars for message tags, but |
113 | | // we don't care ;) |
114 | 0 | SetMaxBufferThreshold(2048); |
115 | 0 | if (m_bFloodProtection) { |
116 | 0 | AddCron(new CIRCFloodTimer(this)); |
117 | 0 | } |
118 | 0 | } |
119 | | |
120 | 0 | CIRCSock::~CIRCSock() { |
121 | 0 | if (!m_bAuthed) { |
122 | 0 | IRCSOCKMODULECALL(OnIRCConnectionError(this), NOTHING); |
123 | 0 | } |
124 | |
|
125 | 0 | const vector<CChan*>& vChans = m_pNetwork->GetChans(); |
126 | 0 | for (CChan* pChan : vChans) { |
127 | 0 | pChan->Reset(); |
128 | 0 | } |
129 | |
|
130 | 0 | m_pNetwork->IRCDisconnected(); |
131 | |
|
132 | 0 | for (const auto& it : m_msChans) { |
133 | 0 | delete it.second; |
134 | 0 | } |
135 | |
|
136 | 0 | Quit(); |
137 | 0 | m_msChans.clear(); |
138 | 0 | m_pNetwork->AddBytesRead(GetBytesRead()); |
139 | 0 | m_pNetwork->AddBytesWritten(GetBytesWritten()); |
140 | 0 | } |
141 | | |
142 | 0 | void CIRCSock::Quit(const CString& sQuitMsg) { |
143 | 0 | if (IsClosed()) { |
144 | 0 | return; |
145 | 0 | } |
146 | 0 | if (!m_bAuthed) { |
147 | 0 | Close(CLT_NOW); |
148 | 0 | return; |
149 | 0 | } |
150 | 0 | if (!sQuitMsg.empty()) { |
151 | 0 | PutIRC("QUIT :" + sQuitMsg); |
152 | 0 | } else { |
153 | 0 | PutIRC("QUIT :" + m_pNetwork->ExpandString(m_pNetwork->GetQuitMsg())); |
154 | 0 | } |
155 | 0 | Close(CLT_AFTERWRITE); |
156 | 0 | } |
157 | | |
158 | 0 | void CIRCSock::ReadLine(const CString& sData) { |
159 | 0 | CString sLine = sData; |
160 | |
|
161 | 0 | sLine.Replace("\n", ""); |
162 | 0 | sLine.Replace("\r", ""); |
163 | |
|
164 | 0 | DEBUG("(" << m_pNetwork->GetUser()->GetUsername() << "/" |
165 | 0 | << m_pNetwork->GetName() << ") IRC -> ZNC [" << sLine << "]"); |
166 | |
|
167 | 0 | bool bReturn = false; |
168 | 0 | IRCSOCKMODULECALL(OnRaw(sLine), &bReturn); |
169 | 0 | if (bReturn) return; |
170 | | |
171 | 0 | CMessage Message(sLine); |
172 | 0 | Message.SetNetwork(m_pNetwork); |
173 | |
|
174 | 0 | IRCSOCKMODULECALL(OnRawMessage(Message), &bReturn); |
175 | 0 | if (bReturn) return; |
176 | | |
177 | 0 | switch (Message.GetType()) { |
178 | 0 | case CMessage::Type::Account: |
179 | 0 | bReturn = OnAccountMessage(Message); |
180 | 0 | break; |
181 | 0 | case CMessage::Type::Action: |
182 | 0 | bReturn = OnActionMessage(Message); |
183 | 0 | break; |
184 | 0 | case CMessage::Type::Away: |
185 | 0 | bReturn = OnAwayMessage(Message); |
186 | 0 | break; |
187 | 0 | case CMessage::Type::Capability: |
188 | 0 | bReturn = OnCapabilityMessage(Message); |
189 | 0 | break; |
190 | 0 | case CMessage::Type::ChgHost: |
191 | 0 | bReturn = OnChgHostMessage(Message); |
192 | 0 | break; |
193 | 0 | case CMessage::Type::CTCP: |
194 | 0 | bReturn = OnCTCPMessage(Message); |
195 | 0 | break; |
196 | 0 | case CMessage::Type::Error: |
197 | 0 | bReturn = OnErrorMessage(Message); |
198 | 0 | break; |
199 | 0 | case CMessage::Type::Invite: |
200 | 0 | bReturn = OnInviteMessage(Message); |
201 | 0 | break; |
202 | 0 | case CMessage::Type::Join: |
203 | 0 | bReturn = OnJoinMessage(Message); |
204 | 0 | break; |
205 | 0 | case CMessage::Type::Kick: |
206 | 0 | bReturn = OnKickMessage(Message); |
207 | 0 | break; |
208 | 0 | case CMessage::Type::Mode: |
209 | 0 | bReturn = OnModeMessage(Message); |
210 | 0 | break; |
211 | 0 | case CMessage::Type::Nick: |
212 | 0 | bReturn = OnNickMessage(Message); |
213 | 0 | break; |
214 | 0 | case CMessage::Type::Notice: |
215 | 0 | bReturn = OnNoticeMessage(Message); |
216 | 0 | break; |
217 | 0 | case CMessage::Type::Numeric: |
218 | 0 | bReturn = OnNumericMessage(Message); |
219 | 0 | break; |
220 | 0 | case CMessage::Type::Part: |
221 | 0 | bReturn = OnPartMessage(Message); |
222 | 0 | break; |
223 | 0 | case CMessage::Type::Ping: |
224 | 0 | bReturn = OnPingMessage(Message); |
225 | 0 | break; |
226 | 0 | case CMessage::Type::Pong: |
227 | 0 | bReturn = OnPongMessage(Message); |
228 | 0 | break; |
229 | 0 | case CMessage::Type::Quit: |
230 | 0 | bReturn = OnQuitMessage(Message); |
231 | 0 | break; |
232 | 0 | case CMessage::Type::TagMsg: |
233 | 0 | bReturn = OnTagMessage(Message); |
234 | 0 | break; |
235 | 0 | case CMessage::Type::Text: |
236 | 0 | bReturn = OnTextMessage(Message); |
237 | 0 | break; |
238 | 0 | case CMessage::Type::Topic: |
239 | 0 | bReturn = OnTopicMessage(Message); |
240 | 0 | break; |
241 | 0 | case CMessage::Type::Wallops: |
242 | 0 | bReturn = OnWallopsMessage(Message); |
243 | 0 | break; |
244 | 0 | default: |
245 | 0 | break; |
246 | 0 | } |
247 | 0 | if (bReturn) return; |
248 | | |
249 | 0 | m_pNetwork->PutUser(Message); |
250 | 0 | } |
251 | | |
252 | 0 | void CIRCSock::SendNextCap() { |
253 | 0 | if (m_uCapPaused) { |
254 | 0 | return; |
255 | 0 | } |
256 | | |
257 | 0 | if (!m_ssPendingCaps.empty()) { |
258 | 0 | CString sCaps = std::move(*m_ssPendingCaps.begin()); |
259 | 0 | m_ssPendingCaps.erase(m_ssPendingCaps.begin()); |
260 | 0 | while (!m_ssPendingCaps.empty()) { |
261 | 0 | const CString& sNext = *m_ssPendingCaps.begin(); |
262 | | // Old version of cap spec allowed NAK to only contain first 100 |
263 | | // symbols of the REQ message. |
264 | | // Alternatively, instead of parsing NAK we could remember the last |
265 | | // REQ sent, but this is simpler, as we need the splitting logic anyway. |
266 | | // TODO: lift this limit to something more reasonable, e.g. 400 |
267 | 0 | if (sCaps.length() + sNext.length() > 98) { |
268 | 0 | break; |
269 | 0 | } |
270 | 0 | sCaps += " " + sNext; |
271 | 0 | m_ssPendingCaps.erase(m_ssPendingCaps.begin()); |
272 | 0 | } |
273 | 0 | PutIRC("CAP REQ :" + sCaps); |
274 | 0 | return; |
275 | 0 | } |
276 | | |
277 | 0 | if (!m_ssPendingCapsPhase2.empty()) { |
278 | | // Those which NAKed in first phase, try them again, one by one, |
279 | | // to know which of them failed. |
280 | 0 | CString sCap = std::move(*m_ssPendingCapsPhase2.begin()); |
281 | 0 | m_ssPendingCapsPhase2.erase(m_ssPendingCapsPhase2.begin()); |
282 | 0 | PutIRC("CAP REQ :" + sCap); |
283 | 0 | return; |
284 | 0 | } |
285 | | |
286 | | // We already got all needed ACK/NAK replies. |
287 | 0 | if (!m_bAuthed) { |
288 | 0 | PutIRC("CAP END"); |
289 | 0 | } |
290 | 0 | } |
291 | | |
292 | 0 | void CIRCSock::PauseCap() { ++m_uCapPaused; } |
293 | | |
294 | 0 | void CIRCSock::ResumeCap() { |
295 | 0 | --m_uCapPaused; |
296 | 0 | SendNextCap(); |
297 | 0 | } |
298 | | |
299 | 0 | bool CIRCSock::OnServerCapAvailable(const CString& sCap, const CString& sValue) { |
300 | 0 | bool bResult = false; |
301 | 0 | IRCSOCKMODULECALL(OnServerCapAvailable(sCap, sValue), &bResult); |
302 | 0 | return bResult; |
303 | 0 | } |
304 | | |
305 | | // #124: OnChanMsg(): nick doesn't have perms |
306 | 0 | static void FixupChanNick(CNick& Nick, CChan* pChan) { |
307 | | // A channel nick has up-to-date channel perms, but might be |
308 | | // lacking (usernames-in-host) the associated ident & host. |
309 | | // An incoming message, on the other hand, has normally a full |
310 | | // nick!ident@host prefix. Sync the two so that channel nicks |
311 | | // get the potentially missing piece of info and module hooks |
312 | | // get the perms. |
313 | 0 | CNick* pChanNick = pChan->FindNick(Nick.GetNick()); |
314 | 0 | if (pChanNick) { |
315 | 0 | if (!Nick.GetIdent().empty()) { |
316 | 0 | pChanNick->SetIdent(Nick.GetIdent()); |
317 | 0 | } |
318 | 0 | if (!Nick.GetHost().empty()) { |
319 | 0 | pChanNick->SetHost(Nick.GetHost()); |
320 | 0 | } |
321 | 0 | Nick.Clone(*pChanNick); |
322 | 0 | } |
323 | 0 | } |
324 | | |
325 | | // #1826: CAP away-notify clients shouldn't receive notifications if all shared |
326 | | // channels are detached |
327 | | // This applies to account, away-notify, and chghost. |
328 | 0 | bool CIRCSock::IsNickVisibleInAttachedChannels(const CString& sNick) const { |
329 | 0 | const vector<CChan*>& vChans = m_pNetwork->GetChans(); |
330 | 0 | for (const CChan* pChan : vChans) { |
331 | 0 | if (!pChan->IsDetached() && pChan->FindNick(sNick)) { |
332 | 0 | return true; |
333 | 0 | } |
334 | 0 | } |
335 | 0 | return false; |
336 | 0 | } |
337 | | |
338 | 0 | bool CIRCSock::OnAccountMessage(CMessage& Message) { |
339 | | // TODO: IRCSOCKMODULECALL(OnAccountMessage(Message)) ? |
340 | | // Do not send ACCOUNT if all shared channels are detached. |
341 | 0 | if (!IsNickVisibleInAttachedChannels(Message.GetNick().GetNick())) { |
342 | 0 | return true; |
343 | 0 | } |
344 | 0 | return false; |
345 | 0 | } |
346 | | |
347 | 0 | bool CIRCSock::OnActionMessage(CActionMessage& Message) { |
348 | 0 | bool bResult = false; |
349 | 0 | CChan* pChan = nullptr; |
350 | 0 | CString sTarget = Message.GetTarget(); |
351 | 0 | if (sTarget.Equals(GetNick())) { |
352 | 0 | IRCSOCKMODULECALL(OnPrivCTCPMessage(Message), &bResult); |
353 | 0 | if (bResult) return true; |
354 | 0 | IRCSOCKMODULECALL(OnPrivActionMessage(Message), &bResult); |
355 | 0 | if (bResult) return true; |
356 | | |
357 | 0 | if (!m_pNetwork->IsUserOnline() || |
358 | 0 | !m_pNetwork->GetUser()->AutoClearQueryBuffer()) { |
359 | 0 | const CNick& Nick = Message.GetNick(); |
360 | 0 | CQuery* pQuery = m_pNetwork->AddQuery(Nick.GetNick()); |
361 | 0 | if (pQuery) { |
362 | 0 | CActionMessage Format; |
363 | 0 | Format.Clone(Message); |
364 | 0 | Format.SetNick(_NAMEDFMT(Nick.GetNickMask())); |
365 | 0 | Format.SetTarget("{target}"); |
366 | 0 | Format.SetText("{text}"); |
367 | 0 | pQuery->AddBuffer(Format, Message.GetText()); |
368 | 0 | } |
369 | 0 | } |
370 | 0 | } else { |
371 | 0 | pChan = m_pNetwork->FindChan(sTarget); |
372 | 0 | if (pChan) { |
373 | 0 | Message.SetChan(pChan); |
374 | 0 | FixupChanNick(Message.GetNick(), pChan); |
375 | 0 | IRCSOCKMODULECALL(OnChanCTCPMessage(Message), &bResult); |
376 | 0 | if (bResult) return true; |
377 | 0 | IRCSOCKMODULECALL(OnChanActionMessage(Message), &bResult); |
378 | 0 | if (bResult) return true; |
379 | | |
380 | 0 | if (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline() || |
381 | 0 | pChan->IsDetached()) { |
382 | 0 | CActionMessage Format; |
383 | 0 | Format.Clone(Message); |
384 | 0 | Format.SetNick(_NAMEDFMT(Message.GetNick().GetNickMask())); |
385 | 0 | Format.SetTarget(_NAMEDFMT(Message.GetTarget())); |
386 | 0 | Format.SetText("{text}"); |
387 | 0 | pChan->AddBuffer(Format, Message.GetText()); |
388 | 0 | } |
389 | 0 | } |
390 | 0 | } |
391 | | |
392 | 0 | return (pChan && pChan->IsDetached()); |
393 | 0 | } |
394 | | |
395 | 0 | bool CIRCSock::OnAwayMessage(CMessage& Message) { |
396 | | // TODO: IRCSOCKMODULECALL(OnAwayMessage(Message)) ? |
397 | | // Do not send away-notify if all shared channels are detached. |
398 | 0 | if (!IsNickVisibleInAttachedChannels(Message.GetNick().GetNick())) { |
399 | 0 | return true; |
400 | 0 | } |
401 | 0 | return false; |
402 | 0 | } |
403 | | |
404 | 0 | bool CIRCSock::OnCapabilityMessage(CMessage& Message) { |
405 | | // The first parameter is most likely "*". No idea why, the |
406 | | // CAP spec don't mention this, but all implementations |
407 | | // I've seen add this extra asterisk |
408 | 0 | CString sSubCmd = Message.GetParam(1); |
409 | | |
410 | | // If the caplist of a reply is too long, it's split |
411 | | // into multiple replies. A "*" is prepended to show |
412 | | // that the list was split into multiple replies. |
413 | | // This is useful mainly for LS. For ACK and NAK |
414 | | // replies, there's no real need for this, because |
415 | | // we request only 1 capability per line. |
416 | | // If we will need to support broken servers or will |
417 | | // send several requests per line, need to delay ACK |
418 | | // actions until all ACK lines are received and |
419 | | // to recognize past request of NAK by 100 chars |
420 | | // of this reply. |
421 | | // As for LS, we shouldn't don't send END after receiving first line, |
422 | | // because interesting caps can be on next line. |
423 | 0 | CString sArgs; |
424 | 0 | bool bSendNext = true; |
425 | 0 | if (Message.GetParam(2) == "*") { |
426 | 0 | bSendNext = false; |
427 | 0 | sArgs = Message.GetParam(3); |
428 | 0 | } else { |
429 | 0 | sArgs = Message.GetParam(2); |
430 | 0 | } |
431 | |
|
432 | 0 | static std::map<CString, std::function<void(CIRCSock * pSock, bool bVal)>> |
433 | 0 | mSupportedCaps = { |
434 | 0 | {"multi-prefix", |
435 | 0 | [](CIRCSock* pSock, bool bVal) { pSock->m_bNamesx = bVal; }}, |
436 | 0 | {"userhost-in-names", |
437 | 0 | [](CIRCSock* pSock, bool bVal) { pSock->m_bUHNames = bVal; }}, |
438 | 0 | {"cap-notify", [](CIRCSock* pSock, bool bVal) {}}, |
439 | 0 | {"invite-notify", [](CIRCSock* pSock, bool bVal) {}}, |
440 | 0 | {"server-time", |
441 | 0 | [](CIRCSock* pSock, bool bVal) { pSock->m_bServerTime = bVal; }}, |
442 | 0 | {"znc.in/server-time-iso", |
443 | 0 | [](CIRCSock* pSock, bool bVal) { pSock->m_bServerTime = bVal; }}, |
444 | 0 | {"chghost", [](CIRCSock* pSock, bool) {}}, |
445 | 0 | {"message-tags", [](CIRCSock* pSock, |
446 | 0 | bool bVal) { pSock->m_bMessageTagCap = bVal; }}, |
447 | 0 | }; |
448 | |
|
449 | 0 | auto RemoveCap = [&](const CString& sCap) { |
450 | 0 | IRCSOCKMODULECALL(OnServerCapResult(sCap, false), NOTHING); |
451 | 0 | auto it = mSupportedCaps.find(sCap); |
452 | 0 | if (it != mSupportedCaps.end()) { |
453 | 0 | it->second(this, false); |
454 | 0 | } |
455 | 0 | m_ssAcceptedCaps.erase(sCap); |
456 | 0 | m_ssPendingCaps.erase(sCap); |
457 | 0 | }; |
458 | |
|
459 | 0 | if (sSubCmd == "LS" || sSubCmd == "NEW") { |
460 | 0 | VCString vsTokens; |
461 | 0 | sArgs.Split(" ", vsTokens, false); |
462 | |
|
463 | 0 | for (const CString& sToken : vsTokens) { |
464 | 0 | CString sCap, sValue; |
465 | 0 | int eq = sToken.find('='); |
466 | 0 | if (eq == std::string::npos) { |
467 | 0 | sCap = sToken; |
468 | 0 | } else { |
469 | 0 | sCap = sToken.substr(0, eq); |
470 | 0 | sValue = sToken.substr(eq + 1); |
471 | 0 | } |
472 | 0 | if (CZNC::Get().GetServerCapBlacklist().count(sCap)) { |
473 | 0 | continue; |
474 | 0 | } |
475 | 0 | m_msCapLsValues[sCap] = sValue; |
476 | 0 | if (OnServerCapAvailable(sCap, sValue) || mSupportedCaps.count(sCap)) { |
477 | 0 | m_ssPendingCaps.insert(sCap); |
478 | 0 | } |
479 | 0 | } |
480 | 0 | } else if (sSubCmd == "ACK") { |
481 | 0 | VCString vsCaps; |
482 | 0 | sArgs.Split(" ", vsCaps, false); |
483 | 0 | for (CString& sCap : vsCaps) { |
484 | 0 | IRCSOCKMODULECALL(OnServerCapResult(sCap, true), NOTHING); |
485 | 0 | auto it = mSupportedCaps.find(sCap); |
486 | 0 | if (it != mSupportedCaps.end()) { |
487 | 0 | it->second(this, true); |
488 | 0 | } |
489 | 0 | m_ssAcceptedCaps.insert(std::move(sCap)); |
490 | 0 | } |
491 | 0 | } else if (sSubCmd == "NAK") { |
492 | | // This should work because there's no [known] |
493 | | // capability with length of name more than 100 characters. |
494 | 0 | VCString vsCaps; |
495 | 0 | sArgs.Split(" ", vsCaps, false); |
496 | 0 | if (vsCaps.size() == 1) { |
497 | 0 | RemoveCap(sArgs); |
498 | 0 | } else { |
499 | | // Retry them one by one |
500 | 0 | for (CString& sCap : vsCaps) { |
501 | 0 | m_ssPendingCapsPhase2.insert(std::move(sCap)); |
502 | 0 | } |
503 | 0 | } |
504 | 0 | } else if (sSubCmd == "DEL") { |
505 | 0 | VCString vsTokens; |
506 | 0 | sArgs.Split(" ", vsTokens, false); |
507 | |
|
508 | 0 | for (const CString& sCap : vsTokens) { |
509 | 0 | RemoveCap(sCap); |
510 | 0 | m_msCapLsValues.erase(sCap); |
511 | 0 | } |
512 | 0 | } |
513 | |
|
514 | 0 | if (bSendNext) { |
515 | 0 | SendNextCap(); |
516 | 0 | } |
517 | | // Don't forward any CAP stuff to the client |
518 | 0 | return true; |
519 | 0 | } |
520 | | |
521 | 0 | bool CIRCSock::OnCTCPMessage(CCTCPMessage& Message) { |
522 | 0 | bool bResult = false; |
523 | 0 | CChan* pChan = nullptr; |
524 | 0 | CString sTarget = Message.GetTarget(); |
525 | 0 | if (sTarget.Equals(GetNick())) { |
526 | 0 | if (Message.IsReply()) { |
527 | 0 | IRCSOCKMODULECALL(OnCTCPReplyMessage(Message), &bResult); |
528 | 0 | return bResult; |
529 | 0 | } else { |
530 | 0 | IRCSOCKMODULECALL(OnPrivCTCPMessage(Message), &bResult); |
531 | 0 | if (bResult) return true; |
532 | 0 | } |
533 | 0 | } else { |
534 | 0 | pChan = m_pNetwork->FindChan(sTarget); |
535 | 0 | if (pChan) { |
536 | 0 | Message.SetChan(pChan); |
537 | 0 | FixupChanNick(Message.GetNick(), pChan); |
538 | 0 | if (Message.IsReply()) { |
539 | 0 | IRCSOCKMODULECALL(OnCTCPReplyMessage(Message), &bResult); |
540 | 0 | return bResult; |
541 | 0 | } else { |
542 | 0 | IRCSOCKMODULECALL(OnChanCTCPMessage(Message), &bResult); |
543 | 0 | } |
544 | 0 | if (bResult) return true; |
545 | 0 | } |
546 | 0 | } |
547 | | |
548 | 0 | const CNick& Nick = Message.GetNick(); |
549 | 0 | const CString& sMessage = Message.GetText(); |
550 | 0 | const MCString& mssCTCPReplies = m_pNetwork->GetUser()->GetCTCPReplies(); |
551 | 0 | CString sQuery = sMessage.Token(0).AsUpper(); |
552 | 0 | MCString::const_iterator it = mssCTCPReplies.find(sQuery); |
553 | 0 | bool bHaveReply = false; |
554 | 0 | CString sReply; |
555 | |
|
556 | 0 | if (it != mssCTCPReplies.end()) { |
557 | 0 | sReply = m_pNetwork->ExpandString(it->second); |
558 | 0 | bHaveReply = true; |
559 | |
|
560 | 0 | if (sReply.empty()) { |
561 | 0 | return true; |
562 | 0 | } |
563 | 0 | } |
564 | | |
565 | 0 | if (!bHaveReply && !m_pNetwork->IsUserAttached()) { |
566 | 0 | if (sQuery == "VERSION") { |
567 | 0 | sReply = CZNC::GetTag(false); |
568 | 0 | } else if (sQuery == "PING") { |
569 | 0 | sReply = sMessage.Token(1, true); |
570 | 0 | } |
571 | 0 | } |
572 | |
|
573 | 0 | if (!sReply.empty()) { |
574 | 0 | unsigned long long now = CUtils::GetMillTime(); |
575 | | // If the last CTCP is older than m_uCTCPFloodTime, reset the counter |
576 | 0 | if (m_lastCTCP + m_uCTCPFloodTime < now) m_uNumCTCP = 0; |
577 | 0 | m_lastCTCP = now; |
578 | | // If we are over the limit, don't reply to this CTCP |
579 | 0 | if (m_uNumCTCP >= m_uCTCPFloodCount) { |
580 | 0 | DEBUG("CTCP flood detected - not replying to query"); |
581 | 0 | return true; |
582 | 0 | } |
583 | 0 | m_uNumCTCP++; |
584 | |
|
585 | 0 | PutIRC("NOTICE " + Nick.GetNick() + " :\001" + sQuery + " " + sReply + |
586 | 0 | "\001"); |
587 | 0 | return true; |
588 | 0 | } |
589 | | |
590 | 0 | return (pChan && pChan->IsDetached()); |
591 | 0 | } |
592 | | |
593 | 0 | bool CIRCSock::OnChgHostMessage(CChgHostMessage& Message) { |
594 | | // Do not send chghost if all shared channels are detached. |
595 | 0 | if (!IsNickVisibleInAttachedChannels(Message.GetNick().GetNick())) { |
596 | 0 | return true; |
597 | 0 | } |
598 | | // The emulation of QUIT+JOIN would be cleaner inside CClient::PutClient() |
599 | | // but computation of new modes is difficult enough so that I don't want to |
600 | | // repeat it for every client |
601 | | // |
602 | | // TODO: make CNick store modes (v, o) instead of perm chars (+, @), that |
603 | | // would simplify this |
604 | 0 | bool bNeedEmulate = false; |
605 | 0 | for (CClient* pClient : m_pNetwork->GetClients()) { |
606 | 0 | if (pClient->HasChgHost()) { |
607 | 0 | pClient->PutClient(Message); |
608 | 0 | } else { |
609 | 0 | bNeedEmulate = true; |
610 | 0 | pClient->PutClient(CMessage(Message.GetNick(), "QUIT", |
611 | 0 | {"Changing hostname"}, |
612 | 0 | Message.GetTags())); |
613 | 0 | } |
614 | 0 | } |
615 | |
|
616 | 0 | CNick NewNick = Message.GetNick(); |
617 | 0 | NewNick.SetIdent(Message.GetNewIdent()); |
618 | 0 | NewNick.SetHost(Message.GetNewHost()); |
619 | |
|
620 | 0 | for (CChan* pChan : m_pNetwork->GetChans()) { |
621 | 0 | if (CNick* pNick = pChan->FindNick(Message.GetNick().GetNick())) { |
622 | 0 | pNick->SetIdent(Message.GetNewIdent()); |
623 | 0 | pNick->SetHost(Message.GetNewHost()); |
624 | 0 | } |
625 | |
|
626 | 0 | if (!bNeedEmulate) continue; |
627 | 0 | if (pChan->IsDisabled()) continue; |
628 | 0 | if (pChan->IsDetached()) continue; |
629 | | |
630 | 0 | if (CNick* pNick = pChan->FindNick(NewNick.GetNick())) { |
631 | 0 | VCString vsModeParams = {pChan->GetName(), "+"}; |
632 | 0 | for (char cPerm : pNick->GetPermStr()) { |
633 | 0 | char cMode = GetModeFromPerm(cPerm); |
634 | 0 | if (cMode) { |
635 | 0 | vsModeParams[1].append(1, cMode); |
636 | 0 | vsModeParams.push_back(NewNick.GetNick()); |
637 | 0 | } |
638 | 0 | } |
639 | |
|
640 | 0 | CTargetMessage ModeMsg; |
641 | 0 | ModeMsg.SetNick(CNick(":irc.znc.in")); |
642 | 0 | ModeMsg.SetTags(Message.GetTags()); |
643 | 0 | ModeMsg.SetCommand("MODE"); |
644 | 0 | ModeMsg.SetParams(std::move(vsModeParams)); |
645 | |
|
646 | 0 | for (CClient* pClient : m_pNetwork->GetClients()) { |
647 | 0 | if (!pClient->HasChgHost()) { |
648 | | // TODO: send account name and real name too, for |
649 | | // extended-join |
650 | 0 | pClient->PutClient(CMessage(NewNick, "JOIN", |
651 | 0 | {pChan->GetName()}, |
652 | 0 | Message.GetTags())); |
653 | 0 | if (ModeMsg.GetParams().size() > 2) { |
654 | 0 | pClient->PutClient(ModeMsg); |
655 | 0 | } |
656 | 0 | } |
657 | 0 | } |
658 | 0 | } |
659 | 0 | } |
660 | |
|
661 | 0 | return true; |
662 | 0 | } |
663 | | |
664 | 0 | bool CIRCSock::OnErrorMessage(CMessage& Message) { |
665 | | // ERROR :Closing Link: nick[24.24.24.24] (Excess Flood) |
666 | 0 | CString sError = Message.GetParam(0); |
667 | 0 | m_pNetwork->PutStatus(t_f("Error from server: {1}")(sError)); |
668 | 0 | return true; |
669 | 0 | } |
670 | | |
671 | 0 | bool CIRCSock::OnInviteMessage(CInviteMessage& Message) { |
672 | 0 | Message.SetChan(GetNetwork()->FindChan(Message.GetChannel())); |
673 | 0 | bool bResult = false; |
674 | 0 | IRCSOCKMODULECALL(OnInviteMessage(Message), &bResult); |
675 | 0 | if (bResult) return true; |
676 | 0 | CNick InvitedNick = Message.GetInvitedNick(); |
677 | 0 | if (InvitedNick.NickEquals(GetNick())) { |
678 | 0 | IRCSOCKMODULECALL(OnInvite(Message.GetNick(), Message.GetParam(1)), |
679 | 0 | &bResult); |
680 | 0 | } |
681 | 0 | return bResult; |
682 | 0 | } |
683 | | |
684 | 0 | bool CIRCSock::OnJoinMessage(CJoinMessage& Message) { |
685 | 0 | const CNick& Nick = Message.GetNick(); |
686 | 0 | CString sChan = Message.GetParam(0); |
687 | 0 | CChan* pChan = nullptr; |
688 | |
|
689 | 0 | if (Nick.NickEquals(GetNick())) { |
690 | 0 | m_pNetwork->AddChan(sChan, false); |
691 | 0 | pChan = m_pNetwork->FindChan(sChan); |
692 | 0 | if (pChan) { |
693 | 0 | pChan->Enable(); |
694 | 0 | pChan->SetIsOn(true); |
695 | 0 | PutIRC("MODE " + sChan); |
696 | 0 | } |
697 | 0 | } else { |
698 | 0 | pChan = m_pNetwork->FindChan(sChan); |
699 | 0 | } |
700 | |
|
701 | 0 | if (pChan) { |
702 | 0 | pChan->AddNick(Nick.GetNickMask()); |
703 | 0 | Message.SetChan(pChan); |
704 | 0 | IRCSOCKMODULECALL(OnJoinMessage(Message), NOTHING); |
705 | |
|
706 | 0 | if (pChan->IsDetached()) { |
707 | 0 | return true; |
708 | 0 | } |
709 | 0 | } |
710 | | |
711 | 0 | return false; |
712 | 0 | } |
713 | | |
714 | 0 | bool CIRCSock::OnKickMessage(CKickMessage& Message) { |
715 | 0 | CString sChan = Message.GetParam(0); |
716 | 0 | CString sKickedNick = Message.GetKickedNick(); |
717 | |
|
718 | 0 | CChan* pChan = m_pNetwork->FindChan(sChan); |
719 | |
|
720 | 0 | if (pChan) { |
721 | 0 | Message.SetChan(pChan); |
722 | 0 | IRCSOCKMODULECALL(OnKickMessage(Message), NOTHING); |
723 | | // do not remove the nick till after the OnKick call, so modules |
724 | | // can do Chan.FindNick or something to get more info. |
725 | 0 | pChan->RemNick(sKickedNick); |
726 | 0 | } |
727 | |
|
728 | 0 | if (GetNick().Equals(sKickedNick) && pChan) { |
729 | 0 | pChan->SetIsOn(false); |
730 | | |
731 | | // Don't try to rejoin! |
732 | 0 | pChan->Disable(); |
733 | 0 | } |
734 | |
|
735 | 0 | return (pChan && pChan->IsDetached()); |
736 | 0 | } |
737 | | |
738 | 0 | bool CIRCSock::OnModeMessage(CModeMessage& Message) { |
739 | 0 | const CNick& Nick = Message.GetNick(); |
740 | 0 | CString sTarget = Message.GetTarget(); |
741 | 0 | VCString vsModes = Message.GetModeParams(); |
742 | 0 | CString sModes = Message.GetModeList(); |
743 | |
|
744 | 0 | CChan* pChan = m_pNetwork->FindChan(sTarget); |
745 | 0 | if (pChan) { |
746 | 0 | pChan->ModeChange(sModes, vsModes, &Nick); |
747 | |
|
748 | 0 | if (pChan->IsDetached()) { |
749 | 0 | return true; |
750 | 0 | } |
751 | 0 | } else if (sTarget == m_Nick.GetNick()) { |
752 | 0 | bool bAdd = true; |
753 | | /* no module call defined (yet?) |
754 | | MODULECALL(OnRawUserMode(*pOpNick, *this, sModeArg, sArgs), |
755 | | m_pNetwork->GetUser(), nullptr, ); |
756 | | */ |
757 | 0 | for (unsigned int a = 0; a < sModes.size(); a++) { |
758 | 0 | const char& cMode = sModes[a]; |
759 | |
|
760 | 0 | if (cMode == '+') { |
761 | 0 | bAdd = true; |
762 | 0 | } else if (cMode == '-') { |
763 | 0 | bAdd = false; |
764 | 0 | } else { |
765 | 0 | if (bAdd) { |
766 | 0 | m_scUserModes.insert(cMode); |
767 | 0 | } else { |
768 | 0 | m_scUserModes.erase(cMode); |
769 | 0 | } |
770 | 0 | } |
771 | 0 | } |
772 | 0 | } |
773 | 0 | return false; |
774 | 0 | } |
775 | | |
776 | 0 | bool CIRCSock::OnNickMessage(CNickMessage& Message) { |
777 | 0 | const CNick& Nick = Message.GetNick(); |
778 | 0 | CString sNewNick = Message.GetNewNick(); |
779 | 0 | bool bIsVisible = false; |
780 | |
|
781 | 0 | vector<CChan*> vFoundChans; |
782 | 0 | const vector<CChan*>& vChans = m_pNetwork->GetChans(); |
783 | |
|
784 | 0 | for (CChan* pChan : vChans) { |
785 | 0 | if (pChan->ChangeNick(Nick.GetNick(), sNewNick)) { |
786 | 0 | vFoundChans.push_back(pChan); |
787 | |
|
788 | 0 | if (!pChan->IsDetached()) { |
789 | 0 | bIsVisible = true; |
790 | 0 | } |
791 | 0 | } |
792 | 0 | } |
793 | |
|
794 | 0 | if (Nick.NickEquals(GetNick())) { |
795 | | // We are changing our own nick, the clients always must see this! |
796 | 0 | bIsVisible = false; |
797 | 0 | SetNick(sNewNick); |
798 | 0 | m_pNetwork->PutUser(Message); |
799 | 0 | } |
800 | |
|
801 | 0 | IRCSOCKMODULECALL(OnNickMessage(Message, vFoundChans), NOTHING); |
802 | |
|
803 | 0 | return !bIsVisible; |
804 | 0 | } |
805 | | |
806 | 0 | bool CIRCSock::OnNoticeMessage(CNoticeMessage& Message) { |
807 | 0 | CString sTarget = Message.GetTarget(); |
808 | 0 | bool bResult = false; |
809 | |
|
810 | 0 | if (sTarget.Equals(GetNick())) { |
811 | 0 | IRCSOCKMODULECALL(OnPrivNoticeMessage(Message), &bResult); |
812 | 0 | if (bResult) return true; |
813 | | |
814 | 0 | if (!m_pNetwork->IsUserOnline()) { |
815 | | // If the user is detached, add to the buffer |
816 | 0 | CNoticeMessage Format; |
817 | 0 | Format.Clone(Message); |
818 | 0 | Format.SetNick(CNick(_NAMEDFMT(Message.GetNick().GetNickMask()))); |
819 | 0 | Format.SetTarget("{target}"); |
820 | 0 | Format.SetText("{text}"); |
821 | 0 | m_pNetwork->AddNoticeBuffer(Format, Message.GetText()); |
822 | 0 | } |
823 | |
|
824 | 0 | return false; |
825 | 0 | } else { |
826 | 0 | CChan* pChan = m_pNetwork->FindChan(sTarget); |
827 | 0 | if (pChan) { |
828 | 0 | Message.SetChan(pChan); |
829 | 0 | FixupChanNick(Message.GetNick(), pChan); |
830 | 0 | IRCSOCKMODULECALL(OnChanNoticeMessage(Message), &bResult); |
831 | 0 | if (bResult) return true; |
832 | | |
833 | 0 | if (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline() || |
834 | 0 | pChan->IsDetached()) { |
835 | 0 | CNoticeMessage Format; |
836 | 0 | Format.Clone(Message); |
837 | 0 | Format.SetNick(_NAMEDFMT(Message.GetNick().GetNickMask())); |
838 | 0 | Format.SetTarget(_NAMEDFMT(Message.GetTarget())); |
839 | 0 | Format.SetText("{text}"); |
840 | 0 | pChan->AddBuffer(Format, Message.GetText()); |
841 | 0 | } |
842 | 0 | } |
843 | | |
844 | 0 | return (pChan && pChan->IsDetached()); |
845 | 0 | } |
846 | 0 | } |
847 | | |
848 | 0 | static CMessage BufferMessage(const CNumericMessage& Message) { |
849 | 0 | CMessage Format(Message); |
850 | 0 | Format.SetNick(CNick(_NAMEDFMT(Message.GetNick().GetHostMask()))); |
851 | 0 | Format.SetParam(0, "{target}"); |
852 | 0 | unsigned uParams = Format.GetParams().size(); |
853 | 0 | for (unsigned int i = 1; i < uParams; ++i) { |
854 | 0 | Format.SetParam(i, _NAMEDFMT(Format.GetParam(i))); |
855 | 0 | } |
856 | 0 | return Format; |
857 | 0 | } |
858 | | |
859 | 0 | bool CIRCSock::OnNumericMessage(CNumericMessage& Message) { |
860 | 0 | const CString& sCmd = Message.GetCommand(); |
861 | 0 | CString sServer = Message.GetNick().GetHostMask(); |
862 | 0 | unsigned int uRaw = Message.GetCode(); |
863 | 0 | CString sNick = Message.GetParam(0); |
864 | |
|
865 | 0 | bool bResult = false; |
866 | 0 | IRCSOCKMODULECALL(OnNumericMessage(Message), &bResult); |
867 | 0 | if (bResult) return true; |
868 | | |
869 | 0 | switch (uRaw) { |
870 | 0 | case 1: { // :irc.server.com 001 nick :Welcome to the Internet Relay |
871 | 0 | if (m_bAuthed && sServer == "irc.znc.in") { |
872 | | // m_bAuthed == true => we already received another 001 => we |
873 | | // might be in a traffic loop |
874 | 0 | m_pNetwork->PutStatus(t_s( |
875 | 0 | "ZNC seems to be connected to itself, disconnecting...")); |
876 | 0 | Quit(); |
877 | 0 | return true; |
878 | 0 | } |
879 | | |
880 | 0 | m_pNetwork->SetIRCServer(sServer); |
881 | | // Now that we are connected, let nature take its course |
882 | 0 | SetTimeout(m_pNetwork->GetUser()->GetNoTrafficTimeout(), TMO_READ); |
883 | 0 | PutIRC("WHO " + sNick); |
884 | |
|
885 | 0 | m_bAuthed = true; |
886 | |
|
887 | 0 | const vector<CClient*>& vClients = m_pNetwork->GetClients(); |
888 | |
|
889 | 0 | for (CClient* pClient : vClients) { |
890 | 0 | CString sClientNick = pClient->GetNick(false); |
891 | |
|
892 | 0 | if (!sClientNick.Equals(sNick)) { |
893 | | // If they connected with a nick that doesn't match the one |
894 | | // we got on irc, then we need to update them |
895 | 0 | pClient->PutClient(":" + sClientNick + "!" + |
896 | 0 | m_Nick.GetIdent() + "@" + |
897 | 0 | m_Nick.GetHost() + " NICK :" + sNick); |
898 | 0 | } |
899 | 0 | } |
900 | |
|
901 | 0 | SetNick(sNick); |
902 | |
|
903 | 0 | m_pNetwork->PutStatus("Connected!"); |
904 | 0 | IRCSOCKMODULECALL(OnIRCConnected(), NOTHING); |
905 | |
|
906 | 0 | m_pNetwork->ClearRawBuffer(); |
907 | 0 | m_pNetwork->AddRawBuffer(BufferMessage(Message)); |
908 | |
|
909 | 0 | m_pNetwork->IRCConnected(); |
910 | |
|
911 | 0 | break; |
912 | 0 | } |
913 | 0 | case 5: |
914 | 0 | ParseISupport(Message); |
915 | 0 | m_pNetwork->UpdateExactRawBuffer(BufferMessage(Message)); |
916 | 0 | break; |
917 | 0 | case 10: { // :irc.server.com 010 nick <hostname> <port> :<info> |
918 | 0 | CString sHost = Message.GetParam(1); |
919 | 0 | CString sPort = Message.GetParam(2); |
920 | 0 | CString sInfo = Message.GetParam(3); |
921 | 0 | m_pNetwork->PutStatus( |
922 | 0 | t_f("Server {1} redirects us to {2}:{3} with reason: {4}")( |
923 | 0 | m_pNetwork->GetCurrentServer()->GetString(false), sHost, |
924 | 0 | sPort, sInfo)); |
925 | 0 | m_pNetwork->PutStatus( |
926 | 0 | t_s("Perhaps you want to add it as a new server.")); |
927 | | // Don't send server redirects to the client |
928 | 0 | return true; |
929 | 0 | } |
930 | 0 | case 2: |
931 | 0 | case 3: |
932 | 0 | case 4: |
933 | 0 | case 250: // highest connection count |
934 | 0 | case 251: // user count |
935 | 0 | case 252: // oper count |
936 | 0 | case 254: // channel count |
937 | 0 | case 255: // client count |
938 | 0 | case 265: // local users |
939 | 0 | case 266: // global users |
940 | 0 | m_pNetwork->UpdateRawBuffer(sCmd, BufferMessage(Message)); |
941 | 0 | break; |
942 | 0 | case 305: |
943 | 0 | m_pNetwork->SetIRCAway(false); |
944 | 0 | break; |
945 | 0 | case 306: |
946 | 0 | m_pNetwork->SetIRCAway(true); |
947 | 0 | break; |
948 | 0 | case 324: { // MODE |
949 | | // :irc.server.com 324 nick #chan +nstk key |
950 | 0 | CChan* pChan = m_pNetwork->FindChan(Message.GetParam(1)); |
951 | |
|
952 | 0 | if (pChan) { |
953 | 0 | pChan->SetModes(Message.GetParam(2), Message.GetParamsSplit(3)); |
954 | | |
955 | | // We don't SetModeKnown(true) here, |
956 | | // because a 329 will follow |
957 | 0 | if (!pChan->IsModeKnown()) { |
958 | | // When we JOIN, we send a MODE |
959 | | // request. This makes sure the |
960 | | // reply isn't forwarded. |
961 | 0 | return true; |
962 | 0 | } |
963 | 0 | if (pChan->IsDetached()) { |
964 | 0 | return true; |
965 | 0 | } |
966 | 0 | } |
967 | 0 | } break; |
968 | 0 | case 329: { |
969 | | // :irc.server.com 329 nick #chan 1234567890 |
970 | 0 | CChan* pChan = m_pNetwork->FindChan(Message.GetParam(1)); |
971 | |
|
972 | 0 | if (pChan) { |
973 | 0 | unsigned long ulDate = Message.GetParam(2).ToULong(); |
974 | 0 | pChan->SetCreationDate(ulDate); |
975 | |
|
976 | 0 | if (!pChan->IsModeKnown()) { |
977 | 0 | pChan->SetModeKnown(true); |
978 | | // When we JOIN, we send a MODE |
979 | | // request. This makes sure the |
980 | | // reply isn't forwarded. |
981 | 0 | return true; |
982 | 0 | } |
983 | 0 | if (pChan->IsDetached()) { |
984 | 0 | return true; |
985 | 0 | } |
986 | 0 | } |
987 | 0 | } break; |
988 | 0 | case 331: { |
989 | | // :irc.server.com 331 yournick #chan :No topic is set. |
990 | 0 | CChan* pChan = m_pNetwork->FindChan(Message.GetParam(1)); |
991 | |
|
992 | 0 | if (pChan) { |
993 | 0 | pChan->SetTopic(""); |
994 | 0 | if (pChan->IsDetached()) { |
995 | 0 | return true; |
996 | 0 | } |
997 | 0 | } |
998 | | |
999 | 0 | break; |
1000 | 0 | } |
1001 | 0 | case 332: { |
1002 | | // :irc.server.com 332 yournick #chan :This is a topic |
1003 | 0 | CChan* pChan = m_pNetwork->FindChan(Message.GetParam(1)); |
1004 | |
|
1005 | 0 | if (pChan) { |
1006 | 0 | CString sTopic = Message.GetParam(2); |
1007 | 0 | pChan->SetTopic(sTopic); |
1008 | 0 | if (pChan->IsDetached()) { |
1009 | 0 | return true; |
1010 | 0 | } |
1011 | 0 | } |
1012 | | |
1013 | 0 | break; |
1014 | 0 | } |
1015 | 0 | case 333: { |
1016 | | // :irc.server.com 333 yournick #chan setternick 1112320796 |
1017 | 0 | CChan* pChan = m_pNetwork->FindChan(Message.GetParam(1)); |
1018 | |
|
1019 | 0 | if (pChan) { |
1020 | 0 | sNick = Message.GetParam(2); |
1021 | 0 | unsigned long ulDate = Message.GetParam(3).ToULong(); |
1022 | |
|
1023 | 0 | pChan->SetTopicOwner(sNick); |
1024 | 0 | pChan->SetTopicDate(ulDate); |
1025 | |
|
1026 | 0 | if (pChan->IsDetached()) { |
1027 | 0 | return true; |
1028 | 0 | } |
1029 | 0 | } |
1030 | | |
1031 | 0 | break; |
1032 | 0 | } |
1033 | 0 | case 352: { // WHO |
1034 | | // :irc.yourserver.com 352 yournick #chan ident theirhost.com irc.theirserver.com theirnick H :0 Real Name |
1035 | 0 | sNick = Message.GetParam(5); |
1036 | 0 | CString sChan = Message.GetParam(1); |
1037 | 0 | CString sIdent = Message.GetParam(2); |
1038 | 0 | CString sHost = Message.GetParam(3); |
1039 | |
|
1040 | 0 | if (sNick.Equals(GetNick())) { |
1041 | 0 | m_Nick.SetIdent(sIdent); |
1042 | 0 | m_Nick.SetHost(sHost); |
1043 | 0 | } |
1044 | |
|
1045 | 0 | m_pNetwork->SetIRCNick(m_Nick); |
1046 | 0 | m_pNetwork->SetIRCServer(sServer); |
1047 | | |
1048 | | // A nick can only have one ident and hostname. Yes, you can query |
1049 | | // this information per-channel, but it is still global. For |
1050 | | // example, if the client supports UHNAMES, but the IRC server does |
1051 | | // not, then AFAIR "passive snooping of WHO replies" is the only way |
1052 | | // that ZNC can figure out the ident and host for the UHNAMES |
1053 | | // replies. |
1054 | 0 | const vector<CChan*>& vChans = m_pNetwork->GetChans(); |
1055 | |
|
1056 | 0 | for (CChan* pChan : vChans) { |
1057 | 0 | pChan->OnWho(sNick, sIdent, sHost); |
1058 | 0 | } |
1059 | |
|
1060 | 0 | CChan* pChan = m_pNetwork->FindChan(sChan); |
1061 | 0 | if (pChan && pChan->IsDetached()) { |
1062 | 0 | return true; |
1063 | 0 | } |
1064 | | |
1065 | 0 | break; |
1066 | 0 | } |
1067 | 0 | case 353: { // NAMES |
1068 | | // :irc.server.com 353 nick @ #chan :nick1 nick2 |
1069 | | // Todo: allow for non @+= server msgs |
1070 | 0 | CChan* pChan = m_pNetwork->FindChan(Message.GetParam(2)); |
1071 | | // If we don't know that channel, some client might have |
1072 | | // requested a /names for it and we really should forward this. |
1073 | 0 | if (pChan) { |
1074 | 0 | CString sNicks = Message.GetParam(3); |
1075 | 0 | pChan->AddNicks(sNicks); |
1076 | 0 | if (pChan->IsDetached()) { |
1077 | 0 | return true; |
1078 | 0 | } |
1079 | 0 | } |
1080 | | |
1081 | 0 | break; |
1082 | 0 | } |
1083 | 0 | case 366: { // end of names list |
1084 | | // :irc.server.com 366 nick #chan :End of /NAMES list. |
1085 | 0 | CChan* pChan = m_pNetwork->FindChan(Message.GetParam(1)); |
1086 | |
|
1087 | 0 | if (pChan) { |
1088 | 0 | if (pChan->IsOn()) { |
1089 | | // If we are the only one in the chan, set our default modes |
1090 | 0 | if (pChan->GetNickCount() == 1) { |
1091 | 0 | CString sModes = pChan->GetDefaultModes(); |
1092 | |
|
1093 | 0 | if (sModes.empty()) { |
1094 | 0 | sModes = |
1095 | 0 | m_pNetwork->GetUser()->GetDefaultChanModes(); |
1096 | 0 | } |
1097 | |
|
1098 | 0 | if (!sModes.empty()) { |
1099 | 0 | PutIRC("MODE " + pChan->GetName() + " " + sModes); |
1100 | 0 | } |
1101 | 0 | } |
1102 | 0 | } |
1103 | 0 | if (pChan->IsDetached()) { |
1104 | | // don't put it to clients |
1105 | 0 | return true; |
1106 | 0 | } |
1107 | 0 | } |
1108 | | |
1109 | 0 | break; |
1110 | 0 | } |
1111 | 0 | case 375: // begin motd |
1112 | 0 | case 422: // MOTD File is missing |
1113 | 0 | if (m_pNetwork->GetIRCServer().Equals(sServer)) { |
1114 | 0 | m_pNetwork->ClearMotdBuffer(); |
1115 | 0 | } |
1116 | 0 | case 372: // motd |
1117 | 0 | case 376: // end motd |
1118 | 0 | if (m_pNetwork->GetIRCServer().Equals(sServer)) { |
1119 | 0 | m_pNetwork->AddMotdBuffer(BufferMessage(Message)); |
1120 | 0 | } |
1121 | 0 | break; |
1122 | 0 | case 437: |
1123 | | // :irc.server.net 437 * badnick :Nick/channel is temporarily unavailable |
1124 | | // :irc.server.net 437 mynick badnick :Nick/channel is temporarily unavailable |
1125 | | // :irc.server.net 437 mynick badnick :Cannot change nickname while banned on channel |
1126 | 0 | if (m_pNetwork->IsChan(Message.GetParam(1)) || sNick != "*") break; |
1127 | 0 | case 432: |
1128 | | // :irc.server.com 432 * nick :Erroneous Nickname: Illegal chars |
1129 | 0 | case 433: { |
1130 | 0 | CString sBadNick = Message.GetParam(1); |
1131 | |
|
1132 | 0 | if (!m_bAuthed) { |
1133 | 0 | SendAltNick(sBadNick); |
1134 | 0 | return true; |
1135 | 0 | } |
1136 | 0 | break; |
1137 | 0 | } |
1138 | 0 | case 451: |
1139 | | // :irc.server.com 451 CAP :You have not registered |
1140 | | // Servers that don't support CAP will give us this error, don't send |
1141 | | // it to the client |
1142 | 0 | if (sNick.Equals("CAP")) return true; |
1143 | 0 | case 470: { |
1144 | | // :irc.unreal.net 470 mynick [Link] #chan1 has become full, so you are automatically being transferred to the linked channel #chan2 |
1145 | | // :mccaffrey.freenode.net 470 mynick #electronics ##electronics :Forwarding to another channel |
1146 | | |
1147 | | // freenode style numeric |
1148 | 0 | CChan* pChan = m_pNetwork->FindChan(Message.GetParam(1)); |
1149 | 0 | if (!pChan) { |
1150 | | // unreal style numeric |
1151 | 0 | pChan = m_pNetwork->FindChan(Message.GetParam(2)); |
1152 | 0 | } |
1153 | 0 | if (pChan) { |
1154 | 0 | pChan->Disable(); |
1155 | 0 | m_pNetwork->PutStatus( |
1156 | 0 | t_f("Channel {1} is linked to another channel and was thus " |
1157 | 0 | "disabled.")(pChan->GetName())); |
1158 | 0 | } |
1159 | 0 | break; |
1160 | 0 | } |
1161 | 0 | case 670: |
1162 | | // :hydra.sector5d.org 670 kylef :STARTTLS successful, go ahead with TLS handshake |
1163 | | // |
1164 | | // 670 is a response to `STARTTLS` telling the client to switch to |
1165 | | // TLS |
1166 | 0 | if (!GetSSL()) { |
1167 | 0 | StartTLS(); |
1168 | 0 | m_pNetwork->PutStatus(t_s("Switched to SSL (STARTTLS)")); |
1169 | 0 | } |
1170 | |
|
1171 | 0 | return true; |
1172 | 0 | } |
1173 | | |
1174 | 0 | return false; |
1175 | 0 | } |
1176 | | |
1177 | 0 | bool CIRCSock::OnPartMessage(CPartMessage& Message) { |
1178 | 0 | const CNick& Nick = Message.GetNick(); |
1179 | 0 | CString sChan = Message.GetTarget(); |
1180 | |
|
1181 | 0 | CChan* pChan = m_pNetwork->FindChan(sChan); |
1182 | 0 | bool bDetached = false; |
1183 | 0 | if (pChan) { |
1184 | 0 | pChan->RemNick(Nick.GetNick()); |
1185 | 0 | Message.SetChan(pChan); |
1186 | 0 | IRCSOCKMODULECALL(OnPartMessage(Message), NOTHING); |
1187 | |
|
1188 | 0 | if (pChan->IsDetached()) bDetached = true; |
1189 | 0 | } |
1190 | |
|
1191 | 0 | if (Nick.NickEquals(GetNick())) { |
1192 | 0 | m_pNetwork->DelChan(sChan); |
1193 | 0 | } |
1194 | | |
1195 | | /* |
1196 | | * We use this boolean because |
1197 | | * m_pNetwork->DelChan() will delete this channel |
1198 | | * and thus we would dereference an |
1199 | | * already-freed pointer! |
1200 | | */ |
1201 | 0 | return bDetached; |
1202 | 0 | } |
1203 | | |
1204 | 0 | bool CIRCSock::OnPingMessage(CMessage& Message) { |
1205 | | // Generate a reply and don't forward this to any user, |
1206 | | // we don't want any PING forwarded |
1207 | 0 | PutIRCQuick("PONG " + Message.GetParam(0)); |
1208 | 0 | return true; |
1209 | 0 | } |
1210 | | |
1211 | 0 | bool CIRCSock::OnPongMessage(CMessage& Message) { |
1212 | | // Block PONGs, we already responded to the pings |
1213 | 0 | return true; |
1214 | 0 | } |
1215 | | |
1216 | 0 | bool CIRCSock::OnQuitMessage(CQuitMessage& Message) { |
1217 | 0 | const CNick& Nick = Message.GetNick(); |
1218 | 0 | bool bIsVisible = false; |
1219 | |
|
1220 | 0 | if (Nick.NickEquals(GetNick())) { |
1221 | 0 | m_pNetwork->PutStatus(t_f("You quit: {1}")(Message.GetReason())); |
1222 | | // We don't call module hooks and we don't |
1223 | | // forward this quit to clients (Some clients |
1224 | | // disconnect if they receive such a QUIT) |
1225 | 0 | return true; |
1226 | 0 | } |
1227 | | |
1228 | 0 | vector<CChan*> vFoundChans; |
1229 | 0 | const vector<CChan*>& vChans = m_pNetwork->GetChans(); |
1230 | |
|
1231 | 0 | for (CChan* pChan : vChans) { |
1232 | 0 | if (pChan->RemNick(Nick.GetNick())) { |
1233 | 0 | vFoundChans.push_back(pChan); |
1234 | |
|
1235 | 0 | if (!pChan->IsDetached()) { |
1236 | 0 | bIsVisible = true; |
1237 | 0 | } |
1238 | 0 | } |
1239 | 0 | } |
1240 | |
|
1241 | 0 | IRCSOCKMODULECALL(OnQuitMessage(Message, vFoundChans), NOTHING); |
1242 | |
|
1243 | 0 | return !bIsVisible; |
1244 | 0 | } |
1245 | | |
1246 | 0 | bool CIRCSock::OnTagMessage(CTargetMessage& Message) { |
1247 | 0 | bool bResult = false; |
1248 | 0 | CChan* pChan = nullptr; |
1249 | 0 | CString sTarget = Message.GetTarget(); |
1250 | |
|
1251 | 0 | if (sTarget.Equals(GetNick())) { |
1252 | 0 | IRCSOCKMODULECALL(OnPrivTagMessage(Message), &bResult); |
1253 | 0 | if (bResult) return true; |
1254 | | |
1255 | 0 | if (!m_pNetwork->IsUserOnline() || |
1256 | 0 | !m_pNetwork->GetUser()->AutoClearQueryBuffer()) { |
1257 | 0 | const CNick& Nick = Message.GetNick(); |
1258 | 0 | CQuery* pQuery = m_pNetwork->AddQuery(Nick.GetNick()); |
1259 | 0 | if (pQuery) { |
1260 | 0 | CTargetMessage Format; |
1261 | 0 | Format.Clone(Message); |
1262 | 0 | Format.SetNick(_NAMEDFMT(Nick.GetNickMask())); |
1263 | 0 | Format.SetTarget("{target}"); |
1264 | 0 | pQuery->AddBuffer(Format); |
1265 | 0 | } |
1266 | 0 | } |
1267 | 0 | } else { |
1268 | 0 | pChan = m_pNetwork->FindChan(sTarget); |
1269 | 0 | if (pChan) { |
1270 | 0 | Message.SetChan(pChan); |
1271 | 0 | FixupChanNick(Message.GetNick(), pChan); |
1272 | 0 | IRCSOCKMODULECALL(OnChanTagMessage(Message), &bResult); |
1273 | 0 | if (bResult) return true; |
1274 | | |
1275 | 0 | if (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline() || |
1276 | 0 | pChan->IsDetached()) { |
1277 | 0 | CTargetMessage Format; |
1278 | 0 | Format.Clone(Message); |
1279 | 0 | Format.SetNick(_NAMEDFMT(Message.GetNick().GetNickMask())); |
1280 | 0 | Format.SetTarget(_NAMEDFMT(Message.GetTarget())); |
1281 | 0 | pChan->AddBuffer(Format); |
1282 | 0 | } |
1283 | 0 | } |
1284 | 0 | } |
1285 | | |
1286 | 0 | return (pChan && pChan->IsDetached()); |
1287 | 0 | } |
1288 | | |
1289 | 0 | bool CIRCSock::OnTextMessage(CTextMessage& Message) { |
1290 | 0 | bool bResult = false; |
1291 | 0 | CChan* pChan = nullptr; |
1292 | 0 | CString sTarget = Message.GetTarget(); |
1293 | |
|
1294 | 0 | if (sTarget.Equals(GetNick())) { |
1295 | 0 | IRCSOCKMODULECALL(OnPrivTextMessage(Message), &bResult); |
1296 | 0 | if (bResult) return true; |
1297 | | |
1298 | 0 | if (!m_pNetwork->IsUserOnline() || |
1299 | 0 | !m_pNetwork->GetUser()->AutoClearQueryBuffer()) { |
1300 | 0 | const CNick& Nick = Message.GetNick(); |
1301 | 0 | CQuery* pQuery = m_pNetwork->AddQuery(Nick.GetNick()); |
1302 | 0 | if (pQuery) { |
1303 | 0 | CTextMessage Format; |
1304 | 0 | Format.Clone(Message); |
1305 | 0 | Format.SetNick(_NAMEDFMT(Nick.GetNickMask())); |
1306 | 0 | Format.SetTarget("{target}"); |
1307 | 0 | Format.SetText("{text}"); |
1308 | 0 | pQuery->AddBuffer(Format, Message.GetText()); |
1309 | 0 | } |
1310 | 0 | } |
1311 | 0 | } else { |
1312 | 0 | pChan = m_pNetwork->FindChan(sTarget); |
1313 | 0 | if (pChan) { |
1314 | 0 | Message.SetChan(pChan); |
1315 | 0 | FixupChanNick(Message.GetNick(), pChan); |
1316 | 0 | IRCSOCKMODULECALL(OnChanTextMessage(Message), &bResult); |
1317 | 0 | if (bResult) return true; |
1318 | | |
1319 | 0 | if (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline() || |
1320 | 0 | pChan->IsDetached()) { |
1321 | 0 | CTextMessage Format; |
1322 | 0 | Format.Clone(Message); |
1323 | 0 | Format.SetNick(_NAMEDFMT(Message.GetNick().GetNickMask())); |
1324 | 0 | Format.SetTarget(_NAMEDFMT(Message.GetTarget())); |
1325 | 0 | Format.SetText("{text}"); |
1326 | 0 | pChan->AddBuffer(Format, Message.GetText()); |
1327 | 0 | } |
1328 | 0 | } |
1329 | 0 | } |
1330 | | |
1331 | 0 | return (pChan && pChan->IsDetached()); |
1332 | 0 | } |
1333 | | |
1334 | 0 | bool CIRCSock::OnTopicMessage(CTopicMessage& Message) { |
1335 | 0 | const CNick& Nick = Message.GetNick(); |
1336 | 0 | CChan* pChan = m_pNetwork->FindChan(Message.GetParam(0)); |
1337 | |
|
1338 | 0 | if (pChan) { |
1339 | 0 | Message.SetChan(pChan); |
1340 | 0 | bool bReturn = false; |
1341 | 0 | IRCSOCKMODULECALL(OnTopicMessage(Message), &bReturn); |
1342 | 0 | if (bReturn) return true; |
1343 | | |
1344 | 0 | pChan->SetTopicOwner(Nick.GetNick()); |
1345 | 0 | pChan->SetTopicDate((unsigned long)time(nullptr)); |
1346 | 0 | pChan->SetTopic(Message.GetTopic()); |
1347 | 0 | } |
1348 | | |
1349 | 0 | return (pChan && pChan->IsDetached()); |
1350 | 0 | } |
1351 | | |
1352 | 0 | bool CIRCSock::OnWallopsMessage(CMessage& Message) { |
1353 | | // :blub!dummy@rox-8DBEFE92 WALLOPS :this is a test |
1354 | 0 | CString sMsg = Message.GetParam(0); |
1355 | |
|
1356 | 0 | if (!m_pNetwork->IsUserOnline()) { |
1357 | 0 | CMessage Format(Message); |
1358 | 0 | Format.SetNick(CNick(_NAMEDFMT(Message.GetNick().GetHostMask()))); |
1359 | 0 | Format.SetParam(0, "{text}"); |
1360 | 0 | m_pNetwork->AddNoticeBuffer(Format, sMsg); |
1361 | 0 | } |
1362 | 0 | return false; |
1363 | 0 | } |
1364 | | |
1365 | 0 | void CIRCSock::PutIRC(const CString& sLine) { |
1366 | 0 | PutIRC(CMessage(sLine)); |
1367 | 0 | } |
1368 | | |
1369 | 0 | void CIRCSock::PutIRC(const CMessage& Message) { |
1370 | | // Only print if the line won't get sent immediately (same condition as in |
1371 | | // TrySend()!) |
1372 | 0 | if (m_bFloodProtection && m_iSendsAllowed <= 0) { |
1373 | 0 | DEBUG("(" << m_pNetwork->GetUser()->GetUsername() << "/" |
1374 | 0 | << m_pNetwork->GetName() << ") ZNC -> IRC [" |
1375 | 0 | << CDebug::Filter(Message.ToString()) << "] (queued)"); |
1376 | 0 | } |
1377 | 0 | m_vSendQueue.push_back(Message); |
1378 | 0 | TrySend(); |
1379 | 0 | } |
1380 | | |
1381 | 0 | void CIRCSock::PutIRCQuick(const CString& sLine) { |
1382 | | // Only print if the line won't get sent immediately (same condition as in |
1383 | | // TrySend()!) |
1384 | 0 | if (m_bFloodProtection && m_iSendsAllowed <= 0) { |
1385 | 0 | DEBUG("(" << m_pNetwork->GetUser()->GetUsername() << "/" |
1386 | 0 | << m_pNetwork->GetName() << ") ZNC -> IRC [" |
1387 | 0 | << CDebug::Filter(sLine) << "] (queued to front)"); |
1388 | 0 | } |
1389 | 0 | m_vSendQueue.emplace_front(sLine); |
1390 | 0 | TrySend(); |
1391 | 0 | } |
1392 | | |
1393 | 0 | void CIRCSock::TrySend() { |
1394 | | // This condition must be the same as in PutIRC() and PutIRCQuick()! |
1395 | 0 | while (!m_vSendQueue.empty() && |
1396 | 0 | (!m_bFloodProtection || m_iSendsAllowed > 0)) { |
1397 | 0 | m_iSendsAllowed--; |
1398 | 0 | CMessage& Message = m_vSendQueue.front(); |
1399 | |
|
1400 | 0 | if (!m_bMessageTagCap) { |
1401 | 0 | MCString mssTags; |
1402 | 0 | for (const auto& it : Message.GetTags()) { |
1403 | 0 | if (IsTagEnabled(it.first)) { |
1404 | 0 | mssTags[it.first] = it.second; |
1405 | 0 | } |
1406 | 0 | } |
1407 | 0 | Message.SetTags(mssTags); |
1408 | 0 | } |
1409 | 0 | Message.SetNetwork(m_pNetwork); |
1410 | |
|
1411 | 0 | bool bSkip = false; |
1412 | 0 | IRCSOCKMODULECALL(OnSendToIRCMessage(Message), &bSkip); |
1413 | |
|
1414 | 0 | if (!bSkip) { |
1415 | 0 | PutIRCRaw(Message.ToString()); |
1416 | 0 | } |
1417 | 0 | m_vSendQueue.pop_front(); |
1418 | |
|
1419 | 0 | if (m_vSendQueue.size() * m_fFloodRate > 600) { |
1420 | 0 | unsigned long long now = CUtils::GetMillTime(); |
1421 | | // Warn no more often than once every 2 minutes |
1422 | 0 | if (now > m_lastFloodWarned + 2 * 60'000) { |
1423 | 0 | m_lastFloodWarned = now; |
1424 | 0 | this->GetNetwork()->PutStatus( |
1425 | 0 | t_f("Warning: flood protection is delaying your messages " |
1426 | 0 | "by {1} seconds")(m_vSendQueue.size() * m_fFloodRate)); |
1427 | 0 | } |
1428 | 0 | } |
1429 | 0 | } |
1430 | 0 | } |
1431 | | |
1432 | 0 | void CIRCSock::PutIRCRaw(const CString& sLine) { |
1433 | 0 | CString sCopy = sLine; |
1434 | 0 | bool bSkip = false; |
1435 | 0 | IRCSOCKMODULECALL(OnSendToIRC(sCopy), &bSkip); |
1436 | 0 | if (!bSkip) { |
1437 | 0 | DEBUG("(" << m_pNetwork->GetUser()->GetUsername() << "/" |
1438 | 0 | << m_pNetwork->GetName() << ") ZNC -> IRC [" |
1439 | 0 | << CDebug::Filter(sCopy) << "]"); |
1440 | 0 | Write(sCopy + "\r\n"); |
1441 | 0 | } |
1442 | 0 | } |
1443 | | |
1444 | 0 | void CIRCSock::SetNick(const CString& sNick) { |
1445 | 0 | m_Nick.SetNick(sNick); |
1446 | 0 | m_pNetwork->SetIRCNick(m_Nick); |
1447 | 0 | } |
1448 | | |
1449 | 0 | void CIRCSock::Connected() { |
1450 | 0 | DEBUG(GetSockName() << " == Connected()"); |
1451 | |
|
1452 | 0 | CString sPass = m_sPass; |
1453 | 0 | CString sNick = m_pNetwork->GetNick(); |
1454 | 0 | CString sIdent = m_pNetwork->GetIdent(); |
1455 | 0 | CString sRealName = m_pNetwork->GetRealName(); |
1456 | |
|
1457 | 0 | bool bReturn = false; |
1458 | 0 | IRCSOCKMODULECALL(OnIRCRegistration(sPass, sNick, sIdent, sRealName), |
1459 | 0 | &bReturn); |
1460 | 0 | if (bReturn) return; |
1461 | | |
1462 | 0 | PutIRC("CAP LS 302"); |
1463 | |
|
1464 | 0 | if (!sPass.empty()) { |
1465 | 0 | PutIRC(CMessage(CNick(), "PASS", {sPass})); |
1466 | 0 | } |
1467 | |
|
1468 | 0 | PutIRC("NICK " + sNick); |
1469 | 0 | PutIRC("USER " + sIdent + " \"" + sIdent + "\" \"" + sIdent + "\" :" + |
1470 | 0 | sRealName); |
1471 | | |
1472 | | // SendAltNick() needs this |
1473 | 0 | m_Nick.SetNick(sNick); |
1474 | 0 | } |
1475 | | |
1476 | 0 | void CIRCSock::Disconnected() { |
1477 | 0 | IRCSOCKMODULECALL(OnIRCDisconnected(), NOTHING); |
1478 | |
|
1479 | 0 | DEBUG(GetSockName() << " == Disconnected()"); |
1480 | 0 | if (!m_pNetwork->GetUser()->IsBeingDeleted() && |
1481 | 0 | m_pNetwork->GetIRCConnectEnabled() && |
1482 | 0 | m_pNetwork->GetServers().size() != 0) { |
1483 | 0 | m_pNetwork->PutStatus(t_s("Disconnected from IRC. Reconnecting...")); |
1484 | 0 | } |
1485 | 0 | m_pNetwork->ClearRawBuffer(); |
1486 | 0 | m_pNetwork->ClearMotdBuffer(); |
1487 | |
|
1488 | 0 | ResetChans(); |
1489 | | |
1490 | | // send a "reset user modes" cmd to the client. |
1491 | | // otherwise, on reconnect, it might think it still |
1492 | | // had user modes that it actually doesn't have. |
1493 | 0 | CString sUserMode; |
1494 | 0 | for (char cMode : m_scUserModes) { |
1495 | 0 | sUserMode += cMode; |
1496 | 0 | } |
1497 | 0 | if (!sUserMode.empty()) { |
1498 | 0 | m_pNetwork->PutUser(":" + m_pNetwork->GetIRCNick().GetNickMask() + |
1499 | 0 | " MODE " + m_pNetwork->GetIRCNick().GetNick() + |
1500 | 0 | " :-" + sUserMode); |
1501 | 0 | } |
1502 | | |
1503 | | // also clear the user modes in our space: |
1504 | 0 | m_scUserModes.clear(); |
1505 | 0 | } |
1506 | | |
1507 | 0 | void CIRCSock::SockError(int iErrno, const CString& sDescription) { |
1508 | 0 | CString sError = sDescription; |
1509 | |
|
1510 | 0 | DEBUG(GetSockName() << " == SockError(" << iErrno << " " << sError << ")"); |
1511 | 0 | if (!m_pNetwork->GetUser()->IsBeingDeleted()) { |
1512 | 0 | if (GetConState() != CST_OK) { |
1513 | 0 | m_pNetwork->PutStatus( |
1514 | 0 | t_f("Cannot connect to IRC ({1}). Retrying...")(sError)); |
1515 | 0 | } else { |
1516 | 0 | m_pNetwork->PutStatus( |
1517 | 0 | t_f("Disconnected from IRC ({1}). Reconnecting...")(sError)); |
1518 | 0 | } |
1519 | 0 | } |
1520 | 0 | for (const CString& s : m_vsSSLError) { |
1521 | 0 | m_pNetwork->PutStatus(s); |
1522 | 0 | } |
1523 | 0 | m_pNetwork->ClearRawBuffer(); |
1524 | 0 | m_pNetwork->ClearMotdBuffer(); |
1525 | |
|
1526 | 0 | ResetChans(); |
1527 | 0 | m_scUserModes.clear(); |
1528 | 0 | } |
1529 | | |
1530 | | #ifdef HAVE_LIBSSL |
1531 | | void CIRCSock::SSLCertError(X509* pCert) { |
1532 | | BIO* mem = BIO_new(BIO_s_mem()); |
1533 | | X509_print(mem, pCert); |
1534 | | char* pCertStr = nullptr; |
1535 | | long iLen = BIO_get_mem_data(mem, &pCertStr); |
1536 | | CString sCert(pCertStr, iLen); |
1537 | | BIO_free(mem); |
1538 | | |
1539 | | VCString vsCert; |
1540 | | sCert.Split("\n", vsCert); |
1541 | | for (const CString& s : vsCert) { |
1542 | | // It shouldn't contain any bad characters, but let's be |
1543 | | // safe... |
1544 | | m_vsSSLError.push_back("|" + s.Escape_n(CString::EDEBUG)); |
1545 | | } |
1546 | | CString sSHA1; |
1547 | | if (GetPeerFingerprint(sSHA1)) |
1548 | | m_vsSSLError.push_back( |
1549 | | "SHA1: " + sSHA1.Escape_n(CString::EHEXCOLON, CString::EHEXCOLON)); |
1550 | | CString sSHA256 = GetSSLPeerFingerprint(pCert); |
1551 | | m_vsSSLError.push_back("SHA-256: " + sSHA256); |
1552 | | m_vsSSLError.push_back( |
1553 | | t_f("If you trust this certificate, do /znc " |
1554 | | "AddTrustedServerFingerprint {1}")(sSHA256)); |
1555 | | } |
1556 | | #endif |
1557 | | |
1558 | 0 | void CIRCSock::Timeout() { |
1559 | 0 | DEBUG(GetSockName() << " == Timeout()"); |
1560 | 0 | if (!m_pNetwork->GetUser()->IsBeingDeleted()) { |
1561 | 0 | m_pNetwork->PutStatus( |
1562 | 0 | t_s("IRC connection timed out. Reconnecting...")); |
1563 | 0 | } |
1564 | 0 | m_pNetwork->ClearRawBuffer(); |
1565 | 0 | m_pNetwork->ClearMotdBuffer(); |
1566 | |
|
1567 | 0 | ResetChans(); |
1568 | 0 | m_scUserModes.clear(); |
1569 | 0 | } |
1570 | | |
1571 | 0 | void CIRCSock::ConnectionRefused() { |
1572 | 0 | DEBUG(GetSockName() << " == ConnectionRefused()"); |
1573 | 0 | if (!m_pNetwork->GetUser()->IsBeingDeleted()) { |
1574 | 0 | m_pNetwork->PutStatus(t_s("Connection Refused. Reconnecting...")); |
1575 | 0 | } |
1576 | 0 | m_pNetwork->ClearRawBuffer(); |
1577 | 0 | m_pNetwork->ClearMotdBuffer(); |
1578 | 0 | } |
1579 | | |
1580 | 0 | void CIRCSock::ReachedMaxBuffer() { |
1581 | 0 | DEBUG(GetSockName() << " == ReachedMaxBuffer()"); |
1582 | 0 | m_pNetwork->PutStatus(t_s("Received a too long line from the IRC server!")); |
1583 | 0 | Quit(); |
1584 | 0 | } |
1585 | | |
1586 | 0 | void CIRCSock::ParseISupport(const CMessage& Message) { |
1587 | 0 | const VCString vsParams = Message.GetParams(); |
1588 | |
|
1589 | 0 | for (size_t i = 1; i + 1 < vsParams.size(); ++i) { |
1590 | 0 | const CString& sParam = vsParams[i]; |
1591 | 0 | CString sName = sParam.Token(0, false, "="); |
1592 | 0 | CString sValue = sParam.Token(1, true, "="); |
1593 | |
|
1594 | 0 | if (0 < sName.length() && ':' == sName[0]) { |
1595 | 0 | break; |
1596 | 0 | } |
1597 | | |
1598 | 0 | m_mISupport[sName] = sValue; |
1599 | |
|
1600 | 0 | if (sName.Equals("PREFIX")) { |
1601 | 0 | CString sPrefixes = sValue.Token(1, false, ")"); |
1602 | 0 | CString sPermModes = sValue.Token(0, false, ")"); |
1603 | 0 | sPermModes.TrimLeft("("); |
1604 | |
|
1605 | 0 | if (!sPrefixes.empty() && sPermModes.size() == sPrefixes.size()) { |
1606 | 0 | m_sPerms = sPrefixes; |
1607 | 0 | m_sPermModes = sPermModes; |
1608 | 0 | } |
1609 | 0 | } else if (sName.Equals("CHANTYPES")) { |
1610 | 0 | m_pNetwork->SetChanPrefixes(sValue); |
1611 | 0 | } else if (sName.Equals("NICKLEN")) { |
1612 | 0 | unsigned int uMax = sValue.ToUInt(); |
1613 | |
|
1614 | 0 | if (uMax) { |
1615 | 0 | m_uMaxNickLen = uMax; |
1616 | 0 | } |
1617 | 0 | } else if (sName.Equals("CHANMODES")) { |
1618 | 0 | if (!sValue.empty()) { |
1619 | 0 | m_mceChanModes.clear(); |
1620 | |
|
1621 | 0 | for (unsigned int a = 0; a < 4; a++) { |
1622 | 0 | CString sModes = sValue.Token(a, false, ","); |
1623 | |
|
1624 | 0 | for (unsigned int b = 0; b < sModes.size(); b++) { |
1625 | 0 | m_mceChanModes[sModes[b]] = (EChanModeArgs)a; |
1626 | 0 | } |
1627 | 0 | } |
1628 | 0 | } |
1629 | 0 | } else if (sName.Equals("NAMESX")) { |
1630 | 0 | if (m_bNamesx) continue; |
1631 | 0 | m_bNamesx = true; |
1632 | 0 | PutIRC("PROTOCTL NAMESX"); |
1633 | 0 | } else if (sName.Equals("UHNAMES")) { |
1634 | 0 | if (m_bUHNames) continue; |
1635 | 0 | m_bUHNames = true; |
1636 | 0 | PutIRC("PROTOCTL UHNAMES"); |
1637 | 0 | } |
1638 | 0 | } |
1639 | 0 | } |
1640 | | |
1641 | | CString CIRCSock::GetISupport(const CString& sKey, |
1642 | 0 | const CString& sDefault) const { |
1643 | 0 | MCString::const_iterator i = m_mISupport.find(sKey.AsUpper()); |
1644 | 0 | if (i == m_mISupport.end()) { |
1645 | 0 | return sDefault; |
1646 | 0 | } else { |
1647 | 0 | return i->second; |
1648 | 0 | } |
1649 | 0 | } |
1650 | | |
1651 | | CString CIRCSock::GetCapLsValue(const CString& sKey, |
1652 | 0 | const CString& sDefault) const { |
1653 | 0 | MCString::const_iterator i = m_msCapLsValues.find(sKey); |
1654 | 0 | if (i == m_msCapLsValues.end()) { |
1655 | 0 | return sDefault; |
1656 | 0 | } else { |
1657 | 0 | return i->second; |
1658 | 0 | } |
1659 | 0 | } |
1660 | | |
1661 | 0 | void CIRCSock::SendAltNick(const CString& sBadNick) { |
1662 | 0 | const CString& sLastNick = m_Nick.GetNick(); |
1663 | | |
1664 | | // We don't know the maximum allowed nick length yet, but we know which |
1665 | | // nick we sent last. If sBadNick is shorter than that, we assume the |
1666 | | // server truncated our nick. |
1667 | 0 | if (sBadNick.length() < sLastNick.length()) |
1668 | 0 | m_uMaxNickLen = (unsigned int)sBadNick.length(); |
1669 | |
|
1670 | 0 | unsigned int uMax = m_uMaxNickLen; |
1671 | |
|
1672 | 0 | const CString& sConfNick = m_pNetwork->GetNick(); |
1673 | 0 | const CString& sAltNick = m_pNetwork->GetAltNick(); |
1674 | 0 | CString sNewNick = sConfNick.Left(uMax - 1); |
1675 | |
|
1676 | 0 | if (sLastNick.Equals(sConfNick)) { |
1677 | 0 | if ((!sAltNick.empty()) && (!sConfNick.Equals(sAltNick))) { |
1678 | 0 | sNewNick = sAltNick; |
1679 | 0 | } else { |
1680 | 0 | sNewNick += "-"; |
1681 | 0 | } |
1682 | 0 | } else if (sLastNick.Equals(sAltNick) && !sAltNick.Equals(sNewNick + "-")) { |
1683 | 0 | sNewNick += "-"; |
1684 | 0 | } else if (sLastNick.Equals(sNewNick + "-") && |
1685 | 0 | !sAltNick.Equals(sNewNick + "|")) { |
1686 | 0 | sNewNick += "|"; |
1687 | 0 | } else if (sLastNick.Equals(sNewNick + "|") && |
1688 | 0 | !sAltNick.Equals(sNewNick + "^")) { |
1689 | 0 | sNewNick += "^"; |
1690 | 0 | } else if (sLastNick.Equals(sNewNick + "^") && |
1691 | 0 | !sAltNick.Equals(sNewNick + "a")) { |
1692 | 0 | sNewNick += "a"; |
1693 | 0 | } else { |
1694 | 0 | char cLetter = 0; |
1695 | 0 | if (sBadNick.empty()) { |
1696 | 0 | m_pNetwork->PutUser(t_s("No free nick available")); |
1697 | 0 | Quit(); |
1698 | 0 | return; |
1699 | 0 | } |
1700 | | |
1701 | 0 | cLetter = sBadNick.back(); |
1702 | |
|
1703 | 0 | if (cLetter == 'z') { |
1704 | 0 | m_pNetwork->PutUser(t_s("No free nick found")); |
1705 | 0 | Quit(); |
1706 | 0 | return; |
1707 | 0 | } |
1708 | | |
1709 | 0 | sNewNick = sConfNick.Left(uMax - 1) + ++cLetter; |
1710 | 0 | if (sNewNick.Equals(sAltNick)) |
1711 | 0 | sNewNick = sConfNick.Left(uMax - 1) + ++cLetter; |
1712 | 0 | } |
1713 | 0 | PutIRC("NICK " + sNewNick); |
1714 | 0 | m_Nick.SetNick(sNewNick); |
1715 | 0 | } |
1716 | | |
1717 | 0 | char CIRCSock::GetPermFromMode(char cMode) const { |
1718 | 0 | if (m_sPermModes.size() == m_sPerms.size()) { |
1719 | 0 | for (unsigned int a = 0; a < m_sPermModes.size(); a++) { |
1720 | 0 | if (m_sPermModes[a] == cMode) { |
1721 | 0 | return m_sPerms[a]; |
1722 | 0 | } |
1723 | 0 | } |
1724 | 0 | } |
1725 | | |
1726 | 0 | return 0; |
1727 | 0 | } |
1728 | | |
1729 | 0 | char CIRCSock::GetModeFromPerm(char cPerm) const { |
1730 | 0 | if (m_sPermModes.size() == m_sPerms.size()) { |
1731 | 0 | for (unsigned int a = 0; a < m_sPermModes.size(); a++) { |
1732 | 0 | if (m_sPerms[a] == cPerm) { |
1733 | 0 | return m_sPermModes[a]; |
1734 | 0 | } |
1735 | 0 | } |
1736 | 0 | } |
1737 | | |
1738 | 0 | return 0; |
1739 | 0 | } |
1740 | | |
1741 | 0 | CIRCSock::EChanModeArgs CIRCSock::GetModeType(char cMode) const { |
1742 | 0 | map<char, EChanModeArgs>::const_iterator it = |
1743 | 0 | m_mceChanModes.find(cMode); |
1744 | |
|
1745 | 0 | if (it == m_mceChanModes.end()) { |
1746 | 0 | return NoArg; |
1747 | 0 | } |
1748 | | |
1749 | 0 | return it->second; |
1750 | 0 | } |
1751 | | |
1752 | 0 | void CIRCSock::ResetChans() { |
1753 | 0 | for (const auto& it : m_msChans) { |
1754 | 0 | it.second->Reset(); |
1755 | 0 | } |
1756 | 0 | } |
1757 | | |
1758 | 0 | void CIRCSock::SetTagSupport(const CString& sTag, bool bState) { |
1759 | 0 | if (bState) { |
1760 | 0 | m_ssSupportedTags.insert(sTag); |
1761 | 0 | } else { |
1762 | 0 | m_ssSupportedTags.erase(sTag); |
1763 | 0 | } |
1764 | 0 | } |
1765 | | |