Passed
Pull Request — teampass_3.0 (#2999)
by
unknown
04:05
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-2021 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 = 0;
1178
        //value 1 can be used to debug - 4 for debuging connections
1179
        $mail->Port = $SETTINGS['email_port'];
1180
        //COULD BE USED
1181
        $mail->CharSet = 'utf-8';
1182
        $mail->SMTPSecure = $SETTINGS['email_security'] === 'tls'
1183
            || $SETTINGS['email_security'] === 'ssl' ? $SETTINGS['email_security'] : '';
1184
        $mail->SMTPAutoTLS = $SETTINGS['email_security'] === 'tls'
1185
            || $SETTINGS['email_security'] === 'ssl' ? true : false;
1186
        $mail->SMTPOptions = [
1187
            'ssl' => [
1188
                'verify_peer' => false,
1189
                'verify_peer_name' => false,
1190
                'allow_self_signed' => true,
1191
            ],
1192
        ];
1193
        $mail->isSmtp();
1194
        // send via SMTP
1195
        $mail->Host = $SETTINGS['email_smtp_server'];
1196
        // SMTP servers
1197
        $mail->SMTPAuth = (int) $SETTINGS['email_smtp_auth'] === 1 ? true : false;
1198
        // turn on SMTP authentication
1199
        $mail->Username = $SETTINGS['email_auth_username'];
1200
        // SMTP username
1201
        $mail->Password = $SETTINGS['email_auth_pwd'];
1202
        // SMTP password
1203
        $mail->From = $SETTINGS['email_from'];
1204
        $mail->FromName = $SETTINGS['email_from_name'];
1205
        // Prepare for each person
1206
        foreach (array_filter(explode(',', $email)) as $dest) {
1207
            $mail->addAddress($dest);
1208
        }
1209
1210
        // Prepare HTML
1211
        $text_html = emailBody($textMail);
1212
        $mail->WordWrap = 80;
1213
        // set word wrap
1214
        $mail->isHtml(true);
1215
        // send as HTML
1216
        $mail->Subject = $subject;
1217
        $mail->Body = $text_html;
1218
        $mail->AltBody = is_null($textMailAlt) === false ? $textMailAlt : '';
1219
        // send email
1220
        $mail->send();
1221
        $mail->smtpClose();
1222
        if ($silent === false) {
1223
            return json_encode(
1224
                [
1225
                    'error' => false,
1226
                    'message' => langHdl('forgot_my_pw_email_sent'),
1227
                ]
1228
            );
1229
        }
1230
    } catch (Exception $e) {
1231
        if ($silent === false) {
1232
            return json_encode(
1233
                [
1234
                    'error' => true,
1235
                    'message' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1236
                ]
1237
            );
1238
        }
1239
        return '';
1240
    }
1241
}
1242
1243
/**
1244
 * Returns the email body.
1245
 *
1246
 * @param string $textMail Text for the email
1247
 */
1248
function emailBody(string $textMail): string
1249
{
1250
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1251
    w3.org/TR/html4/loose.dtd"><html>
1252
    <head><title>Email Template</title>
1253
    <style type="text/css">
1254
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1255
    </style></head>
1256
    <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">
1257
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1258
    <tr><td style="border-collapse: collapse;"><br>
1259
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1260
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1261
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1262
        </td></tr></table></td>
1263
    </tr>
1264
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1265
        <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;">
1266
        <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;">
1267
        <br><div style="float:right;">' .
1268
        $textMail .
1269
        '<br><br></td></tr></table>
1270
    </td></tr></table>
1271
    <br></body></html>';
1272
}
1273
1274
/**
1275
 * Generate a Key.
1276
 */
1277
function generateKey(): string
1278
{
1279
    return substr(md5(rand() . rand()), 0, 15);
1280
}
1281
1282
/**
1283
 * Convert date to timestamp.
1284
 *
1285
 * @param string $date     The date
1286
 * @param array  $SETTINGS Teampass settings
1287
 *
1288
 * @return string
1289
 */
1290
function dateToStamp(string $date, array $SETTINGS): string
1291
{
1292
    $date = date_parse_from_format($SETTINGS['date_format'], $date);
1293
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1294
        return mktime(23, 59, 59, $date['month'], $date['day'], $date['year']);
1295
    }
1296
    return '';
1297
}
1298
1299
/**
1300
 * Is this a date.
1301
 *
1302
 * @param string $date Date
1303
 *
1304
 * @return bool
1305
 */
1306
function isDate(string $date): bool
1307
{
1308
    return strtotime($date) !== false;
1309
}
1310
1311
/**
1312
 * Check if isUTF8().
1313
 *
1314
 * @param string|array $string Is the string
1315
 *
1316
 * @return int is the string in UTF8 format
1317
 */
1318
function isUTF8($string): int
1319
{
1320
    if (is_array($string) === true) {
1321
        $string = $string['string'];
1322
    }
1323
1324
    return preg_match(
1325
        '%^(?:
1326
        [\x09\x0A\x0D\x20-\x7E] # ASCII
1327
        | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
1328
        | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
1329
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
1330
        | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
1331
        | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
1332
        | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
1333
        | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
1334
        )*$%xs',
1335
        $string
1336
    );
1337
}
1338
1339
/**
1340
 * Prepare an array to UTF8 format before JSON_encode.
1341
 *
1342
 * @param array $array Array of values
1343
 *
1344
 * @return array
1345
 */
1346
function utf8Converter(array $array): array
1347
{
1348
    array_walk_recursive(
1349
        $array,
1350
        static function (&$item): void {
1351
            if (mb_detect_encoding((string) $item, 'utf-8', true) === false) {
1352
                $item = utf8_encode($item);
1353
            }
1354
        }
1355
    );
1356
    return $array;
1357
}
1358
1359
/**
1360
 * Permits to prepare data to be exchanged.
1361
 *
1362
 * @param array|string $data Text
1363
 * @param string       $type Parameter
1364
 * @param string       $key  Optional key
1365
 *
1366
 * @return resource|string|array
1367
 */
1368
function prepareExchangedData($data, string $type, ?string $key = null)
1369
{
1370
    if (file_exists('../includes/config/tp.config.php')) {
1371
        include '../includes/config/tp.config.php';
1372
    } elseif (file_exists('./includes/config/tp.config.php')) {
1373
        include './includes/config/tp.config.php';
1374
    } elseif (file_exists('../../includes/config/tp.config.php')) {
1375
        include '../../includes/config/tp.config.php';
1376
    } else {
1377
        throw new Exception("Error file '/includes/config/tp.config.php' not exists", 1);
1378
    }
1379
1380
    if (isset($SETTINGS) === false) {
1381
        return 'ERROR';
1382
    }
1383
1384
    // Load superglobal
1385
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1386
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1387
    // Get superglobals
1388
    if ($key !== null) {
1389
        $superGlobal->put('key', $key, 'SESSION');
1390
        $globalsKey = $key;
1391
    } else {
1392
        $globalsKey = $superGlobal->get('key', 'SESSION');
1393
    }
1394
1395
    //load ClassLoader
1396
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1397
    //Load AES
1398
    $aes = new SplClassLoader('Encryption\Crypt', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1399
    $aes->register();
1400
    if ($type === 'encode' && is_array($data) === true) {
1401
        // Ensure UTF8 format
1402
        $data = utf8Converter($data);
1403
        // Now encode
1404
        return Encryption\Crypt\aesctr::encrypt(
1405
            json_encode(
1406
                $data,
1407
                JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1408
            ),
1409
            $globalsKey,
1410
            256
1411
        );
1412
    }
1413
    if ($type === 'decode' && is_array($data) === false) {
1414
        return json_decode(
1415
            Encryption\Crypt\aesctr::decrypt(
1416
                /** @scrutinizer ignore-type */
1417
                (string) $data,
1418
                $globalsKey,
1419
                256
1420
            ),
1421
            true
1422
        );
1423
    }
1424
}
1425
1426
/**
1427
 * Create a thumbnail.
1428
 *
1429
 * @param string  $src           Source
1430
 * @param string  $dest          Destination
1431
 * @param int $desired_width Size of width
1432
 */
1433
function makeThumbnail(string $src, string $dest, int $desired_width)
1434
{
1435
    /* read the source image */
1436
    if (is_file($src) === true && mime_content_type($src) === 'image/png') {
1437
        $source_image = imagecreatefrompng($src);
1438
        if ($source_image === false) {
1439
            return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1440
        }
1441
    } else {
1442
        return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1443
    }
1444
1445
    // Get height and width
1446
    $width = imagesx($source_image);
1447
    $height = imagesy($source_image);
1448
    /* find the "desired height" of this thumbnail, relative to the desired width  */
1449
    $desired_height = (int) floor($height * $desired_width / $width);
1450
    /* create a new, "virtual" image */
1451
    $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
1452
    if ($virtual_image === false) {
1453
        return false;
1454
    }
1455
    /* copy source image at a resized size */
1456
    imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
1457
    /* create the physical thumbnail image to its destination */
1458
    imagejpeg($virtual_image, $dest);
1459
}
1460
1461
/**
1462
 * Check table prefix in SQL query.
1463
 *
1464
 * @param string $table Table name
1465
 */
1466
function prefixTable(string $table): string
1467
{
1468
    $safeTable = htmlspecialchars(DB_PREFIX . $table);
1469
    if (! empty($safeTable)) {
1470
        // sanitize string
1471
        return $safeTable;
1472
    }
1473
    // stop error no table
1474
    return 'table_not_exists';
1475
}
1476
1477
/**
1478
 * GenerateCryptKey
1479
 *
1480
 * @param int     $size      Length
1481
 * @param bool $secure Secure
1482
 * @param bool $numerals Numerics
1483
 * @param bool $uppercase Uppercase letters
1484
 * @param bool $symbols Symbols
1485
 * @param bool $lowercase Lowercase
1486
 * @param array   $SETTINGS  SETTINGS
1487
 */
1488
function GenerateCryptKey(
1489
    int $size = 10,
1490
    bool $secure = false,
1491
    bool $numerals = false,
1492
    bool $uppercase = false,
1493
    bool $symbols = false,
1494
    bool $lowercase = false,
1495
    array $SETTINGS = []
1496
): string {
1497
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1498
    $generator = new SplClassLoader('PasswordGenerator\Generator', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1499
    $generator->register();
1500
    $generator = new PasswordGenerator\Generator\ComputerPasswordGenerator();
1501
    // Is PHP7 being used?
1502
    if (version_compare(PHP_VERSION, '7.0.0', '>=')) {
1503
        $php7generator = new SplClassLoader('PasswordGenerator\RandomGenerator', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1504
        $php7generator->register();
1505
        $generator->setRandomGenerator(new PasswordGenerator\RandomGenerator\Php7RandomGenerator());
1506
    }
1507
1508
    // Manage size
1509
    $generator->setLength((int) $size);
1510
    if ($secure === true) {
1511
        $generator->setSymbols(true);
1512
        $generator->setLowercase(true);
1513
        $generator->setUppercase(true);
1514
        $generator->setNumbers(true);
1515
    } else {
1516
        $generator->setLowercase($lowercase);
1517
        $generator->setUppercase($uppercase);
1518
        $generator->setNumbers($numerals);
1519
        $generator->setSymbols($symbols);
1520
    }
1521
1522
    return $generator->generatePasswords()[0];
1523
}
1524
1525
/*
1526
* Send sysLOG message
1527
* @param string $message
1528
* @param string $host
1529
*/
1530
function send_syslog($message, $host, $port, $component = 'teampass'): void
1531
{
1532
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1533
    $syslog_message = '<123>' . date('M d H:i:s ') . $component . ': ' . $message;
1534
    socket_sendto($sock, $syslog_message, strlen($syslog_message), 0, $host, $port);
1535
    socket_close($sock);
1536
}
1537
1538
/**
1539
 * Permits to log events into DB
1540
 *
1541
 * @param array  $SETTINGS Teampass settings
1542
 * @param string $type     Type
1543
 * @param string $label    Label
1544
 * @param string $who      Who
1545
 * @param string $login    Login
1546
 * @param string $field_1  Field
1547
 */
1548
function logEvents(array $SETTINGS, string $type, string $label, string $who, ?string $login = null, ?string $field_1 = null): void
1549
{
1550
    if (empty($who)) {
1551
        $who = getClientIpServer();
1552
    }
1553
1554
    // include librairies & connect to DB
1555
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1556
    if (defined('DB_PASSWD_CLEAR') === false) {
1557
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1558
    }
1559
    DB::$host = DB_HOST;
1560
    DB::$user = DB_USER;
1561
    DB::$password = DB_PASSWD_CLEAR;
1562
    DB::$dbName = DB_NAME;
1563
    DB::$port = DB_PORT;
1564
    DB::$encoding = DB_ENCODING;
1565
    DB::insert(
1566
        prefixTable('log_system'),
1567
        [
1568
            'type' => $type,
1569
            'date' => time(),
1570
            'label' => $label,
1571
            'qui' => $who,
1572
            'field_1' => $field_1 === null ? '' : $field_1,
1573
        ]
1574
    );
1575
    // If SYSLOG
1576
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1577
        if ($type === 'user_mngt') {
1578
            send_syslog(
1579
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1580
                $SETTINGS['syslog_host'],
1581
                $SETTINGS['syslog_port'],
1582
                'teampass'
1583
            );
1584
        } else {
1585
            send_syslog(
1586
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1587
                $SETTINGS['syslog_host'],
1588
                $SETTINGS['syslog_port'],
1589
                'teampass'
1590
            );
1591
        }
1592
    }
1593
}
1594
1595
/**
1596
 * Log events.
1597
 *
1598
 * @param array  $SETTINGS        Teampass settings
1599
 * @param int    $item_id         Item id
1600
 * @param string $item_label      Item label
1601
 * @param int    $id_user         User id
1602
 * @param string $action          Code for reason
1603
 * @param string $login           User login
1604
 * @param string $raison          Code for reason
1605
 * @param string $encryption_type Encryption on
1606
 */
1607
function logItems(
1608
    array $SETTINGS,
1609
    int $item_id,
1610
    string $item_label,
1611
    int $id_user,
1612
    string $action,
1613
    ?string $login = null,
1614
    ?string $raison = null,
1615
    ?string $encryption_type = null
1616
): void {
1617
    // include librairies & connect to DB
1618
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1619
    if (defined('DB_PASSWD_CLEAR') === false) {
1620
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1621
    }
1622
    DB::$host = DB_HOST;
1623
    DB::$user = DB_USER;
1624
    DB::$password = DB_PASSWD_CLEAR;
1625
    DB::$dbName = DB_NAME;
1626
    DB::$port = DB_PORT;
1627
    DB::$encoding = DB_ENCODING;
1628
    // Insert log in DB
1629
    DB::insert(
1630
        prefixTable('log_items'),
1631
        [
1632
            'id_item' => $item_id,
1633
            'date' => time(),
1634
            'id_user' => $id_user,
1635
            'action' => $action,
1636
            'raison' => $raison,
1637
            'raison_iv' => '',
1638
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1639
        ]
1640
    );
1641
    // Timestamp the last change
1642
    if ($action === 'at_creation' || $action === 'at_modifiation' || $action === 'at_delete' || $action === 'at_import') {
1643
        DB::update(
1644
            prefixTable('misc'),
1645
            [
1646
                'valeur' => time(),
1647
            ],
1648
            'type = %s AND intitule = %s',
1649
            'timestamp',
1650
            'last_item_change'
1651
        );
1652
    }
1653
1654
    // SYSLOG
1655
    if (isset($SETTINGS['syslog_enable']) === true && $SETTINGS['syslog_enable'] === '1') {
1656
        // Extract reason
1657
        $attribute = is_null($raison) === true ? '' : explode(' : ', $raison);
1658
        // Get item info if not known
1659
        if (empty($item_label) === true) {
1660
            $dataItem = DB::queryfirstrow(
1661
                'SELECT id, id_tree, label
1662
                FROM ' . prefixTable('items') . '
1663
                WHERE id = %i',
1664
                $item_id
1665
            );
1666
            $item_label = $dataItem['label'];
1667
        }
1668
1669
        send_syslog(
1670
            'action=' . str_replace('at_', '', $action) .
1671
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1672
                ' itemno=' . $item_id .
1673
                ' user=' . is_null($login) === true ? '' : addslashes((string) $login) .
1674
                ' itemname="' . addslashes($item_label) . '"',
1675
            $SETTINGS['syslog_host'],
1676
            $SETTINGS['syslog_port'],
1677
            'teampass'
1678
        );
1679
    }
1680
1681
    // send notification if enabled
1682
    notifyOnChange($item_id, $action, $SETTINGS);
1683
}
1684
1685
/**
1686
 * If enabled, then notify admin/manager.
1687
 *
1688
 * @param int    $item_id  Item id
1689
 * @param string $action   Action to do
1690
 * @param array  $SETTINGS Teampass settings
1691
 */
1692
function notifyOnChange(int $item_id, string $action, array $SETTINGS): void
1693
{
1694
    if (
1695
        isset($SETTINGS['enable_email_notification_on_item_shown']) === true
1696
        && (int) $SETTINGS['enable_email_notification_on_item_shown'] === 1
1697
        && $action === 'at_shown'
1698
    ) {
1699
        // Load superglobal
1700
        include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1701
        $superGlobal = new protect\SuperGlobal\SuperGlobal();
1702
        // Get superglobals
1703
        $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1704
        $globalsName = $superGlobal->get('name', 'SESSION');
1705
        $globalsNotifiedEmails = $superGlobal->get('listNotificationEmails', 'SESSION');
1706
        // Get info about item
1707
        $dataItem = DB::queryfirstrow(
1708
            'SELECT id, id_tree, label
1709
            FROM ' . prefixTable('items') . '
1710
            WHERE id = %i',
1711
            $item_id
1712
        );
1713
        $item_label = $dataItem['label'];
1714
        // send back infos
1715
        DB::insert(
1716
            prefixTable('emails'),
1717
            [
1718
                'timestamp' => time(),
1719
                'subject' => langHdl('email_on_open_notification_subject'),
1720
                'body' => str_replace(
1721
                    ['#tp_user#', '#tp_item#', '#tp_link#'],
1722
                    [
1723
                        addslashes($globalsName . ' ' . $globalsLastname),
1724
                        addslashes($item_label),
1725
                        $SETTINGS['cpassman_url'] . '/index.php?page=items&group=' . $dataItem['id_tree'] . '&id=' . $item_id,
1726
                    ],
1727
                    langHdl('email_on_open_notification_mail')
1728
                ),
1729
                'receivers' => $globalsNotifiedEmails,
1730
                'status' => '',
1731
            ]
1732
        );
1733
    }
1734
}
1735
1736
/**
1737
 * Prepare notification email to subscribers.
1738
 *
1739
 * @param int    $item_id  Item id
1740
 * @param string $label    Item label
1741
 * @param array  $changes  List of changes
1742
 * @param array  $SETTINGS Teampass settings
1743
 */
1744
function notifyChangesToSubscribers(int $item_id, string $label, array $changes, array $SETTINGS): void
1745
{
1746
    // Load superglobal
1747
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1748
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1749
    // Get superglobals
1750
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
1751
    $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1752
    $globalsName = $superGlobal->get('name', 'SESSION');
1753
    // send email to user that what to be notified
1754
    $notification = DB::queryOneColumn(
1755
        'email',
1756
        'SELECT *
1757
        FROM ' . prefixTable('notification') . ' AS n
1758
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1759
        WHERE n.item_id = %i AND n.user_id != %i',
1760
        $item_id,
1761
        $globalsUserId
1762
    );
1763
    if (DB::count() > 0) {
1764
        // Prepare path
1765
        $path = geItemReadablePath($item_id, '', $SETTINGS);
1766
        // Get list of changes
1767
        $htmlChanges = '<ul>';
1768
        foreach ($changes as $change) {
1769
            $htmlChanges .= '<li>' . $change . '</li>';
1770
        }
1771
        $htmlChanges .= '</ul>';
1772
        // send email
1773
        DB::insert(
1774
            prefixTable('emails'),
1775
            [
1776
                'timestamp' => time(),
1777
                'subject' => langHdl('email_subject_item_updated'),
1778
                'body' => str_replace(
1779
                    ['#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'],
1780
                    [$label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges],
1781
                    langHdl('email_body_item_updated')
1782
                ),
1783
                'receivers' => implode(',', $notification),
1784
                'status' => '',
1785
            ]
1786
        );
1787
    }
1788
}
1789
1790
/**
1791
 * Returns the Item + path.
1792
 *
1793
 * @param int    $id_tree  Node id
1794
 * @param string $label    Label
1795
 * @param array  $SETTINGS TP settings
1796
 */
1797
function geItemReadablePath(int $id_tree, string $label, array $SETTINGS): string
1798
{
1799
    // Class loader
1800
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1801
    //Load Tree
1802
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1803
    $tree->register();
1804
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1805
    $arbo = $tree->getPath($id_tree, true);
1806
    $path = '';
1807
    foreach ($arbo as $elem) {
1808
        if (empty($path) === true) {
1809
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
1810
        } else {
1811
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
1812
        }
1813
    }
1814
1815
    // Build text to show user
1816
    if (empty($label) === false) {
1817
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
1818
    }
1819
    return empty($path) === true ? '' : $path;
1820
}
1821
1822
/**
1823
 * Get the client ip address.
1824
 *
1825
 * @return string IP address
1826
 */
1827
function getClientIpServer(): string
1828
{
1829
    if (getenv('HTTP_CLIENT_IP')) {
1830
        $ipaddress = getenv('HTTP_CLIENT_IP');
1831
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
1832
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
1833
    } elseif (getenv('HTTP_X_FORWARDED')) {
1834
        $ipaddress = getenv('HTTP_X_FORWARDED');
1835
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
1836
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
1837
    } elseif (getenv('HTTP_FORWARDED')) {
1838
        $ipaddress = getenv('HTTP_FORWARDED');
1839
    } elseif (getenv('REMOTE_ADDR')) {
1840
        $ipaddress = getenv('REMOTE_ADDR');
1841
    } else {
1842
        $ipaddress = 'UNKNOWN';
1843
    }
1844
1845
    return $ipaddress;
1846
}
1847
1848
/**
1849
 * Escape all HTML, JavaScript, and CSS.
1850
 *
1851
 * @param string $input    The input string
1852
 * @param string $encoding Which character encoding are we using?
1853
 */
1854
function noHTML(string $input, string $encoding = 'UTF-8'): string
1855
{
1856
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
1857
}
1858
1859
/**
1860
 * Permits to handle the Teampass config file
1861
 * $action accepts "rebuild" and "update"
1862
 *
1863
 * @param string $action   Action to perform
1864
 * @param array  $SETTINGS Teampass settings
1865
 * @param string $field    Field to refresh
1866
 * @param string $value    Value to set
1867
 *
1868
 * @return string|bool
1869
 */
1870
function handleConfigFile($action, $SETTINGS, $field = null, $value = null)
1871
{
1872
    $tp_config_file = $SETTINGS['cpassman_dir'] . '/includes/config/tp.config.php';
1873
    // include librairies & connect to DB
1874
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1875
    if (defined('DB_PASSWD_CLEAR') === false) {
1876
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1877
    }
1878
    DB::$host = DB_HOST;
1879
    DB::$user = DB_USER;
1880
    DB::$password = DB_PASSWD_CLEAR;
1881
    DB::$dbName = DB_NAME;
1882
    DB::$port = DB_PORT;
1883
    DB::$encoding = DB_ENCODING;
1884
    if (file_exists($tp_config_file) === false || $action === 'rebuild') {
1885
        // perform a copy
1886
        if (file_exists($tp_config_file)) {
1887
            if (! copy($tp_config_file, $tp_config_file . '.' . date('Y_m_d_His', time()))) {
1888
                return "ERROR: Could not copy file '" . $tp_config_file . "'";
1889
            }
1890
        }
1891
1892
        // regenerate
1893
        $data = [];
1894
        $data[0] = "<?php\n";
1895
        $data[1] = "global \$SETTINGS;\n";
1896
        $data[2] = "\$SETTINGS = array (\n";
1897
        $rows = DB::query(
1898
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s',
1899
            'admin'
1900
        );
1901
        foreach ($rows as $record) {
1902
            array_push($data, "    '" . $record['intitule'] . "' => '" . $record['valeur'] . "',\n");
1903
        }
1904
        array_push($data, ");\n");
1905
        $data = array_unique($data);
1906
    // ---
1907
    } elseif ($action === 'update' && empty($field) === false) {
1908
        $data = file($tp_config_file);
1909
        $inc = 0;
1910
        $bFound = false;
1911
        foreach ($data as $line) {
1912
            if (stristr($line, ');')) {
1913
                break;
1914
            }
1915
1916
            if (stristr($line, "'" . $field . "' => '")) {
1917
                $data[$inc] = "    '" . $field . "' => '" . filter_var($value, FILTER_SANITIZE_STRING) . "',\n";
1918
                $bFound = true;
1919
                break;
1920
            }
1921
            ++$inc;
1922
        }
1923
        if ($bFound === false) {
1924
            $data[$inc] = "    '" . $field . "' => '" . filter_var($value, FILTER_SANITIZE_STRING) . "',\n);\n";
1925
        }
1926
    }
1927
1928
    // update file
1929
    file_put_contents($tp_config_file, implode('', $data ?? []));
1930
    return true;
1931
}
1932
1933
/**
1934
 * Permits to replace &#92; to permit correct display
1935
 *
1936
 * @param string $input Some text
1937
 */
1938
function handleBackslash(string $input): string
1939
{
1940
    return str_replace('&amp;#92;', '&#92;', $input);
1941
}
1942
1943
/*
1944
** Permits to load settings
1945
*/
1946
function loadSettings(): void
1947
{
1948
    global $SETTINGS;
1949
    /* LOAD CPASSMAN SETTINGS */
1950
    if (! isset($SETTINGS['loaded']) || $SETTINGS['loaded'] !== 1) {
1951
        $SETTINGS = [];
1952
        $SETTINGS['duplicate_folder'] = 0;
1953
        //by default, this is set to 0;
1954
        $SETTINGS['duplicate_item'] = 0;
1955
        //by default, this is set to 0;
1956
        $SETTINGS['number_of_used_pw'] = 5;
1957
        //by default, this value is set to 5;
1958
        $settings = [];
1959
        $rows = DB::query(
1960
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
1961
            [
1962
                'type' => 'admin',
1963
                'type2' => 'settings',
1964
            ]
1965
        );
1966
        foreach ($rows as $record) {
1967
            if ($record['type'] === 'admin') {
1968
                $SETTINGS[$record['intitule']] = $record['valeur'];
1969
            } else {
1970
                $settings[$record['intitule']] = $record['valeur'];
1971
            }
1972
        }
1973
        $SETTINGS['loaded'] = 1;
1974
        $SETTINGS['default_session_expiration_time'] = 5;
1975
    }
1976
}
1977
1978
/*
1979
** check if folder has custom fields.
1980
** Ensure that target one also has same custom fields
1981
*/
1982
function checkCFconsistency($source_id, $target_id)
1983
{
1984
    $source_cf = [];
1985
    $rows = DB::QUERY(
1986
        'SELECT id_category
1987
            FROM ' . prefixTable('categories_folders') . '
1988
            WHERE id_folder = %i',
1989
        $source_id
1990
    );
1991
    foreach ($rows as $record) {
1992
        array_push($source_cf, $record['id_category']);
1993
    }
1994
1995
    $target_cf = [];
1996
    $rows = DB::QUERY(
1997
        'SELECT id_category
1998
            FROM ' . prefixTable('categories_folders') . '
1999
            WHERE id_folder = %i',
2000
        $target_id
2001
    );
2002
    foreach ($rows as $record) {
2003
        array_push($target_cf, $record['id_category']);
2004
    }
2005
2006
    $cf_diff = array_diff($source_cf, $target_cf);
2007
    if (count($cf_diff) > 0) {
2008
        return false;
2009
    }
2010
2011
    return true;
2012
}
2013
2014
/**
2015
 * Will encrypte/decrypt a fil eusing Defuse.
2016
 *
2017
 * @param string $type        can be either encrypt or decrypt
2018
 * @param string $source_file path to source file
2019
 * @param string $target_file path to target file
2020
 * @param array  $SETTINGS    Settings
2021
 * @param string $password    A password
2022
 *
2023
 * @return string|bool
2024
 */
2025
function prepareFileWithDefuse(
2026
    $type,
2027
    $source_file,
2028
    $target_file,
2029
    $SETTINGS,
2030
    $password = null
2031
) {
2032
    // Load AntiXSS
2033
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/AntiXSS.php';
2034
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/ASCII.php';
2035
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/UTF8.php';
2036
    $antiXss = new voku\helper\AntiXSS();
2037
    // Protect against bad inputs
2038
    if (is_array($source_file) === true || is_array($target_file) === true) {
2039
        return 'error_cannot_be_array';
2040
    }
2041
2042
    // Sanitize
2043
    $source_file = $antiXss->xss_clean($source_file);
2044
    $target_file = $antiXss->xss_clean($target_file);
2045
    if (empty($password) === true || is_null($password) === true) {
2046
        // get KEY to define password
2047
        $ascii_key = file_get_contents(SECUREPATH . '/teampass-seckey.txt');
2048
        $password = \Defuse\Crypto\Key::loadFromAsciiSafeString($ascii_key);
2049
    }
2050
2051
    $err = '';
2052
    if ($type === 'decrypt') {
2053
        // Decrypt file
2054
        $err = defuseFileDecrypt(
2055
            $source_file,
2056
            $target_file,
2057
            $SETTINGS, /** @scrutinizer ignore-type */
2058
            $password
2059
        );
2060
    } elseif ($type === 'encrypt') {
2061
        // Encrypt file
2062
        $err = defuseFileEncrypt(
2063
            $source_file,
2064
            $target_file,
2065
            $SETTINGS, /** @scrutinizer ignore-type */
2066
            $password
2067
        );
2068
    }
2069
2070
    // return error
2071
    return empty($err) === false ? $err : '';
2072
}
2073
2074
/**
2075
 * Encrypt a file with Defuse.
2076
 *
2077
 * @param string $source_file path to source file
2078
 * @param string $target_file path to target file
2079
 * @param array  $SETTINGS    Settings
2080
 * @param string $password    A password
2081
 *
2082
 * @return string|bool
2083
 */
2084
function defuseFileEncrypt(
2085
    $source_file,
2086
    $target_file,
2087
    $SETTINGS,
2088
    $password = null
2089
) {
2090
    // load PhpEncryption library
2091
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2092
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2093
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2094
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2095
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2096
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2097
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2098
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2099
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2100
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2101
    try {
2102
        \Defuse\Crypto\File::encryptFileWithPassword(
2103
            $source_file,
2104
            $target_file,
2105
            $password
2106
        );
2107
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2108
        $err = 'wrong_key';
2109
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2110
        $err = $ex;
2111
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2112
        $err = $ex;
2113
    }
2114
2115
    // return error
2116
    return empty($err) === false ? $err : true;
2117
}
2118
2119
/**
2120
 * Decrypt a file with Defuse.
2121
 *
2122
 * @param string $source_file path to source file
2123
 * @param string $target_file path to target file
2124
 * @param array  $SETTINGS    Settings
2125
 * @param string $password    A password
2126
 *
2127
 * @return string|bool
2128
 */
2129
function defuseFileDecrypt(
2130
    $source_file,
2131
    $target_file,
2132
    $SETTINGS,
2133
    $password = null
2134
) {
2135
    // load PhpEncryption library
2136
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2137
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2138
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2139
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2140
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2141
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2142
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2143
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2144
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2145
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2146
    try {
2147
        \Defuse\Crypto\File::decryptFileWithPassword(
2148
            $source_file,
2149
            $target_file,
2150
            $password
2151
        );
2152
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2153
        $err = 'wrong_key';
2154
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2155
        $err = $ex;
2156
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2157
        $err = $ex;
2158
    }
2159
2160
    // return error
2161
    return empty($err) === false ? $err : true;
2162
}
2163
2164
/*
2165
* NOT TO BE USED
2166
*/
2167
/**
2168
 * Undocumented function.
2169
 *
2170
 * @param string $text Text to debug
2171
 */
2172
function debugTeampass(string $text): void
2173
{
2174
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
2175
    if ($debugFile !== false) {
2176
        fputs($debugFile, $text);
2177
        fclose($debugFile);
2178
    }
2179
}
2180
2181
/**
2182
 * DELETE the file with expected command depending on server type.
2183
 *
2184
 * @param string $file     Path to file
2185
 * @param array  $SETTINGS Teampass settings
2186
 */
2187
function fileDelete(string $file, array $SETTINGS): void
2188
{
2189
    // Load AntiXSS
2190
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/ASCII.php';
2191
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/UTF8.php';
2192
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/AntiXSS.php';
2193
    $antiXss = new voku\helper\AntiXSS();
2194
    $file = $antiXss->xss_clean($file);
2195
    if (is_file($file)) {
2196
        unlink($file);
2197
    }
2198
}
2199
2200
/**
2201
 * Permits to extract the file extension.
2202
 *
2203
 * @param string $file File name
2204
 */
2205
function getFileExtension(string $file): string
2206
{
2207
    if (strpos($file, '.') === false) {
2208
        return $file;
2209
    }
2210
2211
    return substr($file, strrpos($file, '.') + 1);
2212
}
2213
2214
/**
2215
 * Chmods files and folders with different permissions.
2216
 *
2217
 * This is an all-PHP alternative to using: \n
2218
 * <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2219
 * <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2220
 *
2221
 * @author Jeppe Toustrup (tenzer at tenzer dot dk)
2222
  *
2223
 * @param string $path      An either relative or absolute path to a file or directory which should be processed.
2224
 * @param int    $filePerm The permissions any found files should get.
2225
 * @param int    $dirPerm  The permissions any found folder should get.
2226
 *
2227
 * @return bool Returns TRUE if the path if found and FALSE if not.
2228
 *
2229
 * @warning The permission levels has to be entered in octal format, which
2230
 * normally means adding a zero ("0") in front of the permission level. \n
2231
 * More info at: http://php.net/chmod.
2232
*/
2233
2234
function recursiveChmod($path, $filePerm = 0644, $dirPerm = 0755) {
2235
    // Check if the path exists
2236
    if (! file_exists($path)) {
2237
        return false;
2238
    }
2239
2240
    // See whether this is a file
2241
    if (is_file($path)) {
2242
        // Chmod the file with our given filepermissions
2243
        chmod($path, $filePerm);
2244
    // If this is a directory...
2245
    } elseif (is_dir($path)) {
2246
        // Then get an array of the contents
2247
        $foldersAndFiles = scandir($path);
2248
        // Remove "." and ".." from the list
2249
        $entries = array_slice($foldersAndFiles, 2);
2250
        // Parse every result...
2251
        foreach ($entries as $entry) {
2252
            // And call this function again recursively, with the same permissions
2253
            recursiveChmod($path.'/'.$entry, $filePerm, $dirPerm);
2254
        }
2255
2256
        // When we are done with the contents of the directory, we chmod the directory itself
2257
        chmod($path, $dirPerm);
2258
    }
2259
2260
    // Everything seemed to work out well, return true
2261
    return true;
2262
}
2263
2264
/**
2265
 * Check if user can access to this item.
2266
 *
2267
 * @param int $item_id ID of item
2268
 */
2269
function accessToItemIsGranted(int $item_id, $SETTINGS)
2270
{
2271
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2272
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2273
    // Prepare superGlobal variables
2274
    $session_groupes_visibles = $superGlobal->get('groupes_visibles', 'SESSION');
2275
    $session_list_restricted_folders_for_items = $superGlobal->get('list_restricted_folders_for_items', 'SESSION');
2276
    // Load item data
2277
    $data = DB::queryFirstRow(
2278
        'SELECT id_tree
2279
        FROM ' . prefixTable('items') . '
2280
        WHERE id = %i',
2281
        $item_id
2282
    );
2283
    // Check if user can access this folder
2284
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2285
        // Now check if this folder is restricted to user
2286
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2287
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2288
        ) {
2289
            return 'ERR_FOLDER_NOT_ALLOWED';
2290
        }
2291
    }
2292
2293
    return true;
2294
}
2295
2296
/**
2297
 * Creates a unique key.
2298
 *
2299
 * @param int $lenght Key lenght
2300
 */
2301
function uniqidReal(int $lenght = 13): string
2302
{
2303
    if (function_exists('random_bytes')) {
2304
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2305
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2306
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2307
    } else {
2308
        throw new Exception('no cryptographically secure random function available');
2309
    }
2310
2311
    return substr(bin2hex($bytes), 0, $lenght);
2312
}
2313
2314
/**
2315
 * Obfuscate an email.
2316
 *
2317
 * @param string $email Email address
2318
 */
2319
function obfuscateEmail(string $email): string
2320
{
2321
    $prop = 2;
2322
    $start = '';
2323
    $end = '';
2324
    $domain = substr(strrchr($email, '@'), 1);
2325
    $mailname = str_replace($domain, '', $email);
2326
    $name_l = strlen($mailname);
2327
    $domain_l = strlen($domain);
2328
    for ($i = 0; $i <= $name_l / $prop - 1; ++$i) {
2329
        $start .= 'x';
2330
    }
2331
2332
    for ($i = 0; $i <= $domain_l / $prop - 1; ++$i) {
2333
        $end .= 'x';
2334
    }
2335
2336
    return (string) substr_replace($mailname, $start, 2, $name_l / $prop)
2337
        . (string) substr_replace($domain, $end, 2, $domain_l / $prop);
2338
}
2339
2340
/**
2341
 * Perform a Query.
2342
 *
2343
 * @param array  $SETTINGS Teamapss settings
2344
 * @param string $fields   Fields to use
2345
 * @param string $table    Table to use
2346
 *
2347
 * @return array
2348
 */
2349
function performDBQuery(array $SETTINGS, string $fields, string $table): array
2350
{
2351
    // include librairies & connect to DB
2352
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2353
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2354
    if (defined('DB_PASSWD_CLEAR') === false) {
2355
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2356
    }
2357
    DB::$host = DB_HOST;
2358
    DB::$user = DB_USER;
2359
    DB::$password = DB_PASSWD_CLEAR;
2360
    DB::$dbName = DB_NAME;
2361
    DB::$port = DB_PORT;
2362
    DB::$encoding = DB_ENCODING;
2363
    // Insert log in DB
2364
    return DB::query(
2365
        'SELECT ' . $fields . '
2366
        FROM ' . prefixTable($table)
2367
    );
2368
}
2369
2370
/**
2371
 * Undocumented function.
2372
 *
2373
 * @param int $bytes Size of file
2374
 */
2375
function formatSizeUnits(int $bytes): string
2376
{
2377
    if ($bytes >= 1073741824) {
2378
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2379
    } elseif ($bytes >= 1048576) {
2380
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2381
    } elseif ($bytes >= 1024) {
2382
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2383
    } elseif ($bytes > 1) {
2384
        $bytes .= ' bytes';
2385
    } elseif ($bytes === 1) {
2386
        $bytes .= ' byte';
2387
    } else {
2388
        $bytes = '0 bytes';
2389
    }
2390
2391
    return $bytes;
2392
}
2393
2394
/**
2395
 * Generate user pair of keys.
2396
 *
2397
 * @param string $userPwd User password
2398
 *
2399
 * @return array
2400
 */
2401
function generateUserKeys(string $userPwd): array
2402
{
2403
    // include library
2404
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2405
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2406
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2407
    // Load classes
2408
    $rsa = new Crypt_RSA();
2409
    $cipher = new Crypt_AES();
2410
    // Create the private and public key
2411
    $res = $rsa->createKey(4096);
2412
    // Encrypt the privatekey
2413
    $cipher->setPassword($userPwd);
2414
    $privatekey = $cipher->encrypt($res['privatekey']);
2415
    return [
2416
        'private_key' => base64_encode($privatekey),
2417
        'public_key' => base64_encode($res['publickey']),
2418
        'private_key_clear' => base64_encode($res['privatekey']),
2419
    ];
2420
}
2421
2422
/**
2423
 * Permits to decrypt the user's privatekey.
2424
 *
2425
 * @param string $userPwd        User password
2426
 * @param string $userPrivateKey User private key
2427
 */
2428
function decryptPrivateKey(string $userPwd, string $userPrivateKey): string
2429
{
2430
    if (empty($userPwd) === false) {
2431
        include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2432
        // Load classes
2433
        $cipher = new Crypt_AES();
2434
        // Encrypt the privatekey
2435
        $cipher->setPassword($userPwd);
2436
        return base64_encode($cipher->decrypt(base64_decode($userPrivateKey)));
2437
    }
2438
    return '';
2439
}
2440
2441
/**
2442
 * Permits to encrypt the user's privatekey.
2443
 *
2444
 * @param string $userPwd        User password
2445
 * @param string $userPrivateKey User private key
2446
 */
2447
function encryptPrivateKey(string $userPwd, string $userPrivateKey): string
2448
{
2449
    if (empty($userPwd) === false) {
2450
        include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2451
        // Load classes
2452
        $cipher = new Crypt_AES();
2453
        // Encrypt the privatekey
2454
        $cipher->setPassword($userPwd);
2455
        return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2456
    }
2457
    return '';
2458
}
2459
2460
/**
2461
 * Encrypts a string using AES.
2462
 *
2463
 * @param string $data String to encrypt
2464
 *
2465
 * @return array
2466
 */
2467
function doDataEncryption(string $data): array
2468
{
2469
    // Includes
2470
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2471
    // Load classes
2472
    $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2473
    // Generate an object key
2474
    $objectKey = uniqidReal(32);
2475
    // Set it as password
2476
    $cipher->setPassword($objectKey);
2477
    return [
2478
        'encrypted' => base64_encode($cipher->encrypt($data)),
2479
        'objectKey' => base64_encode($objectKey),
2480
    ];
2481
}
2482
2483
/**
2484
 * Decrypts a string using AES.
2485
 *
2486
 * @param string $data Encrypted data
2487
 * @param string $key  Key to uncrypt
2488
 */
2489
function doDataDecryption(string $data, string $key): string
2490
{
2491
    // Includes
2492
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2493
    // Load classes
2494
    $cipher = new Crypt_AES();
2495
    // Set the object key
2496
    $cipher->setPassword(base64_decode($key));
2497
    return base64_encode($cipher->decrypt(base64_decode($data)));
2498
}
2499
2500
/**
2501
 * Encrypts using RSA a string using a public key.
2502
 *
2503
 * @param string $key       Key to be encrypted
2504
 * @param string $publicKey User public key
2505
 */
2506
function encryptUserObjectKey(string $key, string $publicKey): string
2507
{
2508
    // Includes
2509
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2510
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2511
    // Load classes
2512
    $rsa = new Crypt_RSA();
2513
    $rsa->loadKey(base64_decode($publicKey));
2514
    // Encrypt
2515
    return base64_encode($rsa->encrypt(base64_decode($key)));
2516
}
2517
2518
/**
2519
 * Decrypts using RSA an encrypted string using a private key.
2520
 *
2521
 * @param string $key        Encrypted key
2522
 * @param string $privateKey User private key
2523
 */
2524
function decryptUserObjectKey(string $key, string $privateKey): string
2525
{
2526
    // Includes
2527
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2528
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2529
    // Load classes
2530
    $rsa = new Crypt_RSA();
2531
    $rsa->loadKey(base64_decode($privateKey));
2532
    // Decrypt
2533
    try {
2534
        $ret = base64_encode($rsa->decrypt(base64_decode($key)));
2535
    } catch (Exception $e) {
2536
        return $e;
2537
    }
2538
2539
    return $ret;
2540
}
2541
2542
/**
2543
 * Encrypts a file.
2544
 *
2545
 * @param string $fileInName File name
2546
 * @param string $fileInPath Path to file
2547
 *
2548
 * @return array
2549
 */
2550
function encryptFile(string $fileInName, string $fileInPath): array
2551
{
2552
    if (defined('FILE_BUFFER_SIZE') === false) {
2553
        define('FILE_BUFFER_SIZE', 128 * 1024);
2554
    }
2555
2556
    // Includes
2557
    include_once '../includes/config/include.php';
2558
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2559
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2560
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2561
    // Load classes
2562
    $cipher = new Crypt_AES();
2563
    // Generate an object key
2564
    $objectKey = uniqidReal(32);
2565
    // Set it as password
2566
    $cipher->setPassword($objectKey);
2567
    // Prevent against out of memory
2568
    $cipher->enableContinuousBuffer();
2569
    //$cipher->disablePadding();
2570
2571
    // Encrypt the file content
2572
    $plaintext = file_get_contents(
2573
        filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL)
2574
    );
2575
    $ciphertext = $cipher->encrypt($plaintext);
2576
    // Save new file
2577
    $hash = md5($plaintext);
2578
    $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2579
    file_put_contents($fileOut, $ciphertext);
2580
    unlink($fileInPath . '/' . $fileInName);
2581
    return [
2582
        'fileHash' => base64_encode($hash),
2583
        'objectKey' => base64_encode($objectKey),
2584
    ];
2585
}
2586
2587
/**
2588
 * Decrypt a file.
2589
 *
2590
 * @param string $fileName File name
2591
 * @param string $filePath Path to file
2592
 * @param string $key      Key to use
2593
 */
2594
function decryptFile(string $fileName, string $filePath, string $key): string
2595
{
2596
    if (! defined('FILE_BUFFER_SIZE')) {
2597
        define('FILE_BUFFER_SIZE', 128 * 1024);
2598
    }
2599
2600
    // Includes
2601
    include_once '../includes/config/include.php';
2602
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2603
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2604
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2605
    // Get file name
2606
    $fileName = base64_decode($fileName);
2607
    // Load classes
2608
    $cipher = new Crypt_AES();
2609
    // Set the object key
2610
    $cipher->setPassword(base64_decode($key));
2611
    // Prevent against out of memory
2612
    $cipher->enableContinuousBuffer();
2613
    $cipher->disablePadding();
2614
    // Get file content
2615
    $ciphertext = file_get_contents($filePath . '/' . TP_FILE_PREFIX . $fileName);
2616
    // Decrypt file content and return
2617
    return base64_encode($cipher->decrypt($ciphertext));
2618
}
2619
2620
/**
2621
 * Generate a simple password
2622
 *
2623
 * @param int $length Length of string
2624
 * @param bool $symbolsincluded Allow symbols
2625
 */
2626
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2627
{
2628
    // Generate new user password
2629
    $small_letters = range('a', 'z');
2630
    $big_letters = range('A', 'Z');
2631
    $digits = range(0, 9);
2632
    $symbols = $symbolsincluded === true ?
2633
        ['#', '_', '-', '@', '$', '+', '&'] : [];
2634
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2635
    $count = count($res);
2636
    // first variant
2637
2638
    $random_string = '';
2639
    for ($i = 0; $i < $length; ++$i) {
2640
        $random_string .= $res[random_int(0, $count - 1)];
2641
    }
2642
2643
    return $random_string;
2644
}
2645
2646
/**
2647
 * Permit to store the sharekey of an object for users.
2648
 *
2649
 * @param string $object_name             Type for table selection
2650
 * @param int    $post_folder_is_personal Personal
2651
 * @param int    $post_folder_id          Folder
2652
 * @param int    $post_object_id          Object
2653
 * @param string $objectKey               Object key
2654
 * @param array  $SETTINGS                Teampass settings
2655
 */
2656
function storeUsersShareKey(
2657
    string $object_name,
2658
    int $post_folder_is_personal,
2659
    int $post_folder_id,
2660
    int $post_object_id,
2661
    string $objectKey,
2662
    array $SETTINGS
2663
): void {
2664
    // include librairies & connect to DB
2665
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2666
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2667
    if (defined('DB_PASSWD_CLEAR') === false) {
2668
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2669
    }
2670
    DB::$host = DB_HOST;
2671
    DB::$user = DB_USER;
2672
    DB::$password = DB_PASSWD_CLEAR;
2673
    DB::$dbName = DB_NAME;
2674
    DB::$port = DB_PORT;
2675
    DB::$encoding = DB_ENCODING;
2676
    // Delete existing entries for this object
2677
    DB::delete(
2678
        $object_name,
2679
        'object_id = %i',
2680
        $post_object_id
2681
    );
2682
    // Superglobals
2683
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2684
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2685
    // Prepare superGlobal variables
2686
    $sessionPpersonaFolders = $superGlobal->get('personal_folders', 'SESSION');
2687
    $sessionUserId = $superGlobal->get('user_id', 'SESSION');
2688
    $sessionUserPublicKey = $superGlobal->get('public_key', 'SESSION', 'user');
2689
    if (
2690
        (int) $post_folder_is_personal === 1
2691
        && in_array($post_folder_id, $sessionPpersonaFolders) === true
2692
    ) {
2693
        // If this is a personal object
2694
        // Only create the sharekey for user
2695
        DB::insert(
2696
            $object_name,
2697
            [
2698
                'object_id' => (int) $post_object_id,
2699
                'user_id' => (int) $sessionUserId,
2700
                'share_key' => encryptUserObjectKey($objectKey, $sessionUserPublicKey),
2701
            ]
2702
        );
2703
    } else {
2704
        // This is a public object
2705
        // Create sharekey for each user
2706
        $users = DB::query(
2707
            'SELECT id, public_key
2708
            FROM ' . prefixTable('users') . '
2709
            WHERE id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '")
2710
            AND public_key != ""'
2711
        );
2712
        foreach ($users as $user) {
2713
            // Insert in DB the new object key for this item by user
2714
            DB::insert(
2715
                $object_name,
2716
                [
2717
                    'object_id' => $post_object_id,
2718
                    'user_id' => (int) $user['id'],
2719
                    'share_key' => encryptUserObjectKey(
2720
                        $objectKey,
2721
                        $user['public_key']
2722
                    ),
2723
                ]
2724
            );
2725
        }
2726
    }
2727
}
2728
2729
/**
2730
 * Is this string base64 encoded?
2731
 *
2732
 * @param string $str Encoded string?
2733
 */
2734
function isBase64(string $str): bool
2735
{
2736
    $str = (string) trim($str);
2737
    if (! isset($str[0])) {
2738
        return false;
2739
    }
2740
2741
    $base64String = (string) base64_decode($str, true);
2742
    if ($base64String && base64_encode($base64String) === $str) {
2743
        return true;
2744
    }
2745
2746
    return false;
2747
}
2748
2749
/**
2750
 * Undocumented function
2751
 *
2752
 * @param string $field Parameter
2753
 *
2754
 * @return array|bool|resource|string
2755
 */
2756
function filterString(string $field)
2757
{
2758
    // Sanitize string
2759
    $field = filter_var(trim($field), FILTER_SANITIZE_STRING);
2760
    if (empty($field) === false) {
2761
        // Load AntiXSS
2762
        include_once '../includes/libraries/voku/helper/AntiXSS.php';
2763
        $antiXss = new voku\helper\AntiXSS();
2764
        // Return
2765
        return $antiXss->xss_clean($field);
2766
    }
2767
2768
    return false;
2769
}
2770
2771
/**
2772
 * CHeck if provided credentials are allowed on server
2773
 *
2774
 * @param string $login    User Login
2775
 * @param string $password User Pwd
2776
 * @param array  $SETTINGS Teampass settings
2777
 *
2778
 * @return bool
2779
 */
2780
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
2781
{
2782
    // Build ldap configuration array
2783
    $config = [
2784
        // Mandatory Configuration Options
2785
        'hosts' => [$SETTINGS['ldap_hosts']],
2786
        'base_dn' => $SETTINGS['ldap_bdn'],
2787
        'username' => $SETTINGS['ldap_username'],
2788
        'password' => $SETTINGS['ldap_password'],
2789
2790
        // Optional Configuration Options
2791
        'port' => $SETTINGS['ldap_port'],
2792
        'use_ssl' => (int) $SETTINGS['ldap_ssl'] === 1 ? true : false,
2793
        'use_tls' => (int) $SETTINGS['ldap_tls'] === 1 ? true : false,
2794
        'version' => 3,
2795
        'timeout' => 5,
2796
        'follow_referrals' => false,
2797
2798
        // Custom LDAP Options
2799
        'options' => [
2800
            // See: http://php.net/ldap_set_option
2801
            LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_HARD,
2802
        ],
2803
    ];
2804
    // Load expected libraries
2805
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Traits/Macroable.php';
2806
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Arr.php';
2807
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/DetectsErrors.php';
2808
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Connection.php';
2809
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/LdapInterface.php';
2810
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/LdapBase.php';
2811
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Ldap.php';
2812
    $ad = new SplClassLoader('LdapRecord', '../includes/libraries');
2813
    $ad->register();
2814
    $connection = new Connection($config);
2815
    // Connect to LDAP
2816
    try {
2817
        $connection->connect();
2818
    } catch (\LdapRecord\Auth\BindException $e) {
2819
        $error = $e->getDetailedError();
2820
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
2821
        return false;
2822
    }
2823
2824
    // Authenticate user
2825
    try {
2826
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory') {
2827
            $connection->auth()->attempt($login, $password, $stayAuthenticated = true);
2828
        } else {
2829
            $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);
2830
        }
2831
    } catch (\LdapRecord\Auth\BindException $e) {
2832
        $error = $e->getDetailedError();
2833
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
2834
        return false;
2835
    }
2836
2837
    return true;
2838
}
2839
2840
/**
2841
 * Removes from DB all sharekeys of this user
2842
 *
2843
 * @param int $userId User's id
2844
 * @param array   $SETTINGS Teampass settings
2845
 *
2846
 * @return bool
2847
 */
2848
function deleteUserObjetsKeys(int $userId, array $SETTINGS): bool
2849
{
2850
    // include librairies & connect to DB
2851
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2852
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2853
    if (defined('DB_PASSWD_CLEAR') === false) {
2854
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2855
    }
2856
    DB::$host = DB_HOST;
2857
    DB::$user = DB_USER;
2858
    DB::$password = DB_PASSWD_CLEAR;
2859
    DB::$dbName = DB_NAME;
2860
    DB::$port = DB_PORT;
2861
    DB::$encoding = DB_ENCODING;
2862
    // Remove all item sharekeys items
2863
    DB::delete(
2864
        prefixTable('sharekeys_items'),
2865
        'user_id = %i',
2866
        $userId
2867
    );
2868
    // Remove all item sharekeys files
2869
    DB::delete(
2870
        prefixTable('sharekeys_files'),
2871
        'user_id = %i',
2872
        $userId
2873
    );
2874
    // Remove all item sharekeys fields
2875
    DB::delete(
2876
        prefixTable('sharekeys_fields'),
2877
        'user_id = %i',
2878
        $userId
2879
    );
2880
    // Remove all item sharekeys logs
2881
    DB::delete(
2882
        prefixTable('sharekeys_logs'),
2883
        'user_id = %i',
2884
        $userId
2885
    );
2886
    // Remove all item sharekeys suggestions
2887
    DB::delete(
2888
        prefixTable('sharekeys_suggestions'),
2889
        'user_id = %i',
2890
        $userId
2891
    );
2892
    return false;
2893
}
2894
2895
/**
2896
 * Manage list of timezones   $SETTINGS Teampass settings
2897
 *
2898
 * @return array
2899
 */
2900
function timezone_list()
2901
{
2902
    static $timezones = null;
2903
    if ($timezones === null) {
2904
        $timezones = [];
2905
        $offsets = [];
2906
        $now = new DateTime('now', new DateTimeZone('UTC'));
2907
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
2908
            $now->setTimezone(new DateTimeZone($timezone));
2909
            $offsets[] = $offset = $now->getOffset();
2910
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
2911
        }
2912
2913
        array_multisort($offsets, $timezones);
2914
    }
2915
2916
    return $timezones;
2917
}
2918
2919
/**
2920
 * Provide timezone offset
2921
 *
2922
 * @param int $offset Timezone offset
2923
 *
2924
 * @return string
2925
 */
2926
function format_GMT_offset($offset)
2927
{
2928
    $hours = intval($offset / 3600);
2929
    $minutes = abs(intval($offset % 3600 / 60));
2930
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
2931
}
2932
2933
/**
2934
 * Provides timezone name
2935
 *
2936
 * @param string $name Timezone name
2937
 *
2938
 * @return string
2939
 */
2940
function format_timezone_name($name)
2941
{
2942
    $name = str_replace('/', ', ', $name);
2943
    $name = str_replace('_', ' ', $name);
2944
2945
    return str_replace('St ', 'St. ', $name);
2946
}
2947
2948
/**
2949
 * Provides info about if user should use MFA
2950
 *
2951
 * @param string $userRolesIds  User roles ids
2952
 * @param string $mfaRoles      Roles for which MFA is requested
2953
 *
2954
 * @return bool
2955
 */
2956
function mfa_auth_requested(string $userRolesIds, string $mfaRoles): bool
2957
{
2958
    if (empty($mfaRoles) === true) {
2959
        return false;
2960
    }
2961
2962
    $mfaRoles = array_values(json_decode($mfaRoles, true));
2963
    $userRolesIds = array_filter(explode(';', $userRolesIds));
2964
    if (count($mfaRoles) === 0 || count($mfaRoles) === 0) {
2965
        return true;
2966
    }
2967
2968
    if (count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
2969
        return true;
2970
    }
2971
    return false;
2972
}
2973