Passed
Push — teampass_3.0 ( f644bb...9dbfce )
by Nils
12:26
created

cacheTableUpdate()   B

Complexity

Conditions 10
Paths 16

Size

Total Lines 76
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 10
eloc 51
c 5
b 0
f 0
nc 16
nop 2
dl 0
loc 76
rs 7.2024

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This library is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
 * ---
12
 *
13
 * @project   Teampass
14
 *
15
 * @file      main.functions.php
16
 * ---
17
 *
18
 * @author    Nils Laumaillé ([email protected])
19
 *
20
 * @copyright 2009-2021 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
87
/**
88
 * genHash().
89
 *
90
 * Generate a hash for user login
91
 */
92
function bCrypt(string $password, $cost)
93
{
94
    $salt = sprintf('$2y$%02d$', $cost);
95
    if (function_exists('openssl_random_pseudo_bytes')) {
96
        $salt .= bin2hex(openssl_random_pseudo_bytes(11));
97
    } else {
98
        $chars = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
99
        for ($i = 0; $i < 22; ++$i) {
100
            $salt .= $chars[mt_rand(0, 63)];
101
        }
102
    }
103
104
    return crypt($password, $salt);
105
}
106
107
/**
108
 * Defuse cryption function.
109
 *
110
 * @param string $message   what to de/crypt
111
 * @param string $ascii_key key to use
112
 * @param string $type      operation to perform
113
 * @param array  $SETTINGS  Teampass settings
114
 *
115
 * @return array
116
 */
117
function cryption(string $message, string $ascii_key, string $type, array $SETTINGS): array
118
{
119
    $ascii_key = empty($ascii_key) === true ? file_get_contents(SECUREPATH . '/teampass-seckey.txt') : $ascii_key;
120
    $err = false;
121
    // load PhpEncryption library
122
    if (isset($SETTINGS['cpassman_dir']) === false || empty($SETTINGS['cpassman_dir']) === true) {
123
        $path = '../includes/libraries/Encryption/Encryption/';
124
    } else {
125
        $path = $SETTINGS['cpassman_dir'] . '/includes/libraries/Encryption/Encryption/';
126
    }
127
128
    include_once $path . 'Crypto.php';
129
    include_once $path . 'Encoding.php';
130
    include_once $path . 'DerivedKeys.php';
131
    include_once $path . 'Key.php';
132
    include_once $path . 'KeyOrPassword.php';
133
    include_once $path . 'File.php';
134
    include_once $path . 'RuntimeTests.php';
135
    include_once $path . 'KeyProtectedByPassword.php';
136
    include_once $path . 'Core.php';
137
    // convert KEY
138
    $key = \Defuse\Crypto\Key::loadFromAsciiSafeString($ascii_key);
139
    try {
140
        if ($type === 'encrypt') {
141
            $text = \Defuse\Crypto\Crypto::encrypt($message, $key);
142
        } elseif ($type === 'decrypt') {
143
            $text = \Defuse\Crypto\Crypto::decrypt($message, $key);
144
        }
145
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
146
        $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.';
147
    } catch (Defuse\Crypto\Exception\BadFormatException $ex) {
148
        $err = $ex;
149
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
150
        $err = $ex;
151
    } catch (Defuse\Crypto\Exception\CryptoException $ex) {
152
        $err = $ex;
153
    } catch (Defuse\Crypto\Exception\IOException $ex) {
154
        $err = $ex;
155
    }
156
    //echo \Defuse\Crypto\Crypto::decrypt($message, $key).' ## ';
157
158
    return [
159
        'string' => $text ?? '',
160
        'error' => $err,
161
    ];
162
}
163
164
/**
165
 * Generating a defuse key.
166
 */
167
function defuse_generate_key(): string
168
{
169
    // load PhpEncryption library
170
    if (file_exists('../includes/config/tp.config.php') === true) {
171
        $path = '../includes/libraries/Encryption/Encryption/';
172
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
173
        $path = './includes/libraries/Encryption/Encryption/';
174
    } else {
175
        $path = '../includes/libraries/Encryption/Encryption/';
176
    }
177
178
    include_once $path . 'Crypto.php';
179
    include_once $path . 'Encoding.php';
180
    include_once $path . 'DerivedKeys.php';
181
    include_once $path . 'Key.php';
182
    include_once $path . 'KeyOrPassword.php';
183
    include_once $path . 'File.php';
184
    include_once $path . 'RuntimeTests.php';
185
    include_once $path . 'KeyProtectedByPassword.php';
186
    include_once $path . 'Core.php';
187
    $key = \Defuse\Crypto\Key::createNewRandomKey();
188
    $key = $key->saveToAsciiSafeString();
189
    return $key;
190
}
191
192
/**
193
 * Generate a Defuse personal key.
194
 *
195
 * @param string $psk psk used
196
 */
197
function defuse_generate_personal_key(string $psk): string
198
{
199
    // load PhpEncryption library
200
    if (file_exists('../includes/config/tp.config.php') === true) {
201
        $path = '../includes/libraries/Encryption/Encryption/';
202
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
203
        $path = './includes/libraries/Encryption/Encryption/';
204
    } else {
205
        $path = '../includes/libraries/Encryption/Encryption/';
206
    }
207
208
    include_once $path . 'Crypto.php';
209
    include_once $path . 'Encoding.php';
210
    include_once $path . 'DerivedKeys.php';
211
    include_once $path . 'Key.php';
212
    include_once $path . 'KeyOrPassword.php';
213
    include_once $path . 'File.php';
214
    include_once $path . 'RuntimeTests.php';
215
    include_once $path . 'KeyProtectedByPassword.php';
216
    include_once $path . 'Core.php';
217
    $protected_key = \Defuse\Crypto\KeyProtectedByPassword::createRandomPasswordProtectedKey($psk);
218
    return $protected_key->saveToAsciiSafeString(); // save this in user table
219
}
220
221
/**
222
 * Validate persoanl key with defuse.
223
 *
224
 * @param string $psk                   the user's psk
225
 * @param string $protected_key_encoded special key
226
 */
227
function defuse_validate_personal_key(string $psk, string $protected_key_encoded): string
228
{
229
    // load PhpEncryption library
230
    if (file_exists('../includes/config/tp.config.php') === true) {
231
        $path = '../includes/libraries/Encryption/Encryption/';
232
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
233
        $path = './includes/libraries/Encryption/Encryption/';
234
    } else {
235
        $path = '../includes/libraries/Encryption/Encryption/';
236
    }
237
238
    include_once $path . 'Crypto.php';
239
    include_once $path . 'Encoding.php';
240
    include_once $path . 'DerivedKeys.php';
241
    include_once $path . 'Key.php';
242
    include_once $path . 'KeyOrPassword.php';
243
    include_once $path . 'File.php';
244
    include_once $path . 'RuntimeTests.php';
245
    include_once $path . 'KeyProtectedByPassword.php';
246
    include_once $path . 'Core.php';
247
    try {
248
        $protected_key = \Defuse\Crypto\KeyProtectedByPassword::loadFromAsciiSafeString($protected_key_encoded);
249
        $user_key = $protected_key->unlockKey($psk);
250
        $user_key_encoded = $user_key->saveToAsciiSafeString();
251
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
252
        return 'Error - Major issue as the encryption is broken.';
253
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
254
        return 'Error - The saltkey is not the correct one.';
255
    }
256
257
    return $user_key_encoded;
258
    // store it in session once user has entered his psk
259
}
260
261
/**
262
 * Decrypt a defuse string if encrypted.
263
 *
264
 * @param string $value Encrypted string
265
 *
266
 * @return string Decrypted string
267
 */
268
function defuseReturnDecrypted(string $value, $SETTINGS): string
269
{
270
    if (substr($value, 0, 3) === 'def') {
271
        $value = cryption($value, '', 'decrypt', $SETTINGS)['string'];
272
    }
273
274
    return $value;
275
}
276
277
/**
278
 * Trims a string depending on a specific string.
279
 *
280
 * @param string|array $chaine  what to trim
281
 * @param string       $element trim on what
282
 */
283
function trimElement(string|array $chaine, string $element): string
284
{
285
    if (! empty($chaine)) {
286
        if (is_array($chaine) === true) {
1 ignored issue
show
introduced by
The condition is_array($chaine) === true is always true.
Loading history...
287
            $chaine = implode(';', $chaine);
288
        }
289
        $chaine = trim($chaine);
290
        if (substr($chaine, 0, 1) === $element) {
291
            $chaine = substr($chaine, 1);
292
        }
293
        if (substr($chaine, strlen($chaine) - 1, 1) === $element) {
294
            $chaine = substr($chaine, 0, strlen($chaine) - 1);
295
        }
296
    }
297
298
    return $chaine;
299
}
300
301
/**
302
 * Permits to suppress all "special" characters from string.
303
 *
304
 * @param string $string  what to clean
305
 * @param bool   $special use of special chars?
306
 */
307
function cleanString(string $string, bool $special = false): string
308
{
309
    // Create temporary table for special characters escape
310
    $tabSpecialChar = [];
311
    for ($i = 0; $i <= 31; ++$i) {
312
        $tabSpecialChar[] = chr($i);
313
    }
314
    array_push($tabSpecialChar, '<br />');
315
    if ((int) $special === 1) {
316
        $tabSpecialChar = array_merge($tabSpecialChar, ['</li>', '<ul>', '<ol>']);
317
    }
318
319
    return str_replace($tabSpecialChar, "\n", $string);
320
}
321
322
/**
323
 * Erro manager for DB.
324
 *
325
 * @param array $params output from query
326
 */
327
function db_error_handler(array $params): void
328
{
329
    echo 'Error: ' . $params['error'] . "<br>\n";
330
    echo 'Query: ' . $params['query'] . "<br>\n";
331
    throw new Exception('Error - Query', 1);
332
}
333
334
/**
335
 * [identifyUserRights description].
336
 *
337
 * @param string $groupesVisiblesUser  [description]
338
 * @param string $groupesInterditsUser [description]
339
 * @param string $isAdmin              [description]
340
 * @param string $idFonctions          [description]
341
 *
342
 * @return string [description]
343
 */
344
function identifyUserRights(
345
    string $groupesVisiblesUser,
346
    string $groupesInterditsUser,
347
    string $isAdmin,
348
    string $idFonctions,
349
    $SETTINGS
350
): string {
351
    //load ClassLoader
352
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
353
    // Load superglobal
354
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
355
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
356
    //Connect to DB
357
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
358
    if (defined('DB_PASSWD_CLEAR') === false) {
359
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
360
    }
361
    DB::$host = DB_HOST;
362
    DB::$user = DB_USER;
363
    DB::$password = DB_PASSWD_CLEAR;
364
    DB::$dbName = DB_NAME;
365
    DB::$port = DB_PORT;
366
    DB::$encoding = DB_ENCODING;
367
    //Build tree
368
    $tree = new SplClassLoader('Tree\NestedTree', $SETTINGS['cpassman_dir'] . '/includes/libraries');
369
    $tree->register();
370
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
371
    // Check if user is ADMINISTRATOR
372
    if ((int) $isAdmin === 1) {
373
        identAdmin(
374
            $idFonctions,
375
            $SETTINGS, /** @scrutinizer ignore-type */ 
376
            $tree
377
        );
378
    } else {
379
        identUser(
380
            $groupesVisiblesUser,
381
            $groupesInterditsUser,
382
            $idFonctions,
383
            $SETTINGS, /** @scrutinizer ignore-type */ 
384
            $tree
385
        );
386
    }
387
388
    // update user's timestamp
389
    DB::update(
390
        prefixTable('users'),
391
        [
392
            'timestamp' => time(),
393
        ],
394
        'id=%i',
395
        $superGlobal->get('user_id', 'SESSION')
396
    );
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return string. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
397
}
398
399
/**
400
 * Identify administrator.
401
 *
402
 * @param string $idFonctions Roles of user
403
 * @param array  $SETTINGS    Teampass settings
404
 * @param array  $tree        Tree of folders
405
 */
406
function identAdmin(string $idFonctions, array $SETTINGS, array $tree): void
407
{
408
    // Load superglobal
409
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
410
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
411
    // Init
412
    $groupesVisibles = [];
413
    $superGlobal->put('personal_folders', [], 'SESSION');
414
    $superGlobal->put('groupes_visibles', [], 'SESSION');
415
    $superGlobal->put('no_access_folders', [], 'SESSION');
416
    $superGlobal->put('personal_visible_groups', [], 'SESSION');
417
    $superGlobal->put('read_only_folders', [], 'SESSION');
418
    $superGlobal->put('list_restricted_folders_for_items', [], 'SESSION');
419
    $superGlobal->put('list_folders_editable_by_role', [], 'SESSION');
420
    $superGlobal->put('list_folders_limited', [], 'SESSION');
421
    $superGlobal->put('no_access_folders', [], 'SESSION');
422
    $superGlobal->put('forbiden_pfs', [], 'SESSION');
423
    // Get superglobals
424
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
425
    $globalsVisibleFolders = $superGlobal->get('groupes_visibles', 'SESSION');
426
    $globalsPersonalVisibleFolders = $superGlobal->get('personal_visible_groups', 'SESSION');
427
    // Get list of Folders
428
    $rows = DB::query('SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i', 0);
429
    foreach ($rows as $record) {
430
        array_push($groupesVisibles, $record['id']);
431
    }
432
    $superGlobal->put('groupes_visibles', $groupesVisibles, 'SESSION');
433
    $superGlobal->put('all_non_personal_folders', $groupesVisibles, 'SESSION');
434
    // Exclude all PF
435
    $where = new WhereClause('and');
436
    // create a WHERE statement of pieces joined by ANDs
437
    $where->add('personal_folder=%i', 1);
438
    if (
439
        isset($SETTINGS['enable_pf_feature']) === true
440
        && (int) $SETTINGS['enable_pf_feature'] === 1
441
    ) {
442
        $where->add('title=%s', $globalsUserId);
443
        $where->negateLast();
444
    }
445
    // Get ID of personal folder
446
    $persfld = DB::queryfirstrow(
447
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE title = %s',
448
        $globalsUserId
449
    );
450
    if (empty($persfld['id']) === false) {
451
        if (in_array($persfld['id'], $globalsVisibleFolders) === false) {
452
            array_push($globalsVisibleFolders, $persfld['id']);
453
            array_push($globalsPersonalVisibleFolders, $persfld['id']);
454
            // get all descendants
455
            $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
456
            $tree->rebuild();
457
            $tst = $tree->getDescendants($persfld['id']);
458
            foreach ($tst as $t) {
459
                array_push($globalsVisibleFolders, $t->id);
460
                array_push($globalsPersonalVisibleFolders, $t->id);
461
            }
462
        }
463
    }
464
465
    // get complete list of ROLES
466
    $tmp = explode(';', $idFonctions);
467
    $rows = DB::query(
468
    'SELECT * FROM ' . prefixTable('roles_title') . '
469
        ORDER BY title ASC'
470
);
471
    foreach ($rows as $record) {
472
        if (! empty($record['id']) && ! in_array($record['id'], $tmp)) {
473
            array_push($tmp, $record['id']);
474
        }
475
    }
476
    $superGlobal->put('fonction_id', implode(';', $tmp), 'SESSION');
477
    $superGlobal->put('is_admin', 1, 'SESSION');
478
    // Check if admin has created Folders and Roles
479
    DB::query('SELECT * FROM ' . prefixTable('nested_tree') . '');
480
    $superGlobal->put('nb_folders', DB::count(), 'SESSION');
481
    DB::query('SELECT * FROM ' . prefixTable('roles_title'));
482
    $superGlobal->put('nb_roles', DB::count(), 'SESSION');
483
}
484
485
/**
486
 * Permits to convert an element to array.
487
 *
488
 * @param string|array $element Any value to be returned as array
489
 *
490
 * @return array
491
 */
492
function convertToArray(string|array $element): array
493
{
494
    if (is_string($element) === true) {
1 ignored issue
show
introduced by
The condition is_string($element) === true is always false.
Loading history...
495
        if (empty($element) === true) {
496
            return [];
497
        }
498
        return explode(
499
                ';',
500
                trimElement($element, ';')
501
            );
502
503
    
504
    }
505
    return $element;
506
507
    
508
}
509
510
/**
511
 * Defines the rights the user has.
512
 *
513
 * @param string|array $allowedFolders  Allowed folders
514
 * @param string|array $noAccessFolders Not allowed folders
515
 * @param string|array $userRoles       Roles of user
516
 * @param array        $SETTINGS        Teampass settings
517
 * @param object       $tree            Tree of folders
518
 */
519
function identUser(
520
    string|array $allowedFolders,
521
    string|array $noAccessFolders,
522
    string|array $userRoles,
523
    array $SETTINGS,
524
    object $tree
525
) {
526
    // Load superglobal
527
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
528
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
529
    // Init
530
    $superGlobal->put('groupes_visibles', [], 'SESSION');
531
    $superGlobal->put('personal_folders', [], 'SESSION');
532
    $superGlobal->put('no_access_folders', [], 'SESSION');
533
    $superGlobal->put('personal_visible_groups', [], 'SESSION');
534
    $superGlobal->put('read_only_folders', [], 'SESSION');
535
    $superGlobal->put('fonction_id', $userRoles, 'SESSION');
536
    $superGlobal->put('is_admin', 0, 'SESSION');
537
    // init
538
    $personalFolders = [];
539
    $readOnlyFolders = [];
540
    $noAccessPersonalFolders = [];
541
    $restrictedFoldersForItems = [];
542
    $foldersLimited = [];
543
    $foldersLimitedFull = [];
544
    $allowedFoldersByRoles = [];
545
    // Get superglobals
546
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
547
    $globalsPersonalFolders = $superGlobal->get('personal_folder', 'SESSION');
548
    // Ensure consistency in array format
549
    $noAccessFolders = convertToArray($noAccessFolders);
550
    $userRoles = convertToArray($userRoles);
551
    $allowedFolders = convertToArray($allowedFolders);
552
    // Get list of folders depending on Roles
553
    $rows = DB::query(
554
        'SELECT *
555
        FROM ' . prefixTable('roles_values') . '
556
        WHERE role_id IN %li AND type IN %ls',
557
        $userRoles,
558
        ['W', 'ND', 'NE', 'NDNE', 'R']
559
    );
560
    foreach ($rows as $record) {
561
        if ($record['type'] === 'R') {
562
            array_push($readOnlyFolders, $record['folder_id']);
563
        } elseif (in_array($record['folder_id'], $allowedFolders) === false) {
564
            array_push($allowedFoldersByRoles, $record['folder_id']);
565
        }
566
    }
567
    $allowedFoldersByRoles = array_unique($allowedFoldersByRoles);
568
    $readOnlyFolders = array_unique($readOnlyFolders);
569
    // Clean arrays
570
    foreach ($allowedFoldersByRoles as $value) {
571
        if (($key = array_search($value, $readOnlyFolders)) !== false) {
572
            unset($readOnlyFolders[$key]);
573
        }
574
    }
575
576
    // Does this user is allowed to see other items
577
    $inc = 0;
578
    $rows = DB::query(
579
    'SELECT id, id_tree FROM ' . prefixTable('items') . '
580
        WHERE restricted_to LIKE %ss AND inactif = %s',
581
    $globalsUserId . ';',
582
    '0'
583
);
584
    foreach ($rows as $record) {
585
        // Exclude restriction on item if folder is fully accessible
586
        if (in_array($record['id_tree'], $allowedFolders) === false) {
587
            $restrictedFoldersForItems[$record['id_tree']][$inc] = $record['id'];
588
            ++$inc;
589
        }
590
    }
591
592
    // Check for the users roles if some specific rights exist on items
593
    $rows = DB::query(
594
        'SELECT i.id_tree, r.item_id
595
        FROM ' . prefixTable('items') . ' as i
596
        INNER JOIN ' . prefixTable('restriction_to_roles') . ' as r ON (r.item_id=i.id)
597
        WHERE r.role_id IN %li
598
        ORDER BY i.id_tree ASC',
599
        $userRoles
600
    );
601
    $inc = 0;
602
    foreach ($rows as $record) {
603
        if (isset($record['id_tree'])) {
604
            $foldersLimited[$record['id_tree']][$inc] = $record['item_id'];
605
            array_push($foldersLimitedFull, $record['item_id']);
606
            ++$inc;
607
        }
608
    }
609
610
    // Get list of Personal Folders
611
    if (
612
        isset($SETTINGS['enable_pf_feature']) === true && (int) $SETTINGS['enable_pf_feature'] === 1
613
        && isset($globalsPersonalFolders) === true && (int) $globalsPersonalFolders === 1
614
    ) {
615
        $persoFld = DB::queryfirstrow(
616
            'SELECT id
617
            FROM ' . prefixTable('nested_tree') . '
618
            WHERE title = %s AND personal_folder = %i',
619
            $globalsUserId,
620
            1
621
        );
622
        if (empty($persoFld['id']) === false) {
623
            if (in_array($persoFld['id'], $allowedFolders) === false) {
624
                array_push($personalFolders, $persoFld['id']);
625
                array_push($allowedFolders, $persoFld['id']);
626
                // get all descendants
627
                $ids = $tree->getChildren($persoFld['id'], false);
628
                foreach ($ids as $ident) {
629
                    if ((int) $ident->personal_folder === 1) {
630
                        array_push($allowedFolders, $ident->id);
631
                        array_push($personalFolders, $ident->id);
632
                    }
633
                }
634
            }
635
        }
636
    }
637
638
    // Exclude all other PF
639
    $where = new WhereClause('and');
640
    $where->add('personal_folder=%i', 1);
641
    if (
642
        isset($SETTINGS['enable_pf_feature']) === true && (int) $SETTINGS['enable_pf_feature'] === 1
643
        && isset($globalsPersonalFolders) === true && (int) $globalsPersonalFolders === 1
644
    ) {
645
        $where->add('title=%s', $globalsUserId);
646
        $where->negateLast();
647
    }
648
    $persoFlds = DB::query(
649
        'SELECT id
650
        FROM ' . prefixTable('nested_tree') . '
651
        WHERE %l',
652
        $where
653
    );
654
    foreach ($persoFlds as $persoFldId) {
655
        array_push($noAccessPersonalFolders, $persoFldId['id']);
656
    }
657
658
    // All folders visibles
659
    $allowedFolders = array_merge(
660
        $foldersLimitedFull,
661
        $allowedFoldersByRoles,
662
        $restrictedFoldersForItems,
663
        $readOnlyFolders
664
    );
665
    // Exclude from allowed folders all the specific user forbidden folders
666
    if (count($noAccessFolders) > 0) {
667
        $allowedFolders = array_diff($allowedFolders, $noAccessFolders);
668
    }
669
670
    // Return data
671
    $superGlobal->put('all_non_personal_folders', $allowedFolders, 'SESSION');
672
    $superGlobal->put('groupes_visibles', array_merge($allowedFolders, $personalFolders), 'SESSION');
673
    $superGlobal->put('read_only_folders', $readOnlyFolders, 'SESSION');
674
    $superGlobal->put('no_access_folders', $noAccessFolders, 'SESSION');
675
    $superGlobal->put('personal_folders', $personalFolders, 'SESSION');
676
    $superGlobal->put('list_folders_limited', $foldersLimited, 'SESSION');
677
    $superGlobal->put('list_folders_editable_by_role', $allowedFoldersByRoles, 'SESSION');
678
    $superGlobal->put('list_restricted_folders_for_items', $restrictedFoldersForItems, 'SESSION');
679
    $superGlobal->put('forbiden_pfs', $noAccessPersonalFolders, 'SESSION');
680
    $superGlobal->put(
681
    'all_folders_including_no_access',
682
    array_merge(
683
            $allowedFolders,
684
            $personalFolders,
685
            $noAccessFolders,
686
            $readOnlyFolders
687
        ),
688
    'SESSION'
689
);
690
    // Folders and Roles numbers
691
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('nested_tree') . '');
692
    $superGlobal->put('nb_folders', DB::count(), 'SESSION');
693
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('roles_title'));
694
    $superGlobal->put('nb_roles', DB::count(), 'SESSION');
695
    // check if change proposals on User's items
696
    if (isset($SETTINGS['enable_suggestion']) === true && (int) $SETTINGS['enable_suggestion'] === 1) {
697
        DB::query(
698
            'SELECT *
699
            FROM ' . prefixTable('items_change') . ' AS c
700
            LEFT JOIN ' . prefixTable('log_items') . ' AS i ON (c.item_id = i.id_item)
701
            WHERE i.action = %s AND i.id_user = %i',
702
            'at_creation',
703
            $globalsUserId
704
        );
705
        $superGlobal->put('nb_item_change_proposals', DB::count(), 'SESSION');
706
    } else {
707
        $superGlobal->put('nb_item_change_proposals', 0, 'SESSION');
708
    }
709
710
    return true;
711
}
712
713
/**
714
 * Update the CACHE table.
715
 *
716
 * @param string $action   What to do
717
 * @param array  $SETTINGS Teampass settings
718
 * @param string $ident    Ident format
719
 */
720
function updateCacheTable(string $action, array $SETTINGS, ?string $ident = null): void
721
{
722
    if ($action === 'reload') {
723
        // Rebuild full cache table
724
        cacheTableRefresh($SETTINGS);
725
    } elseif ($action === 'update_value' && is_null($ident) === false) {
726
        // UPDATE an item
727
        cacheTableUpdate($SETTINGS, $ident);
728
    } elseif ($action === 'add_value' && is_null($ident) === false) {
729
        // ADD an item
730
        cacheTableAdd($SETTINGS, $ident);
731
    } elseif ($action === 'delete_value' && is_null($ident) === false) {
732
        // DELETE an item
733
        DB::delete(prefixTable('cache'), 'id = %i', $ident);
734
    }
735
}
736
737
/**
738
 * Cache table - refresh.
739
 *
740
 * @param array $SETTINGS Teampass settings
741
 */
742
function cacheTableRefresh(array $SETTINGS): void
743
{
744
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
745
    //Connect to DB
746
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
747
    if (defined('DB_PASSWD_CLEAR') === false) {
748
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
749
    }
750
    DB::$host = DB_HOST;
751
    DB::$user = DB_USER;
752
    DB::$password = DB_PASSWD_CLEAR;
753
    DB::$dbName = DB_NAME;
754
    DB::$port = DB_PORT;
755
    DB::$encoding = DB_ENCODING;
756
    //Load Tree
757
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
758
    $tree->register();
759
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
760
    // truncate table
761
    DB::query('TRUNCATE TABLE ' . prefixTable('cache'));
762
    // reload date
763
    $rows = DB::query(
764
        'SELECT *
765
        FROM ' . prefixTable('items') . ' as i
766
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
767
        AND l.action = %s
768
        AND i.inactif = %i',
769
        'at_creation',
770
        0
771
    );
772
    foreach ($rows as $record) {
773
        if (empty($record['id_tree']) === false) {
774
            // Get all TAGS
775
            $tags = '';
776
            $itemTags = DB::query(
777
    'SELECT tag
778
                FROM ' . prefixTable('tags') . '
779
                WHERE item_id = %i AND tag != ""',
780
    $record['id']
781
);
782
            foreach ($itemTags as $itemTag) {
783
                $tags .= $itemTag['tag'] . ' ';
784
            }
785
786
            // Get renewal period
787
            $resNT = DB::queryfirstrow(
788
                'SELECT renewal_period
789
                FROM ' . prefixTable('nested_tree') . '
790
                WHERE id = %i',
791
                $record['id_tree']
792
            );
793
            // form id_tree to full foldername
794
            $folder = [];
795
            $arbo = $tree->getPath($record['id_tree'], true);
796
            foreach ($arbo as $elem) {
797
                // Check if title is the ID of a user
798
                if (is_numeric($elem->title) === true) {
799
                    // Is this a User id?
800
                    $user = DB::queryfirstrow(
801
                        'SELECT id, login
802
                        FROM ' . prefixTable('users') . '
803
                        WHERE id = %i',
804
                        $elem->title
805
                    );
806
                    if (count($user) > 0) {
807
                        $elem->title = $user['login'];
808
                    }
809
                }
810
                // Build path
811
                array_push($folder, stripslashes($elem->title));
812
            }
813
            // store data
814
            DB::insert(
815
                prefixTable('cache'),
816
                [
817
                    'id' => $record['id'],
818
                    'label' => $record['label'],
819
                    'description' => $record['description'] ?? '',
820
                    'url' => isset($record['url']) && ! empty($record['url']) ? $record['url'] : '0',
821
                    'tags' => $tags,
822
                    'id_tree' => $record['id_tree'],
823
                    'perso' => $record['perso'],
824
                    'restricted_to' => isset($record['restricted_to']) && ! empty($record['restricted_to']) ? $record['restricted_to'] : '0',
825
                    'login' => $record['login'] ?? '',
826
                    'folder' => implode(' > ', $folder),
827
                    'author' => $record['id_user'],
828
                    'renewal_period' => $resNT['renewal_period'] ?? '0',
829
                    'timestamp' => $record['date'],
830
                ]
831
            );
832
        }
833
    }
834
}
835
836
/**
837
 * Cache table - update existing value.
838
 *
839
 * @param array  $SETTINGS Teampass settings
840
 * @param string $ident    Ident format
841
 */
842
function cacheTableUpdate(array $SETTINGS, ?string $ident = null): void
843
{
844
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
845
    // Load superglobal
846
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
847
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
848
    //Connect to DB
849
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
850
    if (defined('DB_PASSWD_CLEAR') === false) {
851
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
852
    }
853
    DB::$host = DB_HOST;
854
    DB::$user = DB_USER;
855
    DB::$password = DB_PASSWD_CLEAR;
856
    DB::$dbName = DB_NAME;
857
    DB::$port = DB_PORT;
858
    DB::$encoding = DB_ENCODING;
859
    //Load Tree
860
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
861
    $tree->register();
862
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
863
    // get new value from db
864
    $data = DB::queryfirstrow(
865
        'SELECT label, description, id_tree, perso, restricted_to, login, url
866
        FROM ' . prefixTable('items') . '
867
        WHERE id=%i',
868
        $ident
869
    );
870
    // Get all TAGS
871
    $tags = '';
872
    $itemTags = DB::query(
873
    'SELECT tag
874
        FROM ' . prefixTable('tags') . '
875
        WHERE item_id = %i AND tag != ""',
876
    $ident
877
);
878
    foreach ($itemTags as $itemTag) {
879
        $tags .= $itemTag['tag'] . ' ';
880
    }
881
    // form id_tree to full foldername
882
    $folder = [];
883
    $arbo = $tree->getPath($data['id_tree'], true);
884
    foreach ($arbo as $elem) {
885
        // Check if title is the ID of a user
886
        if (is_numeric($elem->title) === true) {
887
            // Is this a User id?
888
            $user = DB::queryfirstrow(
889
                'SELECT id, login
890
                FROM ' . prefixTable('users') . '
891
                WHERE id = %i',
892
                $elem->title
893
            );
894
            if (count($user) > 0) {
895
                $elem->title = $user['login'];
896
            }
897
        }
898
        // Build path
899
        array_push($folder, stripslashes($elem->title));
900
    }
901
    // finaly update
902
    DB::update(
903
        prefixTable('cache'),
904
        [
905
            'label' => $data['label'],
906
            'description' => $data['description'],
907
            'tags' => $tags,
908
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
909
            'id_tree' => $data['id_tree'],
910
            'perso' => $data['perso'],
911
            'restricted_to' => isset($data['restricted_to']) && ! empty($data['restricted_to']) ? $data['restricted_to'] : '0',
912
            'login' => $data['login'] ?? '',
913
            'folder' => implode(' » ', $folder),
914
            'author' => $superGlobal->get('user_id', 'SESSION'),
915
        ],
916
        'id = %i',
917
        $ident
918
    );
919
}
920
921
/**
922
 * Cache table - add new value.
923
 *
924
 * @param array  $SETTINGS Teampass settings
925
 * @param string $ident    Ident format
926
 */
927
function cacheTableAdd(array $SETTINGS, ?string $ident = null): void
928
{
929
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
930
    // Load superglobal
931
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
932
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
933
    // Get superglobals
934
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
935
    //Connect to DB
936
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
937
    if (defined('DB_PASSWD_CLEAR') === false) {
938
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
939
    }
940
    DB::$host = DB_HOST;
941
    DB::$user = DB_USER;
942
    DB::$password = DB_PASSWD_CLEAR;
943
    DB::$dbName = DB_NAME;
944
    DB::$port = DB_PORT;
945
    DB::$encoding = DB_ENCODING;
946
    //Load Tree
947
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
948
    $tree->register();
949
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
950
    // get new value from db
951
    $data = DB::queryFirstRow(
952
        'SELECT i.label, i.description, i.id_tree as id_tree, i.perso, i.restricted_to, i.id, i.login, i.url, l.date
953
        FROM ' . prefixTable('items') . ' as i
954
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
955
        WHERE i.id = %i
956
        AND l.action = %s',
957
        $ident,
958
        'at_creation'
959
    );
960
    // Get all TAGS
961
    $tags = '';
962
    $itemTags = DB::query(
963
    'SELECT tag
964
        FROM ' . prefixTable('tags') . '
965
        WHERE item_id = %i AND tag != ""',
966
    $ident
967
);
968
    foreach ($itemTags as $itemTag) {
969
        $tags .= $itemTag['tag'] . ' ';
970
    }
971
    // form id_tree to full foldername
972
    $folder = [];
973
    $arbo = $tree->getPath($data['id_tree'], true);
974
    foreach ($arbo as $elem) {
975
        // Check if title is the ID of a user
976
        if (is_numeric($elem->title) === true) {
977
            // Is this a User id?
978
            $user = DB::queryfirstrow(
979
                'SELECT id, login
980
                FROM ' . prefixTable('users') . '
981
                WHERE id = %i',
982
                $elem->title
983
            );
984
            if (count($user) > 0) {
985
                $elem->title = $user['login'];
986
            }
987
        }
988
        // Build path
989
        array_push($folder, stripslashes($elem->title));
990
    }
991
    // finaly update
992
    DB::insert(
993
        prefixTable('cache'),
994
        [
995
            'id' => $data['id'],
996
            'label' => $data['label'],
997
            'description' => $data['description'],
998
            'tags' => isset($tags) && empty($tags) === false ? $tags : 'None',
999
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
1000
            'id_tree' => $data['id_tree'],
1001
            'perso' => isset($data['perso']) && empty($data['perso']) === false && $data['perso'] !== 'None' ? $data['perso'] : '0',
1002
            'restricted_to' => isset($data['restricted_to']) && empty($data['restricted_to']) === false ? $data['restricted_to'] : '0',
1003
            'login' => $data['login'] ?? '',
1004
            'folder' => implode(' » ', $folder),
1005
            'author' => $globalsUserId,
1006
            'timestamp' => $data['date'],
1007
        ]
1008
    );
1009
}
1010
1011
/**
1012
 * Do statistics.
1013
 *
1014
 * @param array $SETTINGS Teampass settings
1015
 *
1016
 * @return array
1017
 */
1018
function getStatisticsData(array $SETTINGS): array
1019
{
1020
    DB::query(
1021
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1022
        0
1023
    );
1024
    $counter_folders = DB::count();
1025
    DB::query(
1026
    'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1027
    1
1028
);
1029
    $counter_folders_perso = DB::count();
1030
    DB::query(
1031
    'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1032
    0
1033
);
1034
    $counter_items = DB::count();
1035
    DB::query(
1036
    'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1037
    1
1038
);
1039
    $counter_items_perso = DB::count();
1040
    DB::query(
1041
    'SELECT id FROM ' . prefixTable('users') . ''
1042
);
1043
    $counter_users = DB::count();
1044
    DB::query(
1045
    'SELECT id FROM ' . prefixTable('users') . ' WHERE admin = %i',
1046
    1
1047
);
1048
    $admins = DB::count();
1049
    DB::query(
1050
    'SELECT id FROM ' . prefixTable('users') . ' WHERE gestionnaire = %i',
1051
    1
1052
);
1053
    $managers = DB::count();
1054
    DB::query(
1055
    'SELECT id FROM ' . prefixTable('users') . ' WHERE read_only = %i',
1056
    1
1057
);
1058
    $readOnly = DB::count();
1059
    // list the languages
1060
    $usedLang = [];
1061
    $tp_languages = DB::query(
1062
    'SELECT name FROM ' . prefixTable('languages')
1063
);
1064
    foreach ($tp_languages as $tp_language) {
1065
        DB::query(
1066
            'SELECT * FROM ' . prefixTable('users') . ' WHERE user_language = %s',
1067
            $tp_language['name']
1068
        );
1069
        $usedLang[$tp_language['name']] = round((DB::count() * 100 / $counter_users), 0);
1070
    }
1071
1072
    // get list of ips
1073
    $usedIp = [];
1074
    $tp_ips = DB::query(
1075
    'SELECT user_ip FROM ' . prefixTable('users')
1076
);
1077
    foreach ($tp_ips as $ip) {
1078
        if (array_key_exists($ip['user_ip'], $usedIp)) {
1079
            $usedIp[$ip['user_ip']] = $usedIp[$ip['user_ip']] + 1;
1080
        } elseif (! empty($ip['user_ip']) && $ip['user_ip'] !== 'none') {
1081
            $usedIp[$ip['user_ip']] = 1;
1082
        }
1083
    }
1084
1085
    return [
1086
        'error' => '',
1087
        'stat_phpversion' => phpversion(),
1088
        'stat_folders' => $counter_folders,
1089
        'stat_folders_shared' => intval($counter_folders) - intval($counter_folders_perso),
1090
        'stat_items' => $counter_items,
1091
        'stat_items_shared' => intval($counter_items) - intval($counter_items_perso),
1092
        'stat_users' => $counter_users,
1093
        'stat_admins' => $admins,
1094
        'stat_managers' => $managers,
1095
        'stat_ro' => $readOnly,
1096
        'stat_kb' => $SETTINGS['enable_kb'],
1097
        'stat_pf' => $SETTINGS['enable_pf_feature'],
1098
        'stat_fav' => $SETTINGS['enable_favourites'],
1099
        'stat_teampassversion' => TP_VERSION_FULL,
1100
        'stat_ldap' => $SETTINGS['ldap_mode'],
1101
        'stat_agses' => $SETTINGS['agses_authentication_enabled'],
1102
        'stat_duo' => $SETTINGS['duo'],
1103
        'stat_suggestion' => $SETTINGS['enable_suggestion'],
1104
        'stat_api' => $SETTINGS['api'],
1105
        'stat_customfields' => $SETTINGS['item_extra_fields'],
1106
        'stat_syslog' => $SETTINGS['syslog_enable'],
1107
        'stat_2fa' => $SETTINGS['google_authentication'],
1108
        'stat_stricthttps' => $SETTINGS['enable_sts'],
1109
        'stat_mysqlversion' => DB::serverVersion(),
1110
        'stat_languages' => $usedLang,
1111
        'stat_country' => $usedIp,
1112
    ];
1113
}
1114
1115
/**
1116
 * Permits to send an email.
1117
 *
1118
 * @param string $subject     email subject
1119
 * @param string $textMail    email message
1120
 * @param string $email       email
1121
 * @param array  $SETTINGS    settings
1122
 * @param string $textMailAlt email message alt
1123
 * @param bool   $silent      no errors
1124
 *
1125
 * @return string some json info
1126
 */
1127
function sendEmail(
1128
    string $subject,
1129
    string $textMail,
1130
    string $email,
1131
    array $SETTINGS,
1132
    ?string $textMailAlt = null,
1133
    bool $silent = true
1134
): string {
1135
    // CAse where email not defined
1136
    if ($email === 'none' || empty($email) === true) {
1137
        return '"error":"" , "message":"' . langHdl('forgot_my_pw_email_sent') . '"';
1138
    }
1139
1140
    // Load settings
1141
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
1142
    // Load superglobal
1143
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1144
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1145
    // Get user language
1146
    include_once $SETTINGS['cpassman_dir'] . '/includes/language/' . $superGlobal->get('user_language', 'SESSION') . '.php';
1147
    // Load library
1148
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1149
    // load PHPMailer
1150
    $mail = new SplClassLoader('PHPMailer\PHPMailer', '../includes/libraries');
1151
    $mail->register();
1152
    $mail = new PHPMailer\PHPMailer\PHPMailer(true);
1153
    try {
1154
        // send to user
1155
        $mail->setLanguage('en', $SETTINGS['cpassman_dir'] . '/includes/libraries/PHPMailer/PHPMailer/language/');
1156
        $mail->SMTPDebug = 0;
1157
        //value 1 can be used to debug - 4 for debuging connections
1158
        $mail->Port = $SETTINGS['email_port'];
1159
        //COULD BE USED
1160
        $mail->CharSet = 'utf-8';
1161
        $mail->SMTPSecure = $SETTINGS['email_security'] === 'tls'
1162
            || $SETTINGS['email_security'] === 'ssl' ? $SETTINGS['email_security'] : '';
1163
        $mail->SMTPAutoTLS = $SETTINGS['email_security'] === 'tls'
1164
            || $SETTINGS['email_security'] === 'ssl' ? true : false;
1165
        $mail->SMTPOptions = [
1166
    'ssl' => [
1167
        'verify_peer' => false,
1168
        'verify_peer_name' => false,
1169
        'allow_self_signed' => true,
1170
    ],
1171
];
1172
        $mail->isSmtp();
1173
        // send via SMTP
1174
        $mail->Host = $SETTINGS['email_smtp_server'];
1175
        // SMTP servers
1176
        $mail->SMTPAuth = (int) $SETTINGS['email_smtp_auth'] === 1 ? true : false;
1177
        // turn on SMTP authentication
1178
        $mail->Username = $SETTINGS['email_auth_username'];
1179
        // SMTP username
1180
        $mail->Password = $SETTINGS['email_auth_pwd'];
1181
        // SMTP password
1182
        $mail->From = $SETTINGS['email_from'];
1183
        $mail->FromName = $SETTINGS['email_from_name'];
1184
        // Prepare for each person
1185
        foreach (array_filter(explode(',', $email)) as $dest) {
1186
            $mail->addAddress($dest);
1187
        }
1188
1189
        // Prepare HTML
1190
        $text_html = emailBody($textMail);
1191
        $mail->WordWrap = 80;
1192
        // set word wrap
1193
        $mail->isHtml(true);
1194
        // send as HTML
1195
        $mail->Subject = $subject;
1196
        $mail->Body = $text_html;
1197
        $mail->AltBody = is_null($textMailAlt) === false ? $textMailAlt : '';
1198
        // send email
1199
        $mail->send();
1200
        $mail->smtpClose();
1201
        if ($silent === false) {
1202
            return json_encode(
1203
                [
1204
                    'error' => false,
1205
                    'message' => langHdl('forgot_my_pw_email_sent'),
1206
                ]
1207
            );
1208
        }
0 ignored issues
show
Bug Best Practice introduced by
The function implicitly returns null when the if condition on line 1201 is false. This is incompatible with the type-hinted return string. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
1209
    } catch (Exception $e) {
1210
        if ($silent === false) {
1211
            return json_encode(
1212
                [
1213
                    'error' => true,
1214
                    'message' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1215
                ]
1216
            );
1217
        }
0 ignored issues
show
Bug Best Practice introduced by
The function implicitly returns null when the if condition on line 1210 is false. This is incompatible with the type-hinted return string. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
1218
    }
1219
}
1220
1221
/**
1222
 * Returns the email body.
1223
 *
1224
 * @param string $textMail Text for the email
1225
 */
1226
function emailBody(string $textMail): string
1227
{
1228
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1229
    w3.org/TR/html4/loose.dtd"><html>
1230
    <head><title>Email Template</title>
1231
    <style type="text/css">
1232
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1233
    </style></head>
1234
    <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">
1235
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1236
    <tr><td style="border-collapse: collapse;"><br>
1237
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1238
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1239
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1240
        </td></tr></table></td>
1241
    </tr>
1242
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1243
        <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;">
1244
        <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;">
1245
        <br><div style="float:right;">' .
1246
        $textMail .
1247
        '<br><br></td></tr></table>
1248
    </td></tr></table>
1249
    <br></body></html>';
1250
}
1251
1252
/**
1253
 * Generate a Key.
1254
 */
1255
function generateKey(): string
1256
{
1257
    return substr(md5(rand() . rand()), 0, 15);
1258
}
1259
1260
/**
1261
 * Convert date to timestamp.
1262
 *
1263
 * @param string $date     The date
1264
 * @param array  $SETTINGS Teampass settings
1265
 */
1266
function dateToStamp(string $date, array $SETTINGS): string
1267
{
1268
    $date = date_parse_from_format($SETTINGS['date_format'], $date);
1269
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1270
        return mktime(23, 59, 59, $date['month'], $date['day'], $date['year']);
1271
    }
1272
    return '';
1273
1274
1275
}
1276
1277
/**
1278
 * Is this a date.
1279
 *
1280
 * @param string $date Date
1281
 */
1282
function isDate(string $date): bool
1283
{
1284
    return strtotime($date) !== false;
1285
}
1286
1287
/**
1288
 * Check if isUTF8().
1289
 *
1290
 * @return int is the string in UTF8 format
1291
 */
1292
function isUTF8($string): int
1293
{
1294
    if (is_array($string) === true) {
1295
        $string = $string['string'];
1296
    }
1297
1298
    return preg_match(
1299
        '%^(?:
1300
        [\x09\x0A\x0D\x20-\x7E] # ASCII
1301
        | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
1302
        | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
1303
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
1304
        | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
1305
        | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
1306
        | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
1307
        | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
1308
        )*$%xs',
1309
        $string
1310
    );
1311
}
1312
1313
/**
1314
 * Prepare an array to UTF8 format before JSON_encode.
1315
 *
1316
 * @param array $array Array of values
1317
 *
1318
 * @return array
1319
 */
1320
function utf8Converter(array $array): array
1321
{
1322
    array_walk_recursive(
1323
        $array,
1324
        static function (&$item): void {
1325
            if (mb_detect_encoding($item, 'utf-8', true) === false) {
1326
                $item = utf8_encode($item);
1327
            }
1328
        }
1329
    );
1330
    return $array;
1331
}
1332
1333
/**
1334
 * Permits to prepare data to be exchanged.
1335
 *
1336
 * @param array|string $data Text
1337
 * @param string       $type Parameter
1338
 * @param string       $key  Optional key
1339
 *
1340
 * @return resource|string|array
1341
 */
1342
function prepareExchangedData(array|string $data, string $type, ?string $key = null)
1343
{
1344
    if (file_exists('../includes/config/tp.config.php')) {
1345
        include '../includes/config/tp.config.php';
1346
    } elseif (file_exists('./includes/config/tp.config.php')) {
1347
        include './includes/config/tp.config.php';
1348
    } elseif (file_exists('../../includes/config/tp.config.php')) {
1349
        include '../../includes/config/tp.config.php';
1350
    } else {
1351
        throw new Exception("Error file '/includes/config/tp.config.php' not exists", 1);
1352
    }
1353
1354
    if (isset($SETTINGS) === false) {
1355
        return 'ERROR';
1356
    }
1357
1358
    // Load superglobal
1359
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1360
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1361
    // Get superglobals
1362
    if ($key !== null) {
1363
        $superGlobal->put('key', $key, 'SESSION');
1364
        $globalsKey = $key;
1365
    } else {
1366
        $globalsKey = $superGlobal->get('key', 'SESSION');
1367
    }
1368
1369
    //load ClassLoader
1370
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1371
    //Load AES
1372
    $aes = new SplClassLoader('Encryption\Crypt', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1373
    $aes->register();
1374
    if ($type === 'encode' && is_array($data) === true) {
1375
        // Ensure UTF8 format
1376
        $data = utf8Converter($data);
1377
        // Now encode
1378
        if (isset($SETTINGS['encryptClientServer'])
1379
            && $SETTINGS['encryptClientServer'] === '0'
1380
        ) {
1381
            return json_encode(
1382
                $data,
1383
                JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1384
            );
1385
        }
1386
        return Encryption\Crypt\aesctr::encrypt(
1387
                json_encode(
1388
                    $data,
1389
                    JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1390
                ),
1391
                $globalsKey,
1392
                256
1393
            );
1394
1395
    
1396
    } elseif ($type === 'decode' && is_array($data) === false) {
1397
        if (isset($SETTINGS['encryptClientServer'])
1398
            && $SETTINGS['encryptClientServer'] === '0'
1399
        ) {
1400
            return json_decode(
1401
                /** @scrutinizer ignore-type */ 
1402
                (string) $data,
1403
                true
1404
            );
1405
        }
1406
        return json_decode(
1407
                Encryption\Crypt\aesctr::decrypt(
1408
                    /** @scrutinizer ignore-type */ 
1409
                    (string) $data,
1410
                    $globalsKey,
1411
                    256
1412
                ),
1413
                true
1414
            );
1415
1416
    
1417
    }
1418
}
1419
1420
/**
1421
 * Create a thumbnail.
1422
 *
1423
 * @param string  $src           Source
1424
 * @param string  $dest          Destination
1425
 * @param int $desired_width Size of width
1426
 */
1427
function makeThumbnail(string $src, string $dest, int $desired_width)
1428
{
1429
    /* read the source image */
1430
    if (is_file($src) === true && mime_content_type($src) === 'image/png') {
1431
        $source_image = imagecreatefrompng($src);
1432
        if ($source_image === false) {
1433
            return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1434
        }
1435
    } else {
1436
        return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1437
    }
1438
1439
    // Get height and width
1440
    $width = imagesx($source_image);
1441
    $height = imagesy($source_image);
1442
    /* find the "desired height" of this thumbnail, relative to the desired width  */
1443
    $desired_height = (int) floor($height * $desired_width / $width);
1444
    /* create a new, "virtual" image */
1445
    $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
1446
    if ($virtual_image === false) {
1447
        return false;
1448
    }
1449
    /* copy source image at a resized size */
1450
    imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
1451
    /* create the physical thumbnail image to its destination */
1452
    imagejpeg($virtual_image, $dest);
1453
}
1454
1455
/**
1456
 * Check table prefix in SQL query.
1457
 *
1458
 * @param string $table Table name
1459
 */
1460
function prefixTable(string $table): string
1461
{
1462
    $safeTable = htmlspecialchars(DB_PREFIX . $table);
1463
    if (! empty($safeTable)) {
1464
        // sanitize string
1465
        return $safeTable;
1466
    }
1467
        // stop error no table
1468
    return 'table_not_exists';
1469
1470
1471
}
1472
1473
/**
1474
 * GenerateCryptKey
1475
 *
1476
 * @param int     $size      Length
1477
 * @param bool $secure Secure
1478
 * @param bool $numerals Numerics
1479
 * @param bool $uppercase Uppercase letters
1480
 * @param bool $symbols Symbols
1481
 * @param bool $lowercase Lowercase
1482
 * @param array   $SETTINGS  SETTINGS
1483
 */
1484
function GenerateCryptKey(
1485
    int $size = 10,
1486
    bool $secure = false,
1487
    bool $numerals = false,
1488
    bool $uppercase = false,
1489
    bool $symbols = false,
1490
    bool $lowercase = false,
1491
    array $SETTINGS = []
1492
): string {
1493
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1494
    $generator = new SplClassLoader('PasswordGenerator\Generator', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1495
    $generator->register();
1496
    $generator = new PasswordGenerator\Generator\ComputerPasswordGenerator();
1497
    // Is PHP7 being used?
1498
    if (version_compare(PHP_VERSION, '7.0.0', '>=')) {
1499
        $php7generator = new SplClassLoader('PasswordGenerator\RandomGenerator', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1500
        $php7generator->register();
1501
        $generator->setRandomGenerator(new PasswordGenerator\RandomGenerator\Php7RandomGenerator());
1502
    }
1503
1504
    // Manage size
1505
    $generator->setLength((int) $size);
1506
    if ($secure === true) {
1507
        $generator->setSymbols(true);
1508
        $generator->setLowercase(true);
1509
        $generator->setUppercase(true);
1510
        $generator->setNumbers(true);
1511
    } else {
1512
        $generator->setLowercase($lowercase);
1513
        $generator->setUppercase($uppercase);
1514
        $generator->setNumbers($numerals);
1515
        $generator->setSymbols($symbols);
1516
    }
1517
1518
    return $generator->generatePasswords()[0];
1519
}
1520
1521
/*
1522
* Send sysLOG message
1523
* @param string $message
1524
* @param string $host
1525
*/
1526
function send_syslog($message, $host, $port, $component = 'teampass'): void
1527
{
1528
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1529
    $syslog_message = '<123>' . date('M d H:i:s ') . $component . ': ' . $message;
1530
    socket_sendto($sock, $syslog_message, strlen($syslog_message), 0, $host, $port);
1531
    socket_close($sock);
1532
}
1533
1534
/**
1535
 * Permits to log events into DB
1536
 *
1537
 * @param array  $SETTINGS Teampass settings
1538
 * @param string $type     Type
1539
 * @param string $label    Label
1540
 * @param string $who      Who
1541
 * @param string $login    Login
1542
 * @param string $field_1  Field
1543
 */
1544
function logEvents(array $SETTINGS, string $type, string $label, string $who, ?string $login = null, ?string $field_1 = null): void
1545
{
1546
    if (empty($who)) {
1547
        $who = getClientIpServer();
1548
    }
1549
1550
    // include librairies & connect to DB
1551
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1552
    if (defined('DB_PASSWD_CLEAR') === false) {
1553
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1554
    }
1555
    DB::$host = DB_HOST;
1556
    DB::$user = DB_USER;
1557
    DB::$password = DB_PASSWD_CLEAR;
1558
    DB::$dbName = DB_NAME;
1559
    DB::$port = DB_PORT;
1560
    DB::$encoding = DB_ENCODING;
1561
    DB::insert(
1562
    prefixTable('log_system'),
1563
    [
1564
            'type' => $type,
1565
            'date' => time(),
1566
            'label' => $label,
1567
            'qui' => $who,
1568
            'field_1' => $field_1 === null ? '' : $field_1,
1569
        ]
1570
);
1571
    // If SYSLOG
1572
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1573
        if ($type === 'user_mngt') {
1574
            send_syslog(
1575
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1576
                $SETTINGS['syslog_host'],
1577
                $SETTINGS['syslog_port'],
1578
                'teampass'
1579
            );
1580
        } else {
1581
            send_syslog(
1582
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1583
                $SETTINGS['syslog_host'],
1584
                $SETTINGS['syslog_port'],
1585
                'teampass'
1586
            );
1587
        }
1588
    }
1589
}
1590
1591
/**
1592
 * Log events.
1593
 *
1594
 * @param array  $SETTINGS        Teampass settings
1595
 * @param int    $item_id         Item id
1596
 * @param string $item_label      Item label
1597
 * @param int    $id_user         User id
1598
 * @param string $action          Code for reason
1599
 * @param string $login           User login
1600
 * @param string $raison          Code for reason
1601
 * @param string $encryption_type Encryption on
1602
 */
1603
function logItems(
1604
    array $SETTINGS,
1605
    int $item_id,
1606
    string $item_label,
1607
    int $id_user,
1608
    string $action,
1609
    ?string $login = null,
1610
    ?string $raison = null,
1611
    ?string $encryption_type = null
1612
): void {
1613
    // include librairies & connect to DB
1614
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1615
    if (defined('DB_PASSWD_CLEAR') === false) {
1616
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1617
    }
1618
    DB::$host = DB_HOST;
1619
    DB::$user = DB_USER;
1620
    DB::$password = DB_PASSWD_CLEAR;
1621
    DB::$dbName = DB_NAME;
1622
    DB::$port = DB_PORT;
1623
    DB::$encoding = DB_ENCODING;
1624
    // Insert log in DB
1625
    DB::insert(
1626
        prefixTable('log_items'),
1627
        [
1628
            'id_item' => $item_id,
1629
            'date' => time(),
1630
            'id_user' => $id_user,
1631
            'action' => $action,
1632
            'raison' => $raison,
1633
            'raison_iv' => '',
1634
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1635
        ]
1636
    );
1637
    // Timestamp the last change
1638
    if ($action === 'at_creation' || $action === 'at_modifiation' || $action === 'at_delete' || $action === 'at_import') {
1639
        DB::update(
1640
            prefixTable('misc'),
1641
            [
1642
                'valeur' => time(),
1643
            ],
1644
            'type = %s AND intitule = %s',
1645
            'timestamp',
1646
            'last_item_change'
1647
        );
1648
    }
1649
1650
    // SYSLOG
1651
    if (isset($SETTINGS['syslog_enable']) === true && $SETTINGS['syslog_enable'] === '1') {
1652
        // Extract reason
1653
        $attribute = is_null($raison) === true ? '' : explode(' : ', $raison);
1654
        // Get item info if not known
1655
        if (empty($item_label) === true) {
1656
            $dataItem = DB::queryfirstrow(
1657
                'SELECT id, id_tree, label
1658
                FROM ' . prefixTable('items') . '
1659
                WHERE id = %i',
1660
                $item_id
1661
            );
1662
            $item_label = $dataItem['label'];
1663
        }
1664
1665
        send_syslog(
1666
            'action=' . str_replace('at_', '', $action) .
1667
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1668
                ' itemno=' . $item_id .
1669
                ' user=' . is_null($login) === true ? '' : addslashes((string) $login) .
1670
                ' itemname="' . addslashes($item_label) . '"',
1671
            $SETTINGS['syslog_host'],
1672
            $SETTINGS['syslog_port'],
1673
            'teampass'
1674
        );
1675
    }
1676
1677
    // send notification if enabled
1678
    notifyOnChange($item_id, $action, $SETTINGS);
1679
}
1680
1681
/**
1682
 * If enabled, then notify admin/manager.
1683
 *
1684
 * @param int    $item_id  Item id
1685
 * @param string $action   Action to do
1686
 * @param array  $SETTINGS Teampass settings
1687
 */
1688
function notifyOnChange(int $item_id, string $action, array $SETTINGS): void
1689
{
1690
    if (
1691
        isset($SETTINGS['enable_email_notification_on_item_shown']) === true
1692
        && (int) $SETTINGS['enable_email_notification_on_item_shown'] === 1
1693
        && $action === 'at_shown'
1694
    ) {
1695
        // Load superglobal
1696
        include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1697
        $superGlobal = new protect\SuperGlobal\SuperGlobal();
1698
        // Get superglobals
1699
        $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1700
        $globalsName = $superGlobal->get('name', 'SESSION');
1701
        $globalsNotifiedEmails = $superGlobal->get('listNotificationEmails', 'SESSION');
1702
        // Get info about item
1703
        $dataItem = DB::queryfirstrow(
1704
            'SELECT id, id_tree, label
1705
            FROM ' . prefixTable('items') . '
1706
            WHERE id = %i',
1707
            $item_id
1708
        );
1709
        $item_label = $dataItem['label'];
1710
        // send back infos
1711
        DB::insert(
1712
            prefixTable('emails'),
1713
            [
1714
                'timestamp' => time(),
1715
                'subject' => langHdl('email_on_open_notification_subject'),
1716
                'body' => str_replace(
1717
                    ['#tp_user#', '#tp_item#', '#tp_link#'],
1718
                    [
1719
                        addslashes($globalsName . ' ' . $globalsLastname),
1720
                        addslashes($item_label),
1721
                        $SETTINGS['cpassman_url'] . '/index.php?page=items&group=' . $dataItem['id_tree'] . '&id=' . $item_id,
1722
                    ],
1723
                    langHdl('email_on_open_notification_mail')
1724
                ),
1725
                'receivers' => $globalsNotifiedEmails,
1726
                'status' => '',
1727
            ]
1728
        );
1729
    }
1730
}
1731
1732
/**
1733
 * Prepare notification email to subscribers.
1734
 *
1735
 * @param int    $item_id  Item id
1736
 * @param string $label    Item label
1737
 * @param array  $changes  List of changes
1738
 * @param array  $SETTINGS Teampass settings
1739
 */
1740
function notifyChangesToSubscribers(int $item_id, string $label, array $changes, array $SETTINGS): void
1741
{
1742
    // Load superglobal
1743
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1744
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1745
    // Get superglobals
1746
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
1747
    $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1748
    $globalsName = $superGlobal->get('name', 'SESSION');
1749
    // send email to user that what to be notified
1750
    $notification = DB::queryOneColumn(
1751
        'email',
1752
        'SELECT *
1753
        FROM ' . prefixTable('notification') . ' AS n
1754
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1755
        WHERE n.item_id = %i AND n.user_id != %i',
1756
        $item_id,
1757
        $globalsUserId
1758
    );
1759
    if (DB::count() > 0) {
1760
        // Prepare path
1761
        $path = geItemReadablePath($item_id, '', $SETTINGS);
1762
        // Get list of changes
1763
        $htmlChanges = '<ul>';
1764
        foreach ($changes as $change) {
1765
            $htmlChanges .= '<li>' . $change . '</li>';
1766
        }
1767
        $htmlChanges .= '</ul>';
1768
        // send email
1769
        DB::insert(
1770
            prefixTable('emails'),
1771
            [
1772
                'timestamp' => time(),
1773
                'subject' => langHdl('email_subject_item_updated'),
1774
                'body' => str_replace(
1775
                    ['#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'],
1776
                    [$label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges],
1777
                    langHdl('email_body_item_updated')
1778
                ),
1779
                'receivers' => implode(',', $notification),
1780
                'status' => '',
1781
            ]
1782
        );
1783
    }
1784
}
1785
1786
/**
1787
 * Returns the Item + path.
1788
 *
1789
 * @param int    $id_tree  Node id
1790
 * @param string $label    Label
1791
 * @param array  $SETTINGS TP settings
1792
 */
1793
function geItemReadablePath(int $id_tree, string $label, array $SETTINGS): string
1794
{
1795
    // Class loader
1796
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1797
    //Load Tree
1798
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1799
    $tree->register();
1800
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1801
    $arbo = $tree->getPath($id_tree, true);
1802
    $path = '';
1803
    foreach ($arbo as $elem) {
1804
        if (empty($path) === true) {
1805
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
1806
        } else {
1807
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
1808
        }
1809
    }
1810
1811
    // Build text to show user
1812
    if (empty($label) === false) {
1813
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
1814
    }
1815
    return empty($path) === true ? '' : $path;
1816
1817
    
1818
}
1819
1820
/**
1821
 * Get the client ip address.
1822
 *
1823
 * @return string IP address
1824
 */
1825
function getClientIpServer(): string
1826
{
1827
    if (getenv('HTTP_CLIENT_IP')) {
1828
        $ipaddress = getenv('HTTP_CLIENT_IP');
1829
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
1830
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
1831
    } elseif (getenv('HTTP_X_FORWARDED')) {
1832
        $ipaddress = getenv('HTTP_X_FORWARDED');
1833
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
1834
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
1835
    } elseif (getenv('HTTP_FORWARDED')) {
1836
        $ipaddress = getenv('HTTP_FORWARDED');
1837
    } elseif (getenv('REMOTE_ADDR')) {
1838
        $ipaddress = getenv('REMOTE_ADDR');
1839
    } else {
1840
        $ipaddress = 'UNKNOWN';
1841
    }
1842
1843
    return $ipaddress;
1844
}
1845
1846
/**
1847
 * Escape all HTML, JavaScript, and CSS.
1848
 *
1849
 * @param string $input    The input string
1850
 * @param string $encoding Which character encoding are we using?
1851
 */
1852
function noHTML(string $input, string $encoding = 'UTF-8'): string
1853
{
1854
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
1855
}
1856
1857
/**
1858
 * Permits to handle the Teampass config file
1859
 * $action accepts "rebuild" and "update"
1860
 *
1861
 * @param string $action   Action to perform
1862
 * @param array  $SETTINGS Teampass settings
1863
 * @param string $field    Field to refresh
1864
 * @param string $value    Value to set
1865
 */
1866
function handleConfigFile(string $action, array $SETTINGS, ?string $field = null, ?string $value = null): string|bool
1867
{
1868
    $tp_config_file = $SETTINGS['cpassman_dir'] . '/includes/config/tp.config.php';
1869
    // include librairies & connect to DB
1870
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1871
    if (defined('DB_PASSWD_CLEAR') === false) {
1872
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1873
    }
1874
    DB::$host = DB_HOST;
1875
    DB::$user = DB_USER;
1876
    DB::$password = DB_PASSWD_CLEAR;
1877
    DB::$dbName = DB_NAME;
1878
    DB::$port = DB_PORT;
1879
    DB::$encoding = DB_ENCODING;
1880
    if (file_exists($tp_config_file) === false || $action === 'rebuild') {
1881
        // perform a copy
1882
        if (file_exists($tp_config_file)) {
1883
            if (! copy($tp_config_file, $tp_config_file . '.' . date('Y_m_d_His', time()))) {
1884
                return "ERROR: Could not copy file '" . $tp_config_file . "'";
1885
            }
1886
        }
1887
1888
        // regenerate
1889
        $data = [];
1890
        $data[0] = "<?php\n";
1891
        $data[1] = "global \$SETTINGS;\n";
1892
        $data[2] = "\$SETTINGS = array (\n";
1893
        $rows = DB::query(
1894
    'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s',
1895
    'admin'
1896
);
1897
        foreach ($rows as $record) {
1898
            array_push($data, "    '" . $record['intitule'] . "' => '" . $record['valeur'] . "',\n");
1899
        }
1900
        array_push($data, ");\n");
1901
        $data = array_unique($data);
1902
    // ---
1903
    } elseif ($action === 'update' && empty($field) === false) {
1904
        $data = file($tp_config_file);
1905
        $inc = 0;
1906
        $bFound = false;
1907
        foreach ($data as $line) {
1908
            if (stristr($line, ');')) {
1909
                break;
1910
            }
1911
1912
            if (stristr($line, "'" . $field . "' => '")) {
1913
                $data[$inc] = "    '" . $field . "' => '" . filter_var($value, FILTER_SANITIZE_STRING) . "',\n";
1914
                $bFound = true;
1915
                break;
1916
            }
1917
            ++$inc;
1918
        }
1919
        if ($bFound === false) {
1920
            $data[$inc] = "    '" . $field . "' => '" . filter_var($value, FILTER_SANITIZE_STRING) . "',\n);\n";
1921
        }
1922
    }
1923
1924
    // update file
1925
    file_put_contents($tp_config_file, implode('', $data ?? []));
1926
    return true;
1927
}
1928
1929
/**
1930
 * Permits to replace &#92; to permit correct display
1931
 *
1932
 * @param string $input Some text
1933
 */
1934
function handleBackslash(string $input): string
1935
{
1936
    return str_replace('&amp;#92;', '&#92;', $input);
1937
}
1938
1939
/*
1940
** Permits to loas settings
1941
*/
1942
function loadSettings(): void
1943
{
1944
    global $SETTINGS;
1945
    /* LOAD CPASSMAN SETTINGS */
1946
    if (! isset($SETTINGS['loaded']) || $SETTINGS['loaded'] !== 1) {
1947
        $SETTINGS['duplicate_folder'] = 0;
1948
        //by default, this is set to 0;
1949
        $SETTINGS['duplicate_item'] = 0;
1950
        //by default, this is set to 0;
1951
        $SETTINGS['number_of_used_pw'] = 5;
1952
        //by default, this value is set to 5;
1953
        $settings = [];
1954
        $rows = DB::query(
1955
    'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
1956
    [
1957
                'type' => 'admin',
1958
                'type2' => 'settings',
1959
            ]
1960
);
1961
        foreach ($rows as $record) {
1962
            if ($record['type'] === 'admin') {
1963
                $SETTINGS[$record['intitule']] = $record['valeur'];
1964
            } else {
1965
                $settings[$record['intitule']] = $record['valeur'];
1966
            }
1967
        }
1968
        $SETTINGS['loaded'] = 1;
1969
        $SETTINGS['default_session_expiration_time'] = 5;
1970
    }
1971
}
1972
1973
/*
1974
** check if folder has custom fields.
1975
** Ensure that target one also has same custom fields
1976
*/
1977
function checkCFconsistency($source_id, $target_id)
1978
{
1979
    $source_cf = [];
1980
    $rows = DB::QUERY(
1981
    'SELECT id_category
1982
        FROM ' . prefixTable('categories_folders') . '
1983
        WHERE id_folder = %i',
1984
    $source_id
1985
);
1986
    foreach ($rows as $record) {
1987
        array_push($source_cf, $record['id_category']);
1988
    }
1989
1990
    $target_cf = [];
1991
    $rows = DB::QUERY(
1992
    'SELECT id_category
1993
        FROM ' . prefixTable('categories_folders') . '
1994
        WHERE id_folder = %i',
1995
    $target_id
1996
);
1997
    foreach ($rows as $record) {
1998
        array_push($target_cf, $record['id_category']);
1999
    }
2000
2001
    $cf_diff = array_diff($source_cf, $target_cf);
2002
    if (count($cf_diff) > 0) {
2003
        return false;
2004
    }
2005
2006
    return true;
2007
}
2008
2009
/**
2010
 * Will encrypte/decrypt a fil eusing Defuse.
2011
 *
2012
 * @param string $type        can be either encrypt or decrypt
2013
 * @param string $source_file path to source file
2014
 * @param string $target_file path to target file
2015
 * @param array  $SETTINGS    Settings
2016
 * @param string $password    A password
2017
 */
2018
function prepareFileWithDefuse(
2019
    string $type,
2020
    string $source_file,
2021
    string $target_file,
2022
    array $SETTINGS,
2023
    ?string $password = null
2024
): string|bool {
2025
    // Load AntiXSS
2026
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/AntiXSS.php';
2027
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/ASCII.php';
2028
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/UTF8.php';
2029
    $antiXss = new voku\helper\AntiXSS();
2030
    // Protect against bad inputs
2031
    if (is_array($source_file) === true || is_array($target_file) === true) {
2032
        return 'error_cannot_be_array';
2033
    }
2034
2035
    // Sanitize
2036
    $source_file = $antiXss->xss_clean($source_file);
2037
    $target_file = $antiXss->xss_clean($target_file);
2038
    if (empty($password) === true || is_null($password) === true) {
2039
        // get KEY to define password
2040
        $ascii_key = file_get_contents(SECUREPATH . '/teampass-seckey.txt');
2041
        $password = \Defuse\Crypto\Key::loadFromAsciiSafeString($ascii_key);
2042
    }
2043
2044
    $err = '';
2045
    if ($type === 'decrypt') {
2046
        // Decrypt file
2047
        $err = defuseFileDecrypt(
2048
            $source_file,
2049
            $target_file,
2050
            $SETTINGS, /** @scrutinizer ignore-type */ 
2051
            $password
2052
        );
2053
    // ---
2054
    } elseif ($type === 'encrypt') {
2055
        // Encrypt file
2056
        $err = defuseFileEncrypt(
2057
            $source_file,
2058
            $target_file,
2059
            $SETTINGS, /** @scrutinizer ignore-type */ 
2060
            $password
2061
        );
2062
    }
2063
2064
    // return error
2065
    return empty($err) === false ? $err : '';
2066
}
2067
2068
/**
2069
 * Encrypt a file with Defuse.
2070
 *
2071
 * @param string $source_file path to source file
2072
 * @param string $target_file path to target file
2073
 * @param array  $SETTINGS    Settings
2074
 * @param string $password    A password
2075
 */
2076
function defuseFileEncrypt(
2077
    string $source_file,
2078
    string $target_file,
2079
    array $SETTINGS,
2080
    ?string $password = null
2081
): string|bool {
2082
    // load PhpEncryption library
2083
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2084
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2085
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2086
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2087
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2088
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2089
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2090
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2091
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2092
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2093
    try {
2094
        \Defuse\Crypto\File::encryptFileWithPassword(
2095
            $source_file,
2096
            $target_file,
2097
            $password
2098
        );
2099
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2100
        $err = 'wrong_key';
2101
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2102
        $err = $ex;
2103
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2104
        $err = $ex;
2105
    }
2106
2107
    // return error
2108
    return empty($err) === false ? $err : true;
2109
}
2110
2111
/**
2112
 * Decrypt a file with Defuse.
2113
 *
2114
 * @param string $source_file path to source file
2115
 * @param string $target_file path to target file
2116
 * @param array  $SETTINGS    Settings
2117
 * @param string $password    A password
2118
 */
2119
function defuseFileDecrypt(
2120
    string $source_file,
2121
    string $target_file,
2122
    array $SETTINGS,
2123
    ?string $password = null
2124
): string|bool {
2125
    // load PhpEncryption library
2126
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2127
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2128
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2129
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2130
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2131
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2132
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2133
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2134
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2135
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2136
    try {
2137
        \Defuse\Crypto\File::decryptFileWithPassword(
2138
            $source_file,
2139
            $target_file,
2140
            $password
2141
        );
2142
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2143
        $err = 'wrong_key';
2144
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2145
        $err = $ex;
2146
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2147
        $err = $ex;
2148
    }
2149
2150
    // return error
2151
    return empty($err) === false ? $err : true;
2152
}
2153
2154
/*
2155
* NOT TO BE USED
2156
*/
2157
/**
2158
 * Undocumented function.
2159
 *
2160
 * @param string $text Text to debug
2161
 */
2162
function debugTeampass(string $text): void
2163
{
2164
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
2165
    if ($debugFile !== false) {
2166
        fputs($debugFile, $text);
2167
        fclose($debugFile);
2168
    }
2169
}
2170
2171
/**
2172
 * DELETE the file with expected command depending on server type.
2173
 *
2174
 * @param string $file     Path to file
2175
 * @param array  $SETTINGS Teampass settings
2176
 */
2177
function fileDelete(string $file, array $SETTINGS): void
2178
{
2179
    // Load AntiXSS
2180
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/ASCII.php';
2181
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/UTF8.php';
2182
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/AntiXSS.php';
2183
    $antiXss = new voku\helper\AntiXSS();
2184
    $file = $antiXss->xss_clean($file);
2185
    if (is_file($file)) {
2186
        unlink($file);
2187
    }
2188
}
2189
2190
/**
2191
 * Permits to extract the file extension.
2192
 *
2193
 * @param string $file File name
2194
 */
2195
function getFileExtension(string $file): string
2196
{
2197
    if (strpos($file, '.') === false) {
2198
        return $file;
2199
    }
2200
2201
    return substr($file, strrpos($file, '.') + 1);
2202
}
2203
2204
/**
2205
 *    Chmods files and folders with different permissions.
2206
 *
2207
 *    This is an all-PHP alternative to using: \n
2208
 *    <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2209
 *    <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2210
 *
2211
 *    @author Jeppe Toustrup (tenzer at tenzer dot dk)
2212
 *
2213
 * @param string $path     An either relative or absolute path to a file or directory which should be processed.
2214
 *    @param string $filePerm The permissions any found files should get.
2215
 *    @param string $dirPerm  The permissions any found folder should get.
2216
 *
2217
 * @return bool TRUE if the path if found and FALSE if not.
2218
 *
2219
 * @warning The permission levels has to be entered in octal format, which
2220
 *    normally means adding a zero ("0") in front of the permission level. \n
2221
 *    More info at: http://php.net/chmod.
2222
 */
2223
2224
function recursiveChmod(string $path, string $filePerm = 0644, string $dirPerm = 0755): bool
2225
{
2226
    // Check if the path exists
2227
    if (! file_exists($path)) {
2228
        return false;
2229
    }
2230
2231
    // See whether this is a file
2232
    if (is_file($path)) {
2233
        // Chmod the file with our given filepermissions
2234
        chmod($path, $filePerm);
0 ignored issues
show
Bug introduced by
It seems like $filePerm can also be of type string; however, parameter $permissions of chmod() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2234
        chmod($path, /** @scrutinizer ignore-type */ $filePerm);
Loading history...
2235
    // If this is a directory...
2236
    } elseif (is_dir($path)) {
2237
        // Then get an array of the contents
2238
        $foldersAndFiles = scandir($path);
2239
        // Remove "." and ".." from the list
2240
        $entries = array_slice($foldersAndFiles, 2);
2241
        // Parse every result...
2242
        foreach ($entries as $entry) {
2243
            // And call this function again recursively, with the same permissions
2244
            recursiveChmod($path.'/'.$entry, $filePerm, $dirPerm);
2245
        }
2246
2247
        // When we are done with the contents of the directory, we chmod the directory itself
2248
        chmod($path, $dirPerm);
2249
    }
2250
2251
    // Everything seemed to work out well, return true
2252
    return true;
2253
}
2254
2255
/**
2256
 * Check if user can access to this item.
2257
 *
2258
 * @param int $item_id ID of item
2259
 */
2260
function accessToItemIsGranted(int $item_id, $SETTINGS)
2261
{
2262
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2263
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2264
    // Prepare superGlobal variables
2265
    $session_groupes_visibles = $superGlobal->get('groupes_visibles', 'SESSION');
2266
    $session_list_restricted_folders_for_items = $superGlobal->get('list_restricted_folders_for_items', 'SESSION');
2267
    // Load item data
2268
    $data = DB::queryFirstRow(
2269
        'SELECT id_tree
2270
        FROM ' . prefixTable('items') . '
2271
        WHERE id = %i',
2272
        $item_id
2273
    );
2274
    // Check if user can access this folder
2275
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2276
        // Now check if this folder is restricted to user
2277
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2278
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2279
        ) {
2280
            return 'ERR_FOLDER_NOT_ALLOWED';
2281
        }
2282
    }
2283
2284
    return true;
2285
}
2286
2287
/**
2288
 * Creates a unique key.
2289
 *
2290
 * @param int $lenght Key lenght
2291
 */
2292
function uniqidReal(int $lenght = 13): string
2293
{
2294
    if (function_exists('random_bytes')) {
2295
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2296
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2297
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2298
    } else {
2299
        throw new Exception('no cryptographically secure random function available');
2300
    }
2301
2302
    return substr(bin2hex($bytes), 0, $lenght);
2303
}
2304
2305
/**
2306
 * Obfuscate an email.
2307
 *
2308
 * @param string $email Email address
2309
 */
2310
function obfuscateEmail(string $email): string
2311
{
2312
    $prop = 2;
2313
    $start = '';
2314
    $end = '';
2315
    $domain = substr(strrchr($email, '@'), 1);
2316
    $mailname = str_replace($domain, '', $email);
2317
    $name_l = strlen($mailname);
2318
    $domain_l = strlen($domain);
2319
    for ($i = 0; $i <= $name_l / $prop - 1; ++$i) {
2320
        $start .= 'x';
2321
    }
2322
2323
    for ($i = 0; $i <= $domain_l / $prop - 1; ++$i) {
2324
        $end .= 'x';
2325
    }
2326
2327
    return (string) substr_replace($mailname, $start, 2, $name_l / $prop)
2328
        . (string) substr_replace($domain, $end, 2, $domain_l / $prop);
2329
}
2330
2331
/**
2332
 * Perform a Query.
2333
 *
2334
 * @param array  $SETTINGS Teamapss settings
2335
 * @param string $fields   Fields to use
2336
 * @param string $table    Table to use
2337
 *
2338
 * @return array
2339
 */
2340
function performDBQuery(array $SETTINGS, string $fields, string $table): array
2341
{
2342
    // include librairies & connect to DB
2343
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2344
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2345
    if (defined('DB_PASSWD_CLEAR') === false) {
2346
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2347
    }
2348
    DB::$host = DB_HOST;
2349
    DB::$user = DB_USER;
2350
    DB::$password = DB_PASSWD_CLEAR;
2351
    DB::$dbName = DB_NAME;
2352
    DB::$port = DB_PORT;
2353
    DB::$encoding = DB_ENCODING;
2354
    // Insert log in DB
2355
    return DB::query(
2356
        'SELECT ' . $fields . '
2357
        FROM ' . prefixTable($table)
2358
    );
2359
}
2360
2361
/**
2362
 * Undocumented function.
2363
 *
2364
 * @param int $bytes Size of file
2365
 */
2366
function formatSizeUnits(int $bytes): string
2367
{
2368
    if ($bytes >= 1073741824) {
2369
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2370
    } elseif ($bytes >= 1048576) {
2371
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2372
    } elseif ($bytes >= 1024) {
2373
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2374
    } elseif ($bytes > 1) {
2375
        $bytes .= ' bytes';
2376
    } elseif ($bytes === 1) {
2377
        $bytes .= ' byte';
2378
    } else {
2379
        $bytes = '0 bytes';
2380
    }
2381
2382
    return $bytes;
2383
}
2384
2385
/**
2386
 * Generate user pair of keys.
2387
 *
2388
 * @param string $userPwd User password
2389
 *
2390
 * @return array
2391
 */
2392
function generateUserKeys(string $userPwd): array
2393
{
2394
    // include library
2395
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2396
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2397
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2398
    // Load classes
2399
    $rsa = new Crypt_RSA();
2400
    $cipher = new Crypt_AES();
2401
    // Create the private and public key
2402
    $res = $rsa->createKey(4096);
2403
    // Encrypt the privatekey
2404
    $cipher->setPassword($userPwd);
2405
    $privatekey = $cipher->encrypt($res['privatekey']);
2406
    return [
2407
    'private_key' => base64_encode($privatekey),
2408
    'public_key' => base64_encode($res['publickey']),
2409
    'private_key_clear' => base64_encode($res['privatekey']),
2410
];
2411
}
2412
2413
/**
2414
 * Permits to decrypt the user's privatekey.
2415
 *
2416
 * @param string $userPwd        User password
2417
 * @param string $userPrivateKey User private key
2418
 */
2419
function decryptPrivateKey(string $userPwd, string $userPrivateKey): string
2420
{
2421
    if (empty($userPwd) === false) {
2422
        include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2423
        // Load classes
2424
        $cipher = new Crypt_AES();
2425
        // Encrypt the privatekey
2426
        $cipher->setPassword($userPwd);
2427
        return base64_encode($cipher->decrypt(base64_decode($userPrivateKey)));
2428
    }
0 ignored issues
show
Bug Best Practice introduced by
The function implicitly returns null when the if condition on line 2421 is false. This is incompatible with the type-hinted return string. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
2429
}
2430
2431
/**
2432
 * Permits to encrypt the user's privatekey.
2433
 *
2434
 * @param string $userPwd        User password
2435
 * @param string $userPrivateKey User private key
2436
 */
2437
function encryptPrivateKey(string $userPwd, string $userPrivateKey): string
2438
{
2439
    if (empty($userPwd) === false) {
2440
        include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2441
        // Load classes
2442
        $cipher = new Crypt_AES();
2443
        // Encrypt the privatekey
2444
        $cipher->setPassword($userPwd);
2445
        return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2446
    }
0 ignored issues
show
Bug Best Practice introduced by
The function implicitly returns null when the if condition on line 2439 is false. This is incompatible with the type-hinted return string. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
2447
}
2448
2449
/**
2450
 * Encrypts a string using AES.
2451
 *
2452
 * @param string $data String to encrypt
2453
 *
2454
 * @return array
2455
 */
2456
function doDataEncryption(string $data): array
2457
{
2458
    // Includes
2459
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2460
    // Load classes
2461
    $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2462
    // Generate an object key
2463
    $objectKey = uniqidReal(32);
2464
    // Set it as password
2465
    $cipher->setPassword($objectKey);
2466
    return [
2467
    'encrypted' => base64_encode($cipher->encrypt($data)),
2468
    'objectKey' => base64_encode($objectKey),
2469
];
2470
}
2471
2472
/**
2473
 * Decrypts a string using AES.
2474
 *
2475
 * @param string $data Encrypted data
2476
 * @param string $key  Key to uncrypt
2477
 */
2478
function doDataDecryption(string $data, string $key): string
2479
{
2480
    // Includes
2481
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2482
    // Load classes
2483
    $cipher = new Crypt_AES();
2484
    // Set the object key
2485
    $cipher->setPassword(base64_decode($key));
2486
    return base64_encode($cipher->decrypt(base64_decode($data)));
2487
}
2488
2489
/**
2490
 * Encrypts using RSA a string using a public key.
2491
 *
2492
 * @param string $key       Key to be encrypted
2493
 * @param string $publicKey User public key
2494
 */
2495
function encryptUserObjectKey(string $key, string $publicKey): string
2496
{
2497
    // Includes
2498
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2499
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2500
    // Load classes
2501
    $rsa = new Crypt_RSA();
2502
    $rsa->loadKey(base64_decode($publicKey));
2503
    // Encrypt
2504
    return base64_encode($rsa->encrypt(base64_decode($key)));
2505
}
2506
2507
/**
2508
 * Decrypts using RSA an encrypted string using a private key.
2509
 *
2510
 * @param string $key        Encrypted key
2511
 * @param string $privateKey User private key
2512
 */
2513
function decryptUserObjectKey(string $key, string $privateKey): string
2514
{
2515
    // Includes
2516
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2517
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2518
    // Load classes
2519
    $rsa = new Crypt_RSA();
2520
    $rsa->loadKey(base64_decode($privateKey));
2521
    // Decrypt
2522
    try {
2523
        $ret = base64_encode($rsa->decrypt(base64_decode($key)));
2524
    } catch (Exception $e) {
2525
        return $e;
2526
    }
2527
2528
    return $ret;
2529
}
2530
2531
/**
2532
 * Encrypts a file.
2533
 *
2534
 * @param string $fileInName File name
2535
 * @param string $fileInPath Path to file
2536
 *
2537
 * @return array
2538
 */
2539
function encryptFile(string $fileInName, string $fileInPath): array
2540
{
2541
    if (defined('FILE_BUFFER_SIZE') === false) {
2542
        define('FILE_BUFFER_SIZE', 128 * 1024);
2543
    }
2544
2545
    // Includes
2546
    include_once '../includes/config/include.php';
2547
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2548
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2549
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2550
    // Load classes
2551
    $cipher = new Crypt_AES();
2552
    // Generate an object key
2553
    $objectKey = uniqidReal(32);
2554
    // Set it as password
2555
    $cipher->setPassword($objectKey);
2556
    // Prevent against out of memory
2557
    $cipher->enableContinuousBuffer();
2558
    //$cipher->disablePadding();
2559
2560
    // Encrypt the file content
2561
    $plaintext = file_get_contents(
2562
        filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL)
2563
    );
2564
    $ciphertext = $cipher->encrypt($plaintext);
2565
    // Save new file
2566
    $hash = md5($plaintext);
2567
    $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2568
    file_put_contents($fileOut, $ciphertext);
0 ignored issues
show
Security File Manipulation introduced by
$ciphertext can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. Read tainted data from array, and $_SERVER['PHP_AUTH_PW'] is assigned to $passwordClear
    in sources/identify.php on line 327
  2. decryptPrivateKey() is called
    in sources/identify.php on line 773
  3. Enters via parameter $userPwd
    in sources/main.functions.php on line 2419
  4. Crypt_Base::setPassword() is called
    in sources/main.functions.php on line 2426
  5. Enters via parameter $password
    in includes/libraries/Encryption/phpseclib/Crypt/Base.php on line 645
  6. $password . $salt is assigned to $t
    in includes/libraries/Encryption/phpseclib/Crypt/Base.php on line 681
  7. Data is passed through substr(), and substr($t, 0, $dkLen) is assigned to $key
    in includes/libraries/Encryption/phpseclib/Crypt/Base.php on line 685
  8. Data is passed through substr(), and Crypt_Base::setIV() is called
    in includes/libraries/Encryption/phpseclib/Crypt/Base.php on line 688
  9. Enters via parameter $iv
    in includes/libraries/Encryption/phpseclib/Crypt/Base.php on line 556
  10. $iv is assigned to property Crypt_AES::$iv
    in includes/libraries/Encryption/phpseclib/Crypt/Base.php on line 562
  11. Read from property Crypt_AES::$iv, and Data is passed through substr(), and Data is passed through str_pad(), and $this->decryptIV = str_pad(substr($this->iv, 0, $this->block_size), $this->block_size, '') is assigned to property Crypt_AES::$encryptIV
    in includes/libraries/Encryption/phpseclib/Crypt/Base.php on line 1944
  12. Read from property Crypt_AES::$encryptIV, and Data is passed through _openssl_ctr_process(), and $this->_openssl_ctr_process($plaintext, $this->encryptIV, $this->enbuffer) is returned
    in includes/libraries/Encryption/phpseclib/Crypt/Base.php on line 769
  13. $cipher->encrypt($plaintext) is assigned to $ciphertext
    in sources/main.functions.php on line 2564

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
2569
    unlink($fileInPath . '/' . $fileInName);
2570
    return [
2571
    'fileHash' => base64_encode($hash),
2572
    'objectKey' => base64_encode($objectKey),
2573
];
2574
}
2575
2576
/**
2577
 * Decrypt a file.
2578
 *
2579
 * @param string $fileName File name
2580
 * @param string $filePath Path to file
2581
 * @param string $key      Key to use
2582
 */
2583
function decryptFile(string $fileName, string $filePath, string $key): string
2584
{
2585
    if (! defined('FILE_BUFFER_SIZE')) {
2586
        define('FILE_BUFFER_SIZE', 128 * 1024);
2587
    }
2588
2589
    // Includes
2590
    include_once '../includes/config/include.php';
2591
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2592
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2593
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2594
    // Get file name
2595
    $fileName = base64_decode($fileName);
2596
    // Load classes
2597
    $cipher = new Crypt_AES();
2598
    // Set the object key
2599
    $cipher->setPassword(base64_decode($key));
2600
    // Prevent against out of memory
2601
    $cipher->enableContinuousBuffer();
2602
    $cipher->disablePadding();
2603
    // Get file content
2604
    $ciphertext = file_get_contents($filePath . '/' . TP_FILE_PREFIX . $fileName);
2605
    // Decrypt file content and return
2606
    return base64_encode($cipher->decrypt($ciphertext));
2607
}
2608
2609
/**
2610
 * Generate a simple password
2611
 *
2612
 * @param int $length Length of string
2613
 * @param bool $symbolsincluded Allow symbols
2614
 */
2615
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2616
{
2617
    // Generate new user password
2618
    $small_letters = range('a', 'z');
2619
    $big_letters = range('A', 'Z');
2620
    $digits = range(0, 9);
2621
    $symbols = $symbolsincluded === true ?
2622
        ['#', '_', '-', '@', '$', '+', '&'] : [];
2623
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2624
    $count = count($res);
2625
    // first variant
2626
2627
    $random_string = '';
2628
    for ($i = 0; $i < $length; ++$i) {
2629
        $random_string .= $res[random_int(0, $count - 1)];
2630
    }
2631
2632
    return $random_string;
2633
}
2634
2635
/**
2636
 * Permit to store the sharekey of an object for users.
2637
 *
2638
 * @param string $object_name             Type for table selection
2639
 * @param int    $post_folder_is_personal Personal
2640
 * @param int    $post_folder_id          Folder
2641
 * @param int    $post_object_id          Object
2642
 * @param string $objectKey               Object key
2643
 * @param array  $SETTINGS                Teampass settings
2644
 */
2645
function storeUsersShareKey(
2646
    string $object_name,
2647
    int $post_folder_is_personal,
2648
    int $post_folder_id,
2649
    int $post_object_id,
2650
    string $objectKey,
2651
    array $SETTINGS
2652
): void {
2653
    // include librairies & connect to DB
2654
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2655
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2656
    if (defined('DB_PASSWD_CLEAR') === false) {
2657
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2658
    }
2659
    DB::$host = DB_HOST;
2660
    DB::$user = DB_USER;
2661
    DB::$password = DB_PASSWD_CLEAR;
2662
    DB::$dbName = DB_NAME;
2663
    DB::$port = DB_PORT;
2664
    DB::$encoding = DB_ENCODING;
2665
    // Delete existing entries for this object
2666
    DB::delete(
2667
        $object_name,
2668
        'object_id = %i',
2669
        $post_object_id
2670
    );
2671
    // Superglobals
2672
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2673
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2674
    // Prepare superGlobal variables
2675
    $sessionPpersonaFolders = $superGlobal->get('personal_folders', 'SESSION');
2676
    $sessionUserId = $superGlobal->get('user_id', 'SESSION');
2677
    $sessionUserPublicKey = $superGlobal->get('public_key', 'SESSION', 'user');
2678
    if (
2679
        (int) $post_folder_is_personal === 1
2680
        && in_array($post_folder_id, $sessionPpersonaFolders) === true
2681
    ) {
2682
        // If this is a personal object
2683
        // Only create the sharekey for user
2684
        DB::insert(
2685
            $object_name,
2686
            [
2687
                'object_id' => $post_object_id,
2688
                'user_id' => $sessionUserId,
2689
                'share_key' => encryptUserObjectKey($objectKey, $sessionUserPublicKey),
2690
            ]
2691
        );
2692
    } else {
2693
        // This is a public object
2694
        // Create sharekey for each user
2695
        $users = DB::query(
2696
            'SELECT id, public_key
2697
            FROM ' . prefixTable('users') . '
2698
            WHERE id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '")
2699
            AND public_key != ""'
2700
        );
2701
        foreach ($users as $user) {
2702
            // Insert in DB the new object key for this item by user
2703
            DB::insert(
2704
                $object_name,
2705
                [
2706
                    'object_id' => $post_object_id,
2707
                    'user_id' => $user['id'],
2708
                    'share_key' => encryptUserObjectKey(
2709
                        $objectKey,
2710
                        $user['public_key']
2711
                    ),
2712
                ]
2713
            );
2714
        }
2715
    }
2716
}
2717
2718
/**
2719
 * Is this string base64 encoded?
2720
 *
2721
 * @param string $str Encoded string?
2722
 */
2723
function isBase64(string $str): bool
2724
{
2725
    $str = (string) trim($str);
2726
    if (! isset($str[0])) {
2727
        return false;
2728
    }
2729
2730
    $base64String = (string) base64_decode($str, true);
2731
    if ($base64String && base64_encode($base64String) === $str) {
2732
        return true;
2733
    }
2734
2735
    return false;
2736
}
2737
2738
/**
2739
 * Undocumented function
2740
 *
2741
 * @param string $field Parameter
2742
 *
2743
 * @return array|bool|resource|string
2744
 */
2745
function filterString(string $field)
2746
{
2747
    // Sanitize string
2748
    $field = filter_var(trim($field), FILTER_SANITIZE_STRING);
2749
    if (empty($field) === false) {
2750
        // Load AntiXSS
2751
        include_once '../includes/libraries/voku/helper/AntiXSS.php';
2752
        $antiXss = new voku\helper\AntiXSS();
2753
        // Return
2754
        return $antiXss->xss_clean($field);
2755
    }
2756
2757
    return false;
2758
}
2759
2760
/**
2761
 * CHeck if provided credentials are allowed on server
2762
 *
2763
 * @param string $login    User Login
2764
 * @param string $password User Pwd
2765
 * @param array  $SETTINGS Teampass settings
2766
 */
2767
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
2768
{
2769
    // Build ldap configuration array
2770
    $config = [
2771
        // Mandatory Configuration Options
2772
        'hosts' => [$SETTINGS['ldap_hosts']],
2773
        'base_dn' => $SETTINGS['ldap_bdn'],
2774
        'username' => $SETTINGS['ldap_username'],
2775
        'password' => $SETTINGS['ldap_password'],
2776
2777
        // Optional Configuration Options
2778
        'port' => $SETTINGS['ldap_port'],
2779
        'use_ssl' => $SETTINGS['ldap_ssl'] === 1 ? true : false,
2780
        'use_tls' => $SETTINGS['ldap_tls'] === 1 ? true : false,
2781
        'version' => 3,
2782
        'timeout' => 5,
2783
        'follow_referrals' => false,
2784
2785
        // Custom LDAP Options
2786
        'options' => [
2787
            // See: http://php.net/ldap_set_option
2788
            LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_HARD,
2789
        ],
2790
    ];
2791
    // Load expected libraries
2792
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Traits/Macroable.php';
2793
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Arr.php';
2794
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/DetectsErrors.php';
2795
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Connection.php';
2796
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/LdapInterface.php';
2797
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/LdapBase.php';
2798
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Ldap.php';
2799
    $ad = new SplClassLoader('LdapRecord', '../includes/libraries');
2800
    $ad->register();
2801
    $connection = new Connection($config);
2802
    // COnnect to LDAP
2803
    try {
2804
        $connection->connect();
2805
    } catch (\LdapRecord\Auth\BindException $e) {
2806
        $error = $e->getDetailedError();
2807
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
2808
        return false;
2809
    }
2810
2811
    // Authenticate user
2812
    try {
2813
        $connection->auth()->attempt($SETTINGS['ldap_user_attribute'].'='.$login.','.$SETTINGS['ldap_bdn'], $password, $stayAuthenticated = true);
2814
    } catch (\LdapRecord\Auth\BindException $e) {
2815
        $error = $e->getDetailedError();
2816
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
2817
        return false;
2818
    }
2819
2820
    return true;
2821
}
2822
2823
/**
2824
 * Removes from DB all sharekeys of this user
2825
 *
2826
 * @param int $userId User's id
2827
 * @param array   $SETTINGS Teampass settings
2828
 */
2829
function deleteUserObjetsKeys(int $userId, array $SETTINGS): null|bool
0 ignored issues
show
Bug introduced by
The type null was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
2830
{
2831
    // include librairies & connect to DB
2832
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2833
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2834
    if (defined('DB_PASSWD_CLEAR') === false) {
2835
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2836
    }
2837
    DB::$host = DB_HOST;
2838
    DB::$user = DB_USER;
2839
    DB::$password = DB_PASSWD_CLEAR;
2840
    DB::$dbName = DB_NAME;
2841
    DB::$port = DB_PORT;
2842
    DB::$encoding = DB_ENCODING;
2843
    // Remove all item sharekeys items
2844
    DB::delete(
2845
        prefixTable('sharekeys_items'),
2846
        'user_id = %i',
2847
        $userId
2848
    );
2849
    // Remove all item sharekeys files
2850
    DB::delete(
2851
        prefixTable('sharekeys_files'),
2852
        'user_id = %i',
2853
        $userId
2854
    );
2855
    // Remove all item sharekeys fields
2856
    DB::delete(
2857
        prefixTable('sharekeys_fields'),
2858
        'user_id = %i',
2859
        $userId
2860
    );
2861
    // Remove all item sharekeys logs
2862
    DB::delete(
2863
        prefixTable('sharekeys_logs'),
2864
        'user_id = %i',
2865
        $userId
2866
    );
2867
    // Remove all item sharekeys suggestions
2868
    DB::delete(
2869
        prefixTable('sharekeys_suggestions'),
2870
        'user_id = %i',
2871
        $userId
2872
    );
2873
    return false;
2874
}
2875
2876
// Manage list of timezones
2877
function timezone_list()
2878
{
2879
    static $timezones = null;
2880
    if ($timezones === null) {
2881
        $timezones = [];
2882
        $offsets = [];
2883
        $now = new DateTime('now', new DateTimeZone('UTC'));
2884
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
2885
            $now->setTimezone(new DateTimeZone($timezone));
2886
            $offsets[] = $offset = $now->getOffset();
2887
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
2888
        }
2889
2890
        array_multisort($offsets, $timezones);
2891
    }
2892
2893
    return $timezones;
2894
}
2895
2896
function format_GMT_offset($offset)
2897
{
2898
    $hours = intval($offset / 3600);
2899
    $minutes = abs(intval($offset % 3600 / 60));
2900
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
2901
}
2902
2903
function format_timezone_name($name)
2904
{
2905
    $name = str_replace('/', ', ', $name);
2906
    $name = str_replace('_', ' ', $name);
2907
    return str_replace('St ', 'St. ', $name);
2908
2909
}
2910
2911
/**
2912
 * Provides info about if user should use MFA
2913
 *
2914
 * @param string $userRolesIds  User roles ids
2915
 * @param string $mfaRoles      Roles for which MFA is requested
2916
 */
2917
function mfa_auth_requested(string $userRolesIds, string $mfaRoles): bool
2918
{
2919
    if (is_null($mfaRoles) === true) {
2920
        return false;
2921
    }
2922
2923
    $mfaRoles = array_values(json_decode($mfaRoles, true));
2924
    $userRolesIds = array_filter(explode(';', $userRolesIds));
2925
    if (count($mfaRoles) === 0 || count($mfaRoles) === 0) {
2926
        return true;
2927
    }
2928
2929
    if (count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
2930
        return true;
2931
    }
2932
    return false;
2933
2934
    
2935
}
2936