Passed
Push — teampass_3.0 ( ae6be7...59a813 )
by Nils
04:28
created

cryption()   B

Complexity

Conditions 11
Paths 52

Size

Total Lines 50
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 7
Bugs 0 Features 0
Metric Value
cc 11
eloc 39
c 7
b 0
f 0
nc 52
nop 4
dl 0
loc 50
rs 7.3166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This library is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
 * ---
12
 *
13
 * @project   Teampass
14
 *
15
 * @file      main.functions.php
16
 * ---
17
 *
18
 * @author    Nils Laumaillé ([email protected])
19
 *
20
 * @copyright 2009-2022 Teampass.net
21
 *
22
 * @license   https://spdx.org/licenses/GPL-3.0-only.html#licenseText GPL-3.0
23
 * ---
24
 *
25
 * @see       https://www.teampass.net
26
 */
27
28
use LdapRecord\Connection;
29
30
if (isset($_SESSION['CPM']) === false || (int) $_SESSION['CPM'] !== 1) {
31
    die('Hacking attempt...');
32
}
33
34
// Load config if $SETTINGS not defined
35
if (isset($SETTINGS['cpassman_dir']) === false || empty($SETTINGS['cpassman_dir']) === true) {
36
    if (file_exists('../includes/config/tp.config.php')) {
37
        include_once '../includes/config/tp.config.php';
38
    } elseif (file_exists('./includes/config/tp.config.php')) {
39
        include_once './includes/config/tp.config.php';
40
    } elseif (file_exists('../../includes/config/tp.config.php')) {
41
        include_once '../../includes/config/tp.config.php';
42
    } else {
43
        throw new Exception("Error file '/includes/config/tp.config.php' not exists", 1);
44
    }
45
}
46
47
header('Content-type: text/html; charset=utf-8');
48
header('Cache-Control: no-cache, must-revalidate');
49
/**
50
 * Convert language code to string.
51
 *
52
 * @param string $string String to get
53
 */
54
function langHdl(string $string): string
55
{
56
    if (empty($string) === true) {
57
        // Manage error
58
        return 'ERROR in language strings!';
59
    }
60
61
    // Load superglobal
62
    if (file_exists('../includes/libraries/protect/SuperGlobal/SuperGlobal.php')) {
63
        include_once '../includes/libraries/protect/SuperGlobal/SuperGlobal.php';
64
    } elseif (file_exists('./includes/libraries/protect/SuperGlobal/SuperGlobal.php')) {
65
        include_once './includes/libraries/protect/SuperGlobal/SuperGlobal.php';
66
    } elseif (file_exists('../../includes/libraries/protect/SuperGlobal/SuperGlobal.php')) {
67
        include_once '../../includes/libraries/protect/SuperGlobal/SuperGlobal.php';
68
    } else {
69
        throw new Exception("Error file '/includes/libraries/protect/SuperGlobal/SuperGlobal.php' not exists", 1);
70
    }
71
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
72
    // Get language string
73
    $session_language = $superGlobal->get(trim($string), 'SESSION', 'lang');
74
    if (isset($session_language) === false) {
75
        // Manage error
76
        return 'ERROR in language strings!';
77
    }
78
    return str_replace(
79
        ['"', "'"],
80
        ['&quot;', '&apos;'],
81
        $session_language
82
    );
83
}
84
85
/**
86
 * genHash().
87
 *
88
 * Generate a hash for user login
89
 *
90
 * @param string $password What password
91
 * @param string $cost     What cost
92
 *
93
 * @return string|void
94
 */
95
function bCrypt(
96
    string $password,
97
    string $cost
98
): ?string
99
{
100
    $salt = sprintf('$2y$%02d$', $cost);
101
    if (function_exists('openssl_random_pseudo_bytes')) {
102
        $salt .= bin2hex(openssl_random_pseudo_bytes(11));
103
    } else {
104
        $chars = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
105
        for ($i = 0; $i < 22; ++$i) {
106
            $salt .= $chars[mt_rand(0, 63)];
107
        }
108
    }
109
110
    return crypt($password, $salt);
111
}
112
113
/**
114
 * Defuse cryption function.
115
 *
116
 * @param string $message   what to de/crypt
117
 * @param string $ascii_key key to use
118
 * @param string $type      operation to perform
119
 * @param array  $SETTINGS  Teampass settings
120
 *
121
 * @return array
122
 */
123
function cryption(string $message, string $ascii_key, string $type, array $SETTINGS): array
124
{
125
    $ascii_key = empty($ascii_key) === true ? file_get_contents(SECUREPATH . '/teampass-seckey.txt') : $ascii_key;
126
    $err = false;
127
    // load PhpEncryption library
128
    if (isset($SETTINGS['cpassman_dir']) === false || empty($SETTINGS['cpassman_dir']) === true) {
129
        $path = '../includes/libraries/Encryption/Encryption/';
130
    } else {
131
        $path = $SETTINGS['cpassman_dir'] . '/includes/libraries/Encryption/Encryption/';
132
    }
133
134
    include_once $path . 'Exception/CryptoException.php';
135
    include_once $path . 'Exception/BadFormatException.php';
136
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
137
    include_once $path . 'Exception/IOException.php';
138
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
139
    include_once $path . 'Crypto.php';
140
    include_once $path . 'Encoding.php';
141
    include_once $path . 'DerivedKeys.php';
142
    include_once $path . 'Key.php';
143
    include_once $path . 'KeyOrPassword.php';
144
    include_once $path . 'File.php';
145
    include_once $path . 'RuntimeTests.php';
146
    include_once $path . 'KeyProtectedByPassword.php';
147
    include_once $path . 'Core.php';
148
    
149
    // convert KEY
150
    $key = \Defuse\Crypto\Key::loadFromAsciiSafeString($ascii_key);
151
    try {
152
        if ($type === 'encrypt') {
153
            $text = \Defuse\Crypto\Crypto::encrypt($message, $key);
154
        } elseif ($type === 'decrypt') {
155
            $text = \Defuse\Crypto\Crypto::decrypt($message, $key);
156
        }
157
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
158
        $err = 'an attack! either the wrong key was loaded, or the ciphertext has changed since it was created either corrupted in the database or intentionally modified by someone trying to carry out an attack.';
159
    } catch (Defuse\Crypto\Exception\BadFormatException $ex) {
160
        $err = $ex;
161
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
162
        $err = $ex;
163
    } catch (Defuse\Crypto\Exception\CryptoException $ex) {
164
        $err = $ex;
165
    } catch (Defuse\Crypto\Exception\IOException $ex) {
166
        $err = $ex;
167
    }
168
    //echo \Defuse\Crypto\Crypto::decrypt($message, $key).' ## ';
169
170
    return [
171
        'string' => $text ?? '',
172
        'error' => $err,
173
    ];
174
}
175
176
/**
177
 * Generating a defuse key.
178
 *
179
 * @return string
180
 */
181
function defuse_generate_key()
182
{
183
    // load PhpEncryption library
184
    if (file_exists('../includes/config/tp.config.php') === true) {
185
        $path = '../includes/libraries/Encryption/Encryption/';
186
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
187
        $path = './includes/libraries/Encryption/Encryption/';
188
    } else {
189
        $path = '../includes/libraries/Encryption/Encryption/';
190
    }
191
192
    include_once $path . 'Exception/CryptoException.php';
193
    include_once $path . 'Exception/BadFormatException.php';
194
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
195
    include_once $path . 'Exception/IOException.php';
196
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
197
    include_once $path . 'Crypto.php';
198
    include_once $path . 'Encoding.php';
199
    include_once $path . 'DerivedKeys.php';
200
    include_once $path . 'Key.php';
201
    include_once $path . 'KeyOrPassword.php';
202
    include_once $path . 'File.php';
203
    include_once $path . 'RuntimeTests.php';
204
    include_once $path . 'KeyProtectedByPassword.php';
205
    include_once $path . 'Core.php';
206
207
    $key = \Defuse\Crypto\Key::createNewRandomKey();
208
    $key = $key->saveToAsciiSafeString();
209
    return $key;
210
}
211
212
/**
213
 * Generate a Defuse personal key.
214
 *
215
 * @param string $psk psk used
216
 *
217
 * @return string
218
 */
219
function defuse_generate_personal_key(string $psk): string
220
{
221
    // load PhpEncryption library
222
    if (file_exists('../includes/config/tp.config.php') === true) {
223
        $path = '../includes/libraries/Encryption/Encryption/';
224
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
225
        $path = './includes/libraries/Encryption/Encryption/';
226
    } else {
227
        $path = '../includes/libraries/Encryption/Encryption/';
228
    }
229
230
    include_once $path . 'Exception/CryptoException.php';
231
    include_once $path . 'Exception/BadFormatException.php';
232
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
233
    include_once $path . 'Exception/IOException.php';
234
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
235
    include_once $path . 'Crypto.php';
236
    include_once $path . 'Encoding.php';
237
    include_once $path . 'DerivedKeys.php';
238
    include_once $path . 'Key.php';
239
    include_once $path . 'KeyOrPassword.php';
240
    include_once $path . 'File.php';
241
    include_once $path . 'RuntimeTests.php';
242
    include_once $path . 'KeyProtectedByPassword.php';
243
    include_once $path . 'Core.php';
244
    
245
    $protected_key = \Defuse\Crypto\KeyProtectedByPassword::createRandomPasswordProtectedKey($psk);
246
    return $protected_key->saveToAsciiSafeString(); // save this in user table
247
}
248
249
/**
250
 * Validate persoanl key with defuse.
251
 *
252
 * @param string $psk                   the user's psk
253
 * @param string $protected_key_encoded special key
254
 *
255
 * @return string
256
 */
257
function defuse_validate_personal_key(string $psk, string $protected_key_encoded): string
258
{
259
    // load PhpEncryption library
260
    if (file_exists('../includes/config/tp.config.php') === true) {
261
        $path = '../includes/libraries/Encryption/Encryption/';
262
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
263
        $path = './includes/libraries/Encryption/Encryption/';
264
    } else {
265
        $path = '../includes/libraries/Encryption/Encryption/';
266
    }
267
268
    include_once $path . 'Exception/CryptoException.php';
269
    include_once $path . 'Exception/BadFormatException.php';
270
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
271
    include_once $path . 'Exception/IOException.php';
272
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
273
    include_once $path . 'Crypto.php';
274
    include_once $path . 'Encoding.php';
275
    include_once $path . 'DerivedKeys.php';
276
    include_once $path . 'Key.php';
277
    include_once $path . 'KeyOrPassword.php';
278
    include_once $path . 'File.php';
279
    include_once $path . 'RuntimeTests.php';
280
    include_once $path . 'KeyProtectedByPassword.php';
281
    include_once $path . 'Core.php';
282
283
    try {
284
        $protected_key = \Defuse\Crypto\KeyProtectedByPassword::loadFromAsciiSafeString($protected_key_encoded);
285
        $user_key = $protected_key->unlockKey($psk);
286
        $user_key_encoded = $user_key->saveToAsciiSafeString();
287
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
288
        return 'Error - Major issue as the encryption is broken.';
289
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
290
        return 'Error - The saltkey is not the correct one.';
291
    }
292
293
    return $user_key_encoded;
294
    // store it in session once user has entered his psk
295
}
296
297
/**
298
 * Decrypt a defuse string if encrypted.
299
 *
300
 * @param string $value Encrypted string
301
 *
302
 * @return string Decrypted string
303
 */
304
function defuseReturnDecrypted(string $value, $SETTINGS): string
305
{
306
    if (substr($value, 0, 3) === 'def') {
307
        $value = cryption($value, '', 'decrypt', $SETTINGS)['string'];
308
    }
309
310
    return $value;
311
}
312
313
/**
314
 * Trims a string depending on a specific string.
315
 *
316
 * @param string|array $chaine  what to trim
317
 * @param string       $element trim on what
318
 *
319
 * @return string
320
 */
321
function trimElement($chaine, string $element): string
322
{
323
    if (! empty($chaine)) {
324
        if (is_array($chaine) === true) {
325
            $chaine = implode(';', $chaine);
326
        }
327
        $chaine = trim($chaine);
328
        if (substr($chaine, 0, 1) === $element) {
329
            $chaine = substr($chaine, 1);
330
        }
331
        if (substr($chaine, strlen($chaine) - 1, 1) === $element) {
332
            $chaine = substr($chaine, 0, strlen($chaine) - 1);
333
        }
334
    }
335
336
    return $chaine;
337
}
338
339
/**
340
 * Permits to suppress all "special" characters from string.
341
 *
342
 * @param string $string  what to clean
343
 * @param bool   $special use of special chars?
344
 *
345
 * @return string
346
 */
347
function cleanString(string $string, bool $special = false): string
348
{
349
    // Create temporary table for special characters escape
350
    $tabSpecialChar = [];
351
    for ($i = 0; $i <= 31; ++$i) {
352
        $tabSpecialChar[] = chr($i);
353
    }
354
    array_push($tabSpecialChar, '<br />');
355
    if ((int) $special === 1) {
356
        $tabSpecialChar = array_merge($tabSpecialChar, ['</li>', '<ul>', '<ol>']);
357
    }
358
359
    return str_replace($tabSpecialChar, "\n", $string);
360
}
361
362
/**
363
 * Erro manager for DB.
364
 *
365
 * @param array $params output from query
366
 *
367
 * @return void
368
 */
369
function db_error_handler(array $params): void
370
{
371
    echo 'Error: ' . $params['error'] . "<br>\n";
372
    echo 'Query: ' . $params['query'] . "<br>\n";
373
    throw new Exception('Error - Query', 1);
374
}
375
376
/**
377
 * Identify user's rights
378
 *
379
 * @param string|array $groupesVisiblesUser  [description]
380
 * @param string|array $groupesInterditsUser [description]
381
 * @param string       $isAdmin              [description]
382
 * @param string       $idFonctions          [description]
383
 *
384
 * @return bool
385
 */
386
function identifyUserRights(
387
    $groupesVisiblesUser,
388
    $groupesInterditsUser,
389
    $isAdmin,
390
    $idFonctions,
391
    $SETTINGS
392
) {
393
    //load ClassLoader
394
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
395
    // Load superglobal
396
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
397
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
398
    //Connect to DB
399
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
400
    if (defined('DB_PASSWD_CLEAR') === false) {
401
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
402
    }
403
    DB::$host = DB_HOST;
404
    DB::$user = DB_USER;
405
    DB::$password = DB_PASSWD_CLEAR;
406
    DB::$dbName = DB_NAME;
407
    DB::$port = DB_PORT;
408
    DB::$encoding = DB_ENCODING;
409
    //Build tree
410
    $tree = new SplClassLoader('Tree\NestedTree', $SETTINGS['cpassman_dir'] . '/includes/libraries');
411
    $tree->register();
412
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
413
    // Check if user is ADMINISTRATOR
414
    if ((int) $isAdmin === 1) {
415
        identAdmin(
416
            $idFonctions,
417
            $SETTINGS, /** @scrutinizer ignore-type */
418
            $tree
419
        );
420
    } else {
421
        identUser(
422
            $groupesVisiblesUser,
423
            $groupesInterditsUser,
424
            $idFonctions,
425
            $SETTINGS, /** @scrutinizer ignore-type */
426
            $tree
427
        );
428
    }
429
430
    // update user's timestamp
431
    DB::update(
432
        prefixTable('users'),
433
        [
434
            'timestamp' => time(),
435
        ],
436
        'id=%i',
437
        $superGlobal->get('user_id', 'SESSION')
438
    );
439
440
    return true;
441
}
442
443
/**
444
 * Identify administrator.
445
 *
446
 * @param string $idFonctions Roles of user
447
 * @param array  $SETTINGS    Teampass settings
448
 * @param array  $tree        Tree of folders
449
 *
450
 * @return bool
451
 */
452
function identAdmin($idFonctions, $SETTINGS, $tree)
453
{
454
    // Load superglobal
455
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
456
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
457
    // Init
458
    $groupesVisibles = [];
459
    $superGlobal->put('personal_folders', [], 'SESSION');
460
    $superGlobal->put('groupes_visibles', [], 'SESSION');
461
    $superGlobal->put('no_access_folders', [], 'SESSION');
462
    $superGlobal->put('personal_visible_groups', [], 'SESSION');
463
    $superGlobal->put('read_only_folders', [], 'SESSION');
464
    $superGlobal->put('list_restricted_folders_for_items', [], 'SESSION');
465
    $superGlobal->put('list_folders_editable_by_role', [], 'SESSION');
466
    $superGlobal->put('list_folders_limited', [], 'SESSION');
467
    $superGlobal->put('no_access_folders', [], 'SESSION');
468
    $superGlobal->put('forbiden_pfs', [], 'SESSION');
469
    // Get superglobals
470
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
471
    $globalsVisibleFolders = $superGlobal->get('groupes_visibles', 'SESSION');
472
    $globalsPersonalVisibleFolders = $superGlobal->get('personal_visible_groups', 'SESSION');
473
    // Get list of Folders
474
    $rows = DB::query('SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i', 0);
475
    foreach ($rows as $record) {
476
        array_push($groupesVisibles, $record['id']);
477
    }
478
    $superGlobal->put('groupes_visibles', $groupesVisibles, 'SESSION');
479
    $superGlobal->put('all_non_personal_folders', $groupesVisibles, 'SESSION');
480
    // Exclude all PF
481
    $where = new WhereClause('and');
482
    // create a WHERE statement of pieces joined by ANDs
483
    $where->add('personal_folder=%i', 1);
484
    if (
485
        isset($SETTINGS['enable_pf_feature']) === true
486
        && (int) $SETTINGS['enable_pf_feature'] === 1
487
    ) {
488
        $where->add('title=%s', $globalsUserId);
489
        $where->negateLast();
490
    }
491
    // Get ID of personal folder
492
    $persfld = DB::queryfirstrow(
493
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE title = %s',
494
        $globalsUserId
495
    );
496
    if (empty($persfld['id']) === false) {
497
        if (in_array($persfld['id'], $globalsVisibleFolders) === false) {
498
            array_push($globalsVisibleFolders, $persfld['id']);
499
            array_push($globalsPersonalVisibleFolders, $persfld['id']);
500
            // get all descendants
501
            $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
502
            $tree->rebuild();
503
            $tst = $tree->getDescendants($persfld['id']);
504
            foreach ($tst as $t) {
505
                array_push($globalsVisibleFolders, $t->id);
506
                array_push($globalsPersonalVisibleFolders, $t->id);
507
            }
508
        }
509
    }
510
511
    // get complete list of ROLES
512
    $tmp = explode(';', $idFonctions);
513
    $rows = DB::query(
514
        'SELECT * FROM ' . prefixTable('roles_title') . '
515
        ORDER BY title ASC'
516
    );
517
    foreach ($rows as $record) {
518
        if (! empty($record['id']) && ! in_array($record['id'], $tmp)) {
519
            array_push($tmp, $record['id']);
520
        }
521
    }
522
    $superGlobal->put('fonction_id', implode(';', $tmp), 'SESSION');
523
    $superGlobal->put('is_admin', 1, 'SESSION');
524
    // Check if admin has created Folders and Roles
525
    DB::query('SELECT * FROM ' . prefixTable('nested_tree') . '');
526
    $superGlobal->put('nb_folders', DB::count(), 'SESSION');
527
    DB::query('SELECT * FROM ' . prefixTable('roles_title'));
528
    $superGlobal->put('nb_roles', DB::count(), 'SESSION');
529
530
    return true;
531
}
532
533
/**
534
 * Permits to convert an element to array.
535
 *
536
 * @param string|array $element Any value to be returned as array
537
 *
538
 * @return array
539
 */
540
function convertToArray($element): array
541
{
542
    if (is_string($element) === true) {
543
        if (empty($element) === true) {
544
            return [];
545
        }
546
        return explode(
547
            ';',
548
            trimElement($element, ';')
549
        );
550
    }
551
    return $element;
552
}
553
554
/**
555
 * Defines the rights the user has.
556
 *
557
 * @param string|array $allowedFolders  Allowed folders
558
 * @param string|array $noAccessFolders Not allowed folders
559
 * @param string|array $userRoles       Roles of user
560
 * @param array        $SETTINGS        Teampass settings
561
 * @param object       $tree            Tree of folders
562
 * 
563
 * @return bool
564
 */
565
function identUser(
566
    $allowedFolders,
567
    $noAccessFolders,
568
    $userRoles,
569
    array $SETTINGS,
570
    object $tree
571
) {
572
    // Load superglobal
573
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
574
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
575
    // Init
576
    $superGlobal->put('groupes_visibles', [], 'SESSION');
577
    $superGlobal->put('personal_folders', [], 'SESSION');
578
    $superGlobal->put('no_access_folders', [], 'SESSION');
579
    $superGlobal->put('personal_visible_groups', [], 'SESSION');
580
    $superGlobal->put('read_only_folders', [], 'SESSION');
581
    $superGlobal->put('fonction_id', $userRoles, 'SESSION');
582
    $superGlobal->put('is_admin', 0, 'SESSION');
583
    // init
584
    $personalFolders = [];
585
    $readOnlyFolders = [];
586
    $noAccessPersonalFolders = [];
587
    $restrictedFoldersForItems = [];
588
    $foldersLimited = [];
589
    $foldersLimitedFull = [];
590
    $allowedFoldersByRoles = [];
591
    // Get superglobals
592
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
593
    $globalsPersonalFolders = $superGlobal->get('personal_folder', 'SESSION');
594
    // Ensure consistency in array format
595
    $noAccessFolders = convertToArray($noAccessFolders);
596
    $userRoles = convertToArray($userRoles);
597
    $allowedFolders = convertToArray($allowedFolders);
598
    // Get list of folders depending on Roles
599
    $rows = DB::query(
600
        'SELECT *
601
        FROM ' . prefixTable('roles_values') . '
602
        WHERE role_id IN %li AND type IN %ls',
603
        $userRoles,
604
        ['W', 'ND', 'NE', 'NDNE', 'R']
605
    );
606
    foreach ($rows as $record) {
607
        if ($record['type'] === 'R') {
608
            array_push($readOnlyFolders, $record['folder_id']);
609
        } elseif (in_array($record['folder_id'], $allowedFolders) === false) {
610
            array_push($allowedFoldersByRoles, $record['folder_id']);
611
        }
612
    }
613
    $allowedFoldersByRoles = array_unique($allowedFoldersByRoles);
614
    $readOnlyFolders = array_unique($readOnlyFolders);
615
    // Clean arrays
616
    foreach ($allowedFoldersByRoles as $value) {
617
        $key = array_search($value, $readOnlyFolders);
618
        if ($key !== false) {
619
            unset($readOnlyFolders[$key]);
620
        }
621
    }
622
623
    // Does this user is allowed to see other items
624
    $inc = 0;
625
    $rows = DB::query(
626
        'SELECT id, id_tree FROM ' . prefixTable('items') . '
627
            WHERE restricted_to LIKE %ss AND inactif = %s'.
628
            (count($allowedFolders) > 0 ? ' AND id_tree NOT IN ('.implode(',', $allowedFolders).')' : ''),
629
        $globalsUserId . ';',
630
        '0'
631
    );
632
    foreach ($rows as $record) {
633
        // Exclude restriction on item if folder is fully accessible
634
        //if (in_array($record['id_tree'], $allowedFolders) === false) {
635
            $restrictedFoldersForItems[$record['id_tree']][$inc] = $record['id'];
636
            ++$inc;
637
        //}
638
    }
639
640
    // Check for the users roles if some specific rights exist on items
641
    $rows = DB::query(
642
        'SELECT i.id_tree, r.item_id
643
        FROM ' . prefixTable('items') . ' as i
644
        INNER JOIN ' . prefixTable('restriction_to_roles') . ' as r ON (r.item_id=i.id)
645
        WHERE r.role_id IN %li AND i.id_tree <> ""
646
        ORDER BY i.id_tree ASC',
647
        $userRoles
648
    );
649
    $inc = 0;
650
    foreach ($rows as $record) {
651
        //if (isset($record['id_tree'])) {
652
            $foldersLimited[$record['id_tree']][$inc] = $record['item_id'];
653
            array_push($foldersLimitedFull, $record['item_id']);
654
            ++$inc;
655
        //}
656
    }
657
658
    // Get list of Personal Folders
659
    if (
660
        isset($SETTINGS['enable_pf_feature']) === true && (int) $SETTINGS['enable_pf_feature'] === 1
661
        && isset($globalsPersonalFolders) === true && (int) $globalsPersonalFolders === 1
662
    ) {
663
        $persoFld = DB::queryfirstrow(
664
            'SELECT id
665
            FROM ' . prefixTable('nested_tree') . '
666
            WHERE title = %s AND personal_folder = %i'.
667
            (count($allowedFolders) > 0 ? ' AND id NOT IN ('.implode(',', $allowedFolders).')' : ''),
668
            $globalsUserId,
669
            1
670
        );
671
        if (empty($persoFld['id']) === false) {
672
            //if (in_array($persoFld['id'], $allowedFolders) === false) {
673
                array_push($personalFolders, $persoFld['id']);
674
                array_push($allowedFolders, $persoFld['id']);
675
                // get all descendants
676
                $ids = $tree->getChildren($persoFld['id'], false);
677
                foreach ($ids as $ident) {
678
                    if ((int) $ident->personal_folder === 1) {
679
                        array_push($allowedFolders, $ident->id);
680
                        array_push($personalFolders, $ident->id);
681
                    }
682
                }
683
            //}
684
        }
685
    }
686
    
687
    // Exclude all other PF
688
    $where = new WhereClause('and');
689
    $where->add('personal_folder=%i', 1);
690
    if (
691
        isset($SETTINGS['enable_pf_feature']) === true && (int) $SETTINGS['enable_pf_feature'] === 1
692
        && isset($globalsPersonalFolders) === true && (int) $globalsPersonalFolders === 1
693
    ) {
694
        $where->add('title=%s', $globalsUserId);
695
        $where->negateLast();
696
    }
697
    $persoFlds = DB::query(
698
        'SELECT id
699
        FROM ' . prefixTable('nested_tree') . '
700
        WHERE %l',
701
        $where
702
    );
703
    foreach ($persoFlds as $persoFldId) {
704
        array_push($noAccessPersonalFolders, $persoFldId['id']);
705
    }
706
707
    // All folders visibles
708
    $allowedFolders = array_merge(
709
        $allowedFolders,
710
        $foldersLimitedFull,
711
        $allowedFoldersByRoles,
712
        $restrictedFoldersForItems,
713
        $readOnlyFolders
714
    );
715
    // Exclude from allowed folders all the specific user forbidden folders
716
    if (count($noAccessFolders) > 0) {
717
        $allowedFolders = array_diff($allowedFolders, $noAccessFolders);
718
    }
719
    // Return data
720
    $superGlobal->put('all_non_personal_folders', $allowedFolders, 'SESSION');
721
    $superGlobal->put('groupes_visibles', array_merge($allowedFolders, $personalFolders), 'SESSION');
722
    $superGlobal->put('read_only_folders', $readOnlyFolders, 'SESSION');
723
    $superGlobal->put('no_access_folders', $noAccessFolders, 'SESSION');
724
    $superGlobal->put('personal_folders', $personalFolders, 'SESSION');
725
    $superGlobal->put('list_folders_limited', $foldersLimited, 'SESSION');
726
    $superGlobal->put('list_folders_editable_by_role', $allowedFoldersByRoles, 'SESSION');
727
    $superGlobal->put('list_restricted_folders_for_items', $restrictedFoldersForItems, 'SESSION');
728
    $superGlobal->put('forbiden_pfs', $noAccessPersonalFolders, 'SESSION');
729
    $superGlobal->put(
730
        'all_folders_including_no_access',
731
        array_merge(
732
            $allowedFolders,
733
            $personalFolders,
734
            $noAccessFolders,
735
            $readOnlyFolders
736
        ),
737
        'SESSION'
738
    );
739
    // Folders and Roles numbers
740
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('nested_tree') . '');
741
    $superGlobal->put('nb_folders', DB::count(), 'SESSION');
742
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('roles_title'));
743
    $superGlobal->put('nb_roles', DB::count(), 'SESSION');
744
    // check if change proposals on User's items
745
    if (isset($SETTINGS['enable_suggestion']) === true && (int) $SETTINGS['enable_suggestion'] === 1) {
746
        DB::query(
747
            'SELECT *
748
            FROM ' . prefixTable('items_change') . ' AS c
749
            LEFT JOIN ' . prefixTable('log_items') . ' AS i ON (c.item_id = i.id_item)
750
            WHERE i.action = %s AND i.id_user = %i',
751
            'at_creation',
752
            $globalsUserId
753
        );
754
        $superGlobal->put('nb_item_change_proposals', DB::count(), 'SESSION');
755
    } else {
756
        $superGlobal->put('nb_item_change_proposals', 0, 'SESSION');
757
    }
758
759
    return true;
760
}
761
762
/**
763
 * Update the CACHE table.
764
 *
765
 * @param string $action   What to do
766
 * @param array  $SETTINGS Teampass settings
767
 * @param int    $ident    Ident format
768
 * 
769
 * @return void
770
 */
771
function updateCacheTable(string $action, array $SETTINGS, ?int $ident = null): void
772
{
773
    if ($action === 'reload') {
774
        // Rebuild full cache table
775
        cacheTableRefresh($SETTINGS);
776
    } elseif ($action === 'update_value' && is_null($ident) === false) {
777
        // UPDATE an item
778
        cacheTableUpdate($SETTINGS, $ident);
779
    } elseif ($action === 'add_value' && is_null($ident) === false) {
780
        // ADD an item
781
        cacheTableAdd($SETTINGS, $ident);
782
    } elseif ($action === 'delete_value' && is_null($ident) === false) {
783
        // DELETE an item
784
        DB::delete(prefixTable('cache'), 'id = %i', $ident);
785
    }
786
}
787
788
/**
789
 * Cache table - refresh.
790
 *
791
 * @param array $SETTINGS Teampass settings
792
 * 
793
 * @return void
794
 */
795
function cacheTableRefresh(array $SETTINGS): void
796
{
797
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
798
    //Connect to DB
799
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
800
    if (defined('DB_PASSWD_CLEAR') === false) {
801
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
802
    }
803
    DB::$host = DB_HOST;
804
    DB::$user = DB_USER;
805
    DB::$password = DB_PASSWD_CLEAR;
806
    DB::$dbName = DB_NAME;
807
    DB::$port = DB_PORT;
808
    DB::$encoding = DB_ENCODING;
809
    //Load Tree
810
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
811
    $tree->register();
812
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
813
    // truncate table
814
    DB::query('TRUNCATE TABLE ' . prefixTable('cache'));
815
    // reload date
816
    $rows = DB::query(
817
        'SELECT *
818
        FROM ' . prefixTable('items') . ' as i
819
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
820
        AND l.action = %s
821
        AND i.inactif = %i',
822
        'at_creation',
823
        0
824
    );
825
    foreach ($rows as $record) {
826
        if (empty($record['id_tree']) === false) {
827
            // Get all TAGS
828
            $tags = '';
829
            $itemTags = DB::query(
830
                'SELECT tag
831
                            FROM ' . prefixTable('tags') . '
832
                            WHERE item_id = %i AND tag != ""',
833
                $record['id']
834
            );
835
            foreach ($itemTags as $itemTag) {
836
                $tags .= $itemTag['tag'] . ' ';
837
            }
838
839
            // Get renewal period
840
            $resNT = DB::queryfirstrow(
841
                'SELECT renewal_period
842
                FROM ' . prefixTable('nested_tree') . '
843
                WHERE id = %i',
844
                $record['id_tree']
845
            );
846
            // form id_tree to full foldername
847
            $folder = [];
848
            $arbo = $tree->getPath($record['id_tree'], true);
849
            foreach ($arbo as $elem) {
850
                // Check if title is the ID of a user
851
                if (is_numeric($elem->title) === true) {
852
                    // Is this a User id?
853
                    $user = DB::queryfirstrow(
854
                        'SELECT id, login
855
                        FROM ' . prefixTable('users') . '
856
                        WHERE id = %i',
857
                        $elem->title
858
                    );
859
                    if (count($user) > 0) {
860
                        $elem->title = $user['login'];
861
                    }
862
                }
863
                // Build path
864
                array_push($folder, stripslashes($elem->title));
865
            }
866
            // store data
867
            DB::insert(
868
                prefixTable('cache'),
869
                [
870
                    'id' => $record['id'],
871
                    'label' => $record['label'],
872
                    'description' => $record['description'] ?? '',
873
                    'url' => isset($record['url']) && ! empty($record['url']) ? $record['url'] : '0',
874
                    'tags' => $tags,
875
                    'id_tree' => $record['id_tree'],
876
                    'perso' => $record['perso'],
877
                    'restricted_to' => isset($record['restricted_to']) && ! empty($record['restricted_to']) ? $record['restricted_to'] : '0',
878
                    'login' => $record['login'] ?? '',
879
                    'folder' => implode(' > ', $folder),
880
                    'author' => $record['id_user'],
881
                    'renewal_period' => $resNT['renewal_period'] ?? '0',
882
                    'timestamp' => $record['date'],
883
                ]
884
            );
885
        }
886
    }
887
}
888
889
/**
890
 * Cache table - update existing value.
891
 *
892
 * @param array  $SETTINGS Teampass settings
893
 * @param int    $ident    Ident format
894
 * 
895
 * @return void
896
 */
897
function cacheTableUpdate(array $SETTINGS, ?int $ident = null): void
898
{
899
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
900
    // Load superglobal
901
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
902
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
903
    //Connect to DB
904
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
905
    if (defined('DB_PASSWD_CLEAR') === false) {
906
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
907
    }
908
    DB::$host = DB_HOST;
909
    DB::$user = DB_USER;
910
    DB::$password = DB_PASSWD_CLEAR;
911
    DB::$dbName = DB_NAME;
912
    DB::$port = DB_PORT;
913
    DB::$encoding = DB_ENCODING;
914
    //Load Tree
915
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
916
    $tree->register();
917
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
918
    // get new value from db
919
    $data = DB::queryfirstrow(
920
        'SELECT label, description, id_tree, perso, restricted_to, login, url
921
        FROM ' . prefixTable('items') . '
922
        WHERE id=%i',
923
        $ident
924
    );
925
    // Get all TAGS
926
    $tags = '';
927
    $itemTags = DB::query(
928
        'SELECT tag
929
            FROM ' . prefixTable('tags') . '
930
            WHERE item_id = %i AND tag != ""',
931
        $ident
932
    );
933
    foreach ($itemTags as $itemTag) {
934
        $tags .= $itemTag['tag'] . ' ';
935
    }
936
    // form id_tree to full foldername
937
    $folder = [];
938
    $arbo = $tree->getPath($data['id_tree'], true);
939
    foreach ($arbo as $elem) {
940
        // Check if title is the ID of a user
941
        if (is_numeric($elem->title) === true) {
942
            // Is this a User id?
943
            $user = DB::queryfirstrow(
944
                'SELECT id, login
945
                FROM ' . prefixTable('users') . '
946
                WHERE id = %i',
947
                $elem->title
948
            );
949
            if (count($user) > 0) {
950
                $elem->title = $user['login'];
951
            }
952
        }
953
        // Build path
954
        array_push($folder, stripslashes($elem->title));
955
    }
956
    // finaly update
957
    DB::update(
958
        prefixTable('cache'),
959
        [
960
            'label' => $data['label'],
961
            'description' => $data['description'],
962
            'tags' => $tags,
963
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
964
            'id_tree' => $data['id_tree'],
965
            'perso' => $data['perso'],
966
            'restricted_to' => isset($data['restricted_to']) && ! empty($data['restricted_to']) ? $data['restricted_to'] : '0',
967
            'login' => $data['login'] ?? '',
968
            'folder' => implode(' » ', $folder),
969
            'author' => $superGlobal->get('user_id', 'SESSION'),
970
        ],
971
        'id = %i',
972
        $ident
973
    );
974
}
975
976
/**
977
 * Cache table - add new value.
978
 *
979
 * @param array  $SETTINGS Teampass settings
980
 * @param int    $ident    Ident format
981
 * 
982
 * @return void
983
 */
984
function cacheTableAdd(array $SETTINGS, ?int $ident = null): void
985
{
986
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
987
    // Load superglobal
988
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
989
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
990
    // Get superglobals
991
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
992
    //Connect to DB
993
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
994
    if (defined('DB_PASSWD_CLEAR') === false) {
995
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
996
    }
997
    DB::$host = DB_HOST;
998
    DB::$user = DB_USER;
999
    DB::$password = DB_PASSWD_CLEAR;
1000
    DB::$dbName = DB_NAME;
1001
    DB::$port = DB_PORT;
1002
    DB::$encoding = DB_ENCODING;
1003
    //Load Tree
1004
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1005
    $tree->register();
1006
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1007
    // get new value from db
1008
    $data = DB::queryFirstRow(
1009
        'SELECT i.label, i.description, i.id_tree as id_tree, i.perso, i.restricted_to, i.id, i.login, i.url, l.date
1010
        FROM ' . prefixTable('items') . ' as i
1011
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
1012
        WHERE i.id = %i
1013
        AND l.action = %s',
1014
        $ident,
1015
        'at_creation'
1016
    );
1017
    // Get all TAGS
1018
    $tags = '';
1019
    $itemTags = DB::query(
1020
        'SELECT tag
1021
            FROM ' . prefixTable('tags') . '
1022
            WHERE item_id = %i AND tag != ""',
1023
        $ident
1024
    );
1025
    foreach ($itemTags as $itemTag) {
1026
        $tags .= $itemTag['tag'] . ' ';
1027
    }
1028
    // form id_tree to full foldername
1029
    $folder = [];
1030
    $arbo = $tree->getPath($data['id_tree'], true);
1031
    foreach ($arbo as $elem) {
1032
        // Check if title is the ID of a user
1033
        if (is_numeric($elem->title) === true) {
1034
            // Is this a User id?
1035
            $user = DB::queryfirstrow(
1036
                'SELECT id, login
1037
                FROM ' . prefixTable('users') . '
1038
                WHERE id = %i',
1039
                $elem->title
1040
            );
1041
            if (count($user) > 0) {
1042
                $elem->title = $user['login'];
1043
            }
1044
        }
1045
        // Build path
1046
        array_push($folder, stripslashes($elem->title));
1047
    }
1048
    // finaly update
1049
    DB::insert(
1050
        prefixTable('cache'),
1051
        [
1052
            'id' => $data['id'],
1053
            'label' => $data['label'],
1054
            'description' => $data['description'],
1055
            'tags' => isset($tags) && empty($tags) === false ? $tags : 'None',
1056
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
1057
            'id_tree' => $data['id_tree'],
1058
            'perso' => isset($data['perso']) && empty($data['perso']) === false && $data['perso'] !== 'None' ? $data['perso'] : '0',
1059
            'restricted_to' => isset($data['restricted_to']) && empty($data['restricted_to']) === false ? $data['restricted_to'] : '0',
1060
            'login' => $data['login'] ?? '',
1061
            'folder' => implode(' » ', $folder),
1062
            'author' => $globalsUserId,
1063
            'timestamp' => $data['date'],
1064
        ]
1065
    );
1066
}
1067
1068
/**
1069
 * Do statistics.
1070
 *
1071
 * @param array $SETTINGS Teampass settings
1072
 *
1073
 * @return array
1074
 */
1075
function getStatisticsData(array $SETTINGS): array
1076
{
1077
    DB::query(
1078
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1079
        0
1080
    );
1081
    $counter_folders = DB::count();
1082
    DB::query(
1083
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1084
        1
1085
    );
1086
    $counter_folders_perso = DB::count();
1087
    DB::query(
1088
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1089
        0
1090
    );
1091
    $counter_items = DB::count();
1092
        DB::query(
1093
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1094
        1
1095
    );
1096
    $counter_items_perso = DB::count();
1097
        DB::query(
1098
        'SELECT id FROM ' . prefixTable('users') . ''
1099
    );
1100
    $counter_users = DB::count();
1101
        DB::query(
1102
        'SELECT id FROM ' . prefixTable('users') . ' WHERE admin = %i',
1103
        1
1104
    );
1105
    $admins = DB::count();
1106
    DB::query(
1107
        'SELECT id FROM ' . prefixTable('users') . ' WHERE gestionnaire = %i',
1108
        1
1109
    );
1110
    $managers = DB::count();
1111
    DB::query(
1112
        'SELECT id FROM ' . prefixTable('users') . ' WHERE read_only = %i',
1113
        1
1114
    );
1115
    $readOnly = DB::count();
1116
    // list the languages
1117
    $usedLang = [];
1118
    $tp_languages = DB::query(
1119
        'SELECT name FROM ' . prefixTable('languages')
1120
    );
1121
    foreach ($tp_languages as $tp_language) {
1122
        DB::query(
1123
            'SELECT * FROM ' . prefixTable('users') . ' WHERE user_language = %s',
1124
            $tp_language['name']
1125
        );
1126
        $usedLang[$tp_language['name']] = round((DB::count() * 100 / $counter_users), 0);
1127
    }
1128
1129
    // get list of ips
1130
    $usedIp = [];
1131
    $tp_ips = DB::query(
1132
        'SELECT user_ip FROM ' . prefixTable('users')
1133
    );
1134
    foreach ($tp_ips as $ip) {
1135
        if (array_key_exists($ip['user_ip'], $usedIp)) {
1136
            $usedIp[$ip['user_ip']] += $usedIp[$ip['user_ip']];
1137
        } elseif (! empty($ip['user_ip']) && $ip['user_ip'] !== 'none') {
1138
            $usedIp[$ip['user_ip']] = 1;
1139
        }
1140
    }
1141
1142
    return [
1143
        'error' => '',
1144
        'stat_phpversion' => phpversion(),
1145
        'stat_folders' => $counter_folders,
1146
        'stat_folders_shared' => intval($counter_folders) - intval($counter_folders_perso),
1147
        'stat_items' => $counter_items,
1148
        'stat_items_shared' => intval($counter_items) - intval($counter_items_perso),
1149
        'stat_users' => $counter_users,
1150
        'stat_admins' => $admins,
1151
        'stat_managers' => $managers,
1152
        'stat_ro' => $readOnly,
1153
        'stat_kb' => $SETTINGS['enable_kb'],
1154
        'stat_pf' => $SETTINGS['enable_pf_feature'],
1155
        'stat_fav' => $SETTINGS['enable_favourites'],
1156
        'stat_teampassversion' => TP_VERSION_FULL,
1157
        'stat_ldap' => $SETTINGS['ldap_mode'],
1158
        'stat_agses' => $SETTINGS['agses_authentication_enabled'],
1159
        'stat_duo' => $SETTINGS['duo'],
1160
        'stat_suggestion' => $SETTINGS['enable_suggestion'],
1161
        'stat_api' => $SETTINGS['api'],
1162
        'stat_customfields' => $SETTINGS['item_extra_fields'],
1163
        'stat_syslog' => $SETTINGS['syslog_enable'],
1164
        'stat_2fa' => $SETTINGS['google_authentication'],
1165
        'stat_stricthttps' => $SETTINGS['enable_sts'],
1166
        'stat_mysqlversion' => DB::serverVersion(),
1167
        'stat_languages' => $usedLang,
1168
        'stat_country' => $usedIp,
1169
    ];
1170
}
1171
1172
/**
1173
 * Permits to send an email.
1174
 *
1175
 * @param string $subject     email subject
1176
 * @param string $textMail    email message
1177
 * @param string $email       email
1178
 * @param array  $SETTINGS    settings
1179
 * @param string $textMailAlt email message alt
1180
 * @param bool   $silent      no errors
1181
 *
1182
 * @return string some json info
1183
 */
1184
function sendEmail(
1185
    $subject,
1186
    $textMail,
1187
    $email,
1188
    $SETTINGS,
1189
    $textMailAlt = null,
1190
    $silent = true
1191
) {
1192
    // CAse where email not defined
1193
    if ($email === 'none' || empty($email) === true) {
1194
        return json_encode(
1195
            [
1196
                'error' => true,
1197
                'message' => langHdl('forgot_my_pw_email_sent'),
1198
            ]
1199
        );
1200
    }
1201
1202
    // Load settings
1203
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
1204
    // Load superglobal
1205
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1206
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1207
    // Get user language
1208
    include_once $SETTINGS['cpassman_dir'] . '/includes/language/' . $superGlobal->get('user_language', 'SESSION') . '.php';
1209
    // Load library
1210
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1211
    // load PHPMailer
1212
    $mail = new SplClassLoader('PHPMailer\PHPMailer', '../includes/libraries');
1213
    $mail->register();
1214
    $mail = new PHPMailer\PHPMailer\PHPMailer(true);
1215
    
1216
    try {
1217
        // send to user
1218
        $mail->setLanguage('en', $SETTINGS['cpassman_dir'] . '/includes/libraries/PHPMailer/PHPMailer/language/');
1219
        $mail->SMTPDebug = isset($SETTINGS['email_debug_level']) === true ? $SETTINGS['email_debug_level'] : 0;
1220
        $mail->Port = $SETTINGS['email_port'];
1221
        //COULD BE USED
1222
        $mail->CharSet = 'utf-8';
1223
        $mail->SMTPSecure = $SETTINGS['email_security'] === 'tls'
1224
            || $SETTINGS['email_security'] === 'ssl' ? $SETTINGS['email_security'] : '';
1225
        $mail->SMTPAutoTLS = $SETTINGS['email_security'] === 'tls'
1226
            || $SETTINGS['email_security'] === 'ssl' ? true : false;
1227
        $mail->SMTPOptions = [
1228
            'ssl' => [
1229
                'verify_peer' => false,
1230
                'verify_peer_name' => false,
1231
                'allow_self_signed' => true,
1232
            ],
1233
        ];
1234
        $mail->isSmtp();
1235
        // send via SMTP
1236
        $mail->Host = $SETTINGS['email_smtp_server'];
1237
        // SMTP servers
1238
        $mail->SMTPAuth = (int) $SETTINGS['email_smtp_auth'] === 1 ? true : false;
1239
        // turn on SMTP authentication
1240
        $mail->Username = $SETTINGS['email_auth_username'];
1241
        // SMTP username
1242
        $mail->Password = $SETTINGS['email_auth_pwd'];
1243
        // SMTP password
1244
        $mail->From = $SETTINGS['email_from'];
1245
        $mail->FromName = $SETTINGS['email_from_name'];
1246
        // Prepare for each person
1247
        foreach (array_filter(explode(',', $email)) as $dest) {
1248
            $mail->addAddress($dest);
1249
        }
1250
1251
        // Prepare HTML
1252
        $text_html = emailBody($textMail);
1253
        $mail->WordWrap = 80;
1254
        // set word wrap
1255
        $mail->isHtml(true);
1256
        // send as HTML
1257
        $mail->Subject = $subject;
1258
        $mail->Body = $text_html;
1259
        $mail->AltBody = is_null($textMailAlt) === false ? $textMailAlt : '';
1260
        // send email
1261
        $mail->send();
1262
        $mail->smtpClose();
1263
        if ($silent === false) {
1264
            return json_encode(
1265
                [
1266
                    'error' => false,
1267
                    'message' => langHdl('forgot_my_pw_email_sent'),
1268
                ]
1269
            );
1270
        }
1271
        // Debug purpose
1272
        if ((int) $SETTINGS['email_debug_level'] !== 0) {
1273
            return json_encode(
1274
                [
1275
                    'error' => true,
1276
                    'message' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1277
                ]
1278
            );
1279
        }
1280
    } catch (Exception $e) {
1281
        if ($silent === false || (int) $SETTINGS['email_debug_level'] !== 0) {
1282
            return json_encode(
1283
                [
1284
                    'error' => true,
1285
                    'message' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1286
                ]
1287
            );
1288
        }
1289
        return '';
1290
    }
1291
}
1292
1293
/**
1294
 * Returns the email body.
1295
 *
1296
 * @param string $textMail Text for the email
1297
 */
1298
function emailBody(string $textMail): string
1299
{
1300
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1301
    w3.org/TR/html4/loose.dtd"><html>
1302
    <head><title>Email Template</title>
1303
    <style type="text/css">
1304
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1305
    </style></head>
1306
    <body style="-ms-text-size-adjust: none; size-adjust: none; margin: 0; padding: 10px 0; background-color: #f0f0f0;" bgcolor="#f0f0f0" leftmargin="0" topmargin="0" marginwidth="0" marginheight="0">
1307
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1308
    <tr><td style="border-collapse: collapse;"><br>
1309
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1310
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1311
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1312
        </td></tr></table></td>
1313
    </tr>
1314
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1315
        <table width="600" cellpadding="0" cellspacing="0" border="0" class="container" bgcolor="#ffffff" style="border-spacing: 0; border-bottom: 1px solid #e0e0e0; box-shadow: 0 0 3px #ddd; color: #434343; font-family: Helvetica, Verdana, sans-serif;">
1316
        <tr><td class="container-padding" bgcolor="#ffffff" style="border-collapse: collapse; border-left: 1px solid #e0e0e0; background-color: #ffffff; padding-left: 30px; padding-right: 30px;">
1317
        <br><div style="float:right;">' .
1318
        $textMail .
1319
        '<br><br></td></tr></table>
1320
    </td></tr></table>
1321
    <br></body></html>';
1322
}
1323
1324
/**
1325
 * Generate a Key.
1326
 * 
1327
 * @return string
1328
 */
1329
function generateKey(): string
1330
{
1331
    return substr(md5(rand() . rand()), 0, 15);
1332
}
1333
1334
/**
1335
 * Convert date to timestamp.
1336
 *
1337
 * @param string $date     The date
1338
 * @param array  $SETTINGS Teampass settings
1339
 *
1340
 * @return string
1341
 */
1342
function dateToStamp(string $date, array $SETTINGS): string
1343
{
1344
    $date = date_parse_from_format($SETTINGS['date_format'], $date);
1345
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1346
        return mktime(23, 59, 59, $date['month'], $date['day'], $date['year']);
1347
    }
1348
    return '';
1349
}
1350
1351
/**
1352
 * Is this a date.
1353
 *
1354
 * @param string $date Date
1355
 *
1356
 * @return bool
1357
 */
1358
function isDate(string $date): bool
1359
{
1360
    return strtotime($date) !== false;
1361
}
1362
1363
/**
1364
 * Check if isUTF8().
1365
 *
1366
 * @param string|array $string Is the string
1367
 *
1368
 * @return int is the string in UTF8 format
1369
 */
1370
function isUTF8($string): int
1371
{
1372
    if (is_array($string) === true) {
1373
        $string = $string['string'];
1374
    }
1375
1376
    return preg_match(
1377
        '%^(?:
1378
        [\x09\x0A\x0D\x20-\x7E] # ASCII
1379
        | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
1380
        | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
1381
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
1382
        | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
1383
        | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
1384
        | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
1385
        | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
1386
        )*$%xs',
1387
        $string
1388
    );
1389
}
1390
1391
/**
1392
 * Prepare an array to UTF8 format before JSON_encode.
1393
 *
1394
 * @param array $array Array of values
1395
 *
1396
 * @return array
1397
 */
1398
function utf8Converter(array $array): array
1399
{
1400
    array_walk_recursive(
1401
        $array,
1402
        static function (&$item): void {
1403
            if (mb_detect_encoding((string) $item, 'utf-8', true) === false) {
1404
                $item = utf8_encode($item);
1405
            }
1406
        }
1407
    );
1408
    return $array;
1409
}
1410
1411
/**
1412
 * Permits to prepare data to be exchanged.
1413
 *
1414
 * @param array|string $data Text
1415
 * @param string       $type Parameter
1416
 * @param string       $key  Optional key
1417
 *
1418
 * @return resource|string|array
1419
 */
1420
function prepareExchangedData($data, string $type, ?string $key = null)
1421
{
1422
    if (file_exists('../includes/config/tp.config.php')) {
1423
        include '../includes/config/tp.config.php';
1424
    } elseif (file_exists('./includes/config/tp.config.php')) {
1425
        include './includes/config/tp.config.php';
1426
    } elseif (file_exists('../../includes/config/tp.config.php')) {
1427
        include '../../includes/config/tp.config.php';
1428
    } else {
1429
        throw new Exception("Error file '/includes/config/tp.config.php' not exists", 1);
1430
    }
1431
1432
    if (isset($SETTINGS) === false) {
1433
        return 'ERROR';
1434
    }
1435
1436
    // Load superglobal
1437
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1438
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1439
    // Get superglobals
1440
    if ($key !== null) {
1441
        $superGlobal->put('key', $key, 'SESSION');
1442
        $globalsKey = $key;
1443
    } else {
1444
        $globalsKey = $superGlobal->get('key', 'SESSION');
1445
    }
1446
1447
    //load ClassLoader
1448
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1449
    //Load AES
1450
    $aes = new SplClassLoader('Encryption\Crypt', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1451
    $aes->register();
1452
    if ($type === 'encode' && is_array($data) === true) {
1453
        // Ensure UTF8 format
1454
        $data = utf8Converter($data);
1455
        // Now encode
1456
        return Encryption\Crypt\aesctr::encrypt(
1457
            json_encode(
1458
                $data,
1459
                JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1460
            ),
1461
            $globalsKey,
1462
            256
1463
        );
1464
    }
1465
    if ($type === 'decode' && is_array($data) === false) {
1466
        return json_decode(
1467
            Encryption\Crypt\aesctr::decrypt(
1468
                /** @scrutinizer ignore-type */
1469
                (string) $data,
1470
                $globalsKey,
1471
                256
1472
            ),
1473
            true
1474
        );
1475
    }
1476
}
1477
1478
/**
1479
 * Create a thumbnail.
1480
 *
1481
 * @param string  $src           Source
1482
 * @param string  $dest          Destination
1483
 * @param int $desired_width Size of width
1484
 * 
1485
 * @return void|string
1486
 */
1487
function makeThumbnail(string $src, string $dest, int $desired_width)
1488
{
1489
    /* read the source image */
1490
    if (is_file($src) === true && mime_content_type($src) === 'image/png') {
1491
        $source_image = imagecreatefrompng($src);
1492
        if ($source_image === false) {
1493
            return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1494
        }
1495
    } else {
1496
        return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1497
    }
1498
1499
    // Get height and width
1500
    $width = imagesx($source_image);
1501
    $height = imagesy($source_image);
1502
    /* find the "desired height" of this thumbnail, relative to the desired width  */
1503
    $desired_height = (int) floor($height * $desired_width / $width);
1504
    /* create a new, "virtual" image */
1505
    $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
1506
    if ($virtual_image === false) {
1507
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
1508
    }
1509
    /* copy source image at a resized size */
1510
    imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
1511
    /* create the physical thumbnail image to its destination */
1512
    imagejpeg($virtual_image, $dest);
1513
}
1514
1515
/**
1516
 * Check table prefix in SQL query.
1517
 *
1518
 * @param string $table Table name
1519
 * 
1520
 * @return string
1521
 */
1522
function prefixTable(string $table): string
1523
{
1524
    $safeTable = htmlspecialchars(DB_PREFIX . $table);
1525
    if (! empty($safeTable)) {
1526
        // sanitize string
1527
        return $safeTable;
1528
    }
1529
    // stop error no table
1530
    return 'table_not_exists';
1531
}
1532
1533
/**
1534
 * GenerateCryptKey
1535
 *
1536
 * @param int     $size      Length
1537
 * @param bool $secure Secure
1538
 * @param bool $numerals Numerics
1539
 * @param bool $uppercase Uppercase letters
1540
 * @param bool $symbols Symbols
1541
 * @param bool $lowercase Lowercase
1542
 * @param array   $SETTINGS  SETTINGS
1543
 * 
1544
 * @return string
1545
 */
1546
function GenerateCryptKey(
1547
    int $size = 10,
1548
    bool $secure = false,
1549
    bool $numerals = false,
1550
    bool $uppercase = false,
1551
    bool $symbols = false,
1552
    bool $lowercase = false,
1553
    array $SETTINGS = []
1554
): string {
1555
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1556
    $generator = new SplClassLoader('PasswordGenerator\Generator', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1557
    $generator->register();
1558
    $generator = new PasswordGenerator\Generator\ComputerPasswordGenerator();
1559
    // Is PHP7 being used?
1560
    if (version_compare(PHP_VERSION, '7.0.0', '>=')) {
1561
        $php7generator = new SplClassLoader('PasswordGenerator\RandomGenerator', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1562
        $php7generator->register();
1563
        $generator->setRandomGenerator(new PasswordGenerator\RandomGenerator\Php7RandomGenerator());
1564
    }
1565
1566
    // Manage size
1567
    $generator->setLength((int) $size);
1568
    if ($secure === true) {
1569
        $generator->setSymbols(true);
1570
        $generator->setLowercase(true);
1571
        $generator->setUppercase(true);
1572
        $generator->setNumbers(true);
1573
    } else {
1574
        $generator->setLowercase($lowercase);
1575
        $generator->setUppercase($uppercase);
1576
        $generator->setNumbers($numerals);
1577
        $generator->setSymbols($symbols);
1578
    }
1579
1580
    return $generator->generatePasswords()[0];
1581
}
1582
1583
/**
1584
 * Send sysLOG message
1585
 *
1586
 * @param string    $message
1587
 * @param string    $host
1588
 * @param int       $port
1589
 * @param string    $component
1590
 * 
1591
 * @return void
1592
*/
1593
function send_syslog($message, $host, $port, $component = 'teampass'): void
1594
{
1595
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1596
    $syslog_message = '<123>' . date('M d H:i:s ') . $component . ': ' . $message;
1597
    socket_sendto($sock, $syslog_message, strlen($syslog_message), 0, $host, $port);
1598
    socket_close($sock);
1599
}
1600
1601
/**
1602
 * Permits to log events into DB
1603
 *
1604
 * @param array  $SETTINGS Teampass settings
1605
 * @param string $type     Type
1606
 * @param string $label    Label
1607
 * @param string $who      Who
1608
 * @param string $login    Login
1609
 * @param string $field_1  Field
1610
 * 
1611
 * @return void
1612
 */
1613
function logEvents(array $SETTINGS, string $type, string $label, string $who, ?string $login = null, ?string $field_1 = null): void
1614
{
1615
    if (empty($who)) {
1616
        $who = getClientIpServer();
1617
    }
1618
1619
    // include librairies & connect to DB
1620
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1621
    if (defined('DB_PASSWD_CLEAR') === false) {
1622
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1623
    }
1624
    DB::$host = DB_HOST;
1625
    DB::$user = DB_USER;
1626
    DB::$password = DB_PASSWD_CLEAR;
1627
    DB::$dbName = DB_NAME;
1628
    DB::$port = DB_PORT;
1629
    DB::$encoding = DB_ENCODING;
1630
    DB::insert(
1631
        prefixTable('log_system'),
1632
        [
1633
            'type' => $type,
1634
            'date' => time(),
1635
            'label' => $label,
1636
            'qui' => $who,
1637
            'field_1' => $field_1 === null ? '' : $field_1,
1638
        ]
1639
    );
1640
    // If SYSLOG
1641
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1642
        if ($type === 'user_mngt') {
1643
            send_syslog(
1644
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1645
                $SETTINGS['syslog_host'],
1646
                $SETTINGS['syslog_port'],
1647
                'teampass'
1648
            );
1649
        } else {
1650
            send_syslog(
1651
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1652
                $SETTINGS['syslog_host'],
1653
                $SETTINGS['syslog_port'],
1654
                'teampass'
1655
            );
1656
        }
1657
    }
1658
}
1659
1660
/**
1661
 * Log events.
1662
 *
1663
 * @param array  $SETTINGS        Teampass settings
1664
 * @param int    $item_id         Item id
1665
 * @param string $item_label      Item label
1666
 * @param int    $id_user         User id
1667
 * @param string $action          Code for reason
1668
 * @param string $login           User login
1669
 * @param string $raison          Code for reason
1670
 * @param string $encryption_type Encryption on
1671
 * 
1672
 * @return void
1673
 */
1674
function logItems(
1675
    array $SETTINGS,
1676
    int $item_id,
1677
    string $item_label,
1678
    int $id_user,
1679
    string $action,
1680
    ?string $login = null,
1681
    ?string $raison = null,
1682
    ?string $encryption_type = null
1683
): void {
1684
    // include librairies & connect to DB
1685
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1686
    if (defined('DB_PASSWD_CLEAR') === false) {
1687
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1688
    }
1689
    DB::$host = DB_HOST;
1690
    DB::$user = DB_USER;
1691
    DB::$password = DB_PASSWD_CLEAR;
1692
    DB::$dbName = DB_NAME;
1693
    DB::$port = DB_PORT;
1694
    DB::$encoding = DB_ENCODING;
1695
    // Insert log in DB
1696
    DB::insert(
1697
        prefixTable('log_items'),
1698
        [
1699
            'id_item' => $item_id,
1700
            'date' => time(),
1701
            'id_user' => $id_user,
1702
            'action' => $action,
1703
            'raison' => $raison,
1704
            'raison_iv' => '',
1705
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1706
        ]
1707
    );
1708
    // Timestamp the last change
1709
    if ($action === 'at_creation' || $action === 'at_modifiation' || $action === 'at_delete' || $action === 'at_import') {
1710
        DB::update(
1711
            prefixTable('misc'),
1712
            [
1713
                'valeur' => time(),
1714
            ],
1715
            'type = %s AND intitule = %s',
1716
            'timestamp',
1717
            'last_item_change'
1718
        );
1719
    }
1720
1721
    // SYSLOG
1722
    if (isset($SETTINGS['syslog_enable']) === true && $SETTINGS['syslog_enable'] === '1') {
1723
        // Extract reason
1724
        $attribute = is_null($raison) === true ? '' : explode(' : ', $raison);
1725
        // Get item info if not known
1726
        if (empty($item_label) === true) {
1727
            $dataItem = DB::queryfirstrow(
1728
                'SELECT id, id_tree, label
1729
                FROM ' . prefixTable('items') . '
1730
                WHERE id = %i',
1731
                $item_id
1732
            );
1733
            $item_label = $dataItem['label'];
1734
        }
1735
1736
        send_syslog(
1737
            'action=' . str_replace('at_', '', $action) .
1738
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1739
                ' itemno=' . $item_id .
1740
                ' user=' . is_null($login) === true ? '' : addslashes((string) $login) .
1741
                ' itemname="' . addslashes($item_label) . '"',
1742
            $SETTINGS['syslog_host'],
1743
            $SETTINGS['syslog_port'],
1744
            'teampass'
1745
        );
1746
    }
1747
1748
    // send notification if enabled
1749
    notifyOnChange($item_id, $action, $SETTINGS);
1750
}
1751
1752
/**
1753
 * If enabled, then notify admin/manager.
1754
 *
1755
 * @param int    $item_id  Item id
1756
 * @param string $action   Action to do
1757
 * @param array  $SETTINGS Teampass settings
1758
 * 
1759
 * @return void
1760
 */
1761
function notifyOnChange(int $item_id, string $action, array $SETTINGS): void
1762
{
1763
    if (
1764
        isset($SETTINGS['enable_email_notification_on_item_shown']) === true
1765
        && (int) $SETTINGS['enable_email_notification_on_item_shown'] === 1
1766
        && $action === 'at_shown'
1767
    ) {
1768
        // Load superglobal
1769
        include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1770
        $superGlobal = new protect\SuperGlobal\SuperGlobal();
1771
        // Get superglobals
1772
        $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1773
        $globalsName = $superGlobal->get('name', 'SESSION');
1774
        $globalsNotifiedEmails = $superGlobal->get('listNotificationEmails', 'SESSION');
1775
        // Get info about item
1776
        $dataItem = DB::queryfirstrow(
1777
            'SELECT id, id_tree, label
1778
            FROM ' . prefixTable('items') . '
1779
            WHERE id = %i',
1780
            $item_id
1781
        );
1782
        $item_label = $dataItem['label'];
1783
        // send back infos
1784
        DB::insert(
1785
            prefixTable('emails'),
1786
            [
1787
                'timestamp' => time(),
1788
                'subject' => langHdl('email_on_open_notification_subject'),
1789
                'body' => str_replace(
1790
                    ['#tp_user#', '#tp_item#', '#tp_link#'],
1791
                    [
1792
                        addslashes($globalsName . ' ' . $globalsLastname),
1793
                        addslashes($item_label),
1794
                        $SETTINGS['cpassman_url'] . '/index.php?page=items&group=' . $dataItem['id_tree'] . '&id=' . $item_id,
1795
                    ],
1796
                    langHdl('email_on_open_notification_mail')
1797
                ),
1798
                'receivers' => $globalsNotifiedEmails,
1799
                'status' => '',
1800
            ]
1801
        );
1802
    }
1803
}
1804
1805
/**
1806
 * Prepare notification email to subscribers.
1807
 *
1808
 * @param int    $item_id  Item id
1809
 * @param string $label    Item label
1810
 * @param array  $changes  List of changes
1811
 * @param array  $SETTINGS Teampass settings
1812
 * 
1813
 * @return void
1814
 */
1815
function notifyChangesToSubscribers(int $item_id, string $label, array $changes, array $SETTINGS): void
1816
{
1817
    // Load superglobal
1818
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1819
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1820
    // Get superglobals
1821
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
1822
    $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1823
    $globalsName = $superGlobal->get('name', 'SESSION');
1824
    // send email to user that what to be notified
1825
    $notification = DB::queryOneColumn(
1826
        'email',
1827
        'SELECT *
1828
        FROM ' . prefixTable('notification') . ' AS n
1829
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1830
        WHERE n.item_id = %i AND n.user_id != %i',
1831
        $item_id,
1832
        $globalsUserId
1833
    );
1834
    if (DB::count() > 0) {
1835
        // Prepare path
1836
        $path = geItemReadablePath($item_id, '', $SETTINGS);
1837
        // Get list of changes
1838
        $htmlChanges = '<ul>';
1839
        foreach ($changes as $change) {
1840
            $htmlChanges .= '<li>' . $change . '</li>';
1841
        }
1842
        $htmlChanges .= '</ul>';
1843
        // send email
1844
        DB::insert(
1845
            prefixTable('emails'),
1846
            [
1847
                'timestamp' => time(),
1848
                'subject' => langHdl('email_subject_item_updated'),
1849
                'body' => str_replace(
1850
                    ['#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'],
1851
                    [$label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges],
1852
                    langHdl('email_body_item_updated')
1853
                ),
1854
                'receivers' => implode(',', $notification),
1855
                'status' => '',
1856
            ]
1857
        );
1858
    }
1859
}
1860
1861
/**
1862
 * Returns the Item + path.
1863
 *
1864
 * @param int    $id_tree  Node id
1865
 * @param string $label    Label
1866
 * @param array  $SETTINGS TP settings
1867
 * 
1868
 * @return string
1869
 */
1870
function geItemReadablePath(int $id_tree, string $label, array $SETTINGS): string
1871
{
1872
    // Class loader
1873
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1874
    //Load Tree
1875
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1876
    $tree->register();
1877
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1878
    $arbo = $tree->getPath($id_tree, true);
1879
    $path = '';
1880
    foreach ($arbo as $elem) {
1881
        if (empty($path) === true) {
1882
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
1883
        } else {
1884
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
1885
        }
1886
    }
1887
1888
    // Build text to show user
1889
    if (empty($label) === false) {
1890
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
1891
    }
1892
    return empty($path) === true ? '' : $path;
1893
}
1894
1895
/**
1896
 * Get the client ip address.
1897
 *
1898
 * @return string IP address
1899
 */
1900
function getClientIpServer(): string
1901
{
1902
    if (getenv('HTTP_CLIENT_IP')) {
1903
        $ipaddress = getenv('HTTP_CLIENT_IP');
1904
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
1905
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
1906
    } elseif (getenv('HTTP_X_FORWARDED')) {
1907
        $ipaddress = getenv('HTTP_X_FORWARDED');
1908
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
1909
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
1910
    } elseif (getenv('HTTP_FORWARDED')) {
1911
        $ipaddress = getenv('HTTP_FORWARDED');
1912
    } elseif (getenv('REMOTE_ADDR')) {
1913
        $ipaddress = getenv('REMOTE_ADDR');
1914
    } else {
1915
        $ipaddress = 'UNKNOWN';
1916
    }
1917
1918
    return $ipaddress;
1919
}
1920
1921
/**
1922
 * Escape all HTML, JavaScript, and CSS.
1923
 *
1924
 * @param string $input    The input string
1925
 * @param string $encoding Which character encoding are we using?
1926
 * 
1927
 * @return string
1928
 */
1929
function noHTML(string $input, string $encoding = 'UTF-8'): string
1930
{
1931
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
1932
}
1933
1934
/**
1935
 * Permits to handle the Teampass config file
1936
 * $action accepts "rebuild" and "update"
1937
 *
1938
 * @param string $action   Action to perform
1939
 * @param array  $SETTINGS Teampass settings
1940
 * @param string $field    Field to refresh
1941
 * @param string $value    Value to set
1942
 *
1943
 * @return string|bool
1944
 */
1945
function handleConfigFile($action, $SETTINGS, $field = null, $value = null)
1946
{
1947
    $tp_config_file = $SETTINGS['cpassman_dir'] . '/includes/config/tp.config.php';
1948
    // include librairies & connect to DB
1949
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1950
    if (defined('DB_PASSWD_CLEAR') === false) {
1951
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1952
    }
1953
    DB::$host = DB_HOST;
1954
    DB::$user = DB_USER;
1955
    DB::$password = DB_PASSWD_CLEAR;
1956
    DB::$dbName = DB_NAME;
1957
    DB::$port = DB_PORT;
1958
    DB::$encoding = DB_ENCODING;
1959
    if (file_exists($tp_config_file) === false || $action === 'rebuild') {
1960
        // perform a copy
1961
        if (file_exists($tp_config_file)) {
1962
            if (! copy($tp_config_file, $tp_config_file . '.' . date('Y_m_d_His', time()))) {
1963
                return "ERROR: Could not copy file '" . $tp_config_file . "'";
1964
            }
1965
        }
1966
1967
        // regenerate
1968
        $data = [];
1969
        $data[0] = "<?php\n";
1970
        $data[1] = "global \$SETTINGS;\n";
1971
        $data[2] = "\$SETTINGS = array (\n";
1972
        $rows = DB::query(
1973
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s',
1974
            'admin'
1975
        );
1976
        foreach ($rows as $record) {
1977
            array_push($data, "    '" . $record['intitule'] . "' => '" . $record['valeur'] . "',\n");
1978
        }
1979
        array_push($data, ");\n");
1980
        $data = array_unique($data);
1981
    // ---
1982
    } elseif ($action === 'update' && empty($field) === false) {
1983
        $data = file($tp_config_file);
1984
        $inc = 0;
1985
        $bFound = false;
1986
        foreach ($data as $line) {
1987
            if (stristr($line, ');')) {
1988
                break;
1989
            }
1990
1991
            if (stristr($line, "'" . $field . "' => '")) {
1992
                $data[$inc] = "    '" . $field . "' => '" . filter_var($value, FILTER_SANITIZE_STRING) . "',\n";
1993
                $bFound = true;
1994
                break;
1995
            }
1996
            ++$inc;
1997
        }
1998
        if ($bFound === false) {
1999
            $data[$inc] = "    '" . $field . "' => '" . filter_var($value, FILTER_SANITIZE_STRING) . "',\n);\n";
2000
        }
2001
    }
2002
2003
    // update file
2004
    file_put_contents($tp_config_file, implode('', $data ?? []));
2005
    return true;
2006
}
2007
2008
/**
2009
 * Permits to replace &#92; to permit correct display
2010
 *
2011
 * @param string $input Some text
2012
 * 
2013
 * @return string
2014
 */
2015
function handleBackslash(string $input): string
2016
{
2017
    return str_replace('&amp;#92;', '&#92;', $input);
2018
}
2019
2020
/**
2021
 * Permits to load settings
2022
 * 
2023
 * @return void
2024
*/
2025
function loadSettings(): void
2026
{
2027
    global $SETTINGS;
2028
    /* LOAD CPASSMAN SETTINGS */
2029
    if (! isset($SETTINGS['loaded']) || $SETTINGS['loaded'] !== 1) {
2030
        $SETTINGS = [];
2031
        $SETTINGS['duplicate_folder'] = 0;
2032
        //by default, this is set to 0;
2033
        $SETTINGS['duplicate_item'] = 0;
2034
        //by default, this is set to 0;
2035
        $SETTINGS['number_of_used_pw'] = 5;
2036
        //by default, this value is set to 5;
2037
        $settings = [];
2038
        $rows = DB::query(
2039
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
2040
            [
2041
                'type' => 'admin',
2042
                'type2' => 'settings',
2043
            ]
2044
        );
2045
        foreach ($rows as $record) {
2046
            if ($record['type'] === 'admin') {
2047
                $SETTINGS[$record['intitule']] = $record['valeur'];
2048
            } else {
2049
                $settings[$record['intitule']] = $record['valeur'];
2050
            }
2051
        }
2052
        $SETTINGS['loaded'] = 1;
2053
        $SETTINGS['default_session_expiration_time'] = 5;
2054
    }
2055
}
2056
2057
/**
2058
 * check if folder has custom fields.
2059
 * Ensure that target one also has same custom fields
2060
 * 
2061
 * @param int $source_id
2062
 * @param int $target_id 
2063
 * 
2064
 * @return bool
2065
*/
2066
function checkCFconsistency(int $source_id, int $target_id): bool
2067
{
2068
    $source_cf = [];
2069
    $rows = DB::QUERY(
2070
        'SELECT id_category
2071
            FROM ' . prefixTable('categories_folders') . '
2072
            WHERE id_folder = %i',
2073
        $source_id
2074
    );
2075
    foreach ($rows as $record) {
2076
        array_push($source_cf, $record['id_category']);
2077
    }
2078
2079
    $target_cf = [];
2080
    $rows = DB::QUERY(
2081
        'SELECT id_category
2082
            FROM ' . prefixTable('categories_folders') . '
2083
            WHERE id_folder = %i',
2084
        $target_id
2085
    );
2086
    foreach ($rows as $record) {
2087
        array_push($target_cf, $record['id_category']);
2088
    }
2089
2090
    $cf_diff = array_diff($source_cf, $target_cf);
2091
    if (count($cf_diff) > 0) {
2092
        return false;
2093
    }
2094
2095
    return true;
2096
}
2097
2098
/**
2099
 * Will encrypte/decrypt a fil eusing Defuse.
2100
 *
2101
 * @param string $type        can be either encrypt or decrypt
2102
 * @param string $source_file path to source file
2103
 * @param string $target_file path to target file
2104
 * @param array  $SETTINGS    Settings
2105
 * @param string $password    A password
2106
 *
2107
 * @return string|bool
2108
 */
2109
function prepareFileWithDefuse(
2110
    string $type,
2111
    string $source_file,
2112
    string $target_file,
2113
    array $SETTINGS,
2114
    string $password = null
2115
) {
2116
    // Load AntiXSS
2117
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/AntiXSS.php';
2118
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/ASCII.php';
2119
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/UTF8.php';
2120
    $antiXss = new voku\helper\AntiXSS();
2121
    // Protect against bad inputs
2122
    if (is_array($source_file) === true || is_array($target_file) === true) {
2123
        return 'error_cannot_be_array';
2124
    }
2125
2126
    // Sanitize
2127
    $source_file = $antiXss->xss_clean($source_file);
2128
    $target_file = $antiXss->xss_clean($target_file);
2129
    if (empty($password) === true || is_null($password) === true) {
2130
        // get KEY to define password
2131
        $ascii_key = file_get_contents(SECUREPATH . '/teampass-seckey.txt');
2132
        $password = \Defuse\Crypto\Key::loadFromAsciiSafeString($ascii_key);
2133
    }
2134
2135
    $err = '';
2136
    if ($type === 'decrypt') {
2137
        // Decrypt file
2138
        $err = defuseFileDecrypt(
2139
            $source_file,
2140
            $target_file,
2141
            $SETTINGS, /** @scrutinizer ignore-type */
2142
            $password
2143
        );
2144
    } elseif ($type === 'encrypt') {
2145
        // Encrypt file
2146
        $err = defuseFileEncrypt(
2147
            $source_file,
2148
            $target_file,
2149
            $SETTINGS, /** @scrutinizer ignore-type */
2150
            $password
2151
        );
2152
    }
2153
2154
    // return error
2155
    return empty($err) === false ? $err : '';
2156
}
2157
2158
/**
2159
 * Encrypt a file with Defuse.
2160
 *
2161
 * @param string $source_file path to source file
2162
 * @param string $target_file path to target file
2163
 * @param array  $SETTINGS    Settings
2164
 * @param string $password    A password
2165
 *
2166
 * @return string|bool
2167
 */
2168
function defuseFileEncrypt(
2169
    string $source_file,
2170
    string $target_file,
2171
    array $SETTINGS,
2172
    string $password = null
2173
) {
2174
    // load PhpEncryption library
2175
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2176
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2177
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2178
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2179
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2180
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2181
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2182
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2183
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2184
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2185
    try {
2186
        \Defuse\Crypto\File::encryptFileWithPassword(
2187
            $source_file,
2188
            $target_file,
2189
            $password
2190
        );
2191
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2192
        $err = 'wrong_key';
2193
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2194
        $err = $ex;
2195
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2196
        $err = $ex;
2197
    }
2198
2199
    // return error
2200
    return empty($err) === false ? $err : true;
2201
}
2202
2203
/**
2204
 * Decrypt a file with Defuse.
2205
 *
2206
 * @param string $source_file path to source file
2207
 * @param string $target_file path to target file
2208
 * @param array  $SETTINGS    Settings
2209
 * @param string $password    A password
2210
 *
2211
 * @return string|bool
2212
 */
2213
function defuseFileDecrypt(
2214
    string $source_file,
2215
    string $target_file,
2216
    array $SETTINGS,
2217
    string $password = null
2218
) {
2219
    // load PhpEncryption library
2220
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2221
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2222
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2223
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2224
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2225
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2226
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2227
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2228
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2229
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2230
    try {
2231
        \Defuse\Crypto\File::decryptFileWithPassword(
2232
            $source_file,
2233
            $target_file,
2234
            $password
2235
        );
2236
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2237
        $err = 'wrong_key';
2238
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2239
        $err = $ex;
2240
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2241
        $err = $ex;
2242
    }
2243
2244
    // return error
2245
    return empty($err) === false ? $err : true;
2246
}
2247
2248
/*
2249
* NOT TO BE USED
2250
*/
2251
/**
2252
 * Undocumented function.
2253
 *
2254
 * @param string $text Text to debug
2255
 */
2256
function debugTeampass(string $text): void
2257
{
2258
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
2259
    if ($debugFile !== false) {
2260
        fputs($debugFile, $text);
2261
        fclose($debugFile);
2262
    }
2263
}
2264
2265
/**
2266
 * DELETE the file with expected command depending on server type.
2267
 *
2268
 * @param string $file     Path to file
2269
 * @param array  $SETTINGS Teampass settings
2270
 *
2271
 * @return void
2272
 */
2273
function fileDelete(string $file, array $SETTINGS): void
2274
{
2275
    // Load AntiXSS
2276
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/ASCII.php';
2277
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/UTF8.php';
2278
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/AntiXSS.php';
2279
    $antiXss = new voku\helper\AntiXSS();
2280
    $file = $antiXss->xss_clean($file);
2281
    if (is_file($file)) {
2282
        unlink($file);
2283
    }
2284
}
2285
2286
/**
2287
 * Permits to extract the file extension.
2288
 *
2289
 * @param string $file File name
2290
 *
2291
 * @return string
2292
 */
2293
function getFileExtension(string $file): string
2294
{
2295
    if (strpos($file, '.') === false) {
2296
        return $file;
2297
    }
2298
2299
    return substr($file, strrpos($file, '.') + 1);
2300
}
2301
2302
/**
2303
 * Chmods files and folders with different permissions.
2304
 *
2305
 * This is an all-PHP alternative to using: \n
2306
 * <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2307
 * <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2308
 *
2309
 * @author Jeppe Toustrup (tenzer at tenzer dot dk)
2310
  *
2311
 * @param string $path      An either relative or absolute path to a file or directory which should be processed.
2312
 * @param int    $filePerm The permissions any found files should get.
2313
 * @param int    $dirPerm  The permissions any found folder should get.
2314
 *
2315
 * @return bool Returns TRUE if the path if found and FALSE if not.
2316
 *
2317
 * @warning The permission levels has to be entered in octal format, which
2318
 * normally means adding a zero ("0") in front of the permission level. \n
2319
 * More info at: http://php.net/chmod.
2320
*/
2321
2322
function recursiveChmod(
2323
    string $path,
2324
    int $filePerm = 0644,
2325
    int  $dirPerm = 0755
2326
) {
2327
    // Check if the path exists
2328
    if (! file_exists($path)) {
2329
        return false;
2330
    }
2331
2332
    // See whether this is a file
2333
    if (is_file($path)) {
2334
        // Chmod the file with our given filepermissions
2335
        chmod($path, $filePerm);
2336
    // If this is a directory...
2337
    } elseif (is_dir($path)) {
2338
        // Then get an array of the contents
2339
        $foldersAndFiles = scandir($path);
2340
        // Remove "." and ".." from the list
2341
        $entries = array_slice($foldersAndFiles, 2);
2342
        // Parse every result...
2343
        foreach ($entries as $entry) {
2344
            // And call this function again recursively, with the same permissions
2345
            recursiveChmod($path.'/'.$entry, $filePerm, $dirPerm);
2346
        }
2347
2348
        // When we are done with the contents of the directory, we chmod the directory itself
2349
        chmod($path, $dirPerm);
2350
    }
2351
2352
    // Everything seemed to work out well, return true
2353
    return true;
2354
}
2355
2356
/**
2357
 * Check if user can access to this item.
2358
 *
2359
 * @param int   $item_id ID of item
2360
 * @param array $SETTINGS
2361
 *
2362
 * @return bool
2363
 */
2364
function accessToItemIsGranted(int $item_id, array $SETTINGS): bool
2365
{
2366
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2367
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2368
    // Prepare superGlobal variables
2369
    $session_groupes_visibles = $superGlobal->get('groupes_visibles', 'SESSION');
2370
    $session_list_restricted_folders_for_items = $superGlobal->get('list_restricted_folders_for_items', 'SESSION');
2371
    // Load item data
2372
    $data = DB::queryFirstRow(
2373
        'SELECT id_tree
2374
        FROM ' . prefixTable('items') . '
2375
        WHERE id = %i',
2376
        $item_id
2377
    );
2378
    // Check if user can access this folder
2379
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2380
        // Now check if this folder is restricted to user
2381
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2382
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2383
        ) {
2384
            return 'ERR_FOLDER_NOT_ALLOWED';
0 ignored issues
show
Bug Best Practice introduced by
The expression return 'ERR_FOLDER_NOT_ALLOWED' returns the type string which is incompatible with the type-hinted return boolean.
Loading history...
2385
        }
2386
    }
2387
2388
    return true;
2389
}
2390
2391
/**
2392
 * Creates a unique key.
2393
 *
2394
 * @param int $lenght Key lenght
2395
 *
2396
 * @return string
2397
 */
2398
function uniqidReal(int $lenght = 13): string
2399
{
2400
    if (function_exists('random_bytes')) {
2401
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2402
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2403
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2404
    } else {
2405
        throw new Exception('no cryptographically secure random function available');
2406
    }
2407
2408
    return substr(bin2hex($bytes), 0, $lenght);
2409
}
2410
2411
/**
2412
 * Obfuscate an email.
2413
 *
2414
 * @param string $email Email address
2415
 *
2416
 * @return string
2417
 */
2418
function obfuscateEmail(string $email): string
2419
{
2420
    $prop = 2;
2421
    $start = '';
2422
    $end = '';
2423
    $domain = substr(strrchr($email, '@'), 1);
2424
    $mailname = str_replace($domain, '', $email);
2425
    $name_l = strlen($mailname);
2426
    $domain_l = strlen($domain);
2427
    for ($i = 0; $i <= $name_l / $prop - 1; ++$i) {
2428
        $start .= 'x';
2429
    }
2430
2431
    for ($i = 0; $i <= $domain_l / $prop - 1; ++$i) {
2432
        $end .= 'x';
2433
    }
2434
2435
    return (string) substr_replace($mailname, $start, 2, $name_l / $prop)
2436
        . (string) substr_replace($domain, $end, 2, $domain_l / $prop);
2437
}
2438
2439
/**
2440
 * Perform a Query.
2441
 *
2442
 * @param array  $SETTINGS Teamapss settings
2443
 * @param string $fields   Fields to use
2444
 * @param string $table    Table to use
2445
 *
2446
 * @return array
2447
 */
2448
function performDBQuery(array $SETTINGS, string $fields, string $table): array
2449
{
2450
    // include librairies & connect to DB
2451
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2452
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2453
    if (defined('DB_PASSWD_CLEAR') === false) {
2454
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2455
    }
2456
    DB::$host = DB_HOST;
2457
    DB::$user = DB_USER;
2458
    DB::$password = DB_PASSWD_CLEAR;
2459
    DB::$dbName = DB_NAME;
2460
    DB::$port = DB_PORT;
2461
    DB::$encoding = DB_ENCODING;
2462
    // Insert log in DB
2463
    return DB::query(
2464
        'SELECT ' . $fields . '
2465
        FROM ' . prefixTable($table)
2466
    );
2467
}
2468
2469
/**
2470
 * Undocumented function.
2471
 *
2472
 * @param int $bytes Size of file
2473
 *
2474
 * @return string
2475
 */
2476
function formatSizeUnits(int $bytes): string
2477
{
2478
    if ($bytes >= 1073741824) {
2479
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2480
    } elseif ($bytes >= 1048576) {
2481
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2482
    } elseif ($bytes >= 1024) {
2483
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2484
    } elseif ($bytes > 1) {
2485
        $bytes .= ' bytes';
2486
    } elseif ($bytes === 1) {
2487
        $bytes .= ' byte';
2488
    } else {
2489
        $bytes = '0 bytes';
2490
    }
2491
2492
    return $bytes;
2493
}
2494
2495
/**
2496
 * Generate user pair of keys.
2497
 *
2498
 * @param string $userPwd User password
2499
 *
2500
 * @return array
2501
 */
2502
function generateUserKeys(string $userPwd): array
2503
{
2504
    // include library
2505
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2506
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2507
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2508
    // Load classes
2509
    $rsa = new Crypt_RSA();
2510
    $cipher = new Crypt_AES();
2511
    // Create the private and public key
2512
    $res = $rsa->createKey(4096);
2513
    // Encrypt the privatekey
2514
    $cipher->setPassword($userPwd);
2515
    $privatekey = $cipher->encrypt($res['privatekey']);
2516
    return [
2517
        'private_key' => base64_encode($privatekey),
2518
        'public_key' => base64_encode($res['publickey']),
2519
        'private_key_clear' => base64_encode($res['privatekey']),
2520
    ];
2521
}
2522
2523
/**
2524
 * Permits to decrypt the user's privatekey.
2525
 *
2526
 * @param string $userPwd        User password
2527
 * @param string $userPrivateKey User private key
2528
 *
2529
 * @return string
2530
 */
2531
function decryptPrivateKey(string $userPwd, string $userPrivateKey): string
2532
{
2533
    if (empty($userPwd) === false) {
2534
        include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2535
        // Load classes
2536
        $cipher = new Crypt_AES();
2537
        // Encrypt the privatekey
2538
        $cipher->setPassword($userPwd);
2539
        return base64_encode($cipher->decrypt(base64_decode($userPrivateKey)));
2540
    }
2541
    return '';
2542
}
2543
2544
/**
2545
 * Permits to encrypt the user's privatekey.
2546
 *
2547
 * @param string $userPwd        User password
2548
 * @param string $userPrivateKey User private key
2549
 *
2550
 * @return string
2551
 */
2552
function encryptPrivateKey(string $userPwd, string $userPrivateKey): string
2553
{
2554
    if (empty($userPwd) === false) {
2555
        include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2556
        // Load classes
2557
        $cipher = new Crypt_AES();
2558
        // Encrypt the privatekey
2559
        $cipher->setPassword($userPwd);
2560
        return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2561
    }
2562
    return '';
2563
}
2564
2565
/**
2566
 * Encrypts a string using AES.
2567
 *
2568
 * @param string $data String to encrypt
2569
 *
2570
 * @return array
2571
 */
2572
function doDataEncryption(string $data): array
2573
{
2574
    // Includes
2575
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2576
    // Load classes
2577
    $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2578
    // Generate an object key
2579
    $objectKey = uniqidReal(32);
2580
    // Set it as password
2581
    $cipher->setPassword($objectKey);
2582
    return [
2583
        'encrypted' => base64_encode($cipher->encrypt($data)),
2584
        'objectKey' => base64_encode($objectKey),
2585
    ];
2586
}
2587
2588
/**
2589
 * Decrypts a string using AES.
2590
 *
2591
 * @param string $data Encrypted data
2592
 * @param string $key  Key to uncrypt
2593
 *
2594
 * @return string
2595
 */
2596
function doDataDecryption(string $data, string $key): string
2597
{
2598
    // Includes
2599
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2600
    // Load classes
2601
    $cipher = new Crypt_AES();
2602
    // Set the object key
2603
    $cipher->setPassword(base64_decode($key));
2604
    return base64_encode($cipher->decrypt(base64_decode($data)));
2605
}
2606
2607
/**
2608
 * Encrypts using RSA a string using a public key.
2609
 *
2610
 * @param string $key       Key to be encrypted
2611
 * @param string $publicKey User public key
2612
 *
2613
 * @return string
2614
 */
2615
function encryptUserObjectKey(string $key, string $publicKey): string
2616
{
2617
    // Includes
2618
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2619
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2620
    // Load classes
2621
    $rsa = new Crypt_RSA();
2622
    $rsa->loadKey(base64_decode($publicKey));
2623
    // Encrypt
2624
    return base64_encode($rsa->encrypt(base64_decode($key)));
2625
}
2626
2627
/**
2628
 * Decrypts using RSA an encrypted string using a private key.
2629
 *
2630
 * @param string $key        Encrypted key
2631
 * @param string $privateKey User private key
2632
 *
2633
 * @return string
2634
 */
2635
function decryptUserObjectKey(string $key, string $privateKey): string
2636
{
2637
    // Includes
2638
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2639
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2640
    // Load classes
2641
    $rsa = new Crypt_RSA();
2642
    $rsa->loadKey(base64_decode($privateKey));
2643
    // Decrypt
2644
    try {
2645
        $ret = base64_encode($rsa->decrypt(base64_decode($key)));
2646
    } catch (Exception $e) {
2647
        return $e;
2648
    }
2649
2650
    return $ret;
2651
}
2652
2653
/**
2654
 * Encrypts a file.
2655
 *
2656
 * @param string $fileInName File name
2657
 * @param string $fileInPath Path to file
2658
 *
2659
 * @return array
2660
 */
2661
function encryptFile(string $fileInName, string $fileInPath): array
2662
{
2663
    if (defined('FILE_BUFFER_SIZE') === false) {
2664
        define('FILE_BUFFER_SIZE', 128 * 1024);
2665
    }
2666
2667
    // Includes
2668
    include_once '../includes/config/include.php';
2669
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2670
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2671
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2672
    // Load classes
2673
    $cipher = new Crypt_AES();
2674
    // Generate an object key
2675
    $objectKey = uniqidReal(32);
2676
    // Set it as password
2677
    $cipher->setPassword($objectKey);
2678
    // Prevent against out of memory
2679
    $cipher->enableContinuousBuffer();
2680
    //$cipher->disablePadding();
2681
2682
    // Encrypt the file content
2683
    $plaintext = file_get_contents(
2684
        filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL)
2685
    );
2686
    $ciphertext = $cipher->encrypt($plaintext);
2687
    // Save new file
2688
    $hash = md5($plaintext);
2689
    $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2690
    file_put_contents($fileOut, $ciphertext);
2691
    unlink($fileInPath . '/' . $fileInName);
2692
    return [
2693
        'fileHash' => base64_encode($hash),
2694
        'objectKey' => base64_encode($objectKey),
2695
    ];
2696
}
2697
2698
/**
2699
 * Decrypt a file.
2700
 *
2701
 * @param string $fileName File name
2702
 * @param string $filePath Path to file
2703
 * @param string $key      Key to use
2704
 *
2705
 * @return string
2706
 */
2707
function decryptFile(string $fileName, string $filePath, string $key): string
2708
{
2709
    if (! defined('FILE_BUFFER_SIZE')) {
2710
        define('FILE_BUFFER_SIZE', 128 * 1024);
2711
    }
2712
2713
    // Includes
2714
    include_once '../includes/config/include.php';
2715
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2716
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2717
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2718
    // Get file name
2719
    $fileName = base64_decode($fileName);
2720
    // Load classes
2721
    $cipher = new Crypt_AES();
2722
    // Set the object key
2723
    $cipher->setPassword(base64_decode($key));
2724
    // Prevent against out of memory
2725
    $cipher->enableContinuousBuffer();
2726
    $cipher->disablePadding();
2727
    // Get file content
2728
    $ciphertext = file_get_contents($filePath . '/' . TP_FILE_PREFIX . $fileName);
2729
    // Decrypt file content and return
2730
    return base64_encode($cipher->decrypt($ciphertext));
2731
}
2732
2733
/**
2734
 * Generate a simple password
2735
 *
2736
 * @param int $length Length of string
2737
 * @param bool $symbolsincluded Allow symbols
2738
 *
2739
 * @return string
2740
 */
2741
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2742
{
2743
    // Generate new user password
2744
    $small_letters = range('a', 'z');
2745
    $big_letters = range('A', 'Z');
2746
    $digits = range(0, 9);
2747
    $symbols = $symbolsincluded === true ?
2748
        ['#', '_', '-', '@', '$', '+', '&'] : [];
2749
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2750
    $count = count($res);
2751
    // first variant
2752
2753
    $random_string = '';
2754
    for ($i = 0; $i < $length; ++$i) {
2755
        $random_string .= $res[random_int(0, $count - 1)];
2756
    }
2757
2758
    return $random_string;
2759
}
2760
2761
/**
2762
 * Permit to store the sharekey of an object for users.
2763
 *
2764
 * @param string $object_name             Type for table selection
2765
 * @param int    $post_folder_is_personal Personal
2766
 * @param int    $post_folder_id          Folder
2767
 * @param int    $post_object_id          Object
2768
 * @param string $objectKey               Object key
2769
 * @param array  $SETTINGS                Teampass settings
2770
 *
2771
 * @return void
2772
 */
2773
function storeUsersShareKey(
2774
    string $object_name,
2775
    int $post_folder_is_personal,
2776
    int $post_folder_id,
2777
    int $post_object_id,
2778
    string $objectKey,
2779
    array $SETTINGS
2780
): void {
2781
    // include librairies & connect to DB
2782
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2783
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2784
    if (defined('DB_PASSWD_CLEAR') === false) {
2785
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2786
    }
2787
    DB::$host = DB_HOST;
2788
    DB::$user = DB_USER;
2789
    DB::$password = DB_PASSWD_CLEAR;
2790
    DB::$dbName = DB_NAME;
2791
    DB::$port = DB_PORT;
2792
    DB::$encoding = DB_ENCODING;
2793
    // Delete existing entries for this object
2794
    DB::delete(
2795
        $object_name,
2796
        'object_id = %i',
2797
        $post_object_id
2798
    );
2799
    // Superglobals
2800
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2801
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2802
    // Prepare superGlobal variables
2803
    $sessionPpersonaFolders = $superGlobal->get('personal_folders', 'SESSION');
2804
    $sessionUserId = $superGlobal->get('user_id', 'SESSION');
2805
    $sessionUserPublicKey = $superGlobal->get('public_key', 'SESSION', 'user');
2806
    if (
2807
        (int) $post_folder_is_personal === 1
2808
        && in_array($post_folder_id, $sessionPpersonaFolders) === true
2809
    ) {
2810
        // If this is a personal object
2811
        // Only create the sharekey for user
2812
        DB::insert(
2813
            $object_name,
2814
            [
2815
                'object_id' => (int) $post_object_id,
2816
                'user_id' => (int) $sessionUserId,
2817
                'share_key' => encryptUserObjectKey($objectKey, $sessionUserPublicKey),
2818
            ]
2819
        );
2820
    } else {
2821
        // This is a public object
2822
        // Create sharekey for each user
2823
        $users = DB::query(
2824
            'SELECT id, public_key
2825
            FROM ' . prefixTable('users') . '
2826
            WHERE id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '")
2827
            AND public_key != ""'
2828
        );
2829
        foreach ($users as $user) {
2830
            // Insert in DB the new object key for this item by user
2831
            DB::insert(
2832
                $object_name,
2833
                [
2834
                    'object_id' => $post_object_id,
2835
                    'user_id' => (int) $user['id'],
2836
                    'share_key' => encryptUserObjectKey(
2837
                        $objectKey,
2838
                        $user['public_key']
2839
                    ),
2840
                ]
2841
            );
2842
        }
2843
    }
2844
}
2845
2846
/**
2847
 * Is this string base64 encoded?
2848
 *
2849
 * @param string $str Encoded string?
2850
 *
2851
 * @return bool
2852
 */
2853
function isBase64(string $str): bool
2854
{
2855
    $str = (string) trim($str);
2856
    if (! isset($str[0])) {
2857
        return false;
2858
    }
2859
2860
    $base64String = (string) base64_decode($str, true);
2861
    if ($base64String && base64_encode($base64String) === $str) {
2862
        return true;
2863
    }
2864
2865
    return false;
2866
}
2867
2868
/**
2869
 * Undocumented function
2870
 *
2871
 * @param string $field Parameter
2872
 *
2873
 * @return array|bool|resource|string
2874
 */
2875
function filterString(string $field)
2876
{
2877
    // Sanitize string
2878
    $field = filter_var(trim($field), FILTER_SANITIZE_STRING);
2879
    if (empty($field) === false) {
2880
        // Load AntiXSS
2881
        include_once '../includes/libraries/voku/helper/AntiXSS.php';
2882
        $antiXss = new voku\helper\AntiXSS();
2883
        // Return
2884
        return $antiXss->xss_clean($field);
2885
    }
2886
2887
    return false;
2888
}
2889
2890
/**
2891
 * CHeck if provided credentials are allowed on server
2892
 *
2893
 * @param string $login    User Login
2894
 * @param string $password User Pwd
2895
 * @param array  $SETTINGS Teampass settings
2896
 *
2897
 * @return bool
2898
 */
2899
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
2900
{
2901
    // Build ldap configuration array
2902
    $config = [
2903
        // Mandatory Configuration Options
2904
        'hosts' => [$SETTINGS['ldap_hosts']],
2905
        'base_dn' => $SETTINGS['ldap_bdn'],
2906
        'username' => $SETTINGS['ldap_username'],
2907
        'password' => $SETTINGS['ldap_password'],
2908
2909
        // Optional Configuration Options
2910
        'port' => $SETTINGS['ldap_port'],
2911
        'use_ssl' => (int) $SETTINGS['ldap_ssl'] === 1 ? true : false,
2912
        'use_tls' => (int) $SETTINGS['ldap_tls'] === 1 ? true : false,
2913
        'version' => 3,
2914
        'timeout' => 5,
2915
        'follow_referrals' => false,
2916
2917
        // Custom LDAP Options
2918
        'options' => [
2919
            // See: http://php.net/ldap_set_option
2920
            LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_HARD,
2921
        ],
2922
    ];
2923
    // Load expected libraries
2924
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Traits/Macroable.php';
2925
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Arr.php';
2926
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/DetectsErrors.php';
2927
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Connection.php';
2928
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/LdapInterface.php';
2929
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/LdapBase.php';
2930
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Ldap.php';
2931
    $ad = new SplClassLoader('LdapRecord', '../includes/libraries');
2932
    $ad->register();
2933
    $connection = new Connection($config);
2934
    // Connect to LDAP
2935
    try {
2936
        $connection->connect();
2937
    } catch (\LdapRecord\Auth\BindException $e) {
2938
        $error = $e->getDetailedError();
2939
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
2940
        return false;
2941
    }
2942
2943
    // Authenticate user
2944
    try {
2945
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory') {
2946
            $connection->auth()->attempt($login, $password, $stayAuthenticated = true);
2947
        } else {
2948
            $connection->auth()->attempt($SETTINGS['ldap_user_attribute'].'='.$login.','.(isset($SETTINGS['ldap_dn_additional_user_dn']) && !empty($SETTINGS['ldap_dn_additional_user_dn']) ? $SETTINGS['ldap_dn_additional_user_dn'].',' : '').$SETTINGS['ldap_bdn'], $password, $stayAuthenticated = true);
2949
        }
2950
    } catch (\LdapRecord\Auth\BindException $e) {
2951
        $error = $e->getDetailedError();
2952
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
2953
        return false;
2954
    }
2955
2956
    return true;
2957
}
2958
2959
/**
2960
 * Removes from DB all sharekeys of this user
2961
 *
2962
 * @param int $userId User's id
2963
 * @param array   $SETTINGS Teampass settings
2964
 *
2965
 * @return bool
2966
 */
2967
function deleteUserObjetsKeys(int $userId, array $SETTINGS): bool
2968
{
2969
    // include librairies & connect to DB
2970
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2971
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2972
    if (defined('DB_PASSWD_CLEAR') === false) {
2973
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2974
    }
2975
    DB::$host = DB_HOST;
2976
    DB::$user = DB_USER;
2977
    DB::$password = DB_PASSWD_CLEAR;
2978
    DB::$dbName = DB_NAME;
2979
    DB::$port = DB_PORT;
2980
    DB::$encoding = DB_ENCODING;
2981
    // Remove all item sharekeys items
2982
    DB::delete(
2983
        prefixTable('sharekeys_items'),
2984
        'user_id = %i',
2985
        $userId
2986
    );
2987
    // Remove all item sharekeys files
2988
    DB::delete(
2989
        prefixTable('sharekeys_files'),
2990
        'user_id = %i',
2991
        $userId
2992
    );
2993
    // Remove all item sharekeys fields
2994
    DB::delete(
2995
        prefixTable('sharekeys_fields'),
2996
        'user_id = %i',
2997
        $userId
2998
    );
2999
    // Remove all item sharekeys logs
3000
    DB::delete(
3001
        prefixTable('sharekeys_logs'),
3002
        'user_id = %i',
3003
        $userId
3004
    );
3005
    // Remove all item sharekeys suggestions
3006
    DB::delete(
3007
        prefixTable('sharekeys_suggestions'),
3008
        'user_id = %i',
3009
        $userId
3010
    );
3011
    return false;
3012
}
3013
3014
/**
3015
 * Manage list of timezones   $SETTINGS Teampass settings
3016
 *
3017
 * @return array
3018
 */
3019
function timezone_list()
3020
{
3021
    static $timezones = null;
3022
    if ($timezones === null) {
3023
        $timezones = [];
3024
        $offsets = [];
3025
        $now = new DateTime('now', new DateTimeZone('UTC'));
3026
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
3027
            $now->setTimezone(new DateTimeZone($timezone));
3028
            $offsets[] = $offset = $now->getOffset();
3029
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
3030
        }
3031
3032
        array_multisort($offsets, $timezones);
3033
    }
3034
3035
    return $timezones;
3036
}
3037
3038
/**
3039
 * Provide timezone offset
3040
 *
3041
 * @param int $offset Timezone offset
3042
 *
3043
 * @return string
3044
 */
3045
function format_GMT_offset($offset): string
3046
{
3047
    $hours = intval($offset / 3600);
3048
    $minutes = abs(intval($offset % 3600 / 60));
3049
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
3050
}
3051
3052
/**
3053
 * Provides timezone name
3054
 *
3055
 * @param string $name Timezone name
3056
 *
3057
 * @return string
3058
 */
3059
function format_timezone_name($name): string
3060
{
3061
    $name = str_replace('/', ', ', $name);
3062
    $name = str_replace('_', ' ', $name);
3063
3064
    return str_replace('St ', 'St. ', $name);
3065
}
3066
3067
/**
3068
 * Provides info about if user should use MFA
3069
 *
3070
 * @param string $userRolesIds  User roles ids
3071
 * @param string $mfaRoles      Roles for which MFA is requested
3072
 *
3073
 * @return bool
3074
 */
3075
function mfa_auth_requested(string $userRolesIds, string $mfaRoles): bool
3076
{
3077
    if (empty($mfaRoles) === true) {
3078
        return false;
3079
    }
3080
3081
    $mfaRoles = array_values(json_decode($mfaRoles, true));
3082
    $userRolesIds = array_filter(explode(';', $userRolesIds));
3083
    if (count($mfaRoles) === 0 || count($mfaRoles) === 0) {
3084
        return true;
3085
    }
3086
3087
    if (count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
3088
        return true;
3089
    }
3090
    return false;
3091
}
3092