Passed
Push — master ( 5a94af...9321c1 )
by Nils
04:12
created

langHdl()   A

Complexity

Conditions 6
Paths 8

Size

Total Lines 32
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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