Passed
Push — teampass_3.0 ( 2c08f5...ae6be7 )
by Nils
04:47
created

defuse_validate_personal_key()   B

Complexity

Conditions 6
Paths 42

Size

Total Lines 40
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

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