Passed
Push — teampass_3.0 ( 6d5809...2c08f5 )
by Nils
08:42 queued 04:08
created

ldapCheckUserPassword()   B

Complexity

Conditions 8
Paths 24

Size

Total Lines 58
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 8
eloc 39
nc 24
nop 3
dl 0
loc 58
rs 8.0515
c 6
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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