Passed
Push — master ( 0de6f7...12d149 )
by Nils
06:34
created

cleanStringForExport()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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