Passed
Push — master ( a48fcc...f9f3f8 )
by Nils
10:04
created

dataSanitizer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 12
c 1
b 0
f 0
nc 1
nop 3
dl 0
loc 21
rs 9.8666
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
            array_push($personalFolders, $persoFld['id']);
795
            array_push($allowedFolders, $persoFld['id']);
796
            // get all descendants
797
            $ids = $tree->getDescendants($persoFld['id'], false, false, true);
798
            foreach ($ids as $id) {
799
                    array_push($allowedFolders, $id);
800
                    array_push($personalFolders, $id);
801
            }
802
        }
803
    }
804
    
805
    // Exclude all other PF
806
    $where = new WhereClause('and');
807
    $where->add('personal_folder=%i', 1);
808
    if (
809
        (int) $enablePfFeature === 1
810
        && (int) $globalsPersonalFolders === 1
811
    ) {
812
        $where->add('title=%s', $globalsUserId);
813
        $where->negateLast();
814
    }
815
    $persoFlds = DB::query(
816
        'SELECT id
817
        FROM ' . prefixTable('nested_tree') . '
818
        WHERE %l',
819
        $where
820
    );
821
    foreach ($persoFlds as $persoFldId) {
822
        array_push($noAccessPersonalFolders, $persoFldId['id']);
823
    }
824
825
    // All folders visibles
826
    $allowedFolders = array_merge(
827
        $allowedFolders,
828
        $foldersLimitedFull,
829
        $allowedFoldersByRoles,
830
        $restrictedFoldersForItems,
831
        $readOnlyFolders
832
    );
833
    // Exclude from allowed folders all the specific user forbidden folders
834
    if (count($noAccessFolders) > 0) {
835
        $allowedFolders = array_diff($allowedFolders, $noAccessFolders);
836
    }
837
838
    return [
839
        'allowedFolders' => $allowedFolders,
840
        'personalFolders' => $personalFolders,
841
        'noAccessPersonalFolders' => $noAccessPersonalFolders
842
    ];
843
}
844
845
846
/**
847
 * Update the CACHE table.
848
 *
849
 * @param string $action   What to do
850
 * @param array  $SETTINGS Teampass settings
851
 * @param int    $ident    Ident format
852
 * 
853
 * @return void
854
 */
855
function updateCacheTable(string $action, array $SETTINGS, ?int $ident = null): void
856
{
857
    if ($action === 'reload') {
858
        // Rebuild full cache table
859
        cacheTableRefresh($SETTINGS);
860
    } elseif ($action === 'update_value' && is_null($ident) === false) {
861
        // UPDATE an item
862
        cacheTableUpdate($SETTINGS, $ident);
863
    } elseif ($action === 'add_value' && is_null($ident) === false) {
864
        // ADD an item
865
        cacheTableAdd($SETTINGS, $ident);
866
    } elseif ($action === 'delete_value' && is_null($ident) === false) {
867
        // DELETE an item
868
        DB::delete(prefixTable('cache'), 'id = %i', $ident);
869
    }
870
}
871
872
/**
873
 * Cache table - refresh.
874
 *
875
 * @param array $SETTINGS Teampass settings
876
 * 
877
 * @return void
878
 */
879
function cacheTableRefresh(array $SETTINGS): void
880
{
881
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
882
    //Connect to DB
883
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
884
    if (defined('DB_PASSWD_CLEAR') === false) {
885
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
886
    }
887
    DB::$host = DB_HOST;
888
    DB::$user = DB_USER;
889
    DB::$password = DB_PASSWD_CLEAR;
890
    DB::$dbName = DB_NAME;
891
    DB::$port = DB_PORT;
892
    DB::$encoding = DB_ENCODING;
893
    //Load Tree
894
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
895
    $tree->register();
896
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
897
    // truncate table
898
    DB::query('TRUNCATE TABLE ' . prefixTable('cache'));
899
    // reload date
900
    $rows = DB::query(
901
        'SELECT *
902
        FROM ' . prefixTable('items') . ' as i
903
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
904
        AND l.action = %s
905
        AND i.inactif = %i',
906
        'at_creation',
907
        0
908
    );
909
    foreach ($rows as $record) {
910
        if (empty($record['id_tree']) === false) {
911
            // Get all TAGS
912
            $tags = '';
913
            $itemTags = DB::query(
914
                'SELECT tag
915
                FROM ' . prefixTable('tags') . '
916
                WHERE item_id = %i AND tag != ""',
917
                $record['id']
918
            );
919
            foreach ($itemTags as $itemTag) {
920
                $tags .= $itemTag['tag'] . ' ';
921
            }
922
923
            // Get renewal period
924
            $resNT = DB::queryfirstrow(
925
                'SELECT renewal_period
926
                FROM ' . prefixTable('nested_tree') . '
927
                WHERE id = %i',
928
                $record['id_tree']
929
            );
930
            // form id_tree to full foldername
931
            $folder = [];
932
            $arbo = $tree->getPath($record['id_tree'], true);
933
            foreach ($arbo as $elem) {
934
                // Check if title is the ID of a user
935
                if (is_numeric($elem->title) === true) {
936
                    // Is this a User id?
937
                    $user = DB::queryfirstrow(
938
                        'SELECT id, login
939
                        FROM ' . prefixTable('users') . '
940
                        WHERE id = %i',
941
                        $elem->title
942
                    );
943
                    if (count($user) > 0) {
944
                        $elem->title = $user['login'];
945
                    }
946
                }
947
                // Build path
948
                array_push($folder, stripslashes($elem->title));
949
            }
950
            // store data
951
            DB::insert(
952
                prefixTable('cache'),
953
                [
954
                    'id' => $record['id'],
955
                    'label' => $record['label'],
956
                    'description' => $record['description'] ?? '',
957
                    'url' => isset($record['url']) && ! empty($record['url']) ? $record['url'] : '0',
958
                    'tags' => $tags,
959
                    'id_tree' => $record['id_tree'],
960
                    'perso' => $record['perso'],
961
                    'restricted_to' => isset($record['restricted_to']) && ! empty($record['restricted_to']) ? $record['restricted_to'] : '0',
962
                    'login' => $record['login'] ?? '',
963
                    'folder' => implode(' > ', $folder),
964
                    'author' => $record['id_user'],
965
                    'renewal_period' => $resNT['renewal_period'] ?? '0',
966
                    'timestamp' => $record['date'],
967
                ]
968
            );
969
        }
970
    }
971
}
972
973
/**
974
 * Cache table - update existing value.
975
 *
976
 * @param array  $SETTINGS Teampass settings
977
 * @param int    $ident    Ident format
978
 * 
979
 * @return void
980
 */
981
function cacheTableUpdate(array $SETTINGS, ?int $ident = null): void
982
{
983
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
984
    // Load superglobal
985
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
986
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
987
    //Connect to DB
988
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
989
    if (defined('DB_PASSWD_CLEAR') === false) {
990
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
991
    }
992
    DB::$host = DB_HOST;
993
    DB::$user = DB_USER;
994
    DB::$password = DB_PASSWD_CLEAR;
995
    DB::$dbName = DB_NAME;
996
    DB::$port = DB_PORT;
997
    DB::$encoding = DB_ENCODING;
998
    //Load Tree
999
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1000
    $tree->register();
1001
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1002
    // get new value from db
1003
    $data = DB::queryfirstrow(
1004
        'SELECT label, description, id_tree, perso, restricted_to, login, url
1005
        FROM ' . prefixTable('items') . '
1006
        WHERE id=%i',
1007
        $ident
1008
    );
1009
    // Get all TAGS
1010
    $tags = '';
1011
    $itemTags = DB::query(
1012
        'SELECT tag
1013
            FROM ' . prefixTable('tags') . '
1014
            WHERE item_id = %i AND tag != ""',
1015
        $ident
1016
    );
1017
    foreach ($itemTags as $itemTag) {
1018
        $tags .= $itemTag['tag'] . ' ';
1019
    }
1020
    // form id_tree to full foldername
1021
    $folder = [];
1022
    $arbo = $tree->getPath($data['id_tree'], true);
1023
    foreach ($arbo as $elem) {
1024
        // Check if title is the ID of a user
1025
        if (is_numeric($elem->title) === true) {
1026
            // Is this a User id?
1027
            $user = DB::queryfirstrow(
1028
                'SELECT id, login
1029
                FROM ' . prefixTable('users') . '
1030
                WHERE id = %i',
1031
                $elem->title
1032
            );
1033
            if (count($user) > 0) {
1034
                $elem->title = $user['login'];
1035
            }
1036
        }
1037
        // Build path
1038
        array_push($folder, stripslashes($elem->title));
1039
    }
1040
    // finaly update
1041
    DB::update(
1042
        prefixTable('cache'),
1043
        [
1044
            'label' => $data['label'],
1045
            'description' => $data['description'],
1046
            'tags' => $tags,
1047
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
1048
            'id_tree' => $data['id_tree'],
1049
            'perso' => $data['perso'],
1050
            'restricted_to' => isset($data['restricted_to']) && ! empty($data['restricted_to']) ? $data['restricted_to'] : '0',
1051
            'login' => $data['login'] ?? '',
1052
            'folder' => implode(' » ', $folder),
1053
            'author' => $superGlobal->get('user_id', 'SESSION'),
1054
        ],
1055
        'id = %i',
1056
        $ident
1057
    );
1058
}
1059
1060
/**
1061
 * Cache table - add new value.
1062
 *
1063
 * @param array  $SETTINGS Teampass settings
1064
 * @param int    $ident    Ident format
1065
 * 
1066
 * @return void
1067
 */
1068
function cacheTableAdd(array $SETTINGS, ?int $ident = null): void
1069
{
1070
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1071
    // Load superglobal
1072
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1073
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1074
    // Get superglobals
1075
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
1076
    //Connect to DB
1077
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1078
    if (defined('DB_PASSWD_CLEAR') === false) {
1079
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1080
    }
1081
    DB::$host = DB_HOST;
1082
    DB::$user = DB_USER;
1083
    DB::$password = DB_PASSWD_CLEAR;
1084
    DB::$dbName = DB_NAME;
1085
    DB::$port = DB_PORT;
1086
    DB::$encoding = DB_ENCODING;
1087
    //Load Tree
1088
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1089
    $tree->register();
1090
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1091
    // get new value from db
1092
    $data = DB::queryFirstRow(
1093
        'SELECT i.label, i.description, i.id_tree as id_tree, i.perso, i.restricted_to, i.id, i.login, i.url, l.date
1094
        FROM ' . prefixTable('items') . ' as i
1095
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
1096
        WHERE i.id = %i
1097
        AND l.action = %s',
1098
        $ident,
1099
        'at_creation'
1100
    );
1101
    // Get all TAGS
1102
    $tags = '';
1103
    $itemTags = DB::query(
1104
        'SELECT tag
1105
            FROM ' . prefixTable('tags') . '
1106
            WHERE item_id = %i AND tag != ""',
1107
        $ident
1108
    );
1109
    foreach ($itemTags as $itemTag) {
1110
        $tags .= $itemTag['tag'] . ' ';
1111
    }
1112
    // form id_tree to full foldername
1113
    $folder = [];
1114
    $arbo = $tree->getPath($data['id_tree'], true);
1115
    foreach ($arbo as $elem) {
1116
        // Check if title is the ID of a user
1117
        if (is_numeric($elem->title) === true) {
1118
            // Is this a User id?
1119
            $user = DB::queryfirstrow(
1120
                'SELECT id, login
1121
                FROM ' . prefixTable('users') . '
1122
                WHERE id = %i',
1123
                $elem->title
1124
            );
1125
            if (count($user) > 0) {
1126
                $elem->title = $user['login'];
1127
            }
1128
        }
1129
        // Build path
1130
        array_push($folder, stripslashes($elem->title));
1131
    }
1132
    // finaly update
1133
    DB::insert(
1134
        prefixTable('cache'),
1135
        [
1136
            'id' => $data['id'],
1137
            'label' => $data['label'],
1138
            'description' => $data['description'],
1139
            'tags' => isset($tags) && empty($tags) === false ? $tags : 'None',
1140
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
1141
            'id_tree' => $data['id_tree'],
1142
            'perso' => isset($data['perso']) && empty($data['perso']) === false && $data['perso'] !== 'None' ? $data['perso'] : '0',
1143
            'restricted_to' => isset($data['restricted_to']) && empty($data['restricted_to']) === false ? $data['restricted_to'] : '0',
1144
            'login' => $data['login'] ?? '',
1145
            'folder' => implode(' » ', $folder),
1146
            'author' => $globalsUserId,
1147
            'timestamp' => $data['date'],
1148
        ]
1149
    );
1150
}
1151
1152
/**
1153
 * Do statistics.
1154
 *
1155
 * @param array $SETTINGS Teampass settings
1156
 *
1157
 * @return array
1158
 */
1159
function getStatisticsData(array $SETTINGS): array
1160
{
1161
    DB::query(
1162
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1163
        0
1164
    );
1165
    $counter_folders = DB::count();
1166
    DB::query(
1167
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1168
        1
1169
    );
1170
    $counter_folders_perso = DB::count();
1171
    DB::query(
1172
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1173
        0
1174
    );
1175
    $counter_items = DB::count();
1176
        DB::query(
1177
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1178
        1
1179
    );
1180
    $counter_items_perso = DB::count();
1181
        DB::query(
1182
        'SELECT id FROM ' . prefixTable('users') . ''
1183
    );
1184
    $counter_users = DB::count();
1185
        DB::query(
1186
        'SELECT id FROM ' . prefixTable('users') . ' WHERE admin = %i',
1187
        1
1188
    );
1189
    $admins = DB::count();
1190
    DB::query(
1191
        'SELECT id FROM ' . prefixTable('users') . ' WHERE gestionnaire = %i',
1192
        1
1193
    );
1194
    $managers = DB::count();
1195
    DB::query(
1196
        'SELECT id FROM ' . prefixTable('users') . ' WHERE read_only = %i',
1197
        1
1198
    );
1199
    $readOnly = DB::count();
1200
    // list the languages
1201
    $usedLang = [];
1202
    $tp_languages = DB::query(
1203
        'SELECT name FROM ' . prefixTable('languages')
1204
    );
1205
    foreach ($tp_languages as $tp_language) {
1206
        DB::query(
1207
            'SELECT * FROM ' . prefixTable('users') . ' WHERE user_language = %s',
1208
            $tp_language['name']
1209
        );
1210
        $usedLang[$tp_language['name']] = round((DB::count() * 100 / $counter_users), 0);
1211
    }
1212
1213
    // get list of ips
1214
    $usedIp = [];
1215
    $tp_ips = DB::query(
1216
        'SELECT user_ip FROM ' . prefixTable('users')
1217
    );
1218
    foreach ($tp_ips as $ip) {
1219
        if (array_key_exists($ip['user_ip'], $usedIp)) {
1220
            $usedIp[$ip['user_ip']] += $usedIp[$ip['user_ip']];
1221
        } elseif (! empty($ip['user_ip']) && $ip['user_ip'] !== 'none') {
1222
            $usedIp[$ip['user_ip']] = 1;
1223
        }
1224
    }
1225
1226
    return [
1227
        'error' => '',
1228
        'stat_phpversion' => phpversion(),
1229
        'stat_folders' => $counter_folders,
1230
        'stat_folders_shared' => intval($counter_folders) - intval($counter_folders_perso),
1231
        'stat_items' => $counter_items,
1232
        'stat_items_shared' => intval($counter_items) - intval($counter_items_perso),
1233
        'stat_users' => $counter_users,
1234
        'stat_admins' => $admins,
1235
        'stat_managers' => $managers,
1236
        'stat_ro' => $readOnly,
1237
        'stat_kb' => $SETTINGS['enable_kb'],
1238
        'stat_pf' => $SETTINGS['enable_pf_feature'],
1239
        'stat_fav' => $SETTINGS['enable_favourites'],
1240
        'stat_teampassversion' => TP_VERSION_FULL,
1241
        'stat_ldap' => $SETTINGS['ldap_mode'],
1242
        'stat_agses' => $SETTINGS['agses_authentication_enabled'],
1243
        'stat_duo' => $SETTINGS['duo'],
1244
        'stat_suggestion' => $SETTINGS['enable_suggestion'],
1245
        'stat_api' => $SETTINGS['api'],
1246
        'stat_customfields' => $SETTINGS['item_extra_fields'],
1247
        'stat_syslog' => $SETTINGS['syslog_enable'],
1248
        'stat_2fa' => $SETTINGS['google_authentication'],
1249
        'stat_stricthttps' => $SETTINGS['enable_sts'],
1250
        'stat_mysqlversion' => DB::serverVersion(),
1251
        'stat_languages' => $usedLang,
1252
        'stat_country' => $usedIp,
1253
    ];
1254
}
1255
1256
/**
1257
 * Permits to send an email.
1258
 *
1259
 * @param string $subject     email subject
1260
 * @param string $textMail    email message
1261
 * @param string $email       email
1262
 * @param array  $SETTINGS    settings
1263
 * @param string $textMailAlt email message alt
1264
 * @param bool   $silent      no errors
1265
 *
1266
 * @return string some json info
1267
 */
1268
function sendEmail(
1269
    $subject,
1270
    $textMail,
1271
    $email,
1272
    $SETTINGS,
1273
    $textMailAlt = null,
1274
    $silent = true
1275
) {
1276
    // CAse where email not defined
1277
    if ($email === 'none' || empty($email) === true) {
1278
        return json_encode(
1279
            [
1280
                'error' => true,
1281
                'message' => langHdl('forgot_my_pw_email_sent'),
1282
            ]
1283
        );
1284
    }
1285
1286
    // Load settings
1287
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
1288
    // Load superglobal
1289
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1290
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1291
    // Get user language
1292
    include_once $SETTINGS['cpassman_dir'] . '/includes/language/' . $superGlobal->get('user_language', 'SESSION') . '.php';
1293
    // Load library
1294
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1295
    // load PHPMailer
1296
    $mail = new SplClassLoader('PHPMailer\PHPMailer', '../includes/libraries');
1297
    $mail->register();
1298
    $mail = new PHPMailer\PHPMailer\PHPMailer(true);
1299
1300
    // send to user
1301
    $mail->setLanguage('en', $SETTINGS['cpassman_dir'] . '/includes/libraries/PHPMailer/PHPMailer/language/');
1302
    $mail->SMTPDebug = isset($SETTINGS['email_debug_level']) === true ? $SETTINGS['email_debug_level'] : 0;
1303
    $mail->Port = (int) $SETTINGS['email_port'];
1304
    //COULD BE USED
1305
    $mail->CharSet = 'utf-8';
1306
    $mail->SMTPSecure = $SETTINGS['email_security'] !== 'none' ? $SETTINGS['email_security'] : '';
1307
    $mail->SMTPAutoTLS = $SETTINGS['email_security'] !== 'none' ? true : false;
1308
    $mail->SMTPOptions = [
1309
        'ssl' => [
1310
            'verify_peer' => false,
1311
            'verify_peer_name' => false,
1312
            'allow_self_signed' => true,
1313
        ],
1314
    ];
1315
    $mail->isSmtp();
1316
    // send via SMTP
1317
    $mail->Host = $SETTINGS['email_smtp_server'];
1318
    // SMTP servers
1319
    $mail->SMTPAuth = (int) $SETTINGS['email_smtp_auth'] === 1 ? true : false;
1320
    // turn on SMTP authentication
1321
    $mail->Username = $SETTINGS['email_auth_username'];
1322
    // SMTP username
1323
    $mail->Password = $SETTINGS['email_auth_pwd'];
1324
    // SMTP password
1325
    $mail->From = $SETTINGS['email_from'];
1326
    $mail->FromName = $SETTINGS['email_from_name'];
1327
    // Prepare for each person
1328
    foreach (array_filter(explode(',', $email)) as $dest) {
1329
        $mail->addAddress($dest);
1330
    }
1331
1332
    // Prepare HTML
1333
    $text_html = emailBody($textMail);
1334
    $mail->WordWrap = 80;
1335
    // set word wrap
1336
    $mail->isHtml(true);
1337
    // send as HTML
1338
    $mail->Subject = $subject;
1339
    $mail->Body = $text_html;
1340
    $mail->AltBody = is_null($textMailAlt) === false ? $textMailAlt : '';
1341
1342
    try {
1343
        // send email
1344
        $mail->send();
1345
    } catch (Exception $e) {
1346
        if ($silent === false || (int) $SETTINGS['email_debug_level'] !== 0) {
1347
            return json_encode(
1348
                [
1349
                    'error' => true,
1350
                    'message' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1351
                ]
1352
            );
1353
        }
1354
        return '';
1355
    }
1356
    $mail->smtpClose();
1357
1358
    if ($silent === false) {
1359
        return json_encode(
1360
            [
1361
                'error' => false,
1362
                'message' => langHdl('forgot_my_pw_email_sent'),
1363
            ]
1364
        );
1365
    }
1366
    // Debug purpose
1367
    if ((int) $SETTINGS['email_debug_level'] !== 0) {
1368
        return json_encode(
1369
            [
1370
                'error' => true,
1371
                'message' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1372
            ]
1373
        );
1374
    }
1375
    return json_encode(
1376
        [
1377
            'error' => false,
1378
            'message' => langHdl('share_sent_ok'),
1379
        ]
1380
    );
1381
}
1382
1383
/**
1384
 * Returns the email body.
1385
 *
1386
 * @param string $textMail Text for the email
1387
 */
1388
function emailBody(string $textMail): string
1389
{
1390
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1391
    w3.org/TR/html4/loose.dtd"><html>
1392
    <head><title>Email Template</title>
1393
    <style type="text/css">
1394
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1395
    </style></head>
1396
    <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">
1397
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1398
    <tr><td style="border-collapse: collapse;"><br>
1399
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1400
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1401
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1402
        </td></tr></table></td>
1403
    </tr>
1404
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1405
        <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;">
1406
        <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;">
1407
        <br><div style="float:right;">' .
1408
        $textMail .
1409
        '<br><br></td></tr></table>
1410
    </td></tr></table>
1411
    <br></body></html>';
1412
}
1413
1414
/**
1415
 * Generate a Key.
1416
 * 
1417
 * @return string
1418
 */
1419
function generateKey(): string
1420
{
1421
    return substr(md5(rand() . rand()), 0, 15);
1422
}
1423
1424
/**
1425
 * Convert date to timestamp.
1426
 *
1427
 * @param string $date     The date
1428
 * @param array  $SETTINGS Teampass settings
1429
 *
1430
 * @return string
1431
 */
1432
function dateToStamp(string $date, array $SETTINGS): string
1433
{
1434
    $date = date_parse_from_format($SETTINGS['date_format'], $date);
1435
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1436
        return mktime(23, 59, 59, $date['month'], $date['day'], $date['year']);
1437
    }
1438
    return '';
1439
}
1440
1441
/**
1442
 * Is this a date.
1443
 *
1444
 * @param string $date Date
1445
 *
1446
 * @return bool
1447
 */
1448
function isDate(string $date): bool
1449
{
1450
    return strtotime($date) !== false;
1451
}
1452
1453
/**
1454
 * Check if isUTF8().
1455
 *
1456
 * @param string|array $string Is the string
1457
 *
1458
 * @return int is the string in UTF8 format
1459
 */
1460
function isUTF8($string): int
1461
{
1462
    if (is_array($string) === true) {
1463
        $string = $string['string'];
1464
    }
1465
1466
    return preg_match(
1467
        '%^(?:
1468
        [\x09\x0A\x0D\x20-\x7E] # ASCII
1469
        | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
1470
        | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
1471
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
1472
        | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
1473
        | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
1474
        | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
1475
        | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
1476
        )*$%xs',
1477
        $string
1478
    );
1479
}
1480
1481
/**
1482
 * Prepare an array to UTF8 format before JSON_encode.
1483
 *
1484
 * @param array $array Array of values
1485
 *
1486
 * @return array
1487
 */
1488
function utf8Converter(array $array): array
1489
{
1490
    array_walk_recursive(
1491
        $array,
1492
        static function (&$item): void {
1493
            if (mb_detect_encoding((string) $item, 'utf-8', true) === false) {
1494
                $item = utf8_encode($item);
1495
            }
1496
        }
1497
    );
1498
    return $array;
1499
}
1500
1501
/**
1502
 * Permits to prepare data to be exchanged.
1503
 *
1504
 * @param string       $teampassDir
1505
 * @param array|string $data Text
1506
 * @param string       $type Parameter
1507
 * @param string       $key  Optional key
1508
 *
1509
 * @return resource|string|array
1510
 */
1511
function prepareExchangedData($teampassDir, $data, string $type, ?string $key = null)
1512
{
1513
    // Load superglobal
1514
    include_once $teampassDir . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1515
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1516
    // Get superglobals
1517
    if ($key !== null) {
1518
        $superGlobal->put('key', $key, 'SESSION');
1519
        $globalsKey = $key;
1520
    } else {
1521
        $globalsKey = $superGlobal->get('key', 'SESSION');
1522
    }
1523
1524
    //load ClassLoader
1525
    include_once $teampassDir . '/sources/SplClassLoader.php';
1526
    //Load AES
1527
    $aes = new SplClassLoader('Encryption\Crypt', $teampassDir . '/includes/libraries');
1528
    $aes->register();
1529
    if ($type === 'encode' && is_array($data) === true) {
1530
        // Ensure UTF8 format
1531
        $data = utf8Converter($data);
1532
        // Now encode
1533
        return Encryption\Crypt\aesctr::encrypt(
1534
            json_encode(
1535
                $data,
1536
                JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1537
            ),
1538
            $globalsKey,
1539
            256
1540
        );
1541
    }
1542
    if ($type === 'decode' && is_array($data) === false) {
1543
        return json_decode(
1544
            Encryption\Crypt\aesctr::decrypt(
1545
                /** @scrutinizer ignore-type */
1546
                (string) $data,
1547
                $globalsKey,
1548
                256
1549
            ),
1550
            true
1551
        );
1552
    }
1553
}
1554
1555
/**
1556
 * Create a thumbnail.
1557
 *
1558
 * @param string  $src           Source
1559
 * @param string  $dest          Destination
1560
 * @param int $desired_width Size of width
1561
 * 
1562
 * @return void|string|bool
1563
 */
1564
function makeThumbnail(string $src, string $dest, int $desired_width)
1565
{
1566
    /* read the source image */
1567
    if (is_file($src) === true && mime_content_type($src) === 'image/png') {
1568
        $source_image = imagecreatefrompng($src);
1569
        if ($source_image === false) {
1570
            return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1571
        }
1572
    } else {
1573
        return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1574
    }
1575
1576
    // Get height and width
1577
    $width = imagesx($source_image);
1578
    $height = imagesy($source_image);
1579
    /* find the "desired height" of this thumbnail, relative to the desired width  */
1580
    $desired_height = (int) floor($height * $desired_width / $width);
1581
    /* create a new, "virtual" image */
1582
    $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
1583
    if ($virtual_image === false) {
1584
        return false;
1585
    }
1586
    /* copy source image at a resized size */
1587
    imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
1588
    /* create the physical thumbnail image to its destination */
1589
    imagejpeg($virtual_image, $dest);
1590
}
1591
1592
/**
1593
 * Check table prefix in SQL query.
1594
 *
1595
 * @param string $table Table name
1596
 * 
1597
 * @return string
1598
 */
1599
function prefixTable(string $table): string
1600
{
1601
    $safeTable = htmlspecialchars(DB_PREFIX . $table);
1602
    if (! empty($safeTable)) {
1603
        // sanitize string
1604
        return $safeTable;
1605
    }
1606
    // stop error no table
1607
    return 'table_not_exists';
1608
}
1609
1610
/**
1611
 * GenerateCryptKey
1612
 *
1613
 * @param int     $size      Length
1614
 * @param bool $secure Secure
1615
 * @param bool $numerals Numerics
1616
 * @param bool $uppercase Uppercase letters
1617
 * @param bool $symbols Symbols
1618
 * @param bool $lowercase Lowercase
1619
 * @param array   $SETTINGS  SETTINGS
1620
 * 
1621
 * @return string
1622
 */
1623
function GenerateCryptKey(
1624
    int $size = 10,
1625
    bool $secure = false,
1626
    bool $numerals = false,
1627
    bool $uppercase = false,
1628
    bool $symbols = false,
1629
    bool $lowercase = false,
1630
    array $SETTINGS = []
1631
): string {
1632
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1633
    $generator = new SplClassLoader('PasswordGenerator\Generator', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1634
    $generator->register();
1635
    $generator = new PasswordGenerator\Generator\ComputerPasswordGenerator();
1636
    // Is PHP7 being used?
1637
    if (version_compare(PHP_VERSION, '7.0.0', '>=')) {
1638
        $php7generator = new SplClassLoader('PasswordGenerator\RandomGenerator', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1639
        $php7generator->register();
1640
        $generator->setRandomGenerator(new PasswordGenerator\RandomGenerator\Php7RandomGenerator());
1641
    }
1642
    
1643
    // Manage size
1644
    $generator->setLength((int) $size);
1645
    if ($secure === true) {
1646
        $generator->setSymbols(true);
1647
        $generator->setLowercase(true);
1648
        $generator->setUppercase(true);
1649
        $generator->setNumbers(true);
1650
    } else {
1651
        $generator->setLowercase($lowercase);
1652
        $generator->setUppercase($uppercase);
1653
        $generator->setNumbers($numerals);
1654
        $generator->setSymbols($symbols);
1655
    }
1656
1657
    return $generator->generatePasswords()[0];
1658
}
1659
1660
/**
1661
 * Send sysLOG message
1662
 *
1663
 * @param string    $message
1664
 * @param string    $host
1665
 * @param int       $port
1666
 * @param string    $component
1667
 * 
1668
 * @return void
1669
*/
1670
function send_syslog($message, $host, $port, $component = 'teampass'): void
1671
{
1672
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1673
    $syslog_message = '<123>' . date('M d H:i:s ') . $component . ': ' . $message;
1674
    socket_sendto($sock, (string) $syslog_message, strlen($syslog_message), 0, (string) $host, (int) $port);
1675
    socket_close($sock);
1676
}
1677
1678
/**
1679
 * Permits to log events into DB
1680
 *
1681
 * @param array  $SETTINGS Teampass settings
1682
 * @param string $type     Type
1683
 * @param string $label    Label
1684
 * @param string $who      Who
1685
 * @param string $login    Login
1686
 * @param string $field_1  Field
1687
 * 
1688
 * @return void
1689
 */
1690
function logEvents(array $SETTINGS, string $type, string $label, string $who, ?string $login = null, ?string $field_1 = null): void
1691
{
1692
    if (empty($who)) {
1693
        $who = getClientIpServer();
1694
    }
1695
1696
    // include librairies & connect to DB
1697
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1698
    if (defined('DB_PASSWD_CLEAR') === false) {
1699
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1700
    }
1701
    DB::$host = DB_HOST;
1702
    DB::$user = DB_USER;
1703
    DB::$password = DB_PASSWD_CLEAR;
1704
    DB::$dbName = DB_NAME;
1705
    DB::$port = DB_PORT;
1706
    DB::$encoding = DB_ENCODING;
1707
    DB::insert(
1708
        prefixTable('log_system'),
1709
        [
1710
            'type' => $type,
1711
            'date' => time(),
1712
            'label' => $label,
1713
            'qui' => $who,
1714
            'field_1' => $field_1 === null ? '' : $field_1,
1715
        ]
1716
    );
1717
    // If SYSLOG
1718
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1719
        if ($type === 'user_mngt') {
1720
            send_syslog(
1721
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1722
                $SETTINGS['syslog_host'],
1723
                $SETTINGS['syslog_port'],
1724
                'teampass'
1725
            );
1726
        } else {
1727
            send_syslog(
1728
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1729
                $SETTINGS['syslog_host'],
1730
                $SETTINGS['syslog_port'],
1731
                'teampass'
1732
            );
1733
        }
1734
    }
1735
}
1736
1737
/**
1738
 * Log events.
1739
 *
1740
 * @param array  $SETTINGS        Teampass settings
1741
 * @param int    $item_id         Item id
1742
 * @param string $item_label      Item label
1743
 * @param int    $id_user         User id
1744
 * @param string $action          Code for reason
1745
 * @param string $login           User login
1746
 * @param string $raison          Code for reason
1747
 * @param string $encryption_type Encryption on
1748
 * 
1749
 * @return void
1750
 */
1751
function logItems(
1752
    array $SETTINGS,
1753
    int $item_id,
1754
    string $item_label,
1755
    int $id_user,
1756
    string $action,
1757
    ?string $login = null,
1758
    ?string $raison = null,
1759
    ?string $encryption_type = null
1760
): void {
1761
    // include librairies & connect to DB
1762
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1763
    if (defined('DB_PASSWD_CLEAR') === false) {
1764
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1765
    }
1766
    DB::$host = DB_HOST;
1767
    DB::$user = DB_USER;
1768
    DB::$password = DB_PASSWD_CLEAR;
1769
    DB::$dbName = DB_NAME;
1770
    DB::$port = DB_PORT;
1771
    DB::$encoding = DB_ENCODING;
1772
    // Insert log in DB
1773
    DB::insert(
1774
        prefixTable('log_items'),
1775
        [
1776
            'id_item' => $item_id,
1777
            'date' => time(),
1778
            'id_user' => $id_user,
1779
            'action' => $action,
1780
            'raison' => $raison,
1781
            'raison_iv' => '',
1782
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1783
        ]
1784
    );
1785
    // Timestamp the last change
1786
    if ($action === 'at_creation' || $action === 'at_modifiation' || $action === 'at_delete' || $action === 'at_import') {
1787
        DB::update(
1788
            prefixTable('misc'),
1789
            [
1790
                'valeur' => time(),
1791
            ],
1792
            'type = %s AND intitule = %s',
1793
            'timestamp',
1794
            'last_item_change'
1795
        );
1796
    }
1797
1798
    // SYSLOG
1799
    if (isset($SETTINGS['syslog_enable']) === true && $SETTINGS['syslog_enable'] === '1') {
1800
        // Extract reason
1801
        $attribute = is_null($raison) === true ? '' : explode(' : ', $raison);
1802
        // Get item info if not known
1803
        if (empty($item_label) === true) {
1804
            $dataItem = DB::queryfirstrow(
1805
                'SELECT id, id_tree, label
1806
                FROM ' . prefixTable('items') . '
1807
                WHERE id = %i',
1808
                $item_id
1809
            );
1810
            $item_label = $dataItem['label'];
1811
        }
1812
1813
        send_syslog(
1814
            'action=' . str_replace('at_', '', $action) .
1815
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1816
                ' itemno=' . $item_id .
1817
                ' user=' . is_null($login) === true ? '' : addslashes((string) $login) .
1818
                ' itemname="' . addslashes($item_label) . '"',
1819
            $SETTINGS['syslog_host'],
1820
            $SETTINGS['syslog_port'],
1821
            'teampass'
1822
        );
1823
    }
1824
1825
    // send notification if enabled
1826
    notifyOnChange($item_id, $action, $SETTINGS);
1827
}
1828
1829
/**
1830
 * If enabled, then notify admin/manager.
1831
 *
1832
 * @param int    $item_id  Item id
1833
 * @param string $action   Action to do
1834
 * @param array  $SETTINGS Teampass settings
1835
 * 
1836
 * @return void
1837
 */
1838
function notifyOnChange(int $item_id, string $action, array $SETTINGS): void
1839
{
1840
    if (
1841
        isset($SETTINGS['enable_email_notification_on_item_shown']) === true
1842
        && (int) $SETTINGS['enable_email_notification_on_item_shown'] === 1
1843
        && $action === 'at_shown'
1844
    ) {
1845
        // Load superglobal
1846
        include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1847
        $superGlobal = new protect\SuperGlobal\SuperGlobal();
1848
        // Get superglobals
1849
        $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1850
        $globalsName = $superGlobal->get('name', 'SESSION');
1851
        $globalsNotifiedEmails = $superGlobal->get('listNotificationEmails', 'SESSION');
1852
        // Get info about item
1853
        $dataItem = DB::queryfirstrow(
1854
            'SELECT id, id_tree, label
1855
            FROM ' . prefixTable('items') . '
1856
            WHERE id = %i',
1857
            $item_id
1858
        );
1859
        $item_label = $dataItem['label'];
1860
        // send back infos
1861
        DB::insert(
1862
            prefixTable('emails'),
1863
            [
1864
                'timestamp' => time(),
1865
                'subject' => langHdl('email_on_open_notification_subject'),
1866
                'body' => str_replace(
1867
                    ['#tp_user#', '#tp_item#', '#tp_link#'],
1868
                    [
1869
                        addslashes($globalsName . ' ' . $globalsLastname),
1870
                        addslashes($item_label),
1871
                        $SETTINGS['cpassman_url'] . '/index.php?page=items&group=' . $dataItem['id_tree'] . '&id=' . $item_id,
1872
                    ],
1873
                    langHdl('email_on_open_notification_mail')
1874
                ),
1875
                'receivers' => $globalsNotifiedEmails,
1876
                'status' => '',
1877
            ]
1878
        );
1879
    }
1880
}
1881
1882
/**
1883
 * Prepare notification email to subscribers.
1884
 *
1885
 * @param int    $item_id  Item id
1886
 * @param string $label    Item label
1887
 * @param array  $changes  List of changes
1888
 * @param array  $SETTINGS Teampass settings
1889
 * 
1890
 * @return void
1891
 */
1892
function notifyChangesToSubscribers(int $item_id, string $label, array $changes, array $SETTINGS): void
1893
{
1894
    // Load superglobal
1895
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1896
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1897
    // Get superglobals
1898
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
1899
    $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1900
    $globalsName = $superGlobal->get('name', 'SESSION');
1901
    // send email to user that what to be notified
1902
    $notification = DB::queryOneColumn(
1903
        'email',
1904
        'SELECT *
1905
        FROM ' . prefixTable('notification') . ' AS n
1906
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1907
        WHERE n.item_id = %i AND n.user_id != %i',
1908
        $item_id,
1909
        $globalsUserId
1910
    );
1911
    if (DB::count() > 0) {
1912
        // Prepare path
1913
        $path = geItemReadablePath($item_id, '', $SETTINGS);
1914
        // Get list of changes
1915
        $htmlChanges = '<ul>';
1916
        foreach ($changes as $change) {
1917
            $htmlChanges .= '<li>' . $change . '</li>';
1918
        }
1919
        $htmlChanges .= '</ul>';
1920
        // send email
1921
        DB::insert(
1922
            prefixTable('emails'),
1923
            [
1924
                'timestamp' => time(),
1925
                'subject' => langHdl('email_subject_item_updated'),
1926
                'body' => str_replace(
1927
                    ['#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'],
1928
                    [$label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges],
1929
                    langHdl('email_body_item_updated')
1930
                ),
1931
                'receivers' => implode(',', $notification),
1932
                'status' => '',
1933
            ]
1934
        );
1935
    }
1936
}
1937
1938
/**
1939
 * Returns the Item + path.
1940
 *
1941
 * @param int    $id_tree  Node id
1942
 * @param string $label    Label
1943
 * @param array  $SETTINGS TP settings
1944
 * 
1945
 * @return string
1946
 */
1947
function geItemReadablePath(int $id_tree, string $label, array $SETTINGS): string
1948
{
1949
    // Class loader
1950
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1951
    //Load Tree
1952
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1953
    $tree->register();
1954
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1955
    $arbo = $tree->getPath($id_tree, true);
1956
    $path = '';
1957
    foreach ($arbo as $elem) {
1958
        if (empty($path) === true) {
1959
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
1960
        } else {
1961
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
1962
        }
1963
    }
1964
1965
    // Build text to show user
1966
    if (empty($label) === false) {
1967
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
1968
    }
1969
    return empty($path) === true ? '' : $path;
1970
}
1971
1972
/**
1973
 * Get the client ip address.
1974
 *
1975
 * @return string IP address
1976
 */
1977
function getClientIpServer(): string
1978
{
1979
    if (getenv('HTTP_CLIENT_IP')) {
1980
        $ipaddress = getenv('HTTP_CLIENT_IP');
1981
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
1982
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
1983
    } elseif (getenv('HTTP_X_FORWARDED')) {
1984
        $ipaddress = getenv('HTTP_X_FORWARDED');
1985
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
1986
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
1987
    } elseif (getenv('HTTP_FORWARDED')) {
1988
        $ipaddress = getenv('HTTP_FORWARDED');
1989
    } elseif (getenv('REMOTE_ADDR')) {
1990
        $ipaddress = getenv('REMOTE_ADDR');
1991
    } else {
1992
        $ipaddress = 'UNKNOWN';
1993
    }
1994
1995
    return $ipaddress;
1996
}
1997
1998
/**
1999
 * Escape all HTML, JavaScript, and CSS.
2000
 *
2001
 * @param string $input    The input string
2002
 * @param string $encoding Which character encoding are we using?
2003
 * 
2004
 * @return string
2005
 */
2006
function noHTML(string $input, string $encoding = 'UTF-8'): string
2007
{
2008
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
2009
}
2010
2011
/**
2012
 * Permits to handle the Teampass config file
2013
 * $action accepts "rebuild" and "update"
2014
 *
2015
 * @param string $action   Action to perform
2016
 * @param array  $SETTINGS Teampass settings
2017
 * @param string $field    Field to refresh
2018
 * @param string $value    Value to set
2019
 *
2020
 * @return string|bool
2021
 */
2022
function handleConfigFile($action, $SETTINGS, $field = null, $value = null)
2023
{
2024
    $tp_config_file = $SETTINGS['cpassman_dir'] . '/includes/config/tp.config.php';
2025
    // include librairies & connect to DB
2026
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2027
    if (defined('DB_PASSWD_CLEAR') === false) {
2028
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2029
    }
2030
    DB::$host = DB_HOST;
2031
    DB::$user = DB_USER;
2032
    DB::$password = DB_PASSWD_CLEAR;
2033
    DB::$dbName = DB_NAME;
2034
    DB::$port = DB_PORT;
2035
    DB::$encoding = DB_ENCODING;
2036
    if (file_exists($tp_config_file) === false || $action === 'rebuild') {
2037
        // perform a copy
2038
        if (file_exists($tp_config_file)) {
2039
            if (! copy($tp_config_file, $tp_config_file . '.' . date('Y_m_d_His', time()))) {
2040
                return "ERROR: Could not copy file '" . $tp_config_file . "'";
2041
            }
2042
        }
2043
2044
        // regenerate
2045
        $data = [];
2046
        $data[0] = "<?php\n";
2047
        $data[1] = "global \$SETTINGS;\n";
2048
        $data[2] = "\$SETTINGS = array (\n";
2049
        $rows = DB::query(
2050
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s',
2051
            'admin'
2052
        );
2053
        foreach ($rows as $record) {
2054
            array_push($data, "    '" . $record['intitule'] . "' => '" . $record['valeur'] . "',\n");
2055
        }
2056
        array_push($data, ");\n");
2057
        $data = array_unique($data);
2058
    // ---
2059
    } elseif ($action === 'update' && empty($field) === false) {
2060
        $data = file($tp_config_file);
2061
        $inc = 0;
2062
        $bFound = false;
2063
        foreach ($data as $line) {
2064
            if (stristr($line, ');')) {
2065
                break;
2066
            }
2067
2068
            if (stristr($line, "'" . $field . "' => '")) {
2069
                $data[$inc] = "    '" . $field . "' => '" . filter_var($value, FILTER_SANITIZE_STRING) . "',\n";
2070
                $bFound = true;
2071
                break;
2072
            }
2073
            ++$inc;
2074
        }
2075
        if ($bFound === false) {
2076
            $data[$inc] = "    '" . $field . "' => '" . filter_var($value, FILTER_SANITIZE_STRING) . "',\n);\n";
2077
        }
2078
    }
2079
2080
    // update file
2081
    file_put_contents($tp_config_file, implode('', $data ?? []));
2082
    return true;
2083
}
2084
2085
/**
2086
 * Permits to replace &#92; to permit correct display
2087
 *
2088
 * @param string $input Some text
2089
 * 
2090
 * @return string
2091
 */
2092
function handleBackslash(string $input): string
2093
{
2094
    return str_replace('&amp;#92;', '&#92;', $input);
2095
}
2096
2097
/**
2098
 * Permits to load settings
2099
 * 
2100
 * @return void
2101
*/
2102
function loadSettings(): void
2103
{
2104
    global $SETTINGS;
2105
    /* LOAD CPASSMAN SETTINGS */
2106
    if (! isset($SETTINGS['loaded']) || $SETTINGS['loaded'] !== 1) {
2107
        $SETTINGS = [];
2108
        $SETTINGS['duplicate_folder'] = 0;
2109
        //by default, this is set to 0;
2110
        $SETTINGS['duplicate_item'] = 0;
2111
        //by default, this is set to 0;
2112
        $SETTINGS['number_of_used_pw'] = 5;
2113
        //by default, this value is set to 5;
2114
        $settings = [];
2115
        $rows = DB::query(
2116
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
2117
            [
2118
                'type' => 'admin',
2119
                'type2' => 'settings',
2120
            ]
2121
        );
2122
        foreach ($rows as $record) {
2123
            if ($record['type'] === 'admin') {
2124
                $SETTINGS[$record['intitule']] = $record['valeur'];
2125
            } else {
2126
                $settings[$record['intitule']] = $record['valeur'];
2127
            }
2128
        }
2129
        $SETTINGS['loaded'] = 1;
2130
        $SETTINGS['default_session_expiration_time'] = 5;
2131
    }
2132
}
2133
2134
/**
2135
 * check if folder has custom fields.
2136
 * Ensure that target one also has same custom fields
2137
 * 
2138
 * @param int $source_id
2139
 * @param int $target_id 
2140
 * 
2141
 * @return bool
2142
*/
2143
function checkCFconsistency(int $source_id, int $target_id): bool
2144
{
2145
    $source_cf = [];
2146
    $rows = DB::QUERY(
2147
        'SELECT id_category
2148
            FROM ' . prefixTable('categories_folders') . '
2149
            WHERE id_folder = %i',
2150
        $source_id
2151
    );
2152
    foreach ($rows as $record) {
2153
        array_push($source_cf, $record['id_category']);
2154
    }
2155
2156
    $target_cf = [];
2157
    $rows = DB::QUERY(
2158
        'SELECT id_category
2159
            FROM ' . prefixTable('categories_folders') . '
2160
            WHERE id_folder = %i',
2161
        $target_id
2162
    );
2163
    foreach ($rows as $record) {
2164
        array_push($target_cf, $record['id_category']);
2165
    }
2166
2167
    $cf_diff = array_diff($source_cf, $target_cf);
2168
    if (count($cf_diff) > 0) {
2169
        return false;
2170
    }
2171
2172
    return true;
2173
}
2174
2175
/**
2176
 * Will encrypte/decrypt a fil eusing Defuse.
2177
 *
2178
 * @param string $type        can be either encrypt or decrypt
2179
 * @param string $source_file path to source file
2180
 * @param string $target_file path to target file
2181
 * @param array  $SETTINGS    Settings
2182
 * @param string $password    A password
2183
 *
2184
 * @return string|bool
2185
 */
2186
function prepareFileWithDefuse(
2187
    string $type,
2188
    string $source_file,
2189
    string $target_file,
2190
    array $SETTINGS,
2191
    string $password = null
2192
) {
2193
    // Load AntiXSS
2194
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/AntiXSS.php';
2195
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/ASCII.php';
2196
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/UTF8.php';
2197
    $antiXss = new voku\helper\AntiXSS();
2198
    // Protect against bad inputs
2199
    if (is_array($source_file) === true || is_array($target_file) === true) {
2200
        return 'error_cannot_be_array';
2201
    }
2202
2203
    // Sanitize
2204
    $source_file = $antiXss->xss_clean($source_file);
2205
    $target_file = $antiXss->xss_clean($target_file);
2206
    if (empty($password) === true || is_null($password) === true) {
2207
        // get KEY to define password
2208
        $ascii_key = file_get_contents(SECUREPATH . '/teampass-seckey.txt');
2209
        $password = \Defuse\Crypto\Key::loadFromAsciiSafeString($ascii_key);
2210
    }
2211
2212
    $err = '';
2213
    if ($type === 'decrypt') {
2214
        // Decrypt file
2215
        $err = defuseFileDecrypt(
2216
            $source_file,
2217
            $target_file,
2218
            $SETTINGS, /** @scrutinizer ignore-type */
2219
            $password
2220
        );
2221
    } elseif ($type === 'encrypt') {
2222
        // Encrypt file
2223
        $err = defuseFileEncrypt(
2224
            $source_file,
2225
            $target_file,
2226
            $SETTINGS, /** @scrutinizer ignore-type */
2227
            $password
2228
        );
2229
    }
2230
2231
    // return error
2232
    return empty($err) === false ? $err : '';
2233
}
2234
2235
/**
2236
 * Encrypt a file with Defuse.
2237
 *
2238
 * @param string $source_file path to source file
2239
 * @param string $target_file path to target file
2240
 * @param array  $SETTINGS    Settings
2241
 * @param string $password    A password
2242
 *
2243
 * @return string|bool
2244
 */
2245
function defuseFileEncrypt(
2246
    string $source_file,
2247
    string $target_file,
2248
    array $SETTINGS,
2249
    string $password = null
2250
) {
2251
    // load PhpEncryption library
2252
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2253
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2254
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2255
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2256
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2257
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2258
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2259
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2260
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2261
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2262
    try {
2263
        \Defuse\Crypto\File::encryptFileWithPassword(
2264
            $source_file,
2265
            $target_file,
2266
            $password
2267
        );
2268
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2269
        $err = 'wrong_key';
2270
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2271
        $err = $ex;
2272
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2273
        $err = $ex;
2274
    }
2275
2276
    // return error
2277
    return empty($err) === false ? $err : true;
2278
}
2279
2280
/**
2281
 * Decrypt a file with Defuse.
2282
 *
2283
 * @param string $source_file path to source file
2284
 * @param string $target_file path to target file
2285
 * @param array  $SETTINGS    Settings
2286
 * @param string $password    A password
2287
 *
2288
 * @return string|bool
2289
 */
2290
function defuseFileDecrypt(
2291
    string $source_file,
2292
    string $target_file,
2293
    array $SETTINGS,
2294
    string $password = null
2295
) {
2296
    // load PhpEncryption library
2297
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2298
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2299
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2300
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2301
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2302
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2303
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2304
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2305
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2306
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2307
    try {
2308
        \Defuse\Crypto\File::decryptFileWithPassword(
2309
            $source_file,
2310
            $target_file,
2311
            $password
2312
        );
2313
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2314
        $err = 'wrong_key';
2315
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2316
        $err = $ex;
2317
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2318
        $err = $ex;
2319
    }
2320
2321
    // return error
2322
    return empty($err) === false ? $err : true;
2323
}
2324
2325
/*
2326
* NOT TO BE USED
2327
*/
2328
/**
2329
 * Undocumented function.
2330
 *
2331
 * @param string $text Text to debug
2332
 */
2333
function debugTeampass(string $text): void
2334
{
2335
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
2336
    if ($debugFile !== false) {
2337
        fputs($debugFile, $text);
2338
        fclose($debugFile);
2339
    }
2340
}
2341
2342
/**
2343
 * DELETE the file with expected command depending on server type.
2344
 *
2345
 * @param string $file     Path to file
2346
 * @param array  $SETTINGS Teampass settings
2347
 *
2348
 * @return void
2349
 */
2350
function fileDelete(string $file, array $SETTINGS): void
2351
{
2352
    // Load AntiXSS
2353
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/ASCII.php';
2354
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/UTF8.php';
2355
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/AntiXSS.php';
2356
    $antiXss = new voku\helper\AntiXSS();
2357
    $file = $antiXss->xss_clean($file);
2358
    if (is_file($file)) {
2359
        unlink($file);
2360
    }
2361
}
2362
2363
/**
2364
 * Permits to extract the file extension.
2365
 *
2366
 * @param string $file File name
2367
 *
2368
 * @return string
2369
 */
2370
function getFileExtension(string $file): string
2371
{
2372
    if (strpos($file, '.') === false) {
2373
        return $file;
2374
    }
2375
2376
    return substr($file, strrpos($file, '.') + 1);
2377
}
2378
2379
/**
2380
 * Chmods files and folders with different permissions.
2381
 *
2382
 * This is an all-PHP alternative to using: \n
2383
 * <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2384
 * <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2385
 *
2386
 * @author Jeppe Toustrup (tenzer at tenzer dot dk)
2387
  *
2388
 * @param string $path      An either relative or absolute path to a file or directory which should be processed.
2389
 * @param int    $filePerm The permissions any found files should get.
2390
 * @param int    $dirPerm  The permissions any found folder should get.
2391
 *
2392
 * @return bool Returns TRUE if the path if found and FALSE if not.
2393
 *
2394
 * @warning The permission levels has to be entered in octal format, which
2395
 * normally means adding a zero ("0") in front of the permission level. \n
2396
 * More info at: http://php.net/chmod.
2397
*/
2398
2399
function recursiveChmod(
2400
    string $path,
2401
    int $filePerm = 0644,
2402
    int  $dirPerm = 0755
2403
) {
2404
    // Check if the path exists
2405
    if (! file_exists($path)) {
2406
        return false;
2407
    }
2408
2409
    // See whether this is a file
2410
    if (is_file($path)) {
2411
        // Chmod the file with our given filepermissions
2412
        chmod($path, $filePerm);
2413
    // If this is a directory...
2414
    } elseif (is_dir($path)) {
2415
        // Then get an array of the contents
2416
        $foldersAndFiles = scandir($path);
2417
        // Remove "." and ".." from the list
2418
        $entries = array_slice($foldersAndFiles, 2);
2419
        // Parse every result...
2420
        foreach ($entries as $entry) {
2421
            // And call this function again recursively, with the same permissions
2422
            recursiveChmod($path.'/'.$entry, $filePerm, $dirPerm);
2423
        }
2424
2425
        // When we are done with the contents of the directory, we chmod the directory itself
2426
        chmod($path, $dirPerm);
2427
    }
2428
2429
    // Everything seemed to work out well, return true
2430
    return true;
2431
}
2432
2433
/**
2434
 * Check if user can access to this item.
2435
 *
2436
 * @param int   $item_id ID of item
2437
 * @param array $SETTINGS
2438
 *
2439
 * @return bool|string
2440
 */
2441
function accessToItemIsGranted(int $item_id, array $SETTINGS)
2442
{
2443
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2444
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2445
    // Prepare superGlobal variables
2446
    $session_groupes_visibles = $superGlobal->get('groupes_visibles', 'SESSION');
2447
    $session_list_restricted_folders_for_items = $superGlobal->get('list_restricted_folders_for_items', 'SESSION');
2448
    // Load item data
2449
    $data = DB::queryFirstRow(
2450
        'SELECT id_tree
2451
        FROM ' . prefixTable('items') . '
2452
        WHERE id = %i',
2453
        $item_id
2454
    );
2455
    // Check if user can access this folder
2456
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2457
        // Now check if this folder is restricted to user
2458
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2459
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2460
        ) {
2461
            return 'ERR_FOLDER_NOT_ALLOWED';
2462
        }
2463
    }
2464
2465
    return true;
2466
}
2467
2468
/**
2469
 * Creates a unique key.
2470
 *
2471
 * @param int $lenght Key lenght
2472
 *
2473
 * @return string
2474
 */
2475
function uniqidReal(int $lenght = 13): string
2476
{
2477
    if (function_exists('random_bytes')) {
2478
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2479
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2480
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2481
    } else {
2482
        throw new Exception('no cryptographically secure random function available');
2483
    }
2484
2485
    return substr(bin2hex($bytes), 0, $lenght);
2486
}
2487
2488
/**
2489
 * Obfuscate an email.
2490
 *
2491
 * @param string $email Email address
2492
 *
2493
 * @return string
2494
 */
2495
function obfuscateEmail(string $email): string
2496
{
2497
    $email = explode("@", $email);
2498
    $name = $email[0];
2499
    if (strlen($name) > 3) {
2500
        $name = substr($name, 0, 2);
2501
        for ($i = 0; $i < strlen($email[0]) - 3; $i++) {
2502
            $name .= "*";
2503
        }
2504
        $name .= substr($email[0], -1, 1);
2505
    }
2506
    $host = explode(".", $email[1])[0];
2507
    if (strlen($host) > 3) {
2508
        $host = substr($host, 0, 1);
2509
        for ($i = 0; $i < strlen(explode(".", $email[1])[0]) - 2; $i++) {
2510
            $host .= "*";
2511
        }
2512
        $host .= substr(explode(".", $email[1])[0], -1, 1);
2513
    }
2514
    $email = $name . "@" . $host . "." . explode(".", $email[1])[1];
2515
    return $email;
2516
}
2517
2518
/**
2519
 * Perform a Query.
2520
 *
2521
 * @param array  $SETTINGS Teamapss settings
2522
 * @param string $fields   Fields to use
2523
 * @param string $table    Table to use
2524
 *
2525
 * @return array
2526
 */
2527
function performDBQuery(array $SETTINGS, string $fields, string $table): array
2528
{
2529
    // include librairies & connect to DB
2530
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2531
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2532
    if (defined('DB_PASSWD_CLEAR') === false) {
2533
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2534
    }
2535
    DB::$host = DB_HOST;
2536
    DB::$user = DB_USER;
2537
    DB::$password = DB_PASSWD_CLEAR;
2538
    DB::$dbName = DB_NAME;
2539
    DB::$port = DB_PORT;
2540
    DB::$encoding = DB_ENCODING;
2541
    // Insert log in DB
2542
    return DB::query(
2543
        'SELECT ' . $fields . '
2544
        FROM ' . prefixTable($table)
2545
    );
2546
}
2547
2548
/**
2549
 * Undocumented function.
2550
 *
2551
 * @param int $bytes Size of file
2552
 *
2553
 * @return string
2554
 */
2555
function formatSizeUnits(int $bytes): string
2556
{
2557
    if ($bytes >= 1073741824) {
2558
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2559
    } elseif ($bytes >= 1048576) {
2560
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2561
    } elseif ($bytes >= 1024) {
2562
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2563
    } elseif ($bytes > 1) {
2564
        $bytes .= ' bytes';
2565
    } elseif ($bytes === 1) {
2566
        $bytes .= ' byte';
2567
    } else {
2568
        $bytes = '0 bytes';
2569
    }
2570
2571
    return $bytes;
2572
}
2573
2574
/**
2575
 * Generate user pair of keys.
2576
 *
2577
 * @param string $userPwd User password
2578
 *
2579
 * @return array
2580
 */
2581
function generateUserKeys(string $userPwd): array
2582
{
2583
    // include library
2584
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2585
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2586
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2587
    // Load classes
2588
    $rsa = new Crypt_RSA();
2589
    $cipher = new Crypt_AES();
2590
    // Create the private and public key
2591
    $res = $rsa->createKey(4096);
2592
    // Encrypt the privatekey
2593
    $cipher->setPassword($userPwd);
2594
    $privatekey = $cipher->encrypt($res['privatekey']);
2595
    return [
2596
        'private_key' => base64_encode($privatekey),
2597
        'public_key' => base64_encode($res['publickey']),
2598
        'private_key_clear' => base64_encode($res['privatekey']),
2599
    ];
2600
}
2601
2602
/**
2603
 * Permits to decrypt the user's privatekey.
2604
 *
2605
 * @param string $userPwd        User password
2606
 * @param string $userPrivateKey User private key
2607
 *
2608
 * @return string
2609
 */
2610
function decryptPrivateKey(string $userPwd, string $userPrivateKey): string
2611
{
2612
    if (empty($userPwd) === false) {
2613
        include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2614
        // Load classes
2615
        $cipher = new Crypt_AES();
2616
        // Encrypt the privatekey
2617
        $cipher->setPassword($userPwd);
2618
        try {
2619
            return base64_encode((string) $cipher->decrypt(base64_decode($userPrivateKey)));
2620
        } catch (Exception $e) {
2621
            return $e;
2622
        }
2623
    }
2624
    return '';
2625
}
2626
2627
/**
2628
 * Permits to encrypt the user's privatekey.
2629
 *
2630
 * @param string $userPwd        User password
2631
 * @param string $userPrivateKey User private key
2632
 *
2633
 * @return string
2634
 */
2635
function encryptPrivateKey(string $userPwd, string $userPrivateKey): string
2636
{
2637
    if (empty($userPwd) === false) {
2638
        include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2639
        // Load classes
2640
        $cipher = new Crypt_AES();
2641
        // Encrypt the privatekey
2642
        $cipher->setPassword($userPwd);        
2643
        try {
2644
            return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2645
        } catch (Exception $e) {
2646
            return $e;
2647
        }
2648
    }
2649
    return '';
2650
}
2651
2652
/**
2653
 * Encrypts a string using AES.
2654
 *
2655
 * @param string $data String to encrypt
2656
 *
2657
 * @return array
2658
 */
2659
function doDataEncryption(string $data): array
2660
{
2661
    // Includes
2662
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2663
    // Load classes
2664
    $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2665
    // Generate an object key
2666
    $objectKey = uniqidReal(32);
2667
    // Set it as password
2668
    $cipher->setPassword($objectKey);
2669
    return [
2670
        'encrypted' => base64_encode($cipher->encrypt($data)),
2671
        'objectKey' => base64_encode($objectKey),
2672
    ];
2673
}
2674
2675
/**
2676
 * Decrypts a string using AES.
2677
 *
2678
 * @param string $data Encrypted data
2679
 * @param string $key  Key to uncrypt
2680
 *
2681
 * @return string
2682
 */
2683
function doDataDecryption(string $data, string $key): string
2684
{
2685
    // Includes
2686
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2687
    // Load classes
2688
    $cipher = new Crypt_AES();
2689
    // Set the object key
2690
    $cipher->setPassword(base64_decode($key));
2691
    return base64_encode($cipher->decrypt(base64_decode($data)));
2692
}
2693
2694
/**
2695
 * Encrypts using RSA a string using a public key.
2696
 *
2697
 * @param string $key       Key to be encrypted
2698
 * @param string $publicKey User public key
2699
 *
2700
 * @return string
2701
 */
2702
function encryptUserObjectKey(string $key, string $publicKey): string
2703
{
2704
    // Includes
2705
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2706
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2707
    // Load classes
2708
    $rsa = new Crypt_RSA();
2709
    $rsa->loadKey(base64_decode($publicKey));
2710
    // Encrypt
2711
    return base64_encode($rsa->encrypt(base64_decode($key)));
2712
}
2713
2714
/**
2715
 * Decrypts using RSA an encrypted string using a private key.
2716
 *
2717
 * @param string $key        Encrypted key
2718
 * @param string $privateKey User private key
2719
 *
2720
 * @return string
2721
 */
2722
function decryptUserObjectKey(string $key, string $privateKey): string
2723
{
2724
    // Includes
2725
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2726
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2727
    // Load classes
2728
    $rsa = new Crypt_RSA();
2729
    $rsa->loadKey(base64_decode($privateKey));
2730
    // Decrypt
2731
    try {
2732
        $ret = base64_encode($rsa->decrypt(base64_decode($key)));
2733
    } catch (Exception $e) {
2734
        return $e;
2735
    }
2736
2737
    return $ret;
2738
}
2739
2740
/**
2741
 * Encrypts a file.
2742
 *
2743
 * @param string $fileInName File name
2744
 * @param string $fileInPath Path to file
2745
 *
2746
 * @return array
2747
 */
2748
function encryptFile(string $fileInName, string $fileInPath): array
2749
{
2750
    if (defined('FILE_BUFFER_SIZE') === false) {
2751
        define('FILE_BUFFER_SIZE', 128 * 1024);
2752
    }
2753
2754
    // Includes
2755
    include_once '../includes/config/include.php';
2756
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2757
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2758
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2759
    // Load classes
2760
    $cipher = new Crypt_AES();
2761
    // Generate an object key
2762
    $objectKey = uniqidReal(32);
2763
    // Set it as password
2764
    $cipher->setPassword($objectKey);
2765
    // Prevent against out of memory
2766
    $cipher->enableContinuousBuffer();
2767
    //$cipher->disablePadding();
2768
2769
    // Encrypt the file content
2770
    $plaintext = file_get_contents(
2771
        filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL)
2772
    );
2773
    $ciphertext = $cipher->encrypt($plaintext);
2774
    // Save new file
2775
    $hash = md5($plaintext);
2776
    $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2777
    file_put_contents($fileOut, $ciphertext);
2778
    unlink($fileInPath . '/' . $fileInName);
2779
    return [
2780
        'fileHash' => base64_encode($hash),
2781
        'objectKey' => base64_encode($objectKey),
2782
    ];
2783
}
2784
2785
/**
2786
 * Decrypt a file.
2787
 *
2788
 * @param string $fileName File name
2789
 * @param string $filePath Path to file
2790
 * @param string $key      Key to use
2791
 *
2792
 * @return string
2793
 */
2794
function decryptFile(string $fileName, string $filePath, string $key): string
2795
{
2796
    if (! defined('FILE_BUFFER_SIZE')) {
2797
        define('FILE_BUFFER_SIZE', 128 * 1024);
2798
    }
2799
2800
    // Includes
2801
    include_once '../includes/config/include.php';
2802
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2803
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2804
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2805
    // Get file name
2806
    $fileName = base64_decode($fileName);
2807
    // Load classes
2808
    $cipher = new Crypt_AES();
2809
    // Set the object key
2810
    $cipher->setPassword(base64_decode($key));
2811
    // Prevent against out of memory
2812
    $cipher->enableContinuousBuffer();
2813
    $cipher->disablePadding();
2814
    // Get file content
2815
    $ciphertext = file_get_contents($filePath . '/' . TP_FILE_PREFIX . $fileName);
2816
    // Decrypt file content and return
2817
    return base64_encode($cipher->decrypt($ciphertext));
2818
}
2819
2820
/**
2821
 * Generate a simple password
2822
 *
2823
 * @param int $length Length of string
2824
 * @param bool $symbolsincluded Allow symbols
2825
 *
2826
 * @return string
2827
 */
2828
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2829
{
2830
    // Generate new user password
2831
    $small_letters = range('a', 'z');
2832
    $big_letters = range('A', 'Z');
2833
    $digits = range(0, 9);
2834
    $symbols = $symbolsincluded === true ?
2835
        ['#', '_', '-', '@', '$', '+', '&'] : [];
2836
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2837
    $count = count($res);
2838
    // first variant
2839
2840
    $random_string = '';
2841
    for ($i = 0; $i < $length; ++$i) {
2842
        $random_string .= $res[random_int(0, $count - 1)];
2843
    }
2844
2845
    return $random_string;
2846
}
2847
2848
/**
2849
 * Permit to store the sharekey of an object for users.
2850
 *
2851
 * @param string $object_name             Type for table selection
2852
 * @param int    $post_folder_is_personal Personal
2853
 * @param int    $post_folder_id          Folder
2854
 * @param int    $post_object_id          Object
2855
 * @param string $objectKey               Object key
2856
 * @param array  $SETTINGS                Teampass settings
2857
 *
2858
 * @return void
2859
 */
2860
function storeUsersShareKey(
2861
    string $object_name,
2862
    int $post_folder_is_personal,
2863
    int $post_folder_id,
2864
    int $post_object_id,
2865
    string $objectKey,
2866
    array $SETTINGS
2867
): void {
2868
    // include librairies & connect to DB
2869
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2870
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2871
    if (defined('DB_PASSWD_CLEAR') === false) {
2872
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2873
    }
2874
    DB::$host = DB_HOST;
2875
    DB::$user = DB_USER;
2876
    DB::$password = DB_PASSWD_CLEAR;
2877
    DB::$dbName = DB_NAME;
2878
    DB::$port = DB_PORT;
2879
    DB::$encoding = DB_ENCODING;
2880
    // Delete existing entries for this object
2881
    DB::delete(
2882
        $object_name,
2883
        'object_id = %i',
2884
        $post_object_id
2885
    );
2886
    // Superglobals
2887
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2888
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2889
    // Prepare superGlobal variables
2890
    $sessionPpersonaFolders = $superGlobal->get('personal_folders', 'SESSION');
2891
    $sessionUserId = $superGlobal->get('user_id', 'SESSION');
2892
    $sessionUserPublicKey = $superGlobal->get('public_key', 'SESSION', 'user');
2893
    if (
2894
        (int) $post_folder_is_personal === 1
2895
        && in_array($post_folder_id, $sessionPpersonaFolders) === true
2896
    ) {
2897
        // If this is a personal object
2898
        // Only create the sharekey for user
2899
        DB::insert(
2900
            $object_name,
2901
            [
2902
                'object_id' => (int) $post_object_id,
2903
                'user_id' => (int) $sessionUserId,
2904
                'share_key' => encryptUserObjectKey($objectKey, $sessionUserPublicKey),
2905
            ]
2906
        );
2907
    } else {
2908
        // This is a public object
2909
        // Create sharekey for each user
2910
        $users = DB::query(
2911
            'SELECT id, public_key
2912
            FROM ' . prefixTable('users') . '
2913
            WHERE id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '")
2914
            AND public_key != ""'
2915
        );
2916
        foreach ($users as $user) {
2917
            // Insert in DB the new object key for this item by user
2918
            DB::insert(
2919
                $object_name,
2920
                [
2921
                    'object_id' => $post_object_id,
2922
                    'user_id' => (int) $user['id'],
2923
                    'share_key' => encryptUserObjectKey(
2924
                        $objectKey,
2925
                        $user['public_key']
2926
                    ),
2927
                ]
2928
            );
2929
        }
2930
    }
2931
}
2932
2933
/**
2934
 * Is this string base64 encoded?
2935
 *
2936
 * @param string $str Encoded string?
2937
 *
2938
 * @return bool
2939
 */
2940
function isBase64(string $str): bool
2941
{
2942
    $str = (string) trim($str);
2943
    if (! isset($str[0])) {
2944
        return false;
2945
    }
2946
2947
    $base64String = (string) base64_decode($str, true);
2948
    if ($base64String && base64_encode($base64String) === $str) {
2949
        return true;
2950
    }
2951
2952
    return false;
2953
}
2954
2955
/**
2956
 * Undocumented function
2957
 *
2958
 * @param string $field Parameter
2959
 *
2960
 * @return array|bool|resource|string
2961
 */
2962
function filterString(string $field)
2963
{
2964
    // Sanitize string
2965
    $field = filter_var(trim($field), FILTER_SANITIZE_STRING);
2966
    if (empty($field) === false) {
2967
        // Load AntiXSS
2968
        include_once '../includes/libraries/voku/helper/AntiXSS.php';
2969
        $antiXss = new voku\helper\AntiXSS();
2970
        // Return
2971
        return $antiXss->xss_clean($field);
2972
    }
2973
2974
    return false;
2975
}
2976
2977
/**
2978
 * CHeck if provided credentials are allowed on server
2979
 *
2980
 * @param string $login    User Login
2981
 * @param string $password User Pwd
2982
 * @param array  $SETTINGS Teampass settings
2983
 *
2984
 * @return bool
2985
 */
2986
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
2987
{
2988
    // Build ldap configuration array
2989
    $config = [
2990
        // Mandatory Configuration Options
2991
        'hosts' => [$SETTINGS['ldap_hosts']],
2992
        'base_dn' => $SETTINGS['ldap_bdn'],
2993
        'username' => $SETTINGS['ldap_username'],
2994
        'password' => $SETTINGS['ldap_password'],
2995
2996
        // Optional Configuration Options
2997
        'port' => $SETTINGS['ldap_port'],
2998
        'use_ssl' => (int) $SETTINGS['ldap_ssl'] === 1 ? true : false,
2999
        'use_tls' => (int) $SETTINGS['ldap_tls'] === 1 ? true : false,
3000
        'version' => 3,
3001
        'timeout' => 5,
3002
        'follow_referrals' => false,
3003
3004
        // Custom LDAP Options
3005
        'options' => [
3006
            // See: http://php.net/ldap_set_option
3007
            LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_HARD,
3008
        ],
3009
    ];
3010
    // Load expected libraries
3011
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Traits/Macroable.php';
3012
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Arr.php';
3013
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/DetectsErrors.php';
3014
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Connection.php';
3015
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/LdapInterface.php';
3016
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Ldap.php';
3017
    $ad = new SplClassLoader('LdapRecord', '../includes/libraries');
3018
    $ad->register();
3019
    $connection = new Connection($config);
3020
    // Connect to LDAP
3021
    try {
3022
        $connection->connect();
3023
    } catch (\LdapRecord\Auth\BindException $e) {
3024
        $error = $e->getDetailedError();
3025
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
3026
        return false;
3027
    }
3028
3029
    // Authenticate user
3030
    try {
3031
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory') {
3032
            $connection->auth()->attempt($login, $password, $stayAuthenticated = true);
3033
        } else {
3034
            $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);
3035
        }
3036
    } catch (\LdapRecord\Auth\BindException $e) {
3037
        $error = $e->getDetailedError();
3038
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
3039
        return false;
3040
    }
3041
3042
    return true;
3043
}
3044
3045
/**
3046
 * Removes from DB all sharekeys of this user
3047
 *
3048
 * @param int $userId User's id
3049
 * @param array   $SETTINGS Teampass settings
3050
 *
3051
 * @return bool
3052
 */
3053
function deleteUserObjetsKeys(int $userId, array $SETTINGS): bool
3054
{
3055
    // include librairies & connect to DB
3056
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
3057
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
3058
    if (defined('DB_PASSWD_CLEAR') === false) {
3059
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
3060
    }
3061
    DB::$host = DB_HOST;
3062
    DB::$user = DB_USER;
3063
    DB::$password = DB_PASSWD_CLEAR;
3064
    DB::$dbName = DB_NAME;
3065
    DB::$port = DB_PORT;
3066
    DB::$encoding = DB_ENCODING;
3067
    // Remove all item sharekeys items
3068
    DB::delete(
3069
        prefixTable('sharekeys_items'),
3070
        'user_id = %i',
3071
        $userId
3072
    );
3073
    // Remove all item sharekeys files
3074
    DB::delete(
3075
        prefixTable('sharekeys_files'),
3076
        'user_id = %i',
3077
        $userId
3078
    );
3079
    // Remove all item sharekeys fields
3080
    DB::delete(
3081
        prefixTable('sharekeys_fields'),
3082
        'user_id = %i',
3083
        $userId
3084
    );
3085
    // Remove all item sharekeys logs
3086
    DB::delete(
3087
        prefixTable('sharekeys_logs'),
3088
        'user_id = %i',
3089
        $userId
3090
    );
3091
    // Remove all item sharekeys suggestions
3092
    DB::delete(
3093
        prefixTable('sharekeys_suggestions'),
3094
        'user_id = %i',
3095
        $userId
3096
    );
3097
    return false;
3098
}
3099
3100
/**
3101
 * Manage list of timezones   $SETTINGS Teampass settings
3102
 *
3103
 * @return array
3104
 */
3105
function timezone_list()
3106
{
3107
    static $timezones = null;
3108
    if ($timezones === null) {
3109
        $timezones = [];
3110
        $offsets = [];
3111
        $now = new DateTime('now', new DateTimeZone('UTC'));
3112
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
3113
            $now->setTimezone(new DateTimeZone($timezone));
3114
            $offsets[] = $offset = $now->getOffset();
3115
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
3116
        }
3117
3118
        array_multisort($offsets, $timezones);
3119
    }
3120
3121
    return $timezones;
3122
}
3123
3124
/**
3125
 * Provide timezone offset
3126
 *
3127
 * @param int $offset Timezone offset
3128
 *
3129
 * @return string
3130
 */
3131
function format_GMT_offset($offset): string
3132
{
3133
    $hours = intval($offset / 3600);
3134
    $minutes = abs(intval($offset % 3600 / 60));
3135
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
3136
}
3137
3138
/**
3139
 * Provides timezone name
3140
 *
3141
 * @param string $name Timezone name
3142
 *
3143
 * @return string
3144
 */
3145
function format_timezone_name($name): string
3146
{
3147
    $name = str_replace('/', ', ', $name);
3148
    $name = str_replace('_', ' ', $name);
3149
3150
    return str_replace('St ', 'St. ', $name);
3151
}
3152
3153
/**
3154
 * Provides info about if user should use MFA
3155
 *
3156
 * @param string $userRolesIds  User roles ids
3157
 * @param string $mfaRoles      Roles for which MFA is requested
3158
 *
3159
 * @return bool
3160
 */
3161
function mfa_auth_requested(string $userRolesIds, string $mfaRoles): bool
3162
{
3163
    if (empty($mfaRoles) === true) {
3164
        return true;
3165
    }
3166
3167
    $mfaRoles = array_values(json_decode($mfaRoles, true));
3168
    $userRolesIds = array_filter(explode(';', $userRolesIds));
3169
    if (count($mfaRoles) === 0 || count($mfaRoles) === 0) {
3170
        return true;
3171
    }
3172
3173
    if (count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
3174
        return true;
3175
    }
3176
    return false;
3177
}
3178
3179
/**
3180
 * Permits to clean a string for export purpose
3181
 *
3182
 * @param string $text
3183
 * @param bool $emptyCheckOnly
3184
 * 
3185
 * @return string
3186
 */
3187
function cleanStringForExport(string $text, bool $emptyCheckOnly = false): string
3188
{
3189
    if (is_null($text) === true || empty($text) === true) {
3190
        return '';
3191
    }
3192
    // only expected to check if $text was empty
3193
    elseif ($emptyCheckOnly === true) {
3194
        return $text;
3195
    }
3196
3197
    return strip_tags(
3198
        cleanString(
3199
            html_entity_decode($text, ENT_QUOTES | ENT_XHTML, 'UTF-8'),
3200
            true)
3201
        );
3202
}
3203
3204
/**
3205
 * Permits to check if user ID is valid
3206
 *
3207
 * @param integer $post_user_id
3208
 * @return bool
3209
 */
3210
function isUserIdValid($userId): bool
3211
{
3212
    if (is_null($userId) === false
3213
        && isset($userId) === true
3214
        && empty($userId) === false
3215
    ) {
3216
        return true;
3217
    }
3218
    return false;
3219
}
3220
3221
/**
3222
 * Check if a key exists and if its value equal the one expected
3223
 *
3224
 * @param string $key
3225
 * @param integer|string $value
3226
 * @param array $array
3227
 * 
3228
 * @return boolean
3229
 */
3230
function isKeyExistingAndEqual(
3231
    string $key,
3232
    /*PHP8 - integer|string*/$value,
3233
    array $array
3234
): 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(
3255
    /*PHP8 - string|null*/$var,
3256
    /*PHP8 - integer|string*/$value
3257
): bool
3258
{
3259
    if (isset($var) === false
3260
        || (is_int($value) === true ?
3261
            (int) $var === $value :
3262
            (string) $var === $value)
3263
    ) {
3264
        return true;
3265
    }
3266
    return false;
3267
}
3268
3269
/**
3270
 * Check if a key exists and if its value < to the one expected
3271
 *
3272
 * @param string $key
3273
 * @param integer $value
3274
 * @param array $array
3275
 * 
3276
 * @return boolean
3277
 */
3278
function isKeyExistingAndInferior(string $key, int $value, array $array): bool
3279
{
3280
    if (isset($array[$key]) === true && (int) $array[$key] < $value) {
3281
        return true;
3282
    }
3283
    return false;
3284
}
3285
3286
/**
3287
 * Check if a key exists and if its value > to the one expected
3288
 *
3289
 * @param string $key
3290
 * @param integer $value
3291
 * @param array $array
3292
 * 
3293
 * @return boolean
3294
 */
3295
function isKeyExistingAndSuperior(string $key, int $value, array $array): bool
3296
{
3297
    if (isset($array[$key]) === true && (int) $array[$key] > $value) {
3298
        return true;
3299
    }
3300
    return false;
3301
}
3302
3303
/**
3304
 * Check if values in array are set
3305
 * Return true if all set
3306
 * Return false if one of them is not set
3307
 *
3308
 * @param array $arrayOfValues
3309
 * @return boolean
3310
 */
3311
function isSetArrayOfValues(array $arrayOfValues): bool
3312
{
3313
    foreach($arrayOfValues as $value) {
3314
        if (isset($value) === false) {
3315
            return false;
3316
        }
3317
    }
3318
    return true;
3319
}
3320
3321
/**
3322
 * Check if values in array are set
3323
 * Return true if all set
3324
 * Return false if one of them is not set
3325
 *
3326
 * @param array $arrayOfValues
3327
 * @param integer|string $value
3328
 * @return boolean
3329
 */
3330
function isArrayOfVarsEqualToValue(
3331
    array $arrayOfVars,
3332
    /*PHP8 - integer|string*/$value
3333
) : bool
3334
{
3335
    foreach($arrayOfVars as $variable) {
3336
        if ($variable !== $value) {
3337
            return false;
3338
        }
3339
    }
3340
    return true;
3341
}
3342
3343
/**
3344
 * Checks if at least one variable in array is equal to value
3345
 *
3346
 * @param array $arrayOfValues
3347
 * @param integer|string $value
3348
 * @return boolean
3349
 */
3350
function isOneVarOfArrayEqualToValue(
3351
    array $arrayOfVars,
3352
    /*PHP8 - integer|string*/$value
3353
) : bool
3354
{
3355
    foreach($arrayOfVars as $variable) {
3356
        if ($variable === $value) {
3357
            return true;
3358
        }
3359
    }
3360
    return false;
3361
}
3362
3363
/**
3364
 * Checks is value is null, not set OR empty
3365
 *
3366
 * @param string|int|null $value
3367
 * @return boolean
3368
 */
3369
function isValueSetNullEmpty(/*PHP8 - string|int|null*/ $value) : bool
3370
{
3371
    if (is_null($value) === true || isset($value) === false || empty($value) === true) {
3372
        return true;
3373
    }
3374
    return false;
3375
}
3376
3377
/**
3378
 * Checks if value is set and if empty is equal to passed boolean
3379
 *
3380
 * @param string|int $value
3381
 * @param boolean $boolean
3382
 * @return boolean
3383
 */
3384
function isValueSetEmpty($value, $boolean = true) : bool
3385
{
3386
    if (isset($value) === true && empty($value) === $boolean) {
3387
        return true;
3388
    }
3389
    return false;
3390
}
3391
3392
/**
3393
 * Ensure Complexity is translated
3394
 *
3395
 * @return void
3396
 */
3397
function defineComplexity() : void
3398
{
3399
    if (defined('TP_PW_COMPLEXITY') === false) {
3400
        define(
3401
            'TP_PW_COMPLEXITY',
3402
            [
3403
                TP_PW_STRENGTH_1 => array(TP_PW_STRENGTH_1, langHdl('complex_level1'), 'fas fa-thermometer-empty text-danger'),
3404
                TP_PW_STRENGTH_2 => array(TP_PW_STRENGTH_2, langHdl('complex_level2'), 'fas fa-thermometer-quarter text-warning'),
3405
                TP_PW_STRENGTH_3 => array(TP_PW_STRENGTH_3, langHdl('complex_level3'), 'fas fa-thermometer-half text-warning'),
3406
                TP_PW_STRENGTH_4 => array(TP_PW_STRENGTH_4, langHdl('complex_level4'), 'fas fa-thermometer-three-quarters text-success'),
3407
                TP_PW_STRENGTH_5 => array(TP_PW_STRENGTH_5, langHdl('complex_level5'), 'fas fa-thermometer-full text-success'),
3408
            ]
3409
        );
3410
    }
3411
}
3412
3413
/**
3414
 * Uses Sanitizer to perform data sanitization
3415
 *
3416
 * @param array     $data
3417
 * @param array     $filters
3418
 * @param string    $path
3419
 * @return array
3420
 */
3421
function dataSanitizer(
3422
    array $data,
3423
    array $filters,
3424
    string $path
3425
): array
3426
{
3427
    // Load Sanitizer library
3428
    require_once $path . '/includes/libraries/Illuminate/Support/Traits/Macroable.php';
3429
    require_once $path . '/includes/libraries/Illuminate/Support/Str.php';
3430
    require_once $path . '/includes/libraries/Illuminate/Validation/ValidationRuleParser.php';
3431
    require_once $path . '/includes/libraries/Illuminate/Support/Arr.php';
3432
    require_once $path . '/includes/libraries/Elegant/sanitizer/Contracts/Filter.php';
3433
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/Trim.php';
3434
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/Cast.php';
3435
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/EscapeHTML.php';
3436
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/EmptyStringToNull.php';
3437
    require_once $path . '/includes/libraries/Elegant/sanitizer/Sanitizer.php';
3438
3439
    // Sanitize post and get variables
3440
    $sanitizer = new Elegant\sanitizer\Sanitizer($data, $filters);
3441
    return $sanitizer->sanitize();
3442
}