Passed
Push — teampass_3.0 ( 4c0075...5e4b29 )
by Nils
05:35
created

identUserGetPFList()   B

Complexity

Conditions 11
Paths 24

Size

Total Lines 80
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 41
c 0
b 0
f 0
nc 24
nop 12
dl 0
loc 80
rs 7.3166

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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