Passed
Push — master ( 97a95f...b0e088 )
by Nils
10:41
created

sendEmail()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 51
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 11
Bugs 0 Features 0
Metric Value
cc 6
eloc 23
nc 4
nop 7
dl 0
loc 51
rs 8.9297
c 11
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This library is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
 * ---
12
 *
13
 * @project   Teampass
14
 * @version   3.0.0.22
15
 * @file      main.functions.php
16
 * ---
17
 *
18
 * @author    Nils Laumaillé ([email protected])
19
 *
20
 * @copyright 2009-2022 Teampass.net
21
 *
22
 * @license   https://spdx.org/licenses/GPL-3.0-only.html#licenseText GPL-3.0
23
 * ---
24
 *
25
 * @see       https://www.teampass.net
26
 */
27
28
use LdapRecord\Connection;
29
use ForceUTF8\Encoding;
30
31
if (isset($_SESSION['CPM']) === false || (int) $_SESSION['CPM'] !== 1) {
32
    //die('Hacking attempt...');
33
}
34
35
// Load config if $SETTINGS not defined
36
if (isset($SETTINGS['cpassman_dir']) === false || empty($SETTINGS['cpassman_dir']) === true) {
37
    include_once __DIR__ . '/../includes/config/tp.config.php';
38
}
39
40
header('Content-type: text/html; charset=utf-8');
41
header('Cache-Control: no-cache, must-revalidate');
42
/**
43
 * Convert language code to string.
44
 *
45
 * @param string $string String to get
46
 */
47
function langHdl(string $string): string
48
{
49
    if (empty($string) === true) {
50
        // Manage error
51
        return 'ERROR in language strings!';
52
    }
53
54
    // Load superglobal
55
    if (file_exists(__DIR__.'/../includes/libraries/protect/SuperGlobal/SuperGlobal.php')) {
56
        include_once __DIR__.'/../includes/libraries/protect/SuperGlobal/SuperGlobal.php';
57
    } elseif (file_exists(__DIR__.'/includes/libraries/protect/SuperGlobal/SuperGlobal.php')) {
58
        include_once __DIR__.'/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
59
    } elseif (file_exists(__DIR__.'/../../includes/libraries/protect/SuperGlobal/SuperGlobal.php')) {
60
        include_once __DIR__.'/../../includes/libraries/protect/SuperGlobal/SuperGlobal.php';
61
    } else {
62
        throw new Exception("Error file '/includes/libraries/protect/SuperGlobal/SuperGlobal.php' not exists", 1);
63
    }
64
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
65
    // Get language string
66
    $session_language = $superGlobal->get(trim($string), 'SESSION', 'lang');
67
    if (is_null($session_language) === true) {
68
        /* 
69
            Load the English version to $_SESSION so we don't 
70
            return bad JSON (multiple includes add BOM characters to the json returned 
71
            which makes jquery unhappy on the UI, especially on the log page)
72
            and improve performance by avoiding to include the file for every missing strings.
73
        */
74
        if (isset($_SESSION['teampass']) === false || isset($_SESSION['teampass']['en_lang'][trim($string)]) === false) {
75
            $_SESSION['teampass']['en_lang'] = include_once __DIR__. '/../includes/language/english.php';
76
            $session_language = isset($_SESSION['teampass']['en_lang'][trim($string)]) === false ? '' : $_SESSION['teampass']['en_lang'][trim($string)];
77
        } else {
78
            $session_language = $_SESSION['teampass']['en_lang'][trim($string)];
79
        }
80
    }
81
    // If after all this, we still don't have the string even in english (especially with old logs), return the language code
82
    if (empty($session_language) === true) {
83
        return trim($string);
84
    }
85
    return addslashes($session_language);
86
}
87
88
/**
89
 * genHash().
90
 *
91
 * Generate a hash for user login
92
 *
93
 * @param string $password What password
94
 * @param string $cost     What cost
95
 *
96
 * @return string|void
97
 */
98
function bCrypt(
99
    string $password,
100
    string $cost
101
): ?string
102
{
103
    $salt = sprintf('$2y$%02d$', $cost);
104
    if (function_exists('openssl_random_pseudo_bytes')) {
105
        $salt .= bin2hex(openssl_random_pseudo_bytes(11));
106
    } else {
107
        $chars = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
108
        for ($i = 0; $i < 22; ++$i) {
109
            $salt .= $chars[mt_rand(0, 63)];
110
        }
111
    }
112
113
    return crypt($password, $salt);
114
}
115
116
/**
117
 * Defuse cryption function.
118
 *
119
 * @param string $message   what to de/crypt
120
 * @param string $ascii_key key to use
121
 * @param string $type      operation to perform
122
 * @param array  $SETTINGS  Teampass settings
123
 *
124
 * @return array
125
 */
126
function cryption(string $message, string $ascii_key, string $type, ?array $SETTINGS = []): array
127
{
128
    $ascii_key = empty($ascii_key) === true ? file_get_contents(SECUREPATH . '/teampass-seckey.txt') : $ascii_key;
129
    $err = false;
130
    
131
    $path = __DIR__.'/../includes/libraries/Encryption/Encryption/';
132
133
    include_once $path . 'Exception/CryptoException.php';
134
    include_once $path . 'Exception/BadFormatException.php';
135
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
136
    include_once $path . 'Exception/IOException.php';
137
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
138
    include_once $path . 'Crypto.php';
139
    include_once $path . 'Encoding.php';
140
    include_once $path . 'DerivedKeys.php';
141
    include_once $path . 'Key.php';
142
    include_once $path . 'KeyOrPassword.php';
143
    include_once $path . 'File.php';
144
    include_once $path . 'RuntimeTests.php';
145
    include_once $path . 'KeyProtectedByPassword.php';
146
    include_once $path . 'Core.php';
147
    
148
    // convert KEY
149
    $key = \Defuse\Crypto\Key::loadFromAsciiSafeString($ascii_key);
150
    try {
151
        if ($type === 'encrypt') {
152
            $text = \Defuse\Crypto\Crypto::encrypt($message, $key);
153
        } elseif ($type === 'decrypt') {
154
            $text = \Defuse\Crypto\Crypto::decrypt($message, $key);
155
        }
156
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
157
        $err = 'an attack! either the wrong key was loaded, or the ciphertext has changed since it was created either corrupted in the database or intentionally modified by someone trying to carry out an attack.';
158
    } catch (Defuse\Crypto\Exception\BadFormatException $ex) {
159
        $err = $ex;
160
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
161
        $err = $ex;
162
    } catch (Defuse\Crypto\Exception\CryptoException $ex) {
163
        $err = $ex;
164
    } catch (Defuse\Crypto\Exception\IOException $ex) {
165
        $err = $ex;
166
    }
167
    //echo \Defuse\Crypto\Crypto::decrypt($message, $key).' ## ';
168
169
    return [
170
        'string' => $text ?? '',
171
        'error' => $err,
172
    ];
173
}
174
175
/**
176
 * Generating a defuse key.
177
 *
178
 * @return string
179
 */
180
function defuse_generate_key()
181
{
182
    // load PhpEncryption library
183
    if (file_exists('../includes/config/tp.config.php') === true) {
184
        $path = '../includes/libraries/Encryption/Encryption/';
185
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
186
        $path = './includes/libraries/Encryption/Encryption/';
187
    } else {
188
        $path = '../includes/libraries/Encryption/Encryption/';
189
    }
190
191
    include_once $path . 'Exception/CryptoException.php';
192
    include_once $path . 'Exception/BadFormatException.php';
193
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
194
    include_once $path . 'Exception/IOException.php';
195
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
196
    include_once $path . 'Crypto.php';
197
    include_once $path . 'Encoding.php';
198
    include_once $path . 'DerivedKeys.php';
199
    include_once $path . 'Key.php';
200
    include_once $path . 'KeyOrPassword.php';
201
    include_once $path . 'File.php';
202
    include_once $path . 'RuntimeTests.php';
203
    include_once $path . 'KeyProtectedByPassword.php';
204
    include_once $path . 'Core.php';
205
206
    $key = \Defuse\Crypto\Key::createNewRandomKey();
207
    $key = $key->saveToAsciiSafeString();
208
    return $key;
209
}
210
211
/**
212
 * Generate a Defuse personal key.
213
 *
214
 * @param string $psk psk used
215
 *
216
 * @return string
217
 */
218
function defuse_generate_personal_key(string $psk): string
219
{
220
    // load PhpEncryption library
221
    if (file_exists('../includes/config/tp.config.php') === true) {
222
        $path = '../includes/libraries/Encryption/Encryption/';
223
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
224
        $path = './includes/libraries/Encryption/Encryption/';
225
    } else {
226
        $path = '../includes/libraries/Encryption/Encryption/';
227
    }
228
229
    include_once $path . 'Exception/CryptoException.php';
230
    include_once $path . 'Exception/BadFormatException.php';
231
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
232
    include_once $path . 'Exception/IOException.php';
233
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
234
    include_once $path . 'Crypto.php';
235
    include_once $path . 'Encoding.php';
236
    include_once $path . 'DerivedKeys.php';
237
    include_once $path . 'Key.php';
238
    include_once $path . 'KeyOrPassword.php';
239
    include_once $path . 'File.php';
240
    include_once $path . 'RuntimeTests.php';
241
    include_once $path . 'KeyProtectedByPassword.php';
242
    include_once $path . 'Core.php';
243
    
244
    $protected_key = \Defuse\Crypto\KeyProtectedByPassword::createRandomPasswordProtectedKey($psk);
245
    return $protected_key->saveToAsciiSafeString(); // save this in user table
246
}
247
248
/**
249
 * Validate persoanl key with defuse.
250
 *
251
 * @param string $psk                   the user's psk
252
 * @param string $protected_key_encoded special key
253
 *
254
 * @return string
255
 */
256
function defuse_validate_personal_key(string $psk, string $protected_key_encoded): string
257
{
258
    // load PhpEncryption library
259
    if (file_exists('../includes/config/tp.config.php') === true) {
260
        $path = '../includes/libraries/Encryption/Encryption/';
261
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
262
        $path = './includes/libraries/Encryption/Encryption/';
263
    } else {
264
        $path = '../includes/libraries/Encryption/Encryption/';
265
    }
266
267
    include_once $path . 'Exception/CryptoException.php';
268
    include_once $path . 'Exception/BadFormatException.php';
269
    include_once $path . 'Exception/EnvironmentIsBrokenException.php';
270
    include_once $path . 'Exception/IOException.php';
271
    include_once $path . 'Exception/WrongKeyOrModifiedCiphertextException.php';
272
    include_once $path . 'Crypto.php';
273
    include_once $path . 'Encoding.php';
274
    include_once $path . 'DerivedKeys.php';
275
    include_once $path . 'Key.php';
276
    include_once $path . 'KeyOrPassword.php';
277
    include_once $path . 'File.php';
278
    include_once $path . 'RuntimeTests.php';
279
    include_once $path . 'KeyProtectedByPassword.php';
280
    include_once $path . 'Core.php';
281
282
    try {
283
        $protected_key_encoded = \Defuse\Crypto\KeyProtectedByPassword::loadFromAsciiSafeString($protected_key_encoded);
284
        $user_key = $protected_key_encoded->unlockKey($psk);
285
        $user_key_encoded = $user_key->saveToAsciiSafeString();
286
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
287
        return 'Error - Major issue as the encryption is broken.';
288
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
289
        return 'Error - The saltkey is not the correct one.';
290
    }
291
292
    return $user_key_encoded;
293
    // store it in session once user has entered his psk
294
}
295
296
/**
297
 * Decrypt a defuse string if encrypted.
298
 *
299
 * @param string $value Encrypted string
300
 *
301
 * @return string Decrypted string
302
 */
303
function defuseReturnDecrypted(string $value, $SETTINGS): string
304
{
305
    if (substr($value, 0, 3) === 'def') {
306
        $value = cryption($value, '', 'decrypt', $SETTINGS)['string'];
307
    }
308
309
    return $value;
310
}
311
312
/**
313
 * Trims a string depending on a specific string.
314
 *
315
 * @param string|array $chaine  what to trim
316
 * @param string       $element trim on what
317
 *
318
 * @return string
319
 */
320
function trimElement($chaine, string $element): string
321
{
322
    if (! empty($chaine)) {
323
        if (is_array($chaine) === true) {
324
            $chaine = implode(';', $chaine);
325
        }
326
        $chaine = trim($chaine);
327
        if (substr($chaine, 0, 1) === $element) {
328
            $chaine = substr($chaine, 1);
329
        }
330
        if (substr($chaine, strlen($chaine) - 1, 1) === $element) {
331
            $chaine = substr($chaine, 0, strlen($chaine) - 1);
332
        }
333
    }
334
335
    return $chaine;
336
}
337
338
/**
339
 * Permits to suppress all "special" characters from string.
340
 *
341
 * @param string $string  what to clean
342
 * @param bool   $special use of special chars?
343
 *
344
 * @return string
345
 */
346
function cleanString(string $string, bool $special = false): string
347
{
348
    // Create temporary table for special characters escape
349
    $tabSpecialChar = [];
350
    for ($i = 0; $i <= 31; ++$i) {
351
        $tabSpecialChar[] = chr($i);
352
    }
353
    array_push($tabSpecialChar, '<br />');
354
    if ((int) $special === 1) {
355
        $tabSpecialChar = array_merge($tabSpecialChar, ['</li>', '<ul>', '<ol>']);
356
    }
357
358
    return str_replace($tabSpecialChar, "\n", $string);
359
}
360
361
/**
362
 * Erro manager for DB.
363
 *
364
 * @param array $params output from query
365
 *
366
 * @return void
367
 */
368
function db_error_handler(array $params): void
369
{
370
    echo 'Error: ' . $params['error'] . "<br>\n";
371
    echo 'Query: ' . $params['query'] . "<br>\n";
372
    throw new Exception('Error - Query', 1);
373
}
374
375
/**
376
 * Identify user's rights
377
 *
378
 * @param string|array $groupesVisiblesUser  [description]
379
 * @param string|array $groupesInterditsUser [description]
380
 * @param string       $isAdmin              [description]
381
 * @param string       $idFonctions          [description]
382
 *
383
 * @return bool
384
 */
385
function identifyUserRights(
386
    $groupesVisiblesUser,
387
    $groupesInterditsUser,
388
    $isAdmin,
389
    $idFonctions,
390
    $SETTINGS
391
) {
392
    //load ClassLoader
393
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
394
    // Load superglobal
395
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
396
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
397
    //Connect to DB
398
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
399
    if (defined('DB_PASSWD_CLEAR') === false) {
400
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
401
    }
402
    DB::$host = DB_HOST;
403
    DB::$user = DB_USER;
404
    DB::$password = DB_PASSWD_CLEAR;
405
    DB::$dbName = DB_NAME;
406
    DB::$port = DB_PORT;
407
    DB::$encoding = DB_ENCODING;
408
    DB::$ssl = DB_SSL;
409
    DB::$connect_options = DB_CONNECT_OPTIONS;
410
    //Build tree
411
    $tree = new SplClassLoader('Tree\NestedTree', $SETTINGS['cpassman_dir'] . '/includes/libraries');
412
    $tree->register();
413
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
414
415
    // Check if user is ADMINISTRATOR    
416
    (int) $isAdmin === 1 ?
417
        identAdmin(
418
            $idFonctions,
419
            $SETTINGS, /** @scrutinizer ignore-type */
420
            $tree
421
        )
422
        :
423
        identUser(
424
            $groupesVisiblesUser,
425
            $groupesInterditsUser,
426
            $idFonctions,
427
            $SETTINGS, /** @scrutinizer ignore-type */
428
            $tree
429
        );
430
431
    // update user's timestamp
432
    DB::update(
433
        prefixTable('users'),
434
        [
435
            'timestamp' => time(),
436
        ],
437
        'id=%i',
438
        $superGlobal->get('user_id', 'SESSION')
439
    );
440
441
    return true;
442
}
443
444
/**
445
 * Identify administrator.
446
 *
447
 * @param string $idFonctions Roles of user
448
 * @param array  $SETTINGS    Teampass settings
449
 * @param array  $tree        Tree of folders
450
 *
451
 * @return bool
452
 */
453
function identAdmin($idFonctions, $SETTINGS, $tree)
454
{
455
    // Load superglobal
456
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
457
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
458
    // Init
459
    $groupesVisibles = [];
460
    $superGlobal->put('personal_folders', [], 'SESSION');
461
    $superGlobal->put('groupes_visibles', [], 'SESSION');
462
    $superGlobal->put('no_access_folders', [], 'SESSION');
463
    $superGlobal->put('personal_visible_groups', [], 'SESSION');
464
    $superGlobal->put('read_only_folders', [], 'SESSION');
465
    $superGlobal->put('list_restricted_folders_for_items', [], 'SESSION');
466
    $superGlobal->put('list_folders_editable_by_role', [], 'SESSION');
467
    $superGlobal->put('list_folders_limited', [], 'SESSION');
468
    $superGlobal->put('no_access_folders', [], 'SESSION');
469
    $superGlobal->put('forbiden_pfs', [], 'SESSION');
470
    // Get superglobals
471
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
472
    $globalsVisibleFolders = $superGlobal->get('groupes_visibles', 'SESSION');
473
    $globalsPersonalVisibleFolders = $superGlobal->get('personal_visible_groups', 'SESSION');
474
    // Get list of Folders
475
    $rows = DB::query('SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i', 0);
476
    foreach ($rows as $record) {
477
        array_push($groupesVisibles, $record['id']);
478
    }
479
    $superGlobal->put('groupes_visibles', $groupesVisibles, 'SESSION');
480
    $superGlobal->put('all_non_personal_folders', $groupesVisibles, 'SESSION');
481
    // Exclude all PF
482
    $where = new WhereClause('and');
483
    // create a WHERE statement of pieces joined by ANDs
484
    $where->add('personal_folder=%i', 1);
485
    if (
486
        isset($SETTINGS['enable_pf_feature']) === true
487
        && (int) $SETTINGS['enable_pf_feature'] === 1
488
    ) {
489
        $where->add('title=%s', $globalsUserId);
490
        $where->negateLast();
491
    }
492
    // Get ID of personal folder
493
    $persfld = DB::queryfirstrow(
494
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE title = %s',
495
        $globalsUserId
496
    );
497
    if (empty($persfld['id']) === false) {
498
        if (in_array($persfld['id'], $globalsVisibleFolders) === false) {
499
            array_push($globalsVisibleFolders, $persfld['id']);
500
            array_push($globalsPersonalVisibleFolders, $persfld['id']);
501
            // get all descendants
502
            $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
503
            $tree->rebuild();
504
            $tst = $tree->getDescendants($persfld['id']);
505
            foreach ($tst as $t) {
506
                array_push($globalsVisibleFolders, $t->id);
507
                array_push($globalsPersonalVisibleFolders, $t->id);
508
            }
509
        }
510
    }
511
512
    // get complete list of ROLES
513
    $tmp = explode(';', $idFonctions);
514
    $rows = DB::query(
515
        'SELECT * FROM ' . prefixTable('roles_title') . '
516
        ORDER BY title ASC'
517
    );
518
    foreach ($rows as $record) {
519
        if (! empty($record['id']) && ! in_array($record['id'], $tmp)) {
520
            array_push($tmp, $record['id']);
521
        }
522
    }
523
    $superGlobal->put('fonction_id', implode(';', $tmp), 'SESSION');
524
    $superGlobal->put('is_admin', 1, 'SESSION');
525
    // Check if admin has created Folders and Roles
526
    DB::query('SELECT * FROM ' . prefixTable('nested_tree') . '');
527
    $superGlobal->put('nb_folders', DB::count(), 'SESSION');
528
    DB::query('SELECT * FROM ' . prefixTable('roles_title'));
529
    $superGlobal->put('nb_roles', DB::count(), 'SESSION');
530
531
    return true;
532
}
533
534
/**
535
 * Permits to convert an element to array.
536
 *
537
 * @param string|array $element Any value to be returned as array
538
 *
539
 * @return array
540
 */
541
function convertToArray($element): array
542
{
543
    if (is_string($element) === true) {
544
        if (empty($element) === true) {
545
            return [];
546
        }
547
        return explode(
548
            ';',
549
            trimElement($element, ';')
550
        );
551
    }
552
    return $element;
553
}
554
555
/**
556
 * Defines the rights the user has.
557
 *
558
 * @param string|array $allowedFolders  Allowed folders
559
 * @param string|array $noAccessFolders Not allowed folders
560
 * @param string|array $userRoles       Roles of user
561
 * @param array        $SETTINGS        Teampass settings
562
 * @param object       $tree            Tree of folders
563
 * 
564
 * @return bool
565
 */
566
function identUser(
567
    $allowedFolders,
568
    $noAccessFolders,
569
    $userRoles,
570
    array $SETTINGS,
571
    object $tree
572
) {
573
    // Load superglobal
574
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
575
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
576
    // Init
577
    $superGlobal->put('groupes_visibles', [], 'SESSION');
578
    $superGlobal->put('personal_folders', [], 'SESSION');
579
    $superGlobal->put('no_access_folders', [], 'SESSION');
580
    $superGlobal->put('personal_visible_groups', [], 'SESSION');
581
    $superGlobal->put('read_only_folders', [], 'SESSION');
582
    $superGlobal->put('fonction_id', $userRoles, 'SESSION');
583
    $superGlobal->put('is_admin', 0, 'SESSION');
584
    // init
585
    $personalFolders = [];
586
    $readOnlyFolders = [];
587
    $noAccessPersonalFolders = [];
588
    $restrictedFoldersForItems = [];
589
    $foldersLimited = [];
590
    $foldersLimitedFull = [];
591
    $allowedFoldersByRoles = [];
592
    // Get superglobals
593
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
594
    $globalsPersonalFolders = $superGlobal->get('personal_folder', 'SESSION');
595
    // Ensure consistency in array format
596
    $noAccessFolders = convertToArray($noAccessFolders);
597
    $userRoles = convertToArray($userRoles);
598
    $allowedFolders = convertToArray($allowedFolders);
599
600
    // Get list of folders depending on Roles
601
    $arrays = identUserGetFoldersFromRoles(
602
        $userRoles,
603
        $allowedFoldersByRoles,
604
        $readOnlyFolders,
605
        $allowedFolders
606
    );
607
    $allowedFoldersByRoles = $arrays['allowedFoldersByRoles'];
608
    $readOnlyFolders = $arrays['readOnlyFolders'];
609
610
    // Does this user is allowed to see other items
611
    $inc = 0;
612
    $rows = DB::query(
613
        'SELECT id, id_tree FROM ' . prefixTable('items') . '
614
            WHERE restricted_to LIKE %ss AND inactif = %s'.
615
            (count($allowedFolders) > 0 ? ' AND id_tree NOT IN ('.implode(',', $allowedFolders).')' : ''),
616
        $globalsUserId . ';',
617
        '0'
618
    );
619
    foreach ($rows as $record) {
620
        // Exclude restriction on item if folder is fully accessible
621
        //if (in_array($record['id_tree'], $allowedFolders) === false) {
622
            $restrictedFoldersForItems[$record['id_tree']][$inc] = $record['id'];
623
            ++$inc;
624
        //}
625
    }
626
627
    // Check for the users roles if some specific rights exist on items
628
    $rows = DB::query(
629
        'SELECT i.id_tree, r.item_id
630
        FROM ' . prefixTable('items') . ' as i
631
        INNER JOIN ' . prefixTable('restriction_to_roles') . ' as r ON (r.item_id=i.id)
632
        WHERE r.role_id IN %li AND i.id_tree <> ""
633
        ORDER BY i.id_tree ASC',
634
        $userRoles
635
    );
636
    $inc = 0;
637
    foreach ($rows as $record) {
638
        //if (isset($record['id_tree'])) {
639
            $foldersLimited[$record['id_tree']][$inc] = $record['item_id'];
640
            array_push($foldersLimitedFull, $record['item_id']);
641
            ++$inc;
642
        //}
643
    }
644
645
    // Get list of Personal Folders
646
    $arrays = identUserGetPFList(
647
        $globalsPersonalFolders,
648
        $allowedFolders,
649
        $globalsUserId,
650
        $personalFolders,
651
        $noAccessPersonalFolders,
652
        $foldersLimitedFull,
653
        $allowedFoldersByRoles,
654
        $restrictedFoldersForItems,
655
        $readOnlyFolders,
656
        $noAccessFolders,
657
        isset($SETTINGS['enable_pf_feature']) === true ? $SETTINGS['enable_pf_feature'] : 0,
658
        $tree
659
    );
660
    $allowedFolders = $arrays['allowedFolders'];
661
    $personalFolders = $arrays['personalFolders'];
662
    $noAccessPersonalFolders = $arrays['noAccessPersonalFolders'];
663
664
    // Return data
665
    $superGlobal->put('all_non_personal_folders', $allowedFolders, 'SESSION');
666
    $superGlobal->put('groupes_visibles', array_unique(array_merge($allowedFolders, $personalFolders), SORT_NUMERIC), 'SESSION');
667
    $superGlobal->put('read_only_folders', $readOnlyFolders, 'SESSION');
668
    $superGlobal->put('no_access_folders', $noAccessFolders, 'SESSION');
669
    $superGlobal->put('personal_folders', $personalFolders, 'SESSION');
670
    $superGlobal->put('list_folders_limited', $foldersLimited, 'SESSION');
671
    $superGlobal->put('list_folders_editable_by_role', $allowedFoldersByRoles, 'SESSION');
672
    $superGlobal->put('list_restricted_folders_for_items', $restrictedFoldersForItems, 'SESSION');
673
    $superGlobal->put('forbiden_pfs', $noAccessPersonalFolders, 'SESSION');
674
    $superGlobal->put(
675
        'all_folders_including_no_access',
676
        array_unique(array_merge(
677
            $allowedFolders,
678
            $personalFolders,
679
            $noAccessFolders,
680
            $readOnlyFolders
681
        ), SORT_NUMERIC),
682
        'SESSION'
683
    );
684
    // Folders and Roles numbers
685
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('nested_tree') . '');
686
    $superGlobal->put('nb_folders', DB::count(), 'SESSION');
687
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('roles_title'));
688
    $superGlobal->put('nb_roles', DB::count(), 'SESSION');
689
    // check if change proposals on User's items
690
    if (isset($SETTINGS['enable_suggestion']) === true && (int) $SETTINGS['enable_suggestion'] === 1) {
691
        $countNewItems = DB::query(
692
            'SELECT COUNT(*)
693
            FROM ' . prefixTable('items_change') . ' AS c
694
            LEFT JOIN ' . prefixTable('log_items') . ' AS i ON (c.item_id = i.id_item)
695
            WHERE i.action = %s AND i.id_user = %i',
696
            'at_creation',
697
            $globalsUserId
698
        );
699
        $superGlobal->put('nb_item_change_proposals', $countNewItems, 'SESSION');
700
    } else {
701
        $superGlobal->put('nb_item_change_proposals', 0, 'SESSION');
702
    }
703
704
    return true;
705
}
706
707
/**
708
 * Get list of folders depending on Roles
709
 * 
710
 * @param array $userRoles
711
 * @param array $allowedFoldersByRoles
712
 * @param array $readOnlyFolders
713
 * @param array $allowedFolders
714
 * 
715
 * @return array
716
 */
717
function identUserGetFoldersFromRoles($userRoles, $allowedFoldersByRoles, $readOnlyFolders, $allowedFolders) : array
718
{
719
    $rows = DB::query(
720
        'SELECT *
721
        FROM ' . prefixTable('roles_values') . '
722
        WHERE role_id IN %li AND type IN %ls',
723
        $userRoles,
724
        ['W', 'ND', 'NE', 'NDNE', 'R']
725
    );
726
    foreach ($rows as $record) {
727
        if ($record['type'] === 'R') {
728
            array_push($readOnlyFolders, $record['folder_id']);
729
        } elseif (in_array($record['folder_id'], $allowedFolders) === false) {
730
            array_push($allowedFoldersByRoles, $record['folder_id']);
731
        }
732
    }
733
    $allowedFoldersByRoles = array_unique($allowedFoldersByRoles);
734
    $readOnlyFolders = array_unique($readOnlyFolders);
735
    // Clean arrays
736
    foreach ($allowedFoldersByRoles as $value) {
737
        $key = array_search($value, $readOnlyFolders);
738
        if ($key !== false) {
739
            unset($readOnlyFolders[$key]);
740
        }
741
    }
742
743
    return [
744
        'readOnlyFolders' => $readOnlyFolders,
745
        'allowedFoldersByRoles' => $allowedFoldersByRoles
746
    ];
747
}
748
749
/**
750
 * Get list of Personal Folders
751
 * 
752
 * @param int $globalsPersonalFolders
753
 * @param array $allowedFolders
754
 * @param int $globalsUserId
755
 * @param array $personalFolders
756
 * @param array $noAccessPersonalFolders
757
 * @param array $foldersLimitedFull
758
 * @param array $allowedFoldersByRoles
759
 * @param array $restrictedFoldersForItems
760
 * @param array $readOnlyFolders
761
 * @param array $noAccessFolders
762
 * @param int $enablePfFeature
763
 * @param object $tree
764
 * 
765
 * @return array
766
 */
767
function identUserGetPFList(
768
    $globalsPersonalFolders,
769
    $allowedFolders,
770
    $globalsUserId,
771
    $personalFolders,
772
    $noAccessPersonalFolders,
773
    $foldersLimitedFull,
774
    $allowedFoldersByRoles,
775
    $restrictedFoldersForItems,
776
    $readOnlyFolders,
777
    $noAccessFolders,
778
    $enablePfFeature,
779
    $tree
780
)
781
{
782
    if (
783
        (int) $enablePfFeature === 1
784
        && (int) $globalsPersonalFolders === 1
785
    ) {
786
        $persoFld = DB::queryfirstrow(
787
            'SELECT id
788
            FROM ' . prefixTable('nested_tree') . '
789
            WHERE title = %s AND personal_folder = %i'.
790
            (count($allowedFolders) > 0 ? ' AND id NOT IN ('.implode(',', $allowedFolders).')' : ''),
791
            $globalsUserId,
792
            1
793
        );
794
        if (empty($persoFld['id']) === false) {
795
            array_push($personalFolders, $persoFld['id']);
796
            array_push($allowedFolders, $persoFld['id']);
797
            // get all descendants
798
            $ids = $tree->getDescendants($persoFld['id'], false, false, true);
799
            foreach ($ids as $id) {
800
                //array_push($allowedFolders, $id);
801
                array_push($personalFolders, $id);
802
            }
803
        }
804
    }
805
    
806
    // Exclude all other PF
807
    $where = new WhereClause('and');
808
    $where->add('personal_folder=%i', 1);
809
    if (count($personalFolders) > 0) {
810
        $where->add('id NOT IN ('.implode(',', $personalFolders).')');
811
    }
812
    if (
813
        (int) $enablePfFeature === 1
814
        && (int) $globalsPersonalFolders === 1
815
    ) {
816
        $where->add('title=%s', $globalsUserId);
817
        $where->negateLast();
818
    }
819
    $persoFlds = DB::query(
820
        'SELECT id
821
        FROM ' . prefixTable('nested_tree') . '
822
        WHERE %l',
823
        $where
824
    );
825
    foreach ($persoFlds as $persoFldId) {
826
        array_push($noAccessPersonalFolders, $persoFldId['id']);
827
    }
828
829
    // All folders visibles
830
    $allowedFolders = array_unique(array_merge(
831
        $allowedFolders,
832
        $foldersLimitedFull,
833
        $allowedFoldersByRoles,
834
        $restrictedFoldersForItems,
835
        $readOnlyFolders
836
    ), SORT_NUMERIC);
837
    // Exclude from allowed folders all the specific user forbidden folders
838
    if (count($noAccessFolders) > 0) {
839
        $allowedFolders = array_diff($allowedFolders, $noAccessFolders);
840
    }
841
842
    return [
843
        'allowedFolders' => array_diff(array_diff($allowedFolders, $noAccessPersonalFolders), $personalFolders),
844
        'personalFolders' => $personalFolders,
845
        'noAccessPersonalFolders' => $noAccessPersonalFolders
846
    ];
847
}
848
849
850
/**
851
 * Update the CACHE table.
852
 *
853
 * @param string $action   What to do
854
 * @param array  $SETTINGS Teampass settings
855
 * @param int    $ident    Ident format
856
 * 
857
 * @return void
858
 */
859
function updateCacheTable(string $action, array $SETTINGS, ?int $ident = null): void
860
{
861
    if ($action === 'reload') {
862
        // Rebuild full cache table
863
        cacheTableRefresh($SETTINGS);
864
    } elseif ($action === 'update_value' && is_null($ident) === false) {
865
        // UPDATE an item
866
        cacheTableUpdate($SETTINGS, $ident);
867
    } elseif ($action === 'add_value' && is_null($ident) === false) {
868
        // ADD an item
869
        cacheTableAdd($SETTINGS, $ident);
870
    } elseif ($action === 'delete_value' && is_null($ident) === false) {
871
        // DELETE an item
872
        DB::delete(prefixTable('cache'), 'id = %i', $ident);
873
    }
874
}
875
876
/**
877
 * Cache table - refresh.
878
 *
879
 * @param array $SETTINGS Teampass settings
880
 * 
881
 * @return void
882
 */
883
function cacheTableRefresh(array $SETTINGS): void
884
{
885
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
886
    //Connect to DB
887
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
888
    if (defined('DB_PASSWD_CLEAR') === false) {
889
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
890
    }
891
    DB::$host = DB_HOST;
892
    DB::$user = DB_USER;
893
    DB::$password = DB_PASSWD_CLEAR;
894
    DB::$dbName = DB_NAME;
895
    DB::$port = DB_PORT;
896
    DB::$encoding = DB_ENCODING;
897
    DB::$ssl = DB_SSL;
898
    DB::$connect_options = DB_CONNECT_OPTIONS;
899
    //Load Tree
900
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
901
    $tree->register();
902
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
903
    // truncate table
904
    DB::query('TRUNCATE TABLE ' . prefixTable('cache'));
905
    // reload date
906
    $rows = DB::query(
907
        'SELECT *
908
        FROM ' . prefixTable('items') . ' as i
909
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
910
        AND l.action = %s
911
        AND i.inactif = %i',
912
        'at_creation',
913
        0
914
    );
915
    foreach ($rows as $record) {
916
        if (empty($record['id_tree']) === false) {
917
            // Get all TAGS
918
            $tags = '';
919
            $itemTags = DB::query(
920
                'SELECT tag
921
                FROM ' . prefixTable('tags') . '
922
                WHERE item_id = %i AND tag != ""',
923
                $record['id']
924
            );
925
            foreach ($itemTags as $itemTag) {
926
                $tags .= $itemTag['tag'] . ' ';
927
            }
928
929
            // Get renewal period
930
            $resNT = DB::queryfirstrow(
931
                'SELECT renewal_period
932
                FROM ' . prefixTable('nested_tree') . '
933
                WHERE id = %i',
934
                $record['id_tree']
935
            );
936
            // form id_tree to full foldername
937
            $folder = [];
938
            $arbo = $tree->getPath($record['id_tree'], true);
939
            foreach ($arbo as $elem) {
940
                // Check if title is the ID of a user
941
                if (is_numeric($elem->title) === true) {
942
                    // Is this a User id?
943
                    $user = DB::queryfirstrow(
944
                        'SELECT id, login
945
                        FROM ' . prefixTable('users') . '
946
                        WHERE id = %i',
947
                        $elem->title
948
                    );
949
                    if (count($user) > 0) {
950
                        $elem->title = $user['login'];
951
                    }
952
                }
953
                // Build path
954
                array_push($folder, stripslashes($elem->title));
955
            }
956
            // store data
957
            DB::insert(
958
                prefixTable('cache'),
959
                [
960
                    'id' => $record['id'],
961
                    'label' => $record['label'],
962
                    'description' => $record['description'] ?? '',
963
                    'url' => isset($record['url']) && ! empty($record['url']) ? $record['url'] : '0',
964
                    'tags' => $tags,
965
                    'id_tree' => $record['id_tree'],
966
                    'perso' => $record['perso'],
967
                    'restricted_to' => isset($record['restricted_to']) && ! empty($record['restricted_to']) ? $record['restricted_to'] : '0',
968
                    'login' => $record['login'] ?? '',
969
                    'folder' => implode(' > ', $folder),
970
                    'author' => $record['id_user'],
971
                    'renewal_period' => $resNT['renewal_period'] ?? '0',
972
                    'timestamp' => $record['date'],
973
                ]
974
            );
975
        }
976
    }
977
}
978
979
/**
980
 * Cache table - update existing value.
981
 *
982
 * @param array  $SETTINGS Teampass settings
983
 * @param int    $ident    Ident format
984
 * 
985
 * @return void
986
 */
987
function cacheTableUpdate(array $SETTINGS, ?int $ident = null): void
988
{
989
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
990
    // Load superglobal
991
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
992
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
993
    //Connect to DB
994
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
995
    if (defined('DB_PASSWD_CLEAR') === false) {
996
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
997
    }
998
    DB::$host = DB_HOST;
999
    DB::$user = DB_USER;
1000
    DB::$password = DB_PASSWD_CLEAR;
1001
    DB::$dbName = DB_NAME;
1002
    DB::$port = DB_PORT;
1003
    DB::$encoding = DB_ENCODING;
1004
    DB::$ssl = DB_SSL;
1005
    DB::$connect_options = DB_CONNECT_OPTIONS;
1006
    //Load Tree
1007
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1008
    $tree->register();
1009
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1010
    // get new value from db
1011
    $data = DB::queryfirstrow(
1012
        'SELECT label, description, id_tree, perso, restricted_to, login, url
1013
        FROM ' . prefixTable('items') . '
1014
        WHERE id=%i',
1015
        $ident
1016
    );
1017
    // Get all TAGS
1018
    $tags = '';
1019
    $itemTags = DB::query(
1020
        'SELECT tag
1021
            FROM ' . prefixTable('tags') . '
1022
            WHERE item_id = %i AND tag != ""',
1023
        $ident
1024
    );
1025
    foreach ($itemTags as $itemTag) {
1026
        $tags .= $itemTag['tag'] . ' ';
1027
    }
1028
    // form id_tree to full foldername
1029
    $folder = [];
1030
    $arbo = $tree->getPath($data['id_tree'], true);
1031
    foreach ($arbo as $elem) {
1032
        // Check if title is the ID of a user
1033
        if (is_numeric($elem->title) === true) {
1034
            // Is this a User id?
1035
            $user = DB::queryfirstrow(
1036
                'SELECT id, login
1037
                FROM ' . prefixTable('users') . '
1038
                WHERE id = %i',
1039
                $elem->title
1040
            );
1041
            if (count($user) > 0) {
1042
                $elem->title = $user['login'];
1043
            }
1044
        }
1045
        // Build path
1046
        array_push($folder, stripslashes($elem->title));
1047
    }
1048
    // finaly update
1049
    DB::update(
1050
        prefixTable('cache'),
1051
        [
1052
            'label' => $data['label'],
1053
            'description' => $data['description'],
1054
            'tags' => $tags,
1055
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
1056
            'id_tree' => $data['id_tree'],
1057
            'perso' => $data['perso'],
1058
            'restricted_to' => isset($data['restricted_to']) && ! empty($data['restricted_to']) ? $data['restricted_to'] : '0',
1059
            'login' => $data['login'] ?? '',
1060
            'folder' => implode(' » ', $folder),
1061
            'author' => $superGlobal->get('user_id', 'SESSION'),
1062
        ],
1063
        'id = %i',
1064
        $ident
1065
    );
1066
}
1067
1068
/**
1069
 * Cache table - add new value.
1070
 *
1071
 * @param array  $SETTINGS Teampass settings
1072
 * @param int    $ident    Ident format
1073
 * 
1074
 * @return void
1075
 */
1076
function cacheTableAdd(array $SETTINGS, ?int $ident = null): void
1077
{
1078
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1079
    // Load superglobal
1080
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1081
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1082
    // Get superglobals
1083
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
1084
    //Connect to DB
1085
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1086
    if (defined('DB_PASSWD_CLEAR') === false) {
1087
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1088
    }
1089
    DB::$host = DB_HOST;
1090
    DB::$user = DB_USER;
1091
    DB::$password = DB_PASSWD_CLEAR;
1092
    DB::$dbName = DB_NAME;
1093
    DB::$port = DB_PORT;
1094
    DB::$encoding = DB_ENCODING;
1095
    DB::$ssl = DB_SSL;
1096
    DB::$connect_options = DB_CONNECT_OPTIONS;
1097
    //Load Tree
1098
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1099
    $tree->register();
1100
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1101
    // get new value from db
1102
    $data = DB::queryFirstRow(
1103
        'SELECT i.label, i.description, i.id_tree as id_tree, i.perso, i.restricted_to, i.id, i.login, i.url, l.date
1104
        FROM ' . prefixTable('items') . ' as i
1105
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
1106
        WHERE i.id = %i
1107
        AND l.action = %s',
1108
        $ident,
1109
        'at_creation'
1110
    );
1111
    // Get all TAGS
1112
    $tags = '';
1113
    $itemTags = DB::query(
1114
        'SELECT tag
1115
            FROM ' . prefixTable('tags') . '
1116
            WHERE item_id = %i AND tag != ""',
1117
        $ident
1118
    );
1119
    foreach ($itemTags as $itemTag) {
1120
        $tags .= $itemTag['tag'] . ' ';
1121
    }
1122
    // form id_tree to full foldername
1123
    $folder = [];
1124
    $arbo = $tree->getPath($data['id_tree'], true);
1125
    foreach ($arbo as $elem) {
1126
        // Check if title is the ID of a user
1127
        if (is_numeric($elem->title) === true) {
1128
            // Is this a User id?
1129
            $user = DB::queryfirstrow(
1130
                'SELECT id, login
1131
                FROM ' . prefixTable('users') . '
1132
                WHERE id = %i',
1133
                $elem->title
1134
            );
1135
            if (count($user) > 0) {
1136
                $elem->title = $user['login'];
1137
            }
1138
        }
1139
        // Build path
1140
        array_push($folder, stripslashes($elem->title));
1141
    }
1142
    // finaly update
1143
    DB::insert(
1144
        prefixTable('cache'),
1145
        [
1146
            'id' => $data['id'],
1147
            'label' => $data['label'],
1148
            'description' => $data['description'],
1149
            'tags' => isset($tags) && empty($tags) === false ? $tags : 'None',
1150
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
1151
            'id_tree' => $data['id_tree'],
1152
            'perso' => isset($data['perso']) && empty($data['perso']) === false && $data['perso'] !== 'None' ? $data['perso'] : '0',
1153
            'restricted_to' => isset($data['restricted_to']) && empty($data['restricted_to']) === false ? $data['restricted_to'] : '0',
1154
            'login' => $data['login'] ?? '',
1155
            'folder' => implode(' » ', $folder),
1156
            'author' => $globalsUserId,
1157
            'timestamp' => $data['date'],
1158
        ]
1159
    );
1160
}
1161
1162
/**
1163
 * Do statistics.
1164
 *
1165
 * @param array $SETTINGS Teampass settings
1166
 *
1167
 * @return array
1168
 */
1169
function getStatisticsData(array $SETTINGS): array
1170
{
1171
    DB::query(
1172
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1173
        0
1174
    );
1175
    $counter_folders = DB::count();
1176
    DB::query(
1177
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1178
        1
1179
    );
1180
    $counter_folders_perso = DB::count();
1181
    DB::query(
1182
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1183
        0
1184
    );
1185
    $counter_items = DB::count();
1186
        DB::query(
1187
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1188
        1
1189
    );
1190
    $counter_items_perso = DB::count();
1191
        DB::query(
1192
        'SELECT id FROM ' . prefixTable('users') . ''
1193
    );
1194
    $counter_users = DB::count();
1195
        DB::query(
1196
        'SELECT id FROM ' . prefixTable('users') . ' WHERE admin = %i',
1197
        1
1198
    );
1199
    $admins = DB::count();
1200
    DB::query(
1201
        'SELECT id FROM ' . prefixTable('users') . ' WHERE gestionnaire = %i',
1202
        1
1203
    );
1204
    $managers = DB::count();
1205
    DB::query(
1206
        'SELECT id FROM ' . prefixTable('users') . ' WHERE read_only = %i',
1207
        1
1208
    );
1209
    $readOnly = DB::count();
1210
    // list the languages
1211
    $usedLang = [];
1212
    $tp_languages = DB::query(
1213
        'SELECT name FROM ' . prefixTable('languages')
1214
    );
1215
    foreach ($tp_languages as $tp_language) {
1216
        DB::query(
1217
            'SELECT * FROM ' . prefixTable('users') . ' WHERE user_language = %s',
1218
            $tp_language['name']
1219
        );
1220
        $usedLang[$tp_language['name']] = round((DB::count() * 100 / $counter_users), 0);
1221
    }
1222
1223
    // get list of ips
1224
    $usedIp = [];
1225
    $tp_ips = DB::query(
1226
        'SELECT user_ip FROM ' . prefixTable('users')
1227
    );
1228
    foreach ($tp_ips as $ip) {
1229
        if (array_key_exists($ip['user_ip'], $usedIp)) {
1230
            $usedIp[$ip['user_ip']] += $usedIp[$ip['user_ip']];
1231
        } elseif (! empty($ip['user_ip']) && $ip['user_ip'] !== 'none') {
1232
            $usedIp[$ip['user_ip']] = 1;
1233
        }
1234
    }
1235
1236
    return [
1237
        'error' => '',
1238
        'stat_phpversion' => phpversion(),
1239
        'stat_folders' => $counter_folders,
1240
        'stat_folders_shared' => intval($counter_folders) - intval($counter_folders_perso),
1241
        'stat_items' => $counter_items,
1242
        'stat_items_shared' => intval($counter_items) - intval($counter_items_perso),
1243
        'stat_users' => $counter_users,
1244
        'stat_admins' => $admins,
1245
        'stat_managers' => $managers,
1246
        'stat_ro' => $readOnly,
1247
        'stat_kb' => $SETTINGS['enable_kb'],
1248
        'stat_pf' => $SETTINGS['enable_pf_feature'],
1249
        'stat_fav' => $SETTINGS['enable_favourites'],
1250
        'stat_teampassversion' => TP_VERSION_FULL,
1251
        'stat_ldap' => $SETTINGS['ldap_mode'],
1252
        'stat_agses' => $SETTINGS['agses_authentication_enabled'],
1253
        'stat_duo' => $SETTINGS['duo'],
1254
        'stat_suggestion' => $SETTINGS['enable_suggestion'],
1255
        'stat_api' => $SETTINGS['api'],
1256
        'stat_customfields' => $SETTINGS['item_extra_fields'],
1257
        'stat_syslog' => $SETTINGS['syslog_enable'],
1258
        'stat_2fa' => $SETTINGS['google_authentication'],
1259
        'stat_stricthttps' => $SETTINGS['enable_sts'],
1260
        'stat_mysqlversion' => DB::serverVersion(),
1261
        'stat_languages' => $usedLang,
1262
        'stat_country' => $usedIp,
1263
    ];
1264
}
1265
1266
/**
1267
 * Permits to prepare the way to send the email
1268
 * 
1269
 * @param string $subject       email subject
1270
 * @param string $body          email message
1271
 * @param string $email         email
1272
 * @param string $receiverName  Receiver name
1273
 * @param array  $SETTINGS      settings
1274
 *
1275
 * @return void
1276
 */
1277
function prepareSendingEmail(
1278
    $subject,
1279
    $body,
1280
    $email,
1281
    $receiverName,
1282
    $SETTINGS
1283
): void 
1284
{
1285
    if (isKeyExistingAndEqual('enable_tasks_manager', 1, $SETTINGS) === true) {
1286
        DB::insert(
1287
            prefixTable('processes'),
1288
            array(
1289
                'created_at' => time(),
1290
                'process_type' => 'send_email',
1291
                'arguments' => json_encode([
1292
                    'subject' => $subject,
1293
                    'receivers' => $email,
1294
                    'body' => $body,
1295
                    'receiver_name' => $receiverName,
1296
                ]),
1297
                'updated_at' => '',
1298
                'finished_at' => '',
1299
                'output' => '',
1300
            )
1301
        );
1302
    } else {
1303
        sendEmail(
1304
            $subject,
1305
            $body,
1306
            $email,
1307
            $SETTINGS,
1308
            $body
1309
        );
1310
    }
1311
}
1312
1313
/**
1314
 * Permits to send an email.
1315
 *
1316
 * @param string $subject     email subject
1317
 * @param string $textMail    email message
1318
 * @param string $email       email
1319
 * @param array  $SETTINGS    settings
1320
 * @param string $textMailAlt email message alt
1321
 * @param bool   $silent      no errors
1322
 *
1323
 * @return string some json info
1324
 */
1325
function sendEmail(
1326
    $subject,
1327
    $textMail,
1328
    $email,
1329
    $SETTINGS,
1330
    $textMailAlt = null,
1331
    $silent = true,
1332
    $cron = false
1333
) {
1334
    // CAse where email not defined
1335
    if ($email === 'none' || empty($email) === true) {
1336
        return json_encode(
1337
            [
1338
                'error' => true,
1339
                'message' => langHdl('forgot_my_pw_email_sent'),
1340
            ]
1341
        );
1342
    }
1343
1344
    // Build and send email
1345
    $email = buildEmail(
1346
        $subject,
1347
        $textMail,
1348
        $email,
1349
        $SETTINGS,
1350
        $textMailAlt = null,
1351
        $silent = true,
1352
        $cron
1353
    );
1354
1355
    if ($silent === false) {
0 ignored issues
show
introduced by
The condition $silent === false is always false.
Loading history...
1356
        return json_encode(
1357
            [
1358
                'error' => false,
1359
                'message' => langHdl('forgot_my_pw_email_sent'),
1360
            ]
1361
        );
1362
    }
1363
    // Debug purpose
1364
    if ((int) $SETTINGS['email_debug_level'] !== 0 && $cron === false) {
1365
        return json_encode(
1366
            [
1367
                'error' => true,
1368
                'message' => $email['ErrorInfo'],
1369
            ]
1370
        );
1371
    }
1372
    return json_encode(
1373
        [
1374
            'error' => false,
1375
            'message' => langHdl('share_sent_ok'),
1376
        ]
1377
    );
1378
}
1379
1380
1381
function buildEmail(
1382
    $subject,
1383
    $textMail,
1384
    $email,
1385
    $SETTINGS,
1386
    $textMailAlt = null,
1387
    $silent = true,
1388
    $cron = false
1389
)
1390
{
1391
    // Load settings
1392
    //include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
1393
    // Load superglobal
1394
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1395
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1396
    // Get user language
1397
    include_once $SETTINGS['cpassman_dir'] . '/includes/language/' . (null !== $superGlobal->get('user_language', 'SESSION', 'user') ? $superGlobal->get('user_language', 'SESSION', 'user') : 'english') . '.php';
1398
    // Load library
1399
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1400
    // load PHPMailer
1401
    $mail = new SplClassLoader('PHPMailer\PHPMailer', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1402
    $mail->register();
1403
    $mail = new PHPMailer\PHPMailer\PHPMailer(true);
1404
1405
    // send to user
1406
    $mail->setLanguage('en', $SETTINGS['cpassman_dir'] . '/includes/libraries/PHPMailer/PHPMailer/language/');
1407
    $mail->SMTPDebug = isset($SETTINGS['email_debug_level']) === true && $cron === false ? $SETTINGS['email_debug_level'] : 0;
1408
    $mail->Port = (int) $SETTINGS['email_port'];
1409
    //COULD BE USED
1410
    $mail->CharSet = 'utf-8';
1411
    $mail->SMTPSecure = $SETTINGS['email_security'] !== 'none' ? $SETTINGS['email_security'] : '';
1412
    $mail->SMTPAutoTLS = $SETTINGS['email_security'] !== 'none' ? true : false;
1413
    $mail->SMTPOptions = [
1414
        'ssl' => [
1415
            'verify_peer' => false,
1416
            'verify_peer_name' => false,
1417
            'allow_self_signed' => true,
1418
        ],
1419
    ];
1420
    $mail->isSmtp();
1421
    // send via SMTP
1422
    $mail->Host = $SETTINGS['email_smtp_server'];
1423
    // SMTP servers
1424
    $mail->SMTPAuth = (int) $SETTINGS['email_smtp_auth'] === 1 ? true : false;
1425
    // turn on SMTP authentication
1426
    $mail->Username = $SETTINGS['email_auth_username'];
1427
    // SMTP username
1428
    $mail->Password = $SETTINGS['email_auth_pwd'];
1429
    // SMTP password
1430
    $mail->From = $SETTINGS['email_from'];
1431
    $mail->FromName = $SETTINGS['email_from_name'];
1432
    // Prepare for each person
1433
    foreach (array_filter(explode(',', $email)) as $dest) {
1434
        $mail->addAddress($dest);
1435
    }
1436
1437
    // Prepare HTML
1438
    $text_html = emailBody($textMail);
1439
    $mail->WordWrap = 80;
1440
    // set word wrap
1441
    $mail->isHtml(true);
1442
    // send as HTML
1443
    $mail->Subject = $subject;
1444
    $mail->Body = $text_html;
1445
    $mail->AltBody = is_null($textMailAlt) === false ? $textMailAlt : '';
1446
1447
    try {
1448
        // send email
1449
        $mail->send();
1450
    } catch (Exception $e) {
1451
        if ($silent === false || (int) $SETTINGS['email_debug_level'] !== 0) {
1452
            return json_encode(
1453
                [
1454
                    'error' => true,
1455
                    'message' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1456
                ]
1457
            );
1458
        }
1459
        return '';
1460
    }
1461
    $mail->smtpClose();
1462
1463
    return json_encode(
1464
        [
1465
            'ErrorInfo' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1466
        ]
1467
    );
1468
}
1469
1470
/**
1471
 * Returns the email body.
1472
 *
1473
 * @param string $textMail Text for the email
1474
 */
1475
function emailBody(string $textMail): string
1476
{
1477
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1478
    w3.org/TR/html4/loose.dtd"><html>
1479
    <head><title>Email Template</title>
1480
    <style type="text/css">
1481
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1482
    </style></head>
1483
    <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">
1484
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1485
    <tr><td style="border-collapse: collapse;"><br>
1486
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1487
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1488
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1489
        </td></tr></table></td>
1490
    </tr>
1491
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1492
        <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;">
1493
        <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;">
1494
        <br><div style="float:right;">' .
1495
        $textMail .
1496
        '<br><br></td></tr></table>
1497
    </td></tr></table>
1498
    <br></body></html>';
1499
}
1500
1501
/**
1502
 * Generate a Key.
1503
 * 
1504
 * @return string
1505
 */
1506
function generateKey(): string
1507
{
1508
    return substr(md5(rand() . rand()), 0, 15);
1509
}
1510
1511
/**
1512
 * Convert date to timestamp.
1513
 *
1514
 * @param string $date        The date
1515
 * @param string $date_format Date format
1516
 *
1517
 * @return int
1518
 */
1519
function dateToStamp(string $date, string $date_format): int
1520
{
1521
    $date = date_parse_from_format($date_format, $date);
1522
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1523
        return mktime(23, 59, 59, $date['month'], $date['day'], $date['year']);
1524
    }
1525
    return 0;
1526
}
1527
1528
/**
1529
 * Is this a date.
1530
 *
1531
 * @param string $date Date
1532
 *
1533
 * @return bool
1534
 */
1535
function isDate(string $date): bool
1536
{
1537
    return strtotime($date) !== false;
1538
}
1539
1540
/**
1541
 * Check if isUTF8().
1542
 *
1543
 * @param string|array $string Is the string
1544
 *
1545
 * @return int is the string in UTF8 format
1546
 */
1547
function isUTF8($string): int
1548
{
1549
    if (is_array($string) === true) {
1550
        $string = $string['string'];
1551
    }
1552
1553
    return preg_match(
1554
        '%^(?:
1555
        [\x09\x0A\x0D\x20-\x7E] # ASCII
1556
        | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
1557
        | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
1558
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
1559
        | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
1560
        | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
1561
        | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
1562
        | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
1563
        )*$%xs',
1564
        $string
1565
    );
1566
}
1567
1568
/**
1569
 * Prepare an array to UTF8 format before JSON_encode.
1570
 *
1571
 * @param array $array Array of values
1572
 *
1573
 * @return array
1574
 */
1575
function utf8Converter(array $array): array
1576
{
1577
    array_walk_recursive(
1578
        $array,
1579
        static function (&$item): void {
1580
            if (mb_detect_encoding((string) $item, 'utf-8', true) === false) {
1581
                $item = utf8_encode($item);
1582
            }
1583
        }
1584
    );
1585
    return $array;
1586
}
1587
1588
/**
1589
 * Permits to prepare data to be exchanged.
1590
 *
1591
 * @param string       $teampassDir
1592
 * @param array|string $data Text
1593
 * @param string       $type Parameter
1594
 * @param string       $key  Optional key
1595
 *
1596
 * @return string|array
1597
 */
1598
function prepareExchangedData($teampassDir, $data, string $type, ?string $key = null)
1599
{
1600
    $teampassDir = __DIR__ . '/..';
1601
    // Load superglobal
1602
    include_once $teampassDir . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1603
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1604
    // Get superglobals
1605
    if ($key !== null) {
1606
        $superGlobal->put('key', $key, 'SESSION');
1607
        $globalsKey = $key;
1608
    } else {
1609
        $globalsKey = $superGlobal->get('key', 'SESSION');
1610
    }
1611
1612
    //load Encoding
1613
    include_once $teampassDir . '/includes/libraries/ForceUTF8/Encoding.php';
1614
    
1615
    //Load CRYPTOJS
1616
    include_once $teampassDir . '/includes/libraries/Encryption/CryptoJs/Encryption.php';
1617
1618
    // Perform
1619
    if ($type === 'encode' && is_array($data) === true) {
1620
        // Now encode
1621
        return Encryption\CryptoJs\Encryption::encrypt(
1622
            json_encode(
1623
                $data,
1624
                JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1625
            ),
1626
            $globalsKey
1627
        );
1628
    }
1629
    if ($type === 'decode' && is_array($data) === false) {
1630
        return json_decode(
1631
            (string) Encryption\CryptoJs\Encryption::decrypt(
1632
                (string) $data,
1633
                $globalsKey
1634
            ),
1635
            true
1636
        );
1637
    }
1638
}
1639
1640
1641
/**
1642
 * Create a thumbnail.
1643
 *
1644
 * @param string  $src           Source
1645
 * @param string  $dest          Destination
1646
 * @param int $desired_width Size of width
1647
 * 
1648
 * @return void|string|bool
1649
 */
1650
function makeThumbnail(string $src, string $dest, int $desired_width)
1651
{
1652
    /* read the source image */
1653
    if (is_file($src) === true && mime_content_type($src) === 'image/png') {
1654
        $source_image = imagecreatefrompng($src);
1655
        if ($source_image === false) {
1656
            return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1657
        }
1658
    } else {
1659
        return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1660
    }
1661
1662
    // Get height and width
1663
    $width = imagesx($source_image);
1664
    $height = imagesy($source_image);
1665
    /* find the "desired height" of this thumbnail, relative to the desired width  */
1666
    $desired_height = (int) floor($height * $desired_width / $width);
1667
    /* create a new, "virtual" image */
1668
    $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
1669
    if ($virtual_image === false) {
1670
        return false;
1671
    }
1672
    /* copy source image at a resized size */
1673
    imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
1674
    /* create the physical thumbnail image to its destination */
1675
    imagejpeg($virtual_image, $dest);
1676
}
1677
1678
/**
1679
 * Check table prefix in SQL query.
1680
 *
1681
 * @param string $table Table name
1682
 * 
1683
 * @return string
1684
 */
1685
function prefixTable(string $table): string
1686
{
1687
    $safeTable = htmlspecialchars(DB_PREFIX . $table);
1688
    if (! empty($safeTable)) {
1689
        // sanitize string
1690
        return $safeTable;
1691
    }
1692
    // stop error no table
1693
    return 'table_not_exists';
1694
}
1695
1696
/**
1697
 * GenerateCryptKey
1698
 *
1699
 * @param int     $size      Length
1700
 * @param bool $secure Secure
1701
 * @param bool $numerals Numerics
1702
 * @param bool $uppercase Uppercase letters
1703
 * @param bool $symbols Symbols
1704
 * @param bool $lowercase Lowercase
1705
 * @param array   $SETTINGS  SETTINGS
1706
 * 
1707
 * @return string
1708
 */
1709
function GenerateCryptKey(
1710
    int $size = 10,
1711
    bool $secure = false,
1712
    bool $numerals = false,
1713
    bool $uppercase = false,
1714
    bool $symbols = false,
1715
    bool $lowercase = false,
1716
    array $SETTINGS = []
1717
): string {
1718
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1719
    $generator = new SplClassLoader('PasswordGenerator\Generator', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1720
    $generator->register();
1721
    $generator = new PasswordGenerator\Generator\ComputerPasswordGenerator();
1722
    // Is PHP7 being used?
1723
    if (version_compare(PHP_VERSION, '7.0.0', '>=')) {
1724
        $php7generator = new SplClassLoader('PasswordGenerator\RandomGenerator', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1725
        $php7generator->register();
1726
        $generator->setRandomGenerator(new PasswordGenerator\RandomGenerator\Php7RandomGenerator());
1727
    }
1728
    
1729
    // Manage size
1730
    $generator->setLength((int) $size);
1731
    if ($secure === true) {
1732
        $generator->setSymbols(true);
1733
        $generator->setLowercase(true);
1734
        $generator->setUppercase(true);
1735
        $generator->setNumbers(true);
1736
    } else {
1737
        $generator->setLowercase($lowercase);
1738
        $generator->setUppercase($uppercase);
1739
        $generator->setNumbers($numerals);
1740
        $generator->setSymbols($symbols);
1741
    }
1742
1743
    return $generator->generatePasswords()[0];
1744
}
1745
1746
/**
1747
 * Send sysLOG message
1748
 *
1749
 * @param string    $message
1750
 * @param string    $host
1751
 * @param int       $port
1752
 * @param string    $component
1753
 * 
1754
 * @return void
1755
*/
1756
function send_syslog($message, $host, $port, $component = 'teampass'): void
1757
{
1758
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1759
    $syslog_message = '<123>' . date('M d H:i:s ') . $component . ': ' . $message;
1760
    socket_sendto($sock, (string) $syslog_message, strlen($syslog_message), 0, (string) $host, (int) $port);
1761
    socket_close($sock);
1762
}
1763
1764
/**
1765
 * Permits to log events into DB
1766
 *
1767
 * @param array  $SETTINGS Teampass settings
1768
 * @param string $type     Type
1769
 * @param string $label    Label
1770
 * @param string $who      Who
1771
 * @param string $login    Login
1772
 * @param string $field_1  Field
1773
 * 
1774
 * @return void
1775
 */
1776
function logEvents(array $SETTINGS, string $type, string $label, string $who, ?string $login = null, ?string $field_1 = null): void
1777
{
1778
    if (empty($who)) {
1779
        $who = getClientIpServer();
1780
    }
1781
1782
    // include librairies & connect to DB
1783
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1784
    if (defined('DB_PASSWD_CLEAR') === false) {
1785
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1786
    }
1787
    DB::$host = DB_HOST;
1788
    DB::$user = DB_USER;
1789
    DB::$password = DB_PASSWD_CLEAR;
1790
    DB::$dbName = DB_NAME;
1791
    DB::$port = DB_PORT;
1792
    DB::$encoding = DB_ENCODING;
1793
    DB::$ssl = DB_SSL;
1794
    DB::$connect_options = DB_CONNECT_OPTIONS;
1795
    DB::insert(
1796
        prefixTable('log_system'),
1797
        [
1798
            'type' => $type,
1799
            'date' => time(),
1800
            'label' => $label,
1801
            'qui' => $who,
1802
            'field_1' => $field_1 === null ? '' : $field_1,
1803
        ]
1804
    );
1805
    // If SYSLOG
1806
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1807
        if ($type === 'user_mngt') {
1808
            send_syslog(
1809
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1810
                $SETTINGS['syslog_host'],
1811
                $SETTINGS['syslog_port'],
1812
                'teampass'
1813
            );
1814
        } else {
1815
            send_syslog(
1816
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1817
                $SETTINGS['syslog_host'],
1818
                $SETTINGS['syslog_port'],
1819
                'teampass'
1820
            );
1821
        }
1822
    }
1823
}
1824
1825
/**
1826
 * Log events.
1827
 *
1828
 * @param array  $SETTINGS        Teampass settings
1829
 * @param int    $item_id         Item id
1830
 * @param string $item_label      Item label
1831
 * @param int    $id_user         User id
1832
 * @param string $action          Code for reason
1833
 * @param string $login           User login
1834
 * @param string $raison          Code for reason
1835
 * @param string $encryption_type Encryption on
1836
 * 
1837
 * @return void
1838
 */
1839
function logItems(
1840
    array $SETTINGS,
1841
    int $item_id,
1842
    string $item_label,
1843
    int $id_user,
1844
    string $action,
1845
    ?string $login = null,
1846
    ?string $raison = null,
1847
    ?string $encryption_type = null
1848
): void {
1849
    // include librairies & connect to DB
1850
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1851
    if (defined('DB_PASSWD_CLEAR') === false) {
1852
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1853
    }
1854
    DB::$host = DB_HOST;
1855
    DB::$user = DB_USER;
1856
    DB::$password = DB_PASSWD_CLEAR;
1857
    DB::$dbName = DB_NAME;
1858
    DB::$port = DB_PORT;
1859
    DB::$encoding = DB_ENCODING;
1860
    DB::$ssl = DB_SSL;
1861
    DB::$connect_options = DB_CONNECT_OPTIONS;
1862
    // Insert log in DB
1863
    DB::insert(
1864
        prefixTable('log_items'),
1865
        [
1866
            'id_item' => $item_id,
1867
            'date' => time(),
1868
            'id_user' => $id_user,
1869
            'action' => $action,
1870
            'raison' => $raison,
1871
            'raison_iv' => '',
1872
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1873
        ]
1874
    );
1875
    // Timestamp the last change
1876
    if ($action === 'at_creation' || $action === 'at_modifiation' || $action === 'at_delete' || $action === 'at_import') {
1877
        DB::update(
1878
            prefixTable('misc'),
1879
            [
1880
                'valeur' => time(),
1881
            ],
1882
            'type = %s AND intitule = %s',
1883
            'timestamp',
1884
            'last_item_change'
1885
        );
1886
    }
1887
1888
    // SYSLOG
1889
    if (isset($SETTINGS['syslog_enable']) === true && $SETTINGS['syslog_enable'] === '1') {
1890
        // Extract reason
1891
        $attribute = is_null($raison) === true ? '' : explode(' : ', $raison);
1892
        // Get item info if not known
1893
        if (empty($item_label) === true) {
1894
            $dataItem = DB::queryfirstrow(
1895
                'SELECT id, id_tree, label
1896
                FROM ' . prefixTable('items') . '
1897
                WHERE id = %i',
1898
                $item_id
1899
            );
1900
            $item_label = $dataItem['label'];
1901
        }
1902
1903
        send_syslog(
1904
            'action=' . str_replace('at_', '', $action) .
1905
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1906
                ' itemno=' . $item_id .
1907
                ' user=' . is_null($login) === true ? '' : addslashes((string) $login) .
1908
                ' itemname="' . addslashes($item_label) . '"',
1909
            $SETTINGS['syslog_host'],
1910
            $SETTINGS['syslog_port'],
1911
            'teampass'
1912
        );
1913
    }
1914
1915
    // send notification if enabled
1916
    //notifyOnChange($item_id, $action, $SETTINGS);
1917
}
1918
1919
/**
1920
 * If enabled, then notify admin/manager.
1921
 *
1922
 * @param int    $item_id  Item id
1923
 * @param string $action   Action to do
1924
 * @param array  $SETTINGS Teampass settings
1925
 * 
1926
 * @return void
1927
 */
1928
/*
1929
function notifyOnChange(int $item_id, string $action, array $SETTINGS): void
1930
{
1931
    if (
1932
        isset($SETTINGS['enable_email_notification_on_item_shown']) === true
1933
        && (int) $SETTINGS['enable_email_notification_on_item_shown'] === 1
1934
        && $action === 'at_shown'
1935
    ) {
1936
        // Load superglobal
1937
        include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1938
        $superGlobal = new protect\SuperGlobal\SuperGlobal();
1939
        // Get superglobals
1940
        $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1941
        $globalsName = $superGlobal->get('name', 'SESSION');
1942
        $globalsNotifiedEmails = $superGlobal->get('listNotificationEmails', 'SESSION');
1943
        // Get info about item
1944
        $dataItem = DB::queryfirstrow(
1945
            'SELECT id, id_tree, label
1946
            FROM ' . prefixTable('items') . '
1947
            WHERE id = %i',
1948
            $item_id
1949
        );
1950
        $item_label = $dataItem['label'];
1951
        // send back infos
1952
        DB::insert(
1953
            prefixTable('emails'),
1954
            [
1955
                'timestamp' => time(),
1956
                'subject' => langHdl('email_on_open_notification_subject'),
1957
                'body' => str_replace(
1958
                    ['#tp_user#', '#tp_item#', '#tp_link#'],
1959
                    [
1960
                        addslashes($globalsName . ' ' . $globalsLastname),
1961
                        addslashes($item_label),
1962
                        $SETTINGS['cpassman_url'] . '/index.php?page=items&group=' . $dataItem['id_tree'] . '&id=' . $item_id,
1963
                    ],
1964
                    langHdl('email_on_open_notification_mail')
1965
                ),
1966
                'receivers' => $globalsNotifiedEmails,
1967
                'status' => '',
1968
            ]
1969
        );
1970
    }
1971
}
1972
*/
1973
1974
/**
1975
 * Prepare notification email to subscribers.
1976
 *
1977
 * @param int    $item_id  Item id
1978
 * @param string $label    Item label
1979
 * @param array  $changes  List of changes
1980
 * @param array  $SETTINGS Teampass settings
1981
 * 
1982
 * @return void
1983
 */
1984
function notifyChangesToSubscribers(int $item_id, string $label, array $changes, array $SETTINGS): void
1985
{
1986
    // Load superglobal
1987
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1988
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1989
    // Get superglobals
1990
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
1991
    $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1992
    $globalsName = $superGlobal->get('name', 'SESSION');
1993
    // send email to user that what to be notified
1994
    $notification = DB::queryOneColumn(
1995
        'email',
1996
        'SELECT *
1997
        FROM ' . prefixTable('notification') . ' AS n
1998
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1999
        WHERE n.item_id = %i AND n.user_id != %i',
2000
        $item_id,
2001
        $globalsUserId
2002
    );
2003
    if (DB::count() > 0) {
2004
        // Prepare path
2005
        $path = geItemReadablePath($item_id, '', $SETTINGS);
2006
        // Get list of changes
2007
        $htmlChanges = '<ul>';
2008
        foreach ($changes as $change) {
2009
            $htmlChanges .= '<li>' . $change . '</li>';
2010
        }
2011
        $htmlChanges .= '</ul>';
2012
        // send email
2013
        DB::insert(
2014
            prefixTable('emails'),
2015
            [
2016
                'timestamp' => time(),
2017
                'subject' => langHdl('email_subject_item_updated'),
2018
                'body' => str_replace(
2019
                    ['#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'],
2020
                    [$label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges],
2021
                    langHdl('email_body_item_updated')
2022
                ),
2023
                'receivers' => implode(',', $notification),
2024
                'status' => '',
2025
            ]
2026
        );
2027
    }
2028
}
2029
2030
/**
2031
 * Returns the Item + path.
2032
 *
2033
 * @param int    $id_tree  Node id
2034
 * @param string $label    Label
2035
 * @param array  $SETTINGS TP settings
2036
 * 
2037
 * @return string
2038
 */
2039
function geItemReadablePath(int $id_tree, string $label, array $SETTINGS): string
2040
{
2041
    // Class loader
2042
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
2043
    //Load Tree
2044
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
2045
    $tree->register();
2046
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
2047
    $arbo = $tree->getPath($id_tree, true);
2048
    $path = '';
2049
    foreach ($arbo as $elem) {
2050
        if (empty($path) === true) {
2051
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
2052
        } else {
2053
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
2054
        }
2055
    }
2056
2057
    // Build text to show user
2058
    if (empty($label) === false) {
2059
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
2060
    }
2061
    return empty($path) === true ? '' : $path;
2062
}
2063
2064
/**
2065
 * Get the client ip address.
2066
 *
2067
 * @return string IP address
2068
 */
2069
function getClientIpServer(): string
2070
{
2071
    if (getenv('HTTP_CLIENT_IP')) {
2072
        $ipaddress = getenv('HTTP_CLIENT_IP');
2073
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
2074
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
2075
    } elseif (getenv('HTTP_X_FORWARDED')) {
2076
        $ipaddress = getenv('HTTP_X_FORWARDED');
2077
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
2078
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
2079
    } elseif (getenv('HTTP_FORWARDED')) {
2080
        $ipaddress = getenv('HTTP_FORWARDED');
2081
    } elseif (getenv('REMOTE_ADDR')) {
2082
        $ipaddress = getenv('REMOTE_ADDR');
2083
    } else {
2084
        $ipaddress = 'UNKNOWN';
2085
    }
2086
2087
    return $ipaddress;
2088
}
2089
2090
/**
2091
 * Escape all HTML, JavaScript, and CSS.
2092
 *
2093
 * @param string $input    The input string
2094
 * @param string $encoding Which character encoding are we using?
2095
 * 
2096
 * @return string
2097
 */
2098
function noHTML(string $input, string $encoding = 'UTF-8'): string
2099
{
2100
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
2101
}
2102
2103
/**
2104
 * Permits to handle the Teampass config file
2105
 * $action accepts "rebuild" and "update"
2106
 *
2107
 * @param string $action   Action to perform
2108
 * @param array  $SETTINGS Teampass settings
2109
 * @param string $field    Field to refresh
2110
 * @param string $value    Value to set
2111
 *
2112
 * @return string|bool
2113
 */
2114
function handleConfigFile($action, $SETTINGS, $field = null, $value = null)
2115
{
2116
    $tp_config_file = $SETTINGS['cpassman_dir'] . '/includes/config/tp.config.php';
2117
    // include librairies & connect to DB
2118
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2119
    if (defined('DB_PASSWD_CLEAR') === false) {
2120
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2121
    }
2122
    DB::$host = DB_HOST;
2123
    DB::$user = DB_USER;
2124
    DB::$password = DB_PASSWD_CLEAR;
2125
    DB::$dbName = DB_NAME;
2126
    DB::$port = DB_PORT;
2127
    DB::$encoding = DB_ENCODING;
2128
    DB::$ssl = DB_SSL;
2129
    DB::$connect_options = DB_CONNECT_OPTIONS;
2130
    if (file_exists($tp_config_file) === false || $action === 'rebuild') {
2131
        // perform a copy
2132
        if (file_exists($tp_config_file)) {
2133
            if (! copy($tp_config_file, $tp_config_file . '.' . date('Y_m_d_His', time()))) {
2134
                return "ERROR: Could not copy file '" . $tp_config_file . "'";
2135
            }
2136
        }
2137
2138
        // regenerate
2139
        $data = [];
2140
        $data[0] = "<?php\n";
2141
        $data[1] = "global \$SETTINGS;\n";
2142
        $data[2] = "\$SETTINGS = array (\n";
2143
        $rows = DB::query(
2144
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s',
2145
            'admin'
2146
        );
2147
        foreach ($rows as $record) {
2148
            array_push($data, "    '" . $record['intitule'] . "' => '" . $record['valeur'] . "',\n");
2149
        }
2150
        array_push($data, ");\n");
2151
        $data = array_unique($data);
2152
    // ---
2153
    } elseif ($action === 'update' && empty($field) === false) {
2154
        $data = file($tp_config_file);
2155
        $inc = 0;
2156
        $bFound = false;
2157
        foreach ($data as $line) {
2158
            if (stristr($line, ');')) {
2159
                break;
2160
            }
2161
2162
            if (stristr($line, "'" . $field . "' => '")) {
2163
                $data[$inc] = "    '" . $field . "' => '" . filter_var($value, FILTER_SANITIZE_STRING) . "',\n";
2164
                $bFound = true;
2165
                break;
2166
            }
2167
            ++$inc;
2168
        }
2169
        if ($bFound === false) {
2170
            $data[$inc] = "    '" . $field . "' => '" . filter_var($value, FILTER_SANITIZE_STRING) . "',\n);\n";
2171
        }
2172
    }
2173
2174
    // update file
2175
    file_put_contents($tp_config_file, implode('', $data ?? []));
2176
    return true;
2177
}
2178
2179
/**
2180
 * Permits to replace &#92; to permit correct display
2181
 *
2182
 * @param string $input Some text
2183
 * 
2184
 * @return string
2185
 */
2186
function handleBackslash(string $input): string
2187
{
2188
    return str_replace('&amp;#92;', '&#92;', $input);
2189
}
2190
2191
/**
2192
 * Permits to load settings
2193
 * 
2194
 * @return void
2195
*/
2196
function loadSettings(): void
2197
{
2198
    global $SETTINGS;
2199
    /* LOAD CPASSMAN SETTINGS */
2200
    if (! isset($SETTINGS['loaded']) || $SETTINGS['loaded'] !== 1) {
2201
        $SETTINGS = [];
2202
        $SETTINGS['duplicate_folder'] = 0;
2203
        //by default, this is set to 0;
2204
        $SETTINGS['duplicate_item'] = 0;
2205
        //by default, this is set to 0;
2206
        $SETTINGS['number_of_used_pw'] = 5;
2207
        //by default, this value is set to 5;
2208
        $settings = [];
2209
        $rows = DB::query(
2210
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
2211
            [
2212
                'type' => 'admin',
2213
                'type2' => 'settings',
2214
            ]
2215
        );
2216
        foreach ($rows as $record) {
2217
            if ($record['type'] === 'admin') {
2218
                $SETTINGS[$record['intitule']] = $record['valeur'];
2219
            } else {
2220
                $settings[$record['intitule']] = $record['valeur'];
2221
            }
2222
        }
2223
        $SETTINGS['loaded'] = 1;
2224
        $SETTINGS['default_session_expiration_time'] = 5;
2225
    }
2226
}
2227
2228
/**
2229
 * check if folder has custom fields.
2230
 * Ensure that target one also has same custom fields
2231
 * 
2232
 * @param int $source_id
2233
 * @param int $target_id 
2234
 * 
2235
 * @return bool
2236
*/
2237
function checkCFconsistency(int $source_id, int $target_id): bool
2238
{
2239
    $source_cf = [];
2240
    $rows = DB::QUERY(
2241
        'SELECT id_category
2242
            FROM ' . prefixTable('categories_folders') . '
2243
            WHERE id_folder = %i',
2244
        $source_id
2245
    );
2246
    foreach ($rows as $record) {
2247
        array_push($source_cf, $record['id_category']);
2248
    }
2249
2250
    $target_cf = [];
2251
    $rows = DB::QUERY(
2252
        'SELECT id_category
2253
            FROM ' . prefixTable('categories_folders') . '
2254
            WHERE id_folder = %i',
2255
        $target_id
2256
    );
2257
    foreach ($rows as $record) {
2258
        array_push($target_cf, $record['id_category']);
2259
    }
2260
2261
    $cf_diff = array_diff($source_cf, $target_cf);
2262
    if (count($cf_diff) > 0) {
2263
        return false;
2264
    }
2265
2266
    return true;
2267
}
2268
2269
/**
2270
 * Will encrypte/decrypt a fil eusing Defuse.
2271
 *
2272
 * @param string $type        can be either encrypt or decrypt
2273
 * @param string $source_file path to source file
2274
 * @param string $target_file path to target file
2275
 * @param array  $SETTINGS    Settings
2276
 * @param string $password    A password
2277
 *
2278
 * @return string|bool
2279
 */
2280
function prepareFileWithDefuse(
2281
    string $type,
2282
    string $source_file,
2283
    string $target_file,
2284
    array $SETTINGS,
2285
    string $password = null
2286
) {
2287
    // Load AntiXSS
2288
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/AntiXSS.php';
2289
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/ASCII.php';
2290
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/UTF8.php';
2291
    $antiXss = new voku\helper\AntiXSS();
2292
    // Protect against bad inputs
2293
    if (is_array($source_file) === true || is_array($target_file) === true) {
2294
        return 'error_cannot_be_array';
2295
    }
2296
2297
    // Sanitize
2298
    $source_file = $antiXss->xss_clean($source_file);
2299
    $target_file = $antiXss->xss_clean($target_file);
2300
    if (empty($password) === true || is_null($password) === true) {
2301
        // get KEY to define password
2302
        $ascii_key = file_get_contents(SECUREPATH . '/teampass-seckey.txt');
2303
        $password = \Defuse\Crypto\Key::loadFromAsciiSafeString($ascii_key);
2304
    }
2305
2306
    $err = '';
2307
    if ($type === 'decrypt') {
2308
        // Decrypt file
2309
        $err = defuseFileDecrypt(
2310
            $source_file,
2311
            $target_file,
2312
            $SETTINGS, /** @scrutinizer ignore-type */
2313
            $password
2314
        );
2315
    } elseif ($type === 'encrypt') {
2316
        // Encrypt file
2317
        $err = defuseFileEncrypt(
2318
            $source_file,
2319
            $target_file,
2320
            $SETTINGS, /** @scrutinizer ignore-type */
2321
            $password
2322
        );
2323
    }
2324
2325
    // return error
2326
    return $err === true ? '' : $err;
2327
}
2328
2329
/**
2330
 * Encrypt a file with Defuse.
2331
 *
2332
 * @param string $source_file path to source file
2333
 * @param string $target_file path to target file
2334
 * @param array  $SETTINGS    Settings
2335
 * @param string $password    A password
2336
 *
2337
 * @return string|bool
2338
 */
2339
function defuseFileEncrypt(
2340
    string $source_file,
2341
    string $target_file,
2342
    array $SETTINGS,
2343
    string $password = null
2344
) {
2345
    // load PhpEncryption library
2346
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2347
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/CryptoException.php';
2348
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/BadFormatException.php';
2349
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/IOException.php';
2350
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/EnvironmentIsBrokenException.php';
2351
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/WrongKeyOrModifiedCiphertextException.php';
2352
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2353
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2354
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2355
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2356
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2357
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2358
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2359
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2360
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2361
    try {
2362
        \Defuse\Crypto\File::encryptFileWithPassword(
2363
            $source_file,
2364
            $target_file,
2365
            $password
2366
        );
2367
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2368
        $err = 'wrong_key';
2369
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2370
        $err = $ex;
2371
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2372
        $err = $ex;
2373
    }
2374
2375
    // return error
2376
    return empty($err) === false ? $err : true;
2377
}
2378
2379
/**
2380
 * Decrypt a file with Defuse.
2381
 *
2382
 * @param string $source_file path to source file
2383
 * @param string $target_file path to target file
2384
 * @param array  $SETTINGS    Settings
2385
 * @param string $password    A password
2386
 *
2387
 * @return string|bool
2388
 */
2389
function defuseFileDecrypt(
2390
    string $source_file,
2391
    string $target_file,
2392
    array $SETTINGS,
2393
    string $password = null
2394
) {
2395
    // load PhpEncryption library
2396
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2397
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/CryptoException.php';
2398
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/BadFormatException.php';
2399
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/IOException.php';
2400
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/EnvironmentIsBrokenException.php';
2401
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Exception/WrongKeyOrModifiedCiphertextException.php';
2402
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2403
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2404
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2405
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2406
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2407
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2408
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2409
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2410
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2411
    try {
2412
        \Defuse\Crypto\File::decryptFileWithPassword(
2413
            $source_file,
2414
            $target_file,
2415
            $password
2416
        );
2417
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2418
        $err = 'wrong_key';
2419
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2420
        $err = $ex;
2421
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2422
        $err = $ex;
2423
    }
2424
2425
    // return error
2426
    return empty($err) === false ? $err : true;
2427
}
2428
2429
/*
2430
* NOT TO BE USED
2431
*/
2432
/**
2433
 * Undocumented function.
2434
 *
2435
 * @param string $text Text to debug
2436
 */
2437
function debugTeampass(string $text): void
2438
{
2439
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
2440
    if ($debugFile !== false) {
2441
        fputs($debugFile, $text);
2442
        fclose($debugFile);
2443
    }
2444
}
2445
2446
/**
2447
 * DELETE the file with expected command depending on server type.
2448
 *
2449
 * @param string $file     Path to file
2450
 * @param array  $SETTINGS Teampass settings
2451
 *
2452
 * @return void
2453
 */
2454
function fileDelete(string $file, array $SETTINGS): void
2455
{
2456
    // Load AntiXSS
2457
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/ASCII.php';
2458
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/UTF8.php';
2459
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/AntiXSS.php';
2460
    $antiXss = new voku\helper\AntiXSS();
2461
    $file = $antiXss->xss_clean($file);
2462
    if (is_file($file)) {
2463
        unlink($file);
2464
    }
2465
}
2466
2467
/**
2468
 * Permits to extract the file extension.
2469
 *
2470
 * @param string $file File name
2471
 *
2472
 * @return string
2473
 */
2474
function getFileExtension(string $file): string
2475
{
2476
    if (strpos($file, '.') === false) {
2477
        return $file;
2478
    }
2479
2480
    return substr($file, strrpos($file, '.') + 1);
2481
}
2482
2483
/**
2484
 * Chmods files and folders with different permissions.
2485
 *
2486
 * This is an all-PHP alternative to using: \n
2487
 * <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2488
 * <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2489
 *
2490
 * @author Jeppe Toustrup (tenzer at tenzer dot dk)
2491
  *
2492
 * @param string $path      An either relative or absolute path to a file or directory which should be processed.
2493
 * @param int    $filePerm The permissions any found files should get.
2494
 * @param int    $dirPerm  The permissions any found folder should get.
2495
 *
2496
 * @return bool Returns TRUE if the path if found and FALSE if not.
2497
 *
2498
 * @warning The permission levels has to be entered in octal format, which
2499
 * normally means adding a zero ("0") in front of the permission level. \n
2500
 * More info at: http://php.net/chmod.
2501
*/
2502
2503
function recursiveChmod(
2504
    string $path,
2505
    int $filePerm = 0644,
2506
    int  $dirPerm = 0755
2507
) {
2508
    // Check if the path exists
2509
    if (! file_exists($path)) {
2510
        return false;
2511
    }
2512
2513
    // See whether this is a file
2514
    if (is_file($path)) {
2515
        // Chmod the file with our given filepermissions
2516
        chmod($path, $filePerm);
2517
    // If this is a directory...
2518
    } elseif (is_dir($path)) {
2519
        // Then get an array of the contents
2520
        $foldersAndFiles = scandir($path);
2521
        // Remove "." and ".." from the list
2522
        $entries = array_slice($foldersAndFiles, 2);
2523
        // Parse every result...
2524
        foreach ($entries as $entry) {
2525
            // And call this function again recursively, with the same permissions
2526
            recursiveChmod($path.'/'.$entry, $filePerm, $dirPerm);
2527
        }
2528
2529
        // When we are done with the contents of the directory, we chmod the directory itself
2530
        chmod($path, $dirPerm);
2531
    }
2532
2533
    // Everything seemed to work out well, return true
2534
    return true;
2535
}
2536
2537
/**
2538
 * Check if user can access to this item.
2539
 *
2540
 * @param int   $item_id ID of item
2541
 * @param array $SETTINGS
2542
 *
2543
 * @return bool|string
2544
 */
2545
function accessToItemIsGranted(int $item_id, array $SETTINGS)
2546
{
2547
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2548
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2549
    // Prepare superGlobal variables
2550
    $session_groupes_visibles = $superGlobal->get('groupes_visibles', 'SESSION');
2551
    $session_list_restricted_folders_for_items = $superGlobal->get('list_restricted_folders_for_items', 'SESSION');
2552
    // Load item data
2553
    $data = DB::queryFirstRow(
2554
        'SELECT id_tree
2555
        FROM ' . prefixTable('items') . '
2556
        WHERE id = %i',
2557
        $item_id
2558
    );
2559
    // Check if user can access this folder
2560
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2561
        // Now check if this folder is restricted to user
2562
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2563
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2564
        ) {
2565
            return 'ERR_FOLDER_NOT_ALLOWED';
2566
        }
2567
    }
2568
2569
    return true;
2570
}
2571
2572
/**
2573
 * Creates a unique key.
2574
 *
2575
 * @param int $lenght Key lenght
2576
 *
2577
 * @return string
2578
 */
2579
function uniqidReal(int $lenght = 13): string
2580
{
2581
    if (function_exists('random_bytes')) {
2582
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2583
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2584
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2585
    } else {
2586
        throw new Exception('no cryptographically secure random function available');
2587
    }
2588
2589
    return substr(bin2hex($bytes), 0, $lenght);
2590
}
2591
2592
/**
2593
 * Obfuscate an email.
2594
 *
2595
 * @param string $email Email address
2596
 *
2597
 * @return string
2598
 */
2599
function obfuscateEmail(string $email): string
2600
{
2601
    $email = explode("@", $email);
2602
    $name = $email[0];
2603
    if (strlen($name) > 3) {
2604
        $name = substr($name, 0, 2);
2605
        for ($i = 0; $i < strlen($email[0]) - 3; $i++) {
2606
            $name .= "*";
2607
        }
2608
        $name .= substr($email[0], -1, 1);
2609
    }
2610
    $host = explode(".", $email[1])[0];
2611
    if (strlen($host) > 3) {
2612
        $host = substr($host, 0, 1);
2613
        for ($i = 0; $i < strlen(explode(".", $email[1])[0]) - 2; $i++) {
2614
            $host .= "*";
2615
        }
2616
        $host .= substr(explode(".", $email[1])[0], -1, 1);
2617
    }
2618
    $email = $name . "@" . $host . "." . explode(".", $email[1])[1];
2619
    return $email;
2620
}
2621
2622
/**
2623
 * Perform a Query.
2624
 *
2625
 * @param array  $SETTINGS Teamapss settings
2626
 * @param string $fields   Fields to use
2627
 * @param string $table    Table to use
2628
 *
2629
 * @return array
2630
 */
2631
function performDBQuery(array $SETTINGS, string $fields, string $table): array
2632
{
2633
    // include librairies & connect to DB
2634
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2635
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2636
    if (defined('DB_PASSWD_CLEAR') === false) {
2637
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2638
    }
2639
    DB::$host = DB_HOST;
2640
    DB::$user = DB_USER;
2641
    DB::$password = DB_PASSWD_CLEAR;
2642
    DB::$dbName = DB_NAME;
2643
    DB::$port = DB_PORT;
2644
    DB::$encoding = DB_ENCODING;
2645
    DB::$ssl = DB_SSL;
2646
    DB::$connect_options = DB_CONNECT_OPTIONS;
2647
    // Insert log in DB
2648
    return DB::query(
2649
        'SELECT ' . $fields . '
2650
        FROM ' . prefixTable($table)
2651
    );
2652
}
2653
2654
/**
2655
 * Undocumented function.
2656
 *
2657
 * @param int $bytes Size of file
2658
 *
2659
 * @return string
2660
 */
2661
function formatSizeUnits(int $bytes): string
2662
{
2663
    if ($bytes >= 1073741824) {
2664
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2665
    } elseif ($bytes >= 1048576) {
2666
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2667
    } elseif ($bytes >= 1024) {
2668
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2669
    } elseif ($bytes > 1) {
2670
        $bytes .= ' bytes';
2671
    } elseif ($bytes === 1) {
2672
        $bytes .= ' byte';
2673
    } else {
2674
        $bytes = '0 bytes';
2675
    }
2676
2677
    return $bytes;
2678
}
2679
2680
/**
2681
 * Generate user pair of keys.
2682
 *
2683
 * @param string $userPwd User password
2684
 *
2685
 * @return array
2686
 */
2687
function generateUserKeys(string $userPwd): array
2688
{
2689
    // include library
2690
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2691
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2692
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2693
    // Load classes
2694
    $rsa = new Crypt_RSA();
2695
    $cipher = new Crypt_AES();
2696
    // Create the private and public key
2697
    $res = $rsa->createKey(4096);
2698
    // Encrypt the privatekey
2699
    $cipher->setPassword($userPwd);
2700
    $privatekey = $cipher->encrypt($res['privatekey']);
2701
    return [
2702
        'private_key' => base64_encode($privatekey),
2703
        'public_key' => base64_encode($res['publickey']),
2704
        'private_key_clear' => base64_encode($res['privatekey']),
2705
    ];
2706
}
2707
2708
/**
2709
 * Permits to decrypt the user's privatekey.
2710
 *
2711
 * @param string $userPwd        User password
2712
 * @param string $userPrivateKey User private key
2713
 *
2714
 * @return string
2715
 */
2716
function decryptPrivateKey(string $userPwd, string $userPrivateKey): string
2717
{
2718
    if (empty($userPwd) === false) {
2719
        include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2720
        // Load classes
2721
        $cipher = new Crypt_AES();
2722
        // Encrypt the privatekey
2723
        $cipher->setPassword($userPwd);
2724
        try {
2725
            return base64_encode((string) $cipher->decrypt(base64_decode($userPrivateKey)));
2726
        } catch (Exception $e) {
2727
            return $e;
2728
        }
2729
    }
2730
    return '';
2731
}
2732
2733
/**
2734
 * Permits to encrypt the user's privatekey.
2735
 *
2736
 * @param string $userPwd        User password
2737
 * @param string $userPrivateKey User private key
2738
 *
2739
 * @return string
2740
 */
2741
function encryptPrivateKey(string $userPwd, string $userPrivateKey): string
2742
{
2743
    if (empty($userPwd) === false) {
2744
        include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2745
        // Load classes
2746
        $cipher = new Crypt_AES();
2747
        // Encrypt the privatekey
2748
        $cipher->setPassword($userPwd);        
2749
        try {
2750
            return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2751
        } catch (Exception $e) {
2752
            return $e;
2753
        }
2754
    }
2755
    return '';
2756
}
2757
2758
/**
2759
 * Encrypts a string using AES.
2760
 *
2761
 * @param string $data String to encrypt
2762
 *
2763
 * @return array
2764
 */
2765
function doDataEncryption(string $data): array
2766
{
2767
    // Includes
2768
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2769
    // Load classes
2770
    $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2771
    // Generate an object key
2772
    $objectKey = uniqidReal(32);
2773
    // Set it as password
2774
    $cipher->setPassword($objectKey);
2775
    return [
2776
        'encrypted' => base64_encode($cipher->encrypt($data)),
2777
        'objectKey' => base64_encode($objectKey),
2778
    ];
2779
}
2780
2781
/**
2782
 * Decrypts a string using AES.
2783
 *
2784
 * @param string $data Encrypted data
2785
 * @param string $key  Key to uncrypt
2786
 *
2787
 * @return string
2788
 */
2789
function doDataDecryption(string $data, string $key): string
2790
{
2791
    // Includes
2792
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2793
    // Load classes
2794
    $cipher = new Crypt_AES();
2795
    // Set the object key
2796
    $cipher->setPassword(base64_decode($key));
2797
    return base64_encode($cipher->decrypt(base64_decode($data)));
2798
}
2799
2800
/**
2801
 * Encrypts using RSA a string using a public key.
2802
 *
2803
 * @param string $key       Key to be encrypted
2804
 * @param string $publicKey User public key
2805
 *
2806
 * @return string
2807
 */
2808
function encryptUserObjectKey(string $key, string $publicKey): string
2809
{
2810
    // Includes
2811
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2812
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2813
    // Load classes
2814
    $rsa = new Crypt_RSA();
2815
    $rsa->loadKey(base64_decode($publicKey));
2816
    // Encrypt
2817
    return base64_encode($rsa->encrypt(base64_decode($key)));
2818
}
2819
2820
/**
2821
 * Decrypts using RSA an encrypted string using a private key.
2822
 *
2823
 * @param string $key        Encrypted key
2824
 * @param string $privateKey User private key
2825
 *
2826
 * @return string
2827
 */
2828
function decryptUserObjectKey(string $key, string $privateKey): string
2829
{
2830
    // Includes
2831
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2832
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2833
    // Load classes
2834
    $rsa = new Crypt_RSA();
2835
    $rsa->loadKey(base64_decode($privateKey));
2836
    // Decrypt
2837
    try {
2838
        $tmpValue = $rsa->decrypt(base64_decode($key));
2839
        if (is_bool($tmpValue) === false) {
0 ignored issues
show
introduced by
The condition is_bool($tmpValue) === false is always true.
Loading history...
2840
            $ret = base64_encode($tmpValue);
0 ignored issues
show
Bug introduced by
$tmpValue of type boolean is incompatible with the type string expected by parameter $string of base64_encode(). ( Ignorable by Annotation )

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

2840
            $ret = base64_encode(/** @scrutinizer ignore-type */ $tmpValue);
Loading history...
2841
        } else {
2842
            $ret = '';
2843
        }
2844
    } catch (Exception $e) {
2845
        return $e;
2846
    }
2847
2848
    return $ret;
2849
}
2850
2851
/**
2852
 * Encrypts a file.
2853
 *
2854
 * @param string $fileInName File name
2855
 * @param string $fileInPath Path to file
2856
 *
2857
 * @return array
2858
 */
2859
function encryptFile(string $fileInName, string $fileInPath): array
2860
{
2861
    if (defined('FILE_BUFFER_SIZE') === false) {
2862
        define('FILE_BUFFER_SIZE', 128 * 1024);
2863
    }
2864
2865
    // Includes
2866
    include_once __DIR__.'/../includes/config/include.php';
2867
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2868
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2869
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2870
    // Load classes
2871
    $cipher = new Crypt_AES();
2872
    // Generate an object key
2873
    $objectKey = uniqidReal(32);
2874
    // Set it as password
2875
    $cipher->setPassword($objectKey);
2876
    // Prevent against out of memory
2877
    $cipher->enableContinuousBuffer();
2878
    //$cipher->disablePadding();
2879
2880
    // Encrypt the file content
2881
    $plaintext = file_get_contents(
2882
        filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL)
2883
    );
2884
    $ciphertext = $cipher->encrypt($plaintext);
2885
    // Save new file
2886
    $hash = md5($plaintext);
2887
    $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2888
    file_put_contents($fileOut, $ciphertext);
2889
    unlink($fileInPath . '/' . $fileInName);
2890
    return [
2891
        'fileHash' => base64_encode($hash),
2892
        'objectKey' => base64_encode($objectKey),
2893
    ];
2894
}
2895
2896
/**
2897
 * Decrypt a file.
2898
 *
2899
 * @param string $fileName File name
2900
 * @param string $filePath Path to file
2901
 * @param string $key      Key to use
2902
 *
2903
 * @return string
2904
 */
2905
function decryptFile(string $fileName, string $filePath, string $key): string
2906
{
2907
    if (! defined('FILE_BUFFER_SIZE')) {
2908
        define('FILE_BUFFER_SIZE', 128 * 1024);
2909
    }
2910
2911
    // Includes
2912
    include_once __DIR__.'/../includes/config/include.php';
2913
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2914
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2915
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2916
    // Get file name
2917
    $fileName = base64_decode($fileName);
2918
    // Load classes
2919
    $cipher = new Crypt_AES();
2920
    // Set the object key
2921
    $cipher->setPassword(base64_decode($key));
2922
    // Prevent against out of memory
2923
    $cipher->enableContinuousBuffer();
2924
    $cipher->disablePadding();
2925
    // Get file content
2926
    $ciphertext = file_get_contents($filePath . '/' . TP_FILE_PREFIX . $fileName);
2927
    // Decrypt file content and return
2928
    return base64_encode($cipher->decrypt($ciphertext));
2929
}
2930
2931
/**
2932
 * Generate a simple password
2933
 *
2934
 * @param int $length Length of string
2935
 * @param bool $symbolsincluded Allow symbols
2936
 *
2937
 * @return string
2938
 */
2939
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2940
{
2941
    // Generate new user password
2942
    $small_letters = range('a', 'z');
2943
    $big_letters = range('A', 'Z');
2944
    $digits = range(0, 9);
2945
    $symbols = $symbolsincluded === true ?
2946
        ['#', '_', '-', '@', '$', '+', '&'] : [];
2947
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2948
    $count = count($res);
2949
    // first variant
2950
2951
    $random_string = '';
2952
    for ($i = 0; $i < $length; ++$i) {
2953
        $random_string .= $res[random_int(0, $count - 1)];
2954
    }
2955
2956
    return $random_string;
2957
}
2958
2959
/**
2960
 * Permit to store the sharekey of an object for users.
2961
 *
2962
 * @param string $object_name             Type for table selection
2963
 * @param int    $post_folder_is_personal Personal
2964
 * @param int    $post_folder_id          Folder
2965
 * @param int    $post_object_id          Object
2966
 * @param string $objectKey               Object key
2967
 * @param array  $SETTINGS                Teampass settings
2968
 *
2969
 * @return void
2970
 */
2971
function storeUsersShareKey(
2972
    string $object_name,
2973
    int $post_folder_is_personal,
2974
    int $post_folder_id,
2975
    int $post_object_id,
2976
    string $objectKey,
2977
    array $SETTINGS
2978
): void {
2979
    // include librairies & connect to DB
2980
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2981
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2982
    if (defined('DB_PASSWD_CLEAR') === false) {
2983
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2984
    }
2985
    DB::$host = DB_HOST;
2986
    DB::$user = DB_USER;
2987
    DB::$password = DB_PASSWD_CLEAR;
2988
    DB::$dbName = DB_NAME;
2989
    DB::$port = DB_PORT;
2990
    DB::$encoding = DB_ENCODING;
2991
    DB::$ssl = DB_SSL;
2992
    DB::$connect_options = DB_CONNECT_OPTIONS;
2993
    // Delete existing entries for this object
2994
    DB::delete(
2995
        $object_name,
2996
        'object_id = %i',
2997
        $post_object_id
2998
    );
2999
    // Superglobals
3000
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
3001
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
3002
    // Prepare superGlobal variables
3003
    $sessionPpersonaFolders = $superGlobal->get('personal_folders', 'SESSION');
3004
    $sessionUserId = $superGlobal->get('user_id', 'SESSION');
3005
    $sessionUserPublicKey = $superGlobal->get('public_key', 'SESSION', 'user');
3006
    if (
3007
        (int) $post_folder_is_personal === 1
3008
        && in_array($post_folder_id, $sessionPpersonaFolders) === true
3009
    ) {
3010
        // If this is a personal object
3011
        // Only create the sharekey for user
3012
        DB::insert(
3013
            $object_name,
3014
            [
3015
                'object_id' => (int) $post_object_id,
3016
                'user_id' => (int) $sessionUserId,
3017
                'share_key' => encryptUserObjectKey($objectKey, $sessionUserPublicKey),
3018
            ]
3019
        );
3020
    } else {
3021
        // This is a public object
3022
        // Create sharekey for each user
3023
        $users = DB::query(
3024
            'SELECT id, public_key
3025
            FROM ' . prefixTable('users') . '
3026
            WHERE id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '")
3027
            AND public_key != ""'
3028
        );
3029
        foreach ($users as $user) {
3030
            // Insert in DB the new object key for this item by user
3031
            DB::insert(
3032
                $object_name,
3033
                [
3034
                    'object_id' => $post_object_id,
3035
                    'user_id' => (int) $user['id'],
3036
                    'share_key' => encryptUserObjectKey(
3037
                        $objectKey,
3038
                        $user['public_key']
3039
                    ),
3040
                ]
3041
            );
3042
        }
3043
    }
3044
}
3045
3046
/**
3047
 * Is this string base64 encoded?
3048
 *
3049
 * @param string $str Encoded string?
3050
 *
3051
 * @return bool
3052
 */
3053
function isBase64(string $str): bool
3054
{
3055
    $str = (string) trim($str);
3056
    if (! isset($str[0])) {
3057
        return false;
3058
    }
3059
3060
    $base64String = (string) base64_decode($str, true);
3061
    if ($base64String && base64_encode($base64String) === $str) {
3062
        return true;
3063
    }
3064
3065
    return false;
3066
}
3067
3068
/**
3069
 * Undocumented function
3070
 *
3071
 * @param string $field Parameter
3072
 *
3073
 * @return array|bool|resource|string
3074
 */
3075
function filterString(string $field)
3076
{
3077
    // Sanitize string
3078
    $field = filter_var(trim($field), FILTER_SANITIZE_STRING);
3079
    if (empty($field) === false) {
3080
        // Load AntiXSS
3081
        include_once __DIR__.'/../includes/libraries/voku/helper/AntiXSS.php';
3082
        $antiXss = new voku\helper\AntiXSS();
3083
        // Return
3084
        return $antiXss->xss_clean($field);
3085
    }
3086
3087
    return false;
3088
}
3089
3090
/**
3091
 * CHeck if provided credentials are allowed on server
3092
 *
3093
 * @param string $login    User Login
3094
 * @param string $password User Pwd
3095
 * @param array  $SETTINGS Teampass settings
3096
 *
3097
 * @return bool
3098
 */
3099
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
3100
{
3101
    // Build ldap configuration array
3102
    $config = [
3103
        // Mandatory Configuration Options
3104
        'hosts' => [$SETTINGS['ldap_hosts']],
3105
        'base_dn' => $SETTINGS['ldap_bdn'],
3106
        'username' => $SETTINGS['ldap_username'],
3107
        'password' => $SETTINGS['ldap_password'],
3108
3109
        // Optional Configuration Options
3110
        'port' => $SETTINGS['ldap_port'],
3111
        'use_ssl' => (int) $SETTINGS['ldap_ssl'] === 1 ? true : false,
3112
        'use_tls' => (int) $SETTINGS['ldap_tls'] === 1 ? true : false,
3113
        'version' => 3,
3114
        'timeout' => 5,
3115
        'follow_referrals' => false,
3116
3117
        // Custom LDAP Options
3118
        'options' => [
3119
            // See: http://php.net/ldap_set_option
3120
            LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_HARD,
3121
        ],
3122
    ];
3123
    // Load expected libraries
3124
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Traits/Macroable.php';
3125
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Arr.php';
3126
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/DetectsErrors.php';
3127
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Connection.php';
3128
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/LdapInterface.php';
3129
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Ldap.php';
3130
    $ad = new SplClassLoader('LdapRecord', '../includes/libraries');
3131
    $ad->register();
3132
    $connection = new Connection($config);
3133
    // Connect to LDAP
3134
    try {
3135
        $connection->connect();
3136
    } catch (\LdapRecord\Auth\BindException $e) {
3137
        $error = $e->getDetailedError();
3138
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
3139
        return false;
3140
    }
3141
3142
    // Authenticate user
3143
    try {
3144
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory') {
3145
            $connection->auth()->attempt($login, $password, $stayAuthenticated = true);
3146
        } else {
3147
            $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);
3148
        }
3149
    } catch (\LdapRecord\Auth\BindException $e) {
3150
        $error = $e->getDetailedError();
3151
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
3152
        return false;
3153
    }
3154
3155
    return true;
3156
}
3157
3158
/**
3159
 * Removes from DB all sharekeys of this user
3160
 *
3161
 * @param int $userId User's id
3162
 * @param array   $SETTINGS Teampass settings
3163
 *
3164
 * @return bool
3165
 */
3166
function deleteUserObjetsKeys(int $userId, array $SETTINGS): bool
3167
{
3168
    // include librairies & connect to DB
3169
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
3170
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
3171
    if (defined('DB_PASSWD_CLEAR') === false) {
3172
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
3173
    }
3174
    DB::$host = DB_HOST;
3175
    DB::$user = DB_USER;
3176
    DB::$password = DB_PASSWD_CLEAR;
3177
    DB::$dbName = DB_NAME;
3178
    DB::$port = DB_PORT;
3179
    DB::$encoding = DB_ENCODING;
3180
    DB::$ssl = DB_SSL;
3181
    DB::$connect_options = DB_CONNECT_OPTIONS;
3182
    // Remove all item sharekeys items
3183
    DB::delete(
3184
        prefixTable('sharekeys_items'),
3185
        'user_id = %i',
3186
        $userId
3187
    );
3188
    // Remove all item sharekeys files
3189
    DB::delete(
3190
        prefixTable('sharekeys_files'),
3191
        'user_id = %i',
3192
        $userId
3193
    );
3194
    // Remove all item sharekeys fields
3195
    DB::delete(
3196
        prefixTable('sharekeys_fields'),
3197
        'user_id = %i',
3198
        $userId
3199
    );
3200
    // Remove all item sharekeys logs
3201
    DB::delete(
3202
        prefixTable('sharekeys_logs'),
3203
        'user_id = %i',
3204
        $userId
3205
    );
3206
    // Remove all item sharekeys suggestions
3207
    DB::delete(
3208
        prefixTable('sharekeys_suggestions'),
3209
        'user_id = %i',
3210
        $userId
3211
    );
3212
    return false;
3213
}
3214
3215
/**
3216
 * Manage list of timezones   $SETTINGS Teampass settings
3217
 *
3218
 * @return array
3219
 */
3220
function timezone_list()
3221
{
3222
    static $timezones = null;
3223
    if ($timezones === null) {
3224
        $timezones = [];
3225
        $offsets = [];
3226
        $now = new DateTime('now', new DateTimeZone('UTC'));
3227
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
3228
            $now->setTimezone(new DateTimeZone($timezone));
3229
            $offsets[] = $offset = $now->getOffset();
3230
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
3231
        }
3232
3233
        array_multisort($offsets, $timezones);
3234
    }
3235
3236
    return $timezones;
3237
}
3238
3239
/**
3240
 * Provide timezone offset
3241
 *
3242
 * @param int $offset Timezone offset
3243
 *
3244
 * @return string
3245
 */
3246
function format_GMT_offset($offset): string
3247
{
3248
    $hours = intval($offset / 3600);
3249
    $minutes = abs(intval($offset % 3600 / 60));
3250
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
3251
}
3252
3253
/**
3254
 * Provides timezone name
3255
 *
3256
 * @param string $name Timezone name
3257
 *
3258
 * @return string
3259
 */
3260
function format_timezone_name($name): string
3261
{
3262
    $name = str_replace('/', ', ', $name);
3263
    $name = str_replace('_', ' ', $name);
3264
3265
    return str_replace('St ', 'St. ', $name);
3266
}
3267
3268
/**
3269
 * Provides info if user should use MFA based on roles
3270
 *
3271
 * @param string $userRolesIds  User roles ids
3272
 * @param string $mfaRoles      Roles for which MFA is requested
3273
 *
3274
 * @return bool
3275
 */
3276
function mfa_auth_requested_roles(string $userRolesIds, string $mfaRoles): bool
3277
{
3278
    if (empty($mfaRoles) === true) {
3279
        return true;
3280
    }
3281
3282
    $mfaRoles = array_values(json_decode($mfaRoles, true));
3283
    $userRolesIds = array_filter(explode(';', $userRolesIds));
3284
    if (count($mfaRoles) === 0 || count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
3285
        return true;
3286
    }
3287
3288
    return false;
3289
}
3290
3291
/**
3292
 * Permits to clean a string for export purpose
3293
 *
3294
 * @param string $text
3295
 * @param bool $emptyCheckOnly
3296
 * 
3297
 * @return string
3298
 */
3299
function cleanStringForExport(string $text, bool $emptyCheckOnly = false): string
3300
{
3301
    if (is_null($text) === true || empty($text) === true) {
3302
        return '';
3303
    }
3304
    // only expected to check if $text was empty
3305
    elseif ($emptyCheckOnly === true) {
3306
        return $text;
3307
    }
3308
3309
    return strip_tags(
3310
        cleanString(
3311
            html_entity_decode($text, ENT_QUOTES | ENT_XHTML, 'UTF-8'),
3312
            true)
3313
        );
3314
}
3315
3316
/**
3317
 * Permits to check if user ID is valid
3318
 *
3319
 * @param integer $post_user_id
3320
 * @return bool
3321
 */
3322
function isUserIdValid($userId): bool
3323
{
3324
    if (is_null($userId) === false
3325
        && isset($userId) === true
3326
        && empty($userId) === false
3327
    ) {
3328
        return true;
3329
    }
3330
    return false;
3331
}
3332
3333
/**
3334
 * Check if a key exists and if its value equal the one expected
3335
 *
3336
 * @param string $key
3337
 * @param integer|string $value
3338
 * @param array $array
3339
 * 
3340
 * @return boolean
3341
 */
3342
function isKeyExistingAndEqual(
3343
    string $key,
3344
    /*PHP8 - integer|string*/$value,
3345
    array $array
3346
): bool
3347
{
3348
    if (isset($array[$key]) === true
3349
        && (is_int($value) === true ?
3350
            (int) $array[$key] === $value :
3351
            (string) $array[$key] === $value)
3352
    ) {
3353
        return true;
3354
    }
3355
    return false;
3356
}
3357
3358
/**
3359
 * Check if a variable is not set or equal to a value
3360
 *
3361
 * @param string|null $var
3362
 * @param integer|string $value
3363
 * 
3364
 * @return boolean
3365
 */
3366
function isKeyNotSetOrEqual(
3367
    /*PHP8 - string|null*/$var,
3368
    /*PHP8 - integer|string*/$value
3369
): bool
3370
{
3371
    if (isset($var) === false
3372
        || (is_int($value) === true ?
3373
            (int) $var === $value :
3374
            (string) $var === $value)
3375
    ) {
3376
        return true;
3377
    }
3378
    return false;
3379
}
3380
3381
/**
3382
 * Check if a key exists and if its value < to the one expected
3383
 *
3384
 * @param string $key
3385
 * @param integer $value
3386
 * @param array $array
3387
 * 
3388
 * @return boolean
3389
 */
3390
function isKeyExistingAndInferior(string $key, int $value, array $array): bool
3391
{
3392
    if (isset($array[$key]) === true && (int) $array[$key] < $value) {
3393
        return true;
3394
    }
3395
    return false;
3396
}
3397
3398
/**
3399
 * Check if a key exists and if its value > to the one expected
3400
 *
3401
 * @param string $key
3402
 * @param integer $value
3403
 * @param array $array
3404
 * 
3405
 * @return boolean
3406
 */
3407
function isKeyExistingAndSuperior(string $key, int $value, array $array): bool
3408
{
3409
    if (isset($array[$key]) === true && (int) $array[$key] > $value) {
3410
        return true;
3411
    }
3412
    return false;
3413
}
3414
3415
/**
3416
 * Check if values in array are set
3417
 * Return true if all set
3418
 * Return false if one of them is not set
3419
 *
3420
 * @param array $arrayOfValues
3421
 * @return boolean
3422
 */
3423
function isSetArrayOfValues(array $arrayOfValues): bool
3424
{
3425
    foreach($arrayOfValues as $value) {
3426
        if (isset($value) === false) {
3427
            return false;
3428
        }
3429
    }
3430
    return true;
3431
}
3432
3433
/**
3434
 * Check if values in array are set
3435
 * Return true if all set
3436
 * Return false if one of them is not set
3437
 *
3438
 * @param array $arrayOfValues
3439
 * @param integer|string $value
3440
 * @return boolean
3441
 */
3442
function isArrayOfVarsEqualToValue(
3443
    array $arrayOfVars,
3444
    /*PHP8 - integer|string*/$value
3445
) : bool
3446
{
3447
    foreach($arrayOfVars as $variable) {
3448
        if ($variable !== $value) {
3449
            return false;
3450
        }
3451
    }
3452
    return true;
3453
}
3454
3455
/**
3456
 * Checks if at least one variable in array is equal to value
3457
 *
3458
 * @param array $arrayOfValues
3459
 * @param integer|string $value
3460
 * @return boolean
3461
 */
3462
function isOneVarOfArrayEqualToValue(
3463
    array $arrayOfVars,
3464
    /*PHP8 - integer|string*/$value
3465
) : bool
3466
{
3467
    foreach($arrayOfVars as $variable) {
3468
        if ($variable === $value) {
3469
            return true;
3470
        }
3471
    }
3472
    return false;
3473
}
3474
3475
/**
3476
 * Checks is value is null, not set OR empty
3477
 *
3478
 * @param string|int|null $value
3479
 * @return boolean
3480
 */
3481
function isValueSetNullEmpty(/*PHP8 - string|int|null*/ $value) : bool
3482
{
3483
    if (is_null($value) === true || isset($value) === false || empty($value) === true) {
3484
        return true;
3485
    }
3486
    return false;
3487
}
3488
3489
/**
3490
 * Checks if value is set and if empty is equal to passed boolean
3491
 *
3492
 * @param string|int $value
3493
 * @param boolean $boolean
3494
 * @return boolean
3495
 */
3496
function isValueSetEmpty($value, $boolean = true) : bool
3497
{
3498
    if (isset($value) === true && empty($value) === $boolean) {
3499
        return true;
3500
    }
3501
    return false;
3502
}
3503
3504
/**
3505
 * Ensure Complexity is translated
3506
 *
3507
 * @return void
3508
 */
3509
function defineComplexity() : void
3510
{
3511
    if (defined('TP_PW_COMPLEXITY') === false) {
3512
        define(
3513
            'TP_PW_COMPLEXITY',
3514
            [
3515
                TP_PW_STRENGTH_1 => array(TP_PW_STRENGTH_1, langHdl('complex_level1'), 'fas fa-thermometer-empty text-danger'),
3516
                TP_PW_STRENGTH_2 => array(TP_PW_STRENGTH_2, langHdl('complex_level2'), 'fas fa-thermometer-quarter text-warning'),
3517
                TP_PW_STRENGTH_3 => array(TP_PW_STRENGTH_3, langHdl('complex_level3'), 'fas fa-thermometer-half text-warning'),
3518
                TP_PW_STRENGTH_4 => array(TP_PW_STRENGTH_4, langHdl('complex_level4'), 'fas fa-thermometer-three-quarters text-success'),
3519
                TP_PW_STRENGTH_5 => array(TP_PW_STRENGTH_5, langHdl('complex_level5'), 'fas fa-thermometer-full text-success'),
3520
            ]
3521
        );
3522
    }
3523
}
3524
3525
/**
3526
 * Uses Sanitizer to perform data sanitization
3527
 *
3528
 * @param array     $data
3529
 * @param array     $filters
3530
 * @param string    $path
3531
 * @return array
3532
 */
3533
function dataSanitizer(
3534
    array $data,
3535
    array $filters,
3536
    string $path
3537
): array
3538
{
3539
    // Load Sanitizer library
3540
    require_once $path . '/includes/libraries/Illuminate/Support/Traits/Macroable.php';
3541
    require_once $path . '/includes/libraries/Illuminate/Support/Str.php';
3542
    require_once $path . '/includes/libraries/Illuminate/Validation/ValidationRuleParser.php';
3543
    require_once $path . '/includes/libraries/Illuminate/Support/Arr.php';
3544
    require_once $path . '/includes/libraries/Elegant/sanitizer/Contracts/Filter.php';
3545
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/Trim.php';
3546
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/Cast.php';
3547
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/EscapeHTML.php';
3548
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/EmptyStringToNull.php';
3549
    require_once $path . '/includes/libraries/Elegant/sanitizer/Sanitizer.php';
3550
3551
    // Sanitize post and get variables
3552
    $sanitizer = new Elegant\sanitizer\Sanitizer($data, $filters);
3553
    return $sanitizer->sanitize();
3554
}
3555
3556
/**
3557
 * Permits to manage the cache tree for a user
3558
 *
3559
 * @param integer $user_id
3560
 * @param string $data
3561
 * @param array $SETTINGS
3562
 * @param string $field_update
3563
 * @return void
3564
 */
3565
function cacheTreeUserHandler(int $user_id, string $data, array $SETTINGS, string $field_update = '')
3566
{
3567
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
3568
    //Connect to DB
3569
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
3570
    if (defined('DB_PASSWD_CLEAR') === false) {
3571
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
3572
    }
3573
    DB::$host = DB_HOST;
3574
    DB::$user = DB_USER;
3575
    DB::$password = DB_PASSWD_CLEAR;
3576
    DB::$dbName = DB_NAME;
3577
    DB::$port = DB_PORT;
3578
    DB::$encoding = DB_ENCODING;
3579
    DB::$ssl = DB_SSL;
3580
    DB::$connect_options = DB_CONNECT_OPTIONS;
3581
3582
    // Exists ?
3583
    $userCacheId = DB::queryfirstrow(
3584
        'SELECT increment_id
3585
        FROM ' . prefixTable('cache_tree') . '
3586
        WHERE user_id = %i',
3587
        $user_id
3588
    );
3589
    
3590
    if (is_null($userCacheId) === true || count($userCacheId) === 0) {
3591
        DB::insert(
3592
            prefixTable('cache_tree'),
3593
            array(
3594
                'data' => $data,
3595
                'timestamp' => time(),
3596
                'user_id' => $user_id,
3597
                'visible_folders' => '',
3598
            )
3599
        );
3600
    } else {
3601
        if (empty($field_update) === true) {
3602
            DB::update(
3603
                prefixTable('cache_tree'),
3604
                [
3605
                    'timestamp' => time(),
3606
                    'data' => $data,
3607
                ],
3608
                'increment_id = %i',
3609
                $userCacheId['increment_id']
3610
            );
3611
        } else {
3612
            DB::update(
3613
                prefixTable('cache_tree'),
3614
                [
3615
                    $field_update => $data,
3616
                ],
3617
                'increment_id = %i',
3618
                $userCacheId['increment_id']
3619
            );
3620
        }
3621
    }
3622
}
3623
3624
/**
3625
 * Permits to calculate a %
3626
 *
3627
 * @param float $nombre
3628
 * @param float $total
3629
 * @param float $pourcentage
3630
 * @return float
3631
 */
3632
function pourcentage(float $nombre, float $total, float $pourcentage): float
3633
{ 
3634
    $resultat = ($nombre/$total) * $pourcentage;
3635
    return round($resultat);
3636
}
3637
3638
/**
3639
 * Load the folders list from the cache
3640
 *
3641
 * @param string $fieldName
3642
 * @param string $sessionName
3643
 * @param boolean $forceRefresh
3644
 * @return array
3645
 */
3646
function loadFoldersListByCache(
3647
    string $fieldName,
3648
    string $sessionName,
3649
    bool $forceRefresh = false
3650
): array
3651
{
3652
    // Case when refresh is EXPECTED / MANDATORY
3653
    if ($forceRefresh === true) {
3654
        return [
3655
            'state' => false,
3656
            'data' => [],
3657
        ];
3658
    }
3659
3660
    // Get last folder update
3661
    $lastFolderChange = DB::queryfirstrow(
3662
        'SELECT valeur FROM ' . prefixTable('misc') . '
3663
        WHERE type = %s AND intitule = %s',
3664
        'timestamp',
3665
        'last_folder_change'
3666
    );
3667
    if (DB::count() === 0) {
3668
        $lastFolderChange['valeur'] = 0;
3669
    }
3670
3671
    // Case when an update in the tree has been done
3672
    // Refresh is then mandatory
3673
    if ((int) $lastFolderChange['valeur'] > (int) (isset($_SESSION['user_tree_last_refresh_timestamp']) === true ? $_SESSION['user_tree_last_refresh_timestamp'] : 0)) {
3674
        return [
3675
            'state' => false,
3676
            'data' => [],
3677
        ];
3678
    }
3679
3680
    // Does this user has the tree structure in session?
3681
    // If yes then use it
3682
    if (count(isset($_SESSION['teampassUser'][$sessionName]) === true ? $_SESSION['teampassUser'][$sessionName] : []) > 0) {
3683
        return [
3684
            'state' => true,
3685
            'data' => json_encode($_SESSION['teampassUser'][$sessionName]),
3686
        ];
3687
    }
3688
3689
    // Does this user has a tree cache
3690
    $userCacheTree = DB::queryfirstrow(
3691
        'SELECT '.$fieldName.'
3692
        FROM ' . prefixTable('cache_tree') . '
3693
        WHERE user_id = %i',
3694
        $_SESSION['user_id']
3695
    );
3696
    if (empty($userCacheTree[$fieldName]) === false && $userCacheTree[$fieldName] !== '[]') {
3697
        return [
3698
            'state' => true,
3699
            'data' => $userCacheTree[$fieldName],
3700
        ];
3701
    }
3702
3703
    return [
3704
        'state' => false,
3705
        'data' => [],
3706
    ];
3707
}
3708
3709
3710
/**
3711
 * Permits to refresh the categories of folders
3712
 *
3713
 * @param array $folderIds
3714
 * @return void
3715
 */
3716
function handleFoldersCategories(
3717
    array $folderIds
3718
)
3719
{
3720
    //load ClassLoader
3721
    include_once __DIR__. '/../sources/SplClassLoader.php';
3722
    
3723
    //Connect to DB
3724
    include_once __DIR__. '/../includes/libraries/Database/Meekrodb/db.class.php';
3725
    if (defined('DB_PASSWD_CLEAR') === false) {
3726
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, []));
3727
    }
3728
    DB::$host = DB_HOST;
3729
    DB::$user = DB_USER;
3730
    DB::$password = DB_PASSWD_CLEAR;
3731
    DB::$dbName = DB_NAME;
3732
    DB::$port = DB_PORT;
3733
    DB::$encoding = DB_ENCODING;
3734
    DB::$ssl = DB_SSL;
3735
    DB::$connect_options = DB_CONNECT_OPTIONS;
3736
3737
    $arr_data = array();
3738
3739
    // force full list of folders
3740
    if (count($folderIds) === 0) {
3741
        $folderIds = DB::queryFirstColumn(
3742
            'SELECT id
3743
            FROM ' . prefixTable('nested_tree') . '
3744
            WHERE personal_folder=%i',
3745
            0
3746
        );
3747
    }
3748
3749
    // Get complexity
3750
    defineComplexity();
3751
3752
    // update
3753
    foreach ($folderIds as $folder) {
3754
        // Do we have Categories
3755
        // get list of associated Categories
3756
        $arrCatList = array();
3757
        $rows_tmp = DB::query(
3758
            'SELECT c.id, c.title, c.level, c.type, c.masked, c.order, c.encrypted_data, c.role_visibility, c.is_mandatory,
3759
            f.id_category AS category_id
3760
            FROM ' . prefixTable('categories_folders') . ' AS f
3761
            INNER JOIN ' . prefixTable('categories') . ' AS c ON (f.id_category = c.parent_id)
3762
            WHERE id_folder=%i',
3763
            $folder
3764
        );
3765
        if (DB::count() > 0) {
3766
            foreach ($rows_tmp as $row) {
3767
                $arrCatList[$row['id']] = array(
3768
                    'id' => $row['id'],
3769
                    'title' => $row['title'],
3770
                    'level' => $row['level'],
3771
                    'type' => $row['type'],
3772
                    'masked' => $row['masked'],
3773
                    'order' => $row['order'],
3774
                    'encrypted_data' => $row['encrypted_data'],
3775
                    'role_visibility' => $row['role_visibility'],
3776
                    'is_mandatory' => $row['is_mandatory'],
3777
                    'category_id' => $row['category_id'],
3778
                );
3779
            }
3780
        }
3781
        $arr_data['categories'] = $arrCatList;
3782
3783
        // Now get complexity
3784
        $valTemp = '';
3785
        $data = DB::queryFirstRow(
3786
            'SELECT valeur
3787
            FROM ' . prefixTable('misc') . '
3788
            WHERE type = %s AND intitule=%i',
3789
            'complex',
3790
            $folder
3791
        );
3792
        if (DB::count() > 0 && empty($data['valeur']) === false) {
3793
            $valTemp = array(
3794
                'value' => $data['valeur'],
3795
                'text' => TP_PW_COMPLEXITY[$data['valeur']][1],
3796
            );
3797
        }
3798
        $arr_data['complexity'] = $valTemp;
3799
3800
        // Now get Roles
3801
        $valTemp = '';
3802
        $rows_tmp = DB::query(
3803
            'SELECT t.title
3804
            FROM ' . prefixTable('roles_values') . ' as v
3805
            INNER JOIN ' . prefixTable('roles_title') . ' as t ON (v.role_id = t.id)
3806
            WHERE v.folder_id = %i
3807
            GROUP BY title',
3808
            $folder
3809
        );
3810
        foreach ($rows_tmp as $record) {
3811
            $valTemp .= (empty($valTemp) === true ? '' : ' - ') . $record['title'];
3812
        }
3813
        $arr_data['visibilityRoles'] = $valTemp;
3814
3815
        // now save in DB
3816
        DB::update(
3817
            prefixTable('nested_tree'),
3818
            array(
3819
                'categories' => json_encode($arr_data),
3820
            ),
3821
            'id = %i',
3822
            $folder
3823
        );
3824
    }
3825
}
3826
3827
/**
3828
 * List all users that have specific roles
3829
 *
3830
 * @param array $roles
3831
 * @return array
3832
 */
3833
function getUsersWithRoles(
3834
    array $roles
3835
): array
3836
{
3837
    $arrUsers = array();
3838
3839
    foreach ($roles as $role) {
3840
        // loop on users and check if user has this role
3841
        $rows = DB::query(
3842
            'SELECT id, fonction_id
3843
            FROM ' . prefixTable('users') . '
3844
            WHERE id != %i',
3845
            $_SESSION['user_id']
3846
        );
3847
        foreach ($rows as $user) {
3848
            $userRoles = explode(';', $user['fonction_id']);
3849
            if (in_array($role, $userRoles, true) === true) {
3850
                array_push($arrUsers, $user['id']);
3851
            }
3852
        }
3853
    }
3854
3855
    return $arrUsers;
3856
}
3857