Passed
Push — master ( 0d3aa7...3fa478 )
by Nils
04:52
created

cacheTreeUserHandler()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 43
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

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