Passed
Pull Request — teampass_3.0 (#2965)
by
unknown
05:49
created

ldapCheckUserPassword()   B

Complexity

Conditions 6
Paths 24

Size

Total Lines 58
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

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

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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