Passed
Push — teampass_3.0 ( 2e6bd1...4c0075 )
by Nils
13:44 queued 05:04
created

identUserGetFoldersFromRoles()   A

Complexity

Conditions 6
Paths 12

Size

Total Lines 30
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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