Coverage Report

Created: 2025-11-03 06:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/znc/src/Client.cpp
Line
Count
Source
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/Client.h>
18
#include <znc/Chan.h>
19
#include <znc/IRCSock.h>
20
#include <znc/User.h>
21
#include <znc/IRCNetwork.h>
22
#include <znc/Query.h>
23
24
using std::set;
25
using std::map;
26
using std::vector;
27
28
#define CALLMOD(MOD, CLIENT, USER, NETWORK, FUNC)                             \
29
0
    {                                                                         \
30
0
        CModule* pModule = nullptr;                                           \
31
0
        if (NETWORK && (pModule = (NETWORK)->GetModules().FindModule(MOD))) { \
32
0
            try {                                                             \
33
0
                CClient* pOldClient = pModule->GetClient();                   \
34
0
                pModule->SetClient(CLIENT);                                   \
35
0
                pModule->FUNC;                                                \
36
0
                pModule->SetClient(pOldClient);                               \
37
0
            } catch (const CModule::EModException& e) {                       \
38
0
                if (e == CModule::UNLOAD) {                                   \
39
0
                    (NETWORK)->GetModules().UnloadModule(MOD);                \
40
0
                }                                                             \
41
0
            }                                                                 \
42
0
        } else if ((pModule = (USER)->GetModules().FindModule(MOD))) {        \
43
0
            try {                                                             \
44
0
                CClient* pOldClient = pModule->GetClient();                   \
45
0
                CIRCNetwork* pOldNetwork = pModule->GetNetwork();             \
46
0
                pModule->SetClient(CLIENT);                                   \
47
0
                pModule->SetNetwork(NETWORK);                                 \
48
0
                pModule->FUNC;                                                \
49
0
                pModule->SetClient(pOldClient);                               \
50
0
                pModule->SetNetwork(pOldNetwork);                             \
51
0
            } catch (const CModule::EModException& e) {                       \
52
0
                if (e == CModule::UNLOAD) {                                   \
53
0
                    (USER)->GetModules().UnloadModule(MOD);                   \
54
0
                }                                                             \
55
0
            }                                                                 \
56
0
        } else if ((pModule = CZNC::Get().GetModules().FindModule(MOD))) {    \
57
0
            try {                                                             \
58
0
                CClient* pOldClient = pModule->GetClient();                   \
59
0
                CIRCNetwork* pOldNetwork = pModule->GetNetwork();             \
60
0
                CUser* pOldUser = pModule->GetUser();                         \
61
0
                pModule->SetClient(CLIENT);                                   \
62
0
                pModule->SetNetwork(NETWORK);                                 \
63
0
                pModule->SetUser(USER);                                       \
64
0
                pModule->FUNC;                                                \
65
0
                pModule->SetClient(pOldClient);                               \
66
0
                pModule->SetNetwork(pOldNetwork);                             \
67
0
                pModule->SetUser(pOldUser);                                   \
68
0
            } catch (const CModule::EModException& e) {                       \
69
0
                if (e == CModule::UNLOAD) {                                   \
70
0
                    CZNC::Get().GetModules().UnloadModule(MOD);               \
71
0
                }                                                             \
72
0
            }                                                                 \
73
0
        } else {                                                              \
74
0
            PutStatus(t_f("No such module {1}")(MOD));                        \
75
0
        }                                                                     \
76
0
    }
77
78
CClient::CClient()
79
0
    : CIRCSocket(),
80
0
      m_bGotPass(false),
81
0
      m_bGotNick(false),
82
0
      m_bGotUser(false),
83
0
      m_uCapVersion(0),
84
0
      m_bInCap(false),
85
0
      m_bCapNotify(false),
86
0
      m_bAwayNotify(false),
87
0
      m_bAccountNotify(false),
88
0
      m_bInviteNotify(false),
89
0
      m_bExtendedJoin(false),
90
0
      m_bNamesx(false),
91
0
      m_bUHNames(false),
92
0
      m_bChgHost(false),
93
0
      m_bAway(false),
94
0
      m_bServerTime(false),
95
0
      m_bBatch(false),
96
0
      m_bEchoMessage(false),
97
0
      m_bSelfMessage(false),
98
0
      m_bMessageTagCap(false),
99
0
      m_bSASLCap(false),
100
0
      m_bPlaybackActive(false),
101
0
      m_pUser(nullptr),
102
0
      m_pNetwork(nullptr),
103
0
      m_sNick("unknown-nick"),
104
0
      m_sPass(""),
105
0
      m_sUser(""),
106
0
      m_sNetwork(""),
107
0
      m_sIdentifier(""),
108
0
      m_sSASLBuffer(""),
109
0
      m_sSASLMechanism(""),
110
0
      m_sSASLUser(""),
111
0
      m_spAuth(),
112
0
      m_ssAcceptedCaps(),
113
0
      m_ssSupportedTags() {
114
0
    EnableReadLine();
115
    // RFC says a line can have 512 chars max, but we are
116
    // a little more gentle ;)
117
0
    SetMaxBufferThreshold(1024);
118
0
}
119
120
0
CClient::~CClient() {
121
0
    if (m_spAuth) {
122
0
        CClientAuth* pAuth = (CClientAuth*)&(*m_spAuth);
123
0
        pAuth->Invalidate();
124
0
    }
125
0
    if (m_pUser != nullptr) {
126
0
        m_pUser->AddBytesRead(GetBytesRead());
127
0
        m_pUser->AddBytesWritten(GetBytesWritten());
128
0
    }
129
0
}
130
131
0
void CClient::SendRequiredPasswordNotice() {
132
0
    PutClient(":irc.znc.in 464 " + GetNick() + " :Password required");
133
0
    PutClient(
134
0
        ":irc.znc.in NOTICE " + GetNick() + " :*** "
135
0
        "You need to send your password. "
136
0
        "Configure your client to send a server password.");
137
0
    PutClient(
138
0
        ":irc.znc.in NOTICE " + GetNick() + " :*** "
139
0
        "To connect now, you can use /quote PASS <username>:<password>, "
140
0
        "or /quote PASS <username>/<network>:<password> to connect to a "
141
0
        "specific network.");
142
0
}
143
144
0
void CClient::ReadLine(const CString& sData) {
145
0
    CLanguageScope user_lang(GetUser() ? GetUser()->GetLanguage() : "");
146
0
    CString sLine = sData;
147
148
0
    sLine.Replace("\n", "");
149
0
    sLine.Replace("\r", "");
150
151
0
    DEBUG("(" << GetFullName() << ") CLI -> ZNC ["
152
0
        << CDebug::Filter(sLine) << "]");
153
154
0
    bool bReturn = false;
155
0
    if (IsAttached()) {
156
0
        NETWORKMODULECALL(OnUserRaw(sLine), m_pUser, m_pNetwork, this,
157
0
                          &bReturn);
158
0
    } else {
159
0
        GLOBALMODULECALL(OnUnknownUserRaw(this, sLine), &bReturn);
160
0
    }
161
0
    if (bReturn) return;
162
163
0
    CMessage Message(sLine);
164
0
    Message.SetClient(this);
165
166
0
    if (IsAttached()) {
167
0
        NETWORKMODULECALL(OnUserRawMessage(Message), m_pUser, m_pNetwork, this,
168
0
                          &bReturn);
169
0
    } else {
170
0
        GLOBALMODULECALL(OnUnknownUserRawMessage(Message), &bReturn);
171
0
    }
172
0
    if (bReturn) return;
173
174
0
    CString sCommand = Message.GetCommand();
175
176
0
    if (!IsAttached()) {
177
        // The following commands happen before authentication with ZNC
178
0
        if (sCommand.Equals("PASS")) {
179
0
            m_bGotPass = true;
180
181
0
            CString sAuthLine = Message.GetParam(0);
182
0
            ParsePass(sAuthLine);
183
184
0
            AuthUser();
185
            // Don't forward this msg.  ZNC has already registered us.
186
0
            return;
187
0
        } else if (sCommand.Equals("NICK")) {
188
0
            CString sNick = Message.GetParam(0);
189
190
0
            m_sNick = sNick;
191
0
            m_bGotNick = true;
192
193
0
            AuthUser();
194
            // Don't forward this msg.  ZNC will handle nick changes until auth
195
            // is complete
196
0
            return;
197
0
        } else if (sCommand.Equals("USER")) {
198
0
            CString sAuthLine = Message.GetParam(0);
199
200
0
            if (m_sUser.empty() && !sAuthLine.empty()) {
201
0
                ParseUser(sAuthLine);
202
0
            }
203
204
0
            m_bGotUser = true;
205
0
            if (m_bGotPass || !m_sSASLUser.empty()) {
206
0
                AuthUser();
207
0
            } else if (!m_bInCap) {
208
0
                SendRequiredPasswordNotice();
209
0
            }
210
211
            // Don't forward this msg.  ZNC has already registered us.
212
0
            return;
213
0
        }
214
0
    }
215
216
0
    if (Message.GetType() == CMessage::Type::Capability) {
217
0
        HandleCap(Message);
218
219
        // Don't let the client talk to the server directly about CAP,
220
        // we don't want anything enabled that ZNC does not support.
221
0
        return;
222
0
    }
223
224
0
    if (Message.GetType() == CMessage::Type::Authenticate) {
225
0
        OnAuthenticateMessage(Message);
226
227
0
        return;
228
0
    }
229
230
0
    if (!m_pUser) {
231
        // Only CAP, NICK, USER and PASS are allowed before login
232
0
        return;
233
0
    }
234
235
0
    switch (Message.GetType()) {
236
0
        case CMessage::Type::Action:
237
0
            bReturn = OnActionMessage(Message);
238
0
            break;
239
0
        case CMessage::Type::CTCP:
240
0
            bReturn = OnCTCPMessage(Message);
241
0
            break;
242
0
        case CMessage::Type::Join:
243
0
            bReturn = OnJoinMessage(Message);
244
0
            break;
245
0
        case CMessage::Type::Mode:
246
0
            bReturn = OnModeMessage(Message);
247
0
            break;
248
0
        case CMessage::Type::Notice:
249
0
            bReturn = OnNoticeMessage(Message);
250
0
            break;
251
0
        case CMessage::Type::Part:
252
0
            bReturn = OnPartMessage(Message);
253
0
            break;
254
0
        case CMessage::Type::Ping:
255
0
            bReturn = OnPingMessage(Message);
256
0
            break;
257
0
        case CMessage::Type::Pong:
258
0
            bReturn = OnPongMessage(Message);
259
0
            break;
260
0
        case CMessage::Type::Quit:
261
0
            bReturn = OnQuitMessage(Message);
262
0
            break;
263
0
        case CMessage::Type::TagMsg:
264
0
            bReturn = OnTagMessage(Message);
265
0
            break;
266
0
        case CMessage::Type::Text:
267
0
            bReturn = OnTextMessage(Message);
268
0
            break;
269
0
        case CMessage::Type::Topic:
270
0
            bReturn = OnTopicMessage(Message);
271
0
            break;
272
0
        default:
273
0
            bReturn = OnOtherMessage(Message);
274
0
            break;
275
0
    }
276
277
0
    if (bReturn) return;
278
279
0
    PutIRCStripping(Message);
280
0
}
281
282
0
void CClient::SetNick(const CString& s) { m_sNick = s; }
283
284
void CClient::SetNetwork(CIRCNetwork* pNetwork, bool bDisconnect,
285
0
                         bool bReconnect) {
286
0
    if (m_pNetwork) {
287
0
        m_pNetwork->ClientDisconnected(this);
288
289
0
        if (bDisconnect) {
290
0
            NETWORKMODULECALL(OnClientDetached(), m_pUser, m_pNetwork, this, NOTHING);
291
            // Tell the client they are no longer in these channels.
292
0
            const vector<CChan*>& vChans = m_pNetwork->GetChans();
293
0
            for (const CChan* pChan : vChans) {
294
0
                if (!(pChan->IsDetached())) {
295
0
                    PutClient(":" + m_pNetwork->GetIRCNick().GetNickMask() +
296
0
                              " PART " + pChan->GetName());
297
0
                }
298
0
            }
299
0
        }
300
0
    } else if (m_pUser) {
301
0
        m_pUser->UserDisconnected(this);
302
0
    }
303
304
0
    m_pNetwork = pNetwork;
305
306
0
    if (bReconnect) {
307
0
        if (m_pNetwork) {
308
0
            m_pNetwork->ClientConnected(this);
309
0
        } else if (m_pUser) {
310
0
            m_pUser->UserConnected(this);
311
0
        }
312
0
        NETWORKMODULECALL(OnClientAttached(), m_pUser, m_pNetwork, this, NOTHING);
313
0
    }
314
0
}
315
316
0
const vector<CClient*>& CClient::GetClients() const {
317
0
    if (m_pNetwork) {
318
0
        return m_pNetwork->GetClients();
319
0
    }
320
321
0
    return m_pUser->GetUserClients();
322
0
}
323
324
0
const CIRCSock* CClient::GetIRCSock() const {
325
0
    if (m_pNetwork) {
326
0
        return m_pNetwork->GetIRCSock();
327
0
    }
328
329
0
    return nullptr;
330
0
}
331
332
0
CIRCSock* CClient::GetIRCSock() {
333
0
    if (m_pNetwork) {
334
0
        return m_pNetwork->GetIRCSock();
335
0
    }
336
337
0
    return nullptr;
338
0
}
339
340
0
void CClient::StatusCTCP(const CString& sLine) {
341
0
    CString sCommand = sLine.Token(0);
342
343
0
    if (sCommand.Equals("PING")) {
344
0
        PutStatusNotice("\001PING " + sLine.Token(1, true) + "\001");
345
0
    } else if (sCommand.Equals("VERSION")) {
346
0
        PutStatusNotice("\001VERSION " + CZNC::GetTag() + "\001");
347
0
    }
348
0
}
349
350
0
bool CClient::SendMotd() {
351
0
    const VCString& vsMotd = CZNC::Get().GetMotd();
352
353
0
    if (!vsMotd.size()) {
354
0
        return false;
355
0
    }
356
357
0
    for (const CString& sLine : vsMotd) {
358
0
        if (m_pNetwork) {
359
0
            PutStatusNotice(m_pNetwork->ExpandString(sLine));
360
0
        } else {
361
0
            PutStatusNotice(m_pUser->ExpandString(sLine));
362
0
        }
363
0
    }
364
365
0
    return true;
366
0
}
367
368
0
void CClient::AuthUser() {
369
0
    if (!m_bGotNick || !m_bGotUser || m_bInCap ||
370
0
        (m_sSASLUser.empty() && !m_bGotPass) || IsAttached())
371
0
        return;
372
373
0
    if (m_sSASLUser.empty()) {
374
0
        m_spAuth = std::make_shared<CClientAuth>(this, m_sUser, m_sPass);
375
0
        CZNC::Get().AuthUser(m_spAuth);
376
0
    } else {
377
        // Already logged in, but the user could have been deleted meanwhile.
378
0
        CUser* pUser = CZNC::Get().FindUser(m_sSASLUser);
379
0
        if (pUser) {
380
0
            AcceptLogin(*pUser);
381
0
        } else {
382
0
            RefuseLogin("SASL login was valid, but user no longer exists");
383
0
        }
384
0
    }
385
0
}
386
387
/** Username+password auth, which reports success/failure to client via SASL. */ 
388
class CClientSASLAuth : public CClientAuth {
389
  public:
390
    using CClientAuth::CClientAuth;
391
    void AcceptedLogin(CUser& User) override;
392
    void RefusedLogin(const CString& sReason) override;
393
};
394
395
void CClient::StartSASLPasswordCheck(const CString& sUser,
396
0
                                     const CString& sPassword, const CString& sAuthorizationId) {
397
0
    ParseUser(sAuthorizationId);
398
0
    if (sUser != m_sUser && sUser != sAuthorizationId) {
399
0
        RefuseSASLLogin("No support for custom AuthzId");
400
0
    }
401
402
0
    m_spAuth = std::make_shared<CClientSASLAuth>(this, m_sUser, sPassword);
403
404
0
    CZNC::Get().AuthUser(m_spAuth);
405
0
}
406
407
CClientAuth::CClientAuth(CClient* pClient, const CString& sUsername,
408
                         const CString& sPassword)
409
0
    : CAuthBase(sUsername, sPassword, pClient), m_pClient(pClient) {}
410
411
0
void CClientSASLAuth::RefusedLogin(const CString& sReason) {
412
0
    if (m_pClient) {
413
0
        m_pClient->RefuseSASLLogin(sReason);
414
0
    }
415
0
}
416
417
0
void CClientAuth::RefusedLogin(const CString& sReason) {
418
0
    if (m_pClient) {
419
0
        m_pClient->RefuseLogin(sReason);
420
0
    }
421
0
}
422
423
0
CString CAuthBase::GetRemoteIP() const {
424
0
    if (m_pSock) return m_pSock->GetRemoteIP();
425
0
    return "";
426
0
}
427
428
0
void CAuthBase::Invalidate() { m_pSock = nullptr; }
429
430
0
void CAuthBase::AcceptLogin(CUser& User) {
431
0
    if (m_pSock) {
432
0
        AcceptedLogin(User);
433
0
    }
434
0
    Invalidate();
435
0
}
436
437
0
void CAuthBase::RefuseLogin(const CString& sReason) {
438
0
    if (!m_pSock) return;
439
440
0
    CUser* pUser = CZNC::Get().FindUser(GetUsername());
441
442
    // If the username is valid, notify that user that someone tried to
443
    // login. Use sReason because there are other reasons than "wrong
444
    // password" for a login to be rejected (e.g. fail2ban).
445
0
    if (pUser) {
446
0
        pUser->PutStatusNotice(t_f(
447
0
            "A client from {1} attempted to login as you, but was rejected: "
448
0
            "{2}")(GetRemoteIP(), sReason));
449
0
    }
450
451
0
    GLOBALMODULECALL(OnFailedLogin(GetUsername(), GetRemoteIP()), NOTHING);
452
0
    RefusedLogin(sReason);
453
0
    Invalidate();
454
0
}
455
456
0
void CClient::RefuseLogin(const CString& sReason) {
457
0
    PutStatus("Bad username and/or password.");
458
0
    PutClient(":irc.znc.in 464 " + GetNick() + " :" + sReason);
459
0
    Close(Csock::CLT_AFTERWRITE);
460
0
}
461
462
0
void CClientSASLAuth::AcceptedLogin(CUser& User) {
463
0
    if (m_pClient) {
464
0
        m_pClient->AcceptSASLLogin(User);
465
0
    }
466
0
}
467
468
0
void CClientAuth::AcceptedLogin(CUser& User) {
469
0
    if (m_pClient) {
470
0
        m_pClient->AcceptLogin(User);
471
0
    }
472
0
}
473
474
0
void CClient::AcceptLogin(CUser& User) {
475
0
    m_sPass = "";
476
0
    m_pUser = &User;
477
0
    m_sSASLMechanism = "";
478
0
    m_sSASLBuffer = "";
479
0
    m_sSASLUser = "";
480
481
    // Set our proper timeout and set back our proper timeout mode
482
    // (constructor set a different timeout and mode)
483
0
    SetTimeout(User.GetNoTrafficTimeout(), TMO_READ);
484
485
0
    SetSockName("USR::" + m_pUser->GetUsername());
486
0
    SetEncoding(m_pUser->GetClientEncoding());
487
488
0
    if (!m_sNetwork.empty()) {
489
0
        m_pNetwork = m_pUser->FindNetwork(m_sNetwork);
490
0
        if (!m_pNetwork) {
491
0
            PutStatus(t_f("Network {1} doesn't exist.")(m_sNetwork));
492
0
        }
493
0
    } else if (!m_pUser->GetNetworks().empty()) {
494
        // If a user didn't supply a network, and they have a network called
495
        // "default" then automatically use this network.
496
0
        m_pNetwork = m_pUser->FindNetwork("default");
497
        // If no "default" network, try "user" network. It's for compatibility
498
        // with early network stuff in ZNC, which converted old configs to
499
        // "user" network.
500
0
        if (!m_pNetwork) m_pNetwork = m_pUser->FindNetwork("user");
501
        // Otherwise, just try any network of the user.
502
0
        if (!m_pNetwork) m_pNetwork = *m_pUser->GetNetworks().begin();
503
0
        if (m_pNetwork && m_pUser->GetNetworks().size() > 1) {
504
0
            PutStatusNotice(
505
0
                t_s("You have several networks configured, but no network was "
506
0
                    "specified for the connection."));
507
0
            PutStatusNotice(
508
0
                t_f("Selecting network {1}. To see list of all configured "
509
0
                    "networks, use /znc ListNetworks")(m_pNetwork->GetName()));
510
0
            PutStatusNotice(t_f(
511
0
                "If you want to choose another network, use /znc JumpNetwork "
512
0
                "<network>, or connect to ZNC with username {1}/<network> "
513
0
                "(instead of just {1})")(m_pUser->GetUsername()));
514
0
        }
515
0
    } else {
516
0
        PutStatusNotice(
517
0
            t_s("You have no networks configured. Use /znc AddNetwork "
518
0
                "<network> to add one."));
519
0
    }
520
521
0
    SetNetwork(m_pNetwork, false);
522
523
0
    SendMotd();
524
525
0
    NETWORKMODULECALL(OnClientLogin(), m_pUser, m_pNetwork, this, NOTHING);
526
0
}
527
528
0
void CClient::Timeout() { PutClient("ERROR :" + t_s("Closing link: Timeout")); }
529
530
0
void CClient::Connected() { DEBUG(GetSockName() << " == Connected();"); }
531
532
0
void CClient::ConnectionRefused() {
533
0
    DEBUG(GetSockName() << " == ConnectionRefused()");
534
0
}
535
536
0
void CClient::Disconnected() {
537
0
    DEBUG(GetSockName() << " == Disconnected()");
538
0
    CIRCNetwork* pNetwork = m_pNetwork;
539
0
    SetNetwork(nullptr, false, false);
540
541
0
    if (m_pUser) {
542
0
        NETWORKMODULECALL(OnClientDisconnect(), m_pUser, pNetwork, this,
543
0
                          NOTHING);
544
0
    }
545
0
}
546
547
0
void CClient::ReachedMaxBuffer() {
548
0
    DEBUG(GetSockName() << " == ReachedMaxBuffer()");
549
0
    if (IsAttached()) {
550
0
        PutClient("ERROR :" + t_s("Closing link: Too long raw line"));
551
0
    }
552
0
    Close();
553
0
}
554
555
0
void CClient::BouncedOff() {
556
0
    PutStatusNotice(
557
0
        t_s("You are being disconnected because another user just "
558
0
            "authenticated as you."));
559
0
    Close(Csock::CLT_AFTERWRITE);
560
0
}
561
562
0
void CClient::PutIRC(const CString& sLine) {
563
0
    if (m_pNetwork) {
564
0
        m_pNetwork->PutIRC(sLine);
565
0
    }
566
0
}
567
568
0
void CClient::PutIRCStripping(CMessage Message) {
569
0
    if (CIRCSock* pSock = GetIRCSock()) {
570
0
        Message.SetNick(CNick{});
571
0
        if (!pSock->HasMessageTagCap()) {
572
0
            Message.SetTags({});
573
0
        }
574
0
        pSock->PutIRC(Message);
575
0
    }
576
0
}
577
578
0
CString CClient::GetFullName() const {
579
0
    if (!m_pUser) return GetRemoteIP();
580
0
    CString sFullName = m_pUser->GetUsername();
581
0
    if (!m_sIdentifier.empty()) sFullName += "@" + m_sIdentifier;
582
0
    if (m_pNetwork) sFullName += "/" + m_pNetwork->GetName();
583
0
    return sFullName;
584
0
}
585
586
0
void CClient::PutClient(const CString& sLine) {
587
0
    PutClient(CMessage(sLine));
588
0
}
589
590
0
bool CClient::PutClient(const CMessage& Message) {
591
0
    switch (Message.GetType()) {
592
0
        case CMessage::Type::Away:
593
0
            if (!m_bAwayNotify) return false;
594
0
            break;
595
0
        case CMessage::Type::Account:
596
0
            if (!m_bAccountNotify) return false;
597
0
            break;
598
0
        case CMessage::Type::TagMsg:
599
0
            if (!m_bMessageTagCap) return false;
600
0
            break;
601
0
        case CMessage::Type::Invite:
602
0
            if (!m_bInviteNotify &&
603
0
                !CNick(Message.As<CInviteMessage>().GetInvitedNick())
604
0
                     .NickEquals(m_sNick)) {
605
0
                return false;
606
0
            }
607
0
            break;
608
0
        default:
609
0
            break;
610
0
    }
611
612
0
    CMessage Msg(Message);
613
614
0
    const CIRCSock* pIRCSock = GetIRCSock();
615
0
    if (pIRCSock) {
616
0
        if (Msg.GetType() == CMessage::Type::Numeric) {
617
0
            unsigned int uCode = Msg.As<CNumericMessage>().GetCode();
618
619
0
            if (uCode == 352) {  // RPL_WHOREPLY
620
0
                if (!m_bNamesx && pIRCSock->HasNamesx()) {
621
                    // The server has NAMESX, but the client doesn't, so we need
622
                    // to remove extra prefixes
623
0
                    CString sNick = Msg.GetParam(6);
624
0
                    if (sNick.size() > 1 && pIRCSock->IsPermChar(sNick[1])) {
625
0
                        CString sNewNick = sNick;
626
0
                        size_t pos =
627
0
                            sNick.find_first_not_of(pIRCSock->GetPerms());
628
0
                        if (pos >= 2 && pos != CString::npos) {
629
0
                            sNewNick = sNick[0] + sNick.substr(pos);
630
0
                        }
631
0
                        Msg.SetParam(6, sNewNick);
632
0
                    }
633
0
                }
634
0
            } else if (uCode == 353) {  // RPL_NAMES
635
0
                if ((!m_bNamesx && pIRCSock->HasNamesx()) ||
636
0
                    (!m_bUHNames && pIRCSock->HasUHNames())) {
637
                    // The server has either UHNAMES or NAMESX, but the client
638
                    // is missing either or both
639
0
                    CString sNicks = Msg.GetParam(3);
640
0
                    VCString vsNicks;
641
0
                    sNicks.Split(" ", vsNicks, false);
642
643
0
                    for (CString& sNick : vsNicks) {
644
0
                        if (sNick.empty()) break;
645
646
0
                        if (!m_bNamesx && pIRCSock->HasNamesx() &&
647
0
                            pIRCSock->IsPermChar(sNick[0])) {
648
                            // The server has NAMESX, but the client doesn't, so
649
                            // we just use the first perm char
650
0
                            size_t pos =
651
0
                                sNick.find_first_not_of(pIRCSock->GetPerms());
652
0
                            if (pos >= 2 && pos != CString::npos) {
653
0
                                sNick = sNick[0] + sNick.substr(pos);
654
0
                            }
655
0
                        }
656
657
0
                        if (!m_bUHNames && pIRCSock->HasUHNames()) {
658
                            // The server has UHNAMES, but the client doesn't,
659
                            // so we strip away ident and host
660
0
                            sNick = sNick.Token(0, false, "!");
661
0
                        }
662
0
                    }
663
664
0
                    Msg.SetParam(
665
0
                        3, CString(" ").Join(vsNicks.begin(), vsNicks.end()));
666
0
                }
667
0
            }
668
0
        } else if (Msg.GetType() == CMessage::Type::Join) {
669
0
            if (!m_bExtendedJoin && pIRCSock->HasExtendedJoin()) {
670
0
                Msg.SetParams({Msg.As<CJoinMessage>().GetTarget()});
671
0
            }
672
0
        }
673
0
    }
674
675
0
    MCString mssNewTags;
676
0
    MCString& mssTags = m_bMessageTagCap ? Msg.GetTags() : mssNewTags;
677
678
0
    if (!m_bMessageTagCap) {
679
0
        for (const auto& it : Msg.GetTags()) {
680
0
            if (IsTagEnabled(it.first)) {
681
0
                mssTags[it.first] = it.second;
682
0
            }
683
0
        }
684
0
    }
685
686
0
    if (HasServerTime()) {
687
        // If the server didn't set the time tag, manually set it
688
0
        mssTags.emplace("time", CUtils::FormatServerTime(Msg.GetTime()));
689
0
    }
690
691
0
    Msg.SetTags(mssTags);
692
0
    Msg.SetClient(this);
693
0
    Msg.SetNetwork(m_pNetwork);
694
695
0
    bool bReturn = false;
696
0
    NETWORKMODULECALL(OnSendToClientMessage(Msg), m_pUser, m_pNetwork, this,
697
0
                      &bReturn);
698
0
    if (bReturn) return false;
699
700
0
    return PutClientRaw(Msg.ToString());
701
0
}
702
703
0
bool CClient::PutClientRaw(const CString& sLine) {
704
0
    CString sCopy = sLine;
705
0
    bool bReturn = false;
706
0
    NETWORKMODULECALL(OnSendToClient(sCopy, *this), m_pUser, m_pNetwork, this,
707
0
                      &bReturn);
708
0
    if (bReturn) return false;
709
710
0
    DEBUG("(" << GetFullName() << ") ZNC -> CLI ["
711
0
        << CDebug::Filter(sCopy) << "]");
712
0
    Write(sCopy + "\r\n");
713
0
    return true;
714
0
}
715
716
0
void CClient::PutStatusNotice(const CString& sLine) {
717
0
    PutModNotice("status", sLine);
718
0
}
719
720
0
unsigned int CClient::PutStatus(const CTable& table) {
721
0
    unsigned int idx = 0;
722
0
    CString sLine;
723
0
    while (table.GetLine(idx++, sLine)) PutStatus(sLine);
724
0
    return idx - 1;
725
0
}
726
727
0
void CClient::PutStatus(const CString& sLine) { PutModule("status", sLine); }
728
729
0
void CClient::PutModNotice(const CString& sModule, const CString& sLine) {
730
0
    if (!m_pUser) {
731
0
        return;
732
0
    }
733
734
0
    DEBUG("(" << GetFullName()
735
0
              << ") ZNC -> CLI [:" + m_pUser->GetStatusPrefix() +
736
0
                     ((sModule.empty()) ? "status" : sModule) + "!" +
737
0
                     ((sModule.empty()) ? "status" : sModule) +
738
0
                     "@znc.in NOTICE "
739
0
              << GetNick() << " :" << sLine << "]");
740
0
    Write(":" + m_pUser->GetStatusPrefix() +
741
0
          ((sModule.empty()) ? "status" : sModule) + "!" +
742
0
          ((sModule.empty()) ? "status" : sModule) + "@znc.in NOTICE " +
743
0
          GetNick() + " :" + sLine + "\r\n");
744
0
}
745
746
0
void CClient::PutModule(const CString& sModule, const CString& sLine) {
747
0
    if (!m_pUser) {
748
0
        return;
749
0
    }
750
751
0
    DEBUG("(" << GetFullName()
752
0
              << ") ZNC -> CLI [:" + m_pUser->GetStatusPrefix() +
753
0
                     ((sModule.empty()) ? "status" : sModule) + "!" +
754
0
                     ((sModule.empty()) ? "status" : sModule) +
755
0
                     "@znc.in PRIVMSG "
756
0
              << GetNick() << " :" << sLine << "]");
757
758
0
    VCString vsLines;
759
0
    sLine.Split("\n", vsLines);
760
0
    for (const CString& s : vsLines) {
761
0
        Write(":" + m_pUser->GetStatusPrefix() +
762
0
              ((sModule.empty()) ? "status" : sModule) + "!" +
763
0
              ((sModule.empty()) ? "status" : sModule) + "@znc.in PRIVMSG " +
764
0
              GetNick() + " :" + s + "\r\n");
765
0
    }
766
0
}
767
768
0
CString CClient::GetNick(bool bAllowIRCNick) const {
769
0
    CString sRet;
770
771
0
    const CIRCSock* pSock = GetIRCSock();
772
0
    if (bAllowIRCNick && pSock && pSock->IsAuthed()) {
773
0
        sRet = pSock->GetNick();
774
0
    }
775
776
0
    return (sRet.empty()) ? m_sNick : sRet;
777
0
}
778
779
0
CString CClient::GetNickMask() const {
780
0
    if (GetIRCSock() && GetIRCSock()->IsAuthed()) {
781
0
        return GetIRCSock()->GetNickMask();
782
0
    }
783
784
0
    CString sHost =
785
0
        m_pNetwork ? m_pNetwork->GetBindHost() : m_pUser->GetBindHost();
786
0
    if (sHost.empty()) {
787
0
        sHost = "irc.znc.in";
788
0
    }
789
790
0
    return GetNick() + "!" +
791
0
           (m_pNetwork ? m_pNetwork->GetIdent() : m_pUser->GetIdent()) + "@" +
792
0
           sHost;
793
0
}
794
795
0
bool CClient::IsValidIdentifier(const CString& sIdentifier) {
796
    // ^[-\w]+$
797
798
0
    if (sIdentifier.empty()) {
799
0
        return false;
800
0
    }
801
802
0
    const char* p = sIdentifier.c_str();
803
0
    while (*p) {
804
0
        if (*p != '_' && *p != '-' && !isalnum(*p)) {
805
0
            return false;
806
0
        }
807
808
0
        p++;
809
0
    }
810
811
0
    return true;
812
0
}
813
814
0
void CClient::RespondCap(const CString& sResponse) {
815
0
    PutClient(":irc.znc.in CAP " + GetNick() + " " + sResponse);
816
0
}
817
818
template <typename ManyStrings>
819
0
static VCString MultiLine(const ManyStrings& ssCaps) {
820
0
    VCString vsRes = {""};
821
0
    for (const CString& sCap : ssCaps) {
822
0
        if (vsRes.back().length() + sCap.length() > 400) {
823
0
            vsRes.push_back(sCap);
824
0
        } else {
825
0
            if (!vsRes.back().empty()) {
826
0
                vsRes.back() += " ";
827
0
            }
828
0
            vsRes.back() += sCap;
829
0
        }
830
0
    }
831
0
    return vsRes;
832
0
}
Unexecuted instantiation: Client.cpp:std::__1::vector<CString, std::__1::allocator<CString> > MultiLine<std::__1::vector<CString, std::__1::allocator<CString> > >(std::__1::vector<CString, std::__1::allocator<CString> > const&)
Unexecuted instantiation: Client.cpp:std::__1::vector<CString, std::__1::allocator<CString> > MultiLine<std::__1::set<CString, std::__1::less<CString>, std::__1::allocator<CString> > >(std::__1::set<CString, std::__1::less<CString>, std::__1::allocator<CString> > const&)
833
834
const std::map<CString, std::function<void(CClient*, bool bVal)>>&
835
0
CClient::CoreCaps() {
836
0
    static const std::map<CString, std::function<void(CClient*, bool bVal)>>
837
0
        mCoreCaps = [] {
838
0
            std::map<CString, std::function<void(CClient*, bool bVal)>>
839
0
                mCoreCaps = {
840
0
                    {"multi-prefix",
841
0
                     [](CClient* pClient, bool bVal) {
842
0
                         pClient->m_bNamesx = bVal;
843
0
                     }},
844
0
                    {"userhost-in-names",
845
0
                     [](CClient* pClient, bool bVal) {
846
0
                         pClient->m_bUHNames = bVal;
847
0
                     }},
848
0
                    {"echo-message",
849
0
                     [](CClient* pClient, bool bVal) {
850
0
                         pClient->m_bEchoMessage = bVal;
851
0
                     }},
852
0
                    {"message-tags",
853
0
                     [](CClient* pClient, bool bVal) {
854
0
                         pClient->m_bMessageTagCap = bVal;
855
0
                     }},
856
0
                    {"server-time",
857
0
                     [](CClient* pClient, bool bVal) {
858
0
                         pClient->m_bServerTime = bVal;
859
0
                         pClient->SetTagSupport("time", bVal);
860
0
                     }},
861
0
                    {"batch",
862
0
                     [](CClient* pClient, bool bVal) {
863
0
                         pClient->m_bBatch = bVal;
864
0
                         pClient->SetTagSupport("batch", bVal);
865
0
                     }},
866
0
                    {"cap-notify",
867
0
                     [](CClient* pClient, bool bVal) {
868
0
                         pClient->m_bCapNotify = bVal;
869
0
                     }},
870
0
                    {"invite-notify",
871
0
                     [](CClient* pClient, bool bVal) {
872
0
                         pClient->m_bInviteNotify = bVal;
873
0
                     }},
874
0
                    {"chghost", [](CClient* pClient,
875
0
                                   bool bVal) { pClient->m_bChgHost = bVal; }},
876
0
                    {"sasl",
877
0
                     [](CClient* pClient, bool bVal) {
878
0
                         if (pClient->IsDuringSASL() && !bVal) {
879
0
                             pClient->AbortSASL(
880
0
                                 ":irc.znc.in 904 " + pClient->GetNick() +
881
0
                                 " :SASL authentication aborted");
882
0
                         }
883
0
                         pClient->m_bSASLCap = bVal;
884
0
                     }},
885
0
                };
886
887
            // For compatibility with older clients
888
0
            mCoreCaps["znc.in/server-time-iso"] = mCoreCaps["server-time"];
889
0
            mCoreCaps["znc.in/batch"] = mCoreCaps["batch"];
890
0
            mCoreCaps["znc.in/self-message"] = [](CClient* pClient, bool bVal) {
891
0
                pClient->m_bSelfMessage = bVal;
892
0
            };
893
894
0
            return mCoreCaps;
895
0
        }();
896
0
    return mCoreCaps;
897
0
}
898
899
0
void CClient::HandleCap(const CMessage& Message) {
900
0
    CString sSubCmd = Message.GetParam(0);
901
902
0
    if (sSubCmd.Equals("LS")) {
903
0
        m_uCapVersion = std::max(m_uCapVersion, Message.GetParam(1).ToUShort());
904
0
        SCString ssOfferCaps;
905
0
        for (const auto& it : CoreCaps()) {
906
            // TODO figure out a better API for this, including for modules
907
0
            if (HasCap302() && it.first == "sasl") {
908
0
                SCString ssMechanisms = EnumerateSASLMechanisms();
909
0
                if (ssMechanisms.empty()) {
910
                    // See the comment near 908. Here "sasl=" would also have wrong meaning.
911
0
                    ssMechanisms.insert("*");
912
0
                }
913
0
                ssOfferCaps.insert(it.first + "=" +
914
0
                                   CString(",").Join(ssMechanisms.begin(),
915
0
                                                     ssMechanisms.end()));
916
0
            } else {
917
0
                ssOfferCaps.insert(it.first);
918
0
            }
919
0
        }
920
0
        NETWORKMODULECALL(OnClientCapLs(this, ssOfferCaps), GetUser(), GetNetwork(), this, NOTHING);
921
0
        VCString vsFilteredCaps;
922
0
        vsFilteredCaps.reserve(ssOfferCaps.size());
923
0
        for (CString sCap : std::move(ssOfferCaps)) {
924
0
            if (!CZNC::Get().GetClientCapBlacklist().count(
925
0
                    sCap.Token(0, false, "="))) {
926
0
                vsFilteredCaps.push_back(std::move(sCap));
927
0
            }
928
0
        }
929
0
        VCString vsCaps = MultiLine(vsFilteredCaps);
930
0
        m_bInCap = true;
931
0
        if (HasCap302()) {
932
0
            m_bCapNotify = true;
933
0
            for (int i = 0; i < vsCaps.size() - 1; ++i) {
934
0
                RespondCap("LS * :" + vsCaps[i]);
935
0
            }
936
0
            RespondCap("LS :" + vsCaps.back());
937
0
        } else {
938
            // Can't send more than one line of caps :(
939
0
            RespondCap("LS :" + vsCaps.front());
940
0
        }
941
0
    } else if (sSubCmd.Equals("END")) {
942
0
        m_bInCap = false;
943
0
        if (!IsAttached()) {
944
0
            if (IsDuringSASL()) {
945
0
                AbortSASL(":irc.znc.in 904 " + GetNick() +
946
0
                          " :SASL authentication aborted");
947
0
            }
948
949
0
            if (m_bGotUser && m_sSASLUser.empty() && !m_bGotPass) {
950
0
                SendRequiredPasswordNotice();
951
0
            } else {
952
0
                AuthUser();
953
0
            }
954
0
        }
955
0
    } else if (sSubCmd.Equals("REQ")) {
956
0
        m_bInCap = true;
957
0
        VCString vsTokens;
958
0
        Message.GetParam(1).Split(" ", vsTokens, false);
959
960
0
        for (const CString& sToken : vsTokens) {
961
0
            bool bVal = true;
962
0
            CString sCap = sToken;
963
0
            if (sCap.TrimPrefix("-")) bVal = false;
964
965
0
            bool bAccepted = false;
966
0
            auto it = CoreCaps().find(sCap);
967
0
            if (CoreCaps().end() != it) {
968
0
                bAccepted = true;
969
0
            }
970
0
            NETWORKMODULECALL(IsClientCapSupported(this, sCap, bVal), GetUser(), GetNetwork(), this,
971
0
                             &bAccepted);
972
973
0
            if (!bAccepted || CZNC::Get().GetClientCapBlacklist().count(sCap)) {
974
                // Some unsupported capability is requested
975
0
                RespondCap("NAK :" + Message.GetParam(1));
976
0
                return;
977
0
            }
978
0
        }
979
980
        // All is fine, we support what was requested
981
0
        for (const CString& sToken : vsTokens) {
982
0
            bool bVal = true;
983
0
            CString sCap = sToken;
984
0
            if (sCap.TrimPrefix("-")) bVal = false;
985
986
0
            auto handler_it = CoreCaps().find(sCap);
987
0
            if (CoreCaps().end() != handler_it) {
988
0
                const auto& handler = handler_it->second;
989
0
                handler(this, bVal);
990
0
            }
991
0
            NETWORKMODULECALL(OnClientCapRequest(this, sCap, bVal), GetUser(), GetNetwork(), this, NOTHING);
992
993
0
            if (bVal) {
994
0
                m_ssAcceptedCaps.insert(sCap);
995
0
            } else {
996
0
                m_ssAcceptedCaps.erase(sCap);
997
0
            }
998
0
        }
999
1000
0
        RespondCap("ACK :" + Message.GetParam(1));
1001
0
    } else if (sSubCmd.Equals("LIST")) {
1002
0
        VCString vsCaps = MultiLine(m_ssAcceptedCaps);
1003
0
        if (HasCap302()) {
1004
0
            for (int i = 0; i < vsCaps.size() - 1; ++i) {
1005
0
                RespondCap("LIST * :" + vsCaps[i]);
1006
0
            }
1007
0
            RespondCap("LIST :" + vsCaps.back());
1008
0
        } else {
1009
            // Can't send more than one line of caps :(
1010
0
            RespondCap("LISTS :" + vsCaps.front());
1011
0
        }
1012
0
    } else {
1013
0
        PutClient(":irc.znc.in 410 " + GetNick() + " " + sSubCmd +
1014
0
                  " :Invalid CAP subcommand");
1015
0
    }
1016
0
}
1017
1018
0
void CClient::ParsePass(const CString& sAuthLine) {
1019
    // [user[@identifier][/network]:]password
1020
1021
0
    const size_t uColon = sAuthLine.find(":");
1022
0
    if (uColon != CString::npos) {
1023
0
        m_sPass = sAuthLine.substr(uColon + 1);
1024
1025
0
        ParseUser(sAuthLine.substr(0, uColon));
1026
0
    } else {
1027
0
        m_sPass = sAuthLine;
1028
0
    }
1029
0
}
1030
1031
0
CString CClient::ParseUser(const CString& sAuthLine) {
1032
    // user[@identifier][/network]
1033
1034
0
    const size_t uSlash = sAuthLine.rfind("/");
1035
0
    if (uSlash != CString::npos) {
1036
0
        m_sNetwork = sAuthLine.substr(uSlash + 1);
1037
1038
0
        ParseIdentifier(sAuthLine.substr(0, uSlash));
1039
0
    } else {
1040
0
        ParseIdentifier(sAuthLine);
1041
0
    }
1042
1043
0
    return m_sUser;
1044
0
}
1045
1046
0
void CClient::ParseIdentifier(const CString& sAuthLine) {
1047
    // user[@identifier]
1048
1049
0
    const size_t uAt = sAuthLine.rfind("@");
1050
0
    if (uAt != CString::npos) {
1051
0
        const CString sId = sAuthLine.substr(uAt + 1);
1052
1053
0
        if (IsValidIdentifier(sId)) {
1054
0
            m_sIdentifier = sId;
1055
0
            m_sUser = sAuthLine.substr(0, uAt);
1056
0
        } else {
1057
0
            m_sUser = sAuthLine;
1058
0
        }
1059
0
    } else {
1060
0
        m_sUser = sAuthLine;
1061
0
    }
1062
0
}
1063
1064
0
void CClient::SetTagSupport(const CString& sTag, bool bState) {
1065
0
    if (bState) {
1066
0
        m_ssSupportedTags.insert(sTag);
1067
0
    } else {
1068
0
        m_ssSupportedTags.erase(sTag);
1069
0
    }
1070
0
}
1071
1072
0
void CClient::NotifyServerDependentCap(const CString& sCap, bool bValue, const CString& sValue) {
1073
0
    if (bValue && !CZNC::Get().GetClientCapBlacklist().count(sCap)) {
1074
0
        if (HasCapNotify()) {
1075
0
            if (HasCap302() && !sValue.empty()) {
1076
0
                PutClient(":irc.znc.in CAP " + GetNick() + " NEW :" + sCap + "=" + sValue);
1077
0
            } else {
1078
0
                PutClient(":irc.znc.in CAP " + GetNick() + " NEW :" + sCap);
1079
0
            }
1080
0
        }
1081
0
    } else {
1082
0
        if (HasCapNotify()) {
1083
0
            PutClient(":irc.znc.in CAP " + GetNick() + " DEL :" + sCap);
1084
0
        }
1085
0
        m_ssAcceptedCaps.erase(sCap);
1086
0
    }
1087
0
}
1088
1089
namespace {
1090
template <typename X, class = void>
1091
struct message_has_text : std::false_type {};
1092
1093
template <typename X>
1094
struct message_has_text<
1095
    X, std::void_t<decltype(std::declval<const X&>().GetText()),
1096
                   decltype(std::declval<X&>().SetText(""))>> : std::true_type {
1097
};
1098
}
1099
1100
template <typename T>
1101
0
void CClient::AddBuffer(const T& Message) {
1102
0
    if (!m_pNetwork) {
1103
0
        return;
1104
0
    }
1105
0
    const CString sTarget = Message.GetTarget();
1106
1107
0
    T Format;
1108
0
    Format.Clone(Message);
1109
0
    Format.SetNick(CNick(_NAMEDFMT(GetNickMask())));
1110
0
    Format.SetTarget(_NAMEDFMT(sTarget));
1111
0
    if constexpr (message_has_text<T>::value) {
1112
0
        Format.SetText("{text}");
1113
0
    }
1114
1115
0
    CString sText;
1116
0
    CChan* pChan = m_pNetwork->FindChan(sTarget);
1117
0
    if (pChan) {
1118
0
        if (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline()) {
1119
0
            if constexpr (message_has_text<T>::value) {
1120
0
                sText = Message.GetText();
1121
0
            }
1122
0
            pChan->AddBuffer(Format, sText);
1123
0
        }
1124
0
    } else if (Message.GetType() != CMessage::Type::Notice) {
1125
0
        if (!m_pUser->AutoClearQueryBuffer() || !m_pNetwork->IsUserOnline()) {
1126
0
            CQuery* pQuery = m_pNetwork->AddQuery(sTarget);
1127
0
            if (pQuery) {
1128
0
                if constexpr (message_has_text<T>::value) {
1129
0
                    sText = Message.GetText();
1130
0
                }
1131
0
                pQuery->AddBuffer(Format, sText);
1132
0
            }
1133
0
        }
1134
0
    }
1135
0
}
Unexecuted instantiation: void CClient::AddBuffer<CActionMessage>(CActionMessage const&)
Unexecuted instantiation: void CClient::AddBuffer<CNoticeMessage>(CNoticeMessage const&)
Unexecuted instantiation: void CClient::AddBuffer<CTargetMessage>(CTargetMessage const&)
Unexecuted instantiation: void CClient::AddBuffer<CTextMessage>(CTextMessage const&)
1136
1137
0
void CClient::EchoMessage(const CMessage& Message) {
1138
0
    CMessage EchoedMessage = Message;
1139
0
    for (CClient* pClient : GetClients()) {
1140
0
        if (pClient->HasEchoMessage() ||
1141
0
            (pClient != this && ((m_pNetwork && m_pNetwork->IsChan(Message.GetParam(0))) ||
1142
0
                                 pClient->HasSelfMessage()))) {
1143
0
            EchoedMessage.SetNick(GetNickMask());
1144
0
            pClient->PutClient(EchoedMessage);
1145
0
        }
1146
0
    }
1147
0
}
1148
1149
0
set<CChan*> CClient::MatchChans(const CString& sPatterns) const {
1150
0
    if (!m_pNetwork) {
1151
0
        return {};
1152
0
    }
1153
0
    VCString vsPatterns;
1154
0
    sPatterns.Replace_n(",", " ")
1155
0
        .Split(" ", vsPatterns, false, "", "", true, true);
1156
1157
0
    set<CChan*> sChans;
1158
0
    for (const CString& sPattern : vsPatterns) {
1159
0
        vector<CChan*> vChans = m_pNetwork->FindChans(sPattern);
1160
0
        sChans.insert(vChans.begin(), vChans.end());
1161
0
    }
1162
0
    return sChans;
1163
0
}
1164
1165
0
unsigned int CClient::AttachChans(const std::set<CChan*>& sChans) {
1166
0
    unsigned int uAttached = 0;
1167
0
    for (CChan* pChan : sChans) {
1168
0
        if (!pChan->IsDetached()) continue;
1169
0
        uAttached++;
1170
0
        pChan->AttachUser();
1171
0
    }
1172
0
    return uAttached;
1173
0
}
1174
1175
0
unsigned int CClient::DetachChans(const std::set<CChan*>& sChans) {
1176
0
    unsigned int uDetached = 0;
1177
0
    for (CChan* pChan : sChans) {
1178
0
        if (pChan->IsDetached()) continue;
1179
0
        uDetached++;
1180
0
        pChan->DetachUser();
1181
0
    }
1182
0
    return uDetached;
1183
0
}
1184
1185
0
bool CClient::OnActionMessage(CActionMessage& Message) {
1186
0
    CString sTargets = Message.GetTarget();
1187
1188
0
    VCString vTargets;
1189
0
    sTargets.Split(",", vTargets, false);
1190
1191
0
    for (CString& sTarget : vTargets) {
1192
0
        Message.SetTarget(sTarget);
1193
0
        if (m_pNetwork) {
1194
            // May be nullptr.
1195
0
            Message.SetChan(m_pNetwork->FindChan(sTarget));
1196
0
        }
1197
1198
0
        bool bContinue = false;
1199
0
        NETWORKMODULECALL(OnUserActionMessage(Message), m_pUser, m_pNetwork,
1200
0
                          this, &bContinue);
1201
0
        if (bContinue) continue;
1202
1203
0
        if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) {
1204
0
            EchoMessage(Message);
1205
0
            continue;
1206
0
        }
1207
1208
0
        if (m_pNetwork) {
1209
0
            AddBuffer(Message);
1210
0
            EchoMessage(Message);
1211
0
            PutIRCStripping(Message);
1212
0
        }
1213
0
    }
1214
1215
0
    return true;
1216
0
}
1217
1218
0
void CClient::SendSASLChallenge(CString sMessage) {
1219
0
    constexpr size_t uMaxSASLMsgLength = 400u;
1220
0
    sMessage.Base64Encode();
1221
0
    size_t uChallengeSize = sMessage.length();
1222
1223
0
    for (int i = 0; i < uChallengeSize; i += uMaxSASLMsgLength) {
1224
0
        CString sMsgPart = sMessage.substr(i, uMaxSASLMsgLength);
1225
0
        PutClient("AUTHENTICATE " + sMsgPart);
1226
0
    }
1227
0
    if (uChallengeSize % uMaxSASLMsgLength == 0) {
1228
0
        PutClient("AUTHENTICATE +");
1229
0
    }
1230
0
}
1231
1232
0
void CClient::OnAuthenticateMessage(const CAuthenticateMessage& Message) {
1233
0
    if (!m_bSASLCap) {
1234
0
        PutClient(":irc.znc.in 904 " + GetNick() + " :SASL not enabled");
1235
0
        return;
1236
0
    }
1237
1238
0
    if (!m_sSASLUser.empty() || IsAttached()) {
1239
0
        PutClient(":irc.znc.in 907 " + GetNick() +
1240
0
                  " :You have already authenticated using SASL");
1241
0
        return;
1242
0
    }
1243
1244
0
    auto SASLReset = [this]() {
1245
0
        m_sSASLMechanism = "";
1246
0
        m_sSASLBuffer = "";
1247
0
    };
1248
0
    CString sMessage = Message.GetText();
1249
1250
0
    if (sMessage.Equals("*")) {
1251
0
        AbortSASL(":irc.znc.in 906 " + GetNick() +
1252
0
                  " :SASL authentication aborted");
1253
0
        return;
1254
0
    }
1255
1256
0
    constexpr size_t uMaxSASLMsgLength = 400u;
1257
0
    if (sMessage.length() > uMaxSASLMsgLength) {
1258
0
        AbortSASL(":irc.znc.in 905 " + GetNick() + " :SASL message too long");
1259
0
        return;
1260
0
    }
1261
1262
0
    if (!IsDuringSASL()) {
1263
0
        if (m_ssPreviouslyFailedSASLMechanisms.find(sMessage) !=
1264
0
            m_ssPreviouslyFailedSASLMechanisms.end()) {
1265
            // This prevents the client from brute forcing multiple passwords
1266
            // on the same connection.
1267
0
            PutClient(":irc.znc.in 904 " + GetNick() +
1268
0
                      " :SASL authentication failed");
1269
0
            SASLReset();
1270
0
            return;
1271
0
        }
1272
0
        SCString ssMechanisms = EnumerateSASLMechanisms();
1273
0
        if (ssMechanisms.find(sMessage) == ssMechanisms.end()) {
1274
0
            if (ssMechanisms.empty()) {
1275
                // If it happens that no mechanisms are available, an empty
1276
                // string will cause issues with IRC frames. Probably we should
1277
                // disable the whole 'sasl' cap, but that becomes complicated
1278
                // because need to track changes to the list of available caps
1279
                // (modules adding new mechanisms) and send cap-notify. This
1280
                // hack is simpler to do. And if a client decides to use
1281
                // actually use this fake '*' mechanism, they probably won't
1282
                // succeed anyway.
1283
0
                PutClient(":irc.znc.in 908 " + GetNick() +
1284
0
                          " * :No SASL mechanisms are available");
1285
0
            } else {
1286
0
                PutClient(":irc.znc.in 908 " + GetNick() + " " +
1287
0
                          CString(",").Join(ssMechanisms.begin(),
1288
0
                                            ssMechanisms.end()) +
1289
0
                          " :are available SASL mechanisms");
1290
0
            }
1291
0
            PutClient(":irc.znc.in 904 " + GetNick() +
1292
0
                      " :SASL authentication failed");
1293
0
            SASLReset();
1294
1295
0
            return;
1296
0
        }
1297
1298
0
        m_sSASLMechanism = sMessage;
1299
1300
0
        bool bResult = false;
1301
0
        CString sChallenge;
1302
0
        _GLOBALMODULECALL(
1303
0
            OnClientSASLServerInitialChallenge(m_sSASLMechanism, sChallenge),
1304
0
            nullptr, nullptr, this, &bResult);
1305
0
        if (!bResult) {
1306
0
            SendSASLChallenge(std::move(sChallenge));
1307
0
        }
1308
0
        return;
1309
0
    }
1310
1311
0
    if (m_sSASLBuffer.length() + sMessage.length() > 10 * 1024) {
1312
0
        AbortSASL(":irc.znc.in 904 " + GetNick() + " :SASL response too long");
1313
0
        return;
1314
0
    }
1315
1316
0
    if (sMessage.length() == uMaxSASLMsgLength) {
1317
0
        m_sSASLBuffer += sMessage;
1318
0
        return;
1319
0
    }
1320
1321
0
    if (sMessage != "+") {
1322
0
        m_sSASLBuffer += sMessage;
1323
0
    }
1324
1325
0
    m_sSASLBuffer.Base64Decode();
1326
1327
0
    bool bResult = false;
1328
1329
0
    _GLOBALMODULECALL(
1330
0
        OnClientSASLAuthenticate(m_sSASLMechanism, m_sSASLBuffer),
1331
0
        nullptr, nullptr, this, &bResult);
1332
0
    m_sSASLBuffer.clear();
1333
0
}
1334
1335
0
void CClient::AbortSASL(const CString& sFullIRCLine) {
1336
0
    PutClient(sFullIRCLine);
1337
0
    _GLOBALMODULECALL(OnClientSASLAborted(), nullptr, nullptr, this, NOTHING);
1338
0
    m_sSASLMechanism = "";
1339
0
    m_sSASLBuffer = "";
1340
0
}
1341
1342
0
void CClient::RefuseSASLLogin(const CString& sReason) {
1343
0
    PutClient(":irc.znc.in 904 " + GetNick() + " :" + sReason);
1344
0
    m_ssPreviouslyFailedSASLMechanisms.insert(m_sSASLMechanism);
1345
0
    m_sSASLMechanism = "";
1346
0
    m_sSASLBuffer = "";
1347
0
    _GLOBALMODULECALL(OnFailedLogin("", GetRemoteIP()), nullptr, nullptr, this,
1348
0
                      NOTHING);
1349
0
}
1350
1351
0
void CClient::AcceptSASLLogin(CUser& User) {
1352
0
    PutClient(":irc.znc.in 900 " + GetNick() + " " + GetNick() + "!" +
1353
0
              User.GetIdent() + "@" + GetRemoteIP() + " " + User.GetUsername() +
1354
0
              " :You are now logged in as " + User.GetUsername());
1355
0
    PutClient(":irc.znc.in 903 " + GetNick() +
1356
0
              " :SASL authentication successful");
1357
0
    m_sSASLMechanism = "";
1358
0
    m_sSASLBuffer = "";
1359
0
    m_sSASLUser = User.GetUsername();
1360
0
}
1361
1362
0
SCString CClient::EnumerateSASLMechanisms() const {
1363
0
    SCString ssMechanisms;
1364
    // FIXME Currently GetClient()==nullptr due to const
1365
0
    GLOBALMODULECALL(OnClientGetSASLMechanisms(ssMechanisms), NOTHING);
1366
0
    return ssMechanisms;
1367
0
}
1368
1369
0
bool CClient::OnCTCPMessage(CCTCPMessage& Message) {
1370
0
    CString sTargets = Message.GetTarget();
1371
1372
0
    VCString vTargets;
1373
0
    sTargets.Split(",", vTargets, false);
1374
1375
0
    if (Message.IsReply()) {
1376
0
        CString sCTCP = Message.GetText();
1377
0
        if (sCTCP.Token(0) == "VERSION") {
1378
            // There are 2 different scenarios:
1379
            //
1380
            // a) CTCP reply for VERSION is not set.
1381
            // 1. ZNC receives CTCP VERSION from someone
1382
            // 2. ZNC forwards CTCP VERSION to client
1383
            // 3. Client replies with something
1384
            // 4. ZNC adds itself to the reply
1385
            // 5. ZNC sends the modified reply to whoever asked
1386
            //
1387
            // b) CTCP reply for VERSION is set.
1388
            // 1. ZNC receives CTCP VERSION from someone
1389
            // 2. ZNC replies with the configured reply (or just drops it if
1390
            //    empty), without forwarding anything to client
1391
            // 3. Client does not see any CTCP request, and does not reply
1392
            //
1393
            // So, if user doesn't want "via ZNC" in CTCP VERSION reply, they
1394
            // can set custom reply.
1395
            //
1396
            // See more bikeshedding at github issues #820 and #1012
1397
0
            Message.SetText(sCTCP + " via " + CZNC::GetTag(false));
1398
0
        }
1399
0
    }
1400
1401
0
    for (CString& sTarget : vTargets) {
1402
0
        Message.SetTarget(sTarget);
1403
0
        if (m_pNetwork) {
1404
            // May be nullptr.
1405
0
            Message.SetChan(m_pNetwork->FindChan(sTarget));
1406
0
        }
1407
1408
0
        bool bContinue = false;
1409
0
        if (Message.IsReply()) {
1410
0
            NETWORKMODULECALL(OnUserCTCPReplyMessage(Message), m_pUser,
1411
0
                              m_pNetwork, this, &bContinue);
1412
0
        } else {
1413
0
            NETWORKMODULECALL(OnUserCTCPMessage(Message), m_pUser, m_pNetwork,
1414
0
                              this, &bContinue);
1415
0
        }
1416
0
        if (bContinue) continue;
1417
1418
0
        if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) {
1419
0
            continue;
1420
0
        }
1421
1422
0
        if (!GetIRCSock()) {
1423
            // Some lagmeters do a NOTICE to their own nick, ignore those.
1424
0
            if (!sTarget.Equals(m_sNick))
1425
0
                PutStatus(t_f(
1426
0
                    "Your CTCP to {1} got lost, you are not connected to IRC!")(
1427
0
                    Message.GetTarget()));
1428
0
            continue;
1429
0
        }
1430
1431
0
        if (m_pNetwork) {
1432
0
            PutIRCStripping(Message);
1433
0
        }
1434
0
    }
1435
1436
0
    return true;
1437
0
}
1438
1439
0
bool CClient::OnJoinMessage(CJoinMessage& Message) {
1440
0
    CString sChans = Message.GetTarget();
1441
0
    CString sKeys = Message.GetKey();
1442
1443
0
    VCString vsChans;
1444
0
    sChans.Split(",", vsChans, false);
1445
0
    sChans.clear();
1446
1447
0
    VCString vsKeys;
1448
0
    sKeys.Split(",", vsKeys, true);
1449
0
    sKeys.clear();
1450
1451
0
    for (unsigned int a = 0; a < vsChans.size(); a++) {
1452
0
        Message.SetTarget(vsChans[a]);
1453
0
        Message.SetKey((a < vsKeys.size()) ? vsKeys[a] : "");
1454
0
        if (m_pNetwork) {
1455
            // May be nullptr.
1456
0
            Message.SetChan(m_pNetwork->FindChan(vsChans[a]));
1457
0
        }
1458
0
        bool bContinue = false;
1459
0
        NETWORKMODULECALL(OnUserJoinMessage(Message), m_pUser, m_pNetwork, this,
1460
0
                          &bContinue);
1461
0
        if (bContinue) continue;
1462
1463
0
        CString sChannel = Message.GetTarget();
1464
0
        CString sKey = Message.GetKey();
1465
1466
0
        if (m_pNetwork) {
1467
0
            CChan* pChan = m_pNetwork->FindChan(sChannel);
1468
0
            if (pChan) {
1469
0
                if (pChan->IsDetached())
1470
0
                    pChan->AttachUser(this);
1471
0
                else
1472
0
                    pChan->JoinUser(sKey);
1473
0
                continue;
1474
0
            } else if (!sChannel.empty()) {
1475
0
                pChan = new CChan(sChannel, m_pNetwork, false);
1476
0
                if (m_pNetwork->AddChan(pChan)) {
1477
0
                    pChan->SetKey(sKey);
1478
0
                }
1479
0
            }
1480
1481
0
            if (!sChannel.empty() && m_pNetwork->IsIRCConnected()) {
1482
0
                sChans += (sChans.empty()) ? sChannel : CString("," + sChannel);
1483
1484
0
                if (!vsKeys.empty()) {
1485
0
                    sKeys += (sKeys.empty()) ? sKey : CString("," + sKey);
1486
0
                }
1487
0
            }
1488
0
        }
1489
0
    }
1490
1491
0
    Message.SetTarget(sChans);
1492
0
    Message.SetKey(sKeys);
1493
1494
0
    return sChans.empty();
1495
0
}
1496
1497
0
bool CClient::OnModeMessage(CModeMessage& Message) {
1498
0
    CString sTarget = Message.GetTarget();
1499
1500
0
    if (m_pNetwork && m_pNetwork->IsChan(sTarget) && !Message.HasModes()) {
1501
        // If we are on that channel and already received a
1502
        // /mode reply from the server, we can answer this
1503
        // request ourself.
1504
1505
0
        CChan* pChan = m_pNetwork->FindChan(sTarget);
1506
0
        if (pChan && pChan->IsOn() && !pChan->GetModeString().empty()) {
1507
0
            PutClient(":" + m_pNetwork->GetIRCServer() + " 324 " + GetNick() +
1508
0
                      " " + sTarget + " " + pChan->GetModeString());
1509
0
            if (pChan->GetCreationDate() > 0) {
1510
0
                PutClient(":" + m_pNetwork->GetIRCServer() + " 329 " +
1511
0
                          GetNick() + " " + sTarget + " " +
1512
0
                          CString(pChan->GetCreationDate()));
1513
0
            }
1514
0
            return true;
1515
0
        }
1516
0
    }
1517
1518
0
    return false;
1519
0
}
1520
1521
0
bool CClient::OnNoticeMessage(CNoticeMessage& Message) {
1522
0
    CString sTargets = Message.GetTarget();
1523
1524
0
    VCString vTargets;
1525
0
    sTargets.Split(",", vTargets, false);
1526
1527
0
    for (CString& sTarget : vTargets) {
1528
0
        Message.SetTarget(sTarget);
1529
0
        if (m_pNetwork) {
1530
            // May be nullptr.
1531
0
            Message.SetChan(m_pNetwork->FindChan(sTarget));
1532
0
        }
1533
1534
0
        if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) {
1535
0
            EchoMessage(Message);
1536
1537
0
            if (!sTarget.Equals("status")) {
1538
0
                CALLMOD(sTarget, this, m_pUser, m_pNetwork,
1539
0
                        OnModNotice(Message.GetText()));
1540
0
            }
1541
0
            continue;
1542
0
        }
1543
1544
0
        bool bContinue = false;
1545
0
        NETWORKMODULECALL(OnUserNoticeMessage(Message), m_pUser, m_pNetwork,
1546
0
                          this, &bContinue);
1547
0
        if (bContinue) continue;
1548
1549
0
        if (!GetIRCSock()) {
1550
            // Some lagmeters do a NOTICE to their own nick, ignore those.
1551
0
            if (!sTarget.Equals(m_sNick))
1552
0
                PutStatus(
1553
0
                    t_f("Your notice to {1} got lost, you are not connected to "
1554
0
                        "IRC!")(Message.GetTarget()));
1555
0
            continue;
1556
0
        }
1557
1558
0
        if (m_pNetwork) {
1559
0
            AddBuffer(Message);
1560
0
            EchoMessage(Message);
1561
0
            PutIRCStripping(Message);
1562
0
        }
1563
0
    }
1564
1565
0
    return true;
1566
0
}
1567
1568
0
bool CClient::OnPartMessage(CPartMessage& Message) {
1569
0
    CString sChans = Message.GetTarget();
1570
1571
0
    VCString vsChans;
1572
0
    sChans.Split(",", vsChans, false);
1573
0
    sChans.clear();
1574
1575
0
    for (CString& sChan : vsChans) {
1576
0
        bool bContinue = false;
1577
0
        Message.SetTarget(sChan);
1578
0
        if (m_pNetwork) {
1579
            // May be nullptr.
1580
0
            Message.SetChan(m_pNetwork->FindChan(sChan));
1581
0
        }
1582
0
        NETWORKMODULECALL(OnUserPartMessage(Message), m_pUser, m_pNetwork, this,
1583
0
                          &bContinue);
1584
0
        if (bContinue) continue;
1585
1586
0
        sChan = Message.GetTarget();
1587
1588
0
        CChan* pChan = m_pNetwork ? m_pNetwork->FindChan(sChan) : nullptr;
1589
1590
0
        if (pChan && !pChan->IsOn()) {
1591
0
            PutStatusNotice(t_f("Removing channel {1}")(sChan));
1592
0
            m_pNetwork->DelChan(sChan);
1593
0
        } else {
1594
0
            sChans += (sChans.empty()) ? sChan : CString("," + sChan);
1595
0
        }
1596
0
    }
1597
1598
0
    if (sChans.empty()) {
1599
0
        return true;
1600
0
    }
1601
1602
0
    Message.SetTarget(sChans);
1603
1604
0
    return false;
1605
0
}
1606
1607
0
bool CClient::OnPingMessage(CMessage& Message) {
1608
    // All PONGs are generated by ZNC. We will still forward this to
1609
    // the ircd, but all PONGs from irc will be blocked.
1610
0
    if (!Message.GetParams().empty())
1611
0
        PutClient(":irc.znc.in PONG irc.znc.in " + Message.GetParamsColon(0));
1612
0
    else
1613
0
        PutClient(":irc.znc.in PONG irc.znc.in");
1614
0
    return false;
1615
0
}
1616
1617
0
bool CClient::OnPongMessage(CMessage& Message) {
1618
    // Block PONGs, we already responded to the pings
1619
0
    return true;
1620
0
}
1621
1622
0
bool CClient::OnQuitMessage(CQuitMessage& Message) {
1623
0
    bool bReturn = false;
1624
0
    NETWORKMODULECALL(OnUserQuitMessage(Message), m_pUser, m_pNetwork, this,
1625
0
                      &bReturn);
1626
0
    if (!bReturn) {
1627
0
        Close(Csock::CLT_AFTERWRITE);  // Treat a client quit as a detach
1628
0
    }
1629
    // Don't forward this msg.  We don't want the client getting us
1630
    // disconnected.
1631
0
    return true;
1632
0
}
1633
1634
0
bool CClient::OnTagMessage(CTargetMessage& Message) {
1635
0
    CString sTargets = Message.GetTarget();
1636
1637
0
    VCString vTargets;
1638
0
    sTargets.Split(",", vTargets, false);
1639
1640
0
    for (CString& sTarget : vTargets) {
1641
0
        Message.SetTarget(sTarget);
1642
0
        if (m_pNetwork) {
1643
            // May be nullptr.
1644
0
            Message.SetChan(m_pNetwork->FindChan(sTarget));
1645
0
        }
1646
1647
0
        bool bContinue = false;
1648
0
        NETWORKMODULECALL(OnUserTagMessage(Message), m_pUser, m_pNetwork,
1649
0
                          this, &bContinue);
1650
0
        if (bContinue) continue;
1651
1652
0
        if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) {
1653
0
            EchoMessage(Message);
1654
0
            continue;
1655
0
        }
1656
1657
0
        if (m_pNetwork) {
1658
0
            AddBuffer(Message);
1659
0
            EchoMessage(Message);
1660
0
            if (GetIRCSock() && GetIRCSock()->HasMessageTagCap()) {
1661
0
                PutIRCStripping(Message);
1662
0
            }
1663
0
        }
1664
0
    }
1665
1666
0
    return true;
1667
0
}
1668
1669
0
bool CClient::OnTextMessage(CTextMessage& Message) {
1670
0
    CString sTargets = Message.GetTarget();
1671
1672
0
    VCString vTargets;
1673
0
    sTargets.Split(",", vTargets, false);
1674
1675
0
    for (CString& sTarget : vTargets) {
1676
0
        Message.SetTarget(sTarget);
1677
0
        if (m_pNetwork) {
1678
            // May be nullptr.
1679
0
            Message.SetChan(m_pNetwork->FindChan(sTarget));
1680
0
        }
1681
1682
0
        if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) {
1683
0
            EchoMessage(Message);
1684
1685
0
            if (sTarget.Equals("status")) {
1686
0
                CString sMsg = Message.GetText();
1687
0
                UserCommand(sMsg);
1688
0
            } else {
1689
0
                CALLMOD(sTarget, this, m_pUser, m_pNetwork,
1690
0
                        OnModCommand(Message.GetText()));
1691
0
            }
1692
0
            continue;
1693
0
        }
1694
1695
0
        bool bContinue = false;
1696
0
        NETWORKMODULECALL(OnUserTextMessage(Message), m_pUser, m_pNetwork, this,
1697
0
                          &bContinue);
1698
0
        if (bContinue) continue;
1699
1700
0
        if (!GetIRCSock()) {
1701
            // Some lagmeters do a PRIVMSG to their own nick, ignore those.
1702
0
            if (!sTarget.Equals(m_sNick))
1703
0
                PutStatus(
1704
0
                    t_f("Your message to {1} got lost, you are not connected "
1705
0
                        "to IRC!")(Message.GetTarget()));
1706
0
            continue;
1707
0
        }
1708
1709
0
        if (m_pNetwork) {
1710
0
            AddBuffer(Message);
1711
0
            EchoMessage(Message);
1712
0
            PutIRCStripping(Message);
1713
0
        }
1714
0
    }
1715
1716
0
    return true;
1717
0
}
1718
1719
0
bool CClient::OnTopicMessage(CTopicMessage& Message) {
1720
0
    bool bReturn = false;
1721
0
    CString sChan = Message.GetTarget();
1722
0
    CString sTopic = Message.GetTopic();
1723
0
    if (m_pNetwork) {
1724
        // May be nullptr.
1725
0
        Message.SetChan(m_pNetwork->FindChan(sChan));
1726
0
    }
1727
1728
0
    if (!sTopic.empty()) {
1729
0
        NETWORKMODULECALL(OnUserTopicMessage(Message), m_pUser, m_pNetwork,
1730
0
                          this, &bReturn);
1731
0
    } else {
1732
0
        NETWORKMODULECALL(OnUserTopicRequest(sChan), m_pUser, m_pNetwork, this,
1733
0
                          &bReturn);
1734
0
        Message.SetTarget(sChan);
1735
0
    }
1736
1737
0
    return bReturn;
1738
0
}
1739
1740
0
bool CClient::OnOtherMessage(CMessage& Message) {
1741
0
    const CString& sCommand = Message.GetCommand();
1742
1743
0
    if (sCommand.Equals("ZNC")) {
1744
0
        CString sTarget = Message.GetParam(0);
1745
0
        CString sModCommand;
1746
1747
0
        if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) {
1748
0
            sModCommand = Message.GetParamsColon(1);
1749
0
        } else {
1750
0
            sTarget = "status";
1751
0
            sModCommand = Message.GetParamsColon(0);
1752
0
        }
1753
1754
0
        if (sTarget.Equals("status")) {
1755
0
            if (sModCommand.empty())
1756
0
                PutStatus(t_s("Hello. How may I help you?"));
1757
0
            else
1758
0
                UserCommand(sModCommand);
1759
0
        } else {
1760
0
            if (sModCommand.empty())
1761
0
                CALLMOD(sTarget, this, m_pUser, m_pNetwork,
1762
0
                        PutModule(t_s("Hello. How may I help you?")))
1763
0
            else
1764
0
                CALLMOD(sTarget, this, m_pUser, m_pNetwork,
1765
0
                        OnModCommand(sModCommand))
1766
0
        }
1767
0
        return true;
1768
0
    } else if (sCommand.Equals("ATTACH")) {
1769
0
        if (!m_pNetwork) {
1770
0
            return true;
1771
0
        }
1772
1773
0
        CString sPatterns = Message.GetParamsColon(0);
1774
1775
0
        if (sPatterns.empty()) {
1776
0
            PutStatusNotice(t_s("Usage: /attach <#chans>"));
1777
0
            return true;
1778
0
        }
1779
1780
0
        set<CChan*> sChans = MatchChans(sPatterns);
1781
0
        unsigned int uAttachedChans = AttachChans(sChans);
1782
1783
0
        PutStatusNotice(t_p("There was {1} channel matching [{2}]",
1784
0
                            "There were {1} channels matching [{2}]",
1785
0
                            sChans.size())(sChans.size(), sPatterns));
1786
0
        PutStatusNotice(t_p("Attached {1} channel", "Attached {1} channels",
1787
0
                            uAttachedChans)(uAttachedChans));
1788
1789
0
        return true;
1790
0
    } else if (sCommand.Equals("DETACH")) {
1791
0
        if (!m_pNetwork) {
1792
0
            return true;
1793
0
        }
1794
1795
0
        CString sPatterns = Message.GetParamsColon(0);
1796
1797
0
        if (sPatterns.empty()) {
1798
0
            PutStatusNotice(t_s("Usage: /detach <#chans>"));
1799
0
            return true;
1800
0
        }
1801
1802
0
        set<CChan*> sChans = MatchChans(sPatterns);
1803
0
        unsigned int uDetached = DetachChans(sChans);
1804
1805
0
        PutStatusNotice(t_p("There was {1} channel matching [{2}]",
1806
0
                            "There were {1} channels matching [{2}]",
1807
0
                            sChans.size())(sChans.size(), sPatterns));
1808
0
        PutStatusNotice(t_p("Detached {1} channel", "Detached {1} channels",
1809
0
                            uDetached)(uDetached));
1810
1811
0
        return true;
1812
0
    } else if (sCommand.Equals("PROTOCTL")) {
1813
0
        for (const CString& sParam : Message.GetParams()) {
1814
0
            if (sParam == "NAMESX") {
1815
0
                m_bNamesx = true;
1816
0
            } else if (sParam == "UHNAMES") {
1817
0
                m_bUHNames = true;
1818
0
            }
1819
0
        }
1820
0
        return true;  // If the server understands it, we already enabled namesx
1821
                      // / uhnames
1822
0
    }
1823
1824
0
    return false;
1825
0
}