Passed
Push — teampass_3.0 ( b28540...71c6f8 )
by Nils
04:22
created

cryption()   C

Complexity

Conditions 12
Paths 104

Size

Total Lines 48
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 12
eloc 35
c 6
b 0
f 0
nc 104
nop 4
dl 0
loc 48
rs 6.9333

How to fix   Complexity   

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
686
    // Return data
687
    $superGlobal->put('all_non_personal_folders', $allowedFolders, 'SESSION');
688
    $superGlobal->put('groupes_visibles', array_merge($allowedFolders, $personalFolders), 'SESSION');
689
    $superGlobal->put('read_only_folders', $readOnlyFolders, 'SESSION');
690
    $superGlobal->put('no_access_folders', $noAccessFolders, 'SESSION');
691
    $superGlobal->put('personal_folders', $personalFolders, 'SESSION');
692
    $superGlobal->put('list_folders_limited', $foldersLimited, 'SESSION');
693
    $superGlobal->put('list_folders_editable_by_role', $allowedFoldersByRoles, 'SESSION');
694
    $superGlobal->put('list_restricted_folders_for_items', $restrictedFoldersForItems, 'SESSION');
695
    $superGlobal->put('forbiden_pfs', $noAccessPersonalFolders, 'SESSION');
696
    $superGlobal->put(
697
        'all_folders_including_no_access',
698
        array_merge(
699
            $allowedFolders,
700
            $personalFolders,
701
            $noAccessFolders,
702
            $readOnlyFolders
703
        ),
704
        'SESSION'
705
    );
706
    // Folders and Roles numbers
707
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('nested_tree') . '');
708
    $superGlobal->put('nb_folders', DB::count(), 'SESSION');
709
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('roles_title'));
710
    $superGlobal->put('nb_roles', DB::count(), 'SESSION');
711
    // check if change proposals on User's items
712
    if (isset($SETTINGS['enable_suggestion']) === true && (int) $SETTINGS['enable_suggestion'] === 1) {
713
        DB::query(
714
            'SELECT *
715
            FROM ' . prefixTable('items_change') . ' AS c
716
            LEFT JOIN ' . prefixTable('log_items') . ' AS i ON (c.item_id = i.id_item)
717
            WHERE i.action = %s AND i.id_user = %i',
718
            'at_creation',
719
            $globalsUserId
720
        );
721
        $superGlobal->put('nb_item_change_proposals', DB::count(), 'SESSION');
722
    } else {
723
        $superGlobal->put('nb_item_change_proposals', 0, 'SESSION');
724
    }
725
726
    return true;
727
}
728
729
/**
730
 * Update the CACHE table.
731
 *
732
 * @param string $action   What to do
733
 * @param array  $SETTINGS Teampass settings
734
 * @param int    $ident    Ident format
735
 */
736
function updateCacheTable(string $action, array $SETTINGS, ?int $ident = null): void
737
{
738
    if ($action === 'reload') {
739
        // Rebuild full cache table
740
        cacheTableRefresh($SETTINGS);
741
    } elseif ($action === 'update_value' && is_null($ident) === false) {
742
        // UPDATE an item
743
        cacheTableUpdate($SETTINGS, $ident);
744
    } elseif ($action === 'add_value' && is_null($ident) === false) {
745
        // ADD an item
746
        cacheTableAdd($SETTINGS, $ident);
747
    } elseif ($action === 'delete_value' && is_null($ident) === false) {
748
        // DELETE an item
749
        DB::delete(prefixTable('cache'), 'id = %i', $ident);
750
    }
751
}
752
753
/**
754
 * Cache table - refresh.
755
 *
756
 * @param array $SETTINGS Teampass settings
757
 */
758
function cacheTableRefresh(array $SETTINGS): void
759
{
760
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
761
    //Connect to DB
762
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
763
    if (defined('DB_PASSWD_CLEAR') === false) {
764
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
765
    }
766
    DB::$host = DB_HOST;
767
    DB::$user = DB_USER;
768
    DB::$password = DB_PASSWD_CLEAR;
769
    DB::$dbName = DB_NAME;
770
    DB::$port = DB_PORT;
771
    DB::$encoding = DB_ENCODING;
772
    //Load Tree
773
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
774
    $tree->register();
775
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
776
    // truncate table
777
    DB::query('TRUNCATE TABLE ' . prefixTable('cache'));
778
    // reload date
779
    $rows = DB::query(
780
        'SELECT *
781
        FROM ' . prefixTable('items') . ' as i
782
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
783
        AND l.action = %s
784
        AND i.inactif = %i',
785
        'at_creation',
786
        0
787
    );
788
    foreach ($rows as $record) {
789
        if (empty($record['id_tree']) === false) {
790
            // Get all TAGS
791
            $tags = '';
792
            $itemTags = DB::query(
793
                'SELECT tag
794
                            FROM ' . prefixTable('tags') . '
795
                            WHERE item_id = %i AND tag != ""',
796
                $record['id']
797
            );
798
            foreach ($itemTags as $itemTag) {
799
                $tags .= $itemTag['tag'] . ' ';
800
            }
801
802
            // Get renewal period
803
            $resNT = DB::queryfirstrow(
804
                'SELECT renewal_period
805
                FROM ' . prefixTable('nested_tree') . '
806
                WHERE id = %i',
807
                $record['id_tree']
808
            );
809
            // form id_tree to full foldername
810
            $folder = [];
811
            $arbo = $tree->getPath($record['id_tree'], true);
812
            foreach ($arbo as $elem) {
813
                // Check if title is the ID of a user
814
                if (is_numeric($elem->title) === true) {
815
                    // Is this a User id?
816
                    $user = DB::queryfirstrow(
817
                        'SELECT id, login
818
                        FROM ' . prefixTable('users') . '
819
                        WHERE id = %i',
820
                        $elem->title
821
                    );
822
                    if (count($user) > 0) {
823
                        $elem->title = $user['login'];
824
                    }
825
                }
826
                // Build path
827
                array_push($folder, stripslashes($elem->title));
828
            }
829
            // store data
830
            DB::insert(
831
                prefixTable('cache'),
832
                [
833
                    'id' => $record['id'],
834
                    'label' => $record['label'],
835
                    'description' => $record['description'] ?? '',
836
                    'url' => isset($record['url']) && ! empty($record['url']) ? $record['url'] : '0',
837
                    'tags' => $tags,
838
                    'id_tree' => $record['id_tree'],
839
                    'perso' => $record['perso'],
840
                    'restricted_to' => isset($record['restricted_to']) && ! empty($record['restricted_to']) ? $record['restricted_to'] : '0',
841
                    'login' => $record['login'] ?? '',
842
                    'folder' => implode(' > ', $folder),
843
                    'author' => $record['id_user'],
844
                    'renewal_period' => $resNT['renewal_period'] ?? '0',
845
                    'timestamp' => $record['date'],
846
                ]
847
            );
848
        }
849
    }
850
}
851
852
/**
853
 * Cache table - update existing value.
854
 *
855
 * @param array  $SETTINGS Teampass settings
856
 * @param int    $ident    Ident format
857
 */
858
function cacheTableUpdate(array $SETTINGS, ?int $ident = null): void
859
{
860
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
861
    // Load superglobal
862
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
863
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
864
    //Connect to DB
865
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
866
    if (defined('DB_PASSWD_CLEAR') === false) {
867
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
868
    }
869
    DB::$host = DB_HOST;
870
    DB::$user = DB_USER;
871
    DB::$password = DB_PASSWD_CLEAR;
872
    DB::$dbName = DB_NAME;
873
    DB::$port = DB_PORT;
874
    DB::$encoding = DB_ENCODING;
875
    //Load Tree
876
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
877
    $tree->register();
878
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
879
    // get new value from db
880
    $data = DB::queryfirstrow(
881
        'SELECT label, description, id_tree, perso, restricted_to, login, url
882
        FROM ' . prefixTable('items') . '
883
        WHERE id=%i',
884
        $ident
885
    );
886
    // Get all TAGS
887
    $tags = '';
888
    $itemTags = DB::query(
889
        'SELECT tag
890
            FROM ' . prefixTable('tags') . '
891
            WHERE item_id = %i AND tag != ""',
892
        $ident
893
    );
894
    foreach ($itemTags as $itemTag) {
895
        $tags .= $itemTag['tag'] . ' ';
896
    }
897
    // form id_tree to full foldername
898
    $folder = [];
899
    $arbo = $tree->getPath($data['id_tree'], true);
900
    foreach ($arbo as $elem) {
901
        // Check if title is the ID of a user
902
        if (is_numeric($elem->title) === true) {
903
            // Is this a User id?
904
            $user = DB::queryfirstrow(
905
                'SELECT id, login
906
                FROM ' . prefixTable('users') . '
907
                WHERE id = %i',
908
                $elem->title
909
            );
910
            if (count($user) > 0) {
911
                $elem->title = $user['login'];
912
            }
913
        }
914
        // Build path
915
        array_push($folder, stripslashes($elem->title));
916
    }
917
    // finaly update
918
    DB::update(
919
        prefixTable('cache'),
920
        [
921
            'label' => $data['label'],
922
            'description' => $data['description'],
923
            'tags' => $tags,
924
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
925
            'id_tree' => $data['id_tree'],
926
            'perso' => $data['perso'],
927
            'restricted_to' => isset($data['restricted_to']) && ! empty($data['restricted_to']) ? $data['restricted_to'] : '0',
928
            'login' => $data['login'] ?? '',
929
            'folder' => implode(' » ', $folder),
930
            'author' => $superGlobal->get('user_id', 'SESSION'),
931
        ],
932
        'id = %i',
933
        $ident
934
    );
935
}
936
937
/**
938
 * Cache table - add new value.
939
 *
940
 * @param array  $SETTINGS Teampass settings
941
 * @param int    $ident    Ident format
942
 */
943
function cacheTableAdd(array $SETTINGS, ?int $ident = null): void
944
{
945
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
946
    // Load superglobal
947
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
948
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
949
    // Get superglobals
950
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
951
    //Connect to DB
952
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
953
    if (defined('DB_PASSWD_CLEAR') === false) {
954
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
955
    }
956
    DB::$host = DB_HOST;
957
    DB::$user = DB_USER;
958
    DB::$password = DB_PASSWD_CLEAR;
959
    DB::$dbName = DB_NAME;
960
    DB::$port = DB_PORT;
961
    DB::$encoding = DB_ENCODING;
962
    //Load Tree
963
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
964
    $tree->register();
965
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
966
    // get new value from db
967
    $data = DB::queryFirstRow(
968
        'SELECT i.label, i.description, i.id_tree as id_tree, i.perso, i.restricted_to, i.id, i.login, i.url, l.date
969
        FROM ' . prefixTable('items') . ' as i
970
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
971
        WHERE i.id = %i
972
        AND l.action = %s',
973
        $ident,
974
        'at_creation'
975
    );
976
    // Get all TAGS
977
    $tags = '';
978
    $itemTags = DB::query(
979
        'SELECT tag
980
            FROM ' . prefixTable('tags') . '
981
            WHERE item_id = %i AND tag != ""',
982
        $ident
983
    );
984
    foreach ($itemTags as $itemTag) {
985
        $tags .= $itemTag['tag'] . ' ';
986
    }
987
    // form id_tree to full foldername
988
    $folder = [];
989
    $arbo = $tree->getPath($data['id_tree'], true);
990
    foreach ($arbo as $elem) {
991
        // Check if title is the ID of a user
992
        if (is_numeric($elem->title) === true) {
993
            // Is this a User id?
994
            $user = DB::queryfirstrow(
995
                'SELECT id, login
996
                FROM ' . prefixTable('users') . '
997
                WHERE id = %i',
998
                $elem->title
999
            );
1000
            if (count($user) > 0) {
1001
                $elem->title = $user['login'];
1002
            }
1003
        }
1004
        // Build path
1005
        array_push($folder, stripslashes($elem->title));
1006
    }
1007
    // finaly update
1008
    DB::insert(
1009
        prefixTable('cache'),
1010
        [
1011
            'id' => $data['id'],
1012
            'label' => $data['label'],
1013
            'description' => $data['description'],
1014
            'tags' => isset($tags) && empty($tags) === false ? $tags : 'None',
1015
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
1016
            'id_tree' => $data['id_tree'],
1017
            'perso' => isset($data['perso']) && empty($data['perso']) === false && $data['perso'] !== 'None' ? $data['perso'] : '0',
1018
            'restricted_to' => isset($data['restricted_to']) && empty($data['restricted_to']) === false ? $data['restricted_to'] : '0',
1019
            'login' => $data['login'] ?? '',
1020
            'folder' => implode(' » ', $folder),
1021
            'author' => $globalsUserId,
1022
            'timestamp' => $data['date'],
1023
        ]
1024
    );
1025
}
1026
1027
/**
1028
 * Do statistics.
1029
 *
1030
 * @param array $SETTINGS Teampass settings
1031
 *
1032
 * @return array
1033
 */
1034
function getStatisticsData(array $SETTINGS): array
1035
{
1036
    DB::query(
1037
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1038
        0
1039
    );
1040
    $counter_folders = DB::count();
1041
    DB::query(
1042
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1043
        1
1044
    );
1045
    $counter_folders_perso = DB::count();
1046
    DB::query(
1047
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1048
        0
1049
    );
1050
    $counter_items = DB::count();
1051
        DB::query(
1052
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1053
        1
1054
    );
1055
    $counter_items_perso = DB::count();
1056
        DB::query(
1057
        'SELECT id FROM ' . prefixTable('users') . ''
1058
    );
1059
    $counter_users = DB::count();
1060
        DB::query(
1061
        'SELECT id FROM ' . prefixTable('users') . ' WHERE admin = %i',
1062
        1
1063
    );
1064
    $admins = DB::count();
1065
    DB::query(
1066
        'SELECT id FROM ' . prefixTable('users') . ' WHERE gestionnaire = %i',
1067
        1
1068
    );
1069
    $managers = DB::count();
1070
    DB::query(
1071
        'SELECT id FROM ' . prefixTable('users') . ' WHERE read_only = %i',
1072
        1
1073
    );
1074
    $readOnly = DB::count();
1075
    // list the languages
1076
    $usedLang = [];
1077
    $tp_languages = DB::query(
1078
        'SELECT name FROM ' . prefixTable('languages')
1079
    );
1080
    foreach ($tp_languages as $tp_language) {
1081
        DB::query(
1082
            'SELECT * FROM ' . prefixTable('users') . ' WHERE user_language = %s',
1083
            $tp_language['name']
1084
        );
1085
        $usedLang[$tp_language['name']] = round((DB::count() * 100 / $counter_users), 0);
1086
    }
1087
1088
    // get list of ips
1089
    $usedIp = [];
1090
    $tp_ips = DB::query(
1091
        'SELECT user_ip FROM ' . prefixTable('users')
1092
    );
1093
    foreach ($tp_ips as $ip) {
1094
        if (array_key_exists($ip['user_ip'], $usedIp)) {
1095
            $usedIp[$ip['user_ip']] += $usedIp[$ip['user_ip']];
1096
        } elseif (! empty($ip['user_ip']) && $ip['user_ip'] !== 'none') {
1097
            $usedIp[$ip['user_ip']] = 1;
1098
        }
1099
    }
1100
1101
    return [
1102
        'error' => '',
1103
        'stat_phpversion' => phpversion(),
1104
        'stat_folders' => $counter_folders,
1105
        'stat_folders_shared' => intval($counter_folders) - intval($counter_folders_perso),
1106
        'stat_items' => $counter_items,
1107
        'stat_items_shared' => intval($counter_items) - intval($counter_items_perso),
1108
        'stat_users' => $counter_users,
1109
        'stat_admins' => $admins,
1110
        'stat_managers' => $managers,
1111
        'stat_ro' => $readOnly,
1112
        'stat_kb' => $SETTINGS['enable_kb'],
1113
        'stat_pf' => $SETTINGS['enable_pf_feature'],
1114
        'stat_fav' => $SETTINGS['enable_favourites'],
1115
        'stat_teampassversion' => TP_VERSION_FULL,
1116
        'stat_ldap' => $SETTINGS['ldap_mode'],
1117
        'stat_agses' => $SETTINGS['agses_authentication_enabled'],
1118
        'stat_duo' => $SETTINGS['duo'],
1119
        'stat_suggestion' => $SETTINGS['enable_suggestion'],
1120
        'stat_api' => $SETTINGS['api'],
1121
        'stat_customfields' => $SETTINGS['item_extra_fields'],
1122
        'stat_syslog' => $SETTINGS['syslog_enable'],
1123
        'stat_2fa' => $SETTINGS['google_authentication'],
1124
        'stat_stricthttps' => $SETTINGS['enable_sts'],
1125
        'stat_mysqlversion' => DB::serverVersion(),
1126
        'stat_languages' => $usedLang,
1127
        'stat_country' => $usedIp,
1128
    ];
1129
}
1130
1131
/**
1132
 * Permits to send an email.
1133
 *
1134
 * @param string $subject     email subject
1135
 * @param string $textMail    email message
1136
 * @param string $email       email
1137
 * @param array  $SETTINGS    settings
1138
 * @param string $textMailAlt email message alt
1139
 * @param bool   $silent      no errors
1140
 *
1141
 * @return string some json info
1142
 */
1143
function sendEmail(
1144
    $subject,
1145
    $textMail,
1146
    $email,
1147
    $SETTINGS,
1148
    $textMailAlt = null,
1149
    $silent = true
1150
) {
1151
    // CAse where email not defined
1152
    if ($email === 'none' || empty($email) === true) {
1153
        return json_encode(
1154
            [
1155
                'error' => true,
1156
                'message' => langHdl('forgot_my_pw_email_sent'),
1157
            ]
1158
        );
1159
    }
1160
1161
    // Load settings
1162
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
1163
    // Load superglobal
1164
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1165
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1166
    // Get user language
1167
    include_once $SETTINGS['cpassman_dir'] . '/includes/language/' . $superGlobal->get('user_language', 'SESSION') . '.php';
1168
    // Load library
1169
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1170
    // load PHPMailer
1171
    $mail = new SplClassLoader('PHPMailer\PHPMailer', '../includes/libraries');
1172
    $mail->register();
1173
    $mail = new PHPMailer\PHPMailer\PHPMailer(true);
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
        if (isset($SETTINGS['encryptClientServer'])
1405
            && (int) $SETTINGS['encryptClientServer'] === 0
1406
        ) {
1407
            return json_encode(
1408
                $data,
1409
                JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1410
            );
1411
        }
1412
        return Encryption\Crypt\aesctr::encrypt(
1413
            json_encode(
1414
                $data,
1415
                JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1416
            ),
1417
            $globalsKey,
1418
            256
1419
        );
1420
    }
1421
    if ($type === 'decode' && is_array($data) === false) {
1422
        if (isset($SETTINGS['encryptClientServer'])
1423
            && (int) $SETTINGS['encryptClientServer'] === 0
1424
        ) {
1425
            return json_decode(
1426
                /** @scrutinizer ignore-type */
1427
                (string) $data,
1428
                true
1429
            );
1430
        }
1431
        return json_decode(
1432
            Encryption\Crypt\aesctr::decrypt(
1433
                /** @scrutinizer ignore-type */
1434
                (string) $data,
1435
                $globalsKey,
1436
                256
1437
            ),
1438
            true
1439
        );
1440
    }
1441
}
1442
1443
/**
1444
 * Create a thumbnail.
1445
 *
1446
 * @param string  $src           Source
1447
 * @param string  $dest          Destination
1448
 * @param int $desired_width Size of width
1449
 */
1450
function makeThumbnail(string $src, string $dest, int $desired_width)
1451
{
1452
    /* read the source image */
1453
    if (is_file($src) === true && mime_content_type($src) === 'image/png') {
1454
        $source_image = imagecreatefrompng($src);
1455
        if ($source_image === false) {
1456
            return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1457
        }
1458
    } else {
1459
        return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1460
    }
1461
1462
    // Get height and width
1463
    $width = imagesx($source_image);
1464
    $height = imagesy($source_image);
1465
    /* find the "desired height" of this thumbnail, relative to the desired width  */
1466
    $desired_height = (int) floor($height * $desired_width / $width);
1467
    /* create a new, "virtual" image */
1468
    $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
1469
    if ($virtual_image === false) {
1470
        return false;
1471
    }
1472
    /* copy source image at a resized size */
1473
    imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
1474
    /* create the physical thumbnail image to its destination */
1475
    imagejpeg($virtual_image, $dest);
1476
}
1477
1478
/**
1479
 * Check table prefix in SQL query.
1480
 *
1481
 * @param string $table Table name
1482
 */
1483
function prefixTable(string $table): string
1484
{
1485
    $safeTable = htmlspecialchars(DB_PREFIX . $table);
1486
    if (! empty($safeTable)) {
1487
        // sanitize string
1488
        return $safeTable;
1489
    }
1490
    // stop error no table
1491
    return 'table_not_exists';
1492
}
1493
1494
/**
1495
 * GenerateCryptKey
1496
 *
1497
 * @param int     $size      Length
1498
 * @param bool $secure Secure
1499
 * @param bool $numerals Numerics
1500
 * @param bool $uppercase Uppercase letters
1501
 * @param bool $symbols Symbols
1502
 * @param bool $lowercase Lowercase
1503
 * @param array   $SETTINGS  SETTINGS
1504
 */
1505
function GenerateCryptKey(
1506
    int $size = 10,
1507
    bool $secure = false,
1508
    bool $numerals = false,
1509
    bool $uppercase = false,
1510
    bool $symbols = false,
1511
    bool $lowercase = false,
1512
    array $SETTINGS = []
1513
): string {
1514
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1515
    $generator = new SplClassLoader('PasswordGenerator\Generator', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1516
    $generator->register();
1517
    $generator = new PasswordGenerator\Generator\ComputerPasswordGenerator();
1518
    // Is PHP7 being used?
1519
    if (version_compare(PHP_VERSION, '7.0.0', '>=')) {
1520
        $php7generator = new SplClassLoader('PasswordGenerator\RandomGenerator', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1521
        $php7generator->register();
1522
        $generator->setRandomGenerator(new PasswordGenerator\RandomGenerator\Php7RandomGenerator());
1523
    }
1524
1525
    // Manage size
1526
    $generator->setLength((int) $size);
1527
    if ($secure === true) {
1528
        $generator->setSymbols(true);
1529
        $generator->setLowercase(true);
1530
        $generator->setUppercase(true);
1531
        $generator->setNumbers(true);
1532
    } else {
1533
        $generator->setLowercase($lowercase);
1534
        $generator->setUppercase($uppercase);
1535
        $generator->setNumbers($numerals);
1536
        $generator->setSymbols($symbols);
1537
    }
1538
1539
    return $generator->generatePasswords()[0];
1540
}
1541
1542
/*
1543
* Send sysLOG message
1544
* @param string $message
1545
* @param string $host
1546
*/
1547
function send_syslog($message, $host, $port, $component = 'teampass'): void
1548
{
1549
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1550
    $syslog_message = '<123>' . date('M d H:i:s ') . $component . ': ' . $message;
1551
    socket_sendto($sock, $syslog_message, strlen($syslog_message), 0, $host, $port);
1552
    socket_close($sock);
1553
}
1554
1555
/**
1556
 * Permits to log events into DB
1557
 *
1558
 * @param array  $SETTINGS Teampass settings
1559
 * @param string $type     Type
1560
 * @param string $label    Label
1561
 * @param string $who      Who
1562
 * @param string $login    Login
1563
 * @param string $field_1  Field
1564
 */
1565
function logEvents(array $SETTINGS, string $type, string $label, string $who, ?string $login = null, ?string $field_1 = null): void
1566
{
1567
    if (empty($who)) {
1568
        $who = getClientIpServer();
1569
    }
1570
1571
    // include librairies & connect to DB
1572
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1573
    if (defined('DB_PASSWD_CLEAR') === false) {
1574
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1575
    }
1576
    DB::$host = DB_HOST;
1577
    DB::$user = DB_USER;
1578
    DB::$password = DB_PASSWD_CLEAR;
1579
    DB::$dbName = DB_NAME;
1580
    DB::$port = DB_PORT;
1581
    DB::$encoding = DB_ENCODING;
1582
    DB::insert(
1583
        prefixTable('log_system'),
1584
        [
1585
            'type' => $type,
1586
            'date' => time(),
1587
            'label' => $label,
1588
            'qui' => $who,
1589
            'field_1' => $field_1 === null ? '' : $field_1,
1590
        ]
1591
    );
1592
    // If SYSLOG
1593
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1594
        if ($type === 'user_mngt') {
1595
            send_syslog(
1596
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1597
                $SETTINGS['syslog_host'],
1598
                $SETTINGS['syslog_port'],
1599
                'teampass'
1600
            );
1601
        } else {
1602
            send_syslog(
1603
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1604
                $SETTINGS['syslog_host'],
1605
                $SETTINGS['syslog_port'],
1606
                'teampass'
1607
            );
1608
        }
1609
    }
1610
}
1611
1612
/**
1613
 * Log events.
1614
 *
1615
 * @param array  $SETTINGS        Teampass settings
1616
 * @param int    $item_id         Item id
1617
 * @param string $item_label      Item label
1618
 * @param int    $id_user         User id
1619
 * @param string $action          Code for reason
1620
 * @param string $login           User login
1621
 * @param string $raison          Code for reason
1622
 * @param string $encryption_type Encryption on
1623
 */
1624
function logItems(
1625
    array $SETTINGS,
1626
    int $item_id,
1627
    string $item_label,
1628
    int $id_user,
1629
    string $action,
1630
    ?string $login = null,
1631
    ?string $raison = null,
1632
    ?string $encryption_type = null
1633
): void {
1634
    // include librairies & connect to DB
1635
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1636
    if (defined('DB_PASSWD_CLEAR') === false) {
1637
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1638
    }
1639
    DB::$host = DB_HOST;
1640
    DB::$user = DB_USER;
1641
    DB::$password = DB_PASSWD_CLEAR;
1642
    DB::$dbName = DB_NAME;
1643
    DB::$port = DB_PORT;
1644
    DB::$encoding = DB_ENCODING;
1645
    // Insert log in DB
1646
    DB::insert(
1647
        prefixTable('log_items'),
1648
        [
1649
            'id_item' => $item_id,
1650
            'date' => time(),
1651
            'id_user' => $id_user,
1652
            'action' => $action,
1653
            'raison' => $raison,
1654
            'raison_iv' => '',
1655
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1656
        ]
1657
    );
1658
    // Timestamp the last change
1659
    if ($action === 'at_creation' || $action === 'at_modifiation' || $action === 'at_delete' || $action === 'at_import') {
1660
        DB::update(
1661
            prefixTable('misc'),
1662
            [
1663
                'valeur' => time(),
1664
            ],
1665
            'type = %s AND intitule = %s',
1666
            'timestamp',
1667
            'last_item_change'
1668
        );
1669
    }
1670
1671
    // SYSLOG
1672
    if (isset($SETTINGS['syslog_enable']) === true && $SETTINGS['syslog_enable'] === '1') {
1673
        // Extract reason
1674
        $attribute = is_null($raison) === true ? '' : explode(' : ', $raison);
1675
        // Get item info if not known
1676
        if (empty($item_label) === true) {
1677
            $dataItem = DB::queryfirstrow(
1678
                'SELECT id, id_tree, label
1679
                FROM ' . prefixTable('items') . '
1680
                WHERE id = %i',
1681
                $item_id
1682
            );
1683
            $item_label = $dataItem['label'];
1684
        }
1685
1686
        send_syslog(
1687
            'action=' . str_replace('at_', '', $action) .
1688
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1689
                ' itemno=' . $item_id .
1690
                ' user=' . is_null($login) === true ? '' : addslashes((string) $login) .
1691
                ' itemname="' . addslashes($item_label) . '"',
1692
            $SETTINGS['syslog_host'],
1693
            $SETTINGS['syslog_port'],
1694
            'teampass'
1695
        );
1696
    }
1697
1698
    // send notification if enabled
1699
    notifyOnChange($item_id, $action, $SETTINGS);
1700
}
1701
1702
/**
1703
 * If enabled, then notify admin/manager.
1704
 *
1705
 * @param int    $item_id  Item id
1706
 * @param string $action   Action to do
1707
 * @param array  $SETTINGS Teampass settings
1708
 */
1709
function notifyOnChange(int $item_id, string $action, array $SETTINGS): void
1710
{
1711
    if (
1712
        isset($SETTINGS['enable_email_notification_on_item_shown']) === true
1713
        && (int) $SETTINGS['enable_email_notification_on_item_shown'] === 1
1714
        && $action === 'at_shown'
1715
    ) {
1716
        // Load superglobal
1717
        include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1718
        $superGlobal = new protect\SuperGlobal\SuperGlobal();
1719
        // Get superglobals
1720
        $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1721
        $globalsName = $superGlobal->get('name', 'SESSION');
1722
        $globalsNotifiedEmails = $superGlobal->get('listNotificationEmails', 'SESSION');
1723
        // Get info about item
1724
        $dataItem = DB::queryfirstrow(
1725
            'SELECT id, id_tree, label
1726
            FROM ' . prefixTable('items') . '
1727
            WHERE id = %i',
1728
            $item_id
1729
        );
1730
        $item_label = $dataItem['label'];
1731
        // send back infos
1732
        DB::insert(
1733
            prefixTable('emails'),
1734
            [
1735
                'timestamp' => time(),
1736
                'subject' => langHdl('email_on_open_notification_subject'),
1737
                'body' => str_replace(
1738
                    ['#tp_user#', '#tp_item#', '#tp_link#'],
1739
                    [
1740
                        addslashes($globalsName . ' ' . $globalsLastname),
1741
                        addslashes($item_label),
1742
                        $SETTINGS['cpassman_url'] . '/index.php?page=items&group=' . $dataItem['id_tree'] . '&id=' . $item_id,
1743
                    ],
1744
                    langHdl('email_on_open_notification_mail')
1745
                ),
1746
                'receivers' => $globalsNotifiedEmails,
1747
                'status' => '',
1748
            ]
1749
        );
1750
    }
1751
}
1752
1753
/**
1754
 * Prepare notification email to subscribers.
1755
 *
1756
 * @param int    $item_id  Item id
1757
 * @param string $label    Item label
1758
 * @param array  $changes  List of changes
1759
 * @param array  $SETTINGS Teampass settings
1760
 */
1761
function notifyChangesToSubscribers(int $item_id, string $label, array $changes, array $SETTINGS): void
1762
{
1763
    // Load superglobal
1764
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1765
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1766
    // Get superglobals
1767
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
1768
    $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1769
    $globalsName = $superGlobal->get('name', 'SESSION');
1770
    // send email to user that what to be notified
1771
    $notification = DB::queryOneColumn(
1772
        'email',
1773
        'SELECT *
1774
        FROM ' . prefixTable('notification') . ' AS n
1775
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1776
        WHERE n.item_id = %i AND n.user_id != %i',
1777
        $item_id,
1778
        $globalsUserId
1779
    );
1780
    if (DB::count() > 0) {
1781
        // Prepare path
1782
        $path = geItemReadablePath($item_id, '', $SETTINGS);
1783
        // Get list of changes
1784
        $htmlChanges = '<ul>';
1785
        foreach ($changes as $change) {
1786
            $htmlChanges .= '<li>' . $change . '</li>';
1787
        }
1788
        $htmlChanges .= '</ul>';
1789
        // send email
1790
        DB::insert(
1791
            prefixTable('emails'),
1792
            [
1793
                'timestamp' => time(),
1794
                'subject' => langHdl('email_subject_item_updated'),
1795
                'body' => str_replace(
1796
                    ['#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'],
1797
                    [$label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges],
1798
                    langHdl('email_body_item_updated')
1799
                ),
1800
                'receivers' => implode(',', $notification),
1801
                'status' => '',
1802
            ]
1803
        );
1804
    }
1805
}
1806
1807
/**
1808
 * Returns the Item + path.
1809
 *
1810
 * @param int    $id_tree  Node id
1811
 * @param string $label    Label
1812
 * @param array  $SETTINGS TP settings
1813
 */
1814
function geItemReadablePath(int $id_tree, string $label, array $SETTINGS): string
1815
{
1816
    // Class loader
1817
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1818
    //Load Tree
1819
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1820
    $tree->register();
1821
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1822
    $arbo = $tree->getPath($id_tree, true);
1823
    $path = '';
1824
    foreach ($arbo as $elem) {
1825
        if (empty($path) === true) {
1826
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
1827
        } else {
1828
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
1829
        }
1830
    }
1831
1832
    // Build text to show user
1833
    if (empty($label) === false) {
1834
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
1835
    }
1836
    return empty($path) === true ? '' : $path;
1837
}
1838
1839
/**
1840
 * Get the client ip address.
1841
 *
1842
 * @return string IP address
1843
 */
1844
function getClientIpServer(): string
1845
{
1846
    if (getenv('HTTP_CLIENT_IP')) {
1847
        $ipaddress = getenv('HTTP_CLIENT_IP');
1848
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
1849
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
1850
    } elseif (getenv('HTTP_X_FORWARDED')) {
1851
        $ipaddress = getenv('HTTP_X_FORWARDED');
1852
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
1853
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
1854
    } elseif (getenv('HTTP_FORWARDED')) {
1855
        $ipaddress = getenv('HTTP_FORWARDED');
1856
    } elseif (getenv('REMOTE_ADDR')) {
1857
        $ipaddress = getenv('REMOTE_ADDR');
1858
    } else {
1859
        $ipaddress = 'UNKNOWN';
1860
    }
1861
1862
    return $ipaddress;
1863
}
1864
1865
/**
1866
 * Escape all HTML, JavaScript, and CSS.
1867
 *
1868
 * @param string $input    The input string
1869
 * @param string $encoding Which character encoding are we using?
1870
 */
1871
function noHTML(string $input, string $encoding = 'UTF-8'): string
1872
{
1873
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
1874
}
1875
1876
/**
1877
 * Permits to handle the Teampass config file
1878
 * $action accepts "rebuild" and "update"
1879
 *
1880
 * @param string $action   Action to perform
1881
 * @param array  $SETTINGS Teampass settings
1882
 * @param string $field    Field to refresh
1883
 * @param string $value    Value to set
1884
 *
1885
 * @return string|bool
1886
 */
1887
function handleConfigFile($action, $SETTINGS, $field = null, $value = null)
1888
{
1889
    $tp_config_file = $SETTINGS['cpassman_dir'] . '/includes/config/tp.config.php';
1890
    // include librairies & connect to DB
1891
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1892
    if (defined('DB_PASSWD_CLEAR') === false) {
1893
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1894
    }
1895
    DB::$host = DB_HOST;
1896
    DB::$user = DB_USER;
1897
    DB::$password = DB_PASSWD_CLEAR;
1898
    DB::$dbName = DB_NAME;
1899
    DB::$port = DB_PORT;
1900
    DB::$encoding = DB_ENCODING;
1901
    if (file_exists($tp_config_file) === false || $action === 'rebuild') {
1902
        // perform a copy
1903
        if (file_exists($tp_config_file)) {
1904
            if (! copy($tp_config_file, $tp_config_file . '.' . date('Y_m_d_His', time()))) {
1905
                return "ERROR: Could not copy file '" . $tp_config_file . "'";
1906
            }
1907
        }
1908
1909
        // regenerate
1910
        $data = [];
1911
        $data[0] = "<?php\n";
1912
        $data[1] = "global \$SETTINGS;\n";
1913
        $data[2] = "\$SETTINGS = array (\n";
1914
        $rows = DB::query(
1915
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s',
1916
            'admin'
1917
        );
1918
        foreach ($rows as $record) {
1919
            array_push($data, "    '" . $record['intitule'] . "' => '" . $record['valeur'] . "',\n");
1920
        }
1921
        array_push($data, ");\n");
1922
        $data = array_unique($data);
1923
    // ---
1924
    } elseif ($action === 'update' && empty($field) === false) {
1925
        $data = file($tp_config_file);
1926
        $inc = 0;
1927
        $bFound = false;
1928
        foreach ($data as $line) {
1929
            if (stristr($line, ');')) {
1930
                break;
1931
            }
1932
1933
            if (stristr($line, "'" . $field . "' => '")) {
1934
                $data[$inc] = "    '" . $field . "' => '" . filter_var($value, FILTER_SANITIZE_STRING) . "',\n";
1935
                $bFound = true;
1936
                break;
1937
            }
1938
            ++$inc;
1939
        }
1940
        if ($bFound === false) {
1941
            $data[$inc] = "    '" . $field . "' => '" . filter_var($value, FILTER_SANITIZE_STRING) . "',\n);\n";
1942
        }
1943
    }
1944
1945
    // update file
1946
    file_put_contents($tp_config_file, implode('', $data ?? []));
1947
    return true;
1948
}
1949
1950
/**
1951
 * Permits to replace &#92; to permit correct display
1952
 *
1953
 * @param string $input Some text
1954
 */
1955
function handleBackslash(string $input): string
1956
{
1957
    return str_replace('&amp;#92;', '&#92;', $input);
1958
}
1959
1960
/*
1961
** Permits to load settings
1962
*/
1963
function loadSettings(): void
1964
{
1965
    global $SETTINGS;
1966
    /* LOAD CPASSMAN SETTINGS */
1967
    if (! isset($SETTINGS['loaded']) || $SETTINGS['loaded'] !== 1) {
1968
        $SETTINGS = [];
1969
        $SETTINGS['duplicate_folder'] = 0;
1970
        //by default, this is set to 0;
1971
        $SETTINGS['duplicate_item'] = 0;
1972
        //by default, this is set to 0;
1973
        $SETTINGS['number_of_used_pw'] = 5;
1974
        //by default, this value is set to 5;
1975
        $settings = [];
1976
        $rows = DB::query(
1977
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
1978
            [
1979
                'type' => 'admin',
1980
                'type2' => 'settings',
1981
            ]
1982
        );
1983
        foreach ($rows as $record) {
1984
            if ($record['type'] === 'admin') {
1985
                $SETTINGS[$record['intitule']] = $record['valeur'];
1986
            } else {
1987
                $settings[$record['intitule']] = $record['valeur'];
1988
            }
1989
        }
1990
        $SETTINGS['loaded'] = 1;
1991
        $SETTINGS['default_session_expiration_time'] = 5;
1992
    }
1993
}
1994
1995
/*
1996
** check if folder has custom fields.
1997
** Ensure that target one also has same custom fields
1998
*/
1999
function checkCFconsistency($source_id, $target_id)
2000
{
2001
    $source_cf = [];
2002
    $rows = DB::QUERY(
2003
        'SELECT id_category
2004
            FROM ' . prefixTable('categories_folders') . '
2005
            WHERE id_folder = %i',
2006
        $source_id
2007
    );
2008
    foreach ($rows as $record) {
2009
        array_push($source_cf, $record['id_category']);
2010
    }
2011
2012
    $target_cf = [];
2013
    $rows = DB::QUERY(
2014
        'SELECT id_category
2015
            FROM ' . prefixTable('categories_folders') . '
2016
            WHERE id_folder = %i',
2017
        $target_id
2018
    );
2019
    foreach ($rows as $record) {
2020
        array_push($target_cf, $record['id_category']);
2021
    }
2022
2023
    $cf_diff = array_diff($source_cf, $target_cf);
2024
    if (count($cf_diff) > 0) {
2025
        return false;
2026
    }
2027
2028
    return true;
2029
}
2030
2031
/**
2032
 * Will encrypte/decrypt a fil eusing Defuse.
2033
 *
2034
 * @param string $type        can be either encrypt or decrypt
2035
 * @param string $source_file path to source file
2036
 * @param string $target_file path to target file
2037
 * @param array  $SETTINGS    Settings
2038
 * @param string $password    A password
2039
 *
2040
 * @return string|bool
2041
 */
2042
function prepareFileWithDefuse(
2043
    $type,
2044
    $source_file,
2045
    $target_file,
2046
    $SETTINGS,
2047
    $password = null
2048
) {
2049
    // Load AntiXSS
2050
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/AntiXSS.php';
2051
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/ASCII.php';
2052
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/UTF8.php';
2053
    $antiXss = new voku\helper\AntiXSS();
2054
    // Protect against bad inputs
2055
    if (is_array($source_file) === true || is_array($target_file) === true) {
2056
        return 'error_cannot_be_array';
2057
    }
2058
2059
    // Sanitize
2060
    $source_file = $antiXss->xss_clean($source_file);
2061
    $target_file = $antiXss->xss_clean($target_file);
2062
    if (empty($password) === true || is_null($password) === true) {
2063
        // get KEY to define password
2064
        $ascii_key = file_get_contents(SECUREPATH . '/teampass-seckey.txt');
2065
        $password = \Defuse\Crypto\Key::loadFromAsciiSafeString($ascii_key);
2066
    }
2067
2068
    $err = '';
2069
    if ($type === 'decrypt') {
2070
        // Decrypt file
2071
        $err = defuseFileDecrypt(
2072
            $source_file,
2073
            $target_file,
2074
            $SETTINGS, /** @scrutinizer ignore-type */
2075
            $password
2076
        );
2077
    } elseif ($type === 'encrypt') {
2078
        // Encrypt file
2079
        $err = defuseFileEncrypt(
2080
            $source_file,
2081
            $target_file,
2082
            $SETTINGS, /** @scrutinizer ignore-type */
2083
            $password
2084
        );
2085
    }
2086
2087
    // return error
2088
    return empty($err) === false ? $err : '';
2089
}
2090
2091
/**
2092
 * Encrypt a file with Defuse.
2093
 *
2094
 * @param string $source_file path to source file
2095
 * @param string $target_file path to target file
2096
 * @param array  $SETTINGS    Settings
2097
 * @param string $password    A password
2098
 *
2099
 * @return string|bool
2100
 */
2101
function defuseFileEncrypt(
2102
    $source_file,
2103
    $target_file,
2104
    $SETTINGS,
2105
    $password = null
2106
) {
2107
    // load PhpEncryption library
2108
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2109
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2110
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2111
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2112
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2113
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2114
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2115
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2116
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2117
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2118
    try {
2119
        \Defuse\Crypto\File::encryptFileWithPassword(
2120
            $source_file,
2121
            $target_file,
2122
            $password
2123
        );
2124
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2125
        $err = 'wrong_key';
2126
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2127
        $err = $ex;
2128
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2129
        $err = $ex;
2130
    }
2131
2132
    // return error
2133
    return empty($err) === false ? $err : true;
2134
}
2135
2136
/**
2137
 * Decrypt a file with Defuse.
2138
 *
2139
 * @param string $source_file path to source file
2140
 * @param string $target_file path to target file
2141
 * @param array  $SETTINGS    Settings
2142
 * @param string $password    A password
2143
 *
2144
 * @return string|bool
2145
 */
2146
function defuseFileDecrypt(
2147
    $source_file,
2148
    $target_file,
2149
    $SETTINGS,
2150
    $password = null
2151
) {
2152
    // load PhpEncryption library
2153
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2154
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2155
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2156
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2157
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2158
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2159
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2160
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2161
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2162
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2163
    try {
2164
        \Defuse\Crypto\File::decryptFileWithPassword(
2165
            $source_file,
2166
            $target_file,
2167
            $password
2168
        );
2169
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2170
        $err = 'wrong_key';
2171
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2172
        $err = $ex;
2173
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2174
        $err = $ex;
2175
    }
2176
2177
    // return error
2178
    return empty($err) === false ? $err : true;
2179
}
2180
2181
/*
2182
* NOT TO BE USED
2183
*/
2184
/**
2185
 * Undocumented function.
2186
 *
2187
 * @param string $text Text to debug
2188
 */
2189
function debugTeampass(string $text): void
2190
{
2191
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
2192
    if ($debugFile !== false) {
2193
        fputs($debugFile, $text);
2194
        fclose($debugFile);
2195
    }
2196
}
2197
2198
/**
2199
 * DELETE the file with expected command depending on server type.
2200
 *
2201
 * @param string $file     Path to file
2202
 * @param array  $SETTINGS Teampass settings
2203
 */
2204
function fileDelete(string $file, array $SETTINGS): void
2205
{
2206
    // Load AntiXSS
2207
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/ASCII.php';
2208
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/UTF8.php';
2209
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/AntiXSS.php';
2210
    $antiXss = new voku\helper\AntiXSS();
2211
    $file = $antiXss->xss_clean($file);
2212
    if (is_file($file)) {
2213
        unlink($file);
2214
    }
2215
}
2216
2217
/**
2218
 * Permits to extract the file extension.
2219
 *
2220
 * @param string $file File name
2221
 */
2222
function getFileExtension(string $file): string
2223
{
2224
    if (strpos($file, '.') === false) {
2225
        return $file;
2226
    }
2227
2228
    return substr($file, strrpos($file, '.') + 1);
2229
}
2230
2231
/**
2232
 * Chmods files and folders with different permissions.
2233
 *
2234
 * This is an all-PHP alternative to using: \n
2235
 * <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2236
 * <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2237
 *
2238
 * @author Jeppe Toustrup (tenzer at tenzer dot dk)
2239
  *
2240
 * @param string $path      An either relative or absolute path to a file or directory which should be processed.
2241
 * @param int    $filePerm The permissions any found files should get.
2242
 * @param int    $dirPerm  The permissions any found folder should get.
2243
 *
2244
 * @return bool Returns TRUE if the path if found and FALSE if not.
2245
 *
2246
 * @warning The permission levels has to be entered in octal format, which
2247
 * normally means adding a zero ("0") in front of the permission level. \n
2248
 * More info at: http://php.net/chmod.
2249
*/
2250
2251
function recursiveChmod($path, $filePerm = 0644, $dirPerm = 0755) {
2252
    // Check if the path exists
2253
    if (! file_exists($path)) {
2254
        return false;
2255
    }
2256
2257
    // See whether this is a file
2258
    if (is_file($path)) {
2259
        // Chmod the file with our given filepermissions
2260
        chmod($path, $filePerm);
2261
    // If this is a directory...
2262
    } elseif (is_dir($path)) {
2263
        // Then get an array of the contents
2264
        $foldersAndFiles = scandir($path);
2265
        // Remove "." and ".." from the list
2266
        $entries = array_slice($foldersAndFiles, 2);
2267
        // Parse every result...
2268
        foreach ($entries as $entry) {
2269
            // And call this function again recursively, with the same permissions
2270
            recursiveChmod($path.'/'.$entry, $filePerm, $dirPerm);
2271
        }
2272
2273
        // When we are done with the contents of the directory, we chmod the directory itself
2274
        chmod($path, $dirPerm);
2275
    }
2276
2277
    // Everything seemed to work out well, return true
2278
    return true;
2279
}
2280
2281
/**
2282
 * Check if user can access to this item.
2283
 *
2284
 * @param int $item_id ID of item
2285
 */
2286
function accessToItemIsGranted(int $item_id, $SETTINGS)
2287
{
2288
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2289
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2290
    // Prepare superGlobal variables
2291
    $session_groupes_visibles = $superGlobal->get('groupes_visibles', 'SESSION');
2292
    $session_list_restricted_folders_for_items = $superGlobal->get('list_restricted_folders_for_items', 'SESSION');
2293
    // Load item data
2294
    $data = DB::queryFirstRow(
2295
        'SELECT id_tree
2296
        FROM ' . prefixTable('items') . '
2297
        WHERE id = %i',
2298
        $item_id
2299
    );
2300
    // Check if user can access this folder
2301
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2302
        // Now check if this folder is restricted to user
2303
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2304
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2305
        ) {
2306
            return 'ERR_FOLDER_NOT_ALLOWED';
2307
        }
2308
    }
2309
2310
    return true;
2311
}
2312
2313
/**
2314
 * Creates a unique key.
2315
 *
2316
 * @param int $lenght Key lenght
2317
 */
2318
function uniqidReal(int $lenght = 13): string
2319
{
2320
    if (function_exists('random_bytes')) {
2321
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2322
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2323
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2324
    } else {
2325
        throw new Exception('no cryptographically secure random function available');
2326
    }
2327
2328
    return substr(bin2hex($bytes), 0, $lenght);
2329
}
2330
2331
/**
2332
 * Obfuscate an email.
2333
 *
2334
 * @param string $email Email address
2335
 */
2336
function obfuscateEmail(string $email): string
2337
{
2338
    $prop = 2;
2339
    $start = '';
2340
    $end = '';
2341
    $domain = substr(strrchr($email, '@'), 1);
2342
    $mailname = str_replace($domain, '', $email);
2343
    $name_l = strlen($mailname);
2344
    $domain_l = strlen($domain);
2345
    for ($i = 0; $i <= $name_l / $prop - 1; ++$i) {
2346
        $start .= 'x';
2347
    }
2348
2349
    for ($i = 0; $i <= $domain_l / $prop - 1; ++$i) {
2350
        $end .= 'x';
2351
    }
2352
2353
    return (string) substr_replace($mailname, $start, 2, $name_l / $prop)
2354
        . (string) substr_replace($domain, $end, 2, $domain_l / $prop);
2355
}
2356
2357
/**
2358
 * Perform a Query.
2359
 *
2360
 * @param array  $SETTINGS Teamapss settings
2361
 * @param string $fields   Fields to use
2362
 * @param string $table    Table to use
2363
 *
2364
 * @return array
2365
 */
2366
function performDBQuery(array $SETTINGS, string $fields, string $table): array
2367
{
2368
    // include librairies & connect to DB
2369
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2370
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2371
    if (defined('DB_PASSWD_CLEAR') === false) {
2372
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2373
    }
2374
    DB::$host = DB_HOST;
2375
    DB::$user = DB_USER;
2376
    DB::$password = DB_PASSWD_CLEAR;
2377
    DB::$dbName = DB_NAME;
2378
    DB::$port = DB_PORT;
2379
    DB::$encoding = DB_ENCODING;
2380
    // Insert log in DB
2381
    return DB::query(
2382
        'SELECT ' . $fields . '
2383
        FROM ' . prefixTable($table)
2384
    );
2385
}
2386
2387
/**
2388
 * Undocumented function.
2389
 *
2390
 * @param int $bytes Size of file
2391
 */
2392
function formatSizeUnits(int $bytes): string
2393
{
2394
    if ($bytes >= 1073741824) {
2395
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2396
    } elseif ($bytes >= 1048576) {
2397
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2398
    } elseif ($bytes >= 1024) {
2399
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2400
    } elseif ($bytes > 1) {
2401
        $bytes .= ' bytes';
2402
    } elseif ($bytes === 1) {
2403
        $bytes .= ' byte';
2404
    } else {
2405
        $bytes = '0 bytes';
2406
    }
2407
2408
    return $bytes;
2409
}
2410
2411
/**
2412
 * Generate user pair of keys.
2413
 *
2414
 * @param string $userPwd User password
2415
 *
2416
 * @return array
2417
 */
2418
function generateUserKeys(string $userPwd): array
2419
{
2420
    // include library
2421
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2422
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2423
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2424
    // Load classes
2425
    $rsa = new Crypt_RSA();
2426
    $cipher = new Crypt_AES();
2427
    // Create the private and public key
2428
    $res = $rsa->createKey(4096);
2429
    // Encrypt the privatekey
2430
    $cipher->setPassword($userPwd);
2431
    $privatekey = $cipher->encrypt($res['privatekey']);
2432
    return [
2433
        'private_key' => base64_encode($privatekey),
2434
        'public_key' => base64_encode($res['publickey']),
2435
        'private_key_clear' => base64_encode($res['privatekey']),
2436
    ];
2437
}
2438
2439
/**
2440
 * Permits to decrypt the user's privatekey.
2441
 *
2442
 * @param string $userPwd        User password
2443
 * @param string $userPrivateKey User private key
2444
 */
2445
function decryptPrivateKey(string $userPwd, string $userPrivateKey): string
2446
{
2447
    if (empty($userPwd) === false) {
2448
        include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2449
        // Load classes
2450
        $cipher = new Crypt_AES();
2451
        // Encrypt the privatekey
2452
        $cipher->setPassword($userPwd);
2453
        return base64_encode($cipher->decrypt(base64_decode($userPrivateKey)));
2454
    }
2455
    return '';
2456
}
2457
2458
/**
2459
 * Permits to encrypt the user's privatekey.
2460
 *
2461
 * @param string $userPwd        User password
2462
 * @param string $userPrivateKey User private key
2463
 */
2464
function encryptPrivateKey(string $userPwd, string $userPrivateKey): string
2465
{
2466
    if (empty($userPwd) === false) {
2467
        include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2468
        // Load classes
2469
        $cipher = new Crypt_AES();
2470
        // Encrypt the privatekey
2471
        $cipher->setPassword($userPwd);
2472
        return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2473
    }
2474
    return '';
2475
}
2476
2477
/**
2478
 * Encrypts a string using AES.
2479
 *
2480
 * @param string $data String to encrypt
2481
 *
2482
 * @return array
2483
 */
2484
function doDataEncryption(string $data): array
2485
{
2486
    // Includes
2487
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2488
    // Load classes
2489
    $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2490
    // Generate an object key
2491
    $objectKey = uniqidReal(32);
2492
    // Set it as password
2493
    $cipher->setPassword($objectKey);
2494
    return [
2495
        'encrypted' => base64_encode($cipher->encrypt($data)),
2496
        'objectKey' => base64_encode($objectKey),
2497
    ];
2498
}
2499
2500
/**
2501
 * Decrypts a string using AES.
2502
 *
2503
 * @param string $data Encrypted data
2504
 * @param string $key  Key to uncrypt
2505
 */
2506
function doDataDecryption(string $data, string $key): string
2507
{
2508
    // Includes
2509
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2510
    // Load classes
2511
    $cipher = new Crypt_AES();
2512
    // Set the object key
2513
    $cipher->setPassword(base64_decode($key));
2514
    return base64_encode($cipher->decrypt(base64_decode($data)));
2515
}
2516
2517
/**
2518
 * Encrypts using RSA a string using a public key.
2519
 *
2520
 * @param string $key       Key to be encrypted
2521
 * @param string $publicKey User public key
2522
 */
2523
function encryptUserObjectKey(string $key, string $publicKey): string
2524
{
2525
    // Includes
2526
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2527
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2528
    // Load classes
2529
    $rsa = new Crypt_RSA();
2530
    $rsa->loadKey(base64_decode($publicKey));
2531
    // Encrypt
2532
    return base64_encode($rsa->encrypt(base64_decode($key)));
2533
}
2534
2535
/**
2536
 * Decrypts using RSA an encrypted string using a private key.
2537
 *
2538
 * @param string $key        Encrypted key
2539
 * @param string $privateKey User private key
2540
 */
2541
function decryptUserObjectKey(string $key, string $privateKey): string
2542
{
2543
    // Includes
2544
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2545
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2546
    // Load classes
2547
    $rsa = new Crypt_RSA();
2548
    $rsa->loadKey(base64_decode($privateKey));
2549
    // Decrypt
2550
    try {
2551
        $ret = base64_encode($rsa->decrypt(base64_decode($key)));
2552
    } catch (Exception $e) {
2553
        return $e;
2554
    }
2555
2556
    return $ret;
2557
}
2558
2559
/**
2560
 * Encrypts a file.
2561
 *
2562
 * @param string $fileInName File name
2563
 * @param string $fileInPath Path to file
2564
 *
2565
 * @return array
2566
 */
2567
function encryptFile(string $fileInName, string $fileInPath): array
2568
{
2569
    if (defined('FILE_BUFFER_SIZE') === false) {
2570
        define('FILE_BUFFER_SIZE', 128 * 1024);
2571
    }
2572
2573
    // Includes
2574
    include_once '../includes/config/include.php';
2575
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2576
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2577
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2578
    // Load classes
2579
    $cipher = new Crypt_AES();
2580
    // Generate an object key
2581
    $objectKey = uniqidReal(32);
2582
    // Set it as password
2583
    $cipher->setPassword($objectKey);
2584
    // Prevent against out of memory
2585
    $cipher->enableContinuousBuffer();
2586
    //$cipher->disablePadding();
2587
2588
    // Encrypt the file content
2589
    $plaintext = file_get_contents(
2590
        filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL)
2591
    );
2592
    $ciphertext = $cipher->encrypt($plaintext);
2593
    // Save new file
2594
    $hash = md5($plaintext);
2595
    $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2596
    file_put_contents($fileOut, $ciphertext);
2597
    unlink($fileInPath . '/' . $fileInName);
2598
    return [
2599
        'fileHash' => base64_encode($hash),
2600
        'objectKey' => base64_encode($objectKey),
2601
    ];
2602
}
2603
2604
/**
2605
 * Decrypt a file.
2606
 *
2607
 * @param string $fileName File name
2608
 * @param string $filePath Path to file
2609
 * @param string $key      Key to use
2610
 */
2611
function decryptFile(string $fileName, string $filePath, string $key): string
2612
{
2613
    if (! defined('FILE_BUFFER_SIZE')) {
2614
        define('FILE_BUFFER_SIZE', 128 * 1024);
2615
    }
2616
2617
    // Includes
2618
    include_once '../includes/config/include.php';
2619
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2620
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2621
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2622
    // Get file name
2623
    $fileName = base64_decode($fileName);
2624
    // Load classes
2625
    $cipher = new Crypt_AES();
2626
    // Set the object key
2627
    $cipher->setPassword(base64_decode($key));
2628
    // Prevent against out of memory
2629
    $cipher->enableContinuousBuffer();
2630
    $cipher->disablePadding();
2631
    // Get file content
2632
    $ciphertext = file_get_contents($filePath . '/' . TP_FILE_PREFIX . $fileName);
2633
    // Decrypt file content and return
2634
    return base64_encode($cipher->decrypt($ciphertext));
2635
}
2636
2637
/**
2638
 * Generate a simple password
2639
 *
2640
 * @param int $length Length of string
2641
 * @param bool $symbolsincluded Allow symbols
2642
 */
2643
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2644
{
2645
    // Generate new user password
2646
    $small_letters = range('a', 'z');
2647
    $big_letters = range('A', 'Z');
2648
    $digits = range(0, 9);
2649
    $symbols = $symbolsincluded === true ?
2650
        ['#', '_', '-', '@', '$', '+', '&'] : [];
2651
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2652
    $count = count($res);
2653
    // first variant
2654
2655
    $random_string = '';
2656
    for ($i = 0; $i < $length; ++$i) {
2657
        $random_string .= $res[random_int(0, $count - 1)];
2658
    }
2659
2660
    return $random_string;
2661
}
2662
2663
/**
2664
 * Permit to store the sharekey of an object for users.
2665
 *
2666
 * @param string $object_name             Type for table selection
2667
 * @param int    $post_folder_is_personal Personal
2668
 * @param int    $post_folder_id          Folder
2669
 * @param int    $post_object_id          Object
2670
 * @param string $objectKey               Object key
2671
 * @param array  $SETTINGS                Teampass settings
2672
 */
2673
function storeUsersShareKey(
2674
    string $object_name,
2675
    int $post_folder_is_personal,
2676
    int $post_folder_id,
2677
    int $post_object_id,
2678
    string $objectKey,
2679
    array $SETTINGS
2680
): void {
2681
    // include librairies & connect to DB
2682
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2683
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2684
    if (defined('DB_PASSWD_CLEAR') === false) {
2685
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2686
    }
2687
    DB::$host = DB_HOST;
2688
    DB::$user = DB_USER;
2689
    DB::$password = DB_PASSWD_CLEAR;
2690
    DB::$dbName = DB_NAME;
2691
    DB::$port = DB_PORT;
2692
    DB::$encoding = DB_ENCODING;
2693
    // Delete existing entries for this object
2694
    DB::delete(
2695
        $object_name,
2696
        'object_id = %i',
2697
        $post_object_id
2698
    );
2699
    // Superglobals
2700
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2701
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2702
    // Prepare superGlobal variables
2703
    $sessionPpersonaFolders = $superGlobal->get('personal_folders', 'SESSION');
2704
    $sessionUserId = $superGlobal->get('user_id', 'SESSION');
2705
    $sessionUserPublicKey = $superGlobal->get('public_key', 'SESSION', 'user');
2706
    if (
2707
        (int) $post_folder_is_personal === 1
2708
        && in_array($post_folder_id, $sessionPpersonaFolders) === true
2709
    ) {
2710
        // If this is a personal object
2711
        // Only create the sharekey for user
2712
        DB::insert(
2713
            $object_name,
2714
            [
2715
                'object_id' => (int) $post_object_id,
2716
                'user_id' => (int) $sessionUserId,
2717
                'share_key' => encryptUserObjectKey($objectKey, $sessionUserPublicKey),
2718
            ]
2719
        );
2720
    } else {
2721
        // This is a public object
2722
        // Create sharekey for each user
2723
        $users = DB::query(
2724
            'SELECT id, public_key
2725
            FROM ' . prefixTable('users') . '
2726
            WHERE id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '")
2727
            AND public_key != ""'
2728
        );
2729
        foreach ($users as $user) {
2730
            // Insert in DB the new object key for this item by user
2731
            DB::insert(
2732
                $object_name,
2733
                [
2734
                    'object_id' => $post_object_id,
2735
                    'user_id' => (int) $user['id'],
2736
                    'share_key' => encryptUserObjectKey(
2737
                        $objectKey,
2738
                        $user['public_key']
2739
                    ),
2740
                ]
2741
            );
2742
        }
2743
    }
2744
}
2745
2746
/**
2747
 * Is this string base64 encoded?
2748
 *
2749
 * @param string $str Encoded string?
2750
 */
2751
function isBase64(string $str): bool
2752
{
2753
    $str = (string) trim($str);
2754
    if (! isset($str[0])) {
2755
        return false;
2756
    }
2757
2758
    $base64String = (string) base64_decode($str, true);
2759
    if ($base64String && base64_encode($base64String) === $str) {
2760
        return true;
2761
    }
2762
2763
    return false;
2764
}
2765
2766
/**
2767
 * Undocumented function
2768
 *
2769
 * @param string $field Parameter
2770
 *
2771
 * @return array|bool|resource|string
2772
 */
2773
function filterString(string $field)
2774
{
2775
    // Sanitize string
2776
    $field = filter_var(trim($field), FILTER_SANITIZE_STRING);
2777
    if (empty($field) === false) {
2778
        // Load AntiXSS
2779
        include_once '../includes/libraries/voku/helper/AntiXSS.php';
2780
        $antiXss = new voku\helper\AntiXSS();
2781
        // Return
2782
        return $antiXss->xss_clean($field);
2783
    }
2784
2785
    return false;
2786
}
2787
2788
/**
2789
 * CHeck if provided credentials are allowed on server
2790
 *
2791
 * @param string $login    User Login
2792
 * @param string $password User Pwd
2793
 * @param array  $SETTINGS Teampass settings
2794
 *
2795
 * @return bool
2796
 */
2797
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
2798
{
2799
    // Build ldap configuration array
2800
    $config = [
2801
        // Mandatory Configuration Options
2802
        'hosts' => [$SETTINGS['ldap_hosts']],
2803
        'base_dn' => $SETTINGS['ldap_bdn'],
2804
        'username' => $SETTINGS['ldap_username'],
2805
        'password' => $SETTINGS['ldap_password'],
2806
2807
        // Optional Configuration Options
2808
        'port' => $SETTINGS['ldap_port'],
2809
        'use_ssl' => $SETTINGS['ldap_ssl'] === 1 ? true : false,
2810
        'use_tls' => $SETTINGS['ldap_tls'] === 1 ? true : false,
2811
        'version' => 3,
2812
        'timeout' => 5,
2813
        'follow_referrals' => false,
2814
2815
        // Custom LDAP Options
2816
        'options' => [
2817
            // See: http://php.net/ldap_set_option
2818
            LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_HARD,
2819
        ],
2820
    ];
2821
    // Load expected libraries
2822
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Traits/Macroable.php';
2823
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Arr.php';
2824
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/DetectsErrors.php';
2825
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Connection.php';
2826
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/LdapInterface.php';
2827
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/LdapBase.php';
2828
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Ldap.php';
2829
    $ad = new SplClassLoader('LdapRecord', '../includes/libraries');
2830
    $ad->register();
2831
    $connection = new Connection($config);
2832
    // COnnect to LDAP
2833
    try {
2834
        $connection->connect();
2835
    } catch (\LdapRecord\Auth\BindException $e) {
2836
        $error = $e->getDetailedError();
2837
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
2838
        return false;
2839
    }
2840
2841
    // Authenticate user
2842
    try {
2843
        $connection->auth()->attempt($SETTINGS['ldap_user_attribute'].'='.$login.','.$SETTINGS['ldap_bdn'], $password, $stayAuthenticated = true);
2844
    } catch (\LdapRecord\Auth\BindException $e) {
2845
        $error = $e->getDetailedError();
2846
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
2847
        return false;
2848
    }
2849
2850
    return true;
2851
}
2852
2853
/**
2854
 * Removes from DB all sharekeys of this user
2855
 *
2856
 * @param int $userId User's id
2857
 * @param array   $SETTINGS Teampass settings
2858
 *
2859
 * @return bool
2860
 */
2861
function deleteUserObjetsKeys(int $userId, array $SETTINGS): bool
2862
{
2863
    // include librairies & connect to DB
2864
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2865
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2866
    if (defined('DB_PASSWD_CLEAR') === false) {
2867
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2868
    }
2869
    DB::$host = DB_HOST;
2870
    DB::$user = DB_USER;
2871
    DB::$password = DB_PASSWD_CLEAR;
2872
    DB::$dbName = DB_NAME;
2873
    DB::$port = DB_PORT;
2874
    DB::$encoding = DB_ENCODING;
2875
    // Remove all item sharekeys items
2876
    DB::delete(
2877
        prefixTable('sharekeys_items'),
2878
        'user_id = %i',
2879
        $userId
2880
    );
2881
    // Remove all item sharekeys files
2882
    DB::delete(
2883
        prefixTable('sharekeys_files'),
2884
        'user_id = %i',
2885
        $userId
2886
    );
2887
    // Remove all item sharekeys fields
2888
    DB::delete(
2889
        prefixTable('sharekeys_fields'),
2890
        'user_id = %i',
2891
        $userId
2892
    );
2893
    // Remove all item sharekeys logs
2894
    DB::delete(
2895
        prefixTable('sharekeys_logs'),
2896
        'user_id = %i',
2897
        $userId
2898
    );
2899
    // Remove all item sharekeys suggestions
2900
    DB::delete(
2901
        prefixTable('sharekeys_suggestions'),
2902
        'user_id = %i',
2903
        $userId
2904
    );
2905
    return false;
2906
}
2907
2908
/**
2909
 * Manage list of timezones   $SETTINGS Teampass settings
2910
 *
2911
 * @return array
2912
 */
2913
function timezone_list()
2914
{
2915
    static $timezones = null;
2916
    if ($timezones === null) {
2917
        $timezones = [];
2918
        $offsets = [];
2919
        $now = new DateTime('now', new DateTimeZone('UTC'));
2920
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
2921
            $now->setTimezone(new DateTimeZone($timezone));
2922
            $offsets[] = $offset = $now->getOffset();
2923
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
2924
        }
2925
2926
        array_multisort($offsets, $timezones);
2927
    }
2928
2929
    return $timezones;
2930
}
2931
2932
/**
2933
 * Provide timezone offset
2934
 *
2935
 * @param int $offset Timezone offset
2936
 *
2937
 * @return string
2938
 */
2939
function format_GMT_offset($offset)
2940
{
2941
    $hours = intval($offset / 3600);
2942
    $minutes = abs(intval($offset % 3600 / 60));
2943
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
2944
}
2945
2946
/**
2947
 * Provides timezone name
2948
 *
2949
 * @param string $name Timezone name
2950
 *
2951
 * @return string
2952
 */
2953
function format_timezone_name($name)
2954
{
2955
    $name = str_replace('/', ', ', $name);
2956
    $name = str_replace('_', ' ', $name);
2957
2958
    return str_replace('St ', 'St. ', $name);
2959
}
2960
2961
/**
2962
 * Provides info about if user should use MFA
2963
 *
2964
 * @param string $userRolesIds  User roles ids
2965
 * @param string $mfaRoles      Roles for which MFA is requested
2966
 *
2967
 * @return bool
2968
 */
2969
function mfa_auth_requested(string $userRolesIds, string $mfaRoles): bool
2970
{
2971
    if (empty($mfaRoles) === true) {
2972
        return false;
2973
    }
2974
2975
    $mfaRoles = array_values(json_decode($mfaRoles, true));
2976
    $userRolesIds = array_filter(explode(';', $userRolesIds));
2977
    if (count($mfaRoles) === 0 || count($mfaRoles) === 0) {
2978
        return true;
2979
    }
2980
2981
    if (count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
2982
        return true;
2983
    }
2984
    return false;
2985
}
2986