Passed
Push — master ( ff9b52...cd135c )
by Nils
05:34
created

validateDataFields()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 27
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 14
c 1
b 0
f 0
nc 4
nop 2
dl 0
loc 27
rs 9.4888
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
 * @file      main.functions.php
15
 * ---
16
 *
17
 * @author    Nils Laumaillé ([email protected])
18
 *
19
 * @copyright 2009-2023 Teampass.net
20
 *
21
 * @license   https://spdx.org/licenses/GPL-3.0-only.html#licenseText GPL-3.0
22
 * ---
23
 *
24
 * @see       https://www.teampass.net
25
 */
26
27
use LdapRecord\Connection;
28
use ForceUTF8\Encoding;
29
30
if (isset($_SESSION['CPM']) === false || (int) $_SESSION['CPM'] !== 1) {
31
    //die('Hacking attempt...');
32
}
33
34
// Load config if $SETTINGS not defined
35
if (isset($SETTINGS['cpassman_dir']) === false || empty($SETTINGS['cpassman_dir']) === true) {
36
    include_once __DIR__ . '/../includes/config/tp.config.php';
37
}
38
39
header('Content-type: text/html; charset=utf-8');
40
header('Cache-Control: no-cache, must-revalidate');
41
/**
42
 * Convert language code to string.
43
 *
44
 * @param string $string String to get
45
 */
46
function langHdl(string $string): string
47
{
48
    if (empty($string) === true) {
49
        // Manage error
50
        return 'ERROR in language strings!';
51
    }
52
53
    // Load superglobal
54
    include_once __DIR__.'/../includes/libraries/protect/SuperGlobal/SuperGlobal.php';
55
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
56
    // Get language string
57
    $session_language = $superGlobal->get(trim($string), 'SESSION', 'lang');
58
    if (is_null($session_language) === true) {
59
        /* 
60
            Load the English version to $_SESSION so we don't 
61
            return bad JSON (multiple includes add BOM characters to the json returned 
62
            which makes jquery unhappy on the UI, especially on the log page)
63
            and improve performance by avoiding to include the file for every missing strings.
64
        */
65
        if (isset($_SESSION['teampass']) === false || isset($_SESSION['teampass']['en_lang'][trim($string)]) === false) {
66
            $_SESSION['teampass']['en_lang'] = include_once __DIR__. '/../includes/language/english.php';
67
            $session_language = isset($_SESSION['teampass']['en_lang'][trim($string)]) === false ? '' : $_SESSION['teampass']['en_lang'][trim($string)];
68
        } else {
69
            $session_language = $_SESSION['teampass']['en_lang'][trim($string)];
70
        }
71
    }
72
    // If after all this, we still don't have the string even in english (especially with old logs), return the language code
73
    if (empty($session_language) === true) {
74
        return trim($string);
75
    }
76
    //return (string) str_replace("'",  "&apos;", $session_language);
77
    return (string) $session_language;
78
}
79
80
/**
81
 * genHash().
82
 *
83
 * Generate a hash for user login
84
 *
85
 * @param string $password What password
86
 * @param string $cost     What cost
87
 *
88
 * @return string|void
89
 */
90
function bCrypt(
91
    string $password,
92
    string $cost
93
): ?string
94
{
95
    $salt = sprintf('$2y$%02d$', $cost);
96
    if (function_exists('openssl_random_pseudo_bytes')) {
97
        $salt .= bin2hex(openssl_random_pseudo_bytes(11));
98
    } else {
99
        $chars = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
100
        for ($i = 0; $i < 22; ++$i) {
101
            $salt .= $chars[mt_rand(0, 63)];
102
        }
103
    }
104
105
    return crypt($password, $salt);
106
}
107
108
/**
109
 * Defuse cryption function.
110
 *
111
 * @param string $message   what to de/crypt
112
 * @param string $ascii_key key to use
113
 * @param string $type      operation to perform
114
 * @param array  $SETTINGS  Teampass settings
115
 *
116
 * @return array
117
 */
118
function cryption(string $message, string $ascii_key, string $type, ?array $SETTINGS = []): array
119
{
120
    $ascii_key = empty($ascii_key) === true ? file_get_contents(SECUREPATH.'/'.SECUREFILE) : $ascii_key;
121
    $err = false;
122
    
123
    $path = __DIR__.'/../includes/libraries/Encryption/Encryption/';
124
125
    include_once $path . 'Exception/CryptoException.php';
126
    include_once $path . 'Exception/BadFormatException.php';
127
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
128
    include_once $path . 'Exception/IOException.php';
129
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
130
    include_once $path . 'Crypto.php';
131
    include_once $path . 'Encoding.php';
132
    include_once $path . 'DerivedKeys.php';
133
    include_once $path . 'Key.php';
134
    include_once $path . 'KeyOrPassword.php';
135
    include_once $path . 'File.php';
136
    include_once $path . 'RuntimeTests.php';
137
    include_once $path . 'KeyProtectedByPassword.php';
138
    include_once $path . 'Core.php';
139
    
140
    // convert KEY
141
    $key = \Defuse\Crypto\Key::loadFromAsciiSafeString($ascii_key);
142
    try {
143
        if ($type === 'encrypt') {
144
            $text = \Defuse\Crypto\Crypto::encrypt($message, $key);
145
        } elseif ($type === 'decrypt') {
146
            $text = \Defuse\Crypto\Crypto::decrypt($message, $key);
147
        }
148
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
149
        $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.';
150
    } catch (Defuse\Crypto\Exception\BadFormatException $ex) {
151
        $err = $ex;
152
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
153
        $err = $ex;
154
    } catch (Defuse\Crypto\Exception\CryptoException $ex) {
155
        $err = $ex;
156
    } catch (Defuse\Crypto\Exception\IOException $ex) {
157
        $err = $ex;
158
    }
159
    //echo \Defuse\Crypto\Crypto::decrypt($message, $key).' ## ';
160
161
    return [
162
        'string' => $text ?? '',
163
        'error' => $err,
164
    ];
165
}
166
167
/**
168
 * Generating a defuse key.
169
 *
170
 * @return string
171
 */
172
function defuse_generate_key()
173
{
174
    // load PhpEncryption library
175
    if (file_exists('../includes/config/tp.config.php') === true) {
176
        $path = '../includes/libraries/Encryption/Encryption/';
177
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
178
        $path = './includes/libraries/Encryption/Encryption/';
179
    } else {
180
        $path = '../includes/libraries/Encryption/Encryption/';
181
    }
182
183
    include_once $path . 'Exception/CryptoException.php';
184
    include_once $path . 'Exception/BadFormatException.php';
185
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
186
    include_once $path . 'Exception/IOException.php';
187
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
188
    include_once $path . 'Crypto.php';
189
    include_once $path . 'Encoding.php';
190
    include_once $path . 'DerivedKeys.php';
191
    include_once $path . 'Key.php';
192
    include_once $path . 'KeyOrPassword.php';
193
    include_once $path . 'File.php';
194
    include_once $path . 'RuntimeTests.php';
195
    include_once $path . 'KeyProtectedByPassword.php';
196
    include_once $path . 'Core.php';
197
198
    $key = \Defuse\Crypto\Key::createNewRandomKey();
199
    $key = $key->saveToAsciiSafeString();
200
    return $key;
201
}
202
203
/**
204
 * Generate a Defuse personal key.
205
 *
206
 * @param string $psk psk used
207
 *
208
 * @return string
209
 */
210
function defuse_generate_personal_key(string $psk): string
211
{
212
    // load PhpEncryption library
213
    if (file_exists('../includes/config/tp.config.php') === true) {
214
        $path = '../includes/libraries/Encryption/Encryption/';
215
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
216
        $path = './includes/libraries/Encryption/Encryption/';
217
    } else {
218
        $path = '../includes/libraries/Encryption/Encryption/';
219
    }
220
221
    include_once $path . 'Exception/CryptoException.php';
222
    include_once $path . 'Exception/BadFormatException.php';
223
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
224
    include_once $path . 'Exception/IOException.php';
225
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
226
    include_once $path . 'Crypto.php';
227
    include_once $path . 'Encoding.php';
228
    include_once $path . 'DerivedKeys.php';
229
    include_once $path . 'Key.php';
230
    include_once $path . 'KeyOrPassword.php';
231
    include_once $path . 'File.php';
232
    include_once $path . 'RuntimeTests.php';
233
    include_once $path . 'KeyProtectedByPassword.php';
234
    include_once $path . 'Core.php';
235
    
236
    $protected_key = \Defuse\Crypto\KeyProtectedByPassword::createRandomPasswordProtectedKey($psk);
237
    return $protected_key->saveToAsciiSafeString(); // save this in user table
238
}
239
240
/**
241
 * Validate persoanl key with defuse.
242
 *
243
 * @param string $psk                   the user's psk
244
 * @param string $protected_key_encoded special key
245
 *
246
 * @return string
247
 */
248
function defuse_validate_personal_key(string $psk, string $protected_key_encoded): string
249
{
250
    // load PhpEncryption library
251
    if (file_exists('../includes/config/tp.config.php') === true) {
252
        $path = '../includes/libraries/Encryption/Encryption/';
253
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
254
        $path = './includes/libraries/Encryption/Encryption/';
255
    } else {
256
        $path = '../includes/libraries/Encryption/Encryption/';
257
    }
258
259
    include_once $path . 'Exception/CryptoException.php';
260
    include_once $path . 'Exception/BadFormatException.php';
261
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
262
    include_once $path . 'Exception/IOException.php';
263
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
264
    include_once $path . 'Crypto.php';
265
    include_once $path . 'Encoding.php';
266
    include_once $path . 'DerivedKeys.php';
267
    include_once $path . 'Key.php';
268
    include_once $path . 'KeyOrPassword.php';
269
    include_once $path . 'File.php';
270
    include_once $path . 'RuntimeTests.php';
271
    include_once $path . 'KeyProtectedByPassword.php';
272
    include_once $path . 'Core.php';
273
274
    try {
275
        $protected_key_encoded = \Defuse\Crypto\KeyProtectedByPassword::loadFromAsciiSafeString($protected_key_encoded);
276
        $user_key = $protected_key_encoded->unlockKey($psk);
277
        $user_key_encoded = $user_key->saveToAsciiSafeString();
278
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
279
        return 'Error - Major issue as the encryption is broken.';
280
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
281
        return 'Error - The saltkey is not the correct one.';
282
    }
283
284
    return $user_key_encoded;
285
    // store it in session once user has entered his psk
286
}
287
288
/**
289
 * Decrypt a defuse string if encrypted.
290
 *
291
 * @param string $value Encrypted string
292
 *
293
 * @return string Decrypted string
294
 */
295
function defuseReturnDecrypted(string $value, $SETTINGS): string
296
{
297
    if (substr($value, 0, 3) === 'def') {
298
        $value = cryption($value, '', 'decrypt', $SETTINGS)['string'];
299
    }
300
301
    return $value;
302
}
303
304
/**
305
 * Trims a string depending on a specific string.
306
 *
307
 * @param string|array $chaine  what to trim
308
 * @param string       $element trim on what
309
 *
310
 * @return string
311
 */
312
function trimElement($chaine, string $element): string
313
{
314
    if (! empty($chaine)) {
315
        if (is_array($chaine) === true) {
316
            $chaine = implode(';', $chaine);
317
        }
318
        $chaine = trim($chaine);
319
        if (substr($chaine, 0, 1) === $element) {
320
            $chaine = substr($chaine, 1);
321
        }
322
        if (substr($chaine, strlen($chaine) - 1, 1) === $element) {
323
            $chaine = substr($chaine, 0, strlen($chaine) - 1);
324
        }
325
    }
326
327
    return $chaine;
328
}
329
330
/**
331
 * Permits to suppress all "special" characters from string.
332
 *
333
 * @param string $string  what to clean
334
 * @param bool   $special use of special chars?
335
 *
336
 * @return string
337
 */
338
function cleanString(string $string, bool $special = false): string
339
{
340
    // Create temporary table for special characters escape
341
    $tabSpecialChar = [];
342
    for ($i = 0; $i <= 31; ++$i) {
343
        $tabSpecialChar[] = chr($i);
344
    }
345
    array_push($tabSpecialChar, '<br />');
346
    if ((int) $special === 1) {
347
        $tabSpecialChar = array_merge($tabSpecialChar, ['</li>', '<ul>', '<ol>']);
348
    }
349
350
    return str_replace($tabSpecialChar, "\n", $string);
351
}
352
353
/**
354
 * Erro manager for DB.
355
 *
356
 * @param array $params output from query
357
 *
358
 * @return void
359
 */
360
function db_error_handler(array $params): void
361
{
362
    echo 'Error: ' . $params['error'] . "<br>\n";
363
    echo 'Query: ' . $params['query'] . "<br>\n";
364
    throw new Exception('Error - Query', 1);
365
}
366
367
/**
368
 * Identify user's rights
369
 *
370
 * @param string|array $groupesVisiblesUser  [description]
371
 * @param string|array $groupesInterditsUser [description]
372
 * @param string       $isAdmin              [description]
373
 * @param string       $idFonctions          [description]
374
 *
375
 * @return bool
376
 */
377
function identifyUserRights(
378
    $groupesVisiblesUser,
379
    $groupesInterditsUser,
380
    $isAdmin,
381
    $idFonctions,
382
    $SETTINGS
383
) {
384
    //load ClassLoader
385
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
386
    // Load superglobal
387
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
388
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
389
    //Connect to DB
390
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
391
    if (defined('DB_PASSWD_CLEAR') === false) {
392
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
393
    }
394
    DB::$host = DB_HOST;
395
    DB::$user = DB_USER;
396
    DB::$password = DB_PASSWD_CLEAR;
397
    DB::$dbName = DB_NAME;
398
    DB::$port = DB_PORT;
399
    DB::$encoding = DB_ENCODING;
400
    DB::$ssl = DB_SSL;
401
    DB::$connect_options = DB_CONNECT_OPTIONS;
402
    //Build tree
403
    $tree = new SplClassLoader('Tree\NestedTree', $SETTINGS['cpassman_dir'] . '/includes/libraries');
404
    $tree->register();
405
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
406
407
    // Check if user is ADMINISTRATOR    
408
    (int) $isAdmin === 1 ?
409
        identAdmin(
410
            $idFonctions,
411
            $SETTINGS, /** @scrutinizer ignore-type */
412
            $tree
413
        )
414
        :
415
        identUser(
416
            $groupesVisiblesUser,
417
            $groupesInterditsUser,
418
            $idFonctions,
419
            $SETTINGS, /** @scrutinizer ignore-type */
420
            $tree
421
        );
422
423
    // update user's timestamp
424
    DB::update(
425
        prefixTable('users'),
426
        [
427
            'timestamp' => time(),
428
        ],
429
        'id=%i',
430
        $superGlobal->get('user_id', 'SESSION')
431
    );
432
433
    return true;
434
}
435
436
/**
437
 * Identify administrator.
438
 *
439
 * @param string $idFonctions Roles of user
440
 * @param array  $SETTINGS    Teampass settings
441
 * @param array  $tree        Tree of folders
442
 *
443
 * @return bool
444
 */
445
function identAdmin($idFonctions, $SETTINGS, $tree)
446
{
447
    // Load superglobal
448
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
449
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
450
    // Init
451
    $groupesVisibles = [];
452
    $superGlobal->put('personal_folders', [], 'SESSION');
453
    $superGlobal->put('groupes_visibles', [], 'SESSION');
454
    $superGlobal->put('no_access_folders', [], 'SESSION');
455
    $superGlobal->put('personal_visible_groups', [], 'SESSION');
456
    $superGlobal->put('read_only_folders', [], 'SESSION');
457
    $superGlobal->put('list_restricted_folders_for_items', [], 'SESSION');
458
    $superGlobal->put('list_folders_editable_by_role', [], 'SESSION');
459
    $superGlobal->put('list_folders_limited', [], 'SESSION');
460
    $superGlobal->put('no_access_folders', [], 'SESSION');
461
    $superGlobal->put('forbiden_pfs', [], 'SESSION');
462
    // Get superglobals
463
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
464
    $globalsVisibleFolders = $superGlobal->get('groupes_visibles', 'SESSION');
465
    $globalsPersonalVisibleFolders = $superGlobal->get('personal_visible_groups', 'SESSION');
466
    // Get list of Folders
467
    $rows = DB::query('SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i', 0);
468
    foreach ($rows as $record) {
469
        array_push($groupesVisibles, $record['id']);
470
    }
471
    $superGlobal->put('groupes_visibles', $groupesVisibles, 'SESSION');
472
    $superGlobal->put('all_non_personal_folders', $groupesVisibles, 'SESSION');
473
    // Exclude all PF
474
    $where = new WhereClause('and');
475
    // create a WHERE statement of pieces joined by ANDs
476
    $where->add('personal_folder=%i', 1);
477
    if (
478
        isset($SETTINGS['enable_pf_feature']) === true
479
        && (int) $SETTINGS['enable_pf_feature'] === 1
480
    ) {
481
        $where->add('title=%s', $globalsUserId);
482
        $where->negateLast();
483
    }
484
    // Get ID of personal folder
485
    $persfld = DB::queryfirstrow(
486
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE title = %s',
487
        $globalsUserId
488
    );
489
    if (empty($persfld['id']) === false) {
490
        if (in_array($persfld['id'], $globalsVisibleFolders) === false) {
491
            array_push($globalsVisibleFolders, $persfld['id']);
492
            array_push($globalsPersonalVisibleFolders, $persfld['id']);
493
            // get all descendants
494
            $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
495
            $tree->rebuild();
496
            $tst = $tree->getDescendants($persfld['id']);
497
            foreach ($tst as $t) {
498
                array_push($globalsVisibleFolders, $t->id);
499
                array_push($globalsPersonalVisibleFolders, $t->id);
500
            }
501
        }
502
    }
503
504
    // get complete list of ROLES
505
    $tmp = explode(';', $idFonctions);
506
    $rows = DB::query(
507
        'SELECT * FROM ' . prefixTable('roles_title') . '
508
        ORDER BY title ASC'
509
    );
510
    foreach ($rows as $record) {
511
        if (! empty($record['id']) && ! in_array($record['id'], $tmp)) {
512
            array_push($tmp, $record['id']);
513
        }
514
    }
515
    $superGlobal->put('fonction_id', implode(';', $tmp), 'SESSION');
516
    $superGlobal->put('is_admin', 1, 'SESSION');
517
    // Check if admin has created Folders and Roles
518
    DB::query('SELECT * FROM ' . prefixTable('nested_tree') . '');
519
    $superGlobal->put('nb_folders', DB::count(), 'SESSION');
520
    DB::query('SELECT * FROM ' . prefixTable('roles_title'));
521
    $superGlobal->put('nb_roles', DB::count(), 'SESSION');
522
523
    return true;
524
}
525
526
/**
527
 * Permits to convert an element to array.
528
 *
529
 * @param string|array $element Any value to be returned as array
530
 *
531
 * @return array
532
 */
533
function convertToArray($element): array
534
{
535
    if (is_string($element) === true) {
536
        if (empty($element) === true) {
537
            return [];
538
        }
539
        return explode(
540
            ';',
541
            trimElement($element, ';')
542
        );
543
    }
544
    return $element;
545
}
546
547
/**
548
 * Defines the rights the user has.
549
 *
550
 * @param string|array $allowedFolders  Allowed folders
551
 * @param string|array $noAccessFolders Not allowed folders
552
 * @param string|array $userRoles       Roles of user
553
 * @param array        $SETTINGS        Teampass settings
554
 * @param object       $tree            Tree of folders
555
 * 
556
 * @return bool
557
 */
558
function identUser(
559
    $allowedFolders,
560
    $noAccessFolders,
561
    $userRoles,
562
    array $SETTINGS,
563
    object $tree
564
) {
565
    // Load superglobal
566
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
567
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
568
    // Init
569
    $superGlobal->put('groupes_visibles', [], 'SESSION');
570
    $superGlobal->put('personal_folders', [], 'SESSION');
571
    $superGlobal->put('no_access_folders', [], 'SESSION');
572
    $superGlobal->put('personal_visible_groups', [], 'SESSION');
573
    $superGlobal->put('read_only_folders', [], 'SESSION');
574
    $superGlobal->put('fonction_id', $userRoles, 'SESSION');
575
    $superGlobal->put('is_admin', 0, 'SESSION');
576
    // init
577
    $personalFolders = [];
578
    $readOnlyFolders = [];
579
    $noAccessPersonalFolders = [];
580
    $restrictedFoldersForItems = [];
581
    $foldersLimited = [];
582
    $foldersLimitedFull = [];
583
    $allowedFoldersByRoles = [];
584
    // Get superglobals
585
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
586
    $globalsPersonalFolders = $superGlobal->get('personal_folder', 'SESSION');
587
    // Ensure consistency in array format
588
    $noAccessFolders = convertToArray($noAccessFolders);
589
    $userRoles = convertToArray($userRoles);
590
    $allowedFolders = convertToArray($allowedFolders);
591
    
592
    // Get list of folders depending on Roles
593
    $arrays = identUserGetFoldersFromRoles(
594
        $userRoles,
595
        $allowedFoldersByRoles,
596
        $readOnlyFolders,
597
        $allowedFolders
598
    );
599
    $allowedFoldersByRoles = $arrays['allowedFoldersByRoles'];
600
    $readOnlyFolders = $arrays['readOnlyFolders'];
601
602
    // Does this user is allowed to see other items
603
    $inc = 0;
604
    $rows = DB::query(
605
        'SELECT id, id_tree FROM ' . prefixTable('items') . '
606
            WHERE restricted_to LIKE %ss AND inactif = %s'.
607
            (count($allowedFolders) > 0 ? ' AND id_tree NOT IN ('.implode(',', $allowedFolders).')' : ''),
608
        $globalsUserId . ';',
609
        '0'
610
    );
611
    foreach ($rows as $record) {
612
        // Exclude restriction on item if folder is fully accessible
613
        //if (in_array($record['id_tree'], $allowedFolders) === false) {
614
            $restrictedFoldersForItems[$record['id_tree']][$inc] = $record['id'];
615
            ++$inc;
616
        //}
617
    }
618
619
    // Check for the users roles if some specific rights exist on items
620
    $rows = DB::query(
621
        'SELECT i.id_tree, r.item_id
622
        FROM ' . prefixTable('items') . ' as i
623
        INNER JOIN ' . prefixTable('restriction_to_roles') . ' as r ON (r.item_id=i.id)
624
        WHERE r.role_id IN %li AND i.id_tree <> ""
625
        ORDER BY i.id_tree ASC',
626
        $userRoles
627
    );
628
    $inc = 0;
629
    foreach ($rows as $record) {
630
        //if (isset($record['id_tree'])) {
631
            $foldersLimited[$record['id_tree']][$inc] = $record['item_id'];
632
            array_push($foldersLimitedFull, $record['id_tree']);
633
            ++$inc;
634
        //}
635
    }
636
637
    // Get list of Personal Folders
638
    $arrays = identUserGetPFList(
639
        $globalsPersonalFolders,
640
        $allowedFolders,
641
        $globalsUserId,
642
        $personalFolders,
643
        $noAccessPersonalFolders,
644
        $foldersLimitedFull,
645
        $allowedFoldersByRoles,
646
        array_keys($restrictedFoldersForItems),
647
        $readOnlyFolders,
648
        $noAccessFolders,
649
        isset($SETTINGS['enable_pf_feature']) === true ? $SETTINGS['enable_pf_feature'] : 0,
650
        $tree
651
    );
652
    $allowedFolders = $arrays['allowedFolders'];
653
    $personalFolders = $arrays['personalFolders'];
654
    $noAccessPersonalFolders = $arrays['noAccessPersonalFolders'];
655
656
    // Return data
657
    $superGlobal->put('all_non_personal_folders', $allowedFolders, 'SESSION');
658
    $superGlobal->put('groupes_visibles', array_unique(array_merge($allowedFolders, $personalFolders), SORT_NUMERIC), 'SESSION');
659
    $superGlobal->put('read_only_folders', $readOnlyFolders, 'SESSION');
660
    $superGlobal->put('no_access_folders', $noAccessFolders, 'SESSION');
661
    $superGlobal->put('personal_folders', $personalFolders, 'SESSION');
662
    $superGlobal->put('list_folders_limited', $foldersLimited, 'SESSION');
663
    $superGlobal->put('list_folders_editable_by_role', $allowedFoldersByRoles, 'SESSION');
664
    $superGlobal->put('list_restricted_folders_for_items', $restrictedFoldersForItems, 'SESSION');
665
    $superGlobal->put('forbiden_pfs', $noAccessPersonalFolders, 'SESSION');
666
    $superGlobal->put(
667
        'all_folders_including_no_access',
668
        array_unique(array_merge(
669
            $allowedFolders,
670
            $personalFolders,
671
            $noAccessFolders,
672
            $readOnlyFolders
673
        ), SORT_NUMERIC),
674
        'SESSION'
675
    );
676
    // Folders and Roles numbers
677
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('nested_tree') . '');
678
    $superGlobal->put('nb_folders', DB::count(), 'SESSION');
679
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('roles_title'));
680
    $superGlobal->put('nb_roles', DB::count(), 'SESSION');
681
    // check if change proposals on User's items
682
    if (isset($SETTINGS['enable_suggestion']) === true && (int) $SETTINGS['enable_suggestion'] === 1) {
683
        $countNewItems = DB::query(
684
            'SELECT COUNT(*)
685
            FROM ' . prefixTable('items_change') . ' AS c
686
            LEFT JOIN ' . prefixTable('log_items') . ' AS i ON (c.item_id = i.id_item)
687
            WHERE i.action = %s AND i.id_user = %i',
688
            'at_creation',
689
            $globalsUserId
690
        );
691
        $superGlobal->put('nb_item_change_proposals', $countNewItems, 'SESSION');
692
    } else {
693
        $superGlobal->put('nb_item_change_proposals', 0, 'SESSION');
694
    }
695
696
    return true;
697
}
698
699
/**
700
 * Get list of folders depending on Roles
701
 * 
702
 * @param array $userRoles
703
 * @param array $allowedFoldersByRoles
704
 * @param array $readOnlyFolders
705
 * @param array $allowedFolders
706
 * 
707
 * @return array
708
 */
709
function identUserGetFoldersFromRoles($userRoles, $allowedFoldersByRoles, $readOnlyFolders, $allowedFolders) : array
710
{
711
    $rows = DB::query(
712
        'SELECT *
713
        FROM ' . prefixTable('roles_values') . '
714
        WHERE role_id IN %li AND type IN %ls',
715
        $userRoles,
716
        ['W', 'ND', 'NE', 'NDNE', 'R']
717
    );
718
    foreach ($rows as $record) {
719
        if ($record['type'] === 'R') {
720
            array_push($readOnlyFolders, $record['folder_id']);
721
        } elseif (in_array($record['folder_id'], $allowedFolders) === false) {
722
            array_push($allowedFoldersByRoles, $record['folder_id']);
723
        }
724
    }
725
    $allowedFoldersByRoles = array_unique($allowedFoldersByRoles);
726
    $readOnlyFolders = array_unique($readOnlyFolders);
727
    // Clean arrays
728
    foreach ($allowedFoldersByRoles as $value) {
729
        $key = array_search($value, $readOnlyFolders);
730
        if ($key !== false) {
731
            unset($readOnlyFolders[$key]);
732
        }
733
    }
734
735
    return [
736
        'readOnlyFolders' => $readOnlyFolders,
737
        'allowedFoldersByRoles' => $allowedFoldersByRoles
738
    ];
739
}
740
741
/**
742
 * Get list of Personal Folders
743
 * 
744
 * @param int $globalsPersonalFolders
745
 * @param array $allowedFolders
746
 * @param int $globalsUserId
747
 * @param array $personalFolders
748
 * @param array $noAccessPersonalFolders
749
 * @param array $foldersLimitedFull
750
 * @param array $allowedFoldersByRoles
751
 * @param array $restrictedFoldersForItems
752
 * @param array $readOnlyFolders
753
 * @param array $noAccessFolders
754
 * @param int $enablePfFeature
755
 * @param object $tree
756
 * 
757
 * @return array
758
 */
759
function identUserGetPFList(
760
    $globalsPersonalFolders,
761
    $allowedFolders,
762
    $globalsUserId,
763
    $personalFolders,
764
    $noAccessPersonalFolders,
765
    $foldersLimitedFull,
766
    $allowedFoldersByRoles,
767
    $restrictedFoldersForItems,
768
    $readOnlyFolders,
769
    $noAccessFolders,
770
    $enablePfFeature,
771
    $tree
772
)
773
{
774
    if (
775
        (int) $enablePfFeature === 1
776
        && (int) $globalsPersonalFolders === 1
777
    ) {
778
        $persoFld = DB::queryfirstrow(
779
            'SELECT id
780
            FROM ' . prefixTable('nested_tree') . '
781
            WHERE title = %s AND personal_folder = %i'.
782
            (count($allowedFolders) > 0 ? ' AND id NOT IN ('.implode(',', $allowedFolders).')' : ''),
783
            $globalsUserId,
784
            1
785
        );
786
        if (empty($persoFld['id']) === false) {
787
            array_push($personalFolders, $persoFld['id']);
788
            array_push($allowedFolders, $persoFld['id']);
789
            // get all descendants
790
            $ids = $tree->getDescendants($persoFld['id'], false, false, true);
791
            foreach ($ids as $id) {
792
                //array_push($allowedFolders, $id);
793
                array_push($personalFolders, $id);
794
            }
795
        }
796
    }
797
    
798
    // Exclude all other PF
799
    $where = new WhereClause('and');
800
    $where->add('personal_folder=%i', 1);
801
    if (count($personalFolders) > 0) {
802
        $where->add('id NOT IN ('.implode(',', $personalFolders).')');
803
    }
804
    if (
805
        (int) $enablePfFeature === 1
806
        && (int) $globalsPersonalFolders === 1
807
    ) {
808
        $where->add('title=%s', $globalsUserId);
809
        $where->negateLast();
810
    }
811
    $persoFlds = DB::query(
812
        'SELECT id
813
        FROM ' . prefixTable('nested_tree') . '
814
        WHERE %l',
815
        $where
816
    );
817
    foreach ($persoFlds as $persoFldId) {
818
        array_push($noAccessPersonalFolders, $persoFldId['id']);
819
    }
820
821
    // All folders visibles
822
    $allowedFolders = array_unique(array_merge(
823
        $allowedFolders,
824
        $foldersLimitedFull,
825
        $allowedFoldersByRoles,
826
        $restrictedFoldersForItems,
827
        $readOnlyFolders
828
    ), SORT_NUMERIC);
829
    // Exclude from allowed folders all the specific user forbidden folders
830
    if (count($noAccessFolders) > 0) {
831
        $allowedFolders = array_diff($allowedFolders, $noAccessFolders);
832
    }
833
834
    return [
835
        'allowedFolders' => array_diff(array_diff($allowedFolders, $noAccessPersonalFolders), $personalFolders),
836
        'personalFolders' => $personalFolders,
837
        'noAccessPersonalFolders' => $noAccessPersonalFolders
838
    ];
839
}
840
841
842
/**
843
 * Update the CACHE table.
844
 *
845
 * @param string $action   What to do
846
 * @param array  $SETTINGS Teampass settings
847
 * @param int    $ident    Ident format
848
 * 
849
 * @return void
850
 */
851
function updateCacheTable(string $action, array $SETTINGS, ?int $ident = null): void
852
{
853
    if ($action === 'reload') {
854
        // Rebuild full cache table
855
        cacheTableRefresh($SETTINGS);
856
    } elseif ($action === 'update_value' && is_null($ident) === false) {
857
        // UPDATE an item
858
        cacheTableUpdate($SETTINGS, $ident);
859
    } elseif ($action === 'add_value' && is_null($ident) === false) {
860
        // ADD an item
861
        cacheTableAdd($SETTINGS, $ident);
862
    } elseif ($action === 'delete_value' && is_null($ident) === false) {
863
        // DELETE an item
864
        DB::delete(prefixTable('cache'), 'id = %i', $ident);
865
    }
866
}
867
868
/**
869
 * Cache table - refresh.
870
 *
871
 * @param array $SETTINGS Teampass settings
872
 * 
873
 * @return void
874
 */
875
function cacheTableRefresh(array $SETTINGS): void
876
{
877
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
878
    //Connect to DB
879
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
880
    if (defined('DB_PASSWD_CLEAR') === false) {
881
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
882
    }
883
    DB::$host = DB_HOST;
884
    DB::$user = DB_USER;
885
    DB::$password = DB_PASSWD_CLEAR;
886
    DB::$dbName = DB_NAME;
887
    DB::$port = DB_PORT;
888
    DB::$encoding = DB_ENCODING;
889
    DB::$ssl = DB_SSL;
890
    DB::$connect_options = DB_CONNECT_OPTIONS;
891
    //Load Tree
892
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
893
    $tree->register();
894
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
895
    // truncate table
896
    DB::query('TRUNCATE TABLE ' . prefixTable('cache'));
897
    // reload date
898
    $rows = DB::query(
899
        'SELECT *
900
        FROM ' . prefixTable('items') . ' as i
901
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
902
        AND l.action = %s
903
        AND i.inactif = %i',
904
        'at_creation',
905
        0
906
    );
907
    foreach ($rows as $record) {
908
        if (empty($record['id_tree']) === false) {
909
            // Get all TAGS
910
            $tags = '';
911
            $itemTags = DB::query(
912
                'SELECT tag
913
                FROM ' . prefixTable('tags') . '
914
                WHERE item_id = %i AND tag != ""',
915
                $record['id']
916
            );
917
            foreach ($itemTags as $itemTag) {
918
                $tags .= $itemTag['tag'] . ' ';
919
            }
920
921
            // Get renewal period
922
            $resNT = DB::queryfirstrow(
923
                'SELECT renewal_period
924
                FROM ' . prefixTable('nested_tree') . '
925
                WHERE id = %i',
926
                $record['id_tree']
927
            );
928
            // form id_tree to full foldername
929
            $folder = [];
930
            $arbo = $tree->getPath($record['id_tree'], true);
931
            foreach ($arbo as $elem) {
932
                // Check if title is the ID of a user
933
                if (is_numeric($elem->title) === true) {
934
                    // Is this a User id?
935
                    $user = DB::queryfirstrow(
936
                        'SELECT id, login
937
                        FROM ' . prefixTable('users') . '
938
                        WHERE id = %i',
939
                        $elem->title
940
                    );
941
                    if (count($user) > 0) {
942
                        $elem->title = $user['login'];
943
                    }
944
                }
945
                // Build path
946
                array_push($folder, stripslashes($elem->title));
947
            }
948
            // store data
949
            DB::insert(
950
                prefixTable('cache'),
951
                [
952
                    'id' => $record['id'],
953
                    'label' => $record['label'],
954
                    'description' => $record['description'] ?? '',
955
                    'url' => isset($record['url']) && ! empty($record['url']) ? $record['url'] : '0',
956
                    'tags' => $tags,
957
                    'id_tree' => $record['id_tree'],
958
                    'perso' => $record['perso'],
959
                    'restricted_to' => isset($record['restricted_to']) && ! empty($record['restricted_to']) ? $record['restricted_to'] : '0',
960
                    'login' => $record['login'] ?? '',
961
                    'folder' => implode(' > ', $folder),
962
                    'author' => $record['id_user'],
963
                    'renewal_period' => $resNT['renewal_period'] ?? '0',
964
                    'timestamp' => $record['date'],
965
                ]
966
            );
967
        }
968
    }
969
}
970
971
/**
972
 * Cache table - update existing value.
973
 *
974
 * @param array  $SETTINGS Teampass settings
975
 * @param int    $ident    Ident format
976
 * 
977
 * @return void
978
 */
979
function cacheTableUpdate(array $SETTINGS, ?int $ident = null): void
980
{
981
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
982
    // Load superglobal
983
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
984
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
985
    //Connect to DB
986
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
987
    if (defined('DB_PASSWD_CLEAR') === false) {
988
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
989
    }
990
    DB::$host = DB_HOST;
991
    DB::$user = DB_USER;
992
    DB::$password = DB_PASSWD_CLEAR;
993
    DB::$dbName = DB_NAME;
994
    DB::$port = DB_PORT;
995
    DB::$encoding = DB_ENCODING;
996
    DB::$ssl = DB_SSL;
997
    DB::$connect_options = DB_CONNECT_OPTIONS;
998
    //Load Tree
999
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1000
    $tree->register();
1001
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1002
    // get new value from db
1003
    $data = DB::queryfirstrow(
1004
        'SELECT label, description, id_tree, perso, restricted_to, login, url
1005
        FROM ' . prefixTable('items') . '
1006
        WHERE id=%i',
1007
        $ident
1008
    );
1009
    // Get all TAGS
1010
    $tags = '';
1011
    $itemTags = DB::query(
1012
        'SELECT tag
1013
            FROM ' . prefixTable('tags') . '
1014
            WHERE item_id = %i AND tag != ""',
1015
        $ident
1016
    );
1017
    foreach ($itemTags as $itemTag) {
1018
        $tags .= $itemTag['tag'] . ' ';
1019
    }
1020
    // form id_tree to full foldername
1021
    $folder = [];
1022
    $arbo = $tree->getPath($data['id_tree'], true);
1023
    foreach ($arbo as $elem) {
1024
        // Check if title is the ID of a user
1025
        if (is_numeric($elem->title) === true) {
1026
            // Is this a User id?
1027
            $user = DB::queryfirstrow(
1028
                'SELECT id, login
1029
                FROM ' . prefixTable('users') . '
1030
                WHERE id = %i',
1031
                $elem->title
1032
            );
1033
            if (count($user) > 0) {
1034
                $elem->title = $user['login'];
1035
            }
1036
        }
1037
        // Build path
1038
        array_push($folder, stripslashes($elem->title));
1039
    }
1040
    // finaly update
1041
    DB::update(
1042
        prefixTable('cache'),
1043
        [
1044
            'label' => $data['label'],
1045
            'description' => $data['description'],
1046
            'tags' => $tags,
1047
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
1048
            'id_tree' => $data['id_tree'],
1049
            'perso' => $data['perso'],
1050
            'restricted_to' => isset($data['restricted_to']) && ! empty($data['restricted_to']) ? $data['restricted_to'] : '0',
1051
            'login' => $data['login'] ?? '',
1052
            'folder' => implode(' » ', $folder),
1053
            'author' => $superGlobal->get('user_id', 'SESSION'),
1054
        ],
1055
        'id = %i',
1056
        $ident
1057
    );
1058
}
1059
1060
/**
1061
 * Cache table - add new value.
1062
 *
1063
 * @param array  $SETTINGS Teampass settings
1064
 * @param int    $ident    Ident format
1065
 * 
1066
 * @return void
1067
 */
1068
function cacheTableAdd(array $SETTINGS, ?int $ident = null): void
1069
{
1070
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1071
    // Load superglobal
1072
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1073
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1074
    // Get superglobals
1075
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
1076
    //Connect to DB
1077
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1078
    if (defined('DB_PASSWD_CLEAR') === false) {
1079
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1080
    }
1081
    DB::$host = DB_HOST;
1082
    DB::$user = DB_USER;
1083
    DB::$password = DB_PASSWD_CLEAR;
1084
    DB::$dbName = DB_NAME;
1085
    DB::$port = DB_PORT;
1086
    DB::$encoding = DB_ENCODING;
1087
    DB::$ssl = DB_SSL;
1088
    DB::$connect_options = DB_CONNECT_OPTIONS;
1089
    //Load Tree
1090
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1091
    $tree->register();
1092
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1093
    // get new value from db
1094
    $data = DB::queryFirstRow(
1095
        'SELECT i.label, i.description, i.id_tree as id_tree, i.perso, i.restricted_to, i.id, i.login, i.url, l.date
1096
        FROM ' . prefixTable('items') . ' as i
1097
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
1098
        WHERE i.id = %i
1099
        AND l.action = %s',
1100
        $ident,
1101
        'at_creation'
1102
    );
1103
    // Get all TAGS
1104
    $tags = '';
1105
    $itemTags = DB::query(
1106
        'SELECT tag
1107
            FROM ' . prefixTable('tags') . '
1108
            WHERE item_id = %i AND tag != ""',
1109
        $ident
1110
    );
1111
    foreach ($itemTags as $itemTag) {
1112
        $tags .= $itemTag['tag'] . ' ';
1113
    }
1114
    // form id_tree to full foldername
1115
    $folder = [];
1116
    $arbo = $tree->getPath($data['id_tree'], true);
1117
    foreach ($arbo as $elem) {
1118
        // Check if title is the ID of a user
1119
        if (is_numeric($elem->title) === true) {
1120
            // Is this a User id?
1121
            $user = DB::queryfirstrow(
1122
                'SELECT id, login
1123
                FROM ' . prefixTable('users') . '
1124
                WHERE id = %i',
1125
                $elem->title
1126
            );
1127
            if (count($user) > 0) {
1128
                $elem->title = $user['login'];
1129
            }
1130
        }
1131
        // Build path
1132
        array_push($folder, stripslashes($elem->title));
1133
    }
1134
    // finaly update
1135
    DB::insert(
1136
        prefixTable('cache'),
1137
        [
1138
            'id' => $data['id'],
1139
            'label' => $data['label'],
1140
            'description' => $data['description'],
1141
            'tags' => isset($tags) && empty($tags) === false ? $tags : 'None',
1142
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
1143
            'id_tree' => $data['id_tree'],
1144
            'perso' => isset($data['perso']) && empty($data['perso']) === false && $data['perso'] !== 'None' ? $data['perso'] : '0',
1145
            'restricted_to' => isset($data['restricted_to']) && empty($data['restricted_to']) === false ? $data['restricted_to'] : '0',
1146
            'login' => $data['login'] ?? '',
1147
            'folder' => implode(' » ', $folder),
1148
            'author' => $globalsUserId,
1149
            'timestamp' => $data['date'],
1150
        ]
1151
    );
1152
}
1153
1154
/**
1155
 * Do statistics.
1156
 *
1157
 * @param array $SETTINGS Teampass settings
1158
 *
1159
 * @return array
1160
 */
1161
function getStatisticsData(array $SETTINGS): array
1162
{
1163
    DB::query(
1164
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1165
        0
1166
    );
1167
    $counter_folders = DB::count();
1168
    DB::query(
1169
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1170
        1
1171
    );
1172
    $counter_folders_perso = DB::count();
1173
    DB::query(
1174
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1175
        0
1176
    );
1177
    $counter_items = DB::count();
1178
        DB::query(
1179
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1180
        1
1181
    );
1182
    $counter_items_perso = DB::count();
1183
        DB::query(
1184
        'SELECT id FROM ' . prefixTable('users') . ''
1185
    );
1186
    $counter_users = DB::count();
1187
        DB::query(
1188
        'SELECT id FROM ' . prefixTable('users') . ' WHERE admin = %i',
1189
        1
1190
    );
1191
    $admins = DB::count();
1192
    DB::query(
1193
        'SELECT id FROM ' . prefixTable('users') . ' WHERE gestionnaire = %i',
1194
        1
1195
    );
1196
    $managers = DB::count();
1197
    DB::query(
1198
        'SELECT id FROM ' . prefixTable('users') . ' WHERE read_only = %i',
1199
        1
1200
    );
1201
    $readOnly = DB::count();
1202
    // list the languages
1203
    $usedLang = [];
1204
    $tp_languages = DB::query(
1205
        'SELECT name FROM ' . prefixTable('languages')
1206
    );
1207
    foreach ($tp_languages as $tp_language) {
1208
        DB::query(
1209
            'SELECT * FROM ' . prefixTable('users') . ' WHERE user_language = %s',
1210
            $tp_language['name']
1211
        );
1212
        $usedLang[$tp_language['name']] = round((DB::count() * 100 / $counter_users), 0);
1213
    }
1214
1215
    // get list of ips
1216
    $usedIp = [];
1217
    $tp_ips = DB::query(
1218
        'SELECT user_ip FROM ' . prefixTable('users')
1219
    );
1220
    foreach ($tp_ips as $ip) {
1221
        if (array_key_exists($ip['user_ip'], $usedIp)) {
1222
            $usedIp[$ip['user_ip']] += $usedIp[$ip['user_ip']];
1223
        } elseif (! empty($ip['user_ip']) && $ip['user_ip'] !== 'none') {
1224
            $usedIp[$ip['user_ip']] = 1;
1225
        }
1226
    }
1227
1228
    return [
1229
        'error' => '',
1230
        'stat_phpversion' => phpversion(),
1231
        'stat_folders' => $counter_folders,
1232
        'stat_folders_shared' => intval($counter_folders) - intval($counter_folders_perso),
1233
        'stat_items' => $counter_items,
1234
        'stat_items_shared' => intval($counter_items) - intval($counter_items_perso),
1235
        'stat_users' => $counter_users,
1236
        'stat_admins' => $admins,
1237
        'stat_managers' => $managers,
1238
        'stat_ro' => $readOnly,
1239
        'stat_kb' => $SETTINGS['enable_kb'],
1240
        'stat_pf' => $SETTINGS['enable_pf_feature'],
1241
        'stat_fav' => $SETTINGS['enable_favourites'],
1242
        'stat_teampassversion' => TP_VERSION,
1243
        'stat_ldap' => $SETTINGS['ldap_mode'],
1244
        'stat_agses' => $SETTINGS['agses_authentication_enabled'],
1245
        'stat_duo' => $SETTINGS['duo'],
1246
        'stat_suggestion' => $SETTINGS['enable_suggestion'],
1247
        'stat_api' => $SETTINGS['api'],
1248
        'stat_customfields' => $SETTINGS['item_extra_fields'],
1249
        'stat_syslog' => $SETTINGS['syslog_enable'],
1250
        'stat_2fa' => $SETTINGS['google_authentication'],
1251
        'stat_stricthttps' => $SETTINGS['enable_sts'],
1252
        'stat_mysqlversion' => DB::serverVersion(),
1253
        'stat_languages' => $usedLang,
1254
        'stat_country' => $usedIp,
1255
    ];
1256
}
1257
1258
/**
1259
 * Permits to prepare the way to send the email
1260
 * 
1261
 * @param string $subject       email subject
1262
 * @param string $body          email message
1263
 * @param string $email         email
1264
 * @param string $receiverName  Receiver name
1265
 * @param array  $SETTINGS      settings
1266
 *
1267
 * @return void
1268
 */
1269
function prepareSendingEmail(
1270
    $subject,
1271
    $body,
1272
    $email,
1273
    $receiverName,
1274
    $SETTINGS
1275
): void 
1276
{
1277
    DB::insert(
1278
        prefixTable('processes'),
1279
        array(
1280
            'created_at' => time(),
1281
            'process_type' => 'send_email',
1282
            'arguments' => json_encode([
1283
                'subject' => $subject,
1284
                'receivers' => $email,
1285
                'body' => $body,
1286
                'receiver_name' => $receiverName,
1287
            ], JSON_HEX_QUOT | JSON_HEX_TAG),
1288
            'updated_at' => '',
1289
            'finished_at' => '',
1290
            'output' => '',
1291
        )
1292
    );
1293
}
1294
1295
/**
1296
 * Permits to send an email.
1297
 *
1298
 * @param string $subject     email subject
1299
 * @param string $textMail    email message
1300
 * @param string $email       email
1301
 * @param array  $SETTINGS    settings
1302
 * @param string $textMailAlt email message alt
1303
 * @param bool   $silent      no errors
1304
 *
1305
 * @return string some json info
1306
 */
1307
function sendEmail(
1308
    $subject,
1309
    $textMail,
1310
    $email,
1311
    $SETTINGS,
1312
    $textMailAlt = null,
1313
    $silent = true,
1314
    $cron = false
1315
) {
1316
    // CAse where email not defined
1317
    if ($email === 'none' || empty($email) === true) {
1318
        return json_encode(
1319
            [
1320
                'error' => true,
1321
                'message' => langHdl('forgot_my_pw_email_sent'),
1322
            ]
1323
        );
1324
    }
1325
1326
    // Build and send email
1327
    $email = buildEmail(
1328
        $subject,
1329
        $textMail,
1330
        $email,
1331
        $SETTINGS,
1332
        $textMailAlt = null,
1333
        $silent = true,
1334
        $cron
1335
    );
1336
1337
    if ($silent === false) {
0 ignored issues
show
introduced by
The condition $silent === false is always false.
Loading history...
1338
        return json_encode(
1339
            [
1340
                'error' => false,
1341
                'message' => langHdl('forgot_my_pw_email_sent'),
1342
            ]
1343
        );
1344
    }
1345
    // Debug purpose
1346
    if ((int) $SETTINGS['email_debug_level'] !== 0 && $cron === false) {
1347
        return json_encode(
1348
            [
1349
                'error' => true,
1350
                'message' => isset($email['ErrorInfo']) === true ? $email['ErrorInfo'] : '',
1351
            ]
1352
        );
1353
    }
1354
    return json_encode(
1355
        [
1356
            'error' => false,
1357
            'message' => langHdl('share_sent_ok'),
1358
        ]
1359
    );
1360
}
1361
1362
1363
function buildEmail(
1364
    $subject,
1365
    $textMail,
1366
    $email,
1367
    $SETTINGS,
1368
    $textMailAlt = null,
1369
    $silent = true,
1370
    $cron = false
1371
)
1372
{
1373
    // Load settings
1374
    //include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
1375
    // Load superglobal
1376
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1377
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1378
    // Get user language
1379
    include_once $SETTINGS['cpassman_dir'] . '/includes/language/' . (null !== $superGlobal->get('user_language', 'SESSION', 'user') ? $superGlobal->get('user_language', 'SESSION', 'user') : 'english') . '.php';
1380
    // Load library
1381
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1382
    // load PHPMailer
1383
    $mail = new SplClassLoader('PHPMailer\PHPMailer', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1384
    $mail->register();
1385
    $mail = new PHPMailer\PHPMailer\PHPMailer(true);
1386
1387
    // send to user
1388
    $mail->setLanguage('en', $SETTINGS['cpassman_dir'] . '/includes/libraries/PHPMailer/PHPMailer/language/');
1389
    $mail->SMTPDebug = isset($SETTINGS['email_debug_level']) === true && $cron === false && $silent === false ? $SETTINGS['email_debug_level'] : 0;
1390
    $mail->Port = (int) $SETTINGS['email_port'];
1391
    //COULD BE USED
1392
    $mail->CharSet = 'utf-8';
1393
    $mail->SMTPSecure = $SETTINGS['email_security'] !== 'none' ? $SETTINGS['email_security'] : '';
1394
    $mail->SMTPAutoTLS = $SETTINGS['email_security'] !== 'none' ? true : false;
1395
    $mail->SMTPOptions = [
1396
        'ssl' => [
1397
            'verify_peer' => false,
1398
            'verify_peer_name' => false,
1399
            'allow_self_signed' => true,
1400
        ],
1401
    ];
1402
    $mail->isSmtp();
1403
    // send via SMTP
1404
    $mail->Host = $SETTINGS['email_smtp_server'];
1405
    // SMTP servers
1406
    $mail->SMTPAuth = (int) $SETTINGS['email_smtp_auth'] === 1 ? true : false;
1407
    // turn on SMTP authentication
1408
    $mail->Username = $SETTINGS['email_auth_username'];
1409
    // SMTP username
1410
    $mail->Password = $SETTINGS['email_auth_pwd'];
1411
    // SMTP password
1412
    $mail->From = $SETTINGS['email_from'];
1413
    $mail->FromName = $SETTINGS['email_from_name'];
1414
    // Prepare for each person
1415
    foreach (array_filter(explode(',', $email)) as $dest) {
1416
        $mail->addAddress($dest);
1417
    }
1418
    
1419
    // Prepare HTML
1420
    $text_html = emailBody($textMail);
1421
    $mail->WordWrap = 80;
1422
    // set word wrap
1423
    $mail->isHtml(true);
1424
    // send as HTML
1425
    $mail->Subject = $subject;
1426
    $mail->Body = $text_html;
1427
    $mail->AltBody = is_null($textMailAlt) === false ? $textMailAlt : '';
1428
1429
    try {
1430
        // send email
1431
        $mail->send();
1432
    } catch (Exception $e) {
1433
        if ($silent === false || (int) $SETTINGS['email_debug_level'] !== 0) {
1434
            return json_encode(
1435
                [
1436
                    'error' => true,
1437
                    'errorInfo' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1438
                ]
1439
            );
1440
        }
1441
        return '';
1442
    }
1443
    $mail->smtpClose();
1444
1445
    return json_encode(
1446
        [
1447
            'error' => true,
1448
            'errorInfo' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1449
        ]
1450
    );
1451
}
1452
1453
/**
1454
 * Returns the email body.
1455
 *
1456
 * @param string $textMail Text for the email
1457
 */
1458
function emailBody(string $textMail): string
1459
{
1460
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1461
    w3.org/TR/html4/loose.dtd"><html>
1462
    <head><title>Email Template</title>
1463
    <style type="text/css">
1464
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1465
    </style></head>
1466
    <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">
1467
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1468
    <tr><td style="border-collapse: collapse;"><br>
1469
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1470
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1471
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1472
        </td></tr></table></td>
1473
    </tr>
1474
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1475
        <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;">
1476
        <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;">
1477
        <br><div style="float:right;">' .
1478
        $textMail .
1479
        '<br><br></td></tr></table>
1480
    </td></tr></table>
1481
    <br></body></html>';
1482
}
1483
1484
/**
1485
 * Generate a Key.
1486
 * 
1487
 * @return string
1488
 */
1489
function generateKey(): string
1490
{
1491
    return substr(md5(rand() . rand()), 0, 15);
1492
}
1493
1494
/**
1495
 * Convert date to timestamp.
1496
 *
1497
 * @param string $date        The date
1498
 * @param string $date_format Date format
1499
 *
1500
 * @return int
1501
 */
1502
function dateToStamp(string $date, string $date_format): int
1503
{
1504
    $date = date_parse_from_format($date_format, $date);
1505
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1506
        return mktime(23, 59, 59, $date['month'], $date['day'], $date['year']);
1507
    }
1508
    return 0;
1509
}
1510
1511
/**
1512
 * Is this a date.
1513
 *
1514
 * @param string $date Date
1515
 *
1516
 * @return bool
1517
 */
1518
function isDate(string $date): bool
1519
{
1520
    return strtotime($date) !== false;
1521
}
1522
1523
/**
1524
 * Check if isUTF8().
1525
 *
1526
 * @param string|array $string Is the string
1527
 *
1528
 * @return int is the string in UTF8 format
1529
 */
1530
function isUTF8($string): int
1531
{
1532
    if (is_array($string) === true) {
1533
        $string = $string['string'];
1534
    }
1535
1536
    return preg_match(
1537
        '%^(?:
1538
        [\x09\x0A\x0D\x20-\x7E] # ASCII
1539
        | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
1540
        | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
1541
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
1542
        | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
1543
        | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
1544
        | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
1545
        | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
1546
        )*$%xs',
1547
        $string
1548
    );
1549
}
1550
1551
/**
1552
 * Prepare an array to UTF8 format before JSON_encode.
1553
 *
1554
 * @param array $array Array of values
1555
 *
1556
 * @return array
1557
 */
1558
function utf8Converter(array $array): array
1559
{
1560
    array_walk_recursive(
1561
        $array,
1562
        static function (&$item): void {
1563
            if (mb_detect_encoding((string) $item, 'utf-8', true) === false) {
1564
                $item = utf8_encode($item);
1565
            }
1566
        }
1567
    );
1568
    return $array;
1569
}
1570
1571
/**
1572
 * Permits to prepare data to be exchanged.
1573
 *
1574
 * @param string       $teampassDir
1575
 * @param array|string $data Text
1576
 * @param string       $type Parameter
1577
 * @param string       $key  Optional key
1578
 *
1579
 * @return string|array
1580
 */
1581
function prepareExchangedData($teampassDir, $data, string $type, ?string $key = null)
1582
{
1583
    $teampassDir = __DIR__ . '/..';
1584
    // Load superglobal
1585
    include_once $teampassDir . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1586
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1587
    // Get superglobals
1588
    if ($key !== null) {
1589
        $superGlobal->put('key', $key, 'SESSION');
1590
        $globalsKey = $key;
1591
    } else {
1592
        $globalsKey = $superGlobal->get('key', 'SESSION');
1593
    }
1594
1595
    //load Encoding
1596
    include_once $teampassDir . '/includes/libraries/ForceUTF8/Encoding.php';
1597
    
1598
    //Load CRYPTOJS
1599
    include_once $teampassDir . '/includes/libraries/Encryption/CryptoJs/Encryption.php';
1600
1601
    // Perform
1602
    if ($type === 'encode' && is_array($data) === true) {
1603
        // Now encode
1604
        return Encryption\CryptoJs\Encryption::encrypt(
1605
            json_encode(
1606
                $data,
1607
                JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1608
            ),
1609
            $globalsKey
1610
        );
1611
    }
1612
    if ($type === 'decode' && is_array($data) === false) {
1613
        // check if key exists
1614
        return json_decode(
1615
            (string) Encryption\CryptoJs\Encryption::decrypt(
1616
                (string) $data,
1617
                $globalsKey
1618
            ),
1619
            true
1620
        );
1621
    }
1622
}
1623
1624
1625
/**
1626
 * Create a thumbnail.
1627
 *
1628
 * @param string  $src           Source
1629
 * @param string  $dest          Destination
1630
 * @param int $desired_width Size of width
1631
 * 
1632
 * @return void|string|bool
1633
 */
1634
function makeThumbnail(string $src, string $dest, int $desired_width)
1635
{
1636
    /* read the source image */
1637
    if (is_file($src) === true && mime_content_type($src) === 'image/png') {
1638
        $source_image = imagecreatefrompng($src);
1639
        if ($source_image === false) {
1640
            return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1641
        }
1642
    } else {
1643
        return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1644
    }
1645
1646
    // Get height and width
1647
    $width = imagesx($source_image);
1648
    $height = imagesy($source_image);
1649
    /* find the "desired height" of this thumbnail, relative to the desired width  */
1650
    $desired_height = (int) floor($height * $desired_width / $width);
1651
    /* create a new, "virtual" image */
1652
    $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
1653
    if ($virtual_image === false) {
1654
        return false;
1655
    }
1656
    /* copy source image at a resized size */
1657
    imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
1658
    /* create the physical thumbnail image to its destination */
1659
    imagejpeg($virtual_image, $dest);
1660
}
1661
1662
/**
1663
 * Check table prefix in SQL query.
1664
 *
1665
 * @param string $table Table name
1666
 * 
1667
 * @return string
1668
 */
1669
function prefixTable(string $table): string
1670
{
1671
    $safeTable = htmlspecialchars(DB_PREFIX . $table);
1672
    if (! empty($safeTable)) {
1673
        // sanitize string
1674
        return $safeTable;
1675
    }
1676
    // stop error no table
1677
    return 'table_not_exists';
1678
}
1679
1680
/**
1681
 * GenerateCryptKey
1682
 *
1683
 * @param int     $size      Length
1684
 * @param bool $secure Secure
1685
 * @param bool $numerals Numerics
1686
 * @param bool $uppercase Uppercase letters
1687
 * @param bool $symbols Symbols
1688
 * @param bool $lowercase Lowercase
1689
 * @param array   $SETTINGS  SETTINGS
1690
 * 
1691
 * @return string
1692
 */
1693
function GenerateCryptKey(
1694
    int $size = 20,
1695
    bool $secure = false,
1696
    bool $numerals = false,
1697
    bool $uppercase = false,
1698
    bool $symbols = false,
1699
    bool $lowercase = false,
1700
    array $SETTINGS = []
1701
): string {
1702
    include_once __DIR__ . '/../sources/SplClassLoader.php';
1703
    $generator = new SplClassLoader('PasswordGenerator\Generator', __DIR__. '/../includes/libraries');
1704
    $generator->register();
1705
    $generator = new PasswordGenerator\Generator\ComputerPasswordGenerator();
1706
    // Is PHP7 being used?
1707
    if (version_compare(PHP_VERSION, '7.0.0', '>=')) {
1708
        $php7generator = new SplClassLoader('PasswordGenerator\RandomGenerator', __DIR__ . '/../includes/libraries');
1709
        $php7generator->register();
1710
        $generator->setRandomGenerator(new PasswordGenerator\RandomGenerator\Php7RandomGenerator());
1711
    }
1712
    
1713
    // Manage size
1714
    $generator->setLength((int) $size);
1715
    if ($secure === true) {
1716
        $generator->setSymbols(true);
1717
        $generator->setLowercase(true);
1718
        $generator->setUppercase(true);
1719
        $generator->setNumbers(true);
1720
    } else {
1721
        $generator->setLowercase($lowercase);
1722
        $generator->setUppercase($uppercase);
1723
        $generator->setNumbers($numerals);
1724
        $generator->setSymbols($symbols);
1725
    }
1726
1727
    return $generator->generatePasswords()[0];
1728
}
1729
1730
/**
1731
 * Send sysLOG message
1732
 *
1733
 * @param string    $message
1734
 * @param string    $host
1735
 * @param int       $port
1736
 * @param string    $component
1737
 * 
1738
 * @return void
1739
*/
1740
function send_syslog($message, $host, $port, $component = 'teampass'): void
1741
{
1742
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1743
    $syslog_message = '<123>' . date('M d H:i:s ') . $component . ': ' . $message;
1744
    socket_sendto($sock, (string) $syslog_message, strlen($syslog_message), 0, (string) $host, (int) $port);
1745
    socket_close($sock);
1746
}
1747
1748
/**
1749
 * Permits to log events into DB
1750
 *
1751
 * @param array  $SETTINGS Teampass settings
1752
 * @param string $type     Type
1753
 * @param string $label    Label
1754
 * @param string $who      Who
1755
 * @param string $login    Login
1756
 * @param string $field_1  Field
1757
 * 
1758
 * @return void
1759
 */
1760
function logEvents(
1761
    array $SETTINGS, 
1762
    string $type, 
1763
    string $label, 
1764
    string $who, 
1765
    ?string $login = null, 
1766
    ?string $field_1 = null
1767
): void
1768
{
1769
    if (empty($who)) {
1770
        $who = getClientIpServer();
1771
    }
1772
1773
    // include librairies & connect to DB
1774
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1775
    if (defined('DB_PASSWD_CLEAR') === false) {
1776
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1777
    }
1778
    DB::$host = DB_HOST;
1779
    DB::$user = DB_USER;
1780
    DB::$password = DB_PASSWD_CLEAR;
1781
    DB::$dbName = DB_NAME;
1782
    DB::$port = DB_PORT;
1783
    DB::$encoding = DB_ENCODING;
1784
    DB::$ssl = DB_SSL;
1785
    DB::$connect_options = DB_CONNECT_OPTIONS;
1786
    DB::insert(
1787
        prefixTable('log_system'),
1788
        [
1789
            'type' => $type,
1790
            'date' => time(),
1791
            'label' => $label,
1792
            'qui' => $who,
1793
            'field_1' => $field_1 === null ? '' : $field_1,
1794
        ]
1795
    );
1796
    // If SYSLOG
1797
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1798
        if ($type === 'user_mngt') {
1799
            send_syslog(
1800
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1801
                $SETTINGS['syslog_host'],
1802
                $SETTINGS['syslog_port'],
1803
                'teampass'
1804
            );
1805
        } else {
1806
            send_syslog(
1807
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1808
                $SETTINGS['syslog_host'],
1809
                $SETTINGS['syslog_port'],
1810
                'teampass'
1811
            );
1812
        }
1813
    }
1814
}
1815
1816
/**
1817
 * Log events.
1818
 *
1819
 * @param array  $SETTINGS        Teampass settings
1820
 * @param int    $item_id         Item id
1821
 * @param string $item_label      Item label
1822
 * @param int    $id_user         User id
1823
 * @param string $action          Code for reason
1824
 * @param string $login           User login
1825
 * @param string $raison          Code for reason
1826
 * @param string $encryption_type Encryption on
1827
 * 
1828
 * @return void
1829
 */
1830
function logItems(
1831
    array $SETTINGS,
1832
    int $item_id,
1833
    string $item_label,
1834
    int $id_user,
1835
    string $action,
1836
    ?string $login = null,
1837
    ?string $raison = null,
1838
    ?string $encryption_type = null
1839
): void {
1840
    // include librairies & connect to DB
1841
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1842
    if (defined('DB_PASSWD_CLEAR') === false) {
1843
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1844
    }
1845
    DB::$host = DB_HOST;
1846
    DB::$user = DB_USER;
1847
    DB::$password = DB_PASSWD_CLEAR;
1848
    DB::$dbName = DB_NAME;
1849
    DB::$port = DB_PORT;
1850
    DB::$encoding = DB_ENCODING;
1851
    DB::$ssl = DB_SSL;
1852
    DB::$connect_options = DB_CONNECT_OPTIONS;
1853
    // Insert log in DB
1854
    DB::insert(
1855
        prefixTable('log_items'),
1856
        [
1857
            'id_item' => $item_id,
1858
            'date' => time(),
1859
            'id_user' => $id_user,
1860
            'action' => $action,
1861
            'raison' => $raison,
1862
            'raison_iv' => '',
1863
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1864
        ]
1865
    );
1866
    // Timestamp the last change
1867
    if ($action === 'at_creation' || $action === 'at_modifiation' || $action === 'at_delete' || $action === 'at_import') {
1868
        DB::update(
1869
            prefixTable('misc'),
1870
            [
1871
                'valeur' => time(),
1872
            ],
1873
            'type = %s AND intitule = %s',
1874
            'timestamp',
1875
            'last_item_change'
1876
        );
1877
    }
1878
1879
    // SYSLOG
1880
    if (isset($SETTINGS['syslog_enable']) === true && $SETTINGS['syslog_enable'] === '1') {
1881
        // Extract reason
1882
        $attribute = is_null($raison) === true ? '' : explode(' : ', $raison);
1883
        // Get item info if not known
1884
        if (empty($item_label) === true) {
1885
            $dataItem = DB::queryfirstrow(
1886
                'SELECT id, id_tree, label
1887
                FROM ' . prefixTable('items') . '
1888
                WHERE id = %i',
1889
                $item_id
1890
            );
1891
            $item_label = $dataItem['label'];
1892
        }
1893
1894
        send_syslog(
1895
            'action=' . str_replace('at_', '', $action) .
1896
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1897
                ' itemno=' . $item_id .
1898
                ' user=' . is_null($login) === true ? '' : addslashes((string) $login) .
1899
                ' itemname="' . addslashes($item_label) . '"',
1900
            $SETTINGS['syslog_host'],
1901
            $SETTINGS['syslog_port'],
1902
            'teampass'
1903
        );
1904
    }
1905
1906
    // send notification if enabled
1907
    //notifyOnChange($item_id, $action, $SETTINGS);
1908
}
1909
1910
/**
1911
 * If enabled, then notify admin/manager.
1912
 *
1913
 * @param int    $item_id  Item id
1914
 * @param string $action   Action to do
1915
 * @param array  $SETTINGS Teampass settings
1916
 * 
1917
 * @return void
1918
 */
1919
/*
1920
function notifyOnChange(int $item_id, string $action, array $SETTINGS): void
1921
{
1922
    if (
1923
        isset($SETTINGS['enable_email_notification_on_item_shown']) === true
1924
        && (int) $SETTINGS['enable_email_notification_on_item_shown'] === 1
1925
        && $action === 'at_shown'
1926
    ) {
1927
        // Load superglobal
1928
        include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1929
        $superGlobal = new protect\SuperGlobal\SuperGlobal();
1930
        // Get superglobals
1931
        $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1932
        $globalsName = $superGlobal->get('name', 'SESSION');
1933
        $globalsNotifiedEmails = $superGlobal->get('listNotificationEmails', 'SESSION');
1934
        // Get info about item
1935
        $dataItem = DB::queryfirstrow(
1936
            'SELECT id, id_tree, label
1937
            FROM ' . prefixTable('items') . '
1938
            WHERE id = %i',
1939
            $item_id
1940
        );
1941
        $item_label = $dataItem['label'];
1942
        // send back infos
1943
        DB::insert(
1944
            prefixTable('emails'),
1945
            [
1946
                'timestamp' => time(),
1947
                'subject' => langHdl('email_on_open_notification_subject'),
1948
                'body' => str_replace(
1949
                    ['#tp_user#', '#tp_item#', '#tp_link#'],
1950
                    [
1951
                        addslashes($globalsName . ' ' . $globalsLastname),
1952
                        addslashes($item_label),
1953
                        $SETTINGS['cpassman_url'] . '/index.php?page=items&group=' . $dataItem['id_tree'] . '&id=' . $item_id,
1954
                    ],
1955
                    langHdl('email_on_open_notification_mail')
1956
                ),
1957
                'receivers' => $globalsNotifiedEmails,
1958
                'status' => '',
1959
            ]
1960
        );
1961
    }
1962
}
1963
*/
1964
1965
/**
1966
 * Prepare notification email to subscribers.
1967
 *
1968
 * @param int    $item_id  Item id
1969
 * @param string $label    Item label
1970
 * @param array  $changes  List of changes
1971
 * @param array  $SETTINGS Teampass settings
1972
 * 
1973
 * @return void
1974
 */
1975
function notifyChangesToSubscribers(int $item_id, string $label, array $changes, array $SETTINGS): void
1976
{
1977
    // Load superglobal
1978
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1979
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1980
    // Get superglobals
1981
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
1982
    $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1983
    $globalsName = $superGlobal->get('name', 'SESSION');
1984
    // send email to user that what to be notified
1985
    $notification = DB::queryOneColumn(
1986
        'email',
1987
        'SELECT *
1988
        FROM ' . prefixTable('notification') . ' AS n
1989
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1990
        WHERE n.item_id = %i AND n.user_id != %i',
1991
        $item_id,
1992
        $globalsUserId
1993
    );
1994
    if (DB::count() > 0) {
1995
        // Prepare path
1996
        $path = geItemReadablePath($item_id, '', $SETTINGS);
1997
        // Get list of changes
1998
        $htmlChanges = '<ul>';
1999
        foreach ($changes as $change) {
2000
            $htmlChanges .= '<li>' . $change . '</li>';
2001
        }
2002
        $htmlChanges .= '</ul>';
2003
        // send email
2004
        DB::insert(
2005
            prefixTable('emails'),
2006
            [
2007
                'timestamp' => time(),
2008
                'subject' => langHdl('email_subject_item_updated'),
2009
                'body' => str_replace(
2010
                    ['#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'],
2011
                    [$label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges],
2012
                    langHdl('email_body_item_updated')
2013
                ),
2014
                'receivers' => implode(',', $notification),
2015
                'status' => '',
2016
            ]
2017
        );
2018
    }
2019
}
2020
2021
/**
2022
 * Returns the Item + path.
2023
 *
2024
 * @param int    $id_tree  Node id
2025
 * @param string $label    Label
2026
 * @param array  $SETTINGS TP settings
2027
 * 
2028
 * @return string
2029
 */
2030
function geItemReadablePath(int $id_tree, string $label, array $SETTINGS): string
2031
{
2032
    // Class loader
2033
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
2034
    //Load Tree
2035
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
2036
    $tree->register();
2037
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
2038
    $arbo = $tree->getPath($id_tree, true);
2039
    $path = '';
2040
    foreach ($arbo as $elem) {
2041
        if (empty($path) === true) {
2042
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
2043
        } else {
2044
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
2045
        }
2046
    }
2047
2048
    // Build text to show user
2049
    if (empty($label) === false) {
2050
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
2051
    }
2052
    return empty($path) === true ? '' : $path;
2053
}
2054
2055
/**
2056
 * Get the client ip address.
2057
 *
2058
 * @return string IP address
2059
 */
2060
function getClientIpServer(): string
2061
{
2062
    if (getenv('HTTP_CLIENT_IP')) {
2063
        $ipaddress = getenv('HTTP_CLIENT_IP');
2064
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
2065
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
2066
    } elseif (getenv('HTTP_X_FORWARDED')) {
2067
        $ipaddress = getenv('HTTP_X_FORWARDED');
2068
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
2069
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
2070
    } elseif (getenv('HTTP_FORWARDED')) {
2071
        $ipaddress = getenv('HTTP_FORWARDED');
2072
    } elseif (getenv('REMOTE_ADDR')) {
2073
        $ipaddress = getenv('REMOTE_ADDR');
2074
    } else {
2075
        $ipaddress = 'UNKNOWN';
2076
    }
2077
2078
    return $ipaddress;
2079
}
2080
2081
/**
2082
 * Escape all HTML, JavaScript, and CSS.
2083
 *
2084
 * @param string $input    The input string
2085
 * @param string $encoding Which character encoding are we using?
2086
 * 
2087
 * @return string
2088
 */
2089
function noHTML(string $input, string $encoding = 'UTF-8'): string
2090
{
2091
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
2092
}
2093
2094
/**
2095
 * Permits to handle the Teampass config file
2096
 * $action accepts "rebuild" and "update"
2097
 *
2098
 * @param string $action   Action to perform
2099
 * @param array  $SETTINGS Teampass settings
2100
 * @param string $field    Field to refresh
2101
 * @param string $value    Value to set
2102
 *
2103
 * @return string|bool
2104
 */
2105
function handleConfigFile($action, $SETTINGS, $field = null, $value = null)
2106
{
2107
    $tp_config_file = $SETTINGS['cpassman_dir'] . '/includes/config/tp.config.php';
2108
    // include librairies & connect to DB
2109
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2110
    if (defined('DB_PASSWD_CLEAR') === false) {
2111
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2112
    }
2113
    DB::$host = DB_HOST;
2114
    DB::$user = DB_USER;
2115
    DB::$password = DB_PASSWD_CLEAR;
2116
    DB::$dbName = DB_NAME;
2117
    DB::$port = DB_PORT;
2118
    DB::$encoding = DB_ENCODING;
2119
    DB::$ssl = DB_SSL;
2120
    DB::$connect_options = DB_CONNECT_OPTIONS;
2121
    if (file_exists($tp_config_file) === false || $action === 'rebuild') {
2122
        // perform a copy
2123
        if (file_exists($tp_config_file)) {
2124
            if (! copy($tp_config_file, $tp_config_file . '.' . date('Y_m_d_His', time()))) {
2125
                return "ERROR: Could not copy file '" . $tp_config_file . "'";
2126
            }
2127
        }
2128
2129
        // regenerate
2130
        $data = [];
2131
        $data[0] = "<?php\n";
2132
        $data[1] = "global \$SETTINGS;\n";
2133
        $data[2] = "\$SETTINGS = array (\n";
2134
        $rows = DB::query(
2135
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s',
2136
            'admin'
2137
        );
2138
        foreach ($rows as $record) {
2139
            array_push($data, "    '" . $record['intitule'] . "' => '" . htmlspecialchars_decode($record['valeur'], ENT_COMPAT) . "',\n");
2140
        }
2141
        array_push($data, ");\n");
2142
        $data = array_unique($data);
2143
    // ---
2144
    } elseif ($action === 'update' && empty($field) === false) {
2145
        $data = file($tp_config_file);
2146
        $inc = 0;
2147
        $bFound = false;
2148
        foreach ($data as $line) {
2149
            if (stristr($line, ');')) {
2150
                break;
2151
            }
2152
2153
            if (stristr($line, "'" . $field . "' => '")) {
2154
                $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode($value, ENT_COMPAT) . "',\n";
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type null; however, parameter $string of htmlspecialchars_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

2154
                $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode(/** @scrutinizer ignore-type */ $value, ENT_COMPAT) . "',\n";
Loading history...
2155
                $bFound = true;
2156
                break;
2157
            }
2158
            ++$inc;
2159
        }
2160
        if ($bFound === false) {
2161
            $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode($value, ENT_COMPAT). "',\n);\n";
2162
        }
2163
    }
2164
2165
    // update file
2166
    file_put_contents($tp_config_file, implode('', $data ?? []));
2167
    return true;
2168
}
2169
2170
/**
2171
 * Permits to replace &#92; to permit correct display
2172
 *
2173
 * @param string $input Some text
2174
 * 
2175
 * @return string
2176
 */
2177
function handleBackslash(string $input): string
2178
{
2179
    return str_replace('&amp;#92;', '&#92;', $input);
2180
}
2181
2182
/**
2183
 * Permits to load settings
2184
 * 
2185
 * @return void
2186
*/
2187
function loadSettings(): void
2188
{
2189
    global $SETTINGS;
2190
    /* LOAD CPASSMAN SETTINGS */
2191
    if (! isset($SETTINGS['loaded']) || $SETTINGS['loaded'] !== 1) {
2192
        $SETTINGS = [];
2193
        $SETTINGS['duplicate_folder'] = 0;
2194
        //by default, this is set to 0;
2195
        $SETTINGS['duplicate_item'] = 0;
2196
        //by default, this is set to 0;
2197
        $SETTINGS['number_of_used_pw'] = 5;
2198
        //by default, this value is set to 5;
2199
        $settings = [];
2200
        $rows = DB::query(
2201
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
2202
            [
2203
                'type' => 'admin',
2204
                'type2' => 'settings',
2205
            ]
2206
        );
2207
        foreach ($rows as $record) {
2208
            if ($record['type'] === 'admin') {
2209
                $SETTINGS[$record['intitule']] = $record['valeur'];
2210
            } else {
2211
                $settings[$record['intitule']] = $record['valeur'];
2212
            }
2213
        }
2214
        $SETTINGS['loaded'] = 1;
2215
        $SETTINGS['default_session_expiration_time'] = 5;
2216
    }
2217
}
2218
2219
/**
2220
 * check if folder has custom fields.
2221
 * Ensure that target one also has same custom fields
2222
 * 
2223
 * @param int $source_id
2224
 * @param int $target_id 
2225
 * 
2226
 * @return bool
2227
*/
2228
function checkCFconsistency(int $source_id, int $target_id): bool
2229
{
2230
    $source_cf = [];
2231
    $rows = DB::QUERY(
2232
        'SELECT id_category
2233
            FROM ' . prefixTable('categories_folders') . '
2234
            WHERE id_folder = %i',
2235
        $source_id
2236
    );
2237
    foreach ($rows as $record) {
2238
        array_push($source_cf, $record['id_category']);
2239
    }
2240
2241
    $target_cf = [];
2242
    $rows = DB::QUERY(
2243
        'SELECT id_category
2244
            FROM ' . prefixTable('categories_folders') . '
2245
            WHERE id_folder = %i',
2246
        $target_id
2247
    );
2248
    foreach ($rows as $record) {
2249
        array_push($target_cf, $record['id_category']);
2250
    }
2251
2252
    $cf_diff = array_diff($source_cf, $target_cf);
2253
    if (count($cf_diff) > 0) {
2254
        return false;
2255
    }
2256
2257
    return true;
2258
}
2259
2260
/**
2261
 * Will encrypte/decrypt a fil eusing Defuse.
2262
 *
2263
 * @param string $type        can be either encrypt or decrypt
2264
 * @param string $source_file path to source file
2265
 * @param string $target_file path to target file
2266
 * @param array  $SETTINGS    Settings
2267
 * @param string $password    A password
2268
 *
2269
 * @return string|bool
2270
 */
2271
function prepareFileWithDefuse(
2272
    string $type,
2273
    string $source_file,
2274
    string $target_file,
2275
    array $SETTINGS,
2276
    string $password = null
2277
) {
2278
    // Load AntiXSS
2279
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/portable-ascii-master/src/voku/helper/ASCII.php';
2280
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/portable-utf8-master/src/voku/helper/UTF8.php';
2281
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/anti-xss-master/src/voku/helper/AntiXSS.php';
2282
    $antiXss = new voku\helper\AntiXSS();
2283
    // Protect against bad inputs
2284
    if (is_array($source_file) === true || is_array($target_file) === true) {
2285
        return 'error_cannot_be_array';
2286
    }
2287
2288
    // Sanitize
2289
    $source_file = $antiXss->xss_clean($source_file);
2290
    $target_file = $antiXss->xss_clean($target_file);
2291
    if (empty($password) === true || is_null($password) === true) {
2292
        // get KEY to define password
2293
        $ascii_key = file_get_contents(SECUREPATH.'/'.SECUREFILE);
2294
        $password = \Defuse\Crypto\Key::loadFromAsciiSafeString($ascii_key);
2295
    }
2296
2297
    $err = '';
2298
    if ($type === 'decrypt') {
2299
        // Decrypt file
2300
        $err = defuseFileDecrypt(
2301
            $source_file,
2302
            $target_file,
2303
            $SETTINGS, /** @scrutinizer ignore-type */
2304
            $password
2305
        );
2306
    } elseif ($type === 'encrypt') {
2307
        // Encrypt file
2308
        $err = defuseFileEncrypt(
2309
            $source_file,
2310
            $target_file,
2311
            $SETTINGS, /** @scrutinizer ignore-type */
2312
            $password
2313
        );
2314
    }
2315
2316
    // return error
2317
    return $err === true ? '' : $err;
2318
}
2319
2320
/**
2321
 * Encrypt a file with Defuse.
2322
 *
2323
 * @param string $source_file path to source file
2324
 * @param string $target_file path to target file
2325
 * @param array  $SETTINGS    Settings
2326
 * @param string $password    A password
2327
 *
2328
 * @return string|bool
2329
 */
2330
function defuseFileEncrypt(
2331
    string $source_file,
2332
    string $target_file,
2333
    array $SETTINGS,
2334
    string $password = null
2335
) {
2336
    // load PhpEncryption library
2337
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2338
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/CryptoException.php';
2339
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/BadFormatException.php';
2340
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/IOException.php';
2341
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/EnvironmentIsBrokenException.php';
2342
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/WrongKeyOrModifiedCiphertextException.php';
2343
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2344
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2345
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2346
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2347
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2348
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2349
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2350
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2351
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2352
    try {
2353
        \Defuse\Crypto\File::encryptFileWithPassword(
2354
            $source_file,
2355
            $target_file,
2356
            $password
2357
        );
2358
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2359
        $err = 'wrong_key';
2360
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2361
        $err = $ex;
2362
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2363
        $err = $ex;
2364
    }
2365
2366
    // return error
2367
    return empty($err) === false ? $err : true;
2368
}
2369
2370
/**
2371
 * Decrypt a file with Defuse.
2372
 *
2373
 * @param string $source_file path to source file
2374
 * @param string $target_file path to target file
2375
 * @param array  $SETTINGS    Settings
2376
 * @param string $password    A password
2377
 *
2378
 * @return string|bool
2379
 */
2380
function defuseFileDecrypt(
2381
    string $source_file,
2382
    string $target_file,
2383
    array $SETTINGS,
2384
    string $password = null
2385
) {
2386
    // load PhpEncryption library
2387
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2388
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/CryptoException.php';
2389
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/BadFormatException.php';
2390
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/IOException.php';
2391
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/EnvironmentIsBrokenException.php';
2392
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/WrongKeyOrModifiedCiphertextException.php';
2393
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2394
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2395
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2396
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2397
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2398
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2399
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2400
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2401
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2402
    try {
2403
        \Defuse\Crypto\File::decryptFileWithPassword(
2404
            $source_file,
2405
            $target_file,
2406
            $password
2407
        );
2408
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2409
        $err = 'wrong_key';
2410
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2411
        $err = $ex;
2412
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2413
        $err = $ex;
2414
    }
2415
2416
    // return error
2417
    return empty($err) === false ? $err : true;
2418
}
2419
2420
/*
2421
* NOT TO BE USED
2422
*/
2423
/**
2424
 * Undocumented function.
2425
 *
2426
 * @param string $text Text to debug
2427
 */
2428
function debugTeampass(string $text): void
2429
{
2430
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
2431
    if ($debugFile !== false) {
2432
        fputs($debugFile, $text);
2433
        fclose($debugFile);
2434
    }
2435
}
2436
2437
/**
2438
 * DELETE the file with expected command depending on server type.
2439
 *
2440
 * @param string $file     Path to file
2441
 * @param array  $SETTINGS Teampass settings
2442
 *
2443
 * @return void
2444
 */
2445
function fileDelete(string $file, array $SETTINGS): void
2446
{
2447
    // Load AntiXSS
2448
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/portable-ascii-master/src/voku/helper/ASCII.php';
2449
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/portable-utf8-master/src/voku/helper/UTF8.php';
2450
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/anti-xss-master/src/voku/helper/AntiXSS.php';
2451
    $antiXss = new voku\helper\AntiXSS();
2452
    $file = $antiXss->xss_clean($file);
2453
    if (is_file($file)) {
2454
        unlink($file);
2455
    }
2456
}
2457
2458
/**
2459
 * Permits to extract the file extension.
2460
 *
2461
 * @param string $file File name
2462
 *
2463
 * @return string
2464
 */
2465
function getFileExtension(string $file): string
2466
{
2467
    if (strpos($file, '.') === false) {
2468
        return $file;
2469
    }
2470
2471
    return substr($file, strrpos($file, '.') + 1);
2472
}
2473
2474
/**
2475
 * Chmods files and folders with different permissions.
2476
 *
2477
 * This is an all-PHP alternative to using: \n
2478
 * <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2479
 * <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2480
 *
2481
 * @author Jeppe Toustrup (tenzer at tenzer dot dk)
2482
  *
2483
 * @param string $path      An either relative or absolute path to a file or directory which should be processed.
2484
 * @param int    $filePerm The permissions any found files should get.
2485
 * @param int    $dirPerm  The permissions any found folder should get.
2486
 *
2487
 * @return bool Returns TRUE if the path if found and FALSE if not.
2488
 *
2489
 * @warning The permission levels has to be entered in octal format, which
2490
 * normally means adding a zero ("0") in front of the permission level. \n
2491
 * More info at: http://php.net/chmod.
2492
*/
2493
2494
function recursiveChmod(
2495
    string $path,
2496
    int $filePerm = 0644,
2497
    int  $dirPerm = 0755
2498
) {
2499
    // Check if the path exists
2500
    if (! file_exists($path)) {
2501
        return false;
2502
    }
2503
2504
    // See whether this is a file
2505
    if (is_file($path)) {
2506
        // Chmod the file with our given filepermissions
2507
        chmod($path, $filePerm);
2508
    // If this is a directory...
2509
    } elseif (is_dir($path)) {
2510
        // Then get an array of the contents
2511
        $foldersAndFiles = scandir($path);
2512
        // Remove "." and ".." from the list
2513
        $entries = array_slice($foldersAndFiles, 2);
2514
        // Parse every result...
2515
        foreach ($entries as $entry) {
2516
            // And call this function again recursively, with the same permissions
2517
            recursiveChmod($path.'/'.$entry, $filePerm, $dirPerm);
2518
        }
2519
2520
        // When we are done with the contents of the directory, we chmod the directory itself
2521
        chmod($path, $dirPerm);
2522
    }
2523
2524
    // Everything seemed to work out well, return true
2525
    return true;
2526
}
2527
2528
/**
2529
 * Check if user can access to this item.
2530
 *
2531
 * @param int   $item_id ID of item
2532
 * @param array $SETTINGS
2533
 *
2534
 * @return bool|string
2535
 */
2536
function accessToItemIsGranted(int $item_id, array $SETTINGS)
2537
{
2538
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2539
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2540
    // Prepare superGlobal variables
2541
    $session_groupes_visibles = $superGlobal->get('groupes_visibles', 'SESSION');
2542
    $session_list_restricted_folders_for_items = $superGlobal->get('list_restricted_folders_for_items', 'SESSION');
2543
    // Load item data
2544
    $data = DB::queryFirstRow(
2545
        'SELECT id_tree
2546
        FROM ' . prefixTable('items') . '
2547
        WHERE id = %i',
2548
        $item_id
2549
    );
2550
    // Check if user can access this folder
2551
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2552
        // Now check if this folder is restricted to user
2553
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2554
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2555
        ) {
2556
            return 'ERR_FOLDER_NOT_ALLOWED';
2557
        }
2558
    }
2559
2560
    return true;
2561
}
2562
2563
/**
2564
 * Creates a unique key.
2565
 *
2566
 * @param int $lenght Key lenght
2567
 *
2568
 * @return string
2569
 */
2570
function uniqidReal(int $lenght = 13): string
2571
{
2572
    if (function_exists('random_bytes')) {
2573
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2574
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2575
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2576
    } else {
2577
        throw new Exception('no cryptographically secure random function available');
2578
    }
2579
2580
    return substr(bin2hex($bytes), 0, $lenght);
2581
}
2582
2583
/**
2584
 * Obfuscate an email.
2585
 *
2586
 * @param string $email Email address
2587
 *
2588
 * @return string
2589
 */
2590
function obfuscateEmail(string $email): string
2591
{
2592
    $email = explode("@", $email);
2593
    $name = $email[0];
2594
    if (strlen($name) > 3) {
2595
        $name = substr($name, 0, 2);
2596
        for ($i = 0; $i < strlen($email[0]) - 3; $i++) {
2597
            $name .= "*";
2598
        }
2599
        $name .= substr($email[0], -1, 1);
2600
    }
2601
    $host = explode(".", $email[1])[0];
2602
    if (strlen($host) > 3) {
2603
        $host = substr($host, 0, 1);
2604
        for ($i = 0; $i < strlen(explode(".", $email[1])[0]) - 2; $i++) {
2605
            $host .= "*";
2606
        }
2607
        $host .= substr(explode(".", $email[1])[0], -1, 1);
2608
    }
2609
    $email = $name . "@" . $host . "." . explode(".", $email[1])[1];
2610
    return $email;
2611
}
2612
2613
/**
2614
 * Perform a Query.
2615
 *
2616
 * @param array  $SETTINGS Teamapss settings
2617
 * @param string $fields   Fields to use
2618
 * @param string $table    Table to use
2619
 *
2620
 * @return array
2621
 */
2622
function performDBQuery(array $SETTINGS, string $fields, string $table): array
2623
{
2624
    // include librairies & connect to DB
2625
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2626
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2627
    if (defined('DB_PASSWD_CLEAR') === false) {
2628
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2629
    }
2630
    DB::$host = DB_HOST;
2631
    DB::$user = DB_USER;
2632
    DB::$password = DB_PASSWD_CLEAR;
2633
    DB::$dbName = DB_NAME;
2634
    DB::$port = DB_PORT;
2635
    DB::$encoding = DB_ENCODING;
2636
    DB::$ssl = DB_SSL;
2637
    DB::$connect_options = DB_CONNECT_OPTIONS;
2638
    // Insert log in DB
2639
    return DB::query(
2640
        'SELECT ' . $fields . '
2641
        FROM ' . prefixTable($table)
2642
    );
2643
}
2644
2645
/**
2646
 * Undocumented function.
2647
 *
2648
 * @param int $bytes Size of file
2649
 *
2650
 * @return string
2651
 */
2652
function formatSizeUnits(int $bytes): string
2653
{
2654
    if ($bytes >= 1073741824) {
2655
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2656
    } elseif ($bytes >= 1048576) {
2657
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2658
    } elseif ($bytes >= 1024) {
2659
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2660
    } elseif ($bytes > 1) {
2661
        $bytes .= ' bytes';
2662
    } elseif ($bytes === 1) {
2663
        $bytes .= ' byte';
2664
    } else {
2665
        $bytes = '0 bytes';
2666
    }
2667
2668
    return $bytes;
2669
}
2670
2671
/**
2672
 * Generate user pair of keys.
2673
 *
2674
 * @param string $userPwd User password
2675
 *
2676
 * @return array
2677
 */
2678
function generateUserKeys(string $userPwd): array
2679
{
2680
    // include library
2681
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2682
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2683
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2684
    // Load classes
2685
    $rsa = new Crypt_RSA();
2686
    $cipher = new Crypt_AES();
2687
    // Create the private and public key
2688
    $res = $rsa->createKey(4096);
2689
    // Encrypt the privatekey
2690
    $cipher->setPassword($userPwd);
2691
    $privatekey = $cipher->encrypt($res['privatekey']);
2692
    return [
2693
        'private_key' => base64_encode($privatekey),
2694
        'public_key' => base64_encode($res['publickey']),
2695
        'private_key_clear' => base64_encode($res['privatekey']),
2696
    ];
2697
}
2698
2699
/**
2700
 * Permits to decrypt the user's privatekey.
2701
 *
2702
 * @param string $userPwd        User password
2703
 * @param string $userPrivateKey User private key
2704
 *
2705
 * @return string
2706
 */
2707
function decryptPrivateKey(string $userPwd, string $userPrivateKey): string
2708
{
2709
    if (empty($userPwd) === false) {
2710
        include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2711
        // Load classes
2712
        $cipher = new Crypt_AES();
2713
        // Encrypt the privatekey
2714
        $cipher->setPassword($userPwd);
2715
        try {
2716
            return base64_encode((string) $cipher->decrypt(base64_decode($userPrivateKey)));
2717
        } catch (Exception $e) {
2718
            return $e;
2719
        }
2720
    }
2721
    return '';
2722
}
2723
2724
/**
2725
 * Permits to encrypt the user's privatekey.
2726
 *
2727
 * @param string $userPwd        User password
2728
 * @param string $userPrivateKey User private key
2729
 *
2730
 * @return string
2731
 */
2732
function encryptPrivateKey(string $userPwd, string $userPrivateKey): string
2733
{
2734
    if (empty($userPwd) === false) {
2735
        include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2736
        // Load classes
2737
        $cipher = new Crypt_AES();
2738
        // Encrypt the privatekey
2739
        $cipher->setPassword($userPwd);        
2740
        try {
2741
            return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2742
        } catch (Exception $e) {
2743
            return $e;
2744
        }
2745
    }
2746
    return '';
2747
}
2748
2749
/**
2750
 * Encrypts a string using AES.
2751
 *
2752
 * @param string $data String to encrypt
2753
 * @param string $key
2754
 *
2755
 * @return array
2756
 */
2757
function doDataEncryption(string $data, string $key = NULL): array
2758
{
2759
    // Includes
2760
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2761
    // Load classes
2762
    $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2763
    // Generate an object key
2764
    $objectKey = is_null($key) === true ? uniqidReal(32) : $key;
2765
    // Set it as password
2766
    $cipher->setPassword($objectKey);
2767
    return [
2768
        'encrypted' => base64_encode($cipher->encrypt($data)),
2769
        'objectKey' => base64_encode($objectKey),
2770
    ];
2771
}
2772
2773
/**
2774
 * Decrypts a string using AES.
2775
 *
2776
 * @param string $data Encrypted data
2777
 * @param string $key  Key to uncrypt
2778
 *
2779
 * @return string
2780
 */
2781
function doDataDecryption(string $data, string $key): string
2782
{
2783
    // Includes
2784
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2785
    // Load classes
2786
    $cipher = new Crypt_AES();
2787
    // Set the object key
2788
    $cipher->setPassword(base64_decode($key));
2789
    return base64_encode($cipher->decrypt(base64_decode($data)));
2790
}
2791
2792
/**
2793
 * Encrypts using RSA a string using a public key.
2794
 *
2795
 * @param string $key       Key to be encrypted
2796
 * @param string $publicKey User public key
2797
 *
2798
 * @return string
2799
 */
2800
function encryptUserObjectKey(string $key, string $publicKey): string
2801
{
2802
    // Includes
2803
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2804
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2805
    // Load classes
2806
    $rsa = new Crypt_RSA();
2807
    $rsa->loadKey(base64_decode($publicKey));
2808
    // Encrypt
2809
    return base64_encode($rsa->encrypt(base64_decode($key)));
2810
}
2811
2812
/**
2813
 * Decrypts using RSA an encrypted string using a private key.
2814
 *
2815
 * @param string $key        Encrypted key
2816
 * @param string $privateKey User private key
2817
 *
2818
 * @return string
2819
 */
2820
function decryptUserObjectKey(string $key, string $privateKey): string
2821
{
2822
    // Includes
2823
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2824
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2825
    // Load classes
2826
    $rsa = new Crypt_RSA();
2827
    $rsa->loadKey(base64_decode($privateKey));
2828
    // Decrypt
2829
    try {
2830
        $tmpValue = $rsa->decrypt(base64_decode($key));
2831
        if (is_bool($tmpValue) === false) {
0 ignored issues
show
introduced by
The condition is_bool($tmpValue) === false is always true.
Loading history...
2832
            $ret = base64_encode((string) /** @scrutinizer ignore-type */$tmpValue);
2833
        } else {
2834
            $ret = '';
2835
        }
2836
    } catch (Exception $e) {
2837
        return $e;
2838
    }
2839
2840
    return $ret;
2841
}
2842
2843
/**
2844
 * Encrypts a file.
2845
 *
2846
 * @param string $fileInName File name
2847
 * @param string $fileInPath Path to file
2848
 *
2849
 * @return array
2850
 */
2851
function encryptFile(string $fileInName, string $fileInPath): array
2852
{
2853
    if (defined('FILE_BUFFER_SIZE') === false) {
2854
        define('FILE_BUFFER_SIZE', 128 * 1024);
2855
    }
2856
2857
    // Includes
2858
    include_once __DIR__.'/../includes/config/include.php';
2859
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2860
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2861
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2862
    // Load classes
2863
    $cipher = new Crypt_AES();
2864
    // Generate an object key
2865
    $objectKey = uniqidReal(32);
2866
    // Set it as password
2867
    $cipher->setPassword($objectKey);
2868
    // Prevent against out of memory
2869
    $cipher->enableContinuousBuffer();
2870
    //$cipher->disablePadding();
2871
2872
    // Encrypt the file content
2873
    $plaintext = file_get_contents(
2874
        filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL)
2875
    );
2876
    $ciphertext = $cipher->encrypt($plaintext);
2877
    // Save new file
2878
    $hash = md5($plaintext);
2879
    $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2880
    file_put_contents($fileOut, $ciphertext);
2881
    unlink($fileInPath . '/' . $fileInName);
2882
    return [
2883
        'fileHash' => base64_encode($hash),
2884
        'objectKey' => base64_encode($objectKey),
2885
    ];
2886
}
2887
2888
/**
2889
 * Decrypt a file.
2890
 *
2891
 * @param string $fileName File name
2892
 * @param string $filePath Path to file
2893
 * @param string $key      Key to use
2894
 *
2895
 * @return string
2896
 */
2897
function decryptFile(string $fileName, string $filePath, string $key): string
2898
{
2899
    if (! defined('FILE_BUFFER_SIZE')) {
2900
        define('FILE_BUFFER_SIZE', 128 * 1024);
2901
    }
2902
2903
    // Includes
2904
    include_once __DIR__.'/../includes/config/include.php';
2905
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2906
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2907
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2908
    // Get file name
2909
    $fileName = base64_decode($fileName);
2910
    // Load classes
2911
    $cipher = new Crypt_AES();
2912
    // Set the object key
2913
    $cipher->setPassword(base64_decode($key));
2914
    // Prevent against out of memory
2915
    $cipher->enableContinuousBuffer();
2916
    $cipher->disablePadding();
2917
    // Get file content
2918
    $ciphertext = file_get_contents($filePath . '/' . TP_FILE_PREFIX . $fileName);
2919
    // Decrypt file content and return
2920
    return base64_encode($cipher->decrypt($ciphertext));
2921
}
2922
2923
/**
2924
 * Generate a simple password
2925
 *
2926
 * @param int $length Length of string
2927
 * @param bool $symbolsincluded Allow symbols
2928
 *
2929
 * @return string
2930
 */
2931
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2932
{
2933
    // Generate new user password
2934
    $small_letters = range('a', 'z');
2935
    $big_letters = range('A', 'Z');
2936
    $digits = range(0, 9);
2937
    $symbols = $symbolsincluded === true ?
2938
        ['#', '_', '-', '@', '$', '+', '&'] : [];
2939
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2940
    $count = count($res);
2941
    // first variant
2942
2943
    $random_string = '';
2944
    for ($i = 0; $i < $length; ++$i) {
2945
        $random_string .= $res[random_int(0, $count - 1)];
2946
    }
2947
2948
    return $random_string;
2949
}
2950
2951
/**
2952
 * Permit to store the sharekey of an object for users.
2953
 *
2954
 * @param string $object_name             Type for table selection
2955
 * @param int    $post_folder_is_personal Personal
2956
 * @param int    $post_folder_id          Folder
2957
 * @param int    $post_object_id          Object
2958
 * @param string $objectKey               Object key
2959
 * @param array  $SETTINGS                Teampass settings
2960
 *
2961
 * @return void
2962
 */
2963
function storeUsersShareKey(
2964
    string $object_name,
2965
    int $post_folder_is_personal,
2966
    int $post_folder_id,
2967
    int $post_object_id,
2968
    string $objectKey,
2969
    array $SETTINGS
2970
): void {
2971
    // include librairies & connect to DB
2972
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2973
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2974
    if (defined('DB_PASSWD_CLEAR') === false) {
2975
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2976
    }
2977
    DB::$host = DB_HOST;
2978
    DB::$user = DB_USER;
2979
    DB::$password = DB_PASSWD_CLEAR;
2980
    DB::$dbName = DB_NAME;
2981
    DB::$port = DB_PORT;
2982
    DB::$encoding = DB_ENCODING;
2983
    DB::$ssl = DB_SSL;
2984
    DB::$connect_options = DB_CONNECT_OPTIONS;
2985
    // Delete existing entries for this object
2986
    DB::delete(
2987
        $object_name,
2988
        'object_id = %i',
2989
        $post_object_id
2990
    );
2991
    // Superglobals
2992
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2993
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2994
    // Prepare superGlobal variables
2995
    $sessionPpersonaFolders = $superGlobal->get('personal_folders', 'SESSION');
2996
    $sessionUserId = $superGlobal->get('user_id', 'SESSION');
2997
    $sessionUserPublicKey = $superGlobal->get('public_key', 'SESSION', 'user');
2998
    if (
2999
        (int) $post_folder_is_personal === 1
3000
        && in_array($post_folder_id, $sessionPpersonaFolders) === true
3001
    ) {
3002
        // If this is a personal object
3003
        // Only create the sharekey for user
3004
        DB::insert(
3005
            $object_name,
3006
            [
3007
                'object_id' => (int) $post_object_id,
3008
                'user_id' => (int) $sessionUserId,
3009
                'share_key' => encryptUserObjectKey($objectKey, $sessionUserPublicKey),
3010
            ]
3011
        );
3012
    } else {
3013
        // This is a public object
3014
        // Create sharekey for each user
3015
        $users = DB::query(
3016
            'SELECT id, public_key
3017
            FROM ' . prefixTable('users') . '
3018
            WHERE id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '")
3019
            AND public_key != ""'
3020
        );
3021
        foreach ($users as $user) {
3022
            // Insert in DB the new object key for this item by user
3023
            DB::insert(
3024
                $object_name,
3025
                [
3026
                    'object_id' => $post_object_id,
3027
                    'user_id' => (int) $user['id'],
3028
                    'share_key' => encryptUserObjectKey(
3029
                        $objectKey,
3030
                        $user['public_key']
3031
                    ),
3032
                ]
3033
            );
3034
        }
3035
    }
3036
}
3037
3038
/**
3039
 * Is this string base64 encoded?
3040
 *
3041
 * @param string $str Encoded string?
3042
 *
3043
 * @return bool
3044
 */
3045
function isBase64(string $str): bool
3046
{
3047
    $str = (string) trim($str);
3048
    if (! isset($str[0])) {
3049
        return false;
3050
    }
3051
3052
    $base64String = (string) base64_decode($str, true);
3053
    if ($base64String && base64_encode($base64String) === $str) {
3054
        return true;
3055
    }
3056
3057
    return false;
3058
}
3059
3060
/**
3061
 * Undocumented function
3062
 *
3063
 * @param string $field Parameter
3064
 *
3065
 * @return array|bool|resource|string
3066
 */
3067
function filterString(string $field)
3068
{
3069
    // Sanitize string
3070
    $field = filter_var(trim($field), FILTER_SANITIZE_FULL_SPECIAL_CHARS);
3071
    if (empty($field) === false) {
3072
        // Load AntiXSS
3073
        include_once __DIR__.'/../includes/libraries/anti-xss-master/src/voku/helper/AntiXSS.php';
3074
        $antiXss = new voku\helper\AntiXSS();
3075
        // Return
3076
        return $antiXss->xss_clean($field);
3077
    }
3078
3079
    return false;
3080
}
3081
3082
/**
3083
 * CHeck if provided credentials are allowed on server
3084
 *
3085
 * @param string $login    User Login
3086
 * @param string $password User Pwd
3087
 * @param array  $SETTINGS Teampass settings
3088
 *
3089
 * @return bool
3090
 */
3091
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
3092
{
3093
    // Build ldap configuration array
3094
    $config = [
3095
        // Mandatory Configuration Options
3096
        'hosts' => [$SETTINGS['ldap_hosts']],
3097
        'base_dn' => $SETTINGS['ldap_bdn'],
3098
        'username' => $SETTINGS['ldap_username'],
3099
        'password' => $SETTINGS['ldap_password'],
3100
3101
        // Optional Configuration Options
3102
        'port' => $SETTINGS['ldap_port'],
3103
        'use_ssl' => (int) $SETTINGS['ldap_ssl'] === 1 ? true : false,
3104
        'use_tls' => (int) $SETTINGS['ldap_tls'] === 1 ? true : false,
3105
        'version' => 3,
3106
        'timeout' => 5,
3107
        'follow_referrals' => false,
3108
3109
        // Custom LDAP Options
3110
        'options' => [
3111
            // See: http://php.net/ldap_set_option
3112
            LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_HARD,
3113
        ],
3114
    ];
3115
    // Load expected libraries
3116
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Traits/Macroable.php';
3117
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Arr.php';
3118
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/DetectsErrors.php';
3119
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Connection.php';
3120
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/LdapInterface.php';
3121
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Ldap.php';
3122
    $ad = new SplClassLoader('LdapRecord', '../includes/libraries');
3123
    $ad->register();
3124
    $connection = new Connection($config);
3125
    // Connect to LDAP
3126
    try {
3127
        $connection->connect();
3128
    } catch (\LdapRecord\Auth\BindException $e) {
3129
        $error = $e->getDetailedError();
3130
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
3131
        return false;
3132
    }
3133
3134
    // Authenticate user
3135
    try {
3136
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory') {
3137
            $connection->auth()->attempt($login, $password, $stayAuthenticated = true);
3138
        } else {
3139
            $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);
3140
        }
3141
    } catch (\LdapRecord\Auth\BindException $e) {
3142
        $error = $e->getDetailedError();
3143
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
3144
        return false;
3145
    }
3146
3147
    return true;
3148
}
3149
3150
/**
3151
 * Removes from DB all sharekeys of this user
3152
 *
3153
 * @param int $userId User's id
3154
 * @param array   $SETTINGS Teampass settings
3155
 *
3156
 * @return bool
3157
 */
3158
function deleteUserObjetsKeys(int $userId, array $SETTINGS = []): bool
3159
{
3160
    // include librairies & connect to DB
3161
    include_once __DIR__. '/../includes/config/settings.php';
3162
    include_once __DIR__. '/../includes/libraries/Database/Meekrodb/db.class.php';
3163
    if (defined('DB_PASSWD_CLEAR') === false) {
3164
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
3165
    }
3166
    DB::$host = DB_HOST;
3167
    DB::$user = DB_USER;
3168
    DB::$password = DB_PASSWD_CLEAR;
3169
    DB::$dbName = DB_NAME;
3170
    DB::$port = DB_PORT;
3171
    DB::$encoding = DB_ENCODING;
3172
    DB::$ssl = DB_SSL;
3173
    DB::$connect_options = DB_CONNECT_OPTIONS;
3174
    // Remove all item sharekeys items
3175
    DB::delete(
3176
        prefixTable('sharekeys_items'),
3177
        'user_id = %i',
3178
        $userId
3179
    );
3180
    // Remove all item sharekeys files
3181
    DB::delete(
3182
        prefixTable('sharekeys_files'),
3183
        'user_id = %i',
3184
        $userId
3185
    );
3186
    // Remove all item sharekeys fields
3187
    DB::delete(
3188
        prefixTable('sharekeys_fields'),
3189
        'user_id = %i',
3190
        $userId
3191
    );
3192
    // Remove all item sharekeys logs
3193
    DB::delete(
3194
        prefixTable('sharekeys_logs'),
3195
        'user_id = %i',
3196
        $userId
3197
    );
3198
    // Remove all item sharekeys suggestions
3199
    DB::delete(
3200
        prefixTable('sharekeys_suggestions'),
3201
        'user_id = %i',
3202
        $userId
3203
    );
3204
    return false;
3205
}
3206
3207
/**
3208
 * Manage list of timezones   $SETTINGS Teampass settings
3209
 *
3210
 * @return array
3211
 */
3212
function timezone_list()
3213
{
3214
    static $timezones = null;
3215
    if ($timezones === null) {
3216
        $timezones = [];
3217
        $offsets = [];
3218
        $now = new DateTime('now', new DateTimeZone('UTC'));
3219
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
3220
            $now->setTimezone(new DateTimeZone($timezone));
3221
            $offsets[] = $offset = $now->getOffset();
3222
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
3223
        }
3224
3225
        array_multisort($offsets, $timezones);
3226
    }
3227
3228
    return $timezones;
3229
}
3230
3231
/**
3232
 * Provide timezone offset
3233
 *
3234
 * @param int $offset Timezone offset
3235
 *
3236
 * @return string
3237
 */
3238
function format_GMT_offset($offset): string
3239
{
3240
    $hours = intval($offset / 3600);
3241
    $minutes = abs(intval($offset % 3600 / 60));
3242
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
3243
}
3244
3245
/**
3246
 * Provides timezone name
3247
 *
3248
 * @param string $name Timezone name
3249
 *
3250
 * @return string
3251
 */
3252
function format_timezone_name($name): string
3253
{
3254
    $name = str_replace('/', ', ', $name);
3255
    $name = str_replace('_', ' ', $name);
3256
3257
    return str_replace('St ', 'St. ', $name);
3258
}
3259
3260
/**
3261
 * Provides info if user should use MFA based on roles
3262
 *
3263
 * @param string $userRolesIds  User roles ids
3264
 * @param string $mfaRoles      Roles for which MFA is requested
3265
 *
3266
 * @return bool
3267
 */
3268
function mfa_auth_requested_roles(string $userRolesIds, string $mfaRoles): bool
3269
{
3270
    if (empty($mfaRoles) === true) {
3271
        return true;
3272
    }
3273
3274
    $mfaRoles = array_values(json_decode($mfaRoles, true));
3275
    $userRolesIds = array_filter(explode(';', $userRolesIds));
3276
    if (count($mfaRoles) === 0 || count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
3277
        return true;
3278
    }
3279
3280
    return false;
3281
}
3282
3283
/**
3284
 * Permits to clean a string for export purpose
3285
 *
3286
 * @param string $text
3287
 * @param bool $emptyCheckOnly
3288
 * 
3289
 * @return string
3290
 */
3291
function cleanStringForExport(string $text, bool $emptyCheckOnly = false): string
3292
{
3293
    if (is_null($text) === true || empty($text) === true) {
3294
        return '';
3295
    }
3296
    // only expected to check if $text was empty
3297
    elseif ($emptyCheckOnly === true) {
3298
        return $text;
3299
    }
3300
3301
    return strip_tags(
3302
        cleanString(
3303
            html_entity_decode($text, ENT_QUOTES | ENT_XHTML, 'UTF-8'),
3304
            true)
3305
        );
3306
}
3307
3308
/**
3309
 * Permits to check if user ID is valid
3310
 *
3311
 * @param integer $post_user_id
3312
 * @return bool
3313
 */
3314
function isUserIdValid($userId): bool
3315
{
3316
    if (is_null($userId) === false
3317
        && isset($userId) === true
3318
        && empty($userId) === false
3319
    ) {
3320
        return true;
3321
    }
3322
    return false;
3323
}
3324
3325
/**
3326
 * Check if a key exists and if its value equal the one expected
3327
 *
3328
 * @param string $key
3329
 * @param integer|string $value
3330
 * @param array $array
3331
 * 
3332
 * @return boolean
3333
 */
3334
function isKeyExistingAndEqual(
3335
    string $key,
3336
    /*PHP8 - integer|string*/$value,
3337
    array $array
3338
): bool
3339
{
3340
    if (isset($array[$key]) === true
3341
        && (is_int($value) === true ?
3342
            (int) $array[$key] === $value :
3343
            (string) $array[$key] === $value)
3344
    ) {
3345
        return true;
3346
    }
3347
    return false;
3348
}
3349
3350
/**
3351
 * Check if a variable is not set or equal to a value
3352
 *
3353
 * @param string|null $var
3354
 * @param integer|string $value
3355
 * 
3356
 * @return boolean
3357
 */
3358
function isKeyNotSetOrEqual(
3359
    /*PHP8 - string|null*/$var,
3360
    /*PHP8 - integer|string*/$value
3361
): bool
3362
{
3363
    if (isset($var) === false
3364
        || (is_int($value) === true ?
3365
            (int) $var === $value :
3366
            (string) $var === $value)
3367
    ) {
3368
        return true;
3369
    }
3370
    return false;
3371
}
3372
3373
/**
3374
 * Check if a key exists and if its value < to the one expected
3375
 *
3376
 * @param string $key
3377
 * @param integer $value
3378
 * @param array $array
3379
 * 
3380
 * @return boolean
3381
 */
3382
function isKeyExistingAndInferior(string $key, int $value, array $array): bool
3383
{
3384
    if (isset($array[$key]) === true && (int) $array[$key] < $value) {
3385
        return true;
3386
    }
3387
    return false;
3388
}
3389
3390
/**
3391
 * Check if a key exists and if its value > to the one expected
3392
 *
3393
 * @param string $key
3394
 * @param integer $value
3395
 * @param array $array
3396
 * 
3397
 * @return boolean
3398
 */
3399
function isKeyExistingAndSuperior(string $key, int $value, array $array): bool
3400
{
3401
    if (isset($array[$key]) === true && (int) $array[$key] > $value) {
3402
        return true;
3403
    }
3404
    return false;
3405
}
3406
3407
/**
3408
 * Check if values in array are set
3409
 * Return true if all set
3410
 * Return false if one of them is not set
3411
 *
3412
 * @param array $arrayOfValues
3413
 * @return boolean
3414
 */
3415
function isSetArrayOfValues(array $arrayOfValues): bool
3416
{
3417
    foreach($arrayOfValues as $value) {
3418
        if (isset($value) === false) {
3419
            return false;
3420
        }
3421
    }
3422
    return true;
3423
}
3424
3425
/**
3426
 * Check if values in array are set
3427
 * Return true if all set
3428
 * Return false if one of them is not set
3429
 *
3430
 * @param array $arrayOfValues
3431
 * @param integer|string $value
3432
 * @return boolean
3433
 */
3434
function isArrayOfVarsEqualToValue(
3435
    array $arrayOfVars,
3436
    /*PHP8 - integer|string*/$value
3437
) : bool
3438
{
3439
    foreach($arrayOfVars as $variable) {
3440
        if ($variable !== $value) {
3441
            return false;
3442
        }
3443
    }
3444
    return true;
3445
}
3446
3447
/**
3448
 * Checks if at least one variable in array is equal to value
3449
 *
3450
 * @param array $arrayOfValues
3451
 * @param integer|string $value
3452
 * @return boolean
3453
 */
3454
function isOneVarOfArrayEqualToValue(
3455
    array $arrayOfVars,
3456
    /*PHP8 - integer|string*/$value
3457
) : bool
3458
{
3459
    foreach($arrayOfVars as $variable) {
3460
        if ($variable === $value) {
3461
            return true;
3462
        }
3463
    }
3464
    return false;
3465
}
3466
3467
/**
3468
 * Checks is value is null, not set OR empty
3469
 *
3470
 * @param string|int|null $value
3471
 * @return boolean
3472
 */
3473
function isValueSetNullEmpty(/*PHP8 - string|int|null*/ $value) : bool
3474
{
3475
    if (is_null($value) === true || isset($value) === false || empty($value) === true) {
3476
        return true;
3477
    }
3478
    return false;
3479
}
3480
3481
/**
3482
 * Checks if value is set and if empty is equal to passed boolean
3483
 *
3484
 * @param string|int $value
3485
 * @param boolean $boolean
3486
 * @return boolean
3487
 */
3488
function isValueSetEmpty($value, $boolean = true) : bool
3489
{
3490
    if (isset($value) === true && empty($value) === $boolean) {
3491
        return true;
3492
    }
3493
    return false;
3494
}
3495
3496
/**
3497
 * Ensure Complexity is translated
3498
 *
3499
 * @return void
3500
 */
3501
function defineComplexity() : void
3502
{
3503
    if (defined('TP_PW_COMPLEXITY') === false) {
3504
        define(
3505
            'TP_PW_COMPLEXITY',
3506
            [
3507
                TP_PW_STRENGTH_1 => array(TP_PW_STRENGTH_1, langHdl('complex_level1'), 'fas fa-thermometer-empty text-danger'),
3508
                TP_PW_STRENGTH_2 => array(TP_PW_STRENGTH_2, langHdl('complex_level2'), 'fas fa-thermometer-quarter text-warning'),
3509
                TP_PW_STRENGTH_3 => array(TP_PW_STRENGTH_3, langHdl('complex_level3'), 'fas fa-thermometer-half text-warning'),
3510
                TP_PW_STRENGTH_4 => array(TP_PW_STRENGTH_4, langHdl('complex_level4'), 'fas fa-thermometer-three-quarters text-success'),
3511
                TP_PW_STRENGTH_5 => array(TP_PW_STRENGTH_5, langHdl('complex_level5'), 'fas fa-thermometer-full text-success'),
3512
            ]
3513
        );
3514
    }
3515
}
3516
3517
/**
3518
 * Uses Sanitizer to perform data sanitization
3519
 *
3520
 * @param array     $data
3521
 * @param array     $filters
3522
 * @param string    $path
3523
 * @return array
3524
 */
3525
function dataSanitizer(
3526
    array $data,
3527
    array $filters,
3528
    string $path = __DIR__. '/..' // Path to Teampass root
3529
): array
3530
{
3531
    // Load Sanitizer library
3532
    require_once $path . '/includes/libraries/Illuminate/Support/Traits/Macroable.php';
3533
    require_once $path . '/includes/libraries/Illuminate/Support/Str.php';
3534
    require_once $path . '/includes/libraries/Illuminate/Validation/ValidationRuleParser.php';
3535
    require_once $path . '/includes/libraries/Illuminate/Support/Arr.php';
3536
    require_once $path . '/includes/libraries/Elegant/sanitizer/Contracts/Filter.php';
3537
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/Trim.php';
3538
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/Cast.php';
3539
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/EscapeHTML.php';
3540
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/EmptyStringToNull.php';
3541
    require_once $path . '/includes/libraries/Elegant/sanitizer/Sanitizer.php';
3542
3543
    // Sanitize post and get variables
3544
    $sanitizer = new Elegant\sanitizer\Sanitizer($data, $filters);
3545
    return $sanitizer->sanitize();
3546
}
3547
3548
/**
3549
 * Permits to manage the cache tree for a user
3550
 *
3551
 * @param integer $user_id
3552
 * @param string $data
3553
 * @param array $SETTINGS
3554
 * @param string $field_update
3555
 * @return void
3556
 */
3557
function cacheTreeUserHandler(int $user_id, string $data, array $SETTINGS, string $field_update = '')
3558
{
3559
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
3560
    //Connect to DB
3561
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
3562
    if (defined('DB_PASSWD_CLEAR') === false) {
3563
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
3564
    }
3565
    DB::$host = DB_HOST;
3566
    DB::$user = DB_USER;
3567
    DB::$password = DB_PASSWD_CLEAR;
3568
    DB::$dbName = DB_NAME;
3569
    DB::$port = DB_PORT;
3570
    DB::$encoding = DB_ENCODING;
3571
    DB::$ssl = DB_SSL;
3572
    DB::$connect_options = DB_CONNECT_OPTIONS;
3573
3574
    // Exists ?
3575
    $userCacheId = DB::queryfirstrow(
3576
        'SELECT increment_id
3577
        FROM ' . prefixTable('cache_tree') . '
3578
        WHERE user_id = %i',
3579
        $user_id
3580
    );
3581
    
3582
    if (is_null($userCacheId) === true || count($userCacheId) === 0) {
3583
        DB::insert(
3584
            prefixTable('cache_tree'),
3585
            array(
3586
                'data' => $data,
3587
                'timestamp' => time(),
3588
                'user_id' => $user_id,
3589
                'visible_folders' => '',
3590
            )
3591
        );
3592
    } else {
3593
        if (empty($field_update) === true) {
3594
            DB::update(
3595
                prefixTable('cache_tree'),
3596
                [
3597
                    'timestamp' => time(),
3598
                    'data' => $data,
3599
                ],
3600
                'increment_id = %i',
3601
                $userCacheId['increment_id']
3602
            );
3603
        } else {
3604
            DB::update(
3605
                prefixTable('cache_tree'),
3606
                [
3607
                    $field_update => $data,
3608
                ],
3609
                'increment_id = %i',
3610
                $userCacheId['increment_id']
3611
            );
3612
        }
3613
    }
3614
}
3615
3616
/**
3617
 * Permits to calculate a %
3618
 *
3619
 * @param float $nombre
3620
 * @param float $total
3621
 * @param float $pourcentage
3622
 * @return float
3623
 */
3624
function pourcentage(float $nombre, float $total, float $pourcentage): float
3625
{ 
3626
    $resultat = ($nombre/$total) * $pourcentage;
3627
    return round($resultat);
3628
}
3629
3630
/**
3631
 * Load the folders list from the cache
3632
 *
3633
 * @param string $fieldName
3634
 * @param string $sessionName
3635
 * @param boolean $forceRefresh
3636
 * @return array
3637
 */
3638
function loadFoldersListByCache(
3639
    string $fieldName,
3640
    string $sessionName,
3641
    bool $forceRefresh = false
3642
): array
3643
{
3644
    // Case when refresh is EXPECTED / MANDATORY
3645
    if ($forceRefresh === true) {
3646
        return [
3647
            'state' => false,
3648
            'data' => [],
3649
        ];
3650
    }
3651
3652
    // Get last folder update
3653
    $lastFolderChange = DB::queryfirstrow(
3654
        'SELECT valeur FROM ' . prefixTable('misc') . '
3655
        WHERE type = %s AND intitule = %s',
3656
        'timestamp',
3657
        'last_folder_change'
3658
    );
3659
    if (DB::count() === 0) {
3660
        $lastFolderChange['valeur'] = 0;
3661
    }
3662
3663
    // Case when an update in the tree has been done
3664
    // Refresh is then mandatory
3665
    if ((int) $lastFolderChange['valeur'] > (int) (isset($_SESSION['user_tree_last_refresh_timestamp']) === true ? $_SESSION['user_tree_last_refresh_timestamp'] : 0)) {
3666
        return [
3667
            'state' => false,
3668
            'data' => [],
3669
        ];
3670
    }
3671
3672
    // Does this user has the tree structure in session?
3673
    // If yes then use it
3674
    if (count(isset($_SESSION['teampassUser'][$sessionName]) === true ? $_SESSION['teampassUser'][$sessionName] : []) > 0) {
3675
        return [
3676
            'state' => true,
3677
            'data' => json_encode($_SESSION['teampassUser'][$sessionName]),
3678
        ];
3679
    }
3680
3681
    // Does this user has a tree cache
3682
    $userCacheTree = DB::queryfirstrow(
3683
        'SELECT '.$fieldName.'
3684
        FROM ' . prefixTable('cache_tree') . '
3685
        WHERE user_id = %i',
3686
        $_SESSION['user_id']
3687
    );
3688
    if (empty($userCacheTree[$fieldName]) === false && $userCacheTree[$fieldName] !== '[]') {
3689
        return [
3690
            'state' => true,
3691
            'data' => $userCacheTree[$fieldName],
3692
        ];
3693
    }
3694
3695
    return [
3696
        'state' => false,
3697
        'data' => [],
3698
    ];
3699
}
3700
3701
3702
/**
3703
 * Permits to refresh the categories of folders
3704
 *
3705
 * @param array $folderIds
3706
 * @return void
3707
 */
3708
function handleFoldersCategories(
3709
    array $folderIds
3710
)
3711
{
3712
    //load ClassLoader
3713
    include_once __DIR__. '/../sources/SplClassLoader.php';
3714
    
3715
    //Connect to DB
3716
    include_once __DIR__. '/../includes/libraries/Database/Meekrodb/db.class.php';
3717
    if (defined('DB_PASSWD_CLEAR') === false) {
3718
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, []));
3719
    }
3720
    DB::$host = DB_HOST;
3721
    DB::$user = DB_USER;
3722
    DB::$password = DB_PASSWD_CLEAR;
3723
    DB::$dbName = DB_NAME;
3724
    DB::$port = DB_PORT;
3725
    DB::$encoding = DB_ENCODING;
3726
    DB::$ssl = DB_SSL;
3727
    DB::$connect_options = DB_CONNECT_OPTIONS;
3728
3729
    $arr_data = array();
3730
3731
    // force full list of folders
3732
    if (count($folderIds) === 0) {
3733
        $folderIds = DB::queryFirstColumn(
3734
            'SELECT id
3735
            FROM ' . prefixTable('nested_tree') . '
3736
            WHERE personal_folder=%i',
3737
            0
3738
        );
3739
    }
3740
3741
    // Get complexity
3742
    defineComplexity();
3743
3744
    // update
3745
    foreach ($folderIds as $folder) {
3746
        // Do we have Categories
3747
        // get list of associated Categories
3748
        $arrCatList = array();
3749
        $rows_tmp = DB::query(
3750
            'SELECT c.id, c.title, c.level, c.type, c.masked, c.order, c.encrypted_data, c.role_visibility, c.is_mandatory,
3751
            f.id_category AS category_id
3752
            FROM ' . prefixTable('categories_folders') . ' AS f
3753
            INNER JOIN ' . prefixTable('categories') . ' AS c ON (f.id_category = c.parent_id)
3754
            WHERE id_folder=%i',
3755
            $folder
3756
        );
3757
        if (DB::count() > 0) {
3758
            foreach ($rows_tmp as $row) {
3759
                $arrCatList[$row['id']] = array(
3760
                    'id' => $row['id'],
3761
                    'title' => $row['title'],
3762
                    'level' => $row['level'],
3763
                    'type' => $row['type'],
3764
                    'masked' => $row['masked'],
3765
                    'order' => $row['order'],
3766
                    'encrypted_data' => $row['encrypted_data'],
3767
                    'role_visibility' => $row['role_visibility'],
3768
                    'is_mandatory' => $row['is_mandatory'],
3769
                    'category_id' => $row['category_id'],
3770
                );
3771
            }
3772
        }
3773
        $arr_data['categories'] = $arrCatList;
3774
3775
        // Now get complexity
3776
        $valTemp = '';
3777
        $data = DB::queryFirstRow(
3778
            'SELECT valeur
3779
            FROM ' . prefixTable('misc') . '
3780
            WHERE type = %s AND intitule=%i',
3781
            'complex',
3782
            $folder
3783
        );
3784
        if (DB::count() > 0 && empty($data['valeur']) === false) {
3785
            $valTemp = array(
3786
                'value' => $data['valeur'],
3787
                'text' => TP_PW_COMPLEXITY[$data['valeur']][1],
3788
            );
3789
        }
3790
        $arr_data['complexity'] = $valTemp;
3791
3792
        // Now get Roles
3793
        $valTemp = '';
3794
        $rows_tmp = DB::query(
3795
            'SELECT t.title
3796
            FROM ' . prefixTable('roles_values') . ' as v
3797
            INNER JOIN ' . prefixTable('roles_title') . ' as t ON (v.role_id = t.id)
3798
            WHERE v.folder_id = %i
3799
            GROUP BY title',
3800
            $folder
3801
        );
3802
        foreach ($rows_tmp as $record) {
3803
            $valTemp .= (empty($valTemp) === true ? '' : ' - ') . $record['title'];
3804
        }
3805
        $arr_data['visibilityRoles'] = $valTemp;
3806
3807
        // now save in DB
3808
        DB::update(
3809
            prefixTable('nested_tree'),
3810
            array(
3811
                'categories' => json_encode($arr_data),
3812
            ),
3813
            'id = %i',
3814
            $folder
3815
        );
3816
    }
3817
}
3818
3819
/**
3820
 * List all users that have specific roles
3821
 *
3822
 * @param array $roles
3823
 * @return array
3824
 */
3825
function getUsersWithRoles(
3826
    array $roles
3827
): array
3828
{
3829
    $arrUsers = array();
3830
3831
    foreach ($roles as $role) {
3832
        // loop on users and check if user has this role
3833
        $rows = DB::query(
3834
            'SELECT id, fonction_id
3835
            FROM ' . prefixTable('users') . '
3836
            WHERE id != %i AND admin = 0 AND fonction_id IS NOT NULL AND fonction_id != ""',
3837
            $_SESSION['user_id']
3838
        );
3839
        foreach ($rows as $user) {
3840
            $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

3840
            $userRoles = explode(';', /** @scrutinizer ignore-type */ is_null($user['fonction_id']) === false && empty($user['fonction_id']) === false ? $user['fonction_id'] : []);
Loading history...
3841
            if (in_array($role, $userRoles, true) === true) {
3842
                array_push($arrUsers, $user['id']);
3843
            }
3844
        }
3845
    }
3846
3847
    return $arrUsers;
3848
}
3849
3850
// #3476 - check if function str_contains exists (using PHP 8.0.0 or h)
3851
// else define it
3852
if (!function_exists('str_contains')) {
3853
    function str_contains($haystack, $needle) {
3854
        return $needle !== '' && mb_strpos($haystack, $needle) !== false;
3855
    }
3856
}
3857
3858
/**
3859
 * Get all users informations
3860
 *
3861
 * @param integer $userId
3862
 * @return array
3863
 */
3864
function getFullUserInfos(
3865
    int $userId
3866
): array
3867
{
3868
    if (empty($userId) === true) {
3869
        return array();
3870
    }
3871
3872
    $val = DB::queryfirstrow(
3873
        'SELECT *
3874
        FROM ' . prefixTable('users') . '
3875
        WHERE id = %i',
3876
        $userId
3877
    );
3878
3879
    return $val;
3880
}
3881
3882
/**
3883
 * Is required an upgrade
3884
 *
3885
 * @return boolean
3886
 */
3887
function upgradeRequired(): bool
3888
{
3889
    // Get settings.php
3890
    include_once __DIR__. '/../includes/config/settings.php';
3891
3892
    // Get timestamp in DB
3893
    $val = DB::queryfirstrow(
3894
        'SELECT valeur
3895
        FROM ' . prefixTable('misc') . '
3896
        WHERE type = %s AND intitule = %s',
3897
        'admin',
3898
        'upgrade_timestamp'
3899
    );
3900
    
3901
    // if not exists then error
3902
    if (is_null($val) === true || count($val) === 0 || defined('UPGRADE_MIN_DATE') === false) return true;
3903
3904
    // if empty or too old then error
3905
    if (empty($val['valeur']) === true || (int) $val['valeur'] < (int) UPGRADE_MIN_DATE) {
3906
        return true;
3907
    }
3908
3909
    return false;
3910
}
3911
3912
/**
3913
 * Permits to change the user keys on his demand
3914
 *
3915
 * @param integer $userId
3916
 * @param string $passwordClear
3917
 * @param string $encryptionKey
3918
 * @param boolean $deleteExistingKeys
3919
 * @param boolean $sendEmailToUser
3920
 * @param boolean $encryptWithUserPassword
3921
 * @param boolean $encryptWithUserPassword
3922
 * @param integer $nbItemsToTreat
3923
 * @return string
3924
 */
3925
function handleUserKeys(
3926
    int $userId,
3927
    string $passwordClear,
3928
    string $encryptionKey = '',
3929
    bool $deleteExistingKeys = false,
3930
    bool $sendEmailToUser = true,
3931
    bool $encryptWithUserPassword = false,
3932
    bool $generate_user_new_password = false,
3933
    int $nbItemsToTreat,
3934
    string $emailBody = ''
3935
): string
3936
{
3937
3938
    // prepapre background tasks for item keys generation        
3939
    $userTP = DB::queryFirstRow(
3940
        'SELECT pw, public_key, private_key
3941
        FROM ' . prefixTable('users') . '
3942
        WHERE id = %i',
3943
        TP_USER_ID
3944
    );
3945
    if (DB::count() > 0) {
3946
        // Do we need to generate new user password
3947
        if ($generate_user_new_password === true) {
3948
            // Generate a new password
3949
            $passwordClear = GenerateCryptKey(20, false, true, true, false, true);
3950
3951
            // Hash the new password
3952
            $pwdlib = new SplClassLoader('PasswordLib', '../includes/libraries');
3953
            $pwdlib->register();
3954
            $pwdlib = new PasswordLib\PasswordLib();
3955
            $hashedPassword = $pwdlib->createPasswordHash($passwordClear);
3956
            if ($pwdlib->verifyPasswordHash($passwordClear, $hashedPassword) === false) {
3957
                return prepareExchangedData(
3958
                    __DIR__.'/..',
3959
                    array(
3960
                        'error' => true,
3961
                        'message' => langHdl('pw_hash_not_correct'),
3962
                    ),
3963
                    'encode'
3964
                );
3965
            }
3966
3967
            // Generate new keys
3968
            $userKeys = generateUserKeys($passwordClear);
3969
3970
            // Save in DB
3971
            DB::update(
3972
                prefixTable('users'),
3973
                array(
3974
                    'pw' => $hashedPassword,
3975
                    'public_key' => $userKeys['public_key'],
3976
                    'private_key' => $userKeys['private_key'],
3977
                ),
3978
                'id=%i',
3979
                $userId
3980
            );
3981
        }
3982
3983
        // Manage empty encryption key
3984
        // Let's take the user's password if asked and if no encryption key provided
3985
        $encryptionKey = $encryptWithUserPassword === true && empty($encryptionKey) === true ? $passwordClear : $encryptionKey;
3986
3987
        // Create process
3988
        DB::insert(
3989
            prefixTable('processes'),
3990
            array(
3991
                'created_at' => time(),
3992
                'process_type' => 'create_user_keys',
3993
                'arguments' => json_encode([
3994
                    'new_user_id' => (int) $userId,
3995
                    'new_user_pwd' => cryption($passwordClear, '','encrypt')['string'],
3996
                    'new_user_code' => cryption(empty($encryptionKey) === true ? uniqidReal(20) : $encryptionKey, '','encrypt')['string'],
3997
                    'owner_id' => (int) TP_USER_ID,
3998
                    'creator_pwd' => $userTP['pw'],
3999
                    'send_email' => $sendEmailToUser === true ? 1 : 0,
4000
                    'otp_provided_new_value' => 1,
4001
                    'email_body' => empty($emailBody) === true ? '' : langHdl($emailBody),
4002
                ]),
4003
                'updated_at' => '',
4004
                'finished_at' => '',
4005
                'output' => '',
4006
            )
4007
        );
4008
        $processId = DB::insertId();
4009
4010
        // Delete existing keys
4011
        if ($deleteExistingKeys === true) {
4012
            deleteUserObjetsKeys(
4013
                (int) $userId,
4014
            );
4015
        }
4016
4017
        // Create tasks
4018
        DB::insert(
4019
            prefixTable('processes_tasks'),
4020
            array(
4021
                'process_id' => $processId,
4022
                'created_at' => time(),
4023
                'task' => json_encode([
4024
                    'step' => 'step0',
4025
                    'index' => 0,
4026
                    'nb' => $nbItemsToTreat,
4027
                ]),
4028
            )
4029
        );
4030
4031
        DB::insert(
4032
            prefixTable('processes_tasks'),
4033
            array(
4034
                'process_id' => $processId,
4035
                'created_at' => time(),
4036
                'task' => json_encode([
4037
                    'step' => 'step1',
4038
                    'index' => 0,
4039
                    'nb' => $nbItemsToTreat,
4040
                ]),
4041
            )
4042
        );
4043
4044
        DB::insert(
4045
            prefixTable('processes_tasks'),
4046
            array(
4047
                'process_id' => $processId,
4048
                'created_at' => time(),
4049
                'task' => json_encode([
4050
                    'step' => 'step2',
4051
                    'index' => 0,
4052
                    'nb' => $nbItemsToTreat,
4053
                ]),
4054
            )
4055
        );
4056
4057
        DB::insert(
4058
            prefixTable('processes_tasks'),
4059
            array(
4060
                'process_id' => $processId,
4061
                'created_at' => time(),
4062
                'task' => json_encode([
4063
                    'step' => 'step3',
4064
                    'index' => 0,
4065
                    'nb' => $nbItemsToTreat,
4066
                ]),
4067
            )
4068
        );
4069
4070
        DB::insert(
4071
            prefixTable('processes_tasks'),
4072
            array(
4073
                'process_id' => $processId,
4074
                'created_at' => time(),
4075
                'task' => json_encode([
4076
                    'step' => 'step4',
4077
                    'index' => 0,
4078
                    'nb' => $nbItemsToTreat,
4079
                ]),
4080
            )
4081
        );
4082
4083
        DB::insert(
4084
            prefixTable('processes_tasks'),
4085
            array(
4086
                'process_id' => $processId,
4087
                'created_at' => time(),
4088
                'task' => json_encode([
4089
                    'step' => 'step5',
4090
                    'index' => 0,
4091
                    'nb' => $nbItemsToTreat,
4092
                ]),
4093
            )
4094
        );
4095
4096
        DB::insert(
4097
            prefixTable('processes_tasks'),
4098
            array(
4099
                'process_id' => $processId,
4100
                'created_at' => time(),
4101
                'task' => json_encode([
4102
                    'step' => 'step6',
4103
                    'index' => 0,
4104
                    'nb' => $nbItemsToTreat,
4105
                ]),
4106
            )
4107
        );
4108
4109
        // update user's new status
4110
        DB::update(
4111
            prefixTable('users'),
4112
            [
4113
                'is_ready_for_usage' => 0,
4114
                'otp_provided' => 1,
4115
                'ongoing_process_id' => $processId,
4116
                'special' => 'generate-keys',
4117
            ],
4118
            'id=%i',
4119
            $userId
4120
        );
4121
    }
4122
4123
    return prepareExchangedData(
4124
        __DIR__.'/..',
4125
        array(
4126
            'error' => false,
4127
            'message' => '',
4128
        ),
4129
        'encode'
4130
    );
4131
}
4132
4133
/**
4134
 * Permeits to check the consistency of date versus columns definition
4135
 *
4136
 * @param string $table
4137
 * @param array $dataFields
4138
 * @return array
4139
 */
4140
function validateDataFields(
4141
    string $table,
4142
    array $dataFields,
4143
): array
4144
{
4145
    // Get table structure
4146
    $result = DB::query("DESCRIBE %l", $table);
4147
4148
    foreach ($result as $row) {
4149
        $field = $row['Field'];
4150
        $type = $row['Type'];
4151
        $length = preg_replace('/[^0-9]/', '', $type);
4152
        $type = explode('(', $type)[0];
4153
4154
        if (array_key_exists($field, $dataFields) === true && in_array($type, ['int', 'tinyint', 'smallint', 'text', 'bigint', 'float', 'double', 'varchar']) === true) {
4155
            if (strlen((string) $dataFields[$field]) > $length) {
4156
                return [
4157
                    'state' => false,
4158
                    'message' => 'Field '.strtoupper($field).' exceeds maximum length of '.$length,
4159
                ];
4160
            }
4161
        }
4162
    }
4163
    
4164
    return [
4165
        'state' => true,
4166
        'message' => '',
4167
    ];
4168
}