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