Coverage Report

Created: 2025-12-14 06:55

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/znc/src/Utils.cpp
Line
Count
Source
1
/*
2
 * Copyright (C) 2004-2025 ZNC, see the NOTICE file for details.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
17
#ifdef __CYGWIN__
18
#ifndef _XOPEN_SOURCE
19
// strptime() wants this
20
#define _XOPEN_SOURCE 600
21
#endif
22
#endif
23
24
#include <znc/Utils.h>
25
#include <znc/ZNCDebug.h>
26
#include <znc/FileUtils.h>
27
#include <znc/Message.h>
28
#ifdef HAVE_LIBSSL
29
#include <openssl/ssl.h>
30
#include <openssl/bn.h>
31
#include <openssl/rsa.h>
32
#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x20700000L))
33
#define X509_getm_notBefore X509_get_notBefore
34
#define X509_getm_notAfter X509_get_notAfter
35
#endif
36
#endif /* HAVE_LIBSSL */
37
#include <memory>
38
#include <unistd.h>
39
#include <time.h>
40
41
#ifdef ZNC_HAVE_GETHOSTNAME
42
#include <limits.h>
43
#endif
44
45
#include <sys/types.h>
46
#include <sys/socket.h>
47
#include <netdb.h>
48
#include <netinet/in.h>
49
50
#ifdef HAVE_TCSETATTR
51
#include <termios.h>
52
#endif
53
54
#ifdef HAVE_ICU
55
#include <unicode/ucnv.h>
56
#include <unicode/errorcode.h>
57
#endif
58
59
#ifdef ZNC_HAVE_ARGON
60
#include <argon2.h>
61
#endif
62
63
#ifdef ZNC_HAVE_UNAME
64
#include <sys/utsname.h>
65
#endif
66
67
// Required with GCC 4.3+ if openssl is disabled
68
#include <cstring>
69
#include <cstdlib>
70
#include <iomanip>
71
#include <chrono>
72
73
#include "cctz/time_zone.h"
74
75
using std::map;
76
using std::vector;
77
78
0
CUtils::CUtils() {}
79
0
CUtils::~CUtils() {}
80
81
#ifdef HAVE_LIBSSL
82
// Generated by "openssl dhparam 2048"
83
constexpr const char* szDefaultDH2048 =
84
    "-----BEGIN DH PARAMETERS-----\n"
85
    "MIIBCAKCAQEAtS/K3TMY8IHzcCATQSjUF3rDidjDDQmT+mLxyxRORmzMPjFIFkKH\n"
86
    "MOmxZvyCBArdaoCCEBBOzrldl/bBLn5TOeZb+MW7mpBLANTuQSOu97DDM7EzbnqC\n"
87
    "b6z3QgixZ2+UqxdmQAu4nBPLFwym6W/XPFEHpz6iHISSvjzzo4cfI0xwWTcoAvFQ\n"
88
    "r/ZU5BXSXp7XuDxSyyAqaaKUxquElf+x56QWrpNJypjzPpslg5ViAKwWQS0TnCrU\n"
89
    "sVuhFtbNlZjqW1tMSBxiWFltS1HoEaaI79MEpf1Ps25OrQl8xqqCGKkZcHlNo4oF\n"
90
    "cvUyzAEcCQYHmiYjp2hoZbSa8b690TQaAwIBAg==\n"
91
    "-----END DH PARAMETERS-----\n";
92
93
void CUtils::GenerateCert(FILE* pOut) {
94
    const int days = 365;
95
    const int years = 10;
96
97
    unsigned int uSeed = (unsigned int)time(nullptr);
98
    int serial = (rand_r(&uSeed) % 9999);
99
100
    std::unique_ptr<BIGNUM, void (*)(BIGNUM*)> pExponent(BN_new(), ::BN_free);
101
    if (!pExponent || !BN_set_word(pExponent.get(), 0x10001)) return;
102
103
    std::unique_ptr<RSA, void (*)(RSA*)> pRSA(RSA_new(), ::RSA_free);
104
    if (!pRSA ||
105
        !RSA_generate_key_ex(pRSA.get(), 2048, pExponent.get(), nullptr))
106
        return;
107
108
    std::unique_ptr<EVP_PKEY, void (*)(EVP_PKEY*)> pKey(EVP_PKEY_new(),
109
                                                        ::EVP_PKEY_free);
110
    if (!pKey || !EVP_PKEY_set1_RSA(pKey.get(), pRSA.get())) return;
111
112
    std::unique_ptr<X509, void (*)(X509*)> pCert(X509_new(), ::X509_free);
113
    if (!pCert) return;
114
115
    X509_set_version(pCert.get(), 2);
116
    ASN1_INTEGER_set(X509_get_serialNumber(pCert.get()), serial);
117
    X509_gmtime_adj(X509_getm_notBefore(pCert.get()), 0);
118
    X509_gmtime_adj(X509_getm_notAfter(pCert.get()),
119
                    (long)60 * 60 * 24 * days * years);
120
    X509_set_pubkey(pCert.get(), pKey.get());
121
122
    const char* pLogName = getenv("LOGNAME");
123
    const CString sHostName = GetHostName();
124
125
    if (!pLogName) pLogName = "Unknown";
126
127
    CString sEmailAddr = pLogName;
128
    sEmailAddr += "@";
129
    sEmailAddr += sHostName;
130
131
    X509_NAME* pName = X509_get_subject_name(pCert.get());
132
    X509_NAME_add_entry_by_txt(pName, "OU", MBSTRING_ASC,
133
                               (unsigned char*)pLogName, -1, -1, 0);
134
    X509_NAME_add_entry_by_txt(pName, "CN", MBSTRING_ASC,
135
                               (unsigned char*)sHostName.c_str(), -1, -1, 0);
136
    X509_NAME_add_entry_by_txt(pName, "emailAddress", MBSTRING_ASC,
137
                               (unsigned char*)sEmailAddr.c_str(), -1, -1, 0);
138
139
    X509_set_issuer_name(pCert.get(), pName);
140
141
    if (!X509_sign(pCert.get(), pKey.get(), EVP_sha256())) return;
142
143
    PEM_write_RSAPrivateKey(pOut, pRSA.get(), nullptr, nullptr, 0, nullptr,
144
                            nullptr);
145
    PEM_write_X509(pOut, pCert.get());
146
147
    fprintf(pOut, "%s", szDefaultDH2048);
148
}
149
#endif /* HAVE_LIBSSL */
150
151
0
CString CUtils::GetIP(unsigned long addr) {
152
0
    char szBuf[16];
153
0
    memset((char*)szBuf, 0, 16);
154
155
0
    if (addr >= (1 << 24)) {
156
0
        unsigned long ip[4];
157
0
        ip[0] = addr >> 24 & 255;
158
0
        ip[1] = addr >> 16 & 255;
159
0
        ip[2] = addr >> 8 & 255;
160
0
        ip[3] = addr & 255;
161
0
        sprintf(szBuf, "%lu.%lu.%lu.%lu", ip[0], ip[1], ip[2], ip[3]);
162
0
    }
163
164
0
    return szBuf;
165
0
}
166
167
0
unsigned long CUtils::GetLongIP(const CString& sIP) {
168
0
    unsigned long ret;
169
0
    char ip[4][4];
170
0
    unsigned int i;
171
172
0
    i = sscanf(sIP.c_str(), "%3[0-9].%3[0-9].%3[0-9].%3[0-9]", ip[0], ip[1],
173
0
               ip[2], ip[3]);
174
0
    if (i != 4) return 0;
175
176
    // Beware that atoi("200") << 24 would overflow and turn negative!
177
0
    ret = atol(ip[0]) << 24;
178
0
    ret += atol(ip[1]) << 16;
179
0
    ret += atol(ip[2]) << 8;
180
0
    ret += atol(ip[3]) << 0;
181
182
0
    return ret;
183
0
}
184
185
0
CString CUtils::GetHostName() {
186
0
    const char *pEnv;
187
188
0
    pEnv = getenv("HOSTNAME");
189
0
    if (pEnv && pEnv[0])
190
0
        return pEnv;
191
192
0
#if defined(ZNC_HAVE_GETHOSTNAME) && defined(_POSIX_HOST_NAME_MAX)
193
0
    char szBuffer[_POSIX_HOST_NAME_MAX + 1];
194
0
    szBuffer[_POSIX_HOST_NAME_MAX] = 0;
195
0
    if (gethostname(szBuffer, _POSIX_HOST_NAME_MAX) == 0)
196
0
        return std::string(szBuffer);
197
0
#endif
198
199
0
#if defined(ZNC_HAVE_UNAME)
200
0
    struct utsname UnameBuffer;
201
202
0
    if (uname(&UnameBuffer) == 0 && UnameBuffer.nodename[0] != '\0')
203
0
        return UnameBuffer.nodename;
204
0
#endif
205
206
0
    return "host.unknown";
207
0
}
208
209
#ifdef ZNC_HAVE_ARGON
210
static CString SaltedArgonHash(const CString& sPass, const CString& sSalt) {
211
#define ZNC_ARGON_PARAMS /* iterations */ 6, /* memory */ 6144, /* parallelism */ 1
212
    constexpr int iHashLen = 32;
213
    CString sOut;
214
    sOut.resize(argon2_encodedlen(ZNC_ARGON_PARAMS, sSalt.length(), iHashLen, Argon2_id) + 1);
215
    int err = argon2id_hash_encoded(ZNC_ARGON_PARAMS, sPass.data(), sPass.length(), sSalt.data(), sSalt.length(), iHashLen, &sOut[0], sOut.length());
216
    if (err) {
217
        CUtils::PrintError(argon2_error_message(err));
218
        sOut.clear();
219
    }
220
    return sOut;
221
}
222
#undef ZNC_ARGON_PARAMS
223
#endif
224
225
0
CString CUtils::AskSaltedHashPassForConfig() {
226
0
    CString sSalt = GetSalt();
227
228
0
    while (true) {
229
0
        CString pass1;
230
0
        do {
231
0
            pass1 = CUtils::GetPass("Enter password");
232
0
        } while (pass1.empty());
233
234
0
        CString pass2 = CUtils::GetPass("Confirm password");
235
236
0
        if (!pass1.Equals(pass2, CString::CaseSensitive)) {
237
0
            CUtils::PrintError("The supplied passwords did not match");
238
0
        } else {
239
            // Construct the salted pass
240
0
            VCString vsLines;
241
0
            vsLines.push_back("<Pass password>");
242
#if ZNC_HAVE_ARGON
243
            vsLines.push_back("\tMethod = Argon2id");
244
            vsLines.push_back("\tHash = " + SaltedArgonHash(pass1, sSalt));
245
#else
246
0
            vsLines.push_back("\tMethod = SHA256");
247
0
            vsLines.push_back("\tHash = " + SaltedSHA256Hash(pass1, sSalt));
248
0
            vsLines.push_back("\tSalt = " + sSalt);
249
0
#endif
250
0
            vsLines.push_back("</Pass>");
251
0
            return CString("\n").Join(vsLines.begin(), vsLines.end());
252
0
        }
253
0
    }
254
0
}
255
256
0
CString CUtils::GetSalt() { return CString::RandomString(20); }
257
258
0
CString CUtils::SaltedMD5Hash(const CString& sPass, const CString& sSalt) {
259
0
    return CString(sPass + sSalt).MD5();
260
0
}
261
262
0
CString CUtils::SaltedSHA256Hash(const CString& sPass, const CString& sSalt) {
263
0
    return CString(sPass + sSalt).SHA256();
264
0
}
265
266
0
CString CUtils::SaltedHash(const CString& sPass, const CString& sSalt) {
267
#ifdef ZNC_HAVE_ARGON
268
    return SaltedArgonHash(sPass, sSalt);
269
#else
270
0
    return SaltedSHA256Hash(sPass, sSalt);
271
0
#endif
272
0
}
273
274
0
CString CUtils::GetPass(const CString& sPrompt) {
275
0
#ifdef HAVE_TCSETATTR
276
    // Disable echo
277
0
    struct termios t;
278
0
    tcgetattr(1, &t);
279
0
    struct termios t2 = t;
280
0
    t2.c_lflag &= ~ECHO;
281
0
    tcsetattr(1, TCSANOW, &t2);
282
    // Read pass
283
0
    CString r;
284
0
    GetInput(sPrompt, r);
285
    // Restore echo and go to new line
286
0
    tcsetattr(1, TCSANOW, &t);
287
0
    fprintf(stdout, "\n");
288
0
    fflush(stdout);
289
0
    return r;
290
#else
291
    PrintPrompt(sPrompt);
292
#ifdef HAVE_GETPASSPHRASE
293
    return getpassphrase("");
294
#else
295
    return getpass("");
296
#endif
297
#endif
298
0
}
299
300
0
bool CUtils::GetBoolInput(const CString& sPrompt, bool bDefault) {
301
0
    return CUtils::GetBoolInput(sPrompt, &bDefault);
302
0
}
303
304
0
bool CUtils::GetBoolInput(const CString& sPrompt, bool* pbDefault) {
305
0
    CString sRet, sDefault;
306
307
0
    if (pbDefault) {
308
0
        sDefault = (*pbDefault) ? "yes" : "no";
309
0
    }
310
311
0
    while (true) {
312
0
        GetInput(sPrompt, sRet, sDefault, "yes/no");
313
314
0
        if (sRet.Equals("y") || sRet.Equals("yes")) {
315
0
            return true;
316
0
        } else if (sRet.Equals("n") || sRet.Equals("no")) {
317
0
            return false;
318
0
        }
319
0
    }
320
0
}
321
322
bool CUtils::GetNumInput(const CString& sPrompt, unsigned int& uRet,
323
                         unsigned int uMin, unsigned int uMax,
324
0
                         unsigned int uDefault) {
325
0
    if (uMin > uMax) {
326
0
        return false;
327
0
    }
328
329
0
    CString sDefault = (uDefault != (unsigned int)~0) ? CString(uDefault) : "";
330
0
    CString sNum, sHint;
331
332
0
    if (uMax != (unsigned int)~0) {
333
0
        sHint = CString(uMin) + " to " + CString(uMax);
334
0
    } else if (uMin > 0) {
335
0
        sHint = CString(uMin) + " and up";
336
0
    }
337
338
0
    while (true) {
339
0
        GetInput(sPrompt, sNum, sDefault, sHint);
340
0
        if (sNum.empty()) {
341
0
            return false;
342
0
        }
343
344
0
        uRet = sNum.ToUInt();
345
346
0
        if ((uRet >= uMin && uRet <= uMax)) {
347
0
            break;
348
0
        }
349
350
0
        CUtils::PrintError("Number must be " + sHint);
351
0
    }
352
353
0
    return true;
354
0
}
355
356
bool CUtils::GetInput(const CString& sPrompt, CString& sRet,
357
0
                      const CString& sDefault, const CString& sHint) {
358
0
    CString sExtra;
359
0
    CString sInput;
360
0
    sExtra += (!sHint.empty()) ? (" (" + sHint + ")") : "";
361
0
    sExtra += (!sDefault.empty()) ? (" [" + sDefault + "]") : "";
362
363
0
    PrintPrompt(sPrompt + sExtra);
364
0
    char szBuf[1024];
365
0
    memset(szBuf, 0, 1024);
366
0
    if (fgets(szBuf, 1024, stdin) == nullptr) {
367
        // Reading failed (Error? EOF?)
368
0
        PrintError("Error while reading from stdin. Exiting...");
369
0
        exit(-1);
370
0
    }
371
0
    sInput = szBuf;
372
373
0
    sInput.TrimSuffix("\n");
374
375
0
    if (sInput.empty()) {
376
0
        sRet = sDefault;
377
0
    } else {
378
0
        sRet = sInput;
379
0
    }
380
381
0
    return !sRet.empty();
382
0
}
383
384
0
#define BOLD "\033[1m"
385
#define NORM "\033[22m"
386
387
#define RED "\033[31m"
388
#define GRN "\033[32m"
389
#define YEL "\033[33m"
390
#define BLU "\033[34m"
391
#define DFL "\033[39m"
392
393
0
void CUtils::PrintError(const CString& sMessage) {
394
0
    if (CDebug::StdoutIsTTY())
395
0
        fprintf(stdout, BOLD BLU "[" RED " ** " BLU "]" DFL NORM " %s\n",
396
0
                sMessage.c_str());
397
0
    else
398
0
        fprintf(stdout, "%s\n", sMessage.c_str());
399
0
    fflush(stdout);
400
0
}
401
402
0
void CUtils::PrintPrompt(const CString& sMessage) {
403
0
    if (CDebug::StdoutIsTTY())
404
0
        fprintf(stdout, BOLD BLU "[" YEL " ?? " BLU "]" DFL NORM " %s: ",
405
0
                sMessage.c_str());
406
0
    else
407
0
        fprintf(stdout, "[ ?? ] %s: ", sMessage.c_str());
408
0
    fflush(stdout);
409
0
}
410
411
0
void CUtils::PrintMessage(const CString& sMessage, bool bStrong) {
412
0
    if (CDebug::StdoutIsTTY()) {
413
0
        if (bStrong)
414
0
            fprintf(stdout,
415
0
                    BOLD BLU "[" YEL " ** " BLU "]" DFL BOLD " %s" NORM "\n",
416
0
                    sMessage.c_str());
417
0
        else
418
0
            fprintf(stdout, BOLD BLU "[" YEL " ** " BLU "]" DFL NORM " %s\n",
419
0
                    sMessage.c_str());
420
0
    } else
421
0
        fprintf(stdout, "%s\n", sMessage.c_str());
422
423
0
    fflush(stdout);
424
0
}
425
426
0
void CUtils::PrintAction(const CString& sMessage) {
427
0
    if (CDebug::StdoutIsTTY())
428
0
        fprintf(stdout, BOLD BLU "[ .. " BLU "]" DFL NORM " %s...\n",
429
0
                sMessage.c_str());
430
0
    else
431
0
        fprintf(stdout, "%s... ", sMessage.c_str());
432
0
    fflush(stdout);
433
0
}
434
435
0
void CUtils::PrintStatus(bool bSuccess, const CString& sMessage) {
436
0
    if (CDebug::StdoutIsTTY()) {
437
0
        if (bSuccess) {
438
0
            if (!sMessage.empty())
439
0
                fprintf(stdout,
440
0
                        BOLD BLU "[" GRN " >> " BLU "]" DFL NORM " %s\n",
441
0
                        sMessage.c_str());
442
0
        } else {
443
0
            fprintf(stdout, BOLD BLU "[" RED " !! " BLU "]" DFL NORM BOLD RED
444
0
                                     " %s" DFL NORM "\n",
445
0
                    sMessage.empty() ? "failed" : sMessage.c_str());
446
0
        }
447
0
    } else {
448
0
        if (bSuccess) {
449
0
            fprintf(stdout, "%s\n", sMessage.c_str());
450
0
        } else {
451
0
            if (!sMessage.empty()) {
452
0
                fprintf(stdout, "[ %s ]", sMessage.c_str());
453
0
            }
454
455
0
            fprintf(stdout, "\n");
456
0
        }
457
0
    }
458
459
0
    fflush(stdout);
460
0
}
461
462
2.13k
timeval CUtils::GetTime() {
463
2.13k
#ifdef HAVE_CLOCK_GETTIME
464
2.13k
    timespec ts;
465
2.13k
    if (clock_gettime(CLOCK_REALTIME, &ts) == 0) {
466
2.13k
        return { ts.tv_sec, static_cast<suseconds_t>(ts.tv_nsec / 1000) };
467
2.13k
    }
468
0
#endif
469
470
0
    struct timeval tv;
471
0
    if (gettimeofday(&tv, nullptr) == 0) {
472
0
        return tv;
473
0
    }
474
475
    // Last resort, no microseconds
476
0
    return { time(nullptr), 0 };
477
0
}
478
479
0
unsigned long long CUtils::GetMillTime() {
480
0
    std::chrono::time_point<std::chrono::steady_clock> time = std::chrono::steady_clock::now();
481
0
    return std::chrono::duration_cast<std::chrono::milliseconds>(time.time_since_epoch()).count();
482
0
}
483
484
0
CString CUtils::CTime(time_t t, const CString& sTimezone) {
485
0
    return FormatTime(t, "%c", sTimezone);
486
0
}
487
488
CString CUtils::FormatTime(time_t t, const CString& sFormat,
489
0
                           const CString& sTimezone) {
490
0
    cctz::time_zone tz;
491
0
    if (sTimezone.empty()) {
492
0
        tz = cctz::local_time_zone();
493
0
    } else if (sTimezone.StartsWith("GMT")) {
494
0
        int offset = CString(sTimezone.substr(3)).ToInt();
495
0
        tz = cctz::fixed_time_zone(cctz::seconds(offset * 60 * 60));
496
0
    } else {
497
0
        cctz::load_time_zone(sTimezone, &tz);
498
0
    }
499
500
0
    return cctz::format(sFormat, std::chrono::system_clock::from_time_t(t), tz);
501
0
}
502
503
CString CUtils::FormatTime(const timeval& tv, const CString& sFormat,
504
0
                           const CString& sTimezone) {
505
    // Parse additional format specifiers before passing them to
506
    // strftime, since the way strftime treats unknown format
507
    // specifiers is undefined.
508
    // TODO: consider using cctz's %E#f instead.
509
0
    CString sFormat2;
510
511
    // Make sure %% is parsed correctly, i.e. %%f is passed through to
512
    // strftime to become %f, and not 123.
513
0
    bool bInFormat = false;
514
0
    int iDigits;
515
0
    CString::size_type uLastCopied = 0, uFormatStart;
516
517
0
    for (CString::size_type i = 0; i < sFormat.length(); i++) {
518
0
        if (!bInFormat) {
519
0
            if (sFormat[i] == '%') {
520
0
                uFormatStart = i;
521
0
                bInFormat = true;
522
0
                iDigits = 3;
523
0
            }
524
0
        } else {
525
0
            switch (sFormat[i]) {
526
0
                case '0': case '1': case '2': case '3': case '4':
527
0
                case '5': case '6': case '7': case '8': case '9':
528
0
                    iDigits = sFormat[i] - '0';
529
0
                    break;
530
0
                case 'f': {
531
0
                    int iVal = tv.tv_usec;
532
0
                    int iDigitDelta = iDigits - 6; // tv_user is in 10^-6 seconds
533
0
                    for (; iDigitDelta > 0; iDigitDelta--)
534
0
                        iVal *= 10;
535
0
                    for (; iDigitDelta < 0; iDigitDelta++)
536
0
                        iVal /= 10;
537
0
                    sFormat2 += sFormat.substr(uLastCopied,
538
0
                        uFormatStart - uLastCopied);
539
0
                    CString sVal = CString(iVal);
540
0
                    sFormat2 += CString(iDigits - sVal.length(), '0');
541
0
                    sFormat2 += sVal;
542
0
                    uLastCopied = i + 1;
543
0
                    bInFormat = false;
544
0
                    break;
545
0
                }
546
0
                default:
547
0
                    bInFormat = false;
548
0
            }
549
0
        }
550
0
    }
551
552
0
    if (uLastCopied) {
553
0
        sFormat2 += sFormat.substr(uLastCopied);
554
0
        return FormatTime(tv.tv_sec, sFormat2, sTimezone);
555
0
    } else {
556
        // If there are no extended format specifiers, avoid doing any
557
        // memory allocations entirely.
558
0
        return FormatTime(tv.tv_sec, sFormat, sTimezone);
559
0
    }
560
0
}
561
562
0
CString CUtils::FormatServerTime(const timeval& tv) {
563
0
    using namespace std::chrono;
564
0
    system_clock::time_point time{duration_cast<system_clock::duration>(
565
0
        seconds(tv.tv_sec) + microseconds(tv.tv_usec))};
566
0
    return cctz::format("%Y-%m-%dT%H:%M:%E3SZ", time, cctz::utc_time_zone());
567
0
}
568
569
0
timeval CUtils::ParseServerTime(const CString& sTime) {
570
0
    using namespace std::chrono;
571
0
    system_clock::time_point tp;
572
0
    cctz::parse("%Y-%m-%dT%H:%M:%E*SZ", sTime, cctz::utc_time_zone(), &tp);
573
0
    struct timeval tv;
574
0
    memset(&tv, 0, sizeof(tv));
575
0
    microseconds usec = duration_cast<microseconds>(tp.time_since_epoch());
576
0
    tv.tv_sec = usec.count() / 1000000;
577
0
    tv.tv_usec = usec.count() % 1000000;
578
0
    return tv;
579
0
}
580
581
namespace {
582
void FillTimezones(const CString& sPath, SCString& result,
583
0
                   const CString& sPrefix) {
584
0
    CDir Dir;
585
0
    Dir.Fill(sPath);
586
0
    for (CFile* pFile : Dir) {
587
0
        CString sName = pFile->GetShortName();
588
0
        CString sFile = pFile->GetLongName();
589
0
        if (sName == "posix" || sName == "right")
590
0
            continue;  // these 2 dirs contain the same filenames
591
0
        if (sName.EndsWith(".tab") || sName == "posixrules" ||
592
0
            sName == "localtime")
593
0
            continue;
594
0
        if (pFile->IsDir()) {
595
0
            if (sName == "Etc") {
596
0
                FillTimezones(sFile, result, sPrefix);
597
0
            } else {
598
0
                FillTimezones(sFile, result, sPrefix + sName + "/");
599
0
            }
600
0
        } else {
601
0
            result.insert(sPrefix + sName);
602
0
        }
603
0
    }
604
0
}
605
}  // namespace
606
607
0
SCString CUtils::GetTimezones() {
608
0
    static SCString result;
609
0
    if (result.empty()) {
610
0
        FillTimezones("/usr/share/zoneinfo", result, "");
611
0
    }
612
0
    return result;
613
0
}
614
615
0
SCString CUtils::GetEncodings() {
616
0
    static SCString ssResult;
617
#ifdef HAVE_ICU
618
    if (ssResult.empty()) {
619
        for (int i = 0; i < ucnv_countAvailable(); ++i) {
620
            const char* pConvName = ucnv_getAvailableName(i);
621
            ssResult.insert(pConvName);
622
            icu::ErrorCode e;
623
            for (int st = 0; st < ucnv_countStandards(); ++st) {
624
                const char* pStdName = ucnv_getStandard(st, e);
625
                icu::LocalUEnumerationPointer ue(
626
                    ucnv_openStandardNames(pConvName, pStdName, e));
627
                while (const char* pStdConvNameEnum =
628
                           uenum_next(ue.getAlias(), nullptr, e)) {
629
                    ssResult.insert(pStdConvNameEnum);
630
                }
631
            }
632
        }
633
    }
634
#endif
635
0
    return ssResult;
636
0
}
637
638
0
bool CUtils::CheckCIDR(const CString& sIP, const CString& sRange) {
639
0
    if (sIP.WildCmp(sRange)) {
640
0
        return true;
641
0
    }
642
0
    auto deleter = [](addrinfo* ai) { freeaddrinfo(ai); };
643
    // Try to split the string into an IP and routing prefix
644
0
    VCString vsSplitCIDR;
645
0
    if (sRange.Split("/", vsSplitCIDR, false) != 2) return false;
646
0
    const CString sRoutingPrefix = vsSplitCIDR.back();
647
0
    const int iRoutingPrefix = sRoutingPrefix.ToInt();
648
0
    if (iRoutingPrefix < 0 || iRoutingPrefix > 128) return false;
649
650
    // If iRoutingPrefix is 0, it could be due to ToInt() failing, so
651
    // sRoutingPrefix needs to be all zeroes
652
0
    if (iRoutingPrefix == 0 && sRoutingPrefix != "0") return false;
653
654
    // Convert each IP from a numeric string to an addrinfo
655
0
    addrinfo aiHints;
656
0
    memset(&aiHints, 0, sizeof(addrinfo));
657
0
    aiHints.ai_flags = AI_NUMERICHOST;
658
659
0
    addrinfo* aiHostC;
660
0
    int iIsHostValid = getaddrinfo(sIP.c_str(), nullptr, &aiHints, &aiHostC);
661
0
    if (iIsHostValid != 0) return false;
662
0
    std::unique_ptr<addrinfo, decltype(deleter)> aiHost(aiHostC, deleter);
663
664
0
    aiHints.ai_family = aiHost->ai_family;  // Host and range must be in
665
                                            // the same address family
666
667
0
    addrinfo* aiRangeC;
668
0
    int iIsRangeValid =
669
0
        getaddrinfo(vsSplitCIDR.front().c_str(), nullptr, &aiHints, &aiRangeC);
670
0
    if (iIsRangeValid != 0) {
671
0
        return false;
672
0
    }
673
0
    std::unique_ptr<addrinfo, decltype(deleter)> aiRange(aiRangeC, deleter);
674
675
    // "/0" allows all IPv[4|6] addresses
676
0
    if (iRoutingPrefix == 0) {
677
0
        return true;
678
0
    }
679
680
    // If both IPs are valid and of the same type, make a bit field mask
681
    // from the routing prefix, AND it to the host and range, and see if
682
    // they match
683
0
    if (aiHost->ai_family == AF_INET) {
684
0
        if (iRoutingPrefix > 32) {
685
0
            return false;
686
0
        }
687
688
0
        const sockaddr_in* saHost =
689
0
            reinterpret_cast<const sockaddr_in*>(aiHost->ai_addr);
690
0
        const sockaddr_in* saRange =
691
0
            reinterpret_cast<const sockaddr_in*>(aiRange->ai_addr);
692
693
        // Make IPv4 bitmask
694
0
        const in_addr_t inBitmask = htonl((~0u) << (32 - iRoutingPrefix));
695
696
        // Compare masked IPv4s
697
0
        return ((inBitmask & saHost->sin_addr.s_addr) ==
698
0
                (inBitmask & saRange->sin_addr.s_addr));
699
0
    } else if (aiHost->ai_family == AF_INET6) {
700
        // Make IPv6 bitmask
701
0
        in6_addr in6aBitmask;
702
0
        memset(&in6aBitmask, 0, sizeof(in6aBitmask));
703
704
0
        for (int i = 0, iBitsLeft = iRoutingPrefix; iBitsLeft > 0;
705
0
             ++i, iBitsLeft -= 8) {
706
0
            if (iBitsLeft >= 8) {
707
0
                in6aBitmask.s6_addr[i] = (uint8_t)(~0u);
708
0
            } else {
709
0
                in6aBitmask.s6_addr[i] = (uint8_t)(~0u) << (8 - iBitsLeft);
710
0
            }
711
0
        }
712
713
        // Compare masked IPv6s
714
0
        const sockaddr_in6* sa6Host =
715
0
            reinterpret_cast<const sockaddr_in6*>(aiHost->ai_addr);
716
0
        const sockaddr_in6* sa6Range =
717
0
            reinterpret_cast<const sockaddr_in6*>(aiRange->ai_addr);
718
719
0
        for (int i = 0; i < 16; ++i) {
720
0
            if ((in6aBitmask.s6_addr[i] & sa6Host->sin6_addr.s6_addr[i]) !=
721
0
                (in6aBitmask.s6_addr[i] & sa6Range->sin6_addr.s6_addr[i])) {
722
0
                return false;
723
0
            }
724
0
        }
725
0
        return true;
726
0
    } else {
727
0
        return false;
728
0
    }
729
0
}
730
731
0
MCString CUtils::GetMessageTags(const CString& sLine) {
732
0
    if (sLine.StartsWith("@")) {
733
0
        return CMessage(sLine).GetTags();
734
0
    }
735
0
    return MCString::EmptyMap;
736
0
}
737
738
0
void CUtils::SetMessageTags(CString& sLine, const MCString& mssTags) {
739
0
    CMessage Message(sLine);
740
0
    Message.SetTags(mssTags);
741
0
    sLine = Message.ToString();
742
0
}
743
744
0
bool CTable::AddColumn(const CString& sName) {
745
0
    if (eStyle == ListStyle && m_vsHeaders.size() >= 2)
746
0
        return false;
747
0
    for (const CString& sHeader : m_vsHeaders) {
748
0
        if (sHeader.Equals(sName)) {
749
0
            return false;
750
0
        }
751
0
    }
752
753
0
    m_vsHeaders.push_back(sName);
754
0
    m_msuWidths[sName] = sName.size();
755
756
0
    return true;
757
0
}
758
759
0
bool CTable::SetStyle(EStyle eNewStyle) {
760
0
    switch (eNewStyle) {
761
0
    case GridStyle:
762
0
        break;
763
0
    case ListStyle:
764
0
        if (m_vsHeaders.size() > 2) return false;
765
0
        break;
766
0
    }
767
768
0
    eStyle = eNewStyle;
769
0
    return true;
770
0
}
771
772
0
CTable::size_type CTable::AddRow() {
773
    // Don't add a row if no headers are defined
774
0
    if (m_vsHeaders.empty()) {
775
0
        return (size_type)-1;
776
0
    }
777
778
    // Add a vector with enough space for each column
779
0
    push_back(vector<CString>(m_vsHeaders.size()));
780
0
    return size() - 1;
781
0
}
782
783
bool CTable::SetCell(const CString& sColumn, const CString& sValue,
784
0
                     size_type uRowIdx) {
785
0
    if (uRowIdx == (size_type)~0) {
786
0
        if (empty()) {
787
0
            return false;
788
0
        }
789
790
0
        uRowIdx = size() - 1;
791
0
    }
792
793
0
    unsigned int uColIdx = GetColumnIndex(sColumn);
794
795
0
    if (uColIdx == (unsigned int)-1) return false;
796
797
0
    (*this)[uRowIdx][uColIdx] = sValue;
798
799
0
    if (m_msuWidths[sColumn] < sValue.size())
800
0
        m_msuWidths[sColumn] = sValue.size();
801
802
0
    return true;
803
0
}
804
805
0
bool CTable::GetLine(unsigned int uIdx, CString& sLine) const {
806
0
    std::stringstream ssRet;
807
808
0
    if (empty()) {
809
0
        return false;
810
0
    }
811
812
0
    if (eStyle == ListStyle) {
813
0
        if (m_vsHeaders.size() > 2) return false; // definition list mode can only do up to two columns
814
0
        if (uIdx >= size()) return false;
815
816
0
        const std::vector<CString>& mRow = (*this)[uIdx];
817
0
        ssRet << "\x02" << mRow[0] << "\x0f"; //bold first column
818
0
        if (m_vsHeaders.size() >= 2 && mRow[1] != "") {
819
0
            ssRet << ": " << mRow[1];
820
0
        }
821
822
0
        sLine = ssRet.str();
823
0
        return true;
824
0
    }
825
826
0
    if (uIdx == 1) {
827
0
        ssRet.fill(' ');
828
0
        ssRet << "| ";
829
830
0
        for (unsigned int a = 0; a < m_vsHeaders.size(); a++) {
831
0
            ssRet.width(GetColumnWidth(a));
832
0
            ssRet << std::left << m_vsHeaders[a];
833
0
            ssRet << ((a == m_vsHeaders.size() - 1) ? " |" : " | ");
834
0
        }
835
836
0
        sLine = ssRet.str();
837
0
        return true;
838
0
    } else if ((uIdx == 0) || (uIdx == 2) || (uIdx == (size() + 3))) {
839
0
        ssRet.fill('-');
840
0
        ssRet << "+-";
841
842
0
        for (unsigned int a = 0; a < m_vsHeaders.size(); a++) {
843
0
            ssRet.width(GetColumnWidth(a));
844
0
            ssRet << std::left << "-";
845
0
            ssRet << ((a == m_vsHeaders.size() - 1) ? "-+" : "-+-");
846
0
        }
847
848
0
        sLine = ssRet.str();
849
0
        return true;
850
0
    } else {
851
0
        uIdx -= 3;
852
853
0
        if (uIdx < size()) {
854
0
            const std::vector<CString>& mRow = (*this)[uIdx];
855
0
            ssRet.fill(' ');
856
0
            ssRet << "| ";
857
858
0
            for (unsigned int c = 0; c < m_vsHeaders.size(); c++) {
859
0
                ssRet.width(GetColumnWidth(c));
860
0
                ssRet << std::left << mRow[c];
861
0
                ssRet << ((c == m_vsHeaders.size() - 1) ? " |" : " | ");
862
0
            }
863
864
0
            sLine = ssRet.str();
865
0
            return true;
866
0
        }
867
0
    }
868
869
0
    return false;
870
0
}
871
872
0
unsigned int CTable::GetColumnIndex(const CString& sName) const {
873
0
    for (unsigned int i = 0; i < m_vsHeaders.size(); i++) {
874
0
        if (m_vsHeaders[i] == sName) return i;
875
0
    }
876
877
0
    DEBUG("CTable::GetColumnIndex(" + sName + ") failed");
878
879
0
    return (unsigned int)-1;
880
0
}
881
882
0
CString::size_type CTable::GetColumnWidth(unsigned int uIdx) const {
883
0
    if (uIdx >= m_vsHeaders.size()) {
884
0
        return 0;
885
0
    }
886
887
0
    const CString& sColName = m_vsHeaders[uIdx];
888
0
    std::map<CString, CString::size_type>::const_iterator it =
889
0
        m_msuWidths.find(sColName);
890
891
0
    if (it == m_msuWidths.end()) {
892
        // AddColumn() and SetCell() should make sure that we get a value :/
893
0
        return 0;
894
0
    }
895
0
    return it->second;
896
0
}
897
898
0
void CTable::Clear() {
899
0
    clear();
900
0
    m_vsHeaders.clear();
901
0
    m_msuWidths.clear();
902
0
}
903
904
#ifdef HAVE_LIBSSL
905
CBlowfish::CBlowfish(const CString& sPassword, int iEncrypt,
906
                     const CString& sIvec)
907
    : m_ivec((unsigned char*)calloc(sizeof(unsigned char), 8)),
908
      m_bkey(),
909
      m_iEncrypt(iEncrypt),
910
      m_num(0) {
911
    if (sIvec.length() >= 8) {
912
        memcpy(m_ivec, sIvec.data(), 8);
913
    }
914
915
    BF_set_key(&m_bkey, (unsigned int)sPassword.length(),
916
               (unsigned char*)sPassword.data());
917
}
918
919
CBlowfish::~CBlowfish() { free(m_ivec); }
920
921
//! output must be freed
922
unsigned char* CBlowfish::MD5(const unsigned char* input, unsigned int ilen) {
923
    unsigned char* output = (unsigned char*)malloc(MD5_DIGEST_LENGTH);
924
    ::MD5(input, ilen, output);
925
    return output;
926
}
927
928
//! returns an md5 of the CString (not hex encoded)
929
CString CBlowfish::MD5(const CString& sInput, bool bHexEncode) {
930
    CString sRet;
931
    unsigned char* data =
932
        MD5((const unsigned char*)sInput.data(), (unsigned int)sInput.length());
933
934
    if (!bHexEncode) {
935
        sRet.append((const char*)data, MD5_DIGEST_LENGTH);
936
    } else {
937
        for (int a = 0; a < MD5_DIGEST_LENGTH; a++) {
938
            sRet += g_HexDigits[data[a] >> 4];
939
            sRet += g_HexDigits[data[a] & 0xf];
940
        }
941
    }
942
943
    free(data);
944
    return sRet;
945
}
946
947
//! output must be the same size as input
948
void CBlowfish::Crypt(unsigned char* input, unsigned char* output,
949
                      unsigned int uBytes) {
950
    BF_cfb64_encrypt(input, output, uBytes, &m_bkey, m_ivec, &m_num,
951
                     m_iEncrypt);
952
}
953
954
//! must free result
955
unsigned char* CBlowfish::Crypt(unsigned char* input, unsigned int uBytes) {
956
    unsigned char* buff = (unsigned char*)malloc(uBytes);
957
    Crypt(input, buff, uBytes);
958
    return buff;
959
}
960
961
CString CBlowfish::Crypt(const CString& sData) {
962
    unsigned char* buff =
963
        Crypt((unsigned char*)sData.data(), (unsigned int)sData.length());
964
    CString sOutput;
965
    sOutput.append((const char*)buff, sData.length());
966
    free(buff);
967
    return sOutput;
968
}
969
970
#endif  // HAVE_LIBSSL