Passed
Push — master ( 789a30...5549f0 )
by Nils
05:08
created

getPHPBinary()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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

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

2163
                $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode(/** @scrutinizer ignore-type */ $value, ENT_COMPAT) . "',\n";
Loading history...
2164
                $bFound = true;
2165
                break;
2166
            }
2167
            ++$inc;
2168
        }
2169
        if ($bFound === false) {
2170
            $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode($value, ENT_COMPAT). "',\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/portable-ascii-master/src/voku/helper/ASCII.php';
2289
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/portable-utf8-master/src/voku/helper/UTF8.php';
2290
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/anti-xss-master/src/voku/helper/AntiXSS.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.'/'.SECUREFILE);
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/portable-ascii-master/src/voku/helper/ASCII.php';
2458
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/portable-utf8-master/src/voku/helper/UTF8.php';
2459
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/anti-xss-master/src/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
 * @param string $key
2763
 *
2764
 * @return array
2765
 */
2766
function doDataEncryption(string $data, string $key = NULL): array
2767
{
2768
    // Includes
2769
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2770
    // Load classes
2771
    $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2772
    // Generate an object key
2773
    $objectKey = is_null($key) === true ? uniqidReal(KEY_LENGTH) : $key;
2774
    // Set it as password
2775
    $cipher->setPassword($objectKey);
2776
    return [
2777
        'encrypted' => base64_encode($cipher->encrypt($data)),
2778
        'objectKey' => base64_encode($objectKey),
2779
    ];
2780
}
2781
2782
/**
2783
 * Decrypts a string using AES.
2784
 *
2785
 * @param string $data Encrypted data
2786
 * @param string $key  Key to uncrypt
2787
 *
2788
 * @return string
2789
 */
2790
function doDataDecryption(string $data, string $key): string
2791
{
2792
    // Includes
2793
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2794
    // Load classes
2795
    $cipher = new Crypt_AES();
2796
    // Set the object key
2797
    $cipher->setPassword(base64_decode($key));
2798
    return base64_encode((string) $cipher->decrypt(base64_decode($data)));
2799
}
2800
2801
/**
2802
 * Encrypts using RSA a string using a public key.
2803
 *
2804
 * @param string $key       Key to be encrypted
2805
 * @param string $publicKey User public key
2806
 *
2807
 * @return string
2808
 */
2809
function encryptUserObjectKey(string $key, string $publicKey): string
2810
{
2811
    // Includes
2812
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2813
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2814
    // Load classes
2815
    $rsa = new Crypt_RSA();
2816
    $rsa->loadKey(base64_decode($publicKey));
2817
    // Encrypt
2818
    return base64_encode($rsa->encrypt(base64_decode($key)));
2819
}
2820
2821
/**
2822
 * Decrypts using RSA an encrypted string using a private key.
2823
 *
2824
 * @param string $key        Encrypted key
2825
 * @param string $privateKey User private key
2826
 *
2827
 * @return string
2828
 */
2829
function decryptUserObjectKey(string $key, string $privateKey): string
2830
{
2831
    // Includes
2832
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2833
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2834
    // Load classes
2835
    $rsa = new Crypt_RSA();
2836
    $rsa->loadKey(base64_decode($privateKey));
2837
    // Decrypt
2838
    try {
2839
        $tmpValue = $rsa->decrypt(base64_decode($key));
2840
        if (is_bool($tmpValue) === false) {
0 ignored issues
show
introduced by
The condition is_bool($tmpValue) === false is always true.
Loading history...
2841
            $ret = base64_encode((string) /** @scrutinizer ignore-type */$tmpValue);
2842
        } else {
2843
            $ret = '';
2844
        }
2845
    } catch (Exception $e) {
2846
        return $e;
2847
    }
2848
2849
    return $ret;
2850
}
2851
2852
/**
2853
 * Encrypts a file.
2854
 *
2855
 * @param string $fileInName File name
2856
 * @param string $fileInPath Path to file
2857
 *
2858
 * @return array
2859
 */
2860
function encryptFile(string $fileInName, string $fileInPath): array
2861
{
2862
    if (defined('FILE_BUFFER_SIZE') === false) {
2863
        define('FILE_BUFFER_SIZE', 128 * 1024);
2864
    }
2865
2866
    // Includes
2867
    include_once __DIR__.'/../includes/config/include.php';
2868
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2869
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2870
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2871
    // Load classes
2872
    $cipher = new Crypt_AES();
2873
    // Generate an object key
2874
    $objectKey = uniqidReal(32);
2875
    // Set it as password
2876
    $cipher->setPassword($objectKey);
2877
    // Prevent against out of memory
2878
    $cipher->enableContinuousBuffer();
2879
    //$cipher->disablePadding();
2880
2881
    // Encrypt the file content
2882
    $plaintext = file_get_contents(
2883
        filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL)
2884
    );
2885
    $ciphertext = $cipher->encrypt($plaintext);
2886
    // Save new file
2887
    $hash = md5($plaintext);
2888
    $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2889
    file_put_contents($fileOut, $ciphertext);
2890
    unlink($fileInPath . '/' . $fileInName);
2891
    return [
2892
        'fileHash' => base64_encode($hash),
2893
        'objectKey' => base64_encode($objectKey),
2894
    ];
2895
}
2896
2897
/**
2898
 * Decrypt a file.
2899
 *
2900
 * @param string $fileName File name
2901
 * @param string $filePath Path to file
2902
 * @param string $key      Key to use
2903
 *
2904
 * @return string
2905
 */
2906
function decryptFile(string $fileName, string $filePath, string $key): string
2907
{
2908
    if (! defined('FILE_BUFFER_SIZE')) {
2909
        define('FILE_BUFFER_SIZE', 128 * 1024);
2910
    }
2911
2912
    // Includes
2913
    include_once __DIR__.'/../includes/config/include.php';
2914
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2915
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2916
    include_once __DIR__.'/../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2917
    // Get file name
2918
    $fileName = base64_decode($fileName);
2919
    // Load classes
2920
    $cipher = new Crypt_AES();
2921
    // Set the object key
2922
    $cipher->setPassword(base64_decode($key));
2923
    // Prevent against out of memory
2924
    $cipher->enableContinuousBuffer();
2925
    $cipher->disablePadding();
2926
    // Get file content
2927
    $ciphertext = file_get_contents($filePath . '/' . TP_FILE_PREFIX . $fileName);
2928
    // Decrypt file content and return
2929
    return base64_encode($cipher->decrypt($ciphertext));
2930
}
2931
2932
/**
2933
 * Generate a simple password
2934
 *
2935
 * @param int $length Length of string
2936
 * @param bool $symbolsincluded Allow symbols
2937
 *
2938
 * @return string
2939
 */
2940
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2941
{
2942
    // Generate new user password
2943
    $small_letters = range('a', 'z');
2944
    $big_letters = range('A', 'Z');
2945
    $digits = range(0, 9);
2946
    $symbols = $symbolsincluded === true ?
2947
        ['#', '_', '-', '@', '$', '+', '&'] : [];
2948
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2949
    $count = count($res);
2950
    // first variant
2951
2952
    $random_string = '';
2953
    for ($i = 0; $i < $length; ++$i) {
2954
        $random_string .= $res[random_int(0, $count - 1)];
2955
    }
2956
2957
    return $random_string;
2958
}
2959
2960
/**
2961
 * Permit to store the sharekey of an object for users.
2962
 *
2963
 * @param string $object_name             Type for table selection
2964
 * @param int    $post_folder_is_personal Personal
2965
 * @param int    $post_folder_id          Folder
2966
 * @param int    $post_object_id          Object
2967
 * @param string $objectKey               Object key
2968
 * @param array  $SETTINGS                Teampass settings
2969
 * @param int    $user_id                 User ID if needed
2970
 * @param bool   $onlyForUser                 User ID if needed
2971
 * @param bool   $deleteAll                 User ID if needed
2972
 * @param array  $objectKeyArray                 User ID if needed
2973
 *
2974
 * @return void
2975
 */
2976
function storeUsersShareKey(
2977
    string $object_name,
2978
    int $post_folder_is_personal,
2979
    int $post_folder_id,
2980
    int $post_object_id,
2981
    string $objectKey,
2982
    array $SETTINGS,
2983
    bool $onlyForUser = false,
2984
    bool $deleteAll = true,
2985
    array $objectKeyArray = []
2986
): void {
2987
    // include librairies
2988
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2989
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2990
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2991
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2992
2993
    // connect to DB
2994
    if (defined('DB_PASSWD_CLEAR') === false) {
2995
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2996
    }
2997
    DB::$host = DB_HOST;
2998
    DB::$user = DB_USER;
2999
    DB::$password = DB_PASSWD_CLEAR;
3000
    DB::$dbName = DB_NAME;
3001
    DB::$port = DB_PORT;
3002
    DB::$encoding = DB_ENCODING;
3003
    DB::$ssl = DB_SSL;
3004
    DB::$connect_options = DB_CONNECT_OPTIONS;
3005
3006
    // Delete existing entries for this object
3007
    if ($deleteAll === true) {
3008
        DB::delete(
3009
            $object_name,
3010
            'object_id = %i',
3011
            $post_object_id
3012
        );
3013
    }
3014
    
3015
    if (
3016
        (int) $post_folder_is_personal === 1
3017
        && in_array($post_folder_id, $superGlobal->get('personal_folders', 'SESSION')) === true
3018
    ) {
3019
        // If this is a personal object
3020
        // Only create the sharekey for user
3021
        if (empty($objectKey) === false) {
3022
            DB::insert(
3023
                $object_name,
3024
                [
3025
                    'object_id' => (int) $post_object_id,
3026
                    'user_id' => (int) $superGlobal->get('user_id', 'SESSION'),
3027
                    'share_key' => encryptUserObjectKey(
3028
                        $objectKey,
3029
                        $superGlobal->get('public_key', 'SESSION', 'user')
3030
                    ),
3031
                ]
3032
            );
3033
        } else if (count($objectKeyArray) > 0) {
3034
            foreach ($objectKeyArray as $object) {
3035
                DB::insert(
3036
                    $object_name,
3037
                    [
3038
                        'object_id' => (int) $object['objectId'],
3039
                        'user_id' => (int) $superGlobal->get('user_id', 'SESSION'),
3040
                        'share_key' => encryptUserObjectKey(
3041
                            $object['objectKey'],
3042
                            $superGlobal->get('public_key', 'SESSION', 'user')
3043
                        ),
3044
                    ]
3045
                );
3046
            }
3047
        }
3048
    } else {
3049
        // This is a public object
3050
        // Create sharekey for each user
3051
        //DB::debugmode(true);
3052
        $users = DB::query(
3053
            'SELECT id, public_key
3054
            FROM ' . prefixTable('users') . '
3055
            WHERE ' . ($onlyForUser === true ? 
3056
                'id IN ("' . TP_USER_ID . '","' . $superGlobal->get('user_id', 'SESSION') . '") ' : 
3057
                'id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '") ') . '
3058
            AND public_key != ""'
3059
        );
3060
        //DB::debugmode(false);
3061
        foreach ($users as $user) {
3062
            // Insert in DB the new object key for this item by user
3063
            if (count($objectKeyArray) === 0) {
3064
                DB::insert(
3065
                    $object_name,
3066
                    [
3067
                        'object_id' => $post_object_id,
3068
                        'user_id' => (int) $user['id'],
3069
                        'share_key' => encryptUserObjectKey(
3070
                            $objectKey,
3071
                            $user['public_key']
3072
                        ),
3073
                    ]
3074
                );
3075
            } else {
3076
                foreach ($objectKeyArray as $object) {
3077
                    DB::insert(
3078
                        $object_name,
3079
                        [
3080
                            'object_id' => (int) $object['objectId'],
3081
                            'user_id' => (int) $user['id'],
3082
                            'share_key' => encryptUserObjectKey(
3083
                                $object['objectKey'],
3084
                                $user['public_key']
3085
                            ),
3086
                        ]
3087
                    );
3088
                }
3089
            }
3090
        }
3091
    }
3092
}
3093
3094
/**
3095
 * Is this string base64 encoded?
3096
 *
3097
 * @param string $str Encoded string?
3098
 *
3099
 * @return bool
3100
 */
3101
function isBase64(string $str): bool
3102
{
3103
    $str = (string) trim($str);
3104
    if (! isset($str[0])) {
3105
        return false;
3106
    }
3107
3108
    $base64String = (string) base64_decode($str, true);
3109
    if ($base64String && base64_encode($base64String) === $str) {
3110
        return true;
3111
    }
3112
3113
    return false;
3114
}
3115
3116
/**
3117
 * Undocumented function
3118
 *
3119
 * @param string $field Parameter
3120
 *
3121
 * @return array|bool|resource|string
3122
 */
3123
function filterString(string $field)
3124
{
3125
    // Sanitize string
3126
    $field = filter_var(trim($field), FILTER_SANITIZE_FULL_SPECIAL_CHARS);
3127
    if (empty($field) === false) {
3128
        // Load AntiXSS
3129
        include_once __DIR__.'/../includes/libraries/anti-xss-master/src/voku/helper/AntiXSS.php';
3130
        $antiXss = new voku\helper\AntiXSS();
3131
        // Return
3132
        return $antiXss->xss_clean($field);
3133
    }
3134
3135
    return false;
3136
}
3137
3138
/**
3139
 * CHeck if provided credentials are allowed on server
3140
 *
3141
 * @param string $login    User Login
3142
 * @param string $password User Pwd
3143
 * @param array  $SETTINGS Teampass settings
3144
 *
3145
 * @return bool
3146
 */
3147
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
3148
{
3149
    // Build ldap configuration array
3150
    $config = [
3151
        // Mandatory Configuration Options
3152
        'hosts' => [$SETTINGS['ldap_hosts']],
3153
        'base_dn' => $SETTINGS['ldap_bdn'],
3154
        'username' => $SETTINGS['ldap_username'],
3155
        'password' => $SETTINGS['ldap_password'],
3156
3157
        // Optional Configuration Options
3158
        'port' => $SETTINGS['ldap_port'],
3159
        'use_ssl' => (int) $SETTINGS['ldap_ssl'] === 1 ? true : false,
3160
        'use_tls' => (int) $SETTINGS['ldap_tls'] === 1 ? true : false,
3161
        'version' => 3,
3162
        'timeout' => 5,
3163
        'follow_referrals' => false,
3164
3165
        // Custom LDAP Options
3166
        'options' => [
3167
            // See: http://php.net/ldap_set_option
3168
            LDAP_OPT_X_TLS_REQUIRE_CERT => (isset($SETTINGS['ldap_tls_certiface_check']) ? $SETTINGS['ldap_tls_certiface_check'] : LDAP_OPT_X_TLS_HARD),
3169
        ],
3170
    ];
3171
    // Load expected libraries
3172
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Traits/Macroable.php';
3173
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Arr.php';
3174
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/DetectsErrors.php';
3175
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Connection.php';
3176
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/LdapInterface.php';
3177
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Ldap.php';
3178
    $ad = new SplClassLoader('LdapRecord', '../includes/libraries');
3179
    $ad->register();
3180
    $connection = new Connection($config);
3181
    // Connect to LDAP
3182
    try {
3183
        $connection->connect();
3184
    } catch (\LdapRecord\Auth\BindException $e) {
3185
        $error = $e->getDetailedError();
3186
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
3187
        return false;
3188
    }
3189
3190
    // Authenticate user
3191
    try {
3192
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory') {
3193
            $connection->auth()->attempt($login, $password, $stayAuthenticated = true);
3194
        } else {
3195
            $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);
3196
        }
3197
    } catch (\LdapRecord\Auth\BindException $e) {
3198
        $error = $e->getDetailedError();
3199
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
3200
        return false;
3201
    }
3202
3203
    return true;
3204
}
3205
3206
/**
3207
 * Removes from DB all sharekeys of this user
3208
 *
3209
 * @param int $userId User's id
3210
 * @param array   $SETTINGS Teampass settings
3211
 *
3212
 * @return bool
3213
 */
3214
function deleteUserObjetsKeys(int $userId, array $SETTINGS = []): bool
3215
{
3216
    // include librairies & connect to DB
3217
    include_once __DIR__. '/../includes/config/settings.php';
3218
    include_once __DIR__. '/../includes/libraries/Database/Meekrodb/db.class.php';
3219
    if (defined('DB_PASSWD_CLEAR') === false) {
3220
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
3221
    }
3222
    DB::$host = DB_HOST;
3223
    DB::$user = DB_USER;
3224
    DB::$password = DB_PASSWD_CLEAR;
3225
    DB::$dbName = DB_NAME;
3226
    DB::$port = DB_PORT;
3227
    DB::$encoding = DB_ENCODING;
3228
    DB::$ssl = DB_SSL;
3229
    DB::$connect_options = DB_CONNECT_OPTIONS;
3230
    // Remove all item sharekeys items
3231
    DB::delete(
3232
        prefixTable('sharekeys_items'),
3233
        'user_id = %i',
3234
        $userId
3235
    );
3236
    // Remove all item sharekeys files
3237
    DB::delete(
3238
        prefixTable('sharekeys_files'),
3239
        'user_id = %i',
3240
        $userId
3241
    );
3242
    // Remove all item sharekeys fields
3243
    DB::delete(
3244
        prefixTable('sharekeys_fields'),
3245
        'user_id = %i',
3246
        $userId
3247
    );
3248
    // Remove all item sharekeys logs
3249
    DB::delete(
3250
        prefixTable('sharekeys_logs'),
3251
        'user_id = %i',
3252
        $userId
3253
    );
3254
    // Remove all item sharekeys suggestions
3255
    DB::delete(
3256
        prefixTable('sharekeys_suggestions'),
3257
        'user_id = %i',
3258
        $userId
3259
    );
3260
    return false;
3261
}
3262
3263
/**
3264
 * Manage list of timezones   $SETTINGS Teampass settings
3265
 *
3266
 * @return array
3267
 */
3268
function timezone_list()
3269
{
3270
    static $timezones = null;
3271
    if ($timezones === null) {
3272
        $timezones = [];
3273
        $offsets = [];
3274
        $now = new DateTime('now', new DateTimeZone('UTC'));
3275
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
3276
            $now->setTimezone(new DateTimeZone($timezone));
3277
            $offsets[] = $offset = $now->getOffset();
3278
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
3279
        }
3280
3281
        array_multisort($offsets, $timezones);
3282
    }
3283
3284
    return $timezones;
3285
}
3286
3287
/**
3288
 * Provide timezone offset
3289
 *
3290
 * @param int $offset Timezone offset
3291
 *
3292
 * @return string
3293
 */
3294
function format_GMT_offset($offset): string
3295
{
3296
    $hours = intval($offset / 3600);
3297
    $minutes = abs(intval($offset % 3600 / 60));
3298
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
3299
}
3300
3301
/**
3302
 * Provides timezone name
3303
 *
3304
 * @param string $name Timezone name
3305
 *
3306
 * @return string
3307
 */
3308
function format_timezone_name($name): string
3309
{
3310
    $name = str_replace('/', ', ', $name);
3311
    $name = str_replace('_', ' ', $name);
3312
3313
    return str_replace('St ', 'St. ', $name);
3314
}
3315
3316
/**
3317
 * Provides info if user should use MFA based on roles
3318
 *
3319
 * @param string $userRolesIds  User roles ids
3320
 * @param string $mfaRoles      Roles for which MFA is requested
3321
 *
3322
 * @return bool
3323
 */
3324
function mfa_auth_requested_roles(string $userRolesIds, string $mfaRoles): bool
3325
{
3326
    if (empty($mfaRoles) === true) {
3327
        return true;
3328
    }
3329
3330
    $mfaRoles = array_values(json_decode($mfaRoles, true));
3331
    $userRolesIds = array_filter(explode(';', $userRolesIds));
3332
    if (count($mfaRoles) === 0 || count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
3333
        return true;
3334
    }
3335
3336
    return false;
3337
}
3338
3339
/**
3340
 * Permits to clean a string for export purpose
3341
 *
3342
 * @param string $text
3343
 * @param bool $emptyCheckOnly
3344
 * 
3345
 * @return string
3346
 */
3347
function cleanStringForExport(string $text, bool $emptyCheckOnly = false): string
3348
{
3349
    if (is_null($text) === true || empty($text) === true) {
3350
        return '';
3351
    }
3352
    // only expected to check if $text was empty
3353
    elseif ($emptyCheckOnly === true) {
3354
        return $text;
3355
    }
3356
3357
    return strip_tags(
3358
        cleanString(
3359
            html_entity_decode($text, ENT_QUOTES | ENT_XHTML, 'UTF-8'),
3360
            true)
3361
        );
3362
}
3363
3364
/**
3365
 * Permits to check if user ID is valid
3366
 *
3367
 * @param integer $post_user_id
3368
 * @return bool
3369
 */
3370
function isUserIdValid($userId): bool
3371
{
3372
    if (is_null($userId) === false
3373
        && isset($userId) === true
3374
        && empty($userId) === false
3375
    ) {
3376
        return true;
3377
    }
3378
    return false;
3379
}
3380
3381
/**
3382
 * Check if a key exists and if its value equal the one expected
3383
 *
3384
 * @param string $key
3385
 * @param integer|string $value
3386
 * @param array $array
3387
 * 
3388
 * @return boolean
3389
 */
3390
function isKeyExistingAndEqual(
3391
    string $key,
3392
    /*PHP8 - integer|string*/$value,
3393
    array $array
3394
): bool
3395
{
3396
    if (isset($array[$key]) === true
3397
        && (is_int($value) === true ?
3398
            (int) $array[$key] === $value :
3399
            (string) $array[$key] === $value)
3400
    ) {
3401
        return true;
3402
    }
3403
    return false;
3404
}
3405
3406
/**
3407
 * Check if a variable is not set or equal to a value
3408
 *
3409
 * @param string|null $var
3410
 * @param integer|string $value
3411
 * 
3412
 * @return boolean
3413
 */
3414
function isKeyNotSetOrEqual(
3415
    /*PHP8 - string|null*/$var,
3416
    /*PHP8 - integer|string*/$value
3417
): bool
3418
{
3419
    if (isset($var) === false
3420
        || (is_int($value) === true ?
3421
            (int) $var === $value :
3422
            (string) $var === $value)
3423
    ) {
3424
        return true;
3425
    }
3426
    return false;
3427
}
3428
3429
/**
3430
 * Check if a key exists and if its value < to the one expected
3431
 *
3432
 * @param string $key
3433
 * @param integer $value
3434
 * @param array $array
3435
 * 
3436
 * @return boolean
3437
 */
3438
function isKeyExistingAndInferior(string $key, int $value, array $array): bool
3439
{
3440
    if (isset($array[$key]) === true && (int) $array[$key] < $value) {
3441
        return true;
3442
    }
3443
    return false;
3444
}
3445
3446
/**
3447
 * Check if a key exists and if its value > to the one expected
3448
 *
3449
 * @param string $key
3450
 * @param integer $value
3451
 * @param array $array
3452
 * 
3453
 * @return boolean
3454
 */
3455
function isKeyExistingAndSuperior(string $key, int $value, array $array): bool
3456
{
3457
    if (isset($array[$key]) === true && (int) $array[$key] > $value) {
3458
        return true;
3459
    }
3460
    return false;
3461
}
3462
3463
/**
3464
 * Check if values in array are set
3465
 * Return true if all set
3466
 * Return false if one of them is not set
3467
 *
3468
 * @param array $arrayOfValues
3469
 * @return boolean
3470
 */
3471
function isSetArrayOfValues(array $arrayOfValues): bool
3472
{
3473
    foreach($arrayOfValues as $value) {
3474
        if (isset($value) === false) {
3475
            return false;
3476
        }
3477
    }
3478
    return true;
3479
}
3480
3481
/**
3482
 * Check if values in array are set
3483
 * Return true if all set
3484
 * Return false if one of them is not set
3485
 *
3486
 * @param array $arrayOfValues
3487
 * @param integer|string $value
3488
 * @return boolean
3489
 */
3490
function isArrayOfVarsEqualToValue(
3491
    array $arrayOfVars,
3492
    /*PHP8 - integer|string*/$value
3493
) : bool
3494
{
3495
    foreach($arrayOfVars as $variable) {
3496
        if ($variable !== $value) {
3497
            return false;
3498
        }
3499
    }
3500
    return true;
3501
}
3502
3503
/**
3504
 * Checks if at least one variable in array is equal to value
3505
 *
3506
 * @param array $arrayOfValues
3507
 * @param integer|string $value
3508
 * @return boolean
3509
 */
3510
function isOneVarOfArrayEqualToValue(
3511
    array $arrayOfVars,
3512
    /*PHP8 - integer|string*/$value
3513
) : bool
3514
{
3515
    foreach($arrayOfVars as $variable) {
3516
        if ($variable === $value) {
3517
            return true;
3518
        }
3519
    }
3520
    return false;
3521
}
3522
3523
/**
3524
 * Checks is value is null, not set OR empty
3525
 *
3526
 * @param string|int|null $value
3527
 * @return boolean
3528
 */
3529
function isValueSetNullEmpty(/*PHP8 - string|int|null*/ $value) : bool
3530
{
3531
    if (is_null($value) === true || isset($value) === false || empty($value) === true) {
3532
        return true;
3533
    }
3534
    return false;
3535
}
3536
3537
/**
3538
 * Checks if value is set and if empty is equal to passed boolean
3539
 *
3540
 * @param string|int $value
3541
 * @param boolean $boolean
3542
 * @return boolean
3543
 */
3544
function isValueSetEmpty($value, $boolean = true) : bool
3545
{
3546
    if (isset($value) === true && empty($value) === $boolean) {
3547
        return true;
3548
    }
3549
    return false;
3550
}
3551
3552
/**
3553
 * Ensure Complexity is translated
3554
 *
3555
 * @return void
3556
 */
3557
function defineComplexity() : void
3558
{
3559
    if (defined('TP_PW_COMPLEXITY') === false) {
3560
        define(
3561
            'TP_PW_COMPLEXITY',
3562
            [
3563
                TP_PW_STRENGTH_1 => array(TP_PW_STRENGTH_1, langHdl('complex_level1'), 'fas fa-thermometer-empty text-danger'),
3564
                TP_PW_STRENGTH_2 => array(TP_PW_STRENGTH_2, langHdl('complex_level2'), 'fas fa-thermometer-quarter text-warning'),
3565
                TP_PW_STRENGTH_3 => array(TP_PW_STRENGTH_3, langHdl('complex_level3'), 'fas fa-thermometer-half text-warning'),
3566
                TP_PW_STRENGTH_4 => array(TP_PW_STRENGTH_4, langHdl('complex_level4'), 'fas fa-thermometer-three-quarters text-success'),
3567
                TP_PW_STRENGTH_5 => array(TP_PW_STRENGTH_5, langHdl('complex_level5'), 'fas fa-thermometer-full text-success'),
3568
            ]
3569
        );
3570
    }
3571
}
3572
3573
/**
3574
 * Uses Sanitizer to perform data sanitization
3575
 *
3576
 * @param array     $data
3577
 * @param array     $filters
3578
 * @param string    $path
3579
 * @return array|string
3580
 */
3581
function dataSanitizer(
3582
    array $data,
3583
    array $filters,
3584
    string $path = __DIR__. '/..' // Path to Teampass root
3585
)
3586
{
3587
    // Load Sanitizer library
3588
    require_once $path . '/includes/libraries/Illuminate/Support/Traits/Macroable.php';
3589
    require_once $path . '/includes/libraries/Illuminate/Support/Str.php';
3590
    require_once $path . '/includes/libraries/Illuminate/Validation/ValidationRuleParser.php';
3591
    require_once $path . '/includes/libraries/Illuminate/Support/Arr.php';
3592
    require_once $path . '/includes/libraries/Elegant/sanitizer/Contracts/Filter.php';
3593
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/Trim.php';
3594
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/Cast.php';
3595
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/EscapeHTML.php';
3596
    require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/EmptyStringToNull.php';
3597
    require_once $path . '/includes/libraries/Elegant/sanitizer/Sanitizer.php';
3598
    $sanitizer = new Elegant\sanitizer\Sanitizer($data, $filters);
3599
3600
    // Load AntiXSS
3601
    include_once $path. '/includes/libraries/portable-ascii-master/src/voku/helper/ASCII.php';
3602
    include_once $path . '/includes/libraries/portable-utf8-master/src/voku/helper/UTF8.php';
3603
    include_once $path . '/includes/libraries/anti-xss-master/src/voku/helper/AntiXSS.php';
3604
    $antiXss = new voku\helper\AntiXSS();
3605
3606
    // Sanitize post and get variables
3607
    return $antiXss->xss_clean($sanitizer->sanitize());
3608
}
3609
3610
/**
3611
 * Permits to manage the cache tree for a user
3612
 *
3613
 * @param integer $user_id
3614
 * @param string $data
3615
 * @param array $SETTINGS
3616
 * @param string $field_update
3617
 * @return void
3618
 */
3619
function cacheTreeUserHandler(int $user_id, string $data, array $SETTINGS, string $field_update = '')
3620
{
3621
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
3622
    //Connect to DB
3623
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
3624
    if (defined('DB_PASSWD_CLEAR') === false) {
3625
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
3626
    }
3627
    DB::$host = DB_HOST;
3628
    DB::$user = DB_USER;
3629
    DB::$password = DB_PASSWD_CLEAR;
3630
    DB::$dbName = DB_NAME;
3631
    DB::$port = DB_PORT;
3632
    DB::$encoding = DB_ENCODING;
3633
    DB::$ssl = DB_SSL;
3634
    DB::$connect_options = DB_CONNECT_OPTIONS;
3635
3636
    // Exists ?
3637
    $userCacheId = DB::queryfirstrow(
3638
        'SELECT increment_id
3639
        FROM ' . prefixTable('cache_tree') . '
3640
        WHERE user_id = %i',
3641
        $user_id
3642
    );
3643
    
3644
    if (is_null($userCacheId) === true || count($userCacheId) === 0) {
3645
        DB::insert(
3646
            prefixTable('cache_tree'),
3647
            array(
3648
                'data' => $data,
3649
                'timestamp' => time(),
3650
                'user_id' => $user_id,
3651
                'visible_folders' => '',
3652
            )
3653
        );
3654
    } else {
3655
        if (empty($field_update) === true) {
3656
            DB::update(
3657
                prefixTable('cache_tree'),
3658
                [
3659
                    'timestamp' => time(),
3660
                    'data' => $data,
3661
                ],
3662
                'increment_id = %i',
3663
                $userCacheId['increment_id']
3664
            );
3665
        } else {
3666
            DB::update(
3667
                prefixTable('cache_tree'),
3668
                [
3669
                    $field_update => $data,
3670
                ],
3671
                'increment_id = %i',
3672
                $userCacheId['increment_id']
3673
            );
3674
        }
3675
    }
3676
}
3677
3678
/**
3679
 * Permits to calculate a %
3680
 *
3681
 * @param float $nombre
3682
 * @param float $total
3683
 * @param float $pourcentage
3684
 * @return float
3685
 */
3686
function pourcentage(float $nombre, float $total, float $pourcentage): float
3687
{ 
3688
    $resultat = ($nombre/$total) * $pourcentage;
3689
    return round($resultat);
3690
}
3691
3692
/**
3693
 * Load the folders list from the cache
3694
 *
3695
 * @param string $fieldName
3696
 * @param string $sessionName
3697
 * @param boolean $forceRefresh
3698
 * @return array
3699
 */
3700
function loadFoldersListByCache(
3701
    string $fieldName,
3702
    string $sessionName,
3703
    bool $forceRefresh = false
3704
): array
3705
{
3706
    // Case when refresh is EXPECTED / MANDATORY
3707
    if ($forceRefresh === true) {
3708
        return [
3709
            'state' => false,
3710
            'data' => [],
3711
        ];
3712
    }
3713
3714
    // Get last folder update
3715
    $lastFolderChange = DB::queryfirstrow(
3716
        'SELECT valeur FROM ' . prefixTable('misc') . '
3717
        WHERE type = %s AND intitule = %s',
3718
        'timestamp',
3719
        'last_folder_change'
3720
    );
3721
    if (DB::count() === 0) {
3722
        $lastFolderChange['valeur'] = 0;
3723
    }
3724
3725
    // Case when an update in the tree has been done
3726
    // Refresh is then mandatory
3727
    if ((int) $lastFolderChange['valeur'] > (int) (isset($_SESSION['user_tree_last_refresh_timestamp']) === true ? $_SESSION['user_tree_last_refresh_timestamp'] : 0)) {
3728
        return [
3729
            'state' => false,
3730
            'data' => [],
3731
        ];
3732
    }
3733
3734
    // Does this user has the tree structure in session?
3735
    // If yes then use it
3736
    if (count(isset($_SESSION['teampassUser'][$sessionName]) === true ? $_SESSION['teampassUser'][$sessionName] : []) > 0) {
3737
        return [
3738
            'state' => true,
3739
            'data' => json_encode($_SESSION['teampassUser'][$sessionName]),
3740
        ];
3741
    }
3742
3743
    // Does this user has a tree cache
3744
    $userCacheTree = DB::queryfirstrow(
3745
        'SELECT '.$fieldName.'
3746
        FROM ' . prefixTable('cache_tree') . '
3747
        WHERE user_id = %i',
3748
        $_SESSION['user_id']
3749
    );
3750
    if (empty($userCacheTree[$fieldName]) === false && $userCacheTree[$fieldName] !== '[]') {
3751
        return [
3752
            'state' => true,
3753
            'data' => $userCacheTree[$fieldName],
3754
        ];
3755
    }
3756
3757
    return [
3758
        'state' => false,
3759
        'data' => [],
3760
    ];
3761
}
3762
3763
3764
/**
3765
 * Permits to refresh the categories of folders
3766
 *
3767
 * @param array $folderIds
3768
 * @return void
3769
 */
3770
function handleFoldersCategories(
3771
    array $folderIds
3772
)
3773
{
3774
    //load ClassLoader
3775
    include_once __DIR__. '/../sources/SplClassLoader.php';
3776
    
3777
    //Connect to DB
3778
    include_once __DIR__. '/../includes/libraries/Database/Meekrodb/db.class.php';
3779
    if (defined('DB_PASSWD_CLEAR') === false) {
3780
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, []));
3781
    }
3782
    DB::$host = DB_HOST;
3783
    DB::$user = DB_USER;
3784
    DB::$password = DB_PASSWD_CLEAR;
3785
    DB::$dbName = DB_NAME;
3786
    DB::$port = DB_PORT;
3787
    DB::$encoding = DB_ENCODING;
3788
    DB::$ssl = DB_SSL;
3789
    DB::$connect_options = DB_CONNECT_OPTIONS;
3790
3791
    $arr_data = array();
3792
3793
    // force full list of folders
3794
    if (count($folderIds) === 0) {
3795
        $folderIds = DB::queryFirstColumn(
3796
            'SELECT id
3797
            FROM ' . prefixTable('nested_tree') . '
3798
            WHERE personal_folder=%i',
3799
            0
3800
        );
3801
    }
3802
3803
    // Get complexity
3804
    defineComplexity();
3805
3806
    // update
3807
    foreach ($folderIds as $folder) {
3808
        // Do we have Categories
3809
        // get list of associated Categories
3810
        $arrCatList = array();
3811
        $rows_tmp = DB::query(
3812
            'SELECT c.id, c.title, c.level, c.type, c.masked, c.order, c.encrypted_data, c.role_visibility, c.is_mandatory,
3813
            f.id_category AS category_id
3814
            FROM ' . prefixTable('categories_folders') . ' AS f
3815
            INNER JOIN ' . prefixTable('categories') . ' AS c ON (f.id_category = c.parent_id)
3816
            WHERE id_folder=%i',
3817
            $folder
3818
        );
3819
        if (DB::count() > 0) {
3820
            foreach ($rows_tmp as $row) {
3821
                $arrCatList[$row['id']] = array(
3822
                    'id' => $row['id'],
3823
                    'title' => $row['title'],
3824
                    'level' => $row['level'],
3825
                    'type' => $row['type'],
3826
                    'masked' => $row['masked'],
3827
                    'order' => $row['order'],
3828
                    'encrypted_data' => $row['encrypted_data'],
3829
                    'role_visibility' => $row['role_visibility'],
3830
                    'is_mandatory' => $row['is_mandatory'],
3831
                    'category_id' => $row['category_id'],
3832
                );
3833
            }
3834
        }
3835
        $arr_data['categories'] = $arrCatList;
3836
3837
        // Now get complexity
3838
        $valTemp = '';
3839
        $data = DB::queryFirstRow(
3840
            'SELECT valeur
3841
            FROM ' . prefixTable('misc') . '
3842
            WHERE type = %s AND intitule=%i',
3843
            'complex',
3844
            $folder
3845
        );
3846
        if (DB::count() > 0 && empty($data['valeur']) === false) {
3847
            $valTemp = array(
3848
                'value' => $data['valeur'],
3849
                'text' => TP_PW_COMPLEXITY[$data['valeur']][1],
3850
            );
3851
        }
3852
        $arr_data['complexity'] = $valTemp;
3853
3854
        // Now get Roles
3855
        $valTemp = '';
3856
        $rows_tmp = DB::query(
3857
            'SELECT t.title
3858
            FROM ' . prefixTable('roles_values') . ' as v
3859
            INNER JOIN ' . prefixTable('roles_title') . ' as t ON (v.role_id = t.id)
3860
            WHERE v.folder_id = %i
3861
            GROUP BY title',
3862
            $folder
3863
        );
3864
        foreach ($rows_tmp as $record) {
3865
            $valTemp .= (empty($valTemp) === true ? '' : ' - ') . $record['title'];
3866
        }
3867
        $arr_data['visibilityRoles'] = $valTemp;
3868
3869
        // now save in DB
3870
        DB::update(
3871
            prefixTable('nested_tree'),
3872
            array(
3873
                'categories' => json_encode($arr_data),
3874
            ),
3875
            'id = %i',
3876
            $folder
3877
        );
3878
    }
3879
}
3880
3881
/**
3882
 * List all users that have specific roles
3883
 *
3884
 * @param array $roles
3885
 * @return array
3886
 */
3887
function getUsersWithRoles(
3888
    array $roles
3889
): array
3890
{
3891
    $arrUsers = array();
3892
3893
    foreach ($roles as $role) {
3894
        // loop on users and check if user has this role
3895
        $rows = DB::query(
3896
            'SELECT id, fonction_id
3897
            FROM ' . prefixTable('users') . '
3898
            WHERE id != %i AND admin = 0 AND fonction_id IS NOT NULL AND fonction_id != ""',
3899
            $_SESSION['user_id']
3900
        );
3901
        foreach ($rows as $user) {
3902
            $userRoles = is_null($user['fonction_id']) === false && empty($user['fonction_id']) === false ? explode(';', $user['fonction_id']) : [];
3903
            if (in_array($role, $userRoles, true) === true) {
3904
                array_push($arrUsers, $user['id']);
3905
            }
3906
        }
3907
    }
3908
3909
    return $arrUsers;
3910
}
3911
3912
// #3476 - check if function str_contains exists (using PHP 8.0.0 or h)
3913
// else define it
3914
if (!function_exists('str_contains')) {
3915
    function str_contains($haystack, $needle) {
3916
        return $needle !== '' && mb_strpos($haystack, $needle) !== false;
3917
    }
3918
}
3919
3920
/**
3921
 * Get all users informations
3922
 *
3923
 * @param integer $userId
3924
 * @return array
3925
 */
3926
function getFullUserInfos(
3927
    int $userId
3928
): array
3929
{
3930
    if (empty($userId) === true) {
3931
        return array();
3932
    }
3933
3934
    $val = DB::queryfirstrow(
3935
        'SELECT *
3936
        FROM ' . prefixTable('users') . '
3937
        WHERE id = %i',
3938
        $userId
3939
    );
3940
3941
    return $val;
3942
}
3943
3944
/**
3945
 * Is required an upgrade
3946
 *
3947
 * @return boolean
3948
 */
3949
function upgradeRequired(): bool
3950
{
3951
    // Get settings.php
3952
    include_once __DIR__. '/../includes/config/settings.php';
3953
3954
    // Get timestamp in DB
3955
    $val = DB::queryfirstrow(
3956
        'SELECT valeur
3957
        FROM ' . prefixTable('misc') . '
3958
        WHERE type = %s AND intitule = %s',
3959
        'admin',
3960
        'upgrade_timestamp'
3961
    );
3962
    
3963
    // if not exists then error
3964
    if (is_null($val) === true || count($val) === 0 || defined('UPGRADE_MIN_DATE') === false) return true;
3965
3966
    // if empty or too old then error
3967
    if (empty($val['valeur']) === true || (int) $val['valeur'] < (int) UPGRADE_MIN_DATE) {
3968
        return true;
3969
    }
3970
3971
    return false;
3972
}
3973
3974
/**
3975
 * Permits to change the user keys on his demand
3976
 *
3977
 * @param integer $userId
3978
 * @param string $passwordClear
3979
 * @param integer $nbItemsToTreat
3980
 * @param string $encryptionKey
3981
 * @param boolean $deleteExistingKeys
3982
 * @param boolean $sendEmailToUser
3983
 * @param boolean $encryptWithUserPassword
3984
 * @param boolean $generate_user_new_password
3985
 * @param string $emailBody
3986
 * @return string
3987
 */
3988
function handleUserKeys(
3989
    int $userId,
3990
    string $passwordClear,
3991
    int $nbItemsToTreat,
3992
    string $encryptionKey = '',
3993
    bool $deleteExistingKeys = false,
3994
    bool $sendEmailToUser = true,
3995
    bool $encryptWithUserPassword = false,
3996
    bool $generate_user_new_password = false,
3997
    string $emailBody = ''
3998
): string
3999
{
4000
4001
    // prepapre background tasks for item keys generation        
4002
    $userTP = DB::queryFirstRow(
4003
        'SELECT pw, public_key, private_key
4004
        FROM ' . prefixTable('users') . '
4005
        WHERE id = %i',
4006
        TP_USER_ID
4007
    );
4008
    if (DB::count() > 0) {
4009
        // Do we need to generate new user password
4010
        if ($generate_user_new_password === true) {
4011
            // Generate a new password
4012
            $passwordClear = GenerateCryptKey(20, false, true, true, false, true);
4013
        }
4014
4015
        // Hash the password
4016
        $pwdlib = new SplClassLoader('PasswordLib', '../includes/libraries');
4017
        $pwdlib->register();
4018
        $pwdlib = new PasswordLib\PasswordLib();
4019
        $hashedPassword = $pwdlib->createPasswordHash($passwordClear);
4020
        if ($pwdlib->verifyPasswordHash($passwordClear, $hashedPassword) === false) {
4021
            return prepareExchangedData(
4022
                __DIR__.'/..',
4023
                array(
4024
                    'error' => true,
4025
                    'message' => langHdl('pw_hash_not_correct'),
4026
                ),
4027
                'encode'
4028
            );
4029
        }
4030
4031
        // Generate new keys
4032
        $userKeys = generateUserKeys($passwordClear);
4033
4034
        // Save in DB
4035
        DB::update(
4036
            prefixTable('users'),
4037
            array(
4038
                'pw' => $hashedPassword,
4039
                'public_key' => $userKeys['public_key'],
4040
                'private_key' => $userKeys['private_key'],
4041
            ),
4042
            'id=%i',
4043
            $userId
4044
        );
4045
4046
        // update session too
4047
        if ($userId === $_SESSION['user_id']) {
4048
            $_SESSION['user']['private_key'] = $userKeys['private_key_clear'];
4049
            $_SESSION['user']['public_key'] = $userKeys['public_key'];
4050
        }
4051
4052
        // Manage empty encryption key
4053
        // Let's take the user's password if asked and if no encryption key provided
4054
        $encryptionKey = $encryptWithUserPassword === true && empty($encryptionKey) === true ? $passwordClear : $encryptionKey;
4055
4056
        // Create process
4057
        DB::insert(
4058
            prefixTable('processes'),
4059
            array(
4060
                'created_at' => time(),
4061
                'process_type' => 'create_user_keys',
4062
                'arguments' => json_encode([
4063
                    'new_user_id' => (int) $userId,
4064
                    'new_user_pwd' => cryption($passwordClear, '','encrypt')['string'],
4065
                    'new_user_code' => cryption(empty($encryptionKey) === true ? uniqidReal(20) : $encryptionKey, '','encrypt')['string'],
4066
                    'owner_id' => (int) TP_USER_ID,
4067
                    'creator_pwd' => $userTP['pw'],
4068
                    'send_email' => $sendEmailToUser === true ? 1 : 0,
4069
                    'otp_provided_new_value' => 1,
4070
                    'email_body' => empty($emailBody) === true ? '' : langHdl($emailBody),
4071
                ]),
4072
                'updated_at' => '',
4073
                'finished_at' => '',
4074
                'output' => '',
4075
            )
4076
        );
4077
        $processId = DB::insertId();
4078
4079
        // Delete existing keys
4080
        if ($deleteExistingKeys === true) {
4081
            deleteUserObjetsKeys(
4082
                (int) $userId,
4083
            );
4084
        }
4085
4086
        // Create tasks
4087
        DB::insert(
4088
            prefixTable('processes_tasks'),
4089
            array(
4090
                'process_id' => $processId,
4091
                'created_at' => time(),
4092
                'task' => json_encode([
4093
                    'step' => 'step0',
4094
                    'index' => 0,
4095
                    'nb' => $nbItemsToTreat,
4096
                ]),
4097
            )
4098
        );
4099
4100
        DB::insert(
4101
            prefixTable('processes_tasks'),
4102
            array(
4103
                'process_id' => $processId,
4104
                'created_at' => time(),
4105
                'task' => json_encode([
4106
                    'step' => 'step10',
4107
                    'index' => 0,
4108
                    'nb' => $nbItemsToTreat,
4109
                ]),
4110
            )
4111
        );
4112
4113
        DB::insert(
4114
            prefixTable('processes_tasks'),
4115
            array(
4116
                'process_id' => $processId,
4117
                'created_at' => time(),
4118
                'task' => json_encode([
4119
                    'step' => 'step20',
4120
                    'index' => 0,
4121
                    'nb' => $nbItemsToTreat,
4122
                ]),
4123
            )
4124
        );
4125
4126
        DB::insert(
4127
            prefixTable('processes_tasks'),
4128
            array(
4129
                'process_id' => $processId,
4130
                'created_at' => time(),
4131
                'task' => json_encode([
4132
                    'step' => 'step30',
4133
                    'index' => 0,
4134
                    'nb' => $nbItemsToTreat,
4135
                ]),
4136
            )
4137
        );
4138
4139
        DB::insert(
4140
            prefixTable('processes_tasks'),
4141
            array(
4142
                'process_id' => $processId,
4143
                'created_at' => time(),
4144
                'task' => json_encode([
4145
                    'step' => 'step40',
4146
                    'index' => 0,
4147
                    'nb' => $nbItemsToTreat,
4148
                ]),
4149
            )
4150
        );
4151
4152
        DB::insert(
4153
            prefixTable('processes_tasks'),
4154
            array(
4155
                'process_id' => $processId,
4156
                'created_at' => time(),
4157
                'task' => json_encode([
4158
                    'step' => 'step50',
4159
                    'index' => 0,
4160
                    'nb' => $nbItemsToTreat,
4161
                ]),
4162
            )
4163
        );
4164
4165
        DB::insert(
4166
            prefixTable('processes_tasks'),
4167
            array(
4168
                'process_id' => $processId,
4169
                'created_at' => time(),
4170
                'task' => json_encode([
4171
                    'step' => 'step60',
4172
                    'index' => 0,
4173
                    'nb' => $nbItemsToTreat,
4174
                ]),
4175
            )
4176
        );
4177
4178
        // update user's new status
4179
        DB::update(
4180
            prefixTable('users'),
4181
            [
4182
                'is_ready_for_usage' => 0,
4183
                'otp_provided' => 1,
4184
                'ongoing_process_id' => $processId,
4185
                'special' => 'generate-keys',
4186
            ],
4187
            'id=%i',
4188
            $userId
4189
        );
4190
    }
4191
4192
    return prepareExchangedData(
4193
        __DIR__.'/..',
4194
        array(
4195
            'error' => false,
4196
            'message' => '',
4197
        ),
4198
        'encode'
4199
    );
4200
}
4201
4202
/**
4203
 * Permeits to check the consistency of date versus columns definition
4204
 *
4205
 * @param string $table
4206
 * @param array $dataFields
4207
 * @return array
4208
 */
4209
function validateDataFields(
4210
    string $table,
4211
    array $dataFields
4212
): array
4213
{
4214
    // Get table structure
4215
    $result = DB::query(
4216
        "SELECT `COLUMN_NAME`, `CHARACTER_MAXIMUM_LENGTH` FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%l' AND TABLE_NAME = '%l';",
4217
        DB_NAME,
4218
        $table
4219
    );
4220
4221
    foreach ($result as $row) {
4222
        $field = $row['COLUMN_NAME'];
4223
        $maxLength = is_null($row['CHARACTER_MAXIMUM_LENGTH']) === false ? (int) $row['CHARACTER_MAXIMUM_LENGTH'] : '';
4224
4225
        if (isset($dataFields[$field]) === true && is_array($dataFields[$field]) === false && empty($maxLength) === false) {
4226
            if (strlen((string) $dataFields[$field]) > $maxLength) {
4227
                return [
4228
                    'state' => false,
4229
                    'field' => $field,
4230
                    'maxLength' => $maxLength,
4231
                    'currentLength' => strlen((string) $dataFields[$field]),
4232
                ];
4233
            }
4234
        }
4235
    }
4236
    
4237
    return [
4238
        'state' => true,
4239
        'message' => '',
4240
    ];
4241
}
4242
4243
/**
4244
 * Adapt special characters sanitized during filter_var with option FILTER_SANITIZE_SPECIAL_CHARS operation
4245
 *
4246
 * @param string $string
4247
 * @return string
4248
 */
4249
function filterVarBack(string $string): string
4250
{
4251
    $arr = [
4252
        '&#060;' => '<',
4253
        '&#062;' => '>',
4254
        '&#034;' => '"',
4255
        '&#039;' => "'",
4256
        '&#038;' => '&',
4257
    ];
4258
4259
    foreach ($arr as $key => $value) {
4260
        $string = str_replace($key, $value, $string);
4261
    }
4262
4263
    return $string;
4264
}
4265
4266
/**
4267
 * 
4268
 */
4269
function storeTask(
4270
    string $taskName,
4271
    int $user_id,
4272
    int $is_personal_folder,
4273
    int $folder_destination_id,
4274
    int $item_id,
4275
    string $object_keys
4276
)
4277
{
4278
    if (in_array($taskName, ['item_copy', 'new_item', 'update_item'])) {
4279
        // Create process
4280
        DB::insert(
4281
            prefixTable('processes'),
4282
            array(
4283
                'created_at' => time(),
4284
                'process_type' => $taskName,
4285
                'arguments' => json_encode([
4286
                    'item_id' => $item_id,
4287
                    'object_key' => $object_keys,
4288
                ]),
4289
                'updated_at' => '',
4290
                'finished_at' => '',
4291
                'output' => '',
4292
                'item_id' => $item_id,
4293
            )
4294
        );
4295
        $processId = DB::insertId();
4296
4297
        // Create tasks
4298
        // 1- Create password sharekeys for users of this new ITEM
4299
        DB::insert(
4300
            prefixTable('processes_tasks'),
4301
            array(
4302
                'process_id' => $processId,
4303
                'created_at' => time(),
4304
                'task' => json_encode([
4305
                    'step' => 'create_users_pwd_key',
4306
                    'index' => 0,
4307
                ]),
4308
            )
4309
        );
4310
4311
        // 2- Create fields sharekeys for users of this new ITEM
4312
        DB::insert(
4313
            prefixTable('processes_tasks'),
4314
            array(
4315
                'process_id' => $processId,
4316
                'created_at' => time(),
4317
                'task' => json_encode([
4318
                    'step' => 'create_users_fields_key',
4319
                    'index' => 0,
4320
                ]),
4321
            )
4322
        );
4323
4324
        // 3- Create files sharekeys for users of this new ITEM
4325
        DB::insert(
4326
            prefixTable('processes_tasks'),
4327
            array(
4328
                'process_id' => $processId,
4329
                'created_at' => time(),
4330
                'task' => json_encode([
4331
                    'step' => 'create_users_files_key',
4332
                    'index' => 0,
4333
                ]),
4334
            )
4335
        );
4336
    }
4337
}
4338
4339
/**
4340
 * Return PHP binary path
4341
 *
4342
 * @return string
4343
 */
4344
function getPHPBinary(): string
4345
{
4346
    require_once __DIR__.'/../includes/libraries/Symfony/Component/Process/Pipes/PipesInterface.php';
4347
    require_once __DIR__.'/../includes/libraries/Symfony/Component/Process/Pipes/AbstractPipes.php';
4348
    require_once __DIR__.'/../includes/libraries/Symfony/Component/Process/Pipes/UnixPipes.php';
4349
    require_once __DIR__.'/../includes/libraries/Symfony/Component/Process/Pipes/WindowsPipes.php';
4350
    require_once __DIR__.'/../includes/libraries/Symfony/Component/Process/ProcessUtils.php';
4351
    require_once __DIR__.'/../includes/libraries/Symfony/Component/Process/Process.php';
4352
    require_once __DIR__.'/../includes/libraries/Symfony/Component/Process/ExecutableFinder.php';
4353
    require_once __DIR__.'/../includes/libraries/Symfony/Component/Process/PhpExecutableFinder.php';
4354
    $phpBinaryFinder = new Symfony\Component\Process\PhpExecutableFinder();
4355
    return $phpBinaryFinder->find();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $phpBinaryFinder->find() could return the type false which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
4356
}