Passed
Push — master ( 5f4a9c...d3570e )
by Nils
04:23
created

loadFoldersListByCache()   B

Complexity

Conditions 9
Paths 9

Size

Total Lines 61
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 29
c 1
b 0
f 0
nc 9
nop 1
dl 0
loc 61
rs 8.0555

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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(__DIR__.'/../includes/libraries/protect/SuperGlobal/SuperGlobal.php')) {
55
        include_once __DIR__.'/../includes/libraries/protect/SuperGlobal/SuperGlobal.php';
56
    } elseif (file_exists(__DIR__.'/includes/libraries/protect/SuperGlobal/SuperGlobal.php')) {
57
        include_once __DIR__.'/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
58
    } elseif (file_exists(__DIR__.'/../../includes/libraries/protect/SuperGlobal/SuperGlobal.php')) {
59
        include_once __DIR__.'/../../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 and get English version
68
        $language_array = include __DIR__. '/../includes/language/english.php';
69
        print_r($session_language);
70
        $session_language = $language_array[trim($string)];
71
    }
72
    return str_replace(
73
        ["'"],
74
        ['&apos;'],
75
        $session_language
76
    );
77
}
78
79
/**
80
 * genHash().
81
 *
82
 * Generate a hash for user login
83
 *
84
 * @param string $password What password
85
 * @param string $cost     What cost
86
 *
87
 * @return string|void
88
 */
89
function bCrypt(
90
    string $password,
91
    string $cost
92
): ?string
93
{
94
    $salt = sprintf('$2y$%02d$', $cost);
95
    if (function_exists('openssl_random_pseudo_bytes')) {
96
        $salt .= bin2hex(openssl_random_pseudo_bytes(11));
97
    } else {
98
        $chars = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
99
        for ($i = 0; $i < 22; ++$i) {
100
            $salt .= $chars[mt_rand(0, 63)];
101
        }
102
    }
103
104
    return crypt($password, $salt);
105
}
106
107
/**
108
 * Defuse cryption function.
109
 *
110
 * @param string $message   what to de/crypt
111
 * @param string $ascii_key key to use
112
 * @param string $type      operation to perform
113
 * @param array  $SETTINGS  Teampass settings
114
 *
115
 * @return array
116
 */
117
function cryption(string $message, string $ascii_key, string $type, array $SETTINGS): array
118
{
119
    $ascii_key = empty($ascii_key) === true ? file_get_contents(SECUREPATH . '/teampass-seckey.txt') : $ascii_key;
120
    $err = false;
121
    // load PhpEncryption library
122
    if (isset($SETTINGS['cpassman_dir']) === false || empty($SETTINGS['cpassman_dir']) === true) {
123
        $path = '../includes/libraries/Encryption/Encryption/';
124
    } else {
125
        $path = $SETTINGS['cpassman_dir'] . '/includes/libraries/Encryption/Encryption/';
126
    }
127
128
    include_once $path . 'Exception/CryptoException.php';
129
    include_once $path . 'Exception/BadFormatException.php';
130
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
131
    include_once $path . 'Exception/IOException.php';
132
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
133
    include_once $path . 'Crypto.php';
134
    include_once $path . 'Encoding.php';
135
    include_once $path . 'DerivedKeys.php';
136
    include_once $path . 'Key.php';
137
    include_once $path . 'KeyOrPassword.php';
138
    include_once $path . 'File.php';
139
    include_once $path . 'RuntimeTests.php';
140
    include_once $path . 'KeyProtectedByPassword.php';
141
    include_once $path . 'Core.php';
142
    
143
    // convert KEY
144
    $key = \Defuse\Crypto\Key::loadFromAsciiSafeString($ascii_key);
145
    try {
146
        if ($type === 'encrypt') {
147
            $text = \Defuse\Crypto\Crypto::encrypt($message, $key);
148
        } elseif ($type === 'decrypt') {
149
            $text = \Defuse\Crypto\Crypto::decrypt($message, $key);
150
        }
151
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
152
        $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.';
153
    } catch (Defuse\Crypto\Exception\BadFormatException $ex) {
154
        $err = $ex;
155
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
156
        $err = $ex;
157
    } catch (Defuse\Crypto\Exception\CryptoException $ex) {
158
        $err = $ex;
159
    } catch (Defuse\Crypto\Exception\IOException $ex) {
160
        $err = $ex;
161
    }
162
    //echo \Defuse\Crypto\Crypto::decrypt($message, $key).' ## ';
163
164
    return [
165
        'string' => $text ?? '',
166
        'error' => $err,
167
    ];
168
}
169
170
/**
171
 * Generating a defuse key.
172
 *
173
 * @return string
174
 */
175
function defuse_generate_key()
176
{
177
    // load PhpEncryption library
178
    if (file_exists('../includes/config/tp.config.php') === true) {
179
        $path = '../includes/libraries/Encryption/Encryption/';
180
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
181
        $path = './includes/libraries/Encryption/Encryption/';
182
    } else {
183
        $path = '../includes/libraries/Encryption/Encryption/';
184
    }
185
186
    include_once $path . 'Exception/CryptoException.php';
187
    include_once $path . 'Exception/BadFormatException.php';
188
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
189
    include_once $path . 'Exception/IOException.php';
190
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
191
    include_once $path . 'Crypto.php';
192
    include_once $path . 'Encoding.php';
193
    include_once $path . 'DerivedKeys.php';
194
    include_once $path . 'Key.php';
195
    include_once $path . 'KeyOrPassword.php';
196
    include_once $path . 'File.php';
197
    include_once $path . 'RuntimeTests.php';
198
    include_once $path . 'KeyProtectedByPassword.php';
199
    include_once $path . 'Core.php';
200
201
    $key = \Defuse\Crypto\Key::createNewRandomKey();
202
    $key = $key->saveToAsciiSafeString();
203
    return $key;
204
}
205
206
/**
207
 * Generate a Defuse personal key.
208
 *
209
 * @param string $psk psk used
210
 *
211
 * @return string
212
 */
213
function defuse_generate_personal_key(string $psk): string
214
{
215
    // load PhpEncryption library
216
    if (file_exists('../includes/config/tp.config.php') === true) {
217
        $path = '../includes/libraries/Encryption/Encryption/';
218
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
219
        $path = './includes/libraries/Encryption/Encryption/';
220
    } else {
221
        $path = '../includes/libraries/Encryption/Encryption/';
222
    }
223
224
    include_once $path . 'Exception/CryptoException.php';
225
    include_once $path . 'Exception/BadFormatException.php';
226
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
227
    include_once $path . 'Exception/IOException.php';
228
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
229
    include_once $path . 'Crypto.php';
230
    include_once $path . 'Encoding.php';
231
    include_once $path . 'DerivedKeys.php';
232
    include_once $path . 'Key.php';
233
    include_once $path . 'KeyOrPassword.php';
234
    include_once $path . 'File.php';
235
    include_once $path . 'RuntimeTests.php';
236
    include_once $path . 'KeyProtectedByPassword.php';
237
    include_once $path . 'Core.php';
238
    
239
    $protected_key = \Defuse\Crypto\KeyProtectedByPassword::createRandomPasswordProtectedKey($psk);
240
    return $protected_key->saveToAsciiSafeString(); // save this in user table
241
}
242
243
/**
244
 * Validate persoanl key with defuse.
245
 *
246
 * @param string $psk                   the user's psk
247
 * @param string $protected_key_encoded special key
248
 *
249
 * @return string
250
 */
251
function defuse_validate_personal_key(string $psk, string $protected_key_encoded): string
252
{
253
    // load PhpEncryption library
254
    if (file_exists('../includes/config/tp.config.php') === true) {
255
        $path = '../includes/libraries/Encryption/Encryption/';
256
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
257
        $path = './includes/libraries/Encryption/Encryption/';
258
    } else {
259
        $path = '../includes/libraries/Encryption/Encryption/';
260
    }
261
262
    include_once $path . 'Exception/CryptoException.php';
263
    include_once $path . 'Exception/BadFormatException.php';
264
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
265
    include_once $path . 'Exception/IOException.php';
266
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
267
    include_once $path . 'Crypto.php';
268
    include_once $path . 'Encoding.php';
269
    include_once $path . 'DerivedKeys.php';
270
    include_once $path . 'Key.php';
271
    include_once $path . 'KeyOrPassword.php';
272
    include_once $path . 'File.php';
273
    include_once $path . 'RuntimeTests.php';
274
    include_once $path . 'KeyProtectedByPassword.php';
275
    include_once $path . 'Core.php';
276
277
    try {
278
        $user_key = $protected_key_encoded->unlockKey($psk);
279
        $user_key_encoded = $user_key->saveToAsciiSafeString();
280
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
281
        return 'Error - Major issue as the encryption is broken.';
282
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
283
        return 'Error - The saltkey is not the correct one.';
284
    }
285
286
    return $user_key_encoded;
287
    // store it in session once user has entered his psk
288
}
289
290
/**
291
 * Decrypt a defuse string if encrypted.
292
 *
293
 * @param string $value Encrypted string
294
 *
295
 * @return string Decrypted string
296
 */
297
function defuseReturnDecrypted(string $value, $SETTINGS): string
298
{
299
    if (substr($value, 0, 3) === 'def') {
300
        $value = cryption($value, '', 'decrypt', $SETTINGS)['string'];
301
    }
302
303
    return $value;
304
}
305
306
/**
307
 * Trims a string depending on a specific string.
308
 *
309
 * @param string|array $chaine  what to trim
310
 * @param string       $element trim on what
311
 *
312
 * @return string
313
 */
314
function trimElement($chaine, string $element): string
315
{
316
    if (! empty($chaine)) {
317
        if (is_array($chaine) === true) {
318
            $chaine = implode(';', $chaine);
319
        }
320
        $chaine = trim($chaine);
321
        if (substr($chaine, 0, 1) === $element) {
322
            $chaine = substr($chaine, 1);
323
        }
324
        if (substr($chaine, strlen($chaine) - 1, 1) === $element) {
325
            $chaine = substr($chaine, 0, strlen($chaine) - 1);
326
        }
327
    }
328
329
    return $chaine;
330
}
331
332
/**
333
 * Permits to suppress all "special" characters from string.
334
 *
335
 * @param string $string  what to clean
336
 * @param bool   $special use of special chars?
337
 *
338
 * @return string
339
 */
340
function cleanString(string $string, bool $special = false): string
341
{
342
    // Create temporary table for special characters escape
343
    $tabSpecialChar = [];
344
    for ($i = 0; $i <= 31; ++$i) {
345
        $tabSpecialChar[] = chr($i);
346
    }
347
    array_push($tabSpecialChar, '<br />');
348
    if ((int) $special === 1) {
349
        $tabSpecialChar = array_merge($tabSpecialChar, ['</li>', '<ul>', '<ol>']);
350
    }
351
352
    return str_replace($tabSpecialChar, "\n", $string);
353
}
354
355
/**
356
 * Erro manager for DB.
357
 *
358
 * @param array $params output from query
359
 *
360
 * @return void
361
 */
362
function db_error_handler(array $params): void
363
{
364
    echo 'Error: ' . $params['error'] . "<br>\n";
365
    echo 'Query: ' . $params['query'] . "<br>\n";
366
    throw new Exception('Error - Query', 1);
367
}
368
369
/**
370
 * Identify user's rights
371
 *
372
 * @param string|array $groupesVisiblesUser  [description]
373
 * @param string|array $groupesInterditsUser [description]
374
 * @param string       $isAdmin              [description]
375
 * @param string       $idFonctions          [description]
376
 *
377
 * @return bool
378
 */
379
function identifyUserRights(
380
    $groupesVisiblesUser,
381
    $groupesInterditsUser,
382
    $isAdmin,
383
    $idFonctions,
384
    $SETTINGS
385
) {
386
    //load ClassLoader
387
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
388
    // Load superglobal
389
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
390
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
391
    //Connect to DB
392
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
393
    if (defined('DB_PASSWD_CLEAR') === false) {
394
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
395
    }
396
    DB::$host = DB_HOST;
397
    DB::$user = DB_USER;
398
    DB::$password = DB_PASSWD_CLEAR;
399
    DB::$dbName = DB_NAME;
400
    DB::$port = DB_PORT;
401
    DB::$encoding = DB_ENCODING;
402
    DB::$ssl = DB_SSL;
403
    DB::$connect_options = DB_CONNECT_OPTIONS;
404
    //Build tree
405
    $tree = new SplClassLoader('Tree\NestedTree', $SETTINGS['cpassman_dir'] . '/includes/libraries');
406
    $tree->register();
407
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
408
409
    // Check if user is ADMINISTRATOR    
410
    (int) $isAdmin === 1 ?
411
        identAdmin(
412
            $idFonctions,
413
            $SETTINGS, /** @scrutinizer ignore-type */
414
            $tree
415
        )
416
        :
417
        identUser(
418
            $groupesVisiblesUser,
419
            $groupesInterditsUser,
420
            $idFonctions,
421
            $SETTINGS, /** @scrutinizer ignore-type */
422
            $tree
423
        );
424
425
    // update user's timestamp
426
    DB::update(
427
        prefixTable('users'),
428
        [
429
            'timestamp' => time(),
430
        ],
431
        'id=%i',
432
        $superGlobal->get('user_id', 'SESSION')
433
    );
434
435
    return true;
436
}
437
438
/**
439
 * Identify administrator.
440
 *
441
 * @param string $idFonctions Roles of user
442
 * @param array  $SETTINGS    Teampass settings
443
 * @param array  $tree        Tree of folders
444
 *
445
 * @return bool
446
 */
447
function identAdmin($idFonctions, $SETTINGS, $tree)
448
{
449
    // Load superglobal
450
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
451
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
452
    // Init
453
    $groupesVisibles = [];
454
    $superGlobal->put('personal_folders', [], 'SESSION');
455
    $superGlobal->put('groupes_visibles', [], 'SESSION');
456
    $superGlobal->put('no_access_folders', [], 'SESSION');
457
    $superGlobal->put('personal_visible_groups', [], 'SESSION');
458
    $superGlobal->put('read_only_folders', [], 'SESSION');
459
    $superGlobal->put('list_restricted_folders_for_items', [], 'SESSION');
460
    $superGlobal->put('list_folders_editable_by_role', [], 'SESSION');
461
    $superGlobal->put('list_folders_limited', [], 'SESSION');
462
    $superGlobal->put('no_access_folders', [], 'SESSION');
463
    $superGlobal->put('forbiden_pfs', [], 'SESSION');
464
    // Get superglobals
465
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
466
    $globalsVisibleFolders = $superGlobal->get('groupes_visibles', 'SESSION');
467
    $globalsPersonalVisibleFolders = $superGlobal->get('personal_visible_groups', 'SESSION');
468
    // Get list of Folders
469
    $rows = DB::query('SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i', 0);
470
    foreach ($rows as $record) {
471
        array_push($groupesVisibles, $record['id']);
472
    }
473
    $superGlobal->put('groupes_visibles', $groupesVisibles, 'SESSION');
474
    $superGlobal->put('all_non_personal_folders', $groupesVisibles, 'SESSION');
475
    // Exclude all PF
476
    $where = new WhereClause('and');
477
    // create a WHERE statement of pieces joined by ANDs
478
    $where->add('personal_folder=%i', 1);
479
    if (
480
        isset($SETTINGS['enable_pf_feature']) === true
481
        && (int) $SETTINGS['enable_pf_feature'] === 1
482
    ) {
483
        $where->add('title=%s', $globalsUserId);
484
        $where->negateLast();
485
    }
486
    // Get ID of personal folder
487
    $persfld = DB::queryfirstrow(
488
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE title = %s',
489
        $globalsUserId
490
    );
491
    if (empty($persfld['id']) === false) {
492
        if (in_array($persfld['id'], $globalsVisibleFolders) === false) {
493
            array_push($globalsVisibleFolders, $persfld['id']);
494
            array_push($globalsPersonalVisibleFolders, $persfld['id']);
495
            // get all descendants
496
            $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
497
            $tree->rebuild();
498
            $tst = $tree->getDescendants($persfld['id']);
499
            foreach ($tst as $t) {
500
                array_push($globalsVisibleFolders, $t->id);
501
                array_push($globalsPersonalVisibleFolders, $t->id);
502
            }
503
        }
504
    }
505
506
    // get complete list of ROLES
507
    $tmp = explode(';', $idFonctions);
508
    $rows = DB::query(
509
        'SELECT * FROM ' . prefixTable('roles_title') . '
510
        ORDER BY title ASC'
511
    );
512
    foreach ($rows as $record) {
513
        if (! empty($record['id']) && ! in_array($record['id'], $tmp)) {
514
            array_push($tmp, $record['id']);
515
        }
516
    }
517
    $superGlobal->put('fonction_id', implode(';', $tmp), 'SESSION');
518
    $superGlobal->put('is_admin', 1, 'SESSION');
519
    // Check if admin has created Folders and Roles
520
    DB::query('SELECT * FROM ' . prefixTable('nested_tree') . '');
521
    $superGlobal->put('nb_folders', DB::count(), 'SESSION');
522
    DB::query('SELECT * FROM ' . prefixTable('roles_title'));
523
    $superGlobal->put('nb_roles', DB::count(), 'SESSION');
524
525
    return true;
526
}
527
528
/**
529
 * Permits to convert an element to array.
530
 *
531
 * @param string|array $element Any value to be returned as array
532
 *
533
 * @return array
534
 */
535
function convertToArray($element): array
536
{
537
    if (is_string($element) === true) {
538
        if (empty($element) === true) {
539
            return [];
540
        }
541
        return explode(
542
            ';',
543
            trimElement($element, ';')
544
        );
545
    }
546
    return $element;
547
}
548
549
/**
550
 * Defines the rights the user has.
551
 *
552
 * @param string|array $allowedFolders  Allowed folders
553
 * @param string|array $noAccessFolders Not allowed folders
554
 * @param string|array $userRoles       Roles of user
555
 * @param array        $SETTINGS        Teampass settings
556
 * @param object       $tree            Tree of folders
557
 * 
558
 * @return bool
559
 */
560
function identUser(
561
    $allowedFolders,
562
    $noAccessFolders,
563
    $userRoles,
564
    array $SETTINGS,
565
    object $tree
566
) {
567
    // Load superglobal
568
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
569
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
570
    // Init
571
    $superGlobal->put('groupes_visibles', [], 'SESSION');
572
    $superGlobal->put('personal_folders', [], 'SESSION');
573
    $superGlobal->put('no_access_folders', [], 'SESSION');
574
    $superGlobal->put('personal_visible_groups', [], 'SESSION');
575
    $superGlobal->put('read_only_folders', [], 'SESSION');
576
    $superGlobal->put('fonction_id', $userRoles, 'SESSION');
577
    $superGlobal->put('is_admin', 0, 'SESSION');
578
    // init
579
    $personalFolders = [];
580
    $readOnlyFolders = [];
581
    $noAccessPersonalFolders = [];
582
    $restrictedFoldersForItems = [];
583
    $foldersLimited = [];
584
    $foldersLimitedFull = [];
585
    $allowedFoldersByRoles = [];
586
    // Get superglobals
587
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
588
    $globalsPersonalFolders = $superGlobal->get('personal_folder', 'SESSION');
589
    // Ensure consistency in array format
590
    $noAccessFolders = convertToArray($noAccessFolders);
591
    $userRoles = convertToArray($userRoles);
592
    $allowedFolders = convertToArray($allowedFolders);
593
594
    // Get list of folders depending on Roles
595
    $arrays = identUserGetFoldersFromRoles(
596
        $userRoles,
597
        $allowedFoldersByRoles,
598
        $readOnlyFolders,
599
        $allowedFolders
600
    );
601
    $allowedFoldersByRoles = $arrays['allowedFoldersByRoles'];
602
    $readOnlyFolders = $arrays['readOnlyFolders'];
603
604
    // Does this user is allowed to see other items
605
    $inc = 0;
606
    $rows = DB::query(
607
        'SELECT id, id_tree FROM ' . prefixTable('items') . '
608
            WHERE restricted_to LIKE %ss AND inactif = %s'.
609
            (count($allowedFolders) > 0 ? ' AND id_tree NOT IN ('.implode(',', $allowedFolders).')' : ''),
610
        $globalsUserId . ';',
611
        '0'
612
    );
613
    foreach ($rows as $record) {
614
        // Exclude restriction on item if folder is fully accessible
615
        //if (in_array($record['id_tree'], $allowedFolders) === false) {
616
            $restrictedFoldersForItems[$record['id_tree']][$inc] = $record['id'];
617
            ++$inc;
618
        //}
619
    }
620
621
    // Check for the users roles if some specific rights exist on items
622
    $rows = DB::query(
623
        'SELECT i.id_tree, r.item_id
624
        FROM ' . prefixTable('items') . ' as i
625
        INNER JOIN ' . prefixTable('restriction_to_roles') . ' as r ON (r.item_id=i.id)
626
        WHERE r.role_id IN %li AND i.id_tree <> ""
627
        ORDER BY i.id_tree ASC',
628
        $userRoles
629
    );
630
    $inc = 0;
631
    foreach ($rows as $record) {
632
        //if (isset($record['id_tree'])) {
633
            $foldersLimited[$record['id_tree']][$inc] = $record['item_id'];
634
            array_push($foldersLimitedFull, $record['item_id']);
635
            ++$inc;
636
        //}
637
    }
638
639
    // Get list of Personal Folders
640
    $arrays = identUserGetPFList(
641
        $globalsPersonalFolders,
642
        $allowedFolders,
643
        $globalsUserId,
644
        $personalFolders,
645
        $noAccessPersonalFolders,
646
        $foldersLimitedFull,
647
        $allowedFoldersByRoles,
648
        $restrictedFoldersForItems,
649
        $readOnlyFolders,
650
        $noAccessFolders,
651
        isset($SETTINGS['enable_pf_feature']) === true ? $SETTINGS['enable_pf_feature'] : 0,
652
        $tree
653
    );
654
    $allowedFolders = $arrays['allowedFolders'];
655
    $personalFolders = $arrays['personalFolders'];
656
    $noAccessPersonalFolders = $arrays['noAccessPersonalFolders'];
657
658
    // Return data
659
    $superGlobal->put('all_non_personal_folders', $allowedFolders, 'SESSION');
660
    $superGlobal->put('groupes_visibles', array_unique(array_merge($allowedFolders, $personalFolders), SORT_NUMERIC), 'SESSION');
661
    $superGlobal->put('read_only_folders', $readOnlyFolders, 'SESSION');
662
    $superGlobal->put('no_access_folders', $noAccessFolders, 'SESSION');
663
    $superGlobal->put('personal_folders', $personalFolders, 'SESSION');
664
    $superGlobal->put('list_folders_limited', $foldersLimited, 'SESSION');
665
    $superGlobal->put('list_folders_editable_by_role', $allowedFoldersByRoles, 'SESSION');
666
    $superGlobal->put('list_restricted_folders_for_items', $restrictedFoldersForItems, 'SESSION');
667
    $superGlobal->put('forbiden_pfs', $noAccessPersonalFolders, 'SESSION');
668
    $superGlobal->put(
669
        'all_folders_including_no_access',
670
        array_unique(array_merge(
671
            $allowedFolders,
672
            $personalFolders,
673
            $noAccessFolders,
674
            $readOnlyFolders
675
        ), SORT_NUMERIC),
676
        'SESSION'
677
    );
678
    // Folders and Roles numbers
679
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('nested_tree') . '');
680
    $superGlobal->put('nb_folders', DB::count(), 'SESSION');
681
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('roles_title'));
682
    $superGlobal->put('nb_roles', DB::count(), 'SESSION');
683
    // check if change proposals on User's items
684
    if (isset($SETTINGS['enable_suggestion']) === true && (int) $SETTINGS['enable_suggestion'] === 1) {
685
        $countNewItems = DB::query(
686
            'SELECT COUNT(*)
687
            FROM ' . prefixTable('items_change') . ' AS c
688
            LEFT JOIN ' . prefixTable('log_items') . ' AS i ON (c.item_id = i.id_item)
689
            WHERE i.action = %s AND i.id_user = %i',
690
            'at_creation',
691
            $globalsUserId
692
        );
693
        $superGlobal->put('nb_item_change_proposals', $countNewItems, 'SESSION');
694
    } else {
695
        $superGlobal->put('nb_item_change_proposals', 0, 'SESSION');
696
    }
697
698
    return true;
699
}
700
701
/**
702
 * Get list of folders depending on Roles
703
 * 
704
 * @param array $userRoles
705
 * @param array $allowedFoldersByRoles
706
 * @param array $readOnlyFolders
707
 * @param array $allowedFolders
708
 * 
709
 * @return array
710
 */
711
function identUserGetFoldersFromRoles($userRoles, $allowedFoldersByRoles, $readOnlyFolders, $allowedFolders) : array
712
{
713
    
714
    $rows = DB::query(
715
        'SELECT *
716
        FROM ' . prefixTable('roles_values') . '
717
        WHERE role_id IN %li AND type IN %ls',
718
        $userRoles,
719
        ['W', 'ND', 'NE', 'NDNE', 'R']
720
    );
721
    foreach ($rows as $record) {
722
        if ($record['type'] === 'R') {
723
            array_push($readOnlyFolders, $record['folder_id']);
724
        } elseif (in_array($record['folder_id'], $allowedFolders) === false) {
725
            array_push($allowedFoldersByRoles, $record['folder_id']);
726
        }
727
    }
728
    $allowedFoldersByRoles = array_unique($allowedFoldersByRoles);
729
    $readOnlyFolders = array_unique($readOnlyFolders);
730
    // Clean arrays
731
    foreach ($allowedFoldersByRoles as $value) {
732
        $key = array_search($value, $readOnlyFolders);
733
        if ($key !== false) {
734
            unset($readOnlyFolders[$key]);
735
        }
736
    }
737
738
    return [
739
        'readOnlyFolders' => $readOnlyFolders,
740
        'allowedFoldersByRoles' => $allowedFoldersByRoles
741
    ];
742
}
743
744
/**
745
 * Get list of Personal Folders
746
 * 
747
 * @param int $globalsPersonalFolders
748
 * @param array $allowedFolders
749
 * @param int $globalsUserId
750
 * @param array $personalFolders
751
 * @param array $noAccessPersonalFolders
752
 * @param array $foldersLimitedFull
753
 * @param array $allowedFoldersByRoles
754
 * @param array $restrictedFoldersForItems
755
 * @param array $readOnlyFolders
756
 * @param array $noAccessFolders
757
 * @param int $enablePfFeature
758
 * @param object $tree
759
 * 
760
 * @return array
761
 */
762
function identUserGetPFList(
763
    $globalsPersonalFolders,
764
    $allowedFolders,
765
    $globalsUserId,
766
    $personalFolders,
767
    $noAccessPersonalFolders,
768
    $foldersLimitedFull,
769
    $allowedFoldersByRoles,
770
    $restrictedFoldersForItems,
771
    $readOnlyFolders,
772
    $noAccessFolders,
773
    $enablePfFeature,
774
    $tree
775
)
776
{
777
    if (
778
        (int) $enablePfFeature === 1
779
        && (int) $globalsPersonalFolders === 1
780
    ) {
781
        $persoFld = DB::queryfirstrow(
782
            'SELECT id
783
            FROM ' . prefixTable('nested_tree') . '
784
            WHERE title = %s AND personal_folder = %i'.
785
            (count($allowedFolders) > 0 ? ' AND id NOT IN ('.implode(',', $allowedFolders).')' : ''),
786
            $globalsUserId,
787
            1
788
        );
789
        if (empty($persoFld['id']) === false) {
790
            array_push($personalFolders, $persoFld['id']);
791
            array_push($allowedFolders, $persoFld['id']);
792
            // get all descendants
793
            $ids = $tree->getDescendants($persoFld['id'], false, false, true);
794
            foreach ($ids as $id) {
795
                //array_push($allowedFolders, $id);
796
                array_push($personalFolders, $id);
797
            }
798
        }
799
    }
800
    
801
    // Exclude all other PF
802
    $where = new WhereClause('and');
803
    $where->add('personal_folder=%i', 1);
804
    if (count($personalFolders) > 0) {
805
        $where->add('id NOT IN ('.implode(',', $personalFolders).')');
806
    }
807
    if (
808
        (int) $enablePfFeature === 1
809
        && (int) $globalsPersonalFolders === 1
810
    ) {
811
        $where->add('title=%s', $globalsUserId);
812
        $where->negateLast();
813
    }
814
    $persoFlds = DB::query(
815
        'SELECT id
816
        FROM ' . prefixTable('nested_tree') . '
817
        WHERE %l',
818
        $where
819
    );
820
    foreach ($persoFlds as $persoFldId) {
821
        array_push($noAccessPersonalFolders, $persoFldId['id']);
822
    }
823
824
    // All folders visibles
825
    $allowedFolders = array_unique(array_merge(
826
        $allowedFolders,
827
        $foldersLimitedFull,
828
        $allowedFoldersByRoles,
829
        $restrictedFoldersForItems,
830
        $readOnlyFolders
831
    ), SORT_NUMERIC);
832
    // Exclude from allowed folders all the specific user forbidden folders
833
    if (count($noAccessFolders) > 0) {
834
        $allowedFolders = array_diff($allowedFolders, $noAccessFolders);
835
    }
836
837
    return [
838
        'allowedFolders' => array_diff(array_diff($allowedFolders, $noAccessPersonalFolders), $personalFolders),
839
        'personalFolders' => $personalFolders,
840
        'noAccessPersonalFolders' => $noAccessPersonalFolders
841
    ];
842
}
843
844
845
/**
846
 * Update the CACHE table.
847
 *
848
 * @param string $action   What to do
849
 * @param array  $SETTINGS Teampass settings
850
 * @param int    $ident    Ident format
851
 * 
852
 * @return void
853
 */
854
function updateCacheTable(string $action, array $SETTINGS, ?int $ident = null): void
855
{
856
    if ($action === 'reload') {
857
        // Rebuild full cache table
858
        cacheTableRefresh($SETTINGS);
859
    } elseif ($action === 'update_value' && is_null($ident) === false) {
860
        // UPDATE an item
861
        cacheTableUpdate($SETTINGS, $ident);
862
    } elseif ($action === 'add_value' && is_null($ident) === false) {
863
        // ADD an item
864
        cacheTableAdd($SETTINGS, $ident);
865
    } elseif ($action === 'delete_value' && is_null($ident) === false) {
866
        // DELETE an item
867
        DB::delete(prefixTable('cache'), 'id = %i', $ident);
868
    }
869
}
870
871
/**
872
 * Cache table - refresh.
873
 *
874
 * @param array $SETTINGS Teampass settings
875
 * 
876
 * @return void
877
 */
878
function cacheTableRefresh(array $SETTINGS): void
879
{
880
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
881
    //Connect to DB
882
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
883
    if (defined('DB_PASSWD_CLEAR') === false) {
884
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
885
    }
886
    DB::$host = DB_HOST;
887
    DB::$user = DB_USER;
888
    DB::$password = DB_PASSWD_CLEAR;
889
    DB::$dbName = DB_NAME;
890
    DB::$port = DB_PORT;
891
    DB::$encoding = DB_ENCODING;
892
    DB::$ssl = DB_SSL;
893
    DB::$connect_options = DB_CONNECT_OPTIONS;
894
    //Load Tree
895
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
896
    $tree->register();
897
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
898
    // truncate table
899
    DB::query('TRUNCATE TABLE ' . prefixTable('cache'));
900
    // reload date
901
    $rows = DB::query(
902
        'SELECT *
903
        FROM ' . prefixTable('items') . ' as i
904
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
905
        AND l.action = %s
906
        AND i.inactif = %i',
907
        'at_creation',
908
        0
909
    );
910
    foreach ($rows as $record) {
911
        if (empty($record['id_tree']) === false) {
912
            // Get all TAGS
913
            $tags = '';
914
            $itemTags = DB::query(
915
                'SELECT tag
916
                FROM ' . prefixTable('tags') . '
917
                WHERE item_id = %i AND tag != ""',
918
                $record['id']
919
            );
920
            foreach ($itemTags as $itemTag) {
921
                $tags .= $itemTag['tag'] . ' ';
922
            }
923
924
            // Get renewal period
925
            $resNT = DB::queryfirstrow(
926
                'SELECT renewal_period
927
                FROM ' . prefixTable('nested_tree') . '
928
                WHERE id = %i',
929
                $record['id_tree']
930
            );
931
            // form id_tree to full foldername
932
            $folder = [];
933
            $arbo = $tree->getPath($record['id_tree'], true);
934
            foreach ($arbo as $elem) {
935
                // Check if title is the ID of a user
936
                if (is_numeric($elem->title) === true) {
937
                    // Is this a User id?
938
                    $user = DB::queryfirstrow(
939
                        'SELECT id, login
940
                        FROM ' . prefixTable('users') . '
941
                        WHERE id = %i',
942
                        $elem->title
943
                    );
944
                    if (count($user) > 0) {
945
                        $elem->title = $user['login'];
946
                    }
947
                }
948
                // Build path
949
                array_push($folder, stripslashes($elem->title));
950
            }
951
            // store data
952
            DB::insert(
953
                prefixTable('cache'),
954
                [
955
                    'id' => $record['id'],
956
                    'label' => $record['label'],
957
                    'description' => $record['description'] ?? '',
958
                    'url' => isset($record['url']) && ! empty($record['url']) ? $record['url'] : '0',
959
                    'tags' => $tags,
960
                    'id_tree' => $record['id_tree'],
961
                    'perso' => $record['perso'],
962
                    'restricted_to' => isset($record['restricted_to']) && ! empty($record['restricted_to']) ? $record['restricted_to'] : '0',
963
                    'login' => $record['login'] ?? '',
964
                    'folder' => implode(' > ', $folder),
965
                    'author' => $record['id_user'],
966
                    'renewal_period' => $resNT['renewal_period'] ?? '0',
967
                    'timestamp' => $record['date'],
968
                ]
969
            );
970
        }
971
    }
972
}
973
974
/**
975
 * Cache table - update existing value.
976
 *
977
 * @param array  $SETTINGS Teampass settings
978
 * @param int    $ident    Ident format
979
 * 
980
 * @return void
981
 */
982
function cacheTableUpdate(array $SETTINGS, ?int $ident = null): void
983
{
984
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
985
    // Load superglobal
986
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
987
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
988
    //Connect to DB
989
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
990
    if (defined('DB_PASSWD_CLEAR') === false) {
991
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
992
    }
993
    DB::$host = DB_HOST;
994
    DB::$user = DB_USER;
995
    DB::$password = DB_PASSWD_CLEAR;
996
    DB::$dbName = DB_NAME;
997
    DB::$port = DB_PORT;
998
    DB::$encoding = DB_ENCODING;
999
    DB::$ssl = DB_SSL;
1000
    DB::$connect_options = DB_CONNECT_OPTIONS;
1001
    //Load Tree
1002
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1003
    $tree->register();
1004
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1005
    // get new value from db
1006
    $data = DB::queryfirstrow(
1007
        'SELECT label, description, id_tree, perso, restricted_to, login, url
1008
        FROM ' . prefixTable('items') . '
1009
        WHERE id=%i',
1010
        $ident
1011
    );
1012
    // Get all TAGS
1013
    $tags = '';
1014
    $itemTags = DB::query(
1015
        'SELECT tag
1016
            FROM ' . prefixTable('tags') . '
1017
            WHERE item_id = %i AND tag != ""',
1018
        $ident
1019
    );
1020
    foreach ($itemTags as $itemTag) {
1021
        $tags .= $itemTag['tag'] . ' ';
1022
    }
1023
    // form id_tree to full foldername
1024
    $folder = [];
1025
    $arbo = $tree->getPath($data['id_tree'], true);
1026
    foreach ($arbo as $elem) {
1027
        // Check if title is the ID of a user
1028
        if (is_numeric($elem->title) === true) {
1029
            // Is this a User id?
1030
            $user = DB::queryfirstrow(
1031
                'SELECT id, login
1032
                FROM ' . prefixTable('users') . '
1033
                WHERE id = %i',
1034
                $elem->title
1035
            );
1036
            if (count($user) > 0) {
1037
                $elem->title = $user['login'];
1038
            }
1039
        }
1040
        // Build path
1041
        array_push($folder, stripslashes($elem->title));
1042
    }
1043
    // finaly update
1044
    DB::update(
1045
        prefixTable('cache'),
1046
        [
1047
            'label' => $data['label'],
1048
            'description' => $data['description'],
1049
            'tags' => $tags,
1050
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
1051
            'id_tree' => $data['id_tree'],
1052
            'perso' => $data['perso'],
1053
            'restricted_to' => isset($data['restricted_to']) && ! empty($data['restricted_to']) ? $data['restricted_to'] : '0',
1054
            'login' => $data['login'] ?? '',
1055
            'folder' => implode(' » ', $folder),
1056
            'author' => $superGlobal->get('user_id', 'SESSION'),
1057
        ],
1058
        'id = %i',
1059
        $ident
1060
    );
1061
}
1062
1063
/**
1064
 * Cache table - add new value.
1065
 *
1066
 * @param array  $SETTINGS Teampass settings
1067
 * @param int    $ident    Ident format
1068
 * 
1069
 * @return void
1070
 */
1071
function cacheTableAdd(array $SETTINGS, ?int $ident = null): void
1072
{
1073
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1074
    // Load superglobal
1075
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1076
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1077
    // Get superglobals
1078
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
1079
    //Connect to DB
1080
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1081
    if (defined('DB_PASSWD_CLEAR') === false) {
1082
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1083
    }
1084
    DB::$host = DB_HOST;
1085
    DB::$user = DB_USER;
1086
    DB::$password = DB_PASSWD_CLEAR;
1087
    DB::$dbName = DB_NAME;
1088
    DB::$port = DB_PORT;
1089
    DB::$encoding = DB_ENCODING;
1090
    DB::$ssl = DB_SSL;
1091
    DB::$connect_options = DB_CONNECT_OPTIONS;
1092
    //Load Tree
1093
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1094
    $tree->register();
1095
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1096
    // get new value from db
1097
    $data = DB::queryFirstRow(
1098
        'SELECT i.label, i.description, i.id_tree as id_tree, i.perso, i.restricted_to, i.id, i.login, i.url, l.date
1099
        FROM ' . prefixTable('items') . ' as i
1100
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
1101
        WHERE i.id = %i
1102
        AND l.action = %s',
1103
        $ident,
1104
        'at_creation'
1105
    );
1106
    // Get all TAGS
1107
    $tags = '';
1108
    $itemTags = DB::query(
1109
        'SELECT tag
1110
            FROM ' . prefixTable('tags') . '
1111
            WHERE item_id = %i AND tag != ""',
1112
        $ident
1113
    );
1114
    foreach ($itemTags as $itemTag) {
1115
        $tags .= $itemTag['tag'] . ' ';
1116
    }
1117
    // form id_tree to full foldername
1118
    $folder = [];
1119
    $arbo = $tree->getPath($data['id_tree'], true);
1120
    foreach ($arbo as $elem) {
1121
        // Check if title is the ID of a user
1122
        if (is_numeric($elem->title) === true) {
1123
            // Is this a User id?
1124
            $user = DB::queryfirstrow(
1125
                'SELECT id, login
1126
                FROM ' . prefixTable('users') . '
1127
                WHERE id = %i',
1128
                $elem->title
1129
            );
1130
            if (count($user) > 0) {
1131
                $elem->title = $user['login'];
1132
            }
1133
        }
1134
        // Build path
1135
        array_push($folder, stripslashes($elem->title));
1136
    }
1137
    // finaly update
1138
    DB::insert(
1139
        prefixTable('cache'),
1140
        [
1141
            'id' => $data['id'],
1142
            'label' => $data['label'],
1143
            'description' => $data['description'],
1144
            'tags' => isset($tags) && empty($tags) === false ? $tags : 'None',
1145
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
1146
            'id_tree' => $data['id_tree'],
1147
            'perso' => isset($data['perso']) && empty($data['perso']) === false && $data['perso'] !== 'None' ? $data['perso'] : '0',
1148
            'restricted_to' => isset($data['restricted_to']) && empty($data['restricted_to']) === false ? $data['restricted_to'] : '0',
1149
            'login' => $data['login'] ?? '',
1150
            'folder' => implode(' » ', $folder),
1151
            'author' => $globalsUserId,
1152
            'timestamp' => $data['date'],
1153
        ]
1154
    );
1155
}
1156
1157
/**
1158
 * Do statistics.
1159
 *
1160
 * @param array $SETTINGS Teampass settings
1161
 *
1162
 * @return array
1163
 */
1164
function getStatisticsData(array $SETTINGS): array
1165
{
1166
    DB::query(
1167
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1168
        0
1169
    );
1170
    $counter_folders = DB::count();
1171
    DB::query(
1172
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1173
        1
1174
    );
1175
    $counter_folders_perso = DB::count();
1176
    DB::query(
1177
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1178
        0
1179
    );
1180
    $counter_items = DB::count();
1181
        DB::query(
1182
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1183
        1
1184
    );
1185
    $counter_items_perso = DB::count();
1186
        DB::query(
1187
        'SELECT id FROM ' . prefixTable('users') . ''
1188
    );
1189
    $counter_users = DB::count();
1190
        DB::query(
1191
        'SELECT id FROM ' . prefixTable('users') . ' WHERE admin = %i',
1192
        1
1193
    );
1194
    $admins = DB::count();
1195
    DB::query(
1196
        'SELECT id FROM ' . prefixTable('users') . ' WHERE gestionnaire = %i',
1197
        1
1198
    );
1199
    $managers = DB::count();
1200
    DB::query(
1201
        'SELECT id FROM ' . prefixTable('users') . ' WHERE read_only = %i',
1202
        1
1203
    );
1204
    $readOnly = DB::count();
1205
    // list the languages
1206
    $usedLang = [];
1207
    $tp_languages = DB::query(
1208
        'SELECT name FROM ' . prefixTable('languages')
1209
    );
1210
    foreach ($tp_languages as $tp_language) {
1211
        DB::query(
1212
            'SELECT * FROM ' . prefixTable('users') . ' WHERE user_language = %s',
1213
            $tp_language['name']
1214
        );
1215
        $usedLang[$tp_language['name']] = round((DB::count() * 100 / $counter_users), 0);
1216
    }
1217
1218
    // get list of ips
1219
    $usedIp = [];
1220
    $tp_ips = DB::query(
1221
        'SELECT user_ip FROM ' . prefixTable('users')
1222
    );
1223
    foreach ($tp_ips as $ip) {
1224
        if (array_key_exists($ip['user_ip'], $usedIp)) {
1225
            $usedIp[$ip['user_ip']] += $usedIp[$ip['user_ip']];
1226
        } elseif (! empty($ip['user_ip']) && $ip['user_ip'] !== 'none') {
1227
            $usedIp[$ip['user_ip']] = 1;
1228
        }
1229
    }
1230
1231
    return [
1232
        'error' => '',
1233
        'stat_phpversion' => phpversion(),
1234
        'stat_folders' => $counter_folders,
1235
        'stat_folders_shared' => intval($counter_folders) - intval($counter_folders_perso),
1236
        'stat_items' => $counter_items,
1237
        'stat_items_shared' => intval($counter_items) - intval($counter_items_perso),
1238
        'stat_users' => $counter_users,
1239
        'stat_admins' => $admins,
1240
        'stat_managers' => $managers,
1241
        'stat_ro' => $readOnly,
1242
        'stat_kb' => $SETTINGS['enable_kb'],
1243
        'stat_pf' => $SETTINGS['enable_pf_feature'],
1244
        'stat_fav' => $SETTINGS['enable_favourites'],
1245
        'stat_teampassversion' => TP_VERSION_FULL,
1246
        'stat_ldap' => $SETTINGS['ldap_mode'],
1247
        'stat_agses' => $SETTINGS['agses_authentication_enabled'],
1248
        'stat_duo' => $SETTINGS['duo'],
1249
        'stat_suggestion' => $SETTINGS['enable_suggestion'],
1250
        'stat_api' => $SETTINGS['api'],
1251
        'stat_customfields' => $SETTINGS['item_extra_fields'],
1252
        'stat_syslog' => $SETTINGS['syslog_enable'],
1253
        'stat_2fa' => $SETTINGS['google_authentication'],
1254
        'stat_stricthttps' => $SETTINGS['enable_sts'],
1255
        'stat_mysqlversion' => DB::serverVersion(),
1256
        'stat_languages' => $usedLang,
1257
        'stat_country' => $usedIp,
1258
    ];
1259
}
1260
1261
/**
1262
 * Permits to send an email.
1263
 *
1264
 * @param string $subject     email subject
1265
 * @param string $textMail    email message
1266
 * @param string $email       email
1267
 * @param array  $SETTINGS    settings
1268
 * @param string $textMailAlt email message alt
1269
 * @param bool   $silent      no errors
1270
 *
1271
 * @return string some json info
1272
 */
1273
function sendEmail(
1274
    $subject,
1275
    $textMail,
1276
    $email,
1277
    $SETTINGS,
1278
    $textMailAlt = null,
1279
    $silent = true
1280
) {
1281
    // CAse where email not defined
1282
    if ($email === 'none' || empty($email) === true) {
1283
        return json_encode(
1284
            [
1285
                'error' => true,
1286
                'message' => langHdl('forgot_my_pw_email_sent'),
1287
            ]
1288
        );
1289
    }
1290
1291
    // Load settings
1292
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
1293
    // Load superglobal
1294
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1295
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1296
    // Get user language
1297
    include_once $SETTINGS['cpassman_dir'] . '/includes/language/' . (null !== $superGlobal->get('user_language', 'SESSION') ? $superGlobal->get('user_language', 'SESSION') : 'english') . '.php';
1298
    // Load library
1299
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1300
    // load PHPMailer
1301
    $mail = new SplClassLoader('PHPMailer\PHPMailer', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1302
    $mail->register();
1303
    $mail = new PHPMailer\PHPMailer\PHPMailer(true);
1304
1305
    // send to user
1306
    $mail->setLanguage('en', $SETTINGS['cpassman_dir'] . '/includes/libraries/PHPMailer/PHPMailer/language/');
1307
    $mail->SMTPDebug = isset($SETTINGS['email_debug_level']) === true ? $SETTINGS['email_debug_level'] : 0;
1308
    $mail->Port = (int) $SETTINGS['email_port'];
1309
    //COULD BE USED
1310
    $mail->CharSet = 'utf-8';
1311
    $mail->SMTPSecure = $SETTINGS['email_security'] !== 'none' ? $SETTINGS['email_security'] : '';
1312
    $mail->SMTPAutoTLS = $SETTINGS['email_security'] !== 'none' ? true : false;
1313
    $mail->SMTPOptions = [
1314
        'ssl' => [
1315
            'verify_peer' => false,
1316
            'verify_peer_name' => false,
1317
            'allow_self_signed' => true,
1318
        ],
1319
    ];
1320
    $mail->isSmtp();
1321
    // send via SMTP
1322
    $mail->Host = $SETTINGS['email_smtp_server'];
1323
    // SMTP servers
1324
    $mail->SMTPAuth = (int) $SETTINGS['email_smtp_auth'] === 1 ? true : false;
1325
    // turn on SMTP authentication
1326
    $mail->Username = $SETTINGS['email_auth_username'];
1327
    // SMTP username
1328
    $mail->Password = $SETTINGS['email_auth_pwd'];
1329
    // SMTP password
1330
    $mail->From = $SETTINGS['email_from'];
1331
    $mail->FromName = $SETTINGS['email_from_name'];
1332
    // Prepare for each person
1333
    foreach (array_filter(explode(',', $email)) as $dest) {
1334
        $mail->addAddress($dest);
1335
    }
1336
1337
    // Prepare HTML
1338
    $text_html = emailBody($textMail);
1339
    $mail->WordWrap = 80;
1340
    // set word wrap
1341
    $mail->isHtml(true);
1342
    // send as HTML
1343
    $mail->Subject = $subject;
1344
    $mail->Body = $text_html;
1345
    $mail->AltBody = is_null($textMailAlt) === false ? $textMailAlt : '';
1346
1347
    try {
1348
        // send email
1349
        $mail->send();
1350
    } catch (Exception $e) {
1351
        if ($silent === false || (int) $SETTINGS['email_debug_level'] !== 0) {
1352
            return json_encode(
1353
                [
1354
                    'error' => true,
1355
                    'message' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1356
                ]
1357
            );
1358
        }
1359
        return '';
1360
    }
1361
    $mail->smtpClose();
1362
1363
    if ($silent === false) {
1364
        return json_encode(
1365
            [
1366
                'error' => false,
1367
                'message' => langHdl('forgot_my_pw_email_sent'),
1368
            ]
1369
        );
1370
    }
1371
    // Debug purpose
1372
    if ((int) $SETTINGS['email_debug_level'] !== 0) {
1373
        return json_encode(
1374
            [
1375
                'error' => true,
1376
                'message' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1377
            ]
1378
        );
1379
    }
1380
    return json_encode(
1381
        [
1382
            'error' => false,
1383
            'message' => langHdl('share_sent_ok'),
1384
        ]
1385
    );
1386
}
1387
1388
/**
1389
 * Returns the email body.
1390
 *
1391
 * @param string $textMail Text for the email
1392
 */
1393
function emailBody(string $textMail): string
1394
{
1395
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1396
    w3.org/TR/html4/loose.dtd"><html>
1397
    <head><title>Email Template</title>
1398
    <style type="text/css">
1399
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1400
    </style></head>
1401
    <body style="-ms-text-size-adjust: none; size-adjust: none; margin: 0; padding: 10px 0; background-color: #f0f0f0;" bgcolor="#f0f0f0" leftmargin="0" topmargin="0" marginwidth="0" marginheight="0">
1402
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1403
    <tr><td style="border-collapse: collapse;"><br>
1404
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1405
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1406
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1407
        </td></tr></table></td>
1408
    </tr>
1409
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1410
        <table width="600" cellpadding="0" cellspacing="0" border="0" class="container" bgcolor="#ffffff" style="border-spacing: 0; border-bottom: 1px solid #e0e0e0; box-shadow: 0 0 3px #ddd; color: #434343; font-family: Helvetica, Verdana, sans-serif;">
1411
        <tr><td class="container-padding" bgcolor="#ffffff" style="border-collapse: collapse; border-left: 1px solid #e0e0e0; background-color: #ffffff; padding-left: 30px; padding-right: 30px;">
1412
        <br><div style="float:right;">' .
1413
        $textMail .
1414
        '<br><br></td></tr></table>
1415
    </td></tr></table>
1416
    <br></body></html>';
1417
}
1418
1419
/**
1420
 * Generate a Key.
1421
 * 
1422
 * @return string
1423
 */
1424
function generateKey(): string
1425
{
1426
    return substr(md5(rand() . rand()), 0, 15);
1427
}
1428
1429
/**
1430
 * Convert date to timestamp.
1431
 *
1432
 * @param string $date     The date
1433
 * @param array  $SETTINGS Teampass settings
1434
 *
1435
 * @return string
1436
 */
1437
function dateToStamp(string $date, array $SETTINGS): string
1438
{
1439
    $date = date_parse_from_format($SETTINGS['date_format'], $date);
1440
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1441
        return mktime(23, 59, 59, $date['month'], $date['day'], $date['year']);
1442
    }
1443
    return '';
1444
}
1445
1446
/**
1447
 * Is this a date.
1448
 *
1449
 * @param string $date Date
1450
 *
1451
 * @return bool
1452
 */
1453
function isDate(string $date): bool
1454
{
1455
    return strtotime($date) !== false;
1456
}
1457
1458
/**
1459
 * Check if isUTF8().
1460
 *
1461
 * @param string|array $string Is the string
1462
 *
1463
 * @return int is the string in UTF8 format
1464
 */
1465
function isUTF8($string): int
1466
{
1467
    if (is_array($string) === true) {
1468
        $string = $string['string'];
1469
    }
1470
1471
    return preg_match(
1472
        '%^(?:
1473
        [\x09\x0A\x0D\x20-\x7E] # ASCII
1474
        | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
1475
        | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
1476
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
1477
        | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
1478
        | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
1479
        | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
1480
        | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
1481
        )*$%xs',
1482
        $string
1483
    );
1484
}
1485
1486
/**
1487
 * Prepare an array to UTF8 format before JSON_encode.
1488
 *
1489
 * @param array $array Array of values
1490
 *
1491
 * @return array
1492
 */
1493
function utf8Converter(array $array): array
1494
{
1495
    array_walk_recursive(
1496
        $array,
1497
        static function (&$item): void {
1498
            if (mb_detect_encoding((string) $item, 'utf-8', true) === false) {
1499
                $item = utf8_encode($item);
1500
            }
1501
        }
1502
    );
1503
    return $array;
1504
}
1505
1506
/**
1507
 * Permits to prepare data to be exchanged.
1508
 *
1509
 * @param string       $teampassDir
1510
 * @param array|string $data Text
1511
 * @param string       $type Parameter
1512
 * @param string       $key  Optional key
1513
 *
1514
 * @return string|array
1515
 */
1516
function prepareExchangedData($teampassDir, $data, string $type, ?string $key = null)
1517
{
1518
    $teampassDir = __DIR__ . '/..';
1519
    // Load superglobal
1520
    include_once $teampassDir . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1521
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1522
    // Get superglobals
1523
    if ($key !== null) {
1524
        $superGlobal->put('key', $key, 'SESSION');
1525
        $globalsKey = $key;
1526
    } else {
1527
        $globalsKey = $superGlobal->get('key', 'SESSION');
1528
    }
1529
1530
    //load ClassLoader
1531
    include_once $teampassDir . '/sources/SplClassLoader.php';
1532
    //Load CRYPTOJS
1533
    include_once $teampassDir . '/includes/libraries/Encryption/CryptoJs/Encryption.php';
1534
    if ($type === 'encode' && is_array($data) === true) {
1535
        // Ensure UTF8 format
1536
        $data = utf8Converter($data);
1537
        // Now encode
1538
        return Encryption\CryptoJs\Encryption::encrypt(
1539
            json_encode(
1540
                $data,
1541
                JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1542
            ),
1543
            $globalsKey
1544
        );
1545
    }
1546
    if ($type === 'decode' && is_array($data) === false) {
1547
        return json_decode(
1548
            Encryption\CryptoJs\Encryption::decrypt(
0 ignored issues
show
Bug introduced by
It seems like Encryption\CryptoJs\Encr...ypt($data, $globalsKey) can also be of type null; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1548
            /** @scrutinizer ignore-type */ Encryption\CryptoJs\Encryption::decrypt(
Loading history...
1549
                $data,
0 ignored issues
show
Bug introduced by
$data of type array is incompatible with the type string expected by parameter $encryptedString of Encryption\CryptoJs\Encryption::decrypt(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1549
                /** @scrutinizer ignore-type */ $data,
Loading history...
1550
                $globalsKey
1551
            ),
1552
            true
1553
        );
1554
    }
1555
}
1556
1557
1558
/**
1559
 * Create a thumbnail.
1560
 *
1561
 * @param string  $src           Source
1562
 * @param string  $dest          Destination
1563
 * @param int $desired_width Size of width
1564
 * 
1565
 * @return void|string|bool
1566
 */
1567
function makeThumbnail(string $src, string $dest, int $desired_width)
1568
{
1569
    /* read the source image */
1570
    if (is_file($src) === true && mime_content_type($src) === 'image/png') {
1571
        $source_image = imagecreatefrompng($src);
1572
        if ($source_image === false) {
1573
            return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1574
        }
1575
    } else {
1576
        return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1577
    }
1578
1579
    // Get height and width
1580
    $width = imagesx($source_image);
1581
    $height = imagesy($source_image);
1582
    /* find the "desired height" of this thumbnail, relative to the desired width  */
1583
    $desired_height = (int) floor($height * $desired_width / $width);
1584
    /* create a new, "virtual" image */
1585
    $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
1586
    if ($virtual_image === false) {
1587
        return false;
1588
    }
1589
    /* copy source image at a resized size */
1590
    imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
1591
    /* create the physical thumbnail image to its destination */
1592
    imagejpeg($virtual_image, $dest);
1593
}
1594
1595
/**
1596
 * Check table prefix in SQL query.
1597
 *
1598
 * @param string $table Table name
1599
 * 
1600
 * @return string
1601
 */
1602
function prefixTable(string $table): string
1603
{
1604
    $safeTable = htmlspecialchars(DB_PREFIX . $table);
1605
    if (! empty($safeTable)) {
1606
        // sanitize string
1607
        return $safeTable;
1608
    }
1609
    // stop error no table
1610
    return 'table_not_exists';
1611
}
1612
1613
/**
1614
 * GenerateCryptKey
1615
 *
1616
 * @param int     $size      Length
1617
 * @param bool $secure Secure
1618
 * @param bool $numerals Numerics
1619
 * @param bool $uppercase Uppercase letters
1620
 * @param bool $symbols Symbols
1621
 * @param bool $lowercase Lowercase
1622
 * @param array   $SETTINGS  SETTINGS
1623
 * 
1624
 * @return string
1625
 */
1626
function GenerateCryptKey(
1627
    int $size = 10,
1628
    bool $secure = false,
1629
    bool $numerals = false,
1630
    bool $uppercase = false,
1631
    bool $symbols = false,
1632
    bool $lowercase = false,
1633
    array $SETTINGS = []
1634
): string {
1635
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1636
    $generator = new SplClassLoader('PasswordGenerator\Generator', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1637
    $generator->register();
1638
    $generator = new PasswordGenerator\Generator\ComputerPasswordGenerator();
1639
    // Is PHP7 being used?
1640
    if (version_compare(PHP_VERSION, '7.0.0', '>=')) {
1641
        $php7generator = new SplClassLoader('PasswordGenerator\RandomGenerator', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1642
        $php7generator->register();
1643
        $generator->setRandomGenerator(new PasswordGenerator\RandomGenerator\Php7RandomGenerator());
1644
    }
1645
    
1646
    // Manage size
1647
    $generator->setLength((int) $size);
1648
    if ($secure === true) {
1649
        $generator->setSymbols(true);
1650
        $generator->setLowercase(true);
1651
        $generator->setUppercase(true);
1652
        $generator->setNumbers(true);
1653
    } else {
1654
        $generator->setLowercase($lowercase);
1655
        $generator->setUppercase($uppercase);
1656
        $generator->setNumbers($numerals);
1657
        $generator->setSymbols($symbols);
1658
    }
1659
1660
    return $generator->generatePasswords()[0];
1661
}
1662
1663
/**
1664
 * Send sysLOG message
1665
 *
1666
 * @param string    $message
1667
 * @param string    $host
1668
 * @param int       $port
1669
 * @param string    $component
1670
 * 
1671
 * @return void
1672
*/
1673
function send_syslog($message, $host, $port, $component = 'teampass'): void
1674
{
1675
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1676
    $syslog_message = '<123>' . date('M d H:i:s ') . $component . ': ' . $message;
1677
    socket_sendto($sock, (string) $syslog_message, strlen($syslog_message), 0, (string) $host, (int) $port);
1678
    socket_close($sock);
1679
}
1680
1681
/**
1682
 * Permits to log events into DB
1683
 *
1684
 * @param array  $SETTINGS Teampass settings
1685
 * @param string $type     Type
1686
 * @param string $label    Label
1687
 * @param string $who      Who
1688
 * @param string $login    Login
1689
 * @param string $field_1  Field
1690
 * 
1691
 * @return void
1692
 */
1693
function logEvents(array $SETTINGS, string $type, string $label, string $who, ?string $login = null, ?string $field_1 = null): void
1694
{
1695
    if (empty($who)) {
1696
        $who = getClientIpServer();
1697
    }
1698
1699
    // include librairies & connect to DB
1700
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1701
    if (defined('DB_PASSWD_CLEAR') === false) {
1702
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1703
    }
1704
    DB::$host = DB_HOST;
1705
    DB::$user = DB_USER;
1706
    DB::$password = DB_PASSWD_CLEAR;
1707
    DB::$dbName = DB_NAME;
1708
    DB::$port = DB_PORT;
1709
    DB::$encoding = DB_ENCODING;
1710
    DB::$ssl = DB_SSL;
1711
    DB::$connect_options = DB_CONNECT_OPTIONS;
1712
    DB::insert(
1713
        prefixTable('log_system'),
1714
        [
1715
            'type' => $type,
1716
            'date' => time(),
1717
            'label' => $label,
1718
            'qui' => $who,
1719
            'field_1' => $field_1 === null ? '' : $field_1,
1720
        ]
1721
    );
1722
    // If SYSLOG
1723
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1724
        if ($type === 'user_mngt') {
1725
            send_syslog(
1726
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1727
                $SETTINGS['syslog_host'],
1728
                $SETTINGS['syslog_port'],
1729
                'teampass'
1730
            );
1731
        } else {
1732
            send_syslog(
1733
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1734
                $SETTINGS['syslog_host'],
1735
                $SETTINGS['syslog_port'],
1736
                'teampass'
1737
            );
1738
        }
1739
    }
1740
}
1741
1742
/**
1743
 * Log events.
1744
 *
1745
 * @param array  $SETTINGS        Teampass settings
1746
 * @param int    $item_id         Item id
1747
 * @param string $item_label      Item label
1748
 * @param int    $id_user         User id
1749
 * @param string $action          Code for reason
1750
 * @param string $login           User login
1751
 * @param string $raison          Code for reason
1752
 * @param string $encryption_type Encryption on
1753
 * 
1754
 * @return void
1755
 */
1756
function logItems(
1757
    array $SETTINGS,
1758
    int $item_id,
1759
    string $item_label,
1760
    int $id_user,
1761
    string $action,
1762
    ?string $login = null,
1763
    ?string $raison = null,
1764
    ?string $encryption_type = null
1765
): void {
1766
    // include librairies & connect to DB
1767
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1768
    if (defined('DB_PASSWD_CLEAR') === false) {
1769
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1770
    }
1771
    DB::$host = DB_HOST;
1772
    DB::$user = DB_USER;
1773
    DB::$password = DB_PASSWD_CLEAR;
1774
    DB::$dbName = DB_NAME;
1775
    DB::$port = DB_PORT;
1776
    DB::$encoding = DB_ENCODING;
1777
    DB::$ssl = DB_SSL;
1778
    DB::$connect_options = DB_CONNECT_OPTIONS;
1779
    // Insert log in DB
1780
    DB::insert(
1781
        prefixTable('log_items'),
1782
        [
1783
            'id_item' => $item_id,
1784
            'date' => time(),
1785
            'id_user' => $id_user,
1786
            'action' => $action,
1787
            'raison' => $raison,
1788
            'raison_iv' => '',
1789
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1790
        ]
1791
    );
1792
    // Timestamp the last change
1793
    if ($action === 'at_creation' || $action === 'at_modifiation' || $action === 'at_delete' || $action === 'at_import') {
1794
        DB::update(
1795
            prefixTable('misc'),
1796
            [
1797
                'valeur' => time(),
1798
            ],
1799
            'type = %s AND intitule = %s',
1800
            'timestamp',
1801
            'last_item_change'
1802
        );
1803
    }
1804
1805
    // SYSLOG
1806
    if (isset($SETTINGS['syslog_enable']) === true && $SETTINGS['syslog_enable'] === '1') {
1807
        // Extract reason
1808
        $attribute = is_null($raison) === true ? '' : explode(' : ', $raison);
1809
        // Get item info if not known
1810
        if (empty($item_label) === true) {
1811
            $dataItem = DB::queryfirstrow(
1812
                'SELECT id, id_tree, label
1813
                FROM ' . prefixTable('items') . '
1814
                WHERE id = %i',
1815
                $item_id
1816
            );
1817
            $item_label = $dataItem['label'];
1818
        }
1819
1820
        send_syslog(
1821
            'action=' . str_replace('at_', '', $action) .
1822
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1823
                ' itemno=' . $item_id .
1824
                ' user=' . is_null($login) === true ? '' : addslashes((string) $login) .
1825
                ' itemname="' . addslashes($item_label) . '"',
1826
            $SETTINGS['syslog_host'],
1827
            $SETTINGS['syslog_port'],
1828
            'teampass'
1829
        );
1830
    }
1831
1832
    // send notification if enabled
1833
    notifyOnChange($item_id, $action, $SETTINGS);
1834
}
1835
1836
/**
1837
 * If enabled, then notify admin/manager.
1838
 *
1839
 * @param int    $item_id  Item id
1840
 * @param string $action   Action to do
1841
 * @param array  $SETTINGS Teampass settings
1842
 * 
1843
 * @return void
1844
 */
1845
function notifyOnChange(int $item_id, string $action, array $SETTINGS): void
1846
{
1847
    if (
1848
        isset($SETTINGS['enable_email_notification_on_item_shown']) === true
1849
        && (int) $SETTINGS['enable_email_notification_on_item_shown'] === 1
1850
        && $action === 'at_shown'
1851
    ) {
1852
        // Load superglobal
1853
        include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1854
        $superGlobal = new protect\SuperGlobal\SuperGlobal();
1855
        // Get superglobals
1856
        $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1857
        $globalsName = $superGlobal->get('name', 'SESSION');
1858
        $globalsNotifiedEmails = $superGlobal->get('listNotificationEmails', 'SESSION');
1859
        // Get info about item
1860
        $dataItem = DB::queryfirstrow(
1861
            'SELECT id, id_tree, label
1862
            FROM ' . prefixTable('items') . '
1863
            WHERE id = %i',
1864
            $item_id
1865
        );
1866
        $item_label = $dataItem['label'];
1867
        // send back infos
1868
        DB::insert(
1869
            prefixTable('emails'),
1870
            [
1871
                'timestamp' => time(),
1872
                'subject' => langHdl('email_on_open_notification_subject'),
1873
                'body' => str_replace(
1874
                    ['#tp_user#', '#tp_item#', '#tp_link#'],
1875
                    [
1876
                        addslashes($globalsName . ' ' . $globalsLastname),
1877
                        addslashes($item_label),
1878
                        $SETTINGS['cpassman_url'] . '/index.php?page=items&group=' . $dataItem['id_tree'] . '&id=' . $item_id,
1879
                    ],
1880
                    langHdl('email_on_open_notification_mail')
1881
                ),
1882
                'receivers' => $globalsNotifiedEmails,
1883
                'status' => '',
1884
            ]
1885
        );
1886
    }
1887
}
1888
1889
/**
1890
 * Prepare notification email to subscribers.
1891
 *
1892
 * @param int    $item_id  Item id
1893
 * @param string $label    Item label
1894
 * @param array  $changes  List of changes
1895
 * @param array  $SETTINGS Teampass settings
1896
 * 
1897
 * @return void
1898
 */
1899
function notifyChangesToSubscribers(int $item_id, string $label, array $changes, array $SETTINGS): void
1900
{
1901
    // Load superglobal
1902
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1903
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1904
    // Get superglobals
1905
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
1906
    $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1907
    $globalsName = $superGlobal->get('name', 'SESSION');
1908
    // send email to user that what to be notified
1909
    $notification = DB::queryOneColumn(
1910
        'email',
1911
        'SELECT *
1912
        FROM ' . prefixTable('notification') . ' AS n
1913
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1914
        WHERE n.item_id = %i AND n.user_id != %i',
1915
        $item_id,
1916
        $globalsUserId
1917
    );
1918
    if (DB::count() > 0) {
1919
        // Prepare path
1920
        $path = geItemReadablePath($item_id, '', $SETTINGS);
1921
        // Get list of changes
1922
        $htmlChanges = '<ul>';
1923
        foreach ($changes as $change) {
1924
            $htmlChanges .= '<li>' . $change . '</li>';
1925
        }
1926
        $htmlChanges .= '</ul>';
1927
        // send email
1928
        DB::insert(
1929
            prefixTable('emails'),
1930
            [
1931
                'timestamp' => time(),
1932
                'subject' => langHdl('email_subject_item_updated'),
1933
                'body' => str_replace(
1934
                    ['#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'],
1935
                    [$label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges],
1936
                    langHdl('email_body_item_updated')
1937
                ),
1938
                'receivers' => implode(',', $notification),
1939
                'status' => '',
1940
            ]
1941
        );
1942
    }
1943
}
1944
1945
/**
1946
 * Returns the Item + path.
1947
 *
1948
 * @param int    $id_tree  Node id
1949
 * @param string $label    Label
1950
 * @param array  $SETTINGS TP settings
1951
 * 
1952
 * @return string
1953
 */
1954
function geItemReadablePath(int $id_tree, string $label, array $SETTINGS): string
1955
{
1956
    // Class loader
1957
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1958
    //Load Tree
1959
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1960
    $tree->register();
1961
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1962
    $arbo = $tree->getPath($id_tree, true);
1963
    $path = '';
1964
    foreach ($arbo as $elem) {
1965
        if (empty($path) === true) {
1966
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
1967
        } else {
1968
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
1969
        }
1970
    }
1971
1972
    // Build text to show user
1973
    if (empty($label) === false) {
1974
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
1975
    }
1976
    return empty($path) === true ? '' : $path;
1977
}
1978
1979
/**
1980
 * Get the client ip address.
1981
 *
1982
 * @return string IP address
1983
 */
1984
function getClientIpServer(): string
1985
{
1986
    if (getenv('HTTP_CLIENT_IP')) {
1987
        $ipaddress = getenv('HTTP_CLIENT_IP');
1988
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
1989
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
1990
    } elseif (getenv('HTTP_X_FORWARDED')) {
1991
        $ipaddress = getenv('HTTP_X_FORWARDED');
1992
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
1993
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
1994
    } elseif (getenv('HTTP_FORWARDED')) {
1995
        $ipaddress = getenv('HTTP_FORWARDED');
1996
    } elseif (getenv('REMOTE_ADDR')) {
1997
        $ipaddress = getenv('REMOTE_ADDR');
1998
    } else {
1999
        $ipaddress = 'UNKNOWN';
2000
    }
2001
2002
    return $ipaddress;
2003
}
2004
2005
/**
2006
 * Escape all HTML, JavaScript, and CSS.
2007
 *
2008
 * @param string $input    The input string
2009
 * @param string $encoding Which character encoding are we using?
2010
 * 
2011
 * @return string
2012
 */
2013
function noHTML(string $input, string $encoding = 'UTF-8'): string
2014
{
2015
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
2016
}
2017
2018
/**
2019
 * Permits to handle the Teampass config file
2020
 * $action accepts "rebuild" and "update"
2021
 *
2022
 * @param string $action   Action to perform
2023
 * @param array  $SETTINGS Teampass settings
2024
 * @param string $field    Field to refresh
2025
 * @param string $value    Value to set
2026
 *
2027
 * @return string|bool
2028
 */
2029
function handleConfigFile($action, $SETTINGS, $field = null, $value = null)
2030
{
2031
    $tp_config_file = $SETTINGS['cpassman_dir'] . '/includes/config/tp.config.php';
2032
    // include librairies & connect to DB
2033
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2034
    if (defined('DB_PASSWD_CLEAR') === false) {
2035
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2036
    }
2037
    DB::$host = DB_HOST;
2038
    DB::$user = DB_USER;
2039
    DB::$password = DB_PASSWD_CLEAR;
2040
    DB::$dbName = DB_NAME;
2041
    DB::$port = DB_PORT;
2042
    DB::$encoding = DB_ENCODING;
2043
    DB::$ssl = DB_SSL;
2044
    DB::$connect_options = DB_CONNECT_OPTIONS;
2045
    if (file_exists($tp_config_file) === false || $action === 'rebuild') {
2046
        // perform a copy
2047
        if (file_exists($tp_config_file)) {
2048
            if (! copy($tp_config_file, $tp_config_file . '.' . date('Y_m_d_His', time()))) {
2049
                return "ERROR: Could not copy file '" . $tp_config_file . "'";
2050
            }
2051
        }
2052
2053
        // regenerate
2054
        $data = [];
2055
        $data[0] = "<?php\n";
2056
        $data[1] = "global \$SETTINGS;\n";
2057
        $data[2] = "\$SETTINGS = array (\n";
2058
        $rows = DB::query(
2059
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s',
2060
            'admin'
2061
        );
2062
        foreach ($rows as $record) {
2063
            array_push($data, "    '" . $record['intitule'] . "' => '" . $record['valeur'] . "',\n");
2064
        }
2065
        array_push($data, ");\n");
2066
        $data = array_unique($data);
2067
    // ---
2068
    } elseif ($action === 'update' && empty($field) === false) {
2069
        $data = file($tp_config_file);
2070
        $inc = 0;
2071
        $bFound = false;
2072
        foreach ($data as $line) {
2073
            if (stristr($line, ');')) {
2074
                break;
2075
            }
2076
2077
            if (stristr($line, "'" . $field . "' => '")) {
2078
                $data[$inc] = "    '" . $field . "' => '" . filter_var($value, FILTER_SANITIZE_STRING) . "',\n";
2079
                $bFound = true;
2080
                break;
2081
            }
2082
            ++$inc;
2083
        }
2084
        if ($bFound === false) {
2085
            $data[$inc] = "    '" . $field . "' => '" . filter_var($value, FILTER_SANITIZE_STRING) . "',\n);\n";
2086
        }
2087
    }
2088
2089
    // update file
2090
    file_put_contents($tp_config_file, implode('', $data ?? []));
2091
    return true;
2092
}
2093
2094
/**
2095
 * Permits to replace &#92; to permit correct display
2096
 *
2097
 * @param string $input Some text
2098
 * 
2099
 * @return string
2100
 */
2101
function handleBackslash(string $input): string
2102
{
2103
    return str_replace('&amp;#92;', '&#92;', $input);
2104
}
2105
2106
/**
2107
 * Permits to load settings
2108
 * 
2109
 * @return void
2110
*/
2111
function loadSettings(): void
2112
{
2113
    global $SETTINGS;
2114
    /* LOAD CPASSMAN SETTINGS */
2115
    if (! isset($SETTINGS['loaded']) || $SETTINGS['loaded'] !== 1) {
2116
        $SETTINGS = [];
2117
        $SETTINGS['duplicate_folder'] = 0;
2118
        //by default, this is set to 0;
2119
        $SETTINGS['duplicate_item'] = 0;
2120
        //by default, this is set to 0;
2121
        $SETTINGS['number_of_used_pw'] = 5;
2122
        //by default, this value is set to 5;
2123
        $settings = [];
2124
        $rows = DB::query(
2125
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
2126
            [
2127
                'type' => 'admin',
2128
                'type2' => 'settings',
2129
            ]
2130
        );
2131
        foreach ($rows as $record) {
2132
            if ($record['type'] === 'admin') {
2133
                $SETTINGS[$record['intitule']] = $record['valeur'];
2134
            } else {
2135
                $settings[$record['intitule']] = $record['valeur'];
2136
            }
2137
        }
2138
        $SETTINGS['loaded'] = 1;
2139
        $SETTINGS['default_session_expiration_time'] = 5;
2140
    }
2141
}
2142
2143
/**
2144
 * check if folder has custom fields.
2145
 * Ensure that target one also has same custom fields
2146
 * 
2147
 * @param int $source_id
2148
 * @param int $target_id 
2149
 * 
2150
 * @return bool
2151
*/
2152
function checkCFconsistency(int $source_id, int $target_id): bool
2153
{
2154
    $source_cf = [];
2155
    $rows = DB::QUERY(
2156
        'SELECT id_category
2157
            FROM ' . prefixTable('categories_folders') . '
2158
            WHERE id_folder = %i',
2159
        $source_id
2160
    );
2161
    foreach ($rows as $record) {
2162
        array_push($source_cf, $record['id_category']);
2163
    }
2164
2165
    $target_cf = [];
2166
    $rows = DB::QUERY(
2167
        'SELECT id_category
2168
            FROM ' . prefixTable('categories_folders') . '
2169
            WHERE id_folder = %i',
2170
        $target_id
2171
    );
2172
    foreach ($rows as $record) {
2173
        array_push($target_cf, $record['id_category']);
2174
    }
2175
2176
    $cf_diff = array_diff($source_cf, $target_cf);
2177
    if (count($cf_diff) > 0) {
2178
        return false;
2179
    }
2180
2181
    return true;
2182
}
2183
2184
/**
2185
 * Will encrypte/decrypt a fil eusing Defuse.
2186
 *
2187
 * @param string $type        can be either encrypt or decrypt
2188
 * @param string $source_file path to source file
2189
 * @param string $target_file path to target file
2190
 * @param array  $SETTINGS    Settings
2191
 * @param string $password    A password
2192
 *
2193
 * @return string|bool
2194
 */
2195
function prepareFileWithDefuse(
2196
    string $type,
2197
    string $source_file,
2198
    string $target_file,
2199
    array $SETTINGS,
2200
    string $password = null
2201
) {
2202
    // Load AntiXSS
2203
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/AntiXSS.php';
2204
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/ASCII.php';
2205
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/UTF8.php';
2206
    $antiXss = new voku\helper\AntiXSS();
2207
    // Protect against bad inputs
2208
    if (is_array($source_file) === true || is_array($target_file) === true) {
2209
        return 'error_cannot_be_array';
2210
    }
2211
2212
    // Sanitize
2213
    $source_file = $antiXss->xss_clean($source_file);
2214
    $target_file = $antiXss->xss_clean($target_file);
2215
    if (empty($password) === true || is_null($password) === true) {
2216
        // get KEY to define password
2217
        $ascii_key = file_get_contents(SECUREPATH . '/teampass-seckey.txt');
2218
        $password = \Defuse\Crypto\Key::loadFromAsciiSafeString($ascii_key);
2219
    }
2220
2221
    $err = '';
2222
    if ($type === 'decrypt') {
2223
        // Decrypt file
2224
        $err = defuseFileDecrypt(
2225
            $source_file,
2226
            $target_file,
2227
            $SETTINGS, /** @scrutinizer ignore-type */
2228
            $password
2229
        );
2230
    } elseif ($type === 'encrypt') {
2231
        // Encrypt file
2232
        $err = defuseFileEncrypt(
2233
            $source_file,
2234
            $target_file,
2235
            $SETTINGS, /** @scrutinizer ignore-type */
2236
            $password
2237
        );
2238
    }
2239
2240
    // return error
2241
    return empty($err) === false ? $err : '';
2242
}
2243
2244
/**
2245
 * Encrypt a file with Defuse.
2246
 *
2247
 * @param string $source_file path to source file
2248
 * @param string $target_file path to target file
2249
 * @param array  $SETTINGS    Settings
2250
 * @param string $password    A password
2251
 *
2252
 * @return string|bool
2253
 */
2254
function defuseFileEncrypt(
2255
    string $source_file,
2256
    string $target_file,
2257
    array $SETTINGS,
2258
    string $password = null
2259
) {
2260
    // load PhpEncryption library
2261
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2262
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2263
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2264
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2265
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2266
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2267
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2268
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2269
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2270
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2271
    try {
2272
        \Defuse\Crypto\File::encryptFileWithPassword(
2273
            $source_file,
2274
            $target_file,
2275
            $password
2276
        );
2277
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2278
        $err = 'wrong_key';
2279
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2280
        $err = $ex;
2281
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2282
        $err = $ex;
2283
    }
2284
2285
    // return error
2286
    return empty($err) === false ? $err : true;
2287
}
2288
2289
/**
2290
 * Decrypt a file with Defuse.
2291
 *
2292
 * @param string $source_file path to source file
2293
 * @param string $target_file path to target file
2294
 * @param array  $SETTINGS    Settings
2295
 * @param string $password    A password
2296
 *
2297
 * @return string|bool
2298
 */
2299
function defuseFileDecrypt(
2300
    string $source_file,
2301
    string $target_file,
2302
    array $SETTINGS,
2303
    string $password = null
2304
) {
2305
    // load PhpEncryption library
2306
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2307
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2308
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2309
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2310
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2311
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2312
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2313
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2314
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2315
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2316
    try {
2317
        \Defuse\Crypto\File::decryptFileWithPassword(
2318
            $source_file,
2319
            $target_file,
2320
            $password
2321
        );
2322
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2323
        $err = 'wrong_key';
2324
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2325
        $err = $ex;
2326
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2327
        $err = $ex;
2328
    }
2329
2330
    // return error
2331
    return empty($err) === false ? $err : true;
2332
}
2333
2334
/*
2335
* NOT TO BE USED
2336
*/
2337
/**
2338
 * Undocumented function.
2339
 *
2340
 * @param string $text Text to debug
2341
 */
2342
function debugTeampass(string $text): void
2343
{
2344
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
2345
    if ($debugFile !== false) {
2346
        fputs($debugFile, $text);
2347
        fclose($debugFile);
2348
    }
2349
}
2350
2351
/**
2352
 * DELETE the file with expected command depending on server type.
2353
 *
2354
 * @param string $file     Path to file
2355
 * @param array  $SETTINGS Teampass settings
2356
 *
2357
 * @return void
2358
 */
2359
function fileDelete(string $file, array $SETTINGS): void
2360
{
2361
    // Load AntiXSS
2362
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/ASCII.php';
2363
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/UTF8.php';
2364
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/AntiXSS.php';
2365
    $antiXss = new voku\helper\AntiXSS();
2366
    $file = $antiXss->xss_clean($file);
2367
    if (is_file($file)) {
2368
        unlink($file);
2369
    }
2370
}
2371
2372
/**
2373
 * Permits to extract the file extension.
2374
 *
2375
 * @param string $file File name
2376
 *
2377
 * @return string
2378
 */
2379
function getFileExtension(string $file): string
2380
{
2381
    if (strpos($file, '.') === false) {
2382
        return $file;
2383
    }
2384
2385
    return substr($file, strrpos($file, '.') + 1);
2386
}
2387
2388
/**
2389
 * Chmods files and folders with different permissions.
2390
 *
2391
 * This is an all-PHP alternative to using: \n
2392
 * <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2393
 * <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2394
 *
2395
 * @author Jeppe Toustrup (tenzer at tenzer dot dk)
2396
  *
2397
 * @param string $path      An either relative or absolute path to a file or directory which should be processed.
2398
 * @param int    $filePerm The permissions any found files should get.
2399
 * @param int    $dirPerm  The permissions any found folder should get.
2400
 *
2401
 * @return bool Returns TRUE if the path if found and FALSE if not.
2402
 *
2403
 * @warning The permission levels has to be entered in octal format, which
2404
 * normally means adding a zero ("0") in front of the permission level. \n
2405
 * More info at: http://php.net/chmod.
2406
*/
2407
2408
function recursiveChmod(
2409
    string $path,
2410
    int $filePerm = 0644,
2411
    int  $dirPerm = 0755
2412
) {
2413
    // Check if the path exists
2414
    if (! file_exists($path)) {
2415
        return false;
2416
    }
2417
2418
    // See whether this is a file
2419
    if (is_file($path)) {
2420
        // Chmod the file with our given filepermissions
2421
        chmod($path, $filePerm);
2422
    // If this is a directory...
2423
    } elseif (is_dir($path)) {
2424
        // Then get an array of the contents
2425
        $foldersAndFiles = scandir($path);
2426
        // Remove "." and ".." from the list
2427
        $entries = array_slice($foldersAndFiles, 2);
2428
        // Parse every result...
2429
        foreach ($entries as $entry) {
2430
            // And call this function again recursively, with the same permissions
2431
            recursiveChmod($path.'/'.$entry, $filePerm, $dirPerm);
2432
        }
2433
2434
        // When we are done with the contents of the directory, we chmod the directory itself
2435
        chmod($path, $dirPerm);
2436
    }
2437
2438
    // Everything seemed to work out well, return true
2439
    return true;
2440
}
2441
2442
/**
2443
 * Check if user can access to this item.
2444
 *
2445
 * @param int   $item_id ID of item
2446
 * @param array $SETTINGS
2447
 *
2448
 * @return bool|string
2449
 */
2450
function accessToItemIsGranted(int $item_id, array $SETTINGS)
2451
{
2452
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2453
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2454
    // Prepare superGlobal variables
2455
    $session_groupes_visibles = $superGlobal->get('groupes_visibles', 'SESSION');
2456
    $session_list_restricted_folders_for_items = $superGlobal->get('list_restricted_folders_for_items', 'SESSION');
2457
    // Load item data
2458
    $data = DB::queryFirstRow(
2459
        'SELECT id_tree
2460
        FROM ' . prefixTable('items') . '
2461
        WHERE id = %i',
2462
        $item_id
2463
    );
2464
    // Check if user can access this folder
2465
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2466
        // Now check if this folder is restricted to user
2467
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2468
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2469
        ) {
2470
            return 'ERR_FOLDER_NOT_ALLOWED';
2471
        }
2472
    }
2473
2474
    return true;
2475
}
2476
2477
/**
2478
 * Creates a unique key.
2479
 *
2480
 * @param int $lenght Key lenght
2481
 *
2482
 * @return string
2483
 */
2484
function uniqidReal(int $lenght = 13): string
2485
{
2486
    if (function_exists('random_bytes')) {
2487
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2488
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2489
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2490
    } else {
2491
        throw new Exception('no cryptographically secure random function available');
2492
    }
2493
2494
    return substr(bin2hex($bytes), 0, $lenght);
2495
}
2496
2497
/**
2498
 * Obfuscate an email.
2499
 *
2500
 * @param string $email Email address
2501
 *
2502
 * @return string
2503
 */
2504
function obfuscateEmail(string $email): string
2505
{
2506
    $email = explode("@", $email);
2507
    $name = $email[0];
2508
    if (strlen($name) > 3) {
2509
        $name = substr($name, 0, 2);
2510
        for ($i = 0; $i < strlen($email[0]) - 3; $i++) {
2511
            $name .= "*";
2512
        }
2513
        $name .= substr($email[0], -1, 1);
2514
    }
2515
    $host = explode(".", $email[1])[0];
2516
    if (strlen($host) > 3) {
2517
        $host = substr($host, 0, 1);
2518
        for ($i = 0; $i < strlen(explode(".", $email[1])[0]) - 2; $i++) {
2519
            $host .= "*";
2520
        }
2521
        $host .= substr(explode(".", $email[1])[0], -1, 1);
2522
    }
2523
    $email = $name . "@" . $host . "." . explode(".", $email[1])[1];
2524
    return $email;
2525
}
2526
2527
/**
2528
 * Perform a Query.
2529
 *
2530
 * @param array  $SETTINGS Teamapss settings
2531
 * @param string $fields   Fields to use
2532
 * @param string $table    Table to use
2533
 *
2534
 * @return array
2535
 */
2536
function performDBQuery(array $SETTINGS, string $fields, string $table): array
2537
{
2538
    // include librairies & connect to DB
2539
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2540
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2541
    if (defined('DB_PASSWD_CLEAR') === false) {
2542
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2543
    }
2544
    DB::$host = DB_HOST;
2545
    DB::$user = DB_USER;
2546
    DB::$password = DB_PASSWD_CLEAR;
2547
    DB::$dbName = DB_NAME;
2548
    DB::$port = DB_PORT;
2549
    DB::$encoding = DB_ENCODING;
2550
    DB::$ssl = DB_SSL;
2551
    DB::$connect_options = DB_CONNECT_OPTIONS;
2552
    // Insert log in DB
2553
    return DB::query(
2554
        'SELECT ' . $fields . '
2555
        FROM ' . prefixTable($table)
2556
    );
2557
}
2558
2559
/**
2560
 * Undocumented function.
2561
 *
2562
 * @param int $bytes Size of file
2563
 *
2564
 * @return string
2565
 */
2566
function formatSizeUnits(int $bytes): string
2567
{
2568
    if ($bytes >= 1073741824) {
2569
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2570
    } elseif ($bytes >= 1048576) {
2571
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2572
    } elseif ($bytes >= 1024) {
2573
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2574
    } elseif ($bytes > 1) {
2575
        $bytes .= ' bytes';
2576
    } elseif ($bytes === 1) {
2577
        $bytes .= ' byte';
2578
    } else {
2579
        $bytes = '0 bytes';
2580
    }
2581
2582
    return $bytes;
2583
}
2584
2585
/**
2586
 * Generate user pair of keys.
2587
 *
2588
 * @param string $userPwd User password
2589
 *
2590
 * @return array
2591
 */
2592
function generateUserKeys(string $userPwd): array
2593
{
2594
    // include library
2595
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2596
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2597
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2598
    // Load classes
2599
    $rsa = new Crypt_RSA();
2600
    $cipher = new Crypt_AES();
2601
    // Create the private and public key
2602
    $res = $rsa->createKey(4096);
2603
    // Encrypt the privatekey
2604
    $cipher->setPassword($userPwd);
2605
    $privatekey = $cipher->encrypt($res['privatekey']);
2606
    return [
2607
        'private_key' => base64_encode($privatekey),
2608
        'public_key' => base64_encode($res['publickey']),
2609
        'private_key_clear' => base64_encode($res['privatekey']),
2610
    ];
2611
}
2612
2613
/**
2614
 * Permits to decrypt the user's privatekey.
2615
 *
2616
 * @param string $userPwd        User password
2617
 * @param string $userPrivateKey User private key
2618
 *
2619
 * @return string
2620
 */
2621
function decryptPrivateKey(string $userPwd, string $userPrivateKey): string
2622
{
2623
    if (empty($userPwd) === false) {
2624
        include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2625
        // Load classes
2626
        $cipher = new Crypt_AES();
2627
        // Encrypt the privatekey
2628
        $cipher->setPassword($userPwd);
2629
        try {
2630
            return base64_encode((string) $cipher->decrypt(base64_decode($userPrivateKey)));
2631
        } catch (Exception $e) {
2632
            return $e;
2633
        }
2634
    }
2635
    return '';
2636
}
2637
2638
/**
2639
 * Permits to encrypt the user's privatekey.
2640
 *
2641
 * @param string $userPwd        User password
2642
 * @param string $userPrivateKey User private key
2643
 *
2644
 * @return string
2645
 */
2646
function encryptPrivateKey(string $userPwd, string $userPrivateKey): string
2647
{
2648
    if (empty($userPwd) === false) {
2649
        include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2650
        // Load classes
2651
        $cipher = new Crypt_AES();
2652
        // Encrypt the privatekey
2653
        $cipher->setPassword($userPwd);        
2654
        try {
2655
            return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2656
        } catch (Exception $e) {
2657
            return $e;
2658
        }
2659
    }
2660
    return '';
2661
}
2662
2663
/**
2664
 * Encrypts a string using AES.
2665
 *
2666
 * @param string $data String to encrypt
2667
 *
2668
 * @return array
2669
 */
2670
function doDataEncryption(string $data): array
2671
{
2672
    // Includes
2673
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2674
    // Load classes
2675
    $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2676
    // Generate an object key
2677
    $objectKey = uniqidReal(32);
2678
    // Set it as password
2679
    $cipher->setPassword($objectKey);
2680
    return [
2681
        'encrypted' => base64_encode($cipher->encrypt($data)),
2682
        'objectKey' => base64_encode($objectKey),
2683
    ];
2684
}
2685
2686
/**
2687
 * Decrypts a string using AES.
2688
 *
2689
 * @param string $data Encrypted data
2690
 * @param string $key  Key to uncrypt
2691
 *
2692
 * @return string
2693
 */
2694
function doDataDecryption(string $data, string $key): string
2695
{
2696
    // Includes
2697
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2698
    // Load classes
2699
    $cipher = new Crypt_AES();
2700
    // Set the object key
2701
    $cipher->setPassword(base64_decode($key));
2702
    return base64_encode($cipher->decrypt(base64_decode($data)));
2703
}
2704
2705
/**
2706
 * Encrypts using RSA a string using a public key.
2707
 *
2708
 * @param string $key       Key to be encrypted
2709
 * @param string $publicKey User public key
2710
 *
2711
 * @return string
2712
 */
2713
function encryptUserObjectKey(string $key, string $publicKey): string
2714
{
2715
    // Includes
2716
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2717
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2718
    // Load classes
2719
    $rsa = new Crypt_RSA();
2720
    $rsa->loadKey(base64_decode($publicKey));
2721
    // Encrypt
2722
    return base64_encode($rsa->encrypt(base64_decode($key)));
2723
}
2724
2725
/**
2726
 * Decrypts using RSA an encrypted string using a private key.
2727
 *
2728
 * @param string $key        Encrypted key
2729
 * @param string $privateKey User private key
2730
 *
2731
 * @return string
2732
 */
2733
function decryptUserObjectKey(string $key, string $privateKey): string
2734
{
2735
    // Includes
2736
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2737
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2738
    // Load classes
2739
    $rsa = new Crypt_RSA();
2740
    $rsa->loadKey(base64_decode($privateKey));
2741
    // Decrypt
2742
    try {
2743
        $ret = base64_encode($rsa->decrypt(base64_decode($key)));
2744
    } catch (Exception $e) {
2745
        return $e;
2746
    }
2747
2748
    return $ret;
2749
}
2750
2751
/**
2752
 * Encrypts a file.
2753
 *
2754
 * @param string $fileInName File name
2755
 * @param string $fileInPath Path to file
2756
 *
2757
 * @return array
2758
 */
2759
function encryptFile(string $fileInName, string $fileInPath): array
2760
{
2761
    if (defined('FILE_BUFFER_SIZE') === false) {
2762
        define('FILE_BUFFER_SIZE', 128 * 1024);
2763
    }
2764
2765
    // Includes
2766
    include_once __DIR__.'/../includes/config/include.php';
2767
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2768
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2769
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2770
    // Load classes
2771
    $cipher = new Crypt_AES();
2772
    // Generate an object key
2773
    $objectKey = uniqidReal(32);
2774
    // Set it as password
2775
    $cipher->setPassword($objectKey);
2776
    // Prevent against out of memory
2777
    $cipher->enableContinuousBuffer();
2778
    //$cipher->disablePadding();
2779
2780
    // Encrypt the file content
2781
    $plaintext = file_get_contents(
2782
        filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL)
2783
    );
2784
    $ciphertext = $cipher->encrypt($plaintext);
2785
    // Save new file
2786
    $hash = md5($plaintext);
2787
    $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2788
    file_put_contents($fileOut, $ciphertext);
2789
    unlink($fileInPath . '/' . $fileInName);
2790
    return [
2791
        'fileHash' => base64_encode($hash),
2792
        'objectKey' => base64_encode($objectKey),
2793
    ];
2794
}
2795
2796
/**
2797
 * Decrypt a file.
2798
 *
2799
 * @param string $fileName File name
2800
 * @param string $filePath Path to file
2801
 * @param string $key      Key to use
2802
 *
2803
 * @return string
2804
 */
2805
function decryptFile(string $fileName, string $filePath, string $key): string
2806
{
2807
    if (! defined('FILE_BUFFER_SIZE')) {
2808
        define('FILE_BUFFER_SIZE', 128 * 1024);
2809
    }
2810
2811
    // Includes
2812
    include_once __DIR__.'/../includes/config/include.php';
2813
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2814
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2815
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2816
    // Get file name
2817
    $fileName = base64_decode($fileName);
2818
    // Load classes
2819
    $cipher = new Crypt_AES();
2820
    // Set the object key
2821
    $cipher->setPassword(base64_decode($key));
2822
    // Prevent against out of memory
2823
    $cipher->enableContinuousBuffer();
2824
    $cipher->disablePadding();
2825
    // Get file content
2826
    $ciphertext = file_get_contents($filePath . '/' . TP_FILE_PREFIX . $fileName);
2827
    // Decrypt file content and return
2828
    return base64_encode($cipher->decrypt($ciphertext));
2829
}
2830
2831
/**
2832
 * Generate a simple password
2833
 *
2834
 * @param int $length Length of string
2835
 * @param bool $symbolsincluded Allow symbols
2836
 *
2837
 * @return string
2838
 */
2839
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2840
{
2841
    // Generate new user password
2842
    $small_letters = range('a', 'z');
2843
    $big_letters = range('A', 'Z');
2844
    $digits = range(0, 9);
2845
    $symbols = $symbolsincluded === true ?
2846
        ['#', '_', '-', '@', '$', '+', '&'] : [];
2847
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2848
    $count = count($res);
2849
    // first variant
2850
2851
    $random_string = '';
2852
    for ($i = 0; $i < $length; ++$i) {
2853
        $random_string .= $res[random_int(0, $count - 1)];
2854
    }
2855
2856
    return $random_string;
2857
}
2858
2859
/**
2860
 * Permit to store the sharekey of an object for users.
2861
 *
2862
 * @param string $object_name             Type for table selection
2863
 * @param int    $post_folder_is_personal Personal
2864
 * @param int    $post_folder_id          Folder
2865
 * @param int    $post_object_id          Object
2866
 * @param string $objectKey               Object key
2867
 * @param array  $SETTINGS                Teampass settings
2868
 *
2869
 * @return void
2870
 */
2871
function storeUsersShareKey(
2872
    string $object_name,
2873
    int $post_folder_is_personal,
2874
    int $post_folder_id,
2875
    int $post_object_id,
2876
    string $objectKey,
2877
    array $SETTINGS
2878
): void {
2879
    // include librairies & connect to DB
2880
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2881
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2882
    if (defined('DB_PASSWD_CLEAR') === false) {
2883
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2884
    }
2885
    DB::$host = DB_HOST;
2886
    DB::$user = DB_USER;
2887
    DB::$password = DB_PASSWD_CLEAR;
2888
    DB::$dbName = DB_NAME;
2889
    DB::$port = DB_PORT;
2890
    DB::$encoding = DB_ENCODING;
2891
    DB::$ssl = DB_SSL;
2892
    DB::$connect_options = DB_CONNECT_OPTIONS;
2893
    // Delete existing entries for this object
2894
    DB::delete(
2895
        $object_name,
2896
        'object_id = %i',
2897
        $post_object_id
2898
    );
2899
    // Superglobals
2900
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2901
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2902
    // Prepare superGlobal variables
2903
    $sessionPpersonaFolders = $superGlobal->get('personal_folders', 'SESSION');
2904
    $sessionUserId = $superGlobal->get('user_id', 'SESSION');
2905
    $sessionUserPublicKey = $superGlobal->get('public_key', 'SESSION', 'user');
2906
    if (
2907
        (int) $post_folder_is_personal === 1
2908
        && in_array($post_folder_id, $sessionPpersonaFolders) === true
2909
    ) {
2910
        // If this is a personal object
2911
        // Only create the sharekey for user
2912
        DB::insert(
2913
            $object_name,
2914
            [
2915
                'object_id' => (int) $post_object_id,
2916
                'user_id' => (int) $sessionUserId,
2917
                'share_key' => encryptUserObjectKey($objectKey, $sessionUserPublicKey),
2918
            ]
2919
        );
2920
    } else {
2921
        // This is a public object
2922
        // Create sharekey for each user
2923
        $users = DB::query(
2924
            'SELECT id, public_key
2925
            FROM ' . prefixTable('users') . '
2926
            WHERE id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '")
2927
            AND public_key != ""'
2928
        );
2929
        foreach ($users as $user) {
2930
            // Insert in DB the new object key for this item by user
2931
            DB::insert(
2932
                $object_name,
2933
                [
2934
                    'object_id' => $post_object_id,
2935
                    'user_id' => (int) $user['id'],
2936
                    'share_key' => encryptUserObjectKey(
2937
                        $objectKey,
2938
                        $user['public_key']
2939
                    ),
2940
                ]
2941
            );
2942
        }
2943
    }
2944
}
2945
2946
/**
2947
 * Is this string base64 encoded?
2948
 *
2949
 * @param string $str Encoded string?
2950
 *
2951
 * @return bool
2952
 */
2953
function isBase64(string $str): bool
2954
{
2955
    $str = (string) trim($str);
2956
    if (! isset($str[0])) {
2957
        return false;
2958
    }
2959
2960
    $base64String = (string) base64_decode($str, true);
2961
    if ($base64String && base64_encode($base64String) === $str) {
2962
        return true;
2963
    }
2964
2965
    return false;
2966
}
2967
2968
/**
2969
 * Undocumented function
2970
 *
2971
 * @param string $field Parameter
2972
 *
2973
 * @return array|bool|resource|string
2974
 */
2975
function filterString(string $field)
2976
{
2977
    // Sanitize string
2978
    $field = filter_var(trim($field), FILTER_SANITIZE_STRING);
2979
    if (empty($field) === false) {
2980
        // Load AntiXSS
2981
        include_once __DIR__.'/../includes/libraries/voku/helper/AntiXSS.php';
2982
        $antiXss = new voku\helper\AntiXSS();
2983
        // Return
2984
        return $antiXss->xss_clean($field);
2985
    }
2986
2987
    return false;
2988
}
2989
2990
/**
2991
 * CHeck if provided credentials are allowed on server
2992
 *
2993
 * @param string $login    User Login
2994
 * @param string $password User Pwd
2995
 * @param array  $SETTINGS Teampass settings
2996
 *
2997
 * @return bool
2998
 */
2999
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
3000
{
3001
    // Build ldap configuration array
3002
    $config = [
3003
        // Mandatory Configuration Options
3004
        'hosts' => [$SETTINGS['ldap_hosts']],
3005
        'base_dn' => $SETTINGS['ldap_bdn'],
3006
        'username' => $SETTINGS['ldap_username'],
3007
        'password' => $SETTINGS['ldap_password'],
3008
3009
        // Optional Configuration Options
3010
        'port' => $SETTINGS['ldap_port'],
3011
        'use_ssl' => (int) $SETTINGS['ldap_ssl'] === 1 ? true : false,
3012
        'use_tls' => (int) $SETTINGS['ldap_tls'] === 1 ? true : false,
3013
        'version' => 3,
3014
        'timeout' => 5,
3015
        'follow_referrals' => false,
3016
3017
        // Custom LDAP Options
3018
        'options' => [
3019
            // See: http://php.net/ldap_set_option
3020
            LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_HARD,
3021
        ],
3022
    ];
3023
    // Load expected libraries
3024
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Traits/Macroable.php';
3025
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Arr.php';
3026
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/DetectsErrors.php';
3027
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Connection.php';
3028
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/LdapInterface.php';
3029
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Ldap.php';
3030
    $ad = new SplClassLoader('LdapRecord', '../includes/libraries');
3031
    $ad->register();
3032
    $connection = new Connection($config);
3033
    // Connect to LDAP
3034
    try {
3035
        $connection->connect();
3036
    } catch (\LdapRecord\Auth\BindException $e) {
3037
        $error = $e->getDetailedError();
3038
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
3039
        return false;
3040
    }
3041
3042
    // Authenticate user
3043
    try {
3044
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory') {
3045
            $connection->auth()->attempt($login, $password, $stayAuthenticated = true);
3046
        } else {
3047
            $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);
3048
        }
3049
    } catch (\LdapRecord\Auth\BindException $e) {
3050
        $error = $e->getDetailedError();
3051
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
3052
        return false;
3053
    }
3054
3055
    return true;
3056
}
3057
3058
/**
3059
 * Removes from DB all sharekeys of this user
3060
 *
3061
 * @param int $userId User's id
3062
 * @param array   $SETTINGS Teampass settings
3063
 *
3064
 * @return bool
3065
 */
3066
function deleteUserObjetsKeys(int $userId, array $SETTINGS): bool
3067
{
3068
    // include librairies & connect to DB
3069
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
3070
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
3071
    if (defined('DB_PASSWD_CLEAR') === false) {
3072
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
3073
    }
3074
    DB::$host = DB_HOST;
3075
    DB::$user = DB_USER;
3076
    DB::$password = DB_PASSWD_CLEAR;
3077
    DB::$dbName = DB_NAME;
3078
    DB::$port = DB_PORT;
3079
    DB::$encoding = DB_ENCODING;
3080
    DB::$ssl = DB_SSL;
3081
    DB::$connect_options = DB_CONNECT_OPTIONS;
3082
    // Remove all item sharekeys items
3083
    DB::delete(
3084
        prefixTable('sharekeys_items'),
3085
        'user_id = %i',
3086
        $userId
3087
    );
3088
    // Remove all item sharekeys files
3089
    DB::delete(
3090
        prefixTable('sharekeys_files'),
3091
        'user_id = %i',
3092
        $userId
3093
    );
3094
    // Remove all item sharekeys fields
3095
    DB::delete(
3096
        prefixTable('sharekeys_fields'),
3097
        'user_id = %i',
3098
        $userId
3099
    );
3100
    // Remove all item sharekeys logs
3101
    DB::delete(
3102
        prefixTable('sharekeys_logs'),
3103
        'user_id = %i',
3104
        $userId
3105
    );
3106
    // Remove all item sharekeys suggestions
3107
    DB::delete(
3108
        prefixTable('sharekeys_suggestions'),
3109
        'user_id = %i',
3110
        $userId
3111
    );
3112
    return false;
3113
}
3114
3115
/**
3116
 * Manage list of timezones   $SETTINGS Teampass settings
3117
 *
3118
 * @return array
3119
 */
3120
function timezone_list()
3121
{
3122
    static $timezones = null;
3123
    if ($timezones === null) {
3124
        $timezones = [];
3125
        $offsets = [];
3126
        $now = new DateTime('now', new DateTimeZone('UTC'));
3127
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
3128
            $now->setTimezone(new DateTimeZone($timezone));
3129
            $offsets[] = $offset = $now->getOffset();
3130
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
3131
        }
3132
3133
        array_multisort($offsets, $timezones);
3134
    }
3135
3136
    return $timezones;
3137
}
3138
3139
/**
3140
 * Provide timezone offset
3141
 *
3142
 * @param int $offset Timezone offset
3143
 *
3144
 * @return string
3145
 */
3146
function format_GMT_offset($offset): string
3147
{
3148
    $hours = intval($offset / 3600);
3149
    $minutes = abs(intval($offset % 3600 / 60));
3150
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
3151
}
3152
3153
/**
3154
 * Provides timezone name
3155
 *
3156
 * @param string $name Timezone name
3157
 *
3158
 * @return string
3159
 */
3160
function format_timezone_name($name): string
3161
{
3162
    $name = str_replace('/', ', ', $name);
3163
    $name = str_replace('_', ' ', $name);
3164
3165
    return str_replace('St ', 'St. ', $name);
3166
}
3167
3168
/**
3169
 * Provides info about if user should use MFA
3170
 *
3171
 * @param string $userRolesIds  User roles ids
3172
 * @param string $mfaRoles      Roles for which MFA is requested
3173
 *
3174
 * @return bool
3175
 */
3176
function mfa_auth_requested(string $userRolesIds, string $mfaRoles): bool
3177
{
3178
    if (empty($mfaRoles) === true) {
3179
        return true;
3180
    }
3181
3182
    $mfaRoles = array_values(json_decode($mfaRoles, true));
3183
    $userRolesIds = array_filter(explode(';', $userRolesIds));
3184
    if (count($mfaRoles) === 0 || count($mfaRoles) === 0) {
3185
        return true;
3186
    }
3187
3188
    if (count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
3189
        return true;
3190
    }
3191
    return false;
3192
}
3193
3194
/**
3195
 * Permits to clean a string for export purpose
3196
 *
3197
 * @param string $text
3198
 * @param bool $emptyCheckOnly
3199
 * 
3200
 * @return string
3201
 */
3202
function cleanStringForExport(string $text, bool $emptyCheckOnly = false): string
3203
{
3204
    if (is_null($text) === true || empty($text) === true) {
3205
        return '';
3206
    }
3207
    // only expected to check if $text was empty
3208
    elseif ($emptyCheckOnly === true) {
3209
        return $text;
3210
    }
3211
3212
    return strip_tags(
3213
        cleanString(
3214
            html_entity_decode($text, ENT_QUOTES | ENT_XHTML, 'UTF-8'),
3215
            true)
3216
        );
3217
}
3218
3219
/**
3220
 * Permits to check if user ID is valid
3221
 *
3222
 * @param integer $post_user_id
3223
 * @return bool
3224
 */
3225
function isUserIdValid($userId): bool
3226
{
3227
    if (is_null($userId) === false
3228
        && isset($userId) === true
3229
        && empty($userId) === false
3230
    ) {
3231
        return true;
3232
    }
3233
    return false;
3234
}
3235
3236
/**
3237
 * Check if a key exists and if its value equal the one expected
3238
 *
3239
 * @param string $key
3240
 * @param integer|string $value
3241
 * @param array $array
3242
 * 
3243
 * @return boolean
3244
 */
3245
function isKeyExistingAndEqual(
3246
    string $key,
3247
    /*PHP8 - integer|string*/$value,
3248
    array $array
3249
): bool
3250
{
3251
    if (isset($array[$key]) === true
3252
        && (is_int($value) === true ?
3253
            (int) $array[$key] === $value :
3254
            (string) $array[$key] === $value)
3255
    ) {
3256
        return true;
3257
    }
3258
    return false;
3259
}
3260
3261
/**
3262
 * Check if a variable is not set or equal to a value
3263
 *
3264
 * @param string|null $var
3265
 * @param integer|string $value
3266
 * 
3267
 * @return boolean
3268
 */
3269
function isKeyNotSetOrEqual(
3270
    /*PHP8 - string|null*/$var,
3271
    /*PHP8 - integer|string*/$value
3272
): bool
3273
{
3274
    if (isset($var) === false
3275
        || (is_int($value) === true ?
3276
            (int) $var === $value :
3277
            (string) $var === $value)
3278
    ) {
3279
        return true;
3280
    }
3281
    return false;
3282
}
3283
3284
/**
3285
 * Check if a key exists and if its value < to the one expected
3286
 *
3287
 * @param string $key
3288
 * @param integer $value
3289
 * @param array $array
3290
 * 
3291
 * @return boolean
3292
 */
3293
function isKeyExistingAndInferior(string $key, int $value, array $array): bool
3294
{
3295
    if (isset($array[$key]) === true && (int) $array[$key] < $value) {
3296
        return true;
3297
    }
3298
    return false;
3299
}
3300
3301
/**
3302
 * Check if a key exists and if its value > to the one expected
3303
 *
3304
 * @param string $key
3305
 * @param integer $value
3306
 * @param array $array
3307
 * 
3308
 * @return boolean
3309
 */
3310
function isKeyExistingAndSuperior(string $key, int $value, array $array): bool
3311
{
3312
    if (isset($array[$key]) === true && (int) $array[$key] > $value) {
3313
        return true;
3314
    }
3315
    return false;
3316
}
3317
3318
/**
3319
 * Check if values in array are set
3320
 * Return true if all set
3321
 * Return false if one of them is not set
3322
 *
3323
 * @param array $arrayOfValues
3324
 * @return boolean
3325
 */
3326
function isSetArrayOfValues(array $arrayOfValues): bool
3327
{
3328
    foreach($arrayOfValues as $value) {
3329
        if (isset($value) === false) {
3330
            return false;
3331
        }
3332
    }
3333
    return true;
3334
}
3335
3336
/**
3337
 * Check if values in array are set
3338
 * Return true if all set
3339
 * Return false if one of them is not set
3340
 *
3341
 * @param array $arrayOfValues
3342
 * @param integer|string $value
3343
 * @return boolean
3344
 */
3345
function isArrayOfVarsEqualToValue(
3346
    array $arrayOfVars,
3347
    /*PHP8 - integer|string*/$value
3348
) : bool
3349
{
3350
    foreach($arrayOfVars as $variable) {
3351
        if ($variable !== $value) {
3352
            return false;
3353
        }
3354
    }
3355
    return true;
3356
}
3357
3358
/**
3359
 * Checks if at least one variable in array is equal to value
3360
 *
3361
 * @param array $arrayOfValues
3362
 * @param integer|string $value
3363
 * @return boolean
3364
 */
3365
function isOneVarOfArrayEqualToValue(
3366
    array $arrayOfVars,
3367
    /*PHP8 - integer|string*/$value
3368
) : bool
3369
{
3370
    foreach($arrayOfVars as $variable) {
3371
        if ($variable === $value) {
3372
            return true;
3373
        }
3374
    }
3375
    return false;
3376
}
3377
3378
/**
3379
 * Checks is value is null, not set OR empty
3380
 *
3381
 * @param string|int|null $value
3382
 * @return boolean
3383
 */
3384
function isValueSetNullEmpty(/*PHP8 - string|int|null*/ $value) : bool
3385
{
3386
    if (is_null($value) === true || isset($value) === false || empty($value) === true) {
3387
        return true;
3388
    }
3389
    return false;
3390
}
3391
3392
/**
3393
 * Checks if value is set and if empty is equal to passed boolean
3394
 *
3395
 * @param string|int $value
3396
 * @param boolean $boolean
3397
 * @return boolean
3398
 */
3399
function isValueSetEmpty($value, $boolean = true) : bool
3400
{
3401
    if (isset($value) === true && empty($value) === $boolean) {
3402
        return true;
3403
    }
3404
    return false;
3405
}
3406
3407
/**
3408
 * Ensure Complexity is translated
3409
 *
3410
 * @return void
3411
 */
3412
function defineComplexity() : void
3413
{
3414
    if (defined('TP_PW_COMPLEXITY') === false) {
3415
        define(
3416
            'TP_PW_COMPLEXITY',
3417
            [
3418
                TP_PW_STRENGTH_1 => array(TP_PW_STRENGTH_1, langHdl('complex_level1'), 'fas fa-thermometer-empty text-danger'),
3419
                TP_PW_STRENGTH_2 => array(TP_PW_STRENGTH_2, langHdl('complex_level2'), 'fas fa-thermometer-quarter text-warning'),
3420
                TP_PW_STRENGTH_3 => array(TP_PW_STRENGTH_3, langHdl('complex_level3'), 'fas fa-thermometer-half text-warning'),
3421
                TP_PW_STRENGTH_4 => array(TP_PW_STRENGTH_4, langHdl('complex_level4'), 'fas fa-thermometer-three-quarters text-success'),
3422
                TP_PW_STRENGTH_5 => array(TP_PW_STRENGTH_5, langHdl('complex_level5'), 'fas fa-thermometer-full text-success'),
3423
            ]
3424
        );
3425
    }
3426
}
3427
3428
/**
3429
 * Uses Sanitizer to perform data sanitization
3430
 *
3431
 * @param array     $data
3432
 * @param array     $filters
3433
 * @param string    $path
3434
 * @return array
3435
 */
3436
function dataSanitizer(
3437
    array $data,
3438
    array $filters,
3439
    string $path
3440
): array
3441
{
3442
    // Load Sanitizer library
3443
    require_once $path . '/includes/libraries/Illuminate/Support/Traits/Macroable.php';
3444
    require_once $path . '/includes/libraries/Illuminate/Support/Str.php';
3445
    require_once $path . '/includes/libraries/Illuminate/Validation/ValidationRuleParser.php';
3446
    require_once $path . '/includes/libraries/Illuminate/Support/Arr.php';
3447
    require_once $path . '/includes/libraries/Elegant/sanitizer/Contracts/Filter.php';
3448
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/Trim.php';
3449
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/Cast.php';
3450
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/EscapeHTML.php';
3451
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/EmptyStringToNull.php';
3452
    require_once $path . '/includes/libraries/Elegant/sanitizer/Sanitizer.php';
3453
3454
    // Sanitize post and get variables
3455
    $sanitizer = new Elegant\sanitizer\Sanitizer($data, $filters);
3456
    return $sanitizer->sanitize();
3457
}
3458
3459
/**
3460
 * Permits to manage the cache tree for a user
3461
 *
3462
 * @param integer $user_id
3463
 * @param string $data
3464
 * @param array $SETTINGS
3465
 * @param string $field_update
3466
 * @return void
3467
 */
3468
function cacheTreeUserHandler(int $user_id, string $data, array $SETTINGS, string $field_update = '')
3469
{
3470
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
3471
    //Connect to DB
3472
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
3473
    if (defined('DB_PASSWD_CLEAR') === false) {
3474
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
3475
    }
3476
    DB::$host = DB_HOST;
3477
    DB::$user = DB_USER;
3478
    DB::$password = DB_PASSWD_CLEAR;
3479
    DB::$dbName = DB_NAME;
3480
    DB::$port = DB_PORT;
3481
    DB::$encoding = DB_ENCODING;
3482
    DB::$ssl = DB_SSL;
3483
    DB::$connect_options = DB_CONNECT_OPTIONS;
3484
3485
    // Exists ?
3486
    $userCacheId = DB::queryfirstrow(
3487
        'SELECT increment_id
3488
        FROM ' . prefixTable('cache_tree') . '
3489
        WHERE user_id = %i',
3490
        $user_id
3491
    );
3492
    
3493
    if (is_null($userCacheId) === true || count($userCacheId) === 0) {
3494
        DB::insert(
3495
            prefixTable('cache_tree'),
3496
            array(
3497
                'data' => $data,
3498
                'timestamp' => time(),
3499
                'user_id' => $user_id
3500
            )
3501
        );
3502
    } else {
3503
        if (empty($field_update) === true) {
3504
            DB::update(
3505
                prefixTable('cache_tree'),
3506
                [
3507
                    'timestamp' => time(),
3508
                    'data' => $data,
3509
                ],
3510
                'increment_id = %i',
3511
                $userCacheId['increment_id']
3512
            );
3513
        } else {
3514
            DB::update(
3515
                prefixTable('cache_tree'),
3516
                [
3517
                    $field_update => $data,
3518
                ],
3519
                'increment_id = %i',
3520
                $userCacheId['increment_id']
3521
            );
3522
        }
3523
    }
3524
}
3525
3526
/**
3527
 * Permits to calculate a %
3528
 *
3529
 * @param float $nombre
3530
 * @param float $total
3531
 * @param float $pourcentage
3532
 * @return float
3533
 */
3534
function pourcentage(float $nombre, float $total, float $pourcentage): float
3535
{ 
3536
    $resultat = ($nombre/$total) * $pourcentage;
3537
    return round($resultat);
3538
}
3539
3540
function loadFoldersListByCache(
3541
    bool $forceRefresh = false
3542
): array
3543
{
3544
    // Case when refresh is EXPECTED / MANDATORY
3545
    if ($forceRefresh === true) {
3546
        return [
3547
            'state' => false,
3548
            'data' => [],
3549
        ];
3550
    }
3551
3552
    // Get last folder update
3553
    $lastFolderChange = DB::queryfirstrow(
3554
        'SELECT valeur FROM ' . prefixTable('misc') . '
3555
        WHERE type = %s AND intitule = %s',
3556
        'timestamp',
3557
        'last_folder_change'
3558
    );
3559
    if (DB::count() === 0) {
3560
        $lastFolderChange['valeur'] = 0;
3561
    }
3562
3563
    // Get last tree refresh
3564
    //$userTreeLastRefresh = isset($_SESSION['user_tree_last_refresh_timestamp']) === true ? $_SESSION['user_tree_last_refresh_timestamp'] : 0
3565
3566
    // Case when an update in the tree has been done
3567
    // Refresh is then mandatory
3568
    if ((int) $lastFolderChange['valeur'] > (int) (isset($_SESSION['user_tree_last_refresh_timestamp']) === true ? $_SESSION['user_tree_last_refresh_timestamp'] : 0)) {
3569
        return [
3570
            'state' => false,
3571
            'data' => [],
3572
        ];
3573
    }
3574
3575
    // Does this user has the tree structure in session?
3576
    // If yes then use it
3577
    if (count(isset($_SESSION['teampassUser']['folders']) === true ? $_SESSION['teampassUser']['folders'] : []) > 0) {
3578
        return [
3579
            'state' => true,
3580
            'data' => json_encode($_SESSION['teampassUser']['folders']),
3581
        ];
3582
    }
3583
3584
    // Does this user has a tree cache
3585
    $userCacheTree = DB::queryfirstrow(
3586
        'SELECT visible_folders
3587
        FROM ' . prefixTable('cache_tree') . '
3588
        WHERE user_id = %i',
3589
        $_SESSION['user_id']
3590
    );
3591
    if (empty($userCacheTree['visible_folders']) === false && $userCacheTree['visible_folders'] !== '[]') {
3592
        return [
3593
            'state' => true,
3594
            'data' => $userCacheTree['visible_folders'],
3595
        ];
3596
    }
3597
3598
    return [
3599
        'state' => false,
3600
        'data' => [],
3601
    ];
3602
}