Passed
Push — master ( b60e83...be2df6 )
by Nils
04:13
created

getFullUserInfos()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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

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

3851
            $userRoles = explode(';', /** @scrutinizer ignore-type */ is_null($user['fonction_id']) === false && empty($user['fonction_id']) === false ? $user['fonction_id'] : []);
Loading history...
3852
            if (in_array($role, $userRoles, true) === true) {
3853
                array_push($arrUsers, $user['id']);
3854
            }
3855
        }
3856
    }
3857
3858
    return $arrUsers;
3859
}
3860
3861
// #3476 - check if function str_contains exists (using PHP 8.0.0 or h)
3862
// else define it
3863
if (!function_exists('str_contains')) {
3864
    function str_contains($haystack, $needle) {
3865
        return $needle !== '' && mb_strpos($haystack, $needle) !== false;
3866
    }
3867
}
3868
3869
3870
function getFullUserInfos(
3871
    int $userId
3872
): array
3873
{
3874
    if (empty($userId) === true) {
3875
        return array();
3876
    }
3877
3878
    $val = DB::queryfirstrow(
0 ignored issues
show
Unused Code introduced by
The assignment to $val is dead and can be removed.
Loading history...
3879
        'SELECT *
3880
        FROM ' . prefixTable('users') . '
3881
        WHERE id = %i',
3882
        $userId
3883
    );
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return array. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
3884
}
3885
3886
/**
3887
 * Is required an upgrade
3888
 *
3889
 * @return boolean
3890
 */
3891
function upgradeRequired(): bool
3892
{
3893
    // Get settings.php
3894
    include_once __DIR__. '/../includes/config/settings.php';
3895
3896
    // Get timestamp in DB
3897
    $val = DB::queryfirstrow(
3898
        'SELECT valeur
3899
        FROM ' . prefixTable('misc') . '
3900
        WHERE type = %s AND intitule = %s',
3901
        'admin',
3902
        'upgrade_timestamp'
3903
    );
3904
    
3905
    // if not exists then error
3906
    if (is_null($val) === true || count($val) === 0 || defined('UPGRADE_MIN_DATE') === false) return true;
3907
3908
    // if empty or too old then error
3909
    if (empty($val['valeur']) === true || (int) $val['valeur'] < (int) UPGRADE_MIN_DATE) {
3910
        return true;
3911
    }
3912
3913
    return false;
3914
}
3915
3916
/**
3917
 * Permits to change the user keys on his demand
3918
 *
3919
 * @param integer $userId
3920
 * @param string $passwordClear
3921
 * @param string $encryptionKey
3922
 * @param boolean $deleteExistingKeys
3923
 * @param boolean $sendEmailToUser
3924
 * @param boolean $encryptWithUserPassword
3925
 * @param integer $nbItemsToTreat
3926
 * @return string
3927
 */
3928
function handleUserKeys(
3929
    int $userId,
3930
    string $passwordClear,
3931
    string $encryptionKey = '',
3932
    bool $deleteExistingKeys = false,
3933
    bool $sendEmailToUser = true,
3934
    bool $encryptWithUserPassword = false,
3935
    int $nbItemsToTreat
3936
): string
3937
{
3938
3939
    // prepapre background tasks for item keys generation        
3940
    $val = DB::queryFirstRow(
3941
        'SELECT pw, public_key, private_key
3942
        FROM ' . prefixTable('users') . '
3943
        WHERE id = %i',
3944
        TP_USER_ID
3945
    );
3946
    if (DB::count() > 0) {
3947
        // Manage empty encryption key
3948
        // Let's take the user's password if asked and if no encryption key provided
3949
        $encryptionKey = $encryptWithUserPassword === true && empty($encryptionKey) === true ? $passwordClear : $encryptionKey;
3950
3951
        // Create process
3952
        DB::insert(
3953
            prefixTable('processes'),
3954
            array(
3955
                'created_at' => time(),
3956
                'process_type' => 'create_user_keys',
3957
                'arguments' => json_encode([
3958
                    'new_user_id' => (int) $userId,
3959
                    'new_user_pwd' => cryption($passwordClear, '','encrypt')['string'],
3960
                    'new_user_code' => cryption(empty($encryptionKey) === true ? uniqidReal(20) : $encryptionKey, '','encrypt')['string'],
3961
                    'owner_id' => (int) TP_USER_ID,
3962
                    'creator_pwd' => $val['pw'],
3963
                    'send_email' => $sendEmailToUser === true ? 1 : 0,
3964
                    'otp_provided_new_value' => 1,
3965
                ]),
3966
                'updated_at' => '',
3967
                'finished_at' => '',
3968
                'output' => '',
3969
            )
3970
        );
3971
        $processId = DB::insertId();
3972
3973
        // Delete existing keys
3974
        if ($deleteExistingKeys === true) {
3975
            deleteUserObjetsKeys(
3976
                (int) $userId,
3977
            );
3978
        }
3979
3980
        // Create tasks
3981
        DB::insert(
3982
            prefixTable('processes_tasks'),
3983
            array(
3984
                'process_id' => $processId,
3985
                'created_at' => time(),
3986
                'task' => json_encode([
3987
                    'step' => 'step0',
3988
                    'index' => 0,
3989
                    'nb' => $nbItemsToTreat,
3990
                ]),
3991
            )
3992
        );
3993
3994
        DB::insert(
3995
            prefixTable('processes_tasks'),
3996
            array(
3997
                'process_id' => $processId,
3998
                'created_at' => time(),
3999
                'task' => json_encode([
4000
                    'step' => 'step1',
4001
                    'index' => 0,
4002
                    'nb' => $nbItemsToTreat,
4003
                ]),
4004
            )
4005
        );
4006
4007
        DB::insert(
4008
            prefixTable('processes_tasks'),
4009
            array(
4010
                'process_id' => $processId,
4011
                'created_at' => time(),
4012
                'task' => json_encode([
4013
                    'step' => 'step2',
4014
                    'index' => 0,
4015
                    'nb' => $nbItemsToTreat,
4016
                ]),
4017
            )
4018
        );
4019
4020
        DB::insert(
4021
            prefixTable('processes_tasks'),
4022
            array(
4023
                'process_id' => $processId,
4024
                'created_at' => time(),
4025
                'task' => json_encode([
4026
                    'step' => 'step3',
4027
                    'index' => 0,
4028
                    'nb' => $nbItemsToTreat,
4029
                ]),
4030
            )
4031
        );
4032
4033
        DB::insert(
4034
            prefixTable('processes_tasks'),
4035
            array(
4036
                'process_id' => $processId,
4037
                'created_at' => time(),
4038
                'task' => json_encode([
4039
                    'step' => 'step4',
4040
                    'index' => 0,
4041
                    'nb' => $nbItemsToTreat,
4042
                ]),
4043
            )
4044
        );
4045
4046
        DB::insert(
4047
            prefixTable('processes_tasks'),
4048
            array(
4049
                'process_id' => $processId,
4050
                'created_at' => time(),
4051
                'task' => json_encode([
4052
                    'step' => 'step5',
4053
                    'index' => 0,
4054
                    'nb' => $nbItemsToTreat,
4055
                ]),
4056
            )
4057
        );
4058
4059
        DB::insert(
4060
            prefixTable('processes_tasks'),
4061
            array(
4062
                'process_id' => $processId,
4063
                'created_at' => time(),
4064
                'task' => json_encode([
4065
                    'step' => 'step6',
4066
                    'index' => 0,
4067
                    'nb' => $nbItemsToTreat,
4068
                ]),
4069
            )
4070
        );
4071
4072
        // update user's new status
4073
        DB::update(
4074
            prefixTable('users'),
4075
            [
4076
                'is_ready_for_usage' => 0,
4077
                'otp_provided' => 1,
4078
                'ongoing_process_id' => $processId,
4079
                'special' => 'generate-keys',
4080
            ],
4081
            'id=%i',
4082
            $userId
4083
        );
4084
    }
4085
4086
    return prepareExchangedData(
4087
        __DIR__.'/..',
4088
        array(
4089
            'error' => false,
4090
            'message' => '',
4091
        ),
4092
        'encode'
4093
    );
4094
}