Passed
Push — teampass_3.0 ( 156bc8...fb33ce )
by Nils
05:48
created

prepareExchangedData()   B

Complexity

Conditions 10
Paths 22

Size

Total Lines 54
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 8
Bugs 0 Features 0
Metric Value
cc 10
eloc 35
c 8
b 0
f 0
nc 22
nop 3
dl 0
loc 54
rs 7.6666

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