Coverage Report

Created: 2025-07-11 06:31

/src/znc/src/User.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (C) 2004-2025 ZNC, see the NOTICE file for details.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
17
#include <znc/User.h>
18
#include <znc/Config.h>
19
#include <znc/FileUtils.h>
20
#include <znc/IRCNetwork.h>
21
#include <znc/IRCSock.h>
22
#include <znc/Chan.h>
23
#include <znc/Query.h>
24
#include <math.h>
25
#include <time.h>
26
#include <algorithm>
27
28
#ifdef ZNC_HAVE_ARGON
29
#include <argon2.h>
30
#endif
31
32
using std::vector;
33
using std::set;
34
35
class CUserTimer : public CCron {
36
  public:
37
0
    CUserTimer(CUser* pUser) : CCron(), m_pUser(pUser) {
38
0
        SetName("CUserTimer::" + m_pUser->GetUsername());
39
0
        Start(m_pUser->GetPingSlack());
40
0
    }
41
0
    ~CUserTimer() override {}
42
43
    CUserTimer(const CUserTimer&) = delete;
44
    CUserTimer& operator=(const CUserTimer&) = delete;
45
46
  private:
47
  protected:
48
0
    void RunJob() override {
49
0
        const vector<CClient*>& vUserClients = m_pUser->GetUserClients();
50
51
0
        for (CClient* pUserClient : vUserClients) {
52
0
            if (pUserClient->GetTimeSinceLastDataTransaction() >=
53
0
                m_pUser->GetPingFrequency()) {
54
0
                pUserClient->PutClient("PING :ZNC");
55
0
            }
56
0
        }
57
58
        // Restart timer for the case if the period had changed. Usually this is
59
        // noop
60
0
        Start(m_pUser->GetPingSlack());
61
0
    }
62
63
    CUser* m_pUser;
64
};
65
66
CUser::CUser(const CString& sUsername)
67
0
    : m_sUsername(sUsername),
68
0
      m_sCleanUsername(MakeCleanUsername(sUsername)),
69
0
      m_sNick(m_sCleanUsername),
70
0
      m_sAltNick(""),
71
0
      m_sIdent(m_sCleanUsername),
72
0
      m_sRealName(""),
73
0
      m_sBindHost(""),
74
0
      m_sDCCBindHost(""),
75
0
      m_sPass(""),
76
0
      m_sPassSalt(""),
77
0
      m_sStatusPrefix("*"),
78
0
      m_sDefaultChanModes(""),
79
0
      m_sClientEncoding(""),
80
0
      m_sQuitMsg(""),
81
0
      m_mssCTCPReplies(),
82
0
      m_sTimestampFormat("[%H:%M:%S]"),
83
0
      m_sTimezone(""),
84
0
      m_eHashType(HASH_NONE),
85
0
      m_sUserPath(CZNC::Get().GetUserPath() + "/" + sUsername),
86
0
      m_bMultiClients(true),
87
0
      m_bDenyLoadMod(false),
88
0
      m_bAdmin(false),
89
0
      m_bDenySetBindHost(false),
90
0
      m_bDenySetIdent(false),
91
0
      m_bDenySetNetwork(false),
92
0
      m_bDenySetRealName(false),
93
0
      m_bDenySetQuitMsg(false),
94
0
      m_bDenySetCTCPReplies(false),
95
0
      m_bAutoClearChanBuffer(true),
96
0
      m_bAutoClearQueryBuffer(true),
97
0
      m_bBeingDeleted(false),
98
0
      m_bAppendTimestamp(false),
99
0
      m_bPrependTimestamp(true),
100
0
      m_bAuthOnlyViaModule(false),
101
0
      m_pUserTimer(nullptr),
102
0
      m_vIRCNetworks(),
103
0
      m_vClients(),
104
0
      m_ssAllowedHosts(),
105
0
      m_uChanBufferSize(50),
106
0
      m_uQueryBufferSize(50),
107
0
      m_uBytesRead(0),
108
0
      m_uBytesWritten(0),
109
0
      m_uMaxJoinTries(10),
110
0
      m_uMaxNetworks(1),
111
0
      m_uMaxQueryBuffers(50),
112
0
      m_uMaxJoins(0),
113
0
      m_uNoTrafficTimeout(180),
114
0
      m_sSkinName(""),
115
0
      m_pModules(new CModules) {
116
0
    m_pUserTimer = new CUserTimer(this);
117
0
    CZNC::Get().GetManager().AddCron(m_pUserTimer);
118
0
}
119
120
0
CUser::~CUser() {
121
    // Delete networks
122
0
    while (!m_vIRCNetworks.empty()) {
123
0
        delete *m_vIRCNetworks.begin();
124
0
    }
125
126
    // Delete clients
127
0
    while (!m_vClients.empty()) {
128
0
        CZNC::Get().GetManager().DelSockByAddr(m_vClients[0]);
129
0
    }
130
0
    m_vClients.clear();
131
132
    // Delete modules (unloads all modules!)
133
0
    delete m_pModules;
134
0
    m_pModules = nullptr;
135
136
0
    CZNC::Get().GetManager().DelCronByAddr(m_pUserTimer);
137
138
0
    CZNC::Get().AddBytesRead(m_uBytesRead);
139
0
    CZNC::Get().AddBytesWritten(m_uBytesWritten);
140
0
}
141
142
namespace {
143
template <class T>
144
struct TOption {
145
    const char* name;
146
    void (CUser::*pSetter)(T);
147
};
148
}
149
150
0
bool CUser::ParseConfig(CConfig* pConfig, CString& sError) {
151
0
    TOption<const CString&> StringOptions[] = {
152
0
        {"nick", &CUser::SetNick},
153
0
        {"quitmsg", &CUser::SetQuitMsg},
154
0
        {"altnick", &CUser::SetAltNick},
155
0
        {"ident", &CUser::SetIdent},
156
0
        {"realname", &CUser::SetRealName},
157
0
        {"chanmodes", &CUser::SetDefaultChanModes},
158
0
        {"bindhost", &CUser::SetBindHost},
159
0
        {"vhost", &CUser::SetBindHost},
160
0
        {"dccbindhost", &CUser::SetDCCBindHost},
161
0
        {"dccvhost", &CUser::SetDCCBindHost},
162
0
        {"timestampformat", &CUser::SetTimestampFormat},
163
0
        {"skin", &CUser::SetSkinName},
164
0
        {"clientencoding", &CUser::SetClientEncoding},
165
0
    };
166
0
    TOption<unsigned int> UIntOptions[] = {
167
0
        {"jointries", &CUser::SetJoinTries},
168
0
        {"maxnetworks", &CUser::SetMaxNetworks},
169
0
        {"maxquerybuffers", &CUser::SetMaxQueryBuffers},
170
0
        {"maxjoins", &CUser::SetMaxJoins},
171
0
        {"notraffictimeout", &CUser::SetNoTrafficTimeout},
172
0
    };
173
0
    TOption<bool> BoolOptions[] = {
174
0
        {"keepbuffer",
175
0
         &CUser::SetKeepBuffer},  // XXX compatibility crap from pre-0.207
176
0
        {"autoclearchanbuffer", &CUser::SetAutoClearChanBuffer},
177
0
        {"autoclearquerybuffer", &CUser::SetAutoClearQueryBuffer},
178
0
        {"multiclients", &CUser::SetMultiClients},
179
0
        {"denyloadmod", &CUser::SetDenyLoadMod},
180
0
        {"admin", &CUser::SetAdmin},
181
0
        {"denysetbindhost", &CUser::SetDenySetBindHost},
182
0
        {"denysetvhost", &CUser::SetDenySetBindHost},
183
0
        {"denysetident", &CUser::SetDenySetIdent},
184
0
        {"denysetnetwork", &CUser::SetDenySetNetwork},
185
0
        {"denysetrealname", &CUser::SetDenySetRealName},
186
0
        {"denysetquitmsg", &CUser::SetDenySetQuitMsg},
187
0
        {"denysetctcpreplies", &CUser::SetDenySetCTCPReplies},
188
0
        {"appendtimestamp", &CUser::SetTimestampAppend},
189
0
        {"prependtimestamp", &CUser::SetTimestampPrepend},
190
0
        {"authonlyviamodule", &CUser::SetAuthOnlyViaModule},
191
0
    };
192
193
0
    for (const auto& Option : StringOptions) {
194
0
        CString sValue;
195
0
        if (pConfig->FindStringEntry(Option.name, sValue))
196
0
            (this->*Option.pSetter)(sValue);
197
0
    }
198
0
    for (const auto& Option : UIntOptions) {
199
0
        CString sValue;
200
0
        if (pConfig->FindStringEntry(Option.name, sValue))
201
0
            (this->*Option.pSetter)(sValue.ToUInt());
202
0
    }
203
0
    for (const auto& Option : BoolOptions) {
204
0
        CString sValue;
205
0
        if (pConfig->FindStringEntry(Option.name, sValue))
206
0
            (this->*Option.pSetter)(sValue.ToBool());
207
0
    }
208
209
0
    VCString vsList;
210
0
    pConfig->FindStringVector("allow", vsList);
211
0
    for (const CString& sHost : vsList) {
212
0
        AddAllowedHost(sHost);
213
0
    }
214
0
    pConfig->FindStringVector("ctcpreply", vsList);
215
0
    for (const CString& sReply : vsList) {
216
0
        AddCTCPReply(sReply.Token(0), sReply.Token(1, true));
217
0
    }
218
219
0
    CString sValue;
220
221
0
    CString sDCCLookupValue;
222
0
    pConfig->FindStringEntry("dcclookupmethod", sDCCLookupValue);
223
0
    if (pConfig->FindStringEntry("bouncedccs", sValue)) {
224
0
        if (sValue.ToBool()) {
225
0
            CUtils::PrintAction("Loading Module [bouncedcc]");
226
0
            CString sModRet;
227
0
            bool bModRet = GetModules().LoadModule(
228
0
                "bouncedcc", "", CModInfo::UserModule, this, nullptr, sModRet);
229
230
0
            CUtils::PrintStatus(bModRet, sModRet);
231
0
            if (!bModRet) {
232
0
                sError = sModRet;
233
0
                return false;
234
0
            }
235
236
0
            if (sDCCLookupValue.Equals("Client")) {
237
0
                GetModules().FindModule("bouncedcc")->SetNV("UseClientIP", "1");
238
0
            }
239
0
        }
240
0
    }
241
0
    if (pConfig->FindStringEntry("buffer", sValue))
242
0
        SetBufferCount(sValue.ToUInt(), true);
243
0
    if (pConfig->FindStringEntry("chanbuffersize", sValue))
244
0
        SetChanBufferSize(sValue.ToUInt(), true);
245
0
    if (pConfig->FindStringEntry("querybuffersize", sValue))
246
0
        SetQueryBufferSize(sValue.ToUInt(), true);
247
0
    if (pConfig->FindStringEntry("awaysuffix", sValue)) {
248
0
        CUtils::PrintMessage(
249
0
            "WARNING: AwaySuffix has been deprecated, instead try -> "
250
0
            "LoadModule = awaynick %nick%_" +
251
0
            sValue);
252
0
    }
253
0
    if (pConfig->FindStringEntry("autocycle", sValue)) {
254
0
        if (sValue.Equals("true"))
255
0
            CUtils::PrintError(
256
0
                "WARNING: AutoCycle has been removed, instead try -> "
257
0
                "LoadModule = autocycle");
258
0
    }
259
0
    if (pConfig->FindStringEntry("keepnick", sValue)) {
260
0
        if (sValue.Equals("true"))
261
0
            CUtils::PrintError(
262
0
                "WARNING: KeepNick has been deprecated, instead try -> "
263
0
                "LoadModule = keepnick");
264
0
    }
265
0
    if (pConfig->FindStringEntry("statusprefix", sValue)) {
266
0
        if (!SetStatusPrefix(sValue)) {
267
0
            sError = "Invalid StatusPrefix [" + sValue +
268
0
                     "] Must be 1-5 chars, no spaces.";
269
0
            CUtils::PrintError(sError);
270
0
            return false;
271
0
        }
272
0
    }
273
0
    if (pConfig->FindStringEntry("timezone", sValue)) {
274
0
        SetTimezone(sValue);
275
0
    }
276
0
    if (pConfig->FindStringEntry("timezoneoffset", sValue)) {
277
0
        if (fabs(sValue.ToDouble()) > 0.1) {
278
0
            CUtils::PrintError(
279
0
                "WARNING: TimezoneOffset has been deprecated, now you can set "
280
0
                "your timezone by name");
281
0
        }
282
0
    }
283
0
    if (pConfig->FindStringEntry("timestamp", sValue)) {
284
0
        if (!sValue.Trim_n().Equals("true")) {
285
0
            if (sValue.Trim_n().Equals("append")) {
286
0
                SetTimestampAppend(true);
287
0
                SetTimestampPrepend(false);
288
0
            } else if (sValue.Trim_n().Equals("prepend")) {
289
0
                SetTimestampAppend(false);
290
0
                SetTimestampPrepend(true);
291
0
            } else if (sValue.Trim_n().Equals("false")) {
292
0
                SetTimestampAppend(false);
293
0
                SetTimestampPrepend(false);
294
0
            } else {
295
0
                SetTimestampFormat(sValue);
296
0
            }
297
0
        }
298
0
    }
299
0
    if (pConfig->FindStringEntry("language", sValue)) {
300
0
        SetLanguage(sValue);
301
0
    }
302
0
    pConfig->FindStringEntry("pass", sValue);
303
    // There are different formats for this available:
304
    // Pass = <plain text>
305
    // Pass = <md5 hash> -
306
    // Pass = plain#<plain text>
307
    // Pass = <hash name>#<hash>
308
    // Pass = <hash name>#<salted hash>#<salt>#
309
    // 'Salted hash' means hash of 'password' + 'salt'
310
    // Possible hashes are md5 and sha256
311
0
    if (sValue.TrimSuffix("-")) {
312
0
        SetPass(sValue.Trim_n(), CUser::HASH_MD5);
313
0
    } else {
314
0
        CString sMethod = sValue.Token(0, false, "#");
315
0
        CString sPass = sValue.Token(1, true, "#");
316
0
        if (sMethod == "md5" || sMethod == "sha256") {
317
0
            CUser::eHashType type = CUser::HASH_MD5;
318
0
            if (sMethod == "sha256") type = CUser::HASH_SHA256;
319
320
0
            CString sSalt = sPass.Token(1, false, "#");
321
0
            sPass = sPass.Token(0, false, "#");
322
0
            SetPass(sPass, type, sSalt);
323
0
        } else if (sMethod == "plain") {
324
0
            SetPass(sPass, CUser::HASH_NONE);
325
0
        } else {
326
0
            SetPass(sValue, CUser::HASH_NONE);
327
0
        }
328
0
    }
329
0
    CConfig::SubConfig subConf;
330
0
    CConfig::SubConfig::const_iterator subIt;
331
0
    pConfig->FindSubConfig("pass", subConf);
332
0
    if (!sValue.empty() && !subConf.empty()) {
333
0
        sError = "Password defined more than once";
334
0
        CUtils::PrintError(sError);
335
0
        return false;
336
0
    }
337
0
    subIt = subConf.begin();
338
0
    if (subIt != subConf.end()) {
339
0
        CConfig* pSubConf = subIt->second.m_pSubConfig;
340
0
        CString sHash;
341
0
        CString sMethod;
342
0
        CString sSalt;
343
0
        CUser::eHashType method;
344
0
        pSubConf->FindStringEntry("hash", sHash);
345
0
        pSubConf->FindStringEntry("method", sMethod);
346
0
        pSubConf->FindStringEntry("salt", sSalt);
347
0
        if (sMethod.empty() || sMethod.Equals("plain"))
348
0
            method = CUser::HASH_NONE;
349
0
        else if (sMethod.Equals("md5"))
350
0
            method = CUser::HASH_MD5;
351
0
        else if (sMethod.Equals("sha256"))
352
0
            method = CUser::HASH_SHA256;
353
0
        else if (sMethod.Equals("Argon2id")) {
354
0
            method = CUser::HASH_ARGON2ID;
355
0
#ifndef ZNC_HAVE_ARGON
356
0
            CUtils::PrintError("ZNC is built without Argon2 support, " + GetUsername() + " won't be able to authenticate");
357
0
#endif
358
0
        } else {
359
0
            sError = "Invalid hash method";
360
0
            CUtils::PrintError(sError);
361
0
            return false;
362
0
        }
363
364
0
        SetPass(sHash, method, sSalt);
365
0
        if (!pSubConf->empty()) {
366
0
            sError = "Unhandled lines in config!";
367
0
            CUtils::PrintError(sError);
368
369
0
            CZNC::DumpConfig(pSubConf);
370
0
            return false;
371
0
        }
372
0
        ++subIt;
373
0
    }
374
0
    if (subIt != subConf.end()) {
375
0
        sError = "Password defined more than once";
376
0
        CUtils::PrintError(sError);
377
0
        return false;
378
0
    }
379
380
0
    pConfig->FindSubConfig("network", subConf);
381
0
    for (subIt = subConf.begin(); subIt != subConf.end(); ++subIt) {
382
0
        const CString& sNetworkName = subIt->first;
383
384
0
        CUtils::PrintMessage("Loading network [" + sNetworkName + "]");
385
386
0
        CIRCNetwork* pNetwork = FindNetwork(sNetworkName);
387
388
0
        if (!pNetwork) {
389
0
            pNetwork = new CIRCNetwork(this, sNetworkName);
390
0
        }
391
392
0
        if (!pNetwork->ParseConfig(subIt->second.m_pSubConfig, sError)) {
393
0
            return false;
394
0
        }
395
0
    }
396
397
0
    if (pConfig->FindStringVector("server", vsList, false) ||
398
0
        pConfig->FindStringVector("chan", vsList, false) ||
399
0
        pConfig->FindSubConfig("chan", subConf, false)) {
400
0
        CIRCNetwork* pNetwork = FindNetwork("default");
401
0
        if (!pNetwork) {
402
0
            CString sErrorDummy;
403
0
            pNetwork = AddNetwork("default", sErrorDummy);
404
0
        }
405
406
0
        if (pNetwork) {
407
0
            CUtils::PrintMessage(
408
0
                "NOTICE: Found deprecated config, upgrading to a network");
409
410
0
            if (!pNetwork->ParseConfig(pConfig, sError, true)) {
411
0
                return false;
412
0
            }
413
0
        }
414
0
    }
415
416
0
    pConfig->FindStringVector("loadmodule", vsList);
417
0
    for (const CString& sMod : vsList) {
418
0
        CString sModName = sMod.Token(0);
419
0
        CString sNotice = "Loading user module [" + sModName + "]";
420
421
        // XXX Legacy crap, added in ZNC 0.089
422
0
        if (sModName == "discon_kick") {
423
0
            sNotice =
424
0
                "NOTICE: [discon_kick] was renamed, loading [disconkick] "
425
0
                "instead";
426
0
            sModName = "disconkick";
427
0
        }
428
429
        // XXX Legacy crap, added in ZNC 0.099
430
0
        if (sModName == "fixfreenode") {
431
0
            sNotice =
432
0
                "NOTICE: [fixfreenode] doesn't do anything useful anymore, "
433
0
                "ignoring it";
434
0
            CUtils::PrintMessage(sNotice);
435
0
            continue;
436
0
        }
437
438
        // XXX Legacy crap, added in ZNC 0.207
439
0
        if (sModName == "admin") {
440
0
            sNotice =
441
0
                "NOTICE: [admin] module was renamed, loading [controlpanel] "
442
0
                "instead";
443
0
            sModName = "controlpanel";
444
0
        }
445
446
        // XXX Legacy crap, should have been added ZNC 0.207, but added only in
447
        // 1.1 :(
448
0
        if (sModName == "away") {
449
0
            sNotice = "NOTICE: [away] was renamed, loading [awaystore] instead";
450
0
            sModName = "awaystore";
451
0
        }
452
453
        // XXX Legacy crap, added in 1.1; fakeonline module was dropped in 1.0
454
        // and returned in 1.1
455
0
        if (sModName == "fakeonline") {
456
0
            sNotice =
457
0
                "NOTICE: [fakeonline] was renamed, loading [modules_online] "
458
0
                "instead";
459
0
            sModName = "modules_online";
460
0
        }
461
462
        // XXX Legacy crap, added in 1.3
463
0
        if (sModName == "charset") {
464
0
            CUtils::PrintAction(
465
0
                "NOTICE: Charset support was moved to core, importing old "
466
0
                "charset module settings");
467
0
            size_t uIndex = 1;
468
0
            if (sMod.Token(uIndex).Equals("-force")) {
469
0
                uIndex++;
470
0
            }
471
0
            VCString vsClient, vsServer;
472
0
            sMod.Token(uIndex).Split(",", vsClient);
473
0
            sMod.Token(uIndex + 1).Split(",", vsServer);
474
0
            if (vsClient.empty() || vsServer.empty()) {
475
0
                CUtils::PrintStatus(
476
0
                    false, "charset module was loaded with wrong parameters.");
477
0
                continue;
478
0
            }
479
0
            SetClientEncoding(vsClient[0]);
480
0
            for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
481
0
                pNetwork->SetEncoding(vsServer[0]);
482
0
            }
483
0
            CUtils::PrintStatus(true, "Using [" + vsClient[0] +
484
0
                                          "] for clients, and [" + vsServer[0] +
485
0
                                          "] for servers");
486
0
            continue;
487
0
        }
488
489
0
        CString sModRet;
490
0
        CString sArgs = sMod.Token(1, true);
491
492
0
        bool bModRet = LoadModule(sModName, sArgs, sNotice, sModRet);
493
494
0
        CUtils::PrintStatus(bModRet, sModRet);
495
0
        if (!bModRet) {
496
            // XXX The awaynick module was retired in 1.6 (still available as
497
            // external module)
498
0
            if (sModName == "awaynick") {
499
                // load simple_away instead, unless it's already on the list
500
0
                if (std::find(vsList.begin(), vsList.end(), "simple_away") ==
501
0
                    vsList.end()) {
502
0
                    sNotice = "Loading [simple_away] module instead";
503
0
                    sModName = "simple_away";
504
                    // not a fatal error if simple_away is not available
505
0
                    LoadModule(sModName, sArgs, sNotice, sModRet);
506
0
                }
507
0
            } else {
508
0
                sError = sModRet;
509
0
                return false;
510
0
            }
511
0
        }
512
0
        continue;
513
0
    }
514
515
    // Move ircconnectenabled to the networks
516
0
    if (pConfig->FindStringEntry("ircconnectenabled", sValue)) {
517
0
        for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
518
0
            pNetwork->SetIRCConnectEnabled(sValue.ToBool());
519
0
        }
520
0
    }
521
522
0
    return true;
523
0
}
524
525
0
CIRCNetwork* CUser::AddNetwork(const CString& sNetwork, CString& sErrorRet) {
526
0
    if (!CIRCNetwork::IsValidNetwork(sNetwork)) {
527
0
        sErrorRet =
528
0
            t_s("Invalid network name. It should be alphanumeric. Not to be "
529
0
                "confused with server name");
530
0
        return nullptr;
531
0
    } else if (FindNetwork(sNetwork)) {
532
0
        sErrorRet = t_f("Network {1} already exists")(sNetwork);
533
0
        return nullptr;
534
0
    }
535
536
0
    CIRCNetwork* pNetwork = new CIRCNetwork(this, sNetwork);
537
538
0
    bool bCancel = false;
539
0
    USERMODULECALL(OnAddNetwork(*pNetwork, sErrorRet), this, nullptr, &bCancel);
540
0
    if (bCancel) {
541
0
        RemoveNetwork(pNetwork);
542
0
        delete pNetwork;
543
0
        return nullptr;
544
0
    }
545
546
0
    return pNetwork;
547
0
}
548
549
0
bool CUser::AddNetwork(CIRCNetwork* pNetwork) {
550
0
    if (FindNetwork(pNetwork->GetName())) {
551
0
        return false;
552
0
    }
553
554
0
    m_vIRCNetworks.push_back(pNetwork);
555
556
0
    return true;
557
0
}
558
559
0
void CUser::RemoveNetwork(CIRCNetwork* pNetwork) {
560
0
    auto it = std::find(m_vIRCNetworks.begin(), m_vIRCNetworks.end(), pNetwork);
561
0
    if (it != m_vIRCNetworks.end()) {
562
0
        m_vIRCNetworks.erase(it);
563
0
    }
564
0
}
565
566
0
bool CUser::DeleteNetwork(const CString& sNetwork) {
567
0
    CIRCNetwork* pNetwork = FindNetwork(sNetwork);
568
569
0
    if (pNetwork) {
570
0
        bool bCancel = false;
571
0
        USERMODULECALL(OnDeleteNetwork(*pNetwork), this, nullptr, &bCancel);
572
0
        if (!bCancel) {
573
0
            delete pNetwork;
574
0
            return true;
575
0
        }
576
0
    }
577
578
0
    return false;
579
0
}
580
581
0
CIRCNetwork* CUser::FindNetwork(const CString& sNetwork) const {
582
0
    for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
583
0
        if (pNetwork->GetName().Equals(sNetwork)) {
584
0
            return pNetwork;
585
0
        }
586
0
    }
587
588
0
    return nullptr;
589
0
}
590
591
0
const vector<CIRCNetwork*>& CUser::GetNetworks() const {
592
0
    return m_vIRCNetworks;
593
0
}
594
595
0
CString CUser::ExpandString(const CString& sStr) const {
596
0
    CString sRet;
597
0
    return ExpandString(sStr, sRet);
598
0
}
599
600
0
CString& CUser::ExpandString(const CString& sStr, CString& sRet) const {
601
0
    CString sTime = CUtils::CTime(time(nullptr), m_sTimezone);
602
603
0
    sRet = sStr;
604
0
    sRet.Replace("%altnick%", GetAltNick());
605
0
    sRet.Replace("%bindhost%", GetBindHost());
606
0
    sRet.Replace("%defnick%", GetNick());
607
0
    sRet.Replace("%ident%", GetIdent());
608
0
    sRet.Replace("%nick%", GetNick());
609
0
    sRet.Replace("%realname%", GetRealName());
610
0
    sRet.Replace("%time%", sTime);
611
0
    sRet.Replace("%uptime%", CZNC::Get().GetUptime());
612
0
    sRet.Replace("%user%", GetUsername());
613
0
    sRet.Replace("%version%", CZNC::GetVersion());
614
0
    sRet.Replace("%vhost%", GetBindHost());
615
0
    sRet.Replace("%znc%", CZNC::GetTag(false));
616
617
    // Allows for escaping ExpandString if necessary, or to prevent
618
    // defaults from kicking in if you don't want them.
619
0
    sRet.Replace("%empty%", "");
620
    // The following lines do not exist. You must be on DrUgS!
621
0
    sRet.Replace("%irc%", "All your IRC are belong to ZNC");
622
    // Chosen by fair zocchihedron dice roll by SilverLeo
623
0
    sRet.Replace("%rand%", "42");
624
625
0
    return sRet;
626
0
}
627
628
0
CString CUser::AddTimestamp(const CString& sStr) const {
629
0
    timeval tv;
630
0
    gettimeofday(&tv, nullptr);
631
0
    return AddTimestamp(tv, sStr);
632
0
}
633
634
0
CString CUser::AddTimestamp(time_t tm, const CString& sStr) const {
635
0
    timeval tv;
636
0
    tv.tv_sec = tm;
637
0
    tv.tv_usec = 0;
638
0
    return AddTimestamp(tv, sStr);
639
0
}
640
641
0
CString CUser::AddTimestamp(timeval tv, const CString& sStr) const {
642
0
    CString sRet = sStr;
643
644
0
    if (!GetTimestampFormat().empty() &&
645
0
        (m_bAppendTimestamp || m_bPrependTimestamp)) {
646
0
        CString sTimestamp =
647
0
            CUtils::FormatTime(tv, GetTimestampFormat(), m_sTimezone);
648
0
        if (sTimestamp.empty()) {
649
0
            return sRet;
650
0
        }
651
652
0
        if (m_bPrependTimestamp) {
653
0
            sRet = sTimestamp;
654
0
            sRet += " " + sStr;
655
0
        }
656
0
        if (m_bAppendTimestamp) {
657
            // From http://www.mirc.com/colors.html
658
            // The Control+O key combination in mIRC inserts ascii character 15,
659
            // which turns off all previous attributes, including color, bold,
660
            // underline, and italics.
661
            //
662
            // \x02 bold
663
            // \x03 mIRC-compatible color
664
            // \x04 RRGGBB color
665
            // \x0F normal/reset (turn off bold, colors, etc.)
666
            // \x12 reverse (weechat)
667
            // \x16 reverse (mirc, kvirc)
668
            // \x1D italic
669
            // \x1F underline
670
            // Also see http://www.visualirc.net/tech-attrs.php
671
            //
672
            // Keep in sync with CIRCSocket::IcuExt__UCallback
673
0
            if (CString::npos !=
674
0
                sRet.find_first_of("\x02\x03\x04\x0F\x12\x16\x1D\x1F")) {
675
0
                sRet += "\x0F";
676
0
            }
677
678
0
            sRet += " " + sTimestamp;
679
0
        }
680
0
    }
681
682
0
    return sRet;
683
0
}
684
685
0
void CUser::BounceAllClients() {
686
0
    for (CClient* pClient : m_vClients) {
687
0
        pClient->BouncedOff();
688
0
    }
689
690
0
    m_vClients.clear();
691
0
}
692
693
0
void CUser::UserConnected(CClient* pClient) {
694
0
    if (!MultiClients()) {
695
0
        BounceAllClients();
696
0
    }
697
698
0
    pClient->PutClient(":irc.znc.in 001 " + pClient->GetNick() + " :" +
699
0
                       t_s("Welcome to ZNC"));
700
701
0
    m_vClients.push_back(pClient);
702
0
}
703
704
0
void CUser::UserDisconnected(CClient* pClient) {
705
0
    auto it = std::find(m_vClients.begin(), m_vClients.end(), pClient);
706
0
    if (it != m_vClients.end()) {
707
0
        m_vClients.erase(it);
708
0
    }
709
0
}
710
711
0
void CUser::CloneNetworks(const CUser& User) {
712
0
    const vector<CIRCNetwork*>& vNetworks = User.GetNetworks();
713
0
    for (CIRCNetwork* pUserNetwork : vNetworks) {
714
0
        CIRCNetwork* pNetwork = FindNetwork(pUserNetwork->GetName());
715
716
0
        if (pNetwork) {
717
0
            pNetwork->Clone(*pUserNetwork);
718
0
        } else {
719
0
            new CIRCNetwork(this, *pUserNetwork);
720
0
        }
721
0
    }
722
723
0
    set<CString> ssDeleteNetworks;
724
0
    for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
725
0
        if (!(User.FindNetwork(pNetwork->GetName()))) {
726
0
            ssDeleteNetworks.insert(pNetwork->GetName());
727
0
        }
728
0
    }
729
730
0
    for (const CString& sNetwork : ssDeleteNetworks) {
731
        // The following will move all the clients to the user.
732
        // So the clients are not disconnected. The client could
733
        // have requested the rehash. Then when we do
734
        // client->PutStatus("Rehashing succeeded!") we would
735
        // crash if there was no client anymore.
736
0
        const vector<CClient*>& vClients = FindNetwork(sNetwork)->GetClients();
737
738
0
        while (vClients.begin() != vClients.end()) {
739
0
            CClient* pClient = vClients.front();
740
            // This line will remove pClient from vClients,
741
            // because it's a reference to the internal Network's vector.
742
0
            pClient->SetNetwork(nullptr);
743
0
        }
744
745
0
        DeleteNetwork(sNetwork);
746
0
    }
747
0
}
748
749
0
bool CUser::Clone(const CUser& User, CString& sErrorRet, bool bCloneNetworks) {
750
0
    sErrorRet.clear();
751
752
0
    if (!User.IsValid(sErrorRet, true)) {
753
0
        return false;
754
0
    }
755
756
    // user names can only specified for the constructor, changing it later
757
    // on breaks too much stuff (e.g. lots of paths depend on the user name)
758
0
    if (GetUsername() != User.GetUsername()) {
759
0
        DEBUG("Ignoring username in CUser::Clone(), old username ["
760
0
              << GetUsername() << "]; New username [" << User.GetUsername()
761
0
              << "]");
762
0
    }
763
764
0
    if (!User.GetPass().empty()) {
765
0
        SetPass(User.GetPass(), User.GetPassHashType(), User.GetPassSalt());
766
0
    }
767
768
0
    SetNick(User.GetNick(false));
769
0
    SetAltNick(User.GetAltNick(false));
770
0
    SetIdent(User.GetIdent(false));
771
0
    SetRealName(User.GetRealName());
772
0
    SetStatusPrefix(User.GetStatusPrefix());
773
0
    SetBindHost(User.GetBindHost());
774
0
    SetDCCBindHost(User.GetDCCBindHost());
775
0
    SetQuitMsg(User.GetQuitMsg());
776
0
    SetSkinName(User.GetSkinName());
777
0
    SetDefaultChanModes(User.GetDefaultChanModes());
778
0
    SetChanBufferSize(User.GetChanBufferSize(), true);
779
0
    SetQueryBufferSize(User.GetQueryBufferSize(), true);
780
0
    SetJoinTries(User.JoinTries());
781
0
    SetMaxNetworks(User.MaxNetworks());
782
0
    SetMaxQueryBuffers(User.MaxQueryBuffers());
783
0
    SetMaxJoins(User.MaxJoins());
784
0
    SetNoTrafficTimeout(User.GetNoTrafficTimeout());
785
0
    SetClientEncoding(User.GetClientEncoding());
786
0
    SetLanguage(User.GetLanguage());
787
788
    // Allowed Hosts
789
0
    m_ssAllowedHosts.clear();
790
0
    const set<CString>& ssHosts = User.GetAllowedHosts();
791
0
    for (const CString& sHost : ssHosts) {
792
0
        AddAllowedHost(sHost);
793
0
    }
794
795
0
    for (CClient* pSock : m_vClients) {
796
0
        if (!IsHostAllowed(pSock->GetRemoteIP())) {
797
0
            pSock->PutStatusNotice(
798
0
                t_s("You are being disconnected because your IP is no longer "
799
0
                    "allowed to connect to this user"));
800
0
            pSock->Close();
801
0
        }
802
0
    }
803
804
    // !Allowed Hosts
805
806
    // Networks
807
0
    if (bCloneNetworks) {
808
0
        CloneNetworks(User);
809
0
    }
810
    // !Networks
811
812
    // CTCP Replies
813
0
    m_mssCTCPReplies.clear();
814
0
    const MCString& msReplies = User.GetCTCPReplies();
815
0
    for (const auto& it : msReplies) {
816
0
        AddCTCPReply(it.first, it.second);
817
0
    }
818
    // !CTCP Replies
819
820
    // Flags
821
0
    SetAutoClearChanBuffer(User.AutoClearChanBuffer());
822
0
    SetAutoClearQueryBuffer(User.AutoClearQueryBuffer());
823
0
    SetMultiClients(User.MultiClients());
824
0
    SetDenyLoadMod(User.DenyLoadMod());
825
0
    SetAdmin(User.IsAdmin());
826
0
    SetDenySetBindHost(User.DenySetBindHost());
827
0
    SetDenySetIdent(User.DenySetIdent());
828
0
    SetDenySetNetwork(User.DenySetNetwork());
829
0
    SetDenySetRealName(User.DenySetRealName());
830
0
    SetDenySetQuitMsg(User.DenySetQuitMsg());
831
0
    SetDenySetCTCPReplies(User.DenySetCTCPReplies());
832
0
    SetAuthOnlyViaModule(User.AuthOnlyViaModule());
833
0
    SetTimestampAppend(User.GetTimestampAppend());
834
0
    SetTimestampPrepend(User.GetTimestampPrepend());
835
0
    SetTimestampFormat(User.GetTimestampFormat());
836
0
    SetTimezone(User.GetTimezone());
837
    // !Flags
838
839
    // Modules
840
0
    set<CString> ssUnloadMods;
841
0
    CModules& vCurMods = GetModules();
842
0
    const CModules& vNewMods = User.GetModules();
843
844
0
    for (CModule* pNewMod : vNewMods) {
845
0
        CString sModRet;
846
0
        CModule* pCurMod = vCurMods.FindModule(pNewMod->GetModName());
847
848
0
        if (!pCurMod) {
849
0
            vCurMods.LoadModule(pNewMod->GetModName(), pNewMod->GetArgs(),
850
0
                                CModInfo::UserModule, this, nullptr, sModRet);
851
0
        } else if (pNewMod->GetArgs() != pCurMod->GetArgs()) {
852
0
            vCurMods.ReloadModule(pNewMod->GetModName(), pNewMod->GetArgs(),
853
0
                                  this, nullptr, sModRet);
854
0
        }
855
0
    }
856
857
0
    for (CModule* pCurMod : vCurMods) {
858
0
        CModule* pNewMod = vNewMods.FindModule(pCurMod->GetModName());
859
860
0
        if (!pNewMod) {
861
0
            ssUnloadMods.insert(pCurMod->GetModName());
862
0
        }
863
0
    }
864
865
0
    for (const CString& sMod : ssUnloadMods) {
866
0
        vCurMods.UnloadModule(sMod);
867
0
    }
868
    // !Modules
869
870
0
    return true;
871
0
}
872
873
0
const set<CString>& CUser::GetAllowedHosts() const { return m_ssAllowedHosts; }
874
0
bool CUser::AddAllowedHost(const CString& sHostMask) {
875
0
    if (sHostMask.empty() ||
876
0
        m_ssAllowedHosts.find(sHostMask) != m_ssAllowedHosts.end()) {
877
0
        return false;
878
0
    }
879
880
0
    m_ssAllowedHosts.insert(sHostMask);
881
0
    return true;
882
0
}
883
0
bool CUser::RemAllowedHost(const CString& sHostMask) {
884
0
    return m_ssAllowedHosts.erase(sHostMask) > 0;
885
0
}
886
0
void CUser::ClearAllowedHosts() { m_ssAllowedHosts.clear(); }
887
888
0
bool CUser::IsHostAllowed(const CString& sHost) const {
889
0
    if (m_ssAllowedHosts.empty()) {
890
0
        return true;
891
0
    }
892
893
0
    for (const CString& sAllowedHost : m_ssAllowedHosts) {
894
0
        if (CUtils::CheckCIDR(sHost, sAllowedHost)) {
895
0
            return true;
896
0
        }
897
0
    }
898
899
0
    return false;
900
0
}
901
902
0
const CString& CUser::GetTimestampFormat() const { return m_sTimestampFormat; }
903
0
bool CUser::GetTimestampAppend() const { return m_bAppendTimestamp; }
904
0
bool CUser::GetTimestampPrepend() const { return m_bPrependTimestamp; }
905
906
0
bool CUser::IsValidUsername(const CString& sUsername) {
907
0
    return CUser::IsValidUserName(sUsername);
908
0
}
909
910
/// @deprecated
911
0
bool CUser::IsValidUserName(const CString& sUsername) {
912
    // /^[a-zA-Z][a-zA-Z@._\-]*$/
913
0
    const char* p = sUsername.c_str();
914
915
0
    if (sUsername.empty()) {
916
0
        return false;
917
0
    }
918
919
0
    if ((*p < 'a' || *p > 'z') && (*p < 'A' || *p > 'Z')) {
920
0
        return false;
921
0
    }
922
923
0
    while (*p) {
924
0
        if (*p != '@' && *p != '.' && *p != '-' && *p != '_' && !isalnum(*p)) {
925
0
            return false;
926
0
        }
927
928
0
        p++;
929
0
    }
930
931
0
    return true;
932
0
}
933
934
0
bool CUser::IsValid(CString& sErrMsg, bool bSkipPass) const {
935
0
    sErrMsg.clear();
936
937
0
    if (!bSkipPass && m_sPass.empty()) {
938
0
        sErrMsg = t_s("Password is empty");
939
0
        return false;
940
0
    }
941
942
0
    if (m_sUsername.empty()) {
943
0
        sErrMsg = t_s("Username is empty");
944
0
        return false;
945
0
    }
946
947
0
    if (!CUser::IsValidUsername(m_sUsername)) {
948
0
        sErrMsg = t_s("Username is invalid");
949
0
        return false;
950
0
    }
951
952
0
    return true;
953
0
}
954
955
0
CConfig CUser::ToConfig() const {
956
0
    CConfig config;
957
0
    CConfig passConfig;
958
959
0
    CString sHash;
960
0
    switch (m_eHashType) {
961
0
        case HASH_NONE:
962
0
            sHash = "Plain";
963
0
            break;
964
0
        case HASH_MD5:
965
0
            sHash = "MD5";
966
0
            break;
967
0
        case HASH_SHA256:
968
0
            sHash = "SHA256";
969
0
            break;
970
0
        case HASH_ARGON2ID:
971
0
            sHash = "Argon2id";
972
0
            break;
973
0
    }
974
0
    passConfig.AddKeyValuePair("Salt", m_sPassSalt);
975
0
    passConfig.AddKeyValuePair("Method", sHash);
976
0
    passConfig.AddKeyValuePair("Hash", GetPass());
977
0
    config.AddSubConfig("Pass", "password", passConfig);
978
979
0
    config.AddKeyValuePair("Nick", GetNick());
980
0
    config.AddKeyValuePair("AltNick", GetAltNick());
981
0
    config.AddKeyValuePair("Ident", GetIdent());
982
0
    config.AddKeyValuePair("RealName", GetRealName());
983
0
    config.AddKeyValuePair("BindHost", GetBindHost());
984
0
    config.AddKeyValuePair("DCCBindHost", GetDCCBindHost());
985
0
    config.AddKeyValuePair("QuitMsg", GetQuitMsg());
986
0
    if (CZNC::Get().GetStatusPrefix() != GetStatusPrefix())
987
0
        config.AddKeyValuePair("StatusPrefix", GetStatusPrefix());
988
0
    config.AddKeyValuePair("Skin", GetSkinName());
989
0
    config.AddKeyValuePair("ChanModes", GetDefaultChanModes());
990
0
    config.AddKeyValuePair("ChanBufferSize", CString(GetChanBufferSize()));
991
0
    config.AddKeyValuePair("QueryBufferSize", CString(GetQueryBufferSize()));
992
0
    config.AddKeyValuePair("AutoClearChanBuffer",
993
0
                           CString(AutoClearChanBuffer()));
994
0
    config.AddKeyValuePair("AutoClearQueryBuffer",
995
0
                           CString(AutoClearQueryBuffer()));
996
0
    config.AddKeyValuePair("MultiClients", CString(MultiClients()));
997
0
    config.AddKeyValuePair("DenyLoadMod", CString(DenyLoadMod()));
998
0
    config.AddKeyValuePair("Admin", CString(IsAdmin()));
999
0
    config.AddKeyValuePair("DenySetBindHost", CString(DenySetBindHost()));
1000
0
    config.AddKeyValuePair("DenySetIdent", CString(DenySetIdent()));
1001
0
    config.AddKeyValuePair("DenySetNetwork", CString(DenySetNetwork()));
1002
0
    config.AddKeyValuePair("DenySetRealName", CString(DenySetRealName()));
1003
0
    config.AddKeyValuePair("DenySetQuitMsg", CString(DenySetQuitMsg()));
1004
0
    config.AddKeyValuePair("DenySetCTCPReplies", CString(DenySetCTCPReplies()));
1005
0
    config.AddKeyValuePair("TimestampFormat", GetTimestampFormat());
1006
0
    config.AddKeyValuePair("AppendTimestamp", CString(GetTimestampAppend()));
1007
0
    config.AddKeyValuePair("PrependTimestamp", CString(GetTimestampPrepend()));
1008
0
    config.AddKeyValuePair("AuthOnlyViaModule", CString(AuthOnlyViaModule()));
1009
0
    config.AddKeyValuePair("Timezone", m_sTimezone);
1010
0
    config.AddKeyValuePair("JoinTries", CString(m_uMaxJoinTries));
1011
0
    config.AddKeyValuePair("MaxNetworks", CString(m_uMaxNetworks));
1012
0
    config.AddKeyValuePair("MaxQueryBuffers", CString(m_uMaxQueryBuffers));
1013
0
    config.AddKeyValuePair("MaxJoins", CString(m_uMaxJoins));
1014
0
    config.AddKeyValuePair("ClientEncoding", GetClientEncoding());
1015
0
    config.AddKeyValuePair("Language", GetLanguage());
1016
0
    config.AddKeyValuePair("NoTrafficTimeout", CString(GetNoTrafficTimeout()));
1017
1018
    // Allow Hosts
1019
0
    if (!m_ssAllowedHosts.empty()) {
1020
0
        for (const CString& sHost : m_ssAllowedHosts) {
1021
0
            config.AddKeyValuePair("Allow", sHost);
1022
0
        }
1023
0
    }
1024
1025
    // CTCP Replies
1026
0
    if (!m_mssCTCPReplies.empty()) {
1027
0
        for (const auto& itb : m_mssCTCPReplies) {
1028
0
            config.AddKeyValuePair("CTCPReply",
1029
0
                                   itb.first.AsUpper() + " " + itb.second);
1030
0
        }
1031
0
    }
1032
1033
    // Modules
1034
0
    const CModules& Mods = GetModules();
1035
1036
0
    if (!Mods.empty()) {
1037
0
        for (CModule* pMod : Mods) {
1038
0
            CString sArgs = pMod->GetArgs();
1039
1040
0
            if (!sArgs.empty()) {
1041
0
                sArgs = " " + sArgs;
1042
0
            }
1043
1044
0
            config.AddKeyValuePair("LoadModule", pMod->GetModName() + sArgs);
1045
0
        }
1046
0
    }
1047
1048
    // Networks
1049
0
    for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
1050
0
        config.AddSubConfig("Network", pNetwork->GetName(),
1051
0
                            pNetwork->ToConfig());
1052
0
    }
1053
1054
0
    return config;
1055
0
}
1056
1057
0
bool CUser::CheckPass(const CString& sPass) {
1058
0
    if(AuthOnlyViaModule() || CZNC::Get().GetAuthOnlyViaModule()) {
1059
0
        return false;
1060
0
    }
1061
1062
0
    bool bResult = false;
1063
0
    bool bUpgrade = false;
1064
0
    switch (m_eHashType) {
1065
0
        case HASH_MD5:
1066
0
            bResult = m_sPass.Equals(CUtils::SaltedMD5Hash(sPass, m_sPassSalt));
1067
0
            bUpgrade = true;
1068
0
            break;
1069
0
        case HASH_SHA256:
1070
0
            bResult = m_sPass.Equals(CUtils::SaltedSHA256Hash(sPass, m_sPassSalt));
1071
#if ZNC_HAVE_ARGON
1072
            bUpgrade = true;
1073
#endif
1074
0
            break;
1075
0
        case HASH_ARGON2ID:
1076
#if ZNC_HAVE_ARGON
1077
            return argon2id_verify(m_sPass.c_str(), sPass.data(), sPass.length()) == ARGON2_OK;
1078
#else
1079
0
            CUtils::PrintError("ZNC is built without Argon2 support, " + GetUsername() + " cannot authenticate");
1080
0
            return false;
1081
0
#endif
1082
0
        case HASH_NONE:
1083
            // Don't upgrade hash, since the only valid use case for plain are
1084
            // manual tests, where it's simpler this way
1085
0
            return (sPass == m_sPass);
1086
0
    }
1087
1088
0
    if (bResult && bUpgrade) {
1089
0
        CString sSalt = CUtils::GetSalt();
1090
0
        CString sHash = CUser::SaltedHash(sPass, sSalt);
1091
0
        SetPass(sHash, CUser::HASH_DEFAULT, sSalt);
1092
0
    }
1093
0
    return bResult;
1094
0
}
1095
1096
/*CClient* CUser::GetClient() {
1097
    // Todo: optimize this by saving a pointer to the sock
1098
    CSockManager& Manager = CZNC::Get().GetManager();
1099
    CString sSockName = "USR::" + m_sUsername;
1100
1101
    for (unsigned int a = 0; a < Manager.size(); a++) {
1102
        Csock* pSock = Manager[a];
1103
        if (pSock->GetSockName().Equals(sSockName)) {
1104
            if (!pSock->IsClosed()) {
1105
                return (CClient*) pSock;
1106
            }
1107
        }
1108
    }
1109
1110
    return (CClient*) CZNC::Get().GetManager().FindSockByName(sSockName);
1111
}*/
1112
1113
0
CString CUser::GetLocalDCCIP() const {
1114
0
    if (!GetDCCBindHost().empty()) return GetDCCBindHost();
1115
1116
0
    for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
1117
0
        CIRCSock* pIRCSock = pNetwork->GetIRCSock();
1118
0
        if (pIRCSock) {
1119
0
            return pIRCSock->GetLocalIP();
1120
0
        }
1121
0
    }
1122
1123
0
    if (!GetAllClients().empty()) {
1124
0
        return GetAllClients()[0]->GetLocalIP();
1125
0
    }
1126
1127
0
    return "";
1128
0
}
1129
1130
bool CUser::PutUser(const CString& sLine, CClient* pClient,
1131
0
                    CClient* pSkipClient) {
1132
0
    for (CClient* pEachClient : m_vClients) {
1133
0
        if ((!pClient || pClient == pEachClient) &&
1134
0
            pSkipClient != pEachClient) {
1135
0
            pEachClient->PutClient(sLine);
1136
1137
0
            if (pClient) {
1138
0
                return true;
1139
0
            }
1140
0
        }
1141
0
    }
1142
1143
0
    for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
1144
0
        if (pNetwork->PutUser(sLine, pClient, pSkipClient)) {
1145
0
            return true;
1146
0
        }
1147
0
    }
1148
1149
0
    return (pClient == nullptr);
1150
0
}
1151
1152
bool CUser::PutStatus(const CString& sLine, CClient* pClient,
1153
0
                      CClient* pSkipClient) {
1154
0
    vector<CClient*> vClients = GetAllClients();
1155
0
    for (CClient* pEachClient : vClients) {
1156
0
        if ((!pClient || pClient == pEachClient) &&
1157
0
            pSkipClient != pEachClient) {
1158
0
            pEachClient->PutStatus(sLine);
1159
1160
0
            if (pClient) {
1161
0
                return true;
1162
0
            }
1163
0
        }
1164
0
    }
1165
1166
0
    return (pClient == nullptr);
1167
0
}
1168
1169
bool CUser::PutStatusNotice(const CString& sLine, CClient* pClient,
1170
0
                            CClient* pSkipClient) {
1171
0
    vector<CClient*> vClients = GetAllClients();
1172
0
    for (CClient* pEachClient : vClients) {
1173
0
        if ((!pClient || pClient == pEachClient) &&
1174
0
            pSkipClient != pEachClient) {
1175
0
            pEachClient->PutStatusNotice(sLine);
1176
1177
0
            if (pClient) {
1178
0
                return true;
1179
0
            }
1180
0
        }
1181
0
    }
1182
1183
0
    return (pClient == nullptr);
1184
0
}
1185
1186
bool CUser::PutModule(const CString& sModule, const CString& sLine,
1187
0
                      CClient* pClient, CClient* pSkipClient) {
1188
0
    for (CClient* pEachClient : GetAllClients()) {
1189
0
        if ((!pClient || pClient == pEachClient) &&
1190
0
            pSkipClient != pEachClient) {
1191
0
            pEachClient->PutModule(sModule, sLine);
1192
1193
0
            if (pClient) {
1194
0
                return true;
1195
0
            }
1196
0
        }
1197
0
    }
1198
1199
0
    return (pClient == nullptr);
1200
0
}
1201
1202
bool CUser::PutModNotice(const CString& sModule, const CString& sLine,
1203
0
                         CClient* pClient, CClient* pSkipClient) {
1204
0
    for (CClient* pEachClient : GetAllClients()) {
1205
0
        if ((!pClient || pClient == pEachClient) &&
1206
0
            pSkipClient != pEachClient) {
1207
0
            pEachClient->PutModNotice(sModule, sLine);
1208
1209
0
            if (pClient) {
1210
0
                return true;
1211
0
            }
1212
0
        }
1213
0
    }
1214
1215
0
    return (pClient == nullptr);
1216
0
}
1217
1218
1219
0
CString CUser::MakeCleanUsername(const CString& sUsername) {
1220
0
    return CUser::MakeCleanUserName(sUsername);
1221
0
}
1222
1223
/// @deprecated
1224
0
CString CUser::MakeCleanUserName(const CString& sUsername) {
1225
0
    return sUsername.Token(0, false, "@").Replace_n(".", "");
1226
0
}
1227
1228
0
bool CUser::IsUserAttached() const {
1229
0
    if (!m_vClients.empty()) {
1230
0
        return true;
1231
0
    }
1232
1233
0
    for (const CIRCNetwork* pNetwork : m_vIRCNetworks) {
1234
0
        if (pNetwork->IsUserAttached()) {
1235
0
            return true;
1236
0
        }
1237
0
    }
1238
1239
0
    return false;
1240
0
}
1241
1242
bool CUser::LoadModule(const CString& sModName, const CString& sArgs,
1243
0
                       const CString& sNotice, CString& sError) {
1244
0
    bool bModRet = true;
1245
0
    CString sModRet;
1246
1247
0
    CModInfo ModInfo;
1248
0
    if (!CZNC::Get().GetModules().GetModInfo(ModInfo, sModName, sModRet)) {
1249
0
        sError = t_f("Unable to find modinfo {1}: {2}")(sModName, sModRet);
1250
0
        return false;
1251
0
    }
1252
1253
0
    CUtils::PrintAction(sNotice);
1254
1255
0
    if (!ModInfo.SupportsType(CModInfo::UserModule) &&
1256
0
        ModInfo.SupportsType(CModInfo::NetworkModule)) {
1257
0
        CUtils::PrintMessage(
1258
0
            "NOTICE: Module [" + sModName +
1259
0
            "] is a network module, loading module for all networks in user.");
1260
1261
        // Do they have old NV?
1262
0
        CFile fNVFile =
1263
0
            CFile(GetUserPath() + "/moddata/" + sModName + "/.registry");
1264
1265
0
        for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
1266
            // Check whether the network already has this module loaded (#954)
1267
0
            if (pNetwork->GetModules().FindModule(sModName)) {
1268
0
                continue;
1269
0
            }
1270
1271
0
            if (fNVFile.Exists()) {
1272
0
                CString sNetworkModPath =
1273
0
                    pNetwork->GetNetworkPath() + "/moddata/" + sModName;
1274
0
                if (!CFile::Exists(sNetworkModPath)) {
1275
0
                    CDir::MakeDir(sNetworkModPath);
1276
0
                }
1277
1278
0
                fNVFile.Copy(sNetworkModPath + "/.registry");
1279
0
            }
1280
1281
0
            bModRet = pNetwork->GetModules().LoadModule(
1282
0
                sModName, sArgs, CModInfo::NetworkModule, this, pNetwork,
1283
0
                sModRet);
1284
0
            if (!bModRet) {
1285
0
                break;
1286
0
            }
1287
0
        }
1288
0
    } else {
1289
0
        bModRet = GetModules().LoadModule(sModName, sArgs, CModInfo::UserModule,
1290
0
                                          this, nullptr, sModRet);
1291
0
    }
1292
1293
0
    if (!bModRet) {
1294
0
        sError = sModRet;
1295
0
    }
1296
0
    return bModRet;
1297
0
}
1298
1299
// Setters
1300
0
void CUser::SetNick(const CString& s) { m_sNick = s; }
1301
0
void CUser::SetAltNick(const CString& s) { m_sAltNick = s; }
1302
0
void CUser::SetIdent(const CString& s) { m_sIdent = s; }
1303
0
void CUser::SetRealName(const CString& s) { m_sRealName = s; }
1304
0
void CUser::SetBindHost(const CString& s) { m_sBindHost = s; }
1305
0
void CUser::SetDCCBindHost(const CString& s) { m_sDCCBindHost = s; }
1306
0
void CUser::SetPass(const CString& s, eHashType eHash, const CString& sSalt) {
1307
0
    m_sPass = s;
1308
0
    m_eHashType = eHash;
1309
0
    switch (eHash) {
1310
0
        case HASH_NONE:
1311
0
        case HASH_ARGON2ID:
1312
            // Salt is embedded in the encoded "hash" in argon
1313
0
            m_sPassSalt = "";
1314
0
            break;
1315
0
        case HASH_MD5:
1316
0
        case HASH_SHA256:
1317
0
            m_sPassSalt = sSalt;
1318
0
            break;
1319
0
    }
1320
0
}
1321
0
void CUser::SetMultiClients(bool b) { m_bMultiClients = b; }
1322
0
void CUser::SetDenyLoadMod(bool b) { m_bDenyLoadMod = b; }
1323
0
void CUser::SetAdmin(bool b) { m_bAdmin = b; }
1324
0
void CUser::SetDenySetBindHost(bool b) { m_bDenySetBindHost = b; }
1325
0
void CUser::SetDenySetIdent(bool b) { m_bDenySetIdent = b; }
1326
0
void CUser::SetDenySetNetwork(bool b) { m_bDenySetNetwork = b; }
1327
0
void CUser::SetDenySetRealName(bool b) { m_bDenySetRealName = b; }
1328
0
void CUser::SetDenySetQuitMsg(bool b) { m_bDenySetQuitMsg = b; }
1329
0
void CUser::SetDenySetCTCPReplies(bool b) { m_bDenySetCTCPReplies = b; }
1330
0
void CUser::SetDefaultChanModes(const CString& s) { m_sDefaultChanModes = s; }
1331
0
void CUser::SetClientEncoding(const CString& s) {
1332
0
    m_sClientEncoding = CZNC::Get().FixupEncoding(s);
1333
0
    for (CClient* pClient : GetAllClients()) {
1334
0
        pClient->SetEncoding(m_sClientEncoding);
1335
0
    }
1336
0
}
1337
0
void CUser::SetQuitMsg(const CString& s) { m_sQuitMsg = s; }
1338
0
void CUser::SetAutoClearChanBuffer(bool b) {
1339
0
    for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
1340
0
        for (CChan* pChan : pNetwork->GetChans()) {
1341
0
            pChan->InheritAutoClearChanBuffer(b);
1342
0
        }
1343
0
    }
1344
0
    m_bAutoClearChanBuffer = b;
1345
0
}
1346
0
void CUser::SetAutoClearQueryBuffer(bool b) { m_bAutoClearQueryBuffer = b; }
1347
1348
0
bool CUser::SetBufferCount(unsigned int u, bool bForce) {
1349
0
    return SetChanBufferSize(u, bForce);
1350
0
}
1351
1352
0
bool CUser::SetChanBufferSize(unsigned int u, bool bForce) {
1353
0
    if (!bForce && u > CZNC::Get().GetMaxBufferSize()) return false;
1354
0
    for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
1355
0
        for (CChan* pChan : pNetwork->GetChans()) {
1356
0
            pChan->InheritBufferCount(u, bForce);
1357
0
        }
1358
0
    }
1359
0
    m_uChanBufferSize = u;
1360
0
    return true;
1361
0
}
1362
1363
0
bool CUser::SetQueryBufferSize(unsigned int u, bool bForce) {
1364
0
    if (!bForce && u > CZNC::Get().GetMaxBufferSize()) return false;
1365
0
    for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
1366
0
        for (CQuery* pQuery : pNetwork->GetQueries()) {
1367
0
            pQuery->SetBufferCount(u, bForce);
1368
0
        }
1369
0
    }
1370
0
    m_uQueryBufferSize = u;
1371
0
    return true;
1372
0
}
1373
1374
0
bool CUser::AddCTCPReply(const CString& sCTCP, const CString& sReply) {
1375
    // Reject CTCP requests containing spaces
1376
0
    if (sCTCP.find_first_of(' ') != CString::npos) {
1377
0
        return false;
1378
0
    }
1379
    // Reject empty CTCP requests
1380
0
    if (sCTCP.empty()) {
1381
0
        return false;
1382
0
    }
1383
0
    m_mssCTCPReplies[sCTCP.AsUpper()] = sReply;
1384
0
    return true;
1385
0
}
1386
1387
0
bool CUser::DelCTCPReply(const CString& sCTCP) {
1388
0
    return m_mssCTCPReplies.erase(sCTCP.AsUpper()) > 0;
1389
0
}
1390
1391
0
bool CUser::SetStatusPrefix(const CString& s) {
1392
0
    if ((!s.empty()) && (s.length() < 6) && (!s.Contains(" "))) {
1393
0
        m_sStatusPrefix = (s.empty()) ? "*" : s;
1394
0
        return true;
1395
0
    }
1396
1397
0
    return false;
1398
0
}
1399
1400
0
bool CUser::SetLanguage(const CString& s) {
1401
    // They look like ru-RU
1402
0
    for (char c : s) {
1403
0
        if (isalpha(c) || c == '-' || c == '_') {
1404
0
        } else {
1405
0
            return false;
1406
0
        }
1407
0
    }
1408
0
    m_sLanguage = s;
1409
    // 1.7.0 accidentally used _ instead of -, which made language
1410
    // non-selectable. But it's possible that someone put _ to znc.conf
1411
    // manually.
1412
    // TODO: cleanup _ some time later.
1413
0
    m_sLanguage.Replace("_", "-");
1414
0
    return true;
1415
0
}
1416
// !Setters
1417
1418
// Getters
1419
0
vector<CClient*> CUser::GetAllClients() const {
1420
0
    vector<CClient*> vClients;
1421
1422
0
    for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
1423
0
        for (CClient* pClient : pNetwork->GetClients()) {
1424
0
            vClients.push_back(pClient);
1425
0
        }
1426
0
    }
1427
1428
0
    for (CClient* pClient : m_vClients) {
1429
0
        vClients.push_back(pClient);
1430
0
    }
1431
1432
0
    return vClients;
1433
0
}
1434
1435
/// @deprecated
1436
0
const CString& CUser::GetUserName() const { return m_sUsername; }
1437
0
const CString& CUser::GetUsername() const { return m_sUsername; }
1438
0
const CString& CUser::GetCleanUserName() const { return m_sCleanUsername; }
1439
0
const CString& CUser::GetNick(bool bAllowDefault) const {
1440
0
    return (bAllowDefault && m_sNick.empty()) ? GetCleanUserName() : m_sNick;
1441
0
}
1442
0
const CString& CUser::GetAltNick(bool bAllowDefault) const {
1443
0
    return (bAllowDefault && m_sAltNick.empty()) ? GetCleanUserName()
1444
0
                                                 : m_sAltNick;
1445
0
}
1446
0
const CString& CUser::GetIdent(bool bAllowDefault) const {
1447
0
    return (bAllowDefault && m_sIdent.empty()) ? GetCleanUserName() : m_sIdent;
1448
0
}
1449
0
CString CUser::GetRealName() const {
1450
    // Not include version number via GetTag() because of
1451
    // https://github.com/znc/znc/issues/818#issuecomment-70402820
1452
0
    return (!m_sRealName.Trim_n().empty()) ? m_sRealName
1453
0
                                           : "ZNC - https://znc.in";
1454
0
}
1455
0
const CString& CUser::GetBindHost() const { return m_sBindHost; }
1456
0
const CString& CUser::GetDCCBindHost() const { return m_sDCCBindHost; }
1457
0
const CString& CUser::GetPass() const { return m_sPass; }
1458
0
CUser::eHashType CUser::GetPassHashType() const { return m_eHashType; }
1459
0
const CString& CUser::GetPassSalt() const { return m_sPassSalt; }
1460
0
bool CUser::DenyLoadMod() const { return m_bDenyLoadMod; }
1461
0
bool CUser::IsAdmin() const { return m_bAdmin; }
1462
0
bool CUser::DenySetBindHost() const { return m_bDenySetBindHost; }
1463
0
bool CUser::DenySetIdent() const { return m_bDenySetIdent; }
1464
0
bool CUser::DenySetNetwork() const { return m_bDenySetNetwork; }
1465
0
bool CUser::DenySetRealName() const { return m_bDenySetRealName; }
1466
0
bool CUser::DenySetQuitMsg() const { return m_bDenySetQuitMsg; }
1467
0
bool CUser::DenySetCTCPReplies() const { return m_bDenySetCTCPReplies; }
1468
0
bool CUser::MultiClients() const { return m_bMultiClients; }
1469
0
bool CUser::AuthOnlyViaModule() const { return m_bAuthOnlyViaModule; }
1470
0
const CString& CUser::GetStatusPrefix() const { return m_sStatusPrefix; }
1471
0
const CString& CUser::GetDefaultChanModes() const {
1472
0
    return m_sDefaultChanModes;
1473
0
}
1474
0
const CString& CUser::GetClientEncoding() const { return m_sClientEncoding; }
1475
0
bool CUser::HasSpaceForNewNetwork() const {
1476
0
    return GetNetworks().size() < MaxNetworks();
1477
0
}
1478
1479
0
CString CUser::GetQuitMsg() const {
1480
0
    return (!m_sQuitMsg.Trim_n().empty()) ? m_sQuitMsg : "%znc%";
1481
0
}
1482
0
const MCString& CUser::GetCTCPReplies() const { return m_mssCTCPReplies; }
1483
0
unsigned int CUser::GetBufferCount() const { return GetChanBufferSize(); }
1484
0
unsigned int CUser::GetChanBufferSize() const { return m_uChanBufferSize; }
1485
0
unsigned int CUser::GetQueryBufferSize() const { return m_uQueryBufferSize; }
1486
0
bool CUser::AutoClearChanBuffer() const { return m_bAutoClearChanBuffer; }
1487
0
bool CUser::AutoClearQueryBuffer() const { return m_bAutoClearQueryBuffer; }
1488
// CString CUser::GetSkinName() const { return (!m_sSkinName.empty()) ?
1489
// m_sSkinName : CZNC::Get().GetSkinName(); }
1490
0
CString CUser::GetSkinName() const { return m_sSkinName; }
1491
0
CString CUser::GetLanguage() const { return m_sLanguage; }
1492
0
const CString& CUser::GetUserPath() const {
1493
0
    if (!CFile::Exists(m_sUserPath)) {
1494
0
        CDir::MakeDir(m_sUserPath);
1495
0
    }
1496
0
    return m_sUserPath;
1497
0
}
1498
// !Getters
1499
1500
0
unsigned long long CUser::BytesRead() const {
1501
0
    unsigned long long uBytes = m_uBytesRead;
1502
0
    for (const CIRCNetwork* pNetwork : m_vIRCNetworks) {
1503
0
        uBytes += pNetwork->BytesRead();
1504
0
    }
1505
0
    return uBytes;
1506
0
}
1507
1508
0
unsigned long long CUser::BytesWritten() const {
1509
0
    unsigned long long uBytes = m_uBytesWritten;
1510
0
    for (const CIRCNetwork* pNetwork : m_vIRCNetworks) {
1511
0
        uBytes += pNetwork->BytesWritten();
1512
0
    }
1513
0
    return uBytes;
1514
0
}