Coverage Report

Created: 2023-09-25 06:17

/src/znc/src/Client.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (C) 2004-2023 ZNC, see the NOTICE file for details.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
17
#include <znc/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
0
CClient::~CClient() {
79
0
    if (m_spAuth) {
80
0
        CClientAuth* pAuth = (CClientAuth*)&(*m_spAuth);
81
0
        pAuth->Invalidate();
82
0
    }
83
0
    if (m_pUser != nullptr) {
84
0
        m_pUser->AddBytesRead(GetBytesRead());
85
0
        m_pUser->AddBytesWritten(GetBytesWritten());
86
0
    }
87
0
}
88
89
0
void CClient::SendRequiredPasswordNotice() {
90
0
    PutClient(":irc.znc.in 464 " + GetNick() + " :Password required");
91
0
    PutClient(
92
0
        ":irc.znc.in NOTICE " + GetNick() + " :*** "
93
0
        "You need to send your password. "
94
0
        "Configure your client to send a server password.");
95
0
    PutClient(
96
0
        ":irc.znc.in NOTICE " + GetNick() + " :*** "
97
0
        "To connect now, you can use /quote PASS <username>:<password>, "
98
0
        "or /quote PASS <username>/<network>:<password> to connect to a "
99
0
        "specific network.");
100
0
}
101
102
0
void CClient::ReadLine(const CString& sData) {
103
0
    CLanguageScope user_lang(GetUser() ? GetUser()->GetLanguage() : "");
104
0
    CString sLine = sData;
105
106
0
    sLine.Replace("\n", "");
107
0
    sLine.Replace("\r", "");
108
109
0
    DEBUG("(" << GetFullName() << ") CLI -> ZNC ["
110
0
        << CDebug::Filter(sLine) << "]");
111
112
0
    bool bReturn = false;
113
0
    if (IsAttached()) {
114
0
        NETWORKMODULECALL(OnUserRaw(sLine), m_pUser, m_pNetwork, this,
115
0
                          &bReturn);
116
0
    } else {
117
0
        GLOBALMODULECALL(OnUnknownUserRaw(this, sLine), &bReturn);
118
0
    }
119
0
    if (bReturn) return;
120
121
0
    CMessage Message(sLine);
122
0
    Message.SetClient(this);
123
124
0
    if (IsAttached()) {
125
0
        NETWORKMODULECALL(OnUserRawMessage(Message), m_pUser, m_pNetwork, this,
126
0
                          &bReturn);
127
0
    } else {
128
0
        GLOBALMODULECALL(OnUnknownUserRawMessage(Message), &bReturn);
129
0
    }
130
0
    if (bReturn) return;
131
132
0
    CString sCommand = Message.GetCommand();
133
134
0
    if (!IsAttached()) {
135
        // The following commands happen before authentication with ZNC
136
0
        if (sCommand.Equals("PASS")) {
137
0
            m_bGotPass = true;
138
139
0
            CString sAuthLine = Message.GetParam(0);
140
0
            ParsePass(sAuthLine);
141
142
0
            AuthUser();
143
            // Don't forward this msg.  ZNC has already registered us.
144
0
            return;
145
0
        } else if (sCommand.Equals("NICK")) {
146
0
            CString sNick = Message.GetParam(0);
147
148
0
            m_sNick = sNick;
149
0
            m_bGotNick = true;
150
151
0
            AuthUser();
152
            // Don't forward this msg.  ZNC will handle nick changes until auth
153
            // is complete
154
0
            return;
155
0
        } else if (sCommand.Equals("USER")) {
156
0
            CString sAuthLine = Message.GetParam(0);
157
158
0
            if (m_sUser.empty() && !sAuthLine.empty()) {
159
0
                ParseUser(sAuthLine);
160
0
            }
161
162
0
            m_bGotUser = true;
163
0
            if (m_bGotPass) {
164
0
                AuthUser();
165
0
            } else if (!m_bInCap) {
166
0
                SendRequiredPasswordNotice();
167
0
            }
168
169
            // Don't forward this msg.  ZNC has already registered us.
170
0
            return;
171
0
        }
172
0
    }
173
174
0
    if (Message.GetType() == CMessage::Type::Capability) {
175
0
        HandleCap(Message);
176
177
        // Don't let the client talk to the server directly about CAP,
178
        // we don't want anything enabled that ZNC does not support.
179
0
        return;
180
0
    }
181
182
0
    if (!m_pUser) {
183
        // Only CAP, NICK, USER and PASS are allowed before login
184
0
        return;
185
0
    }
186
187
0
    switch (Message.GetType()) {
188
0
        case CMessage::Type::Action:
189
0
            bReturn = OnActionMessage(Message);
190
0
            break;
191
0
        case CMessage::Type::CTCP:
192
0
            bReturn = OnCTCPMessage(Message);
193
0
            break;
194
0
        case CMessage::Type::Join:
195
0
            bReturn = OnJoinMessage(Message);
196
0
            break;
197
0
        case CMessage::Type::Mode:
198
0
            bReturn = OnModeMessage(Message);
199
0
            break;
200
0
        case CMessage::Type::Notice:
201
0
            bReturn = OnNoticeMessage(Message);
202
0
            break;
203
0
        case CMessage::Type::Part:
204
0
            bReturn = OnPartMessage(Message);
205
0
            break;
206
0
        case CMessage::Type::Ping:
207
0
            bReturn = OnPingMessage(Message);
208
0
            break;
209
0
        case CMessage::Type::Pong:
210
0
            bReturn = OnPongMessage(Message);
211
0
            break;
212
0
        case CMessage::Type::Quit:
213
0
            bReturn = OnQuitMessage(Message);
214
0
            break;
215
0
        case CMessage::Type::Text:
216
0
            bReturn = OnTextMessage(Message);
217
0
            break;
218
0
        case CMessage::Type::Topic:
219
0
            bReturn = OnTopicMessage(Message);
220
0
            break;
221
0
        default:
222
0
            bReturn = OnOtherMessage(Message);
223
0
            break;
224
0
    }
225
226
0
    if (bReturn) return;
227
228
0
    PutIRC(Message.ToString(CMessage::ExcludePrefix | CMessage::ExcludeTags));
229
0
}
230
231
0
void CClient::SetNick(const CString& s) { m_sNick = s; }
232
233
void CClient::SetNetwork(CIRCNetwork* pNetwork, bool bDisconnect,
234
0
                         bool bReconnect) {
235
0
    if (m_pNetwork) {
236
0
        m_pNetwork->ClientDisconnected(this);
237
238
0
        if (bDisconnect) {
239
0
            ClearServerDependentCaps();
240
            // Tell the client they are no longer in these channels.
241
0
            const vector<CChan*>& vChans = m_pNetwork->GetChans();
242
0
            for (const CChan* pChan : vChans) {
243
0
                if (!(pChan->IsDetached())) {
244
0
                    PutClient(":" + m_pNetwork->GetIRCNick().GetNickMask() +
245
0
                              " PART " + pChan->GetName());
246
0
                }
247
0
            }
248
0
        }
249
0
    } else if (m_pUser) {
250
0
        m_pUser->UserDisconnected(this);
251
0
    }
252
253
0
    m_pNetwork = pNetwork;
254
255
0
    if (bReconnect) {
256
0
        if (m_pNetwork) {
257
0
            m_pNetwork->ClientConnected(this);
258
0
        } else if (m_pUser) {
259
0
            m_pUser->UserConnected(this);
260
0
        }
261
0
    }
262
0
}
263
264
0
const vector<CClient*>& CClient::GetClients() const {
265
0
    if (m_pNetwork) {
266
0
        return m_pNetwork->GetClients();
267
0
    }
268
269
0
    return m_pUser->GetUserClients();
270
0
}
271
272
0
const CIRCSock* CClient::GetIRCSock() const {
273
0
    if (m_pNetwork) {
274
0
        return m_pNetwork->GetIRCSock();
275
0
    }
276
277
0
    return nullptr;
278
0
}
279
280
0
CIRCSock* CClient::GetIRCSock() {
281
0
    if (m_pNetwork) {
282
0
        return m_pNetwork->GetIRCSock();
283
0
    }
284
285
0
    return nullptr;
286
0
}
287
288
0
void CClient::StatusCTCP(const CString& sLine) {
289
0
    CString sCommand = sLine.Token(0);
290
291
0
    if (sCommand.Equals("PING")) {
292
0
        PutStatusNotice("\001PING " + sLine.Token(1, true) + "\001");
293
0
    } else if (sCommand.Equals("VERSION")) {
294
0
        PutStatusNotice("\001VERSION " + CZNC::GetTag() + "\001");
295
0
    }
296
0
}
297
298
0
bool CClient::SendMotd() {
299
0
    const VCString& vsMotd = CZNC::Get().GetMotd();
300
301
0
    if (!vsMotd.size()) {
302
0
        return false;
303
0
    }
304
305
0
    for (const CString& sLine : vsMotd) {
306
0
        if (m_pNetwork) {
307
0
            PutStatusNotice(m_pNetwork->ExpandString(sLine));
308
0
        } else {
309
0
            PutStatusNotice(m_pUser->ExpandString(sLine));
310
0
        }
311
0
    }
312
313
0
    return true;
314
0
}
315
316
0
void CClient::AuthUser() {
317
0
    if (!m_bGotNick || !m_bGotUser || !m_bGotPass || m_bInCap || IsAttached())
318
0
        return;
319
320
0
    m_spAuth = std::make_shared<CClientAuth>(this, m_sUser, m_sPass);
321
322
0
    CZNC::Get().AuthUser(m_spAuth);
323
0
}
324
325
CClientAuth::CClientAuth(CClient* pClient, const CString& sUsername,
326
                         const CString& sPassword)
327
0
    : CAuthBase(sUsername, sPassword, pClient), m_pClient(pClient) {}
328
329
0
void CClientAuth::RefusedLogin(const CString& sReason) {
330
0
    if (m_pClient) {
331
0
        m_pClient->RefuseLogin(sReason);
332
0
    }
333
0
}
334
335
0
CString CAuthBase::GetRemoteIP() const {
336
0
    if (m_pSock) return m_pSock->GetRemoteIP();
337
0
    return "";
338
0
}
339
340
0
void CAuthBase::Invalidate() { m_pSock = nullptr; }
341
342
0
void CAuthBase::AcceptLogin(CUser& User) {
343
0
    if (m_pSock) {
344
0
        AcceptedLogin(User);
345
0
        Invalidate();
346
0
    }
347
0
}
348
349
0
void CAuthBase::RefuseLogin(const CString& sReason) {
350
0
    if (!m_pSock) return;
351
352
0
    CUser* pUser = CZNC::Get().FindUser(GetUsername());
353
354
    // If the username is valid, notify that user that someone tried to
355
    // login. Use sReason because there are other reasons than "wrong
356
    // password" for a login to be rejected (e.g. fail2ban).
357
0
    if (pUser) {
358
0
        pUser->PutStatusNotice(t_f(
359
0
            "A client from {1} attempted to login as you, but was rejected: "
360
0
            "{2}")(GetRemoteIP(), sReason));
361
0
    }
362
363
0
    GLOBALMODULECALL(OnFailedLogin(GetUsername(), GetRemoteIP()), NOTHING);
364
0
    RefusedLogin(sReason);
365
0
    Invalidate();
366
0
}
367
368
0
void CClient::RefuseLogin(const CString& sReason) {
369
0
    PutStatus("Bad username and/or password.");
370
0
    PutClient(":irc.znc.in 464 " + GetNick() + " :" + sReason);
371
0
    Close(Csock::CLT_AFTERWRITE);
372
0
}
373
374
0
void CClientAuth::AcceptedLogin(CUser& User) {
375
0
    if (m_pClient) {
376
0
        m_pClient->AcceptLogin(User);
377
0
    }
378
0
}
379
380
0
void CClient::AcceptLogin(CUser& User) {
381
0
    m_sPass = "";
382
0
    m_pUser = &User;
383
384
    // Set our proper timeout and set back our proper timeout mode
385
    // (constructor set a different timeout and mode)
386
0
    SetTimeout(User.GetNoTrafficTimeout(), TMO_READ);
387
388
0
    SetSockName("USR::" + m_pUser->GetUsername());
389
0
    SetEncoding(m_pUser->GetClientEncoding());
390
391
0
    if (!m_sNetwork.empty()) {
392
0
        m_pNetwork = m_pUser->FindNetwork(m_sNetwork);
393
0
        if (!m_pNetwork) {
394
0
            PutStatus(t_f("Network {1} doesn't exist.")(m_sNetwork));
395
0
        }
396
0
    } else if (!m_pUser->GetNetworks().empty()) {
397
        // If a user didn't supply a network, and they have a network called
398
        // "default" then automatically use this network.
399
0
        m_pNetwork = m_pUser->FindNetwork("default");
400
        // If no "default" network, try "user" network. It's for compatibility
401
        // with early network stuff in ZNC, which converted old configs to
402
        // "user" network.
403
0
        if (!m_pNetwork) m_pNetwork = m_pUser->FindNetwork("user");
404
        // Otherwise, just try any network of the user.
405
0
        if (!m_pNetwork) m_pNetwork = *m_pUser->GetNetworks().begin();
406
0
        if (m_pNetwork && m_pUser->GetNetworks().size() > 1) {
407
0
            PutStatusNotice(
408
0
                t_s("You have several networks configured, but no network was "
409
0
                    "specified for the connection."));
410
0
            PutStatusNotice(
411
0
                t_f("Selecting network {1}. To see list of all configured "
412
0
                    "networks, use /znc ListNetworks")(m_pNetwork->GetName()));
413
0
            PutStatusNotice(t_f(
414
0
                "If you want to choose another network, use /znc JumpNetwork "
415
0
                "<network>, or connect to ZNC with username {1}/<network> "
416
0
                "(instead of just {1})")(m_pUser->GetUsername()));
417
0
        }
418
0
    } else {
419
0
        PutStatusNotice(
420
0
            t_s("You have no networks configured. Use /znc AddNetwork "
421
0
                "<network> to add one."));
422
0
    }
423
424
0
    SetNetwork(m_pNetwork, false);
425
426
0
    SendMotd();
427
428
0
    NETWORKMODULECALL(OnClientLogin(), m_pUser, m_pNetwork, this, NOTHING);
429
0
}
430
431
0
void CClient::Timeout() { PutClient("ERROR :" + t_s("Closing link: Timeout")); }
432
433
0
void CClient::Connected() { DEBUG(GetSockName() << " == Connected();"); }
434
435
0
void CClient::ConnectionRefused() {
436
0
    DEBUG(GetSockName() << " == ConnectionRefused()");
437
0
}
438
439
0
void CClient::Disconnected() {
440
0
    DEBUG(GetSockName() << " == Disconnected()");
441
0
    CIRCNetwork* pNetwork = m_pNetwork;
442
0
    SetNetwork(nullptr, false, false);
443
444
0
    if (m_pUser) {
445
0
        NETWORKMODULECALL(OnClientDisconnect(), m_pUser, pNetwork, this,
446
0
                          NOTHING);
447
0
    }
448
0
}
449
450
0
void CClient::ReachedMaxBuffer() {
451
0
    DEBUG(GetSockName() << " == ReachedMaxBuffer()");
452
0
    if (IsAttached()) {
453
0
        PutClient("ERROR :" + t_s("Closing link: Too long raw line"));
454
0
    }
455
0
    Close();
456
0
}
457
458
0
void CClient::BouncedOff() {
459
0
    PutStatusNotice(
460
0
        t_s("You are being disconnected because another user just "
461
0
            "authenticated as you."));
462
0
    Close(Csock::CLT_AFTERWRITE);
463
0
}
464
465
0
void CClient::PutIRC(const CString& sLine) {
466
0
    if (m_pNetwork) {
467
0
        m_pNetwork->PutIRC(sLine);
468
0
    }
469
0
}
470
471
0
CString CClient::GetFullName() const {
472
0
    if (!m_pUser) return GetRemoteIP();
473
0
    CString sFullName = m_pUser->GetUsername();
474
0
    if (!m_sIdentifier.empty()) sFullName += "@" + m_sIdentifier;
475
0
    if (m_pNetwork) sFullName += "/" + m_pNetwork->GetName();
476
0
    return sFullName;
477
0
}
478
479
0
void CClient::PutClient(const CString& sLine) {
480
0
    PutClient(CMessage(sLine));
481
0
}
482
483
0
bool CClient::PutClient(const CMessage& Message) {
484
0
    if (!m_bAwayNotify && Message.GetType() == CMessage::Type::Away) {
485
0
        return false;
486
0
    } else if (!m_bAccountNotify &&
487
0
               Message.GetType() == CMessage::Type::Account) {
488
0
        return false;
489
0
    }
490
491
0
    CMessage Msg(Message);
492
493
0
    const CIRCSock* pIRCSock = GetIRCSock();
494
0
    if (pIRCSock) {
495
0
        if (Msg.GetType() == CMessage::Type::Numeric) {
496
0
            unsigned int uCode = Msg.As<CNumericMessage>().GetCode();
497
498
0
            if (uCode == 352) {  // RPL_WHOREPLY
499
0
                if (!m_bNamesx && pIRCSock->HasNamesx()) {
500
                    // The server has NAMESX, but the client doesn't, so we need
501
                    // to remove extra prefixes
502
0
                    CString sNick = Msg.GetParam(6);
503
0
                    if (sNick.size() > 1 && pIRCSock->IsPermChar(sNick[1])) {
504
0
                        CString sNewNick = sNick;
505
0
                        size_t pos =
506
0
                            sNick.find_first_not_of(pIRCSock->GetPerms());
507
0
                        if (pos >= 2 && pos != CString::npos) {
508
0
                            sNewNick = sNick[0] + sNick.substr(pos);
509
0
                        }
510
0
                        Msg.SetParam(6, sNewNick);
511
0
                    }
512
0
                }
513
0
            } else if (uCode == 353) {  // RPL_NAMES
514
0
                if ((!m_bNamesx && pIRCSock->HasNamesx()) ||
515
0
                    (!m_bUHNames && pIRCSock->HasUHNames())) {
516
                    // The server has either UHNAMES or NAMESX, but the client
517
                    // is missing either or both
518
0
                    CString sNicks = Msg.GetParam(3);
519
0
                    VCString vsNicks;
520
0
                    sNicks.Split(" ", vsNicks, false);
521
522
0
                    for (CString& sNick : vsNicks) {
523
0
                        if (sNick.empty()) break;
524
525
0
                        if (!m_bNamesx && pIRCSock->HasNamesx() &&
526
0
                            pIRCSock->IsPermChar(sNick[0])) {
527
                            // The server has NAMESX, but the client doesn't, so
528
                            // we just use the first perm char
529
0
                            size_t pos =
530
0
                                sNick.find_first_not_of(pIRCSock->GetPerms());
531
0
                            if (pos >= 2 && pos != CString::npos) {
532
0
                                sNick = sNick[0] + sNick.substr(pos);
533
0
                            }
534
0
                        }
535
536
0
                        if (!m_bUHNames && pIRCSock->HasUHNames()) {
537
                            // The server has UHNAMES, but the client doesn't,
538
                            // so we strip away ident and host
539
0
                            sNick = sNick.Token(0, false, "!");
540
0
                        }
541
0
                    }
542
543
0
                    Msg.SetParam(
544
0
                        3, CString(" ").Join(vsNicks.begin(), vsNicks.end()));
545
0
                }
546
0
            }
547
0
        } else if (Msg.GetType() == CMessage::Type::Join) {
548
0
            if (!m_bExtendedJoin && pIRCSock->HasExtendedJoin()) {
549
0
                Msg.SetParams({Msg.As<CJoinMessage>().GetTarget()});
550
0
            }
551
0
        }
552
0
    }
553
554
0
    MCString mssTags;
555
556
0
    for (const auto& it : Msg.GetTags()) {
557
0
        if (IsTagEnabled(it.first)) {
558
0
            mssTags[it.first] = it.second;
559
0
        }
560
0
    }
561
562
0
    if (HasServerTime()) {
563
        // If the server didn't set the time tag, manually set it
564
0
        mssTags.emplace("time", CUtils::FormatServerTime(Msg.GetTime()));
565
0
    }
566
567
0
    Msg.SetTags(mssTags);
568
0
    Msg.SetClient(this);
569
0
    Msg.SetNetwork(m_pNetwork);
570
571
0
    bool bReturn = false;
572
0
    NETWORKMODULECALL(OnSendToClientMessage(Msg), m_pUser, m_pNetwork, this,
573
0
                      &bReturn);
574
0
    if (bReturn) return false;
575
576
0
    return PutClientRaw(Msg.ToString());
577
0
}
578
579
0
bool CClient::PutClientRaw(const CString& sLine) {
580
0
    CString sCopy = sLine;
581
0
    bool bReturn = false;
582
0
    NETWORKMODULECALL(OnSendToClient(sCopy, *this), m_pUser, m_pNetwork, this,
583
0
                      &bReturn);
584
0
    if (bReturn) return false;
585
586
0
    DEBUG("(" << GetFullName() << ") ZNC -> CLI ["
587
0
        << CDebug::Filter(sCopy) << "]");
588
0
    Write(sCopy + "\r\n");
589
0
    return true;
590
0
}
591
592
0
void CClient::PutStatusNotice(const CString& sLine) {
593
0
    PutModNotice("status", sLine);
594
0
}
595
596
0
unsigned int CClient::PutStatus(const CTable& table) {
597
0
    unsigned int idx = 0;
598
0
    CString sLine;
599
0
    while (table.GetLine(idx++, sLine)) PutStatus(sLine);
600
0
    return idx - 1;
601
0
}
602
603
0
void CClient::PutStatus(const CString& sLine) { PutModule("status", sLine); }
604
605
0
void CClient::PutModNotice(const CString& sModule, const CString& sLine) {
606
0
    if (!m_pUser) {
607
0
        return;
608
0
    }
609
610
0
    DEBUG("(" << GetFullName()
611
0
              << ") ZNC -> CLI [:" + m_pUser->GetStatusPrefix() +
612
0
                     ((sModule.empty()) ? "status" : sModule) + "!" +
613
0
                     ((sModule.empty()) ? "status" : sModule) +
614
0
                     "@znc.in NOTICE "
615
0
              << GetNick() << " :" << sLine << "]");
616
0
    Write(":" + m_pUser->GetStatusPrefix() +
617
0
          ((sModule.empty()) ? "status" : sModule) + "!" +
618
0
          ((sModule.empty()) ? "status" : sModule) + "@znc.in NOTICE " +
619
0
          GetNick() + " :" + sLine + "\r\n");
620
0
}
621
622
0
void CClient::PutModule(const CString& sModule, const CString& sLine) {
623
0
    if (!m_pUser) {
624
0
        return;
625
0
    }
626
627
0
    DEBUG("(" << GetFullName()
628
0
              << ") ZNC -> CLI [:" + m_pUser->GetStatusPrefix() +
629
0
                     ((sModule.empty()) ? "status" : sModule) + "!" +
630
0
                     ((sModule.empty()) ? "status" : sModule) +
631
0
                     "@znc.in PRIVMSG "
632
0
              << GetNick() << " :" << sLine << "]");
633
634
0
    VCString vsLines;
635
0
    sLine.Split("\n", vsLines);
636
0
    for (const CString& s : vsLines) {
637
0
        Write(":" + m_pUser->GetStatusPrefix() +
638
0
              ((sModule.empty()) ? "status" : sModule) + "!" +
639
0
              ((sModule.empty()) ? "status" : sModule) + "@znc.in PRIVMSG " +
640
0
              GetNick() + " :" + s + "\r\n");
641
0
    }
642
0
}
643
644
0
CString CClient::GetNick(bool bAllowIRCNick) const {
645
0
    CString sRet;
646
647
0
    const CIRCSock* pSock = GetIRCSock();
648
0
    if (bAllowIRCNick && pSock && pSock->IsAuthed()) {
649
0
        sRet = pSock->GetNick();
650
0
    }
651
652
0
    return (sRet.empty()) ? m_sNick : sRet;
653
0
}
654
655
0
CString CClient::GetNickMask() const {
656
0
    if (GetIRCSock() && GetIRCSock()->IsAuthed()) {
657
0
        return GetIRCSock()->GetNickMask();
658
0
    }
659
660
0
    CString sHost =
661
0
        m_pNetwork ? m_pNetwork->GetBindHost() : m_pUser->GetBindHost();
662
0
    if (sHost.empty()) {
663
0
        sHost = "irc.znc.in";
664
0
    }
665
666
0
    return GetNick() + "!" +
667
0
           (m_pNetwork ? m_pNetwork->GetIdent() : m_pUser->GetIdent()) + "@" +
668
0
           sHost;
669
0
}
670
671
0
bool CClient::IsValidIdentifier(const CString& sIdentifier) {
672
    // ^[-\w]+$
673
674
0
    if (sIdentifier.empty()) {
675
0
        return false;
676
0
    }
677
678
0
    const char* p = sIdentifier.c_str();
679
0
    while (*p) {
680
0
        if (*p != '_' && *p != '-' && !isalnum(*p)) {
681
0
            return false;
682
0
        }
683
684
0
        p++;
685
0
    }
686
687
0
    return true;
688
0
}
689
690
0
void CClient::RespondCap(const CString& sResponse) {
691
0
    PutClient(":irc.znc.in CAP " + GetNick() + " " + sResponse);
692
0
}
693
694
0
void CClient::HandleCap(const CMessage& Message) {
695
0
    CString sSubCmd = Message.GetParam(0);
696
697
0
    if (sSubCmd.Equals("LS")) {
698
0
        SCString ssOfferCaps;
699
0
        for (const auto& it : m_mCoreCaps) {
700
0
            bool bServerDependent = std::get<0>(it.second);
701
0
            if (!bServerDependent ||
702
0
                m_ssServerDependentCaps.count(it.first) > 0)
703
0
                ssOfferCaps.insert(it.first);
704
0
        }
705
0
        GLOBALMODULECALL(OnClientCapLs(this, ssOfferCaps), NOTHING);
706
0
        CString sRes =
707
0
            CString(" ").Join(ssOfferCaps.begin(), ssOfferCaps.end());
708
0
        RespondCap("LS :" + sRes);
709
0
        m_bInCap = true;
710
0
        if (Message.GetParam(1).ToInt() >= 302) {
711
0
            m_bCapNotify = true;
712
0
        }
713
0
    } else if (sSubCmd.Equals("END")) {
714
0
        m_bInCap = false;
715
0
        if (!IsAttached()) {
716
0
            if (!m_pUser && m_bGotUser && !m_bGotPass) {
717
0
                SendRequiredPasswordNotice();
718
0
            } else {
719
0
                AuthUser();
720
0
            }
721
0
        }
722
0
    } else if (sSubCmd.Equals("REQ")) {
723
0
        VCString vsTokens;
724
0
        Message.GetParam(1).Split(" ", vsTokens, false);
725
726
0
        for (const CString& sToken : vsTokens) {
727
0
            bool bVal = true;
728
0
            CString sCap = sToken;
729
0
            if (sCap.TrimPrefix("-")) bVal = false;
730
731
0
            bool bAccepted = false;
732
0
            const auto& it = m_mCoreCaps.find(sCap);
733
0
            if (m_mCoreCaps.end() != it) {
734
0
                bool bServerDependent = std::get<0>(it->second);
735
0
                bAccepted = !bServerDependent ||
736
0
                            m_ssServerDependentCaps.count(sCap) > 0;
737
0
            }
738
0
            GLOBALMODULECALL(IsClientCapSupported(this, sCap, bVal),
739
0
                             &bAccepted);
740
741
0
            if (!bAccepted) {
742
                // Some unsupported capability is requested
743
0
                RespondCap("NAK :" + Message.GetParam(1));
744
0
                return;
745
0
            }
746
0
        }
747
748
        // All is fine, we support what was requested
749
0
        for (const CString& sToken : vsTokens) {
750
0
            bool bVal = true;
751
0
            CString sCap = sToken;
752
0
            if (sCap.TrimPrefix("-")) bVal = false;
753
754
0
            auto handler_it = m_mCoreCaps.find(sCap);
755
0
            if (m_mCoreCaps.end() != handler_it) {
756
0
                const auto& handler = std::get<1>(handler_it->second);
757
0
                handler(bVal);
758
0
            }
759
0
            GLOBALMODULECALL(OnClientCapRequest(this, sCap, bVal), NOTHING);
760
761
0
            if (bVal) {
762
0
                m_ssAcceptedCaps.insert(sCap);
763
0
            } else {
764
0
                m_ssAcceptedCaps.erase(sCap);
765
0
            }
766
0
        }
767
768
0
        RespondCap("ACK :" + Message.GetParam(1));
769
0
    } else if (sSubCmd.Equals("LIST")) {
770
0
        CString sList =
771
0
            CString(" ").Join(m_ssAcceptedCaps.begin(), m_ssAcceptedCaps.end());
772
0
        RespondCap("LIST :" + sList);
773
0
    } else {
774
0
        PutClient(":irc.znc.in 410 " + GetNick() + " " + sSubCmd +
775
0
                  " :Invalid CAP subcommand");
776
0
    }
777
0
}
778
779
0
void CClient::ParsePass(const CString& sAuthLine) {
780
    // [user[@identifier][/network]:]password
781
782
0
    const size_t uColon = sAuthLine.find(":");
783
0
    if (uColon != CString::npos) {
784
0
        m_sPass = sAuthLine.substr(uColon + 1);
785
786
0
        ParseUser(sAuthLine.substr(0, uColon));
787
0
    } else {
788
0
        m_sPass = sAuthLine;
789
0
    }
790
0
}
791
792
0
void CClient::ParseUser(const CString& sAuthLine) {
793
    // user[@identifier][/network]
794
795
0
    const size_t uSlash = sAuthLine.rfind("/");
796
0
    if (uSlash != CString::npos) {
797
0
        m_sNetwork = sAuthLine.substr(uSlash + 1);
798
799
0
        ParseIdentifier(sAuthLine.substr(0, uSlash));
800
0
    } else {
801
0
        ParseIdentifier(sAuthLine);
802
0
    }
803
0
}
804
805
0
void CClient::ParseIdentifier(const CString& sAuthLine) {
806
    // user[@identifier]
807
808
0
    const size_t uAt = sAuthLine.rfind("@");
809
0
    if (uAt != CString::npos) {
810
0
        const CString sId = sAuthLine.substr(uAt + 1);
811
812
0
        if (IsValidIdentifier(sId)) {
813
0
            m_sIdentifier = sId;
814
0
            m_sUser = sAuthLine.substr(0, uAt);
815
0
        } else {
816
0
            m_sUser = sAuthLine;
817
0
        }
818
0
    } else {
819
0
        m_sUser = sAuthLine;
820
0
    }
821
0
}
822
823
0
void CClient::SetTagSupport(const CString& sTag, bool bState) {
824
0
    if (bState) {
825
0
        m_ssSupportedTags.insert(sTag);
826
0
    } else {
827
0
        m_ssSupportedTags.erase(sTag);
828
0
    }
829
0
}
830
831
0
void CClient::NotifyServerDependentCaps(const SCString& ssCaps) {
832
0
    for (const CString& sCap : ssCaps) {
833
0
        const auto& it = m_mCoreCaps.find(sCap);
834
0
        if (m_mCoreCaps.end() != it) {
835
0
            bool bServerDependent = std::get<0>(it->second);
836
0
            if (bServerDependent) {
837
0
                m_ssServerDependentCaps.insert(sCap);
838
0
            }
839
0
        }
840
0
    }
841
842
0
    if (HasCapNotify() && !m_ssServerDependentCaps.empty()) {
843
0
        CString sCaps = CString(" ").Join(m_ssServerDependentCaps.begin(),
844
0
                                          m_ssServerDependentCaps.end());
845
0
        PutClient(":irc.znc.in CAP " + GetNick() + " NEW :" + sCaps);
846
0
    }
847
0
}
848
849
0
void CClient::ClearServerDependentCaps() {
850
0
    if (HasCapNotify() && !m_ssServerDependentCaps.empty()) {
851
0
        CString sCaps = CString(" ").Join(m_ssServerDependentCaps.begin(),
852
0
                                          m_ssServerDependentCaps.end());
853
0
        PutClient(":irc.znc.in CAP " + GetNick() + " DEL :" + sCaps);
854
855
0
        for (const CString& sCap : m_ssServerDependentCaps) {
856
0
            const auto& it = m_mCoreCaps.find(sCap);
857
0
            if (m_mCoreCaps.end() != it) {
858
0
                const auto& handler = std::get<1>(it->second);
859
0
                handler(false);
860
0
            }
861
0
            m_ssAcceptedCaps.erase(sCap);
862
0
        }
863
0
    }
864
865
0
    m_ssServerDependentCaps.clear();
866
0
}
867
868
template <typename T>
869
0
void CClient::AddBuffer(const T& Message) {
870
0
    if (!m_pNetwork) {
871
0
        return;
872
0
    }
873
0
    const CString sTarget = Message.GetTarget();
874
875
0
    T Format;
876
0
    Format.Clone(Message);
877
0
    Format.SetNick(CNick(_NAMEDFMT(GetNickMask())));
878
0
    Format.SetTarget(_NAMEDFMT(sTarget));
879
0
    Format.SetText("{text}");
880
881
0
    CChan* pChan = m_pNetwork->FindChan(sTarget);
882
0
    if (pChan) {
883
0
        if (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline()) {
884
0
            pChan->AddBuffer(Format, Message.GetText());
885
0
        }
886
0
    } else if (Message.GetType() != CMessage::Type::Notice) {
887
0
        if (!m_pUser->AutoClearQueryBuffer() || !m_pNetwork->IsUserOnline()) {
888
0
            CQuery* pQuery = m_pNetwork->AddQuery(sTarget);
889
0
            if (pQuery) {
890
0
                pQuery->AddBuffer(Format, Message.GetText());
891
0
            }
892
0
        }
893
0
    }
894
0
}
Unexecuted instantiation: void CClient::AddBuffer<CActionMessage>(CActionMessage const&)
Unexecuted instantiation: void CClient::AddBuffer<CNoticeMessage>(CNoticeMessage const&)
Unexecuted instantiation: void CClient::AddBuffer<CTextMessage>(CTextMessage const&)
895
896
0
void CClient::EchoMessage(const CMessage& Message) {
897
0
    CMessage EchoedMessage = Message;
898
0
    for (CClient* pClient : GetClients()) {
899
0
        if (pClient->HasEchoMessage() ||
900
0
            (pClient != this && ((m_pNetwork && m_pNetwork->IsChan(Message.GetParam(0))) ||
901
0
                                 pClient->HasSelfMessage()))) {
902
0
            EchoedMessage.SetNick(GetNickMask());
903
0
            pClient->PutClient(EchoedMessage);
904
0
        }
905
0
    }
906
0
}
907
908
0
set<CChan*> CClient::MatchChans(const CString& sPatterns) const {
909
0
    if (!m_pNetwork) {
910
0
        return {};
911
0
    }
912
0
    VCString vsPatterns;
913
0
    sPatterns.Replace_n(",", " ")
914
0
        .Split(" ", vsPatterns, false, "", "", true, true);
915
916
0
    set<CChan*> sChans;
917
0
    for (const CString& sPattern : vsPatterns) {
918
0
        vector<CChan*> vChans = m_pNetwork->FindChans(sPattern);
919
0
        sChans.insert(vChans.begin(), vChans.end());
920
0
    }
921
0
    return sChans;
922
0
}
923
924
0
unsigned int CClient::AttachChans(const std::set<CChan*>& sChans) {
925
0
    unsigned int uAttached = 0;
926
0
    for (CChan* pChan : sChans) {
927
0
        if (!pChan->IsDetached()) continue;
928
0
        uAttached++;
929
0
        pChan->AttachUser();
930
0
    }
931
0
    return uAttached;
932
0
}
933
934
0
unsigned int CClient::DetachChans(const std::set<CChan*>& sChans) {
935
0
    unsigned int uDetached = 0;
936
0
    for (CChan* pChan : sChans) {
937
0
        if (pChan->IsDetached()) continue;
938
0
        uDetached++;
939
0
        pChan->DetachUser();
940
0
    }
941
0
    return uDetached;
942
0
}
943
944
0
bool CClient::OnActionMessage(CActionMessage& Message) {
945
0
    CString sTargets = Message.GetTarget();
946
947
0
    VCString vTargets;
948
0
    sTargets.Split(",", vTargets, false);
949
950
0
    for (CString& sTarget : vTargets) {
951
0
        Message.SetTarget(sTarget);
952
0
        if (m_pNetwork) {
953
            // May be nullptr.
954
0
            Message.SetChan(m_pNetwork->FindChan(sTarget));
955
0
        }
956
957
0
        bool bContinue = false;
958
0
        NETWORKMODULECALL(OnUserActionMessage(Message), m_pUser, m_pNetwork,
959
0
                          this, &bContinue);
960
0
        if (bContinue) continue;
961
962
0
        if (m_pNetwork) {
963
0
            AddBuffer(Message);
964
0
            EchoMessage(Message);
965
0
            PutIRC(Message.ToString(CMessage::ExcludePrefix |
966
0
                                    CMessage::ExcludeTags));
967
0
        }
968
0
    }
969
970
0
    return true;
971
0
}
972
973
0
bool CClient::OnCTCPMessage(CCTCPMessage& Message) {
974
0
    CString sTargets = Message.GetTarget();
975
976
0
    VCString vTargets;
977
0
    sTargets.Split(",", vTargets, false);
978
979
0
    if (Message.IsReply()) {
980
0
        CString sCTCP = Message.GetText();
981
0
        if (sCTCP.Token(0) == "VERSION") {
982
            // There are 2 different scenarios:
983
            //
984
            // a) CTCP reply for VERSION is not set.
985
            // 1. ZNC receives CTCP VERSION from someone
986
            // 2. ZNC forwards CTCP VERSION to client
987
            // 3. Client replies with something
988
            // 4. ZNC adds itself to the reply
989
            // 5. ZNC sends the modified reply to whoever asked
990
            //
991
            // b) CTCP reply for VERSION is set.
992
            // 1. ZNC receives CTCP VERSION from someone
993
            // 2. ZNC replies with the configured reply (or just drops it if
994
            //    empty), without forwarding anything to client
995
            // 3. Client does not see any CTCP request, and does not reply
996
            //
997
            // So, if user doesn't want "via ZNC" in CTCP VERSION reply, they
998
            // can set custom reply.
999
            //
1000
            // See more bikeshedding at github issues #820 and #1012
1001
0
            Message.SetText(sCTCP + " via " + CZNC::GetTag(false));
1002
0
        }
1003
0
    }
1004
1005
0
    for (CString& sTarget : vTargets) {
1006
0
        Message.SetTarget(sTarget);
1007
0
        if (m_pNetwork) {
1008
            // May be nullptr.
1009
0
            Message.SetChan(m_pNetwork->FindChan(sTarget));
1010
0
        }
1011
1012
0
        bool bContinue = false;
1013
0
        if (Message.IsReply()) {
1014
0
            NETWORKMODULECALL(OnUserCTCPReplyMessage(Message), m_pUser,
1015
0
                              m_pNetwork, this, &bContinue);
1016
0
        } else {
1017
0
            NETWORKMODULECALL(OnUserCTCPMessage(Message), m_pUser, m_pNetwork,
1018
0
                              this, &bContinue);
1019
0
        }
1020
0
        if (bContinue) continue;
1021
1022
0
        if (!GetIRCSock()) {
1023
            // Some lagmeters do a NOTICE to their own nick, ignore those.
1024
0
            if (!sTarget.Equals(m_sNick))
1025
0
                PutStatus(t_f(
1026
0
                    "Your CTCP to {1} got lost, you are not connected to IRC!")(
1027
0
                    Message.GetTarget()));
1028
0
            continue;
1029
0
        }
1030
1031
0
        if (m_pNetwork) {
1032
0
            PutIRC(Message.ToString(CMessage::ExcludePrefix |
1033
0
                                    CMessage::ExcludeTags));
1034
0
        }
1035
0
    }
1036
1037
0
    return true;
1038
0
}
1039
1040
0
bool CClient::OnJoinMessage(CJoinMessage& Message) {
1041
0
    CString sChans = Message.GetTarget();
1042
0
    CString sKeys = Message.GetKey();
1043
1044
0
    VCString vsChans;
1045
0
    sChans.Split(",", vsChans, false);
1046
0
    sChans.clear();
1047
1048
0
    VCString vsKeys;
1049
0
    sKeys.Split(",", vsKeys, true);
1050
0
    sKeys.clear();
1051
1052
0
    for (unsigned int a = 0; a < vsChans.size(); a++) {
1053
0
        Message.SetTarget(vsChans[a]);
1054
0
        Message.SetKey((a < vsKeys.size()) ? vsKeys[a] : "");
1055
0
        if (m_pNetwork) {
1056
            // May be nullptr.
1057
0
            Message.SetChan(m_pNetwork->FindChan(vsChans[a]));
1058
0
        }
1059
0
        bool bContinue = false;
1060
0
        NETWORKMODULECALL(OnUserJoinMessage(Message), m_pUser, m_pNetwork, this,
1061
0
                          &bContinue);
1062
0
        if (bContinue) continue;
1063
1064
0
        CString sChannel = Message.GetTarget();
1065
0
        CString sKey = Message.GetKey();
1066
1067
0
        if (m_pNetwork) {
1068
0
            CChan* pChan = m_pNetwork->FindChan(sChannel);
1069
0
            if (pChan) {
1070
0
                if (pChan->IsDetached())
1071
0
                    pChan->AttachUser(this);
1072
0
                else
1073
0
                    pChan->JoinUser(sKey);
1074
0
                continue;
1075
0
            } else if (!sChannel.empty()) {
1076
0
                pChan = new CChan(sChannel, m_pNetwork, false);
1077
0
                if (m_pNetwork->AddChan(pChan)) {
1078
0
                    pChan->SetKey(sKey);
1079
0
                }
1080
0
            }
1081
0
        }
1082
1083
0
        if (!sChannel.empty()) {
1084
0
            sChans += (sChans.empty()) ? sChannel : CString("," + sChannel);
1085
1086
0
            if (!vsKeys.empty()) {
1087
0
                sKeys += (sKeys.empty()) ? sKey : CString("," + sKey);
1088
0
            }
1089
0
        }
1090
0
    }
1091
1092
0
    Message.SetTarget(sChans);
1093
0
    Message.SetKey(sKeys);
1094
1095
0
    return sChans.empty();
1096
0
}
1097
1098
0
bool CClient::OnModeMessage(CModeMessage& Message) {
1099
0
    CString sTarget = Message.GetTarget();
1100
1101
0
    if (m_pNetwork && m_pNetwork->IsChan(sTarget) && !Message.HasModes()) {
1102
        // If we are on that channel and already received a
1103
        // /mode reply from the server, we can answer this
1104
        // request ourself.
1105
1106
0
        CChan* pChan = m_pNetwork->FindChan(sTarget);
1107
0
        if (pChan && pChan->IsOn() && !pChan->GetModeString().empty()) {
1108
0
            PutClient(":" + m_pNetwork->GetIRCServer() + " 324 " + GetNick() +
1109
0
                      " " + sTarget + " " + pChan->GetModeString());
1110
0
            if (pChan->GetCreationDate() > 0) {
1111
0
                PutClient(":" + m_pNetwork->GetIRCServer() + " 329 " +
1112
0
                          GetNick() + " " + sTarget + " " +
1113
0
                          CString(pChan->GetCreationDate()));
1114
0
            }
1115
0
            return true;
1116
0
        }
1117
0
    }
1118
1119
0
    return false;
1120
0
}
1121
1122
0
bool CClient::OnNoticeMessage(CNoticeMessage& Message) {
1123
0
    CString sTargets = Message.GetTarget();
1124
1125
0
    VCString vTargets;
1126
0
    sTargets.Split(",", vTargets, false);
1127
1128
0
    for (CString& sTarget : vTargets) {
1129
0
        Message.SetTarget(sTarget);
1130
0
        if (m_pNetwork) {
1131
            // May be nullptr.
1132
0
            Message.SetChan(m_pNetwork->FindChan(sTarget));
1133
0
        }
1134
1135
0
        if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) {
1136
0
            if (!sTarget.Equals("status")) {
1137
0
                CALLMOD(sTarget, this, m_pUser, m_pNetwork,
1138
0
                        OnModNotice(Message.GetText()));
1139
0
            }
1140
0
            continue;
1141
0
        }
1142
1143
0
        bool bContinue = false;
1144
0
        NETWORKMODULECALL(OnUserNoticeMessage(Message), m_pUser, m_pNetwork,
1145
0
                          this, &bContinue);
1146
0
        if (bContinue) continue;
1147
1148
0
        if (!GetIRCSock()) {
1149
            // Some lagmeters do a NOTICE to their own nick, ignore those.
1150
0
            if (!sTarget.Equals(m_sNick))
1151
0
                PutStatus(
1152
0
                    t_f("Your notice to {1} got lost, you are not connected to "
1153
0
                        "IRC!")(Message.GetTarget()));
1154
0
            continue;
1155
0
        }
1156
1157
0
        if (m_pNetwork) {
1158
0
            AddBuffer(Message);
1159
0
            EchoMessage(Message);
1160
0
            PutIRC(Message.ToString(CMessage::ExcludePrefix |
1161
0
                                    CMessage::ExcludeTags));
1162
0
        }
1163
0
    }
1164
1165
0
    return true;
1166
0
}
1167
1168
0
bool CClient::OnPartMessage(CPartMessage& Message) {
1169
0
    CString sChans = Message.GetTarget();
1170
1171
0
    VCString vsChans;
1172
0
    sChans.Split(",", vsChans, false);
1173
0
    sChans.clear();
1174
1175
0
    for (CString& sChan : vsChans) {
1176
0
        bool bContinue = false;
1177
0
        Message.SetTarget(sChan);
1178
0
        if (m_pNetwork) {
1179
            // May be nullptr.
1180
0
            Message.SetChan(m_pNetwork->FindChan(sChan));
1181
0
        }
1182
0
        NETWORKMODULECALL(OnUserPartMessage(Message), m_pUser, m_pNetwork, this,
1183
0
                          &bContinue);
1184
0
        if (bContinue) continue;
1185
1186
0
        sChan = Message.GetTarget();
1187
1188
0
        CChan* pChan = m_pNetwork ? m_pNetwork->FindChan(sChan) : nullptr;
1189
1190
0
        if (pChan && !pChan->IsOn()) {
1191
0
            PutStatusNotice(t_f("Removing channel {1}")(sChan));
1192
0
            m_pNetwork->DelChan(sChan);
1193
0
        } else {
1194
0
            sChans += (sChans.empty()) ? sChan : CString("," + sChan);
1195
0
        }
1196
0
    }
1197
1198
0
    if (sChans.empty()) {
1199
0
        return true;
1200
0
    }
1201
1202
0
    Message.SetTarget(sChans);
1203
1204
0
    return false;
1205
0
}
1206
1207
0
bool CClient::OnPingMessage(CMessage& Message) {
1208
    // All PONGs are generated by ZNC. We will still forward this to
1209
    // the ircd, but all PONGs from irc will be blocked.
1210
0
    if (!Message.GetParams().empty())
1211
0
        PutClient(":irc.znc.in PONG irc.znc.in " + Message.GetParamsColon(0));
1212
0
    else
1213
0
        PutClient(":irc.znc.in PONG irc.znc.in");
1214
0
    return false;
1215
0
}
1216
1217
0
bool CClient::OnPongMessage(CMessage& Message) {
1218
    // Block PONGs, we already responded to the pings
1219
0
    return true;
1220
0
}
1221
1222
0
bool CClient::OnQuitMessage(CQuitMessage& Message) {
1223
0
    bool bReturn = false;
1224
0
    NETWORKMODULECALL(OnUserQuitMessage(Message), m_pUser, m_pNetwork, this,
1225
0
                      &bReturn);
1226
0
    if (!bReturn) {
1227
0
        Close(Csock::CLT_AFTERWRITE);  // Treat a client quit as a detach
1228
0
    }
1229
    // Don't forward this msg.  We don't want the client getting us
1230
    // disconnected.
1231
0
    return true;
1232
0
}
1233
1234
0
bool CClient::OnTextMessage(CTextMessage& Message) {
1235
0
    CString sTargets = Message.GetTarget();
1236
1237
0
    VCString vTargets;
1238
0
    sTargets.Split(",", vTargets, false);
1239
1240
0
    for (CString& sTarget : vTargets) {
1241
0
        Message.SetTarget(sTarget);
1242
0
        if (m_pNetwork) {
1243
            // May be nullptr.
1244
0
            Message.SetChan(m_pNetwork->FindChan(sTarget));
1245
0
        }
1246
1247
0
        if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) {
1248
0
            EchoMessage(Message);
1249
1250
0
            if (sTarget.Equals("status")) {
1251
0
                CString sMsg = Message.GetText();
1252
0
                UserCommand(sMsg);
1253
0
            } else {
1254
0
                CALLMOD(sTarget, this, m_pUser, m_pNetwork,
1255
0
                        OnModCommand(Message.GetText()));
1256
0
            }
1257
0
            continue;
1258
0
        }
1259
1260
0
        bool bContinue = false;
1261
0
        NETWORKMODULECALL(OnUserTextMessage(Message), m_pUser, m_pNetwork, this,
1262
0
                          &bContinue);
1263
0
        if (bContinue) continue;
1264
1265
0
        if (!GetIRCSock()) {
1266
            // Some lagmeters do a PRIVMSG to their own nick, ignore those.
1267
0
            if (!sTarget.Equals(m_sNick))
1268
0
                PutStatus(
1269
0
                    t_f("Your message to {1} got lost, you are not connected "
1270
0
                        "to IRC!")(Message.GetTarget()));
1271
0
            continue;
1272
0
        }
1273
1274
0
        if (m_pNetwork) {
1275
0
            AddBuffer(Message);
1276
0
            EchoMessage(Message);
1277
0
            PutIRC(Message.ToString(CMessage::ExcludePrefix |
1278
0
                                    CMessage::ExcludeTags));
1279
0
        }
1280
0
    }
1281
1282
0
    return true;
1283
0
}
1284
1285
0
bool CClient::OnTopicMessage(CTopicMessage& Message) {
1286
0
    bool bReturn = false;
1287
0
    CString sChan = Message.GetTarget();
1288
0
    CString sTopic = Message.GetTopic();
1289
0
    if (m_pNetwork) {
1290
        // May be nullptr.
1291
0
        Message.SetChan(m_pNetwork->FindChan(sChan));
1292
0
    }
1293
1294
0
    if (!sTopic.empty()) {
1295
0
        NETWORKMODULECALL(OnUserTopicMessage(Message), m_pUser, m_pNetwork,
1296
0
                          this, &bReturn);
1297
0
    } else {
1298
0
        NETWORKMODULECALL(OnUserTopicRequest(sChan), m_pUser, m_pNetwork, this,
1299
0
                          &bReturn);
1300
0
        Message.SetTarget(sChan);
1301
0
    }
1302
1303
0
    return bReturn;
1304
0
}
1305
1306
0
bool CClient::OnOtherMessage(CMessage& Message) {
1307
0
    const CString& sCommand = Message.GetCommand();
1308
1309
0
    if (sCommand.Equals("ZNC")) {
1310
0
        CString sTarget = Message.GetParam(0);
1311
0
        CString sModCommand;
1312
1313
0
        if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) {
1314
0
            sModCommand = Message.GetParamsColon(1);
1315
0
        } else {
1316
0
            sTarget = "status";
1317
0
            sModCommand = Message.GetParamsColon(0);
1318
0
        }
1319
1320
0
        if (sTarget.Equals("status")) {
1321
0
            if (sModCommand.empty())
1322
0
                PutStatus(t_s("Hello. How may I help you?"));
1323
0
            else
1324
0
                UserCommand(sModCommand);
1325
0
        } else {
1326
0
            if (sModCommand.empty())
1327
0
                CALLMOD(sTarget, this, m_pUser, m_pNetwork,
1328
0
                        PutModule(t_s("Hello. How may I help you?")))
1329
0
            else
1330
0
                CALLMOD(sTarget, this, m_pUser, m_pNetwork,
1331
0
                        OnModCommand(sModCommand))
1332
0
        }
1333
0
        return true;
1334
0
    } else if (sCommand.Equals("ATTACH")) {
1335
0
        if (!m_pNetwork) {
1336
0
            return true;
1337
0
        }
1338
1339
0
        CString sPatterns = Message.GetParamsColon(0);
1340
1341
0
        if (sPatterns.empty()) {
1342
0
            PutStatusNotice(t_s("Usage: /attach <#chans>"));
1343
0
            return true;
1344
0
        }
1345
1346
0
        set<CChan*> sChans = MatchChans(sPatterns);
1347
0
        unsigned int uAttachedChans = AttachChans(sChans);
1348
1349
0
        PutStatusNotice(t_p("There was {1} channel matching [{2}]",
1350
0
                            "There were {1} channels matching [{2}]",
1351
0
                            sChans.size())(sChans.size(), sPatterns));
1352
0
        PutStatusNotice(t_p("Attached {1} channel", "Attached {1} channels",
1353
0
                            uAttachedChans)(uAttachedChans));
1354
1355
0
        return true;
1356
0
    } else if (sCommand.Equals("DETACH")) {
1357
0
        if (!m_pNetwork) {
1358
0
            return true;
1359
0
        }
1360
1361
0
        CString sPatterns = Message.GetParamsColon(0);
1362
1363
0
        if (sPatterns.empty()) {
1364
0
            PutStatusNotice(t_s("Usage: /detach <#chans>"));
1365
0
            return true;
1366
0
        }
1367
1368
0
        set<CChan*> sChans = MatchChans(sPatterns);
1369
0
        unsigned int uDetached = DetachChans(sChans);
1370
1371
0
        PutStatusNotice(t_p("There was {1} channel matching [{2}]",
1372
0
                            "There were {1} channels matching [{2}]",
1373
0
                            sChans.size())(sChans.size(), sPatterns));
1374
0
        PutStatusNotice(t_p("Detached {1} channel", "Detached {1} channels",
1375
0
                            uDetached)(uDetached));
1376
1377
0
        return true;
1378
0
    } else if (sCommand.Equals("PROTOCTL")) {
1379
0
        for (const CString& sParam : Message.GetParams()) {
1380
0
            if (sParam == "NAMESX") {
1381
0
                m_bNamesx = true;
1382
0
            } else if (sParam == "UHNAMES") {
1383
0
                m_bUHNames = true;
1384
0
            }
1385
0
        }
1386
0
        return true;  // If the server understands it, we already enabled namesx
1387
                      // / uhnames
1388
0
    }
1389
1390
0
    return false;
1391
0
}