Coverage Report

Created: 2025-07-04 06:51

/src/znc/src/IRCSock.cpp
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