Coverage Report

Created: 2026-02-03 06:42

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