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