Passed
Push — master ( 579e3e...fe5bf2 )
by Nils
05:15
created

isValueSetEmpty()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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