Passed
Push — wip_sessions ( ce0b61...221b0c )
by Nils
04:59
created

prepareExchangedData()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 5
eloc 14
c 4
b 0
f 0
nc 3
nop 3
dl 0
loc 26
rs 9.4888
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
 * @file      main.functions.php
15
 * ---
16
 *
17
 * @author    Nils Laumaillé ([email protected])
18
 *
19
 * @copyright 2009-2023 Teampass.net
20
 *
21
 * @license   https://spdx.org/licenses/GPL-3.0-only.html#licenseText GPL-3.0
22
 * ---
23
 *
24
 * @see       https://www.teampass.net
25
 */
26
27
use LdapRecord\Connection;
28
use ForceUTF8\Encoding;
29
use Elegant\Sanitizer\Sanitizer;
30
use voku\helper\AntiXSS;
31
use Hackzilla\PasswordGenerator\Generator\ComputerPasswordGenerator;
32
use Hackzilla\PasswordGenerator\RandomGenerator\Php7RandomGenerator;
33
use TeampassClasses\SessionManager\SessionManager;
34
use Symfony\Component\HttpFoundation\Request;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Request. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
35
use TeampassClasses\Language\Language;
36
use TeampassClasses\NestedTree\NestedTree;
37
use Defuse\Crypto\Key;
38
use Defuse\Crypto\Crypto;
39
use Defuse\Crypto\KeyProtectedByPassword;
40
use Defuse\Crypto\File as CryptoFile;
41
use Defuse\Crypto\Exception as CryptoException;
42
use Elegant\Sanitizer\Filters\Uppercase;
43
use PHPMailer\PHPMailer\PHPMailer;
44
use PasswordLib\PasswordLib;
45
use Symfony\Component\Process\Exception\ProcessFailedException;
46
use Symfony\Component\Process\Process;
47
use Symfony\Component\Process\PhpExecutableFinder;
48
use TeampassClasses\Encryption\Encryption;
49
50
51
// Load config if $SETTINGS not defined
52
if (isset($SETTINGS['cpassman_dir']) === false || empty($SETTINGS['cpassman_dir']) === true) {
53
    include_once __DIR__ . '/../includes/config/tp.config.php';
54
}
55
56
header('Content-type: text/html; charset=utf-8');
57
header('Cache-Control: no-cache, must-revalidate');
58
59
loadClasses('DB');
60
$session = SessionManager::getSession();
61
62
63
/**
64
 * genHash().
65
 *
66
 * Generate a hash for user login
67
 *
68
 * @param string $password What password
69
 * @param string $cost     What cost
70
 *
71
 * @return string|void
72
 */
73
function bCrypt(
74
    string $password,
75
    string $cost
76
): ?string
77
{
78
    $salt = sprintf('$2y$%02d$', $cost);
79
    if (function_exists('openssl_random_pseudo_bytes')) {
80
        $salt .= bin2hex(openssl_random_pseudo_bytes(11));
81
    } else {
82
        $chars = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
83
        for ($i = 0; $i < 22; ++$i) {
84
            $salt .= $chars[mt_rand(0, 63)];
85
        }
86
    }
87
88
    return crypt($password, $salt);
89
}
90
91
/**
92
 * Checks if a string is hex encoded
93
 *
94
 * @param string $str
95
 * @return boolean
96
 */
97
function isHex(string $str): bool
98
{
99
    if (str_starts_with(strtolower($str), '0x')) {
100
        $str = substr($str, 2);
101
    }
102
103
    return ctype_xdigit($str);
104
}
105
106
/**
107
 * Defuse cryption function.
108
 *
109
 * @param string $message   what to de/crypt
110
 * @param string $ascii_key key to use
111
 * @param string $type      operation to perform
112
 * @param array  $SETTINGS  Teampass settings
113
 *
114
 * @return array
115
 */
116
function cryption(string $message, string $ascii_key, string $type, ?array $SETTINGS = []): array
117
{
118
    $ascii_key = empty($ascii_key) === true ? file_get_contents(SECUREPATH.'/'.SECUREFILE) : $ascii_key;
119
    $err = false;
120
    
121
    // convert KEY
122
    $key = Key::loadFromAsciiSafeString($ascii_key);
123
    try {
124
        if ($type === 'encrypt') {
125
            $text = Crypto::encrypt($message, $key);
126
        } elseif ($type === 'decrypt') {
127
            $text = Crypto::decrypt($message, $key);
128
        }
129
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
130
        $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.';
131
    } catch (CryptoException\BadFormatException $ex) {
132
        $err = $ex;
133
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
134
        $err = $ex;
135
    } catch (CryptoException\CryptoException $ex) {
136
        $err = $ex;
137
    } catch (CryptoException\IOException $ex) {
138
        $err = $ex;
139
    }
140
141
    return [
142
        'string' => $text ?? '',
143
        'error' => $err,
144
    ];
145
}
146
147
/**
148
 * Generating a defuse key.
149
 *
150
 * @return string
151
 */
152
function defuse_generate_key()
153
{
154
    $key = Key::createNewRandomKey();
155
    $key = $key->saveToAsciiSafeString();
156
    return $key;
157
}
158
159
/**
160
 * Generate a Defuse personal key.
161
 *
162
 * @param string $psk psk used
163
 *
164
 * @return string
165
 */
166
function defuse_generate_personal_key(string $psk): string
167
{
168
    $protected_key = KeyProtectedByPassword::createRandomPasswordProtectedKey($psk);
169
    return $protected_key->saveToAsciiSafeString(); // save this in user table
170
}
171
172
/**
173
 * Validate persoanl key with defuse.
174
 *
175
 * @param string $psk                   the user's psk
176
 * @param string $protected_key_encoded special key
177
 *
178
 * @return string
179
 */
180
function defuse_validate_personal_key(string $psk, string $protected_key_encoded): string
181
{
182
    try {
183
        $protected_key_encoded = KeyProtectedByPassword::loadFromAsciiSafeString($protected_key_encoded);
184
        $user_key = $protected_key_encoded->unlockKey($psk);
185
        $user_key_encoded = $user_key->saveToAsciiSafeString();
186
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
187
        return 'Error - Major issue as the encryption is broken.';
188
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
189
        return 'Error - The saltkey is not the correct one.';
190
    }
191
192
    return $user_key_encoded;
193
    // store it in session once user has entered his psk
194
}
195
196
/**
197
 * Decrypt a defuse string if encrypted.
198
 *
199
 * @param string $value Encrypted string
200
 *
201
 * @return string Decrypted string
202
 */
203
function defuseReturnDecrypted(string $value, $SETTINGS): string
204
{
205
    if (substr($value, 0, 3) === 'def') {
206
        $value = cryption($value, '', 'decrypt', $SETTINGS)['string'];
207
    }
208
209
    return $value;
210
}
211
212
/**
213
 * Trims a string depending on a specific string.
214
 *
215
 * @param string|array $chaine  what to trim
216
 * @param string       $element trim on what
217
 *
218
 * @return string
219
 */
220
function trimElement($chaine, string $element): string
221
{
222
    if (! empty($chaine)) {
223
        if (is_array($chaine) === true) {
224
            $chaine = implode(';', $chaine);
225
        }
226
        $chaine = trim($chaine);
227
        if (substr($chaine, 0, 1) === $element) {
228
            $chaine = substr($chaine, 1);
229
        }
230
        if (substr($chaine, strlen($chaine) - 1, 1) === $element) {
231
            $chaine = substr($chaine, 0, strlen($chaine) - 1);
232
        }
233
    }
234
235
    return $chaine;
236
}
237
238
/**
239
 * Permits to suppress all "special" characters from string.
240
 *
241
 * @param string $string  what to clean
242
 * @param bool   $special use of special chars?
243
 *
244
 * @return string
245
 */
246
function cleanString(string $string, bool $special = false): string
247
{
248
    // Create temporary table for special characters escape
249
    $tabSpecialChar = [];
250
    for ($i = 0; $i <= 31; ++$i) {
251
        $tabSpecialChar[] = chr($i);
252
    }
253
    array_push($tabSpecialChar, '<br />');
254
    if ((int) $special === 1) {
255
        $tabSpecialChar = array_merge($tabSpecialChar, ['</li>', '<ul>', '<ol>']);
256
    }
257
258
    return str_replace($tabSpecialChar, "\n", $string);
259
}
260
261
/**
262
 * Erro manager for DB.
263
 *
264
 * @param array $params output from query
265
 *
266
 * @return void
267
 */
268
function db_error_handler(array $params): void
269
{
270
    echo 'Error: ' . $params['error'] . "<br>\n";
271
    echo 'Query: ' . $params['query'] . "<br>\n";
272
    throw new Exception('Error - Query', 1);
273
}
274
275
/**
276
 * Identify user's rights
277
 *
278
 * @param string|array $groupesVisiblesUser  [description]
279
 * @param string|array $groupesInterditsUser [description]
280
 * @param string       $isAdmin              [description]
281
 * @param string       $idFonctions          [description]
282
 *
283
 * @return bool
284
 */
285
function identifyUserRights(
286
    $groupesVisiblesUser,
287
    $groupesInterditsUser,
288
    $isAdmin,
289
    $idFonctions,
290
    $SETTINGS
291
) {
292
    $session = SessionManager::getSession();
293
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
294
295
    // Check if user is ADMINISTRATOR    
296
    (int) $isAdmin === 1 ?
297
        identAdmin(
298
            $idFonctions,
299
            $SETTINGS, /** @scrutinizer ignore-type */
300
            $tree
301
        )
302
        :
303
        identUser(
304
            $groupesVisiblesUser,
305
            $groupesInterditsUser,
306
            $idFonctions,
307
            $SETTINGS, /** @scrutinizer ignore-type */
308
            $tree
309
        );
310
311
    // update user's timestamp
312
    DB::update(
313
        prefixTable('users'),
314
        [
315
            'timestamp' => time(),
316
        ],
317
        'id=%i',
318
        $session->get('user-id')
319
    );
320
321
    return true;
322
}
323
324
/**
325
 * Identify administrator.
326
 *
327
 * @param string $idFonctions Roles of user
328
 * @param array  $SETTINGS    Teampass settings
329
 * @param object $tree        Tree of folders
330
 *
331
 * @return bool
332
 */
333
function identAdmin($idFonctions, $SETTINGS, $tree)
334
{
335
    
336
    $session = SessionManager::getSession();
337
    $groupesVisibles = [];
338
    $session->set('user-personal_folders', []);
339
    $session->set('user-accessible_folders', []);
340
    $session->set('user-no_access_folders', []);
341
    $session->set('user-personal_visible_folders', []);
342
    $session->set('user-read_only_folders', []);
343
    $session->set('system-list_restricted_folders_for_items', []);
344
    $session->set('system-list_folders_editable_by_role', []);
345
    $session->set('user-list_folders_limited', []);
346
    $session->set('user-forbiden_personal_folders', []);
347
    $globalsUserId = $session->get('user-id');
348
    $globalsVisibleFolders = $session->get('user-accessible_folders');
349
    $globalsPersonalVisibleFolders = $session->get('user-personal_visible_folders');
350
    // Get list of Folders
351
    $rows = DB::query('SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i', 0);
352
    foreach ($rows as $record) {
353
        array_push($groupesVisibles, $record['id']);
354
    }
355
    $session->set('user-accessible_folders', $groupesVisibles);
356
    $session->set('user-all_non_personal_folders', $groupesVisibles);
357
    // Exclude all PF
358
    $where = new WhereClause('and');
359
    // create a WHERE statement of pieces joined by ANDs
360
    $where->add('personal_folder=%i', 1);
361
    if (
362
        isset($SETTINGS['enable_pf_feature']) === true
363
        && (int) $SETTINGS['enable_pf_feature'] === 1
364
    ) {
365
        $where->add('title=%s', $globalsUserId);
366
        $where->negateLast();
367
    }
368
    // Get ID of personal folder
369
    $persfld = DB::queryfirstrow(
370
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE title = %s',
371
        $globalsUserId
372
    );
373
    if (empty($persfld['id']) === false) {
374
        if (in_array($persfld['id'], $globalsVisibleFolders) === false) {
375
            array_push($globalsVisibleFolders, $persfld['id']);
376
            array_push($globalsPersonalVisibleFolders, $persfld['id']);
377
            // get all descendants
378
            $tree->rebuild();
379
            $tst = $tree->getDescendants($persfld['id']);
380
            foreach ($tst as $t) {
381
                array_push($globalsVisibleFolders, $t->id);
382
                array_push($globalsPersonalVisibleFolders, $t->id);
383
            }
384
        }
385
    }
386
387
    // get complete list of ROLES
388
    $tmp = explode(';', $idFonctions);
389
    $rows = DB::query(
390
        'SELECT * FROM ' . prefixTable('roles_title') . '
391
        ORDER BY title ASC'
392
    );
393
    foreach ($rows as $record) {
394
        if (! empty($record['id']) && ! in_array($record['id'], $tmp)) {
395
            array_push($tmp, $record['id']);
396
        }
397
    }
398
    $session->set('user-roles', implode(';', $tmp));
399
    $session->set('user-admin', 1);
400
    // Check if admin has created Folders and Roles
401
    DB::query('SELECT * FROM ' . prefixTable('nested_tree') . '');
402
    $session->set('user-nb_folders', DB::count());
403
    DB::query('SELECT * FROM ' . prefixTable('roles_title'));
404
    $session->set('user-nb_roles', DB::count());
405
406
    return true;
407
}
408
409
/**
410
 * Permits to convert an element to array.
411
 *
412
 * @param string|array $element Any value to be returned as array
413
 *
414
 * @return array
415
 */
416
function convertToArray($element): array
417
{
418
    if (is_string($element) === true) {
419
        if (empty($element) === true) {
420
            return [];
421
        }
422
        return explode(
423
            ';',
424
            trimElement($element, ';')
425
        );
426
    }
427
    return $element;
428
}
429
430
/**
431
 * Defines the rights the user has.
432
 *
433
 * @param string|array $allowedFolders  Allowed folders
434
 * @param string|array $noAccessFolders Not allowed folders
435
 * @param string|array $userRoles       Roles of user
436
 * @param array        $SETTINGS        Teampass settings
437
 * @param object       $tree            Tree of folders
438
 * 
439
 * @return bool
440
 */
441
function identUser(
442
    $allowedFolders,
443
    $noAccessFolders,
444
    $userRoles,
445
    array $SETTINGS,
446
    object $tree
447
) {
448
    
449
    $session = SessionManager::getSession();
450
    // Init
451
    $session->set('user-accessible_folders', []);
452
    $session->set('user-personal_folders', []);
453
    $session->set('user-no_access_folders', []);
454
    $session->set('user-personal_visible_folders', []);
455
    $session->set('user-read_only_folders', []);
456
    $session->set('user-user-roles', $userRoles);
457
    $session->set('user-admin', 0);
458
    // init
459
    $personalFolders = [];
460
    $readOnlyFolders = [];
461
    $noAccessPersonalFolders = [];
462
    $restrictedFoldersForItems = [];
463
    $foldersLimited = [];
464
    $foldersLimitedFull = [];
465
    $allowedFoldersByRoles = [];
466
    $globalsUserId = $session->get('user-id');
467
    $globalsPersonalFolders = $session->get('user-personal_folder_enabled');
468
    // Ensure consistency in array format
469
    $noAccessFolders = convertToArray($noAccessFolders);
470
    $userRoles = convertToArray($userRoles);
471
    $allowedFolders = convertToArray($allowedFolders);
472
    
473
    // Get list of folders depending on Roles
474
    $arrays = identUserGetFoldersFromRoles(
475
        $userRoles,
476
        $allowedFoldersByRoles,
477
        $readOnlyFolders,
478
        $allowedFolders
479
    );
480
    $allowedFoldersByRoles = $arrays['allowedFoldersByRoles'];
481
    $readOnlyFolders = $arrays['readOnlyFolders'];
482
483
    // Does this user is allowed to see other items
484
    $inc = 0;
485
    $rows = DB::query(
486
        'SELECT id, id_tree FROM ' . prefixTable('items') . '
487
            WHERE restricted_to LIKE %ss AND inactif = %s'.
488
            (count($allowedFolders) > 0 ? ' AND id_tree NOT IN ('.implode(',', $allowedFolders).')' : ''),
489
        $globalsUserId,
490
        '0'
491
    );
492
    foreach ($rows as $record) {
493
        // Exclude restriction on item if folder is fully accessible
494
        //if (in_array($record['id_tree'], $allowedFolders) === false) {
495
            $restrictedFoldersForItems[$record['id_tree']][$inc] = $record['id'];
496
            ++$inc;
497
        //}
498
    }
499
500
    // Check for the users roles if some specific rights exist on items
501
    $rows = DB::query(
502
        'SELECT i.id_tree, r.item_id
503
        FROM ' . prefixTable('items') . ' as i
504
        INNER JOIN ' . prefixTable('restriction_to_roles') . ' as r ON (r.item_id=i.id)
505
        WHERE i.id_tree <> "" '.
506
        (count($userRoles) > 0 ? 'AND r.role_id IN %li ' : '').
507
        'ORDER BY i.id_tree ASC',
508
        $userRoles
509
    );
510
    $inc = 0;
511
    foreach ($rows as $record) {
512
        //if (isset($record['id_tree'])) {
513
            $foldersLimited[$record['id_tree']][$inc] = $record['item_id'];
514
            array_push($foldersLimitedFull, $record['id_tree']);
515
            ++$inc;
516
        //}
517
    }
518
519
    // Get list of Personal Folders
520
    $arrays = identUserGetPFList(
521
        $globalsPersonalFolders,
522
        $allowedFolders,
523
        $globalsUserId,
524
        $personalFolders,
525
        $noAccessPersonalFolders,
526
        $foldersLimitedFull,
527
        $allowedFoldersByRoles,
528
        array_keys($restrictedFoldersForItems),
529
        $readOnlyFolders,
530
        $noAccessFolders,
531
        isset($SETTINGS['enable_pf_feature']) === true ? $SETTINGS['enable_pf_feature'] : 0,
532
        $tree
533
    );
534
    $allowedFolders = $arrays['allowedFolders'];
535
    $personalFolders = $arrays['personalFolders'];
536
    $noAccessPersonalFolders = $arrays['noAccessPersonalFolders'];
537
538
    // Return data
539
    $session->set('user-all_non_personal_folders', $allowedFolders);
540
    $session->set('user-accessible_folders', array_unique(array_merge($allowedFolders, $personalFolders), SORT_NUMERIC));
541
    $session->set('user-read_only_folders', $readOnlyFolders);
542
    $session->set('user-no_access_folders', $noAccessFolders);
543
    $session->set('user-personal_folders', $personalFolders);
544
    $session->set('user-list_folders_limited', $foldersLimited);
545
    $session->set('system-list_folders_editable_by_role', $allowedFoldersByRoles, 'SESSION');
546
    $session->set('system-list_restricted_folders_for_items', $restrictedFoldersForItems);
547
    $session->set('user-forbiden_personal_folders', $noAccessPersonalFolders);
548
    $session->set(
549
        'all_folders_including_no_access',
550
        array_unique(array_merge(
551
            $allowedFolders,
552
            $personalFolders,
553
            $noAccessFolders,
554
            $readOnlyFolders
555
        ), SORT_NUMERIC)
556
    );
557
    // Folders and Roles numbers
558
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('nested_tree') . '');
559
    $session->set('user-nb_folders', DB::count());
560
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('roles_title'));
561
    $session->set('user-nb_roles', DB::count());
562
    // check if change proposals on User's items
563
    if (isset($SETTINGS['enable_suggestion']) === true && (int) $SETTINGS['enable_suggestion'] === 1) {
564
        $countNewItems = DB::query(
565
            'SELECT COUNT(*)
566
            FROM ' . prefixTable('items_change') . ' AS c
567
            LEFT JOIN ' . prefixTable('log_items') . ' AS i ON (c.item_id = i.id_item)
568
            WHERE i.action = %s AND i.id_user = %i',
569
            'at_creation',
570
            $globalsUserId
571
        );
572
        $session->set('user-nb_item_change_proposals', $countNewItems);
573
    } else {
574
        $session->set('user-nb_item_change_proposals', 0);
575
    }
576
577
    return true;
578
}
579
580
/**
581
 * Get list of folders depending on Roles
582
 * 
583
 * @param array $userRoles
584
 * @param array $allowedFoldersByRoles
585
 * @param array $readOnlyFolders
586
 * @param array $allowedFolders
587
 * 
588
 * @return array
589
 */
590
function identUserGetFoldersFromRoles($userRoles, $allowedFoldersByRoles, $readOnlyFolders, $allowedFolders) : array
591
{
592
    $rows = DB::query(
593
        'SELECT *
594
        FROM ' . prefixTable('roles_values') . '
595
        WHERE type IN %ls'.(count($userRoles) > 0 ? ' AND role_id IN %li' : ''),
596
        ['W', 'ND', 'NE', 'NDNE', 'R'],
597
        $userRoles,
598
    );
599
    foreach ($rows as $record) {
600
        if ($record['type'] === 'R') {
601
            array_push($readOnlyFolders, $record['folder_id']);
602
        } elseif (in_array($record['folder_id'], $allowedFolders) === false) {
603
            array_push($allowedFoldersByRoles, $record['folder_id']);
604
        }
605
    }
606
    $allowedFoldersByRoles = array_unique($allowedFoldersByRoles);
607
    $readOnlyFolders = array_unique($readOnlyFolders);
608
    
609
    // Clean arrays
610
    foreach ($allowedFoldersByRoles as $value) {
611
        $key = array_search($value, $readOnlyFolders);
612
        if ($key !== false) {
613
            unset($readOnlyFolders[$key]);
614
        }
615
    }
616
    return [
617
        'readOnlyFolders' => $readOnlyFolders,
618
        'allowedFoldersByRoles' => $allowedFoldersByRoles
619
    ];
620
}
621
622
/**
623
 * Get list of Personal Folders
624
 * 
625
 * @param int $globalsPersonalFolders
626
 * @param array $allowedFolders
627
 * @param int $globalsUserId
628
 * @param array $personalFolders
629
 * @param array $noAccessPersonalFolders
630
 * @param array $foldersLimitedFull
631
 * @param array $allowedFoldersByRoles
632
 * @param array $restrictedFoldersForItems
633
 * @param array $readOnlyFolders
634
 * @param array $noAccessFolders
635
 * @param int $enablePfFeature
636
 * @param object $tree
637
 * 
638
 * @return array
639
 */
640
function identUserGetPFList(
641
    $globalsPersonalFolders,
642
    $allowedFolders,
643
    $globalsUserId,
644
    $personalFolders,
645
    $noAccessPersonalFolders,
646
    $foldersLimitedFull,
647
    $allowedFoldersByRoles,
648
    $restrictedFoldersForItems,
649
    $readOnlyFolders,
650
    $noAccessFolders,
651
    $enablePfFeature,
652
    $tree
653
)
654
{
655
    if (
656
        (int) $enablePfFeature === 1
657
        && (int) $globalsPersonalFolders === 1
658
    ) {
659
        $persoFld = DB::queryfirstrow(
660
            'SELECT id
661
            FROM ' . prefixTable('nested_tree') . '
662
            WHERE title = %s AND personal_folder = %i'.
663
            (count($allowedFolders) > 0 ? ' AND id NOT IN ('.implode(',', $allowedFolders).')' : ''),
664
            $globalsUserId,
665
            1
666
        );
667
        if (empty($persoFld['id']) === false) {
668
            array_push($personalFolders, $persoFld['id']);
669
            array_push($allowedFolders, $persoFld['id']);
670
            // get all descendants
671
            $ids = $tree->getDescendants($persoFld['id'], false, false, true);
672
            foreach ($ids as $id) {
673
                //array_push($allowedFolders, $id);
674
                array_push($personalFolders, $id);
675
            }
676
        }
677
    }
678
    
679
    // Exclude all other PF
680
    $where = new WhereClause('and');
681
    $where->add('personal_folder=%i', 1);
682
    if (count($personalFolders) > 0) {
683
        $where->add('id NOT IN ('.implode(',', $personalFolders).')');
684
    }
685
    if (
686
        (int) $enablePfFeature === 1
687
        && (int) $globalsPersonalFolders === 1
688
    ) {
689
        $where->add('title=%s', $globalsUserId);
690
        $where->negateLast();
691
    }
692
    $persoFlds = DB::query(
693
        'SELECT id
694
        FROM ' . prefixTable('nested_tree') . '
695
        WHERE %l',
696
        $where
697
    );
698
    foreach ($persoFlds as $persoFldId) {
699
        array_push($noAccessPersonalFolders, $persoFldId['id']);
700
    }
701
702
    // All folders visibles
703
    $allowedFolders = array_unique(array_merge(
704
        $allowedFolders,
705
        $foldersLimitedFull,
706
        $allowedFoldersByRoles,
707
        $restrictedFoldersForItems,
708
        $readOnlyFolders
709
    ), SORT_NUMERIC);
710
    // Exclude from allowed folders all the specific user forbidden folders
711
    if (count($noAccessFolders) > 0) {
712
        $allowedFolders = array_diff($allowedFolders, $noAccessFolders);
713
    }
714
715
    return [
716
        'allowedFolders' => array_diff(array_diff($allowedFolders, $noAccessPersonalFolders), $personalFolders),
717
        'personalFolders' => $personalFolders,
718
        'noAccessPersonalFolders' => $noAccessPersonalFolders
719
    ];
720
}
721
722
723
/**
724
 * Update the CACHE table.
725
 *
726
 * @param string $action   What to do
727
 * @param array  $SETTINGS Teampass settings
728
 * @param int    $ident    Ident format
729
 * 
730
 * @return void
731
 */
732
function updateCacheTable(string $action, ?int $ident = null): void
733
{
734
    if ($action === 'reload') {
735
        // Rebuild full cache table
736
        cacheTableRefresh();
737
    } elseif ($action === 'update_value' && is_null($ident) === false) {
738
        // UPDATE an item
739
        cacheTableUpdate($ident);
740
    } elseif ($action === 'add_value' && is_null($ident) === false) {
741
        // ADD an item
742
        cacheTableAdd($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
 * @return void
753
 */
754
function cacheTableRefresh(): void
755
{
756
    // Load class DB
757
    loadClasses('DB');
758
759
    //Load Tree
760
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
761
    // truncate table
762
    DB::query('TRUNCATE TABLE ' . prefixTable('cache'));
763
    // reload date
764
    $rows = DB::query(
765
        'SELECT *
766
        FROM ' . prefixTable('items') . ' as i
767
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
768
        AND l.action = %s
769
        AND i.inactif = %i',
770
        'at_creation',
771
        0
772
    );
773
    foreach ($rows as $record) {
774
        if (empty($record['id_tree']) === false) {
775
            // Get all TAGS
776
            $tags = '';
777
            $itemTags = DB::query(
778
                'SELECT tag
779
                FROM ' . prefixTable('tags') . '
780
                WHERE item_id = %i AND tag != ""',
781
                $record['id']
782
            );
783
            foreach ($itemTags as $itemTag) {
784
                $tags .= $itemTag['tag'] . ' ';
785
            }
786
787
            // Get renewal period
788
            $resNT = DB::queryfirstrow(
789
                'SELECT renewal_period
790
                FROM ' . prefixTable('nested_tree') . '
791
                WHERE id = %i',
792
                $record['id_tree']
793
            );
794
            // form id_tree to full foldername
795
            $folder = [];
796
            $arbo = $tree->getPath($record['id_tree'], true);
797
            foreach ($arbo as $elem) {
798
                // Check if title is the ID of a user
799
                if (is_numeric($elem->title) === true) {
800
                    // Is this a User id?
801
                    $user = DB::queryfirstrow(
802
                        'SELECT id, login
803
                        FROM ' . prefixTable('users') . '
804
                        WHERE id = %i',
805
                        $elem->title
806
                    );
807
                    if (count($user) > 0) {
808
                        $elem->title = $user['login'];
809
                    }
810
                }
811
                // Build path
812
                array_push($folder, stripslashes($elem->title));
813
            }
814
            // store data
815
            DB::insert(
816
                prefixTable('cache'),
817
                [
818
                    'id' => $record['id'],
819
                    'label' => $record['label'],
820
                    'description' => $record['description'] ?? '',
821
                    'url' => isset($record['url']) && ! empty($record['url']) ? $record['url'] : '0',
822
                    'tags' => $tags,
823
                    'id_tree' => $record['id_tree'],
824
                    'perso' => $record['perso'],
825
                    'restricted_to' => isset($record['restricted_to']) && ! empty($record['restricted_to']) ? $record['restricted_to'] : '0',
826
                    'login' => $record['login'] ?? '',
827
                    'folder' => implode(' > ', $folder),
828
                    'author' => $record['id_user'],
829
                    'renewal_period' => $resNT['renewal_period'] ?? '0',
830
                    'timestamp' => $record['date'],
831
                ]
832
            );
833
        }
834
    }
835
}
836
837
/**
838
 * Cache table - update existing value.
839
 *
840
 * @param int    $ident    Ident format
841
 * 
842
 * @return void
843
 */
844
function cacheTableUpdate(?int $ident = null): void
845
{
846
    $session = SessionManager::getSession();
847
    loadClasses('DB');
848
849
    //Load Tree
850
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
851
    // get new value from db
852
    $data = DB::queryfirstrow(
853
        'SELECT label, description, id_tree, perso, restricted_to, login, url
854
        FROM ' . prefixTable('items') . '
855
        WHERE id=%i',
856
        $ident
857
    );
858
    // Get all TAGS
859
    $tags = '';
860
    $itemTags = DB::query(
861
        'SELECT tag
862
            FROM ' . prefixTable('tags') . '
863
            WHERE item_id = %i AND tag != ""',
864
        $ident
865
    );
866
    foreach ($itemTags as $itemTag) {
867
        $tags .= $itemTag['tag'] . ' ';
868
    }
869
    // form id_tree to full foldername
870
    $folder = [];
871
    $arbo = $tree->getPath($data['id_tree'], true);
872
    foreach ($arbo as $elem) {
873
        // Check if title is the ID of a user
874
        if (is_numeric($elem->title) === true) {
875
            // Is this a User id?
876
            $user = DB::queryfirstrow(
877
                'SELECT id, login
878
                FROM ' . prefixTable('users') . '
879
                WHERE id = %i',
880
                $elem->title
881
            );
882
            if (count($user) > 0) {
883
                $elem->title = $user['login'];
884
            }
885
        }
886
        // Build path
887
        array_push($folder, stripslashes($elem->title));
888
    }
889
    // finaly update
890
    DB::update(
891
        prefixTable('cache'),
892
        [
893
            'label' => $data['label'],
894
            'description' => $data['description'],
895
            'tags' => $tags,
896
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
897
            'id_tree' => $data['id_tree'],
898
            'perso' => $data['perso'],
899
            'restricted_to' => isset($data['restricted_to']) && ! empty($data['restricted_to']) ? $data['restricted_to'] : '0',
900
            'login' => $data['login'] ?? '',
901
            'folder' => implode(' » ', $folder),
902
            'author' => $session->get('user-id'),
903
        ],
904
        'id = %i',
905
        $ident
906
    );
907
}
908
909
/**
910
 * Cache table - add new value.
911
 *
912
 * @param int    $ident    Ident format
913
 * 
914
 * @return void
915
 */
916
function cacheTableAdd(?int $ident = null): void
917
{
918
    $session = SessionManager::getSession();
919
    $globalsUserId = $session->get('user-id');
920
921
    // Load class DB
922
    loadClasses('DB');
923
924
    //Load Tree
925
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
926
    // get new value from db
927
    $data = DB::queryFirstRow(
928
        'SELECT i.label, i.description, i.id_tree as id_tree, i.perso, i.restricted_to, i.id, i.login, i.url, l.date
929
        FROM ' . prefixTable('items') . ' as i
930
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
931
        WHERE i.id = %i
932
        AND l.action = %s',
933
        $ident,
934
        'at_creation'
935
    );
936
    // Get all TAGS
937
    $tags = '';
938
    $itemTags = DB::query(
939
        'SELECT tag
940
            FROM ' . prefixTable('tags') . '
941
            WHERE item_id = %i AND tag != ""',
942
        $ident
943
    );
944
    foreach ($itemTags as $itemTag) {
945
        $tags .= $itemTag['tag'] . ' ';
946
    }
947
    // form id_tree to full foldername
948
    $folder = [];
949
    $arbo = $tree->getPath($data['id_tree'], true);
950
    foreach ($arbo as $elem) {
951
        // Check if title is the ID of a user
952
        if (is_numeric($elem->title) === true) {
953
            // Is this a User id?
954
            $user = DB::queryfirstrow(
955
                'SELECT id, login
956
                FROM ' . prefixTable('users') . '
957
                WHERE id = %i',
958
                $elem->title
959
            );
960
            if (count($user) > 0) {
961
                $elem->title = $user['login'];
962
            }
963
        }
964
        // Build path
965
        array_push($folder, stripslashes($elem->title));
966
    }
967
    // finaly update
968
    DB::insert(
969
        prefixTable('cache'),
970
        [
971
            'id' => $data['id'],
972
            'label' => $data['label'],
973
            'description' => $data['description'],
974
            'tags' => isset($tags) && empty($tags) === false ? $tags : 'None',
975
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
976
            'id_tree' => $data['id_tree'],
977
            'perso' => isset($data['perso']) && empty($data['perso']) === false && $data['perso'] !== 'None' ? $data['perso'] : '0',
978
            'restricted_to' => isset($data['restricted_to']) && empty($data['restricted_to']) === false ? $data['restricted_to'] : '0',
979
            'login' => $data['login'] ?? '',
980
            'folder' => implode(' » ', $folder),
981
            'author' => $globalsUserId,
982
            'timestamp' => $data['date'],
983
        ]
984
    );
985
}
986
987
/**
988
 * Do statistics.
989
 *
990
 * @param array $SETTINGS Teampass settings
991
 *
992
 * @return array
993
 */
994
function getStatisticsData(array $SETTINGS): array
995
{
996
    DB::query(
997
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
998
        0
999
    );
1000
    $counter_folders = DB::count();
1001
    DB::query(
1002
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1003
        1
1004
    );
1005
    $counter_folders_perso = DB::count();
1006
    DB::query(
1007
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1008
        0
1009
    );
1010
    $counter_items = DB::count();
1011
        DB::query(
1012
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1013
        1
1014
    );
1015
    $counter_items_perso = DB::count();
1016
        DB::query(
1017
        'SELECT id FROM ' . prefixTable('users') . ''
1018
    );
1019
    $counter_users = DB::count();
1020
        DB::query(
1021
        'SELECT id FROM ' . prefixTable('users') . ' WHERE admin = %i',
1022
        1
1023
    );
1024
    $admins = DB::count();
1025
    DB::query(
1026
        'SELECT id FROM ' . prefixTable('users') . ' WHERE gestionnaire = %i',
1027
        1
1028
    );
1029
    $managers = DB::count();
1030
    DB::query(
1031
        'SELECT id FROM ' . prefixTable('users') . ' WHERE read_only = %i',
1032
        1
1033
    );
1034
    $readOnly = DB::count();
1035
    // list the languages
1036
    $usedLang = [];
1037
    $tp_languages = DB::query(
1038
        'SELECT name FROM ' . prefixTable('languages')
1039
    );
1040
    foreach ($tp_languages as $tp_language) {
1041
        DB::query(
1042
            'SELECT * FROM ' . prefixTable('users') . ' WHERE user_language = %s',
1043
            $tp_language['name']
1044
        );
1045
        $usedLang[$tp_language['name']] = round((DB::count() * 100 / $counter_users), 0);
1046
    }
1047
1048
    // get list of ips
1049
    $usedIp = [];
1050
    $tp_ips = DB::query(
1051
        'SELECT user_ip FROM ' . prefixTable('users')
1052
    );
1053
    foreach ($tp_ips as $ip) {
1054
        if (array_key_exists($ip['user_ip'], $usedIp)) {
1055
            $usedIp[$ip['user_ip']] += $usedIp[$ip['user_ip']];
1056
        } elseif (! empty($ip['user_ip']) && $ip['user_ip'] !== 'none') {
1057
            $usedIp[$ip['user_ip']] = 1;
1058
        }
1059
    }
1060
1061
    return [
1062
        'error' => '',
1063
        'stat_phpversion' => phpversion(),
1064
        'stat_folders' => $counter_folders,
1065
        'stat_folders_shared' => intval($counter_folders) - intval($counter_folders_perso),
1066
        'stat_items' => $counter_items,
1067
        'stat_items_shared' => intval($counter_items) - intval($counter_items_perso),
1068
        'stat_users' => $counter_users,
1069
        'stat_admins' => $admins,
1070
        'stat_managers' => $managers,
1071
        'stat_ro' => $readOnly,
1072
        'stat_kb' => $SETTINGS['enable_kb'],
1073
        'stat_pf' => $SETTINGS['enable_pf_feature'],
1074
        'stat_fav' => $SETTINGS['enable_favourites'],
1075
        'stat_teampassversion' => TP_VERSION,
1076
        'stat_ldap' => $SETTINGS['ldap_mode'],
1077
        'stat_agses' => $SETTINGS['agses_authentication_enabled'],
1078
        'stat_duo' => $SETTINGS['duo'],
1079
        'stat_suggestion' => $SETTINGS['enable_suggestion'],
1080
        'stat_api' => $SETTINGS['api'],
1081
        'stat_customfields' => $SETTINGS['item_extra_fields'],
1082
        'stat_syslog' => $SETTINGS['syslog_enable'],
1083
        'stat_2fa' => $SETTINGS['google_authentication'],
1084
        'stat_stricthttps' => $SETTINGS['enable_sts'],
1085
        'stat_mysqlversion' => DB::serverVersion(),
1086
        'stat_languages' => $usedLang,
1087
        'stat_country' => $usedIp,
1088
    ];
1089
}
1090
1091
/**
1092
 * Permits to prepare the way to send the email
1093
 * 
1094
 * @param string $subject       email subject
1095
 * @param string $body          email message
1096
 * @param string $email         email
1097
 * @param string $receiverName  Receiver name
1098
 * @param array  $SETTINGS      settings
1099
 *
1100
 * @return void
1101
 */
1102
function prepareSendingEmail(
1103
    $subject,
1104
    $body,
1105
    $email,
1106
    $receiverName = ''
1107
): void 
1108
{
1109
    DB::insert(
1110
        prefixTable('processes'),
1111
        array(
1112
            'created_at' => time(),
1113
            'process_type' => 'send_email',
1114
            'arguments' => json_encode([
1115
                'subject' => $subject,
1116
                'receivers' => $email,
1117
                'body' => $body,
1118
                'receiver_name' => $receiverName,
1119
            ], JSON_HEX_QUOT | JSON_HEX_TAG),
1120
            'updated_at' => '',
1121
            'finished_at' => '',
1122
            'output' => '',
1123
        )
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
    $cron = false
1147
) {
1148
    $lang = new Language(); 
1149
1150
    // CAse where email not defined
1151
    if ($email === 'none' || empty($email) === true) {
1152
        return json_encode(
1153
            [
1154
                'error' => true,
1155
                'message' => $lang->get('forgot_my_pw_email_sent'),
1156
            ]
1157
        );
1158
    }
1159
1160
    // Build and send email
1161
    $email = buildEmail(
1162
        $subject,
1163
        $textMail,
1164
        $email,
1165
        $SETTINGS,
1166
        $textMailAlt = null,
1167
        $silent = true,
1168
        $cron
1169
    );
1170
1171
    if ($silent === false) {
0 ignored issues
show
introduced by
The condition $silent === false is always false.
Loading history...
1172
        return json_encode(
1173
            [
1174
                'error' => false,
1175
                'message' => $lang->get('forgot_my_pw_email_sent'),
1176
            ]
1177
        );
1178
    }
1179
    // Debug purpose
1180
    if ((int) $SETTINGS['email_debug_level'] !== 0 && $cron === false) {
1181
        return json_encode(
1182
            [
1183
                'error' => true,
1184
                'message' => isset($email['ErrorInfo']) === true ? $email['ErrorInfo'] : '',
1185
            ]
1186
        );
1187
    }
1188
    return json_encode(
1189
        [
1190
            'error' => false,
1191
            'message' => $lang->get('share_sent_ok'),
1192
        ]
1193
    );
1194
}
1195
1196
1197
function buildEmail(
1198
    $subject,
1199
    $textMail,
1200
    $email,
1201
    $SETTINGS,
1202
    $textMailAlt = null,
1203
    $silent = true,
1204
    $cron = false
1205
)
1206
{
1207
    // Load PHPMailer
1208
    $mail = new PHPMailer(true);
1209
    $languageDir = $SETTINGS['cpassman_dir'] . '/vendor/phpmailer/phpmailer/language/';
1210
1211
    try {
1212
        // Set language and SMTPDebug
1213
        $mail->setLanguage('en', $languageDir);
1214
        $mail->SMTPDebug = ($cron || $silent) ? 0 : $SETTINGS['email_debug_level'];
1215
        
1216
        // Define custom Debug output function
1217
        $mail->Debugoutput = function($str, $level) {
1218
            // Path to your log file
1219
            $logFilePath = '/var/log/phpmailer.log';
1220
            file_put_contents($logFilePath, gmdate('Y-m-d H:i:s'). "\t$level\t$str\n", FILE_APPEND | LOCK_EX);
1221
        };
1222
1223
        // Configure SMTP
1224
        $mail->isSMTP();
1225
        $mail->Host = $SETTINGS['email_smtp_server'];
1226
        $mail->SMTPAuth = (int) $SETTINGS['email_smtp_auth'] === 1;
1227
        $mail->Username = $SETTINGS['email_auth_username'];
1228
        $mail->Password = $SETTINGS['email_auth_pwd'];
1229
        $mail->Port = (int) $SETTINGS['email_port'];
1230
        $mail->SMTPSecure = $SETTINGS['email_security'] !== 'none' ? $SETTINGS['email_security'] : '';
1231
        $mail->SMTPAutoTLS = $SETTINGS['email_security'] !== 'none';
1232
        $mail->SMTPOptions = [
1233
            'ssl' => [
1234
                'verify_peer' => false,
1235
                'verify_peer_name' => false,
1236
                'allow_self_signed' => true,
1237
            ],
1238
        ];
1239
1240
        // Set From and FromName
1241
        $mail->From = $SETTINGS['email_from'];
1242
        $mail->FromName = $SETTINGS['email_from_name'];
1243
1244
        // Prepare recipients
1245
        foreach (array_filter(explode(',', $email)) as $dest) {
1246
            $mail->addAddress($dest);
1247
        }
1248
        
1249
        // Prepare HTML and AltBody
1250
        $text_html = emailBody($textMail);
1251
        $mail->WordWrap = 80;
1252
        $mail->isHtml(true);
1253
        $mail->Subject = $subject;
1254
        $mail->Body = $text_html;
1255
        $mail->AltBody = is_null($textMailAlt) ? '' : $textMailAlt;
1256
        error_log('Je vais envoyer le mail - error log level: '.$SETTINGS['email_debug_level'].' - Server: '.$SETTINGS['email_smtp_server']);
1257
        // Send email
1258
        $mail->send();
1259
        $mail->smtpClose();
1260
        error_log('Mail envoyé');
1261
        return '';
1262
    } catch (Exception $e) {
1263
        if (!$silent || (int) $SETTINGS['email_debug_level'] !== 0) {
1264
            return json_encode([
1265
                'error' => true,
1266
                'errorInfo' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1267
            ]);
1268
        }
1269
        return '';
1270
    }
1271
    
1272
    /*
1273
    // load PHPMailer
1274
    $mail = new PHPMailer(true);
1275
1276
    // send to user
1277
    $mail->setLanguage('en', $SETTINGS['cpassman_dir'] . '/vendor/phpmailer/phpmailer/language/');
1278
    $mail->SMTPDebug = isset($SETTINGS['email_debug_level']) === true && $cron === false && $silent === false ? $SETTINGS['email_debug_level'] : 0;
1279
    $mail->Port = (int) $SETTINGS['email_port'];
1280
    //COULD BE USED
1281
    $mail->CharSet = 'utf-8';
1282
    $mail->SMTPSecure = $SETTINGS['email_security'] !== 'none' ? $SETTINGS['email_security'] : '';
1283
    $mail->SMTPAutoTLS = $SETTINGS['email_security'] !== 'none' ? true : false;
1284
    $mail->SMTPOptions = [
1285
        'ssl' => [
1286
            'verify_peer' => false,
1287
            'verify_peer_name' => false,
1288
            'allow_self_signed' => true,
1289
        ],
1290
    ];
1291
    $mail->isSmtp();
1292
    $mail->Host = $SETTINGS['email_smtp_server'];
1293
    $mail->SMTPAuth = (int) $SETTINGS['email_smtp_auth'] === 1 ? true : false;
1294
    $mail->Username = $SETTINGS['email_auth_username'];
1295
    $mail->Password = $SETTINGS['email_auth_pwd'];
1296
    $mail->From = $SETTINGS['email_from'];
1297
    $mail->FromName = $SETTINGS['email_from_name'];
1298
    // Prepare recipients
1299
    foreach (array_filter(explode(',', $email)) as $dest) {
1300
        $mail->addAddress($dest);
1301
    }
1302
    
1303
    // Prepare HTML
1304
    $text_html = emailBody($textMail);
1305
    $mail->WordWrap = 80;
1306
    // set word wrap
1307
    $mail->isHtml(true);
1308
    // send as HTML
1309
    $mail->Subject = $subject;
1310
    $mail->Body = $text_html;
1311
    $mail->AltBody = is_null($textMailAlt) === false ? $textMailAlt : '';
1312
1313
    try {
1314
        // send email
1315
        $mail->send();
1316
    } catch (Exception $e) {
1317
        if ($silent === false || (int) $SETTINGS['email_debug_level'] !== 0) {
1318
            return json_encode(
1319
                [
1320
                    'error' => true,
1321
                    'errorInfo' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1322
                ]
1323
            );
1324
        }
1325
        return '';
1326
    }
1327
    $mail->smtpClose();
1328
1329
    return json_encode(
1330
        [
1331
            'error' => true,
1332
            'errorInfo' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1333
        ]
1334
    );
1335
    */
1336
}
1337
1338
/**
1339
 * Returns the email body.
1340
 *
1341
 * @param string $textMail Text for the email
1342
 */
1343
function emailBody(string $textMail): string
1344
{
1345
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1346
    w3.org/TR/html4/loose.dtd"><html>
1347
    <head><title>Email Template</title>
1348
    <style type="text/css">
1349
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1350
    </style></head>
1351
    <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">
1352
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1353
    <tr><td style="border-collapse: collapse;"><br>
1354
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1355
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1356
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1357
        </td></tr></table></td>
1358
    </tr>
1359
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1360
        <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;">
1361
        <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;">
1362
        <br><div style="float:right;">' .
1363
        $textMail .
1364
        '<br><br></td></tr></table>
1365
    </td></tr></table>
1366
    <br></body></html>';
1367
}
1368
1369
/**
1370
 * Generate a Key.
1371
 * 
1372
 * @return string
1373
 */
1374
function generateKey(): string
1375
{
1376
    return substr(md5(rand() . rand()), 0, 15);
1377
}
1378
1379
/**
1380
 * Convert date to timestamp.
1381
 *
1382
 * @param string $date        The date
1383
 * @param string $date_format Date format
1384
 *
1385
 * @return int
1386
 */
1387
function dateToStamp(string $date, string $date_format): int
1388
{
1389
    $date = date_parse_from_format($date_format, $date);
1390
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1391
        return mktime(
1392
            empty($date['hour']) === false ? $date['hour'] : 23,
1393
            empty($date['minute']) === false ? $date['minute'] : 59,
1394
            empty($date['second']) === false ? $date['second'] : 59,
1395
            $date['month'],
1396
            $date['day'],
1397
            $date['year']
1398
        );
1399
    }
1400
    return 0;
1401
}
1402
1403
/**
1404
 * Is this a date.
1405
 *
1406
 * @param string $date Date
1407
 *
1408
 * @return bool
1409
 */
1410
function isDate(string $date): bool
1411
{
1412
    return strtotime($date) !== false;
1413
}
1414
1415
/**
1416
 * Check if isUTF8().
1417
 *
1418
 * @param string|array $string Is the string
1419
 *
1420
 * @return int is the string in UTF8 format
1421
 */
1422
function isUTF8($string): int
1423
{
1424
    if (is_array($string) === true) {
1425
        $string = $string['string'];
1426
    }
1427
1428
    return preg_match(
1429
        '%^(?:
1430
        [\x09\x0A\x0D\x20-\x7E] # ASCII
1431
        | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
1432
        | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
1433
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
1434
        | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
1435
        | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
1436
        | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
1437
        | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
1438
        )*$%xs',
1439
        $string
1440
    );
1441
}
1442
1443
/**
1444
 * Prepare an array to UTF8 format before JSON_encode.
1445
 *
1446
 * @param array $array Array of values
1447
 *
1448
 * @return array
1449
 */
1450
function utf8Converter(array $array): array
1451
{
1452
    array_walk_recursive(
1453
        $array,
1454
        static function (&$item): void {
1455
            if (mb_detect_encoding((string) $item, 'utf-8', true) === false) {
1456
                $item = mb_convert_encoding($item, 'ISO-8859-1', 'UTF-8');
1457
            }
1458
        }
1459
    );
1460
    return $array;
1461
}
1462
1463
/**
1464
 * Permits to prepare data to be exchanged.
1465
 *
1466
 * @param array|string $data Text
1467
 * @param string       $type Parameter
1468
 * @param string       $key  Optional key
1469
 *
1470
 * @return string|array
1471
 */
1472
function prepareExchangedData($data, string $type, ?string $key = null)
1473
{
1474
    $session = SessionManager::getSession();
1475
    
1476
    // Perform
1477
    if ($type === 'encode' && is_array($data) === true) {
1478
        // Now encode
1479
        return Encryption::encrypt(
1480
            json_encode(
1481
                $data,
1482
                JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1483
            ),
1484
            $session->get('key')
1485
        );
1486
    }
1487
    if ($type === 'decode' && is_array($data) === false) {
1488
        // check if key exists
1489
        return json_decode(
1490
            (string) Encryption::decrypt(
1491
                (string) $data,
1492
                $session->get('key')
1493
            ),
1494
            true
1495
        );
1496
    }
1497
    return '';
1498
}
1499
1500
1501
/**
1502
 * Create a thumbnail.
1503
 *
1504
 * @param string  $src           Source
1505
 * @param string  $dest          Destination
1506
 * @param int $desired_width Size of width
1507
 * 
1508
 * @return void|string|bool
1509
 */
1510
function makeThumbnail(string $src, string $dest, int $desired_width)
1511
{
1512
    /* read the source image */
1513
    if (is_file($src) === true && mime_content_type($src) === 'image/png') {
1514
        $source_image = imagecreatefrompng($src);
1515
        if ($source_image === false) {
1516
            return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1517
        }
1518
    } else {
1519
        return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1520
    }
1521
1522
    // Get height and width
1523
    $width = imagesx($source_image);
1524
    $height = imagesy($source_image);
1525
    /* find the "desired height" of this thumbnail, relative to the desired width  */
1526
    $desired_height = (int) floor($height * $desired_width / $width);
1527
    /* create a new, "virtual" image */
1528
    $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
1529
    if ($virtual_image === false) {
1530
        return false;
1531
    }
1532
    /* copy source image at a resized size */
1533
    imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
1534
    /* create the physical thumbnail image to its destination */
1535
    imagejpeg($virtual_image, $dest);
1536
}
1537
1538
/**
1539
 * Check table prefix in SQL query.
1540
 *
1541
 * @param string $table Table name
1542
 * 
1543
 * @return string
1544
 */
1545
function prefixTable(string $table): string
1546
{
1547
    $safeTable = htmlspecialchars(DB_PREFIX . $table);
1548
    if (empty($safeTable) === false) {
1549
        // sanitize string
1550
        return $safeTable;
1551
    }
1552
    // stop error no table
1553
    return 'table_not_exists';
1554
}
1555
1556
/**
1557
 * GenerateCryptKey
1558
 *
1559
 * @param int     $size      Length
1560
 * @param bool $secure Secure
1561
 * @param bool $numerals Numerics
1562
 * @param bool $uppercase Uppercase letters
1563
 * @param bool $symbols Symbols
1564
 * @param bool $lowercase Lowercase
1565
 * @param array   $SETTINGS  SETTINGS
1566
 * 
1567
 * @return string
1568
 */
1569
function GenerateCryptKey(
1570
    int $size = 20,
1571
    bool $secure = false,
1572
    bool $numerals = false,
1573
    bool $uppercase = false,
1574
    bool $symbols = false,
1575
    bool $lowercase = false,
1576
    array $SETTINGS = []
1577
): string {
1578
    $generator = new ComputerPasswordGenerator();
1579
    $generator->setRandomGenerator(new Php7RandomGenerator());
1580
    
1581
    // Manage size
1582
    $generator->setLength((int) $size);
1583
    if ($secure === true) {
1584
        $generator->setSymbols(true);
1585
        $generator->setLowercase(true);
1586
        $generator->setUppercase(true);
1587
        $generator->setNumbers(true);
1588
    } else {
1589
        $generator->setLowercase($lowercase);
1590
        $generator->setUppercase($uppercase);
1591
        $generator->setNumbers($numerals);
1592
        $generator->setSymbols($symbols);
1593
    }
1594
1595
    return $generator->generatePasswords()[0];
1596
}
1597
1598
/**
1599
 * Send sysLOG message
1600
 *
1601
 * @param string    $message
1602
 * @param string    $host
1603
 * @param int       $port
1604
 * @param string    $component
1605
 * 
1606
 * @return void
1607
*/
1608
function send_syslog($message, $host, $port, $component = 'teampass'): void
1609
{
1610
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1611
    $syslog_message = '<123>' . date('M d H:i:s ') . $component . ': ' . $message;
1612
    socket_sendto($sock, (string) $syslog_message, strlen($syslog_message), 0, (string) $host, (int) $port);
1613
    socket_close($sock);
1614
}
1615
1616
/**
1617
 * Permits to log events into DB
1618
 *
1619
 * @param array  $SETTINGS Teampass settings
1620
 * @param string $type     Type
1621
 * @param string $label    Label
1622
 * @param string $who      Who
1623
 * @param string $login    Login
1624
 * @param string|int $field_1  Field
1625
 * 
1626
 * @return void
1627
 */
1628
function logEvents(
1629
    array $SETTINGS, 
1630
    string $type, 
1631
    string $label, 
1632
    string $who, 
1633
    ?string $login = null, 
1634
    $field_1 = null
1635
): void
1636
{
1637
    if (empty($who)) {
1638
        $who = getClientIpServer();
1639
    }
1640
1641
    // Load class DB
1642
    loadClasses('DB');
1643
1644
    DB::insert(
1645
        prefixTable('log_system'),
1646
        [
1647
            'type' => $type,
1648
            'date' => time(),
1649
            'label' => $label,
1650
            'qui' => $who,
1651
            'field_1' => $field_1 === null ? '' : $field_1,
1652
        ]
1653
    );
1654
    // If SYSLOG
1655
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1656
        if ($type === 'user_mngt') {
1657
            send_syslog(
1658
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1659
                $SETTINGS['syslog_host'],
1660
                $SETTINGS['syslog_port'],
1661
                'teampass'
1662
            );
1663
        } else {
1664
            send_syslog(
1665
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1666
                $SETTINGS['syslog_host'],
1667
                $SETTINGS['syslog_port'],
1668
                'teampass'
1669
            );
1670
        }
1671
    }
1672
}
1673
1674
/**
1675
 * Log events.
1676
 *
1677
 * @param array  $SETTINGS        Teampass settings
1678
 * @param int    $item_id         Item id
1679
 * @param string $item_label      Item label
1680
 * @param int    $id_user         User id
1681
 * @param string $action          Code for reason
1682
 * @param string $login           User login
1683
 * @param string $raison          Code for reason
1684
 * @param string $encryption_type Encryption on
1685
 * @param string $time Encryption Time
1686
 * @param string $old_value       Old value
1687
 * 
1688
 * @return void
1689
 */
1690
function logItems(
1691
    array $SETTINGS,
1692
    int $item_id,
1693
    string $item_label,
1694
    int $id_user,
1695
    string $action,
1696
    ?string $login = null,
1697
    ?string $raison = null,
1698
    ?string $encryption_type = null,
1699
    ?string $time = null,
1700
    ?string $old_value = null
1701
): void {
1702
    // Load class DB
1703
    loadClasses('DB');
1704
1705
    // Insert log in DB
1706
    DB::insert(
1707
        prefixTable('log_items'),
1708
        [
1709
            'id_item' => $item_id,
1710
            'date' => is_null($time) === true ? time() : $time,
1711
            'id_user' => $id_user,
1712
            'action' => $action,
1713
            'raison' => $raison,
1714
            'old_value' => $old_value,
1715
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1716
        ]
1717
    );
1718
    // Timestamp the last change
1719
    if ($action === 'at_creation' || $action === 'at_modifiation' || $action === 'at_delete' || $action === 'at_import') {
1720
        DB::update(
1721
            prefixTable('misc'),
1722
            [
1723
                'valeur' => time(),
1724
            ],
1725
            'type = %s AND intitule = %s',
1726
            'timestamp',
1727
            'last_item_change'
1728
        );
1729
    }
1730
1731
    // SYSLOG
1732
    if (isset($SETTINGS['syslog_enable']) === true && $SETTINGS['syslog_enable'] === '1') {
1733
        // Extract reason
1734
        $attribute = is_null($raison) === true ? Array('') : explode(' : ', $raison);
1735
        // Get item info if not known
1736
        if (empty($item_label) === true) {
1737
            $dataItem = DB::queryfirstrow(
1738
                'SELECT id, id_tree, label
1739
                FROM ' . prefixTable('items') . '
1740
                WHERE id = %i',
1741
                $item_id
1742
            );
1743
            $item_label = $dataItem['label'];
1744
        }
1745
1746
        send_syslog(
1747
            'action=' . str_replace('at_', '', $action) .
1748
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1749
                ' itemno=' . $item_id .
1750
                ' user=' . is_null($login) === true ? '' : addslashes((string) $login) .
1751
                ' itemname="' . addslashes($item_label) . '"',
1752
            $SETTINGS['syslog_host'],
1753
            $SETTINGS['syslog_port'],
1754
            'teampass'
1755
        );
1756
    }
1757
1758
    // send notification if enabled
1759
    //notifyOnChange($item_id, $action, $SETTINGS);
1760
}
1761
1762
/**
1763
 * Prepare notification email to subscribers.
1764
 *
1765
 * @param int    $item_id  Item id
1766
 * @param string $label    Item label
1767
 * @param array  $changes  List of changes
1768
 * @param array  $SETTINGS Teampass settings
1769
 * 
1770
 * @return void
1771
 */
1772
function notifyChangesToSubscribers(int $item_id, string $label, array $changes, array $SETTINGS): void
1773
{
1774
    $session = SessionManager::getSession();
1775
    $lang = new Language(); 
1776
    $globalsUserId = $session->get('user-id');
1777
    $globalsLastname = $session->get('user-lastname');
1778
    $globalsName = $session->get('user-name');
1779
    // send email to user that what to be notified
1780
    $notification = DB::queryOneColumn(
1781
        'email',
1782
        'SELECT *
1783
        FROM ' . prefixTable('notification') . ' AS n
1784
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1785
        WHERE n.item_id = %i AND n.user_id != %i',
1786
        $item_id,
1787
        $globalsUserId
1788
    );
1789
    if (DB::count() > 0) {
1790
        // Prepare path
1791
        $path = geItemReadablePath($item_id, '', $SETTINGS);
1792
        // Get list of changes
1793
        $htmlChanges = '<ul>';
1794
        foreach ($changes as $change) {
1795
            $htmlChanges .= '<li>' . $change . '</li>';
1796
        }
1797
        $htmlChanges .= '</ul>';
1798
        // send email
1799
        DB::insert(
1800
            prefixTable('emails'),
1801
            [
1802
                'timestamp' => time(),
1803
                'subject' => $lang->get('email_subject_item_updated'),
1804
                'body' => str_replace(
1805
                    ['#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'],
1806
                    [$label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges],
1807
                    $lang->get('email_body_item_updated')
1808
                ),
1809
                'receivers' => implode(',', $notification),
1810
                'status' => '',
1811
            ]
1812
        );
1813
    }
1814
}
1815
1816
/**
1817
 * Returns the Item + path.
1818
 *
1819
 * @param int    $id_tree  Node id
1820
 * @param string $label    Label
1821
 * @param array  $SETTINGS TP settings
1822
 * 
1823
 * @return string
1824
 */
1825
function geItemReadablePath(int $id_tree, string $label, array $SETTINGS): string
1826
{
1827
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1828
    $arbo = $tree->getPath($id_tree, true);
1829
    $path = '';
1830
    foreach ($arbo as $elem) {
1831
        if (empty($path) === true) {
1832
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
1833
        } else {
1834
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
1835
        }
1836
    }
1837
1838
    // Build text to show user
1839
    if (empty($label) === false) {
1840
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
1841
    }
1842
    return empty($path) === true ? '' : $path;
1843
}
1844
1845
/**
1846
 * Get the client ip address.
1847
 *
1848
 * @return string IP address
1849
 */
1850
function getClientIpServer(): string
1851
{
1852
    if (getenv('HTTP_CLIENT_IP')) {
1853
        $ipaddress = getenv('HTTP_CLIENT_IP');
1854
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
1855
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
1856
    } elseif (getenv('HTTP_X_FORWARDED')) {
1857
        $ipaddress = getenv('HTTP_X_FORWARDED');
1858
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
1859
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
1860
    } elseif (getenv('HTTP_FORWARDED')) {
1861
        $ipaddress = getenv('HTTP_FORWARDED');
1862
    } elseif (getenv('REMOTE_ADDR')) {
1863
        $ipaddress = getenv('REMOTE_ADDR');
1864
    } else {
1865
        $ipaddress = 'UNKNOWN';
1866
    }
1867
1868
    return $ipaddress;
1869
}
1870
1871
/**
1872
 * Escape all HTML, JavaScript, and CSS.
1873
 *
1874
 * @param string $input    The input string
1875
 * @param string $encoding Which character encoding are we using?
1876
 * 
1877
 * @return string
1878
 */
1879
function noHTML(string $input, string $encoding = 'UTF-8'): string
1880
{
1881
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
1882
}
1883
1884
/**
1885
 * Permits to handle the Teampass config file
1886
 * $action accepts "rebuild" and "update"
1887
 *
1888
 * @param string $action   Action to perform
1889
 * @param array  $SETTINGS Teampass settings
1890
 * @param string $field    Field to refresh
1891
 * @param string $value    Value to set
1892
 *
1893
 * @return string|bool
1894
 */
1895
function handleConfigFile($action, $SETTINGS, $field = null, $value = null)
1896
{
1897
    $tp_config_file = $SETTINGS['cpassman_dir'] . '/includes/config/tp.config.php';
1898
1899
    // Load class DB
1900
    loadClasses('DB');
1901
1902
    if (file_exists($tp_config_file) === false || $action === 'rebuild') {
1903
        // perform a copy
1904
        if (file_exists($tp_config_file)) {
1905
            if (! copy($tp_config_file, $tp_config_file . '.' . date('Y_m_d_His', time()))) {
1906
                return "ERROR: Could not copy file '" . $tp_config_file . "'";
1907
            }
1908
        }
1909
1910
        // regenerate
1911
        $data = [];
1912
        $data[0] = "<?php\n";
1913
        $data[1] = "global \$SETTINGS;\n";
1914
        $data[2] = "\$SETTINGS = array (\n";
1915
        $rows = DB::query(
1916
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s',
1917
            'admin'
1918
        );
1919
        foreach ($rows as $record) {
1920
            array_push($data, "    '" . $record['intitule'] . "' => '" . htmlspecialchars_decode($record['valeur'], ENT_COMPAT) . "',\n");
1921
        }
1922
        array_push($data, ");\n");
1923
        $data = array_unique($data);
1924
    // ---
1925
    } elseif ($action === 'update' && empty($field) === false) {
1926
        $data = file($tp_config_file);
1927
        $inc = 0;
1928
        $bFound = false;
1929
        foreach ($data as $line) {
1930
            if (stristr($line, ');')) {
1931
                break;
1932
            }
1933
1934
            if (stristr($line, "'" . $field . "' => '")) {
1935
                $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode($value ?? '', ENT_COMPAT) . "',\n";
1936
                $bFound = true;
1937
                break;
1938
            }
1939
            ++$inc;
1940
        }
1941
        if ($bFound === false) {
1942
            $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode($value ?? '', ENT_COMPAT). "',\n);\n";
1943
        }
1944
    }
1945
1946
    // update file
1947
    file_put_contents($tp_config_file, implode('', $data ?? []));
1948
    return true;
1949
}
1950
1951
/**
1952
 * Permits to replace &#92; to permit correct display
1953
 *
1954
 * @param string $input Some text
1955
 * 
1956
 * @return string
1957
 */
1958
function handleBackslash(string $input): string
1959
{
1960
    return str_replace('&amp;#92;', '&#92;', $input);
1961
}
1962
1963
/**
1964
 * Permits to load settings
1965
 * 
1966
 * @return void
1967
*/
1968
function loadSettings(): void
1969
{
1970
    global $SETTINGS;
1971
    /* LOAD CPASSMAN SETTINGS */
1972
    if (! isset($SETTINGS['loaded']) || $SETTINGS['loaded'] !== 1) {
1973
        $SETTINGS = [];
1974
        $SETTINGS['duplicate_folder'] = 0;
1975
        //by default, this is set to 0;
1976
        $SETTINGS['duplicate_item'] = 0;
1977
        //by default, this is set to 0;
1978
        $SETTINGS['number_of_used_pw'] = 5;
1979
        //by default, this value is set to 5;
1980
        $settings = [];
1981
        $rows = DB::query(
1982
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
1983
            [
1984
                'type' => 'admin',
1985
                'type2' => 'settings',
1986
            ]
1987
        );
1988
        foreach ($rows as $record) {
1989
            if ($record['type'] === 'admin') {
1990
                $SETTINGS[$record['intitule']] = $record['valeur'];
1991
            } else {
1992
                $settings[$record['intitule']] = $record['valeur'];
1993
            }
1994
        }
1995
        $SETTINGS['loaded'] = 1;
1996
        $SETTINGS['default_session_expiration_time'] = 5;
1997
    }
1998
}
1999
2000
/**
2001
 * check if folder has custom fields.
2002
 * Ensure that target one also has same custom fields
2003
 * 
2004
 * @param int $source_id
2005
 * @param int $target_id 
2006
 * 
2007
 * @return bool
2008
*/
2009
function checkCFconsistency(int $source_id, int $target_id): bool
2010
{
2011
    $source_cf = [];
2012
    $rows = DB::QUERY(
2013
        'SELECT id_category
2014
            FROM ' . prefixTable('categories_folders') . '
2015
            WHERE id_folder = %i',
2016
        $source_id
2017
    );
2018
    foreach ($rows as $record) {
2019
        array_push($source_cf, $record['id_category']);
2020
    }
2021
2022
    $target_cf = [];
2023
    $rows = DB::QUERY(
2024
        'SELECT id_category
2025
            FROM ' . prefixTable('categories_folders') . '
2026
            WHERE id_folder = %i',
2027
        $target_id
2028
    );
2029
    foreach ($rows as $record) {
2030
        array_push($target_cf, $record['id_category']);
2031
    }
2032
2033
    $cf_diff = array_diff($source_cf, $target_cf);
2034
    if (count($cf_diff) > 0) {
2035
        return false;
2036
    }
2037
2038
    return true;
2039
}
2040
2041
/**
2042
 * Will encrypte/decrypt a fil eusing Defuse.
2043
 *
2044
 * @param string $type        can be either encrypt or decrypt
2045
 * @param string $source_file path to source file
2046
 * @param string $target_file path to target file
2047
 * @param array  $SETTINGS    Settings
2048
 * @param string $password    A password
2049
 *
2050
 * @return string|bool
2051
 */
2052
function prepareFileWithDefuse(
2053
    string $type,
2054
    string $source_file,
2055
    string $target_file,
2056
    array $SETTINGS,
2057
    string $password = null
2058
) {
2059
    // Load AntiXSS
2060
    $antiXss = new AntiXSS();
2061
    // Protect against bad inputs
2062
    if (is_array($source_file) === true || is_array($target_file) === true) {
2063
        return 'error_cannot_be_array';
2064
    }
2065
2066
    // Sanitize
2067
    $source_file = $antiXss->xss_clean($source_file);
2068
    $target_file = $antiXss->xss_clean($target_file);
2069
    if (empty($password) === true || is_null($password) === true) {
2070
        // get KEY to define password
2071
        $ascii_key = file_get_contents(SECUREPATH.'/'.SECUREFILE);
2072
        $password = Key::loadFromAsciiSafeString($ascii_key);
2073
    }
2074
2075
    $err = '';
2076
    if ($type === 'decrypt') {
2077
        // Decrypt file
2078
        $err = defuseFileDecrypt(
2079
            $source_file,
2080
            $target_file,
2081
            $SETTINGS, /** @scrutinizer ignore-type */
2082
            $password
2083
        );
2084
    } elseif ($type === 'encrypt') {
2085
        // Encrypt file
2086
        $err = defuseFileEncrypt(
2087
            $source_file,
2088
            $target_file,
2089
            $SETTINGS, /** @scrutinizer ignore-type */
2090
            $password
2091
        );
2092
    }
2093
2094
    // return error
2095
    return $err === true ? '' : $err;
2096
}
2097
2098
/**
2099
 * Encrypt a file with Defuse.
2100
 *
2101
 * @param string $source_file path to source file
2102
 * @param string $target_file path to target file
2103
 * @param array  $SETTINGS    Settings
2104
 * @param string $password    A password
2105
 *
2106
 * @return string|bool
2107
 */
2108
function defuseFileEncrypt(
2109
    string $source_file,
2110
    string $target_file,
2111
    array $SETTINGS,
2112
    string $password = null
2113
) {
2114
    try {
2115
        CryptoFile::encryptFileWithPassword(
2116
            $source_file,
2117
            $target_file,
2118
            $password
2119
        );
2120
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
2121
        $err = 'wrong_key';
2122
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
2123
        $err = $ex;
2124
    } catch (CryptoException\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
    string $source_file,
2144
    string $target_file,
2145
    array $SETTINGS,
2146
    string $password = null
2147
) {
2148
    try {
2149
        CryptoFile::decryptFileWithPassword(
2150
            $source_file,
2151
            $target_file,
2152
            $password
2153
        );
2154
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
2155
        $err = 'wrong_key';
2156
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
2157
        $err = $ex;
2158
    } catch (CryptoException\IOException $ex) {
2159
        $err = $ex;
2160
    }
2161
2162
    // return error
2163
    return empty($err) === false ? $err : true;
2164
}
2165
2166
/*
2167
* NOT TO BE USED
2168
*/
2169
/**
2170
 * Undocumented function.
2171
 *
2172
 * @param string $text Text to debug
2173
 */
2174
function debugTeampass(string $text): void
2175
{
2176
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
2177
    if ($debugFile !== false) {
2178
        fputs($debugFile, $text);
2179
        fclose($debugFile);
2180
    }
2181
}
2182
2183
/**
2184
 * DELETE the file with expected command depending on server type.
2185
 *
2186
 * @param string $file     Path to file
2187
 * @param array  $SETTINGS Teampass settings
2188
 *
2189
 * @return void
2190
 */
2191
function fileDelete(string $file, array $SETTINGS): void
2192
{
2193
    // Load AntiXSS
2194
    $antiXss = new AntiXSS();
2195
    $file = $antiXss->xss_clean($file);
2196
    if (is_file($file)) {
2197
        unlink($file);
2198
    }
2199
}
2200
2201
/**
2202
 * Permits to extract the file extension.
2203
 *
2204
 * @param string $file File name
2205
 *
2206
 * @return string
2207
 */
2208
function getFileExtension(string $file): string
2209
{
2210
    if (strpos($file, '.') === false) {
2211
        return $file;
2212
    }
2213
2214
    return substr($file, strrpos($file, '.') + 1);
2215
}
2216
2217
/**
2218
 * Chmods files and folders with different permissions.
2219
 *
2220
 * This is an all-PHP alternative to using: \n
2221
 * <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2222
 * <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2223
 *
2224
 * @author Jeppe Toustrup (tenzer at tenzer dot dk)
2225
  *
2226
 * @param string $path      An either relative or absolute path to a file or directory which should be processed.
2227
 * @param int    $filePerm The permissions any found files should get.
2228
 * @param int    $dirPerm  The permissions any found folder should get.
2229
 *
2230
 * @return bool Returns TRUE if the path if found and FALSE if not.
2231
 *
2232
 * @warning The permission levels has to be entered in octal format, which
2233
 * normally means adding a zero ("0") in front of the permission level. \n
2234
 * More info at: http://php.net/chmod.
2235
*/
2236
2237
function recursiveChmod(
2238
    string $path,
2239
    int $filePerm = 0644,
2240
    int  $dirPerm = 0755
2241
) {
2242
    // Check if the path exists
2243
    if (! file_exists($path)) {
2244
        return false;
2245
    }
2246
2247
    // See whether this is a file
2248
    if (is_file($path)) {
2249
        // Chmod the file with our given filepermissions
2250
        try {
2251
            chmod($path, $filePerm);
2252
        } catch (Exception $e) {
2253
            return false;
2254
        }
2255
    // If this is a directory...
2256
    } elseif (is_dir($path)) {
2257
        // Then get an array of the contents
2258
        $foldersAndFiles = scandir($path);
2259
        // Remove "." and ".." from the list
2260
        $entries = array_slice($foldersAndFiles, 2);
2261
        // Parse every result...
2262
        foreach ($entries as $entry) {
2263
            // And call this function again recursively, with the same permissions
2264
            recursiveChmod($path.'/'.$entry, $filePerm, $dirPerm);
2265
        }
2266
2267
        // When we are done with the contents of the directory, we chmod the directory itself
2268
        try {
2269
            chmod($path, $filePerm);
2270
        } catch (Exception $e) {
2271
            return false;
2272
        }
2273
    }
2274
2275
    // Everything seemed to work out well, return true
2276
    return true;
2277
}
2278
2279
/**
2280
 * Check if user can access to this item.
2281
 *
2282
 * @param int   $item_id ID of item
2283
 * @param array $SETTINGS
2284
 *
2285
 * @return bool|string
2286
 */
2287
function accessToItemIsGranted(int $item_id, array $SETTINGS)
2288
{
2289
    
2290
    $session = SessionManager::getSession();
2291
    $session_groupes_visibles = $session->get('user-accessible_folders');
2292
    $session_list_restricted_folders_for_items = $session->get('system-list_restricted_folders_for_items');
2293
    // Load item data
2294
    $data = DB::queryFirstRow(
2295
        'SELECT id_tree
2296
        FROM ' . prefixTable('items') . '
2297
        WHERE id = %i',
2298
        $item_id
2299
    );
2300
    // Check if user can access this folder
2301
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2302
        // Now check if this folder is restricted to user
2303
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2304
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2305
        ) {
2306
            return 'ERR_FOLDER_NOT_ALLOWED';
2307
        }
2308
    }
2309
2310
    return true;
2311
}
2312
2313
/**
2314
 * Creates a unique key.
2315
 *
2316
 * @param int $lenght Key lenght
2317
 *
2318
 * @return string
2319
 */
2320
function uniqidReal(int $lenght = 13): string
2321
{
2322
    if (function_exists('random_bytes')) {
2323
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2324
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2325
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2326
    } else {
2327
        throw new Exception('no cryptographically secure random function available');
2328
    }
2329
2330
    return substr(bin2hex($bytes), 0, $lenght);
2331
}
2332
2333
/**
2334
 * Obfuscate an email.
2335
 *
2336
 * @param string $email Email address
2337
 *
2338
 * @return string
2339
 */
2340
function obfuscateEmail(string $email): string
2341
{
2342
    $email = explode("@", $email);
2343
    $name = $email[0];
2344
    if (strlen($name) > 3) {
2345
        $name = substr($name, 0, 2);
2346
        for ($i = 0; $i < strlen($email[0]) - 3; $i++) {
2347
            $name .= "*";
2348
        }
2349
        $name .= substr($email[0], -1, 1);
2350
    }
2351
    $host = explode(".", $email[1])[0];
2352
    if (strlen($host) > 3) {
2353
        $host = substr($host, 0, 1);
2354
        for ($i = 0; $i < strlen(explode(".", $email[1])[0]) - 2; $i++) {
2355
            $host .= "*";
2356
        }
2357
        $host .= substr(explode(".", $email[1])[0], -1, 1);
2358
    }
2359
    $email = $name . "@" . $host . "." . explode(".", $email[1])[1];
2360
    return $email;
2361
}
2362
2363
/**
2364
 * Perform a Query.
2365
 *
2366
 * @param array  $SETTINGS Teamapss settings
2367
 * @param string $fields   Fields to use
2368
 * @param string $table    Table to use
2369
 *
2370
 * @return array
2371
 */
2372
function performDBQuery(array $SETTINGS, string $fields, string $table): array
2373
{
2374
    // include librairies & connect to DB
2375
    //include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2376
2377
    // Load class DB
2378
    loadClasses('DB');
2379
    
2380
    // Insert log in DB
2381
    return DB::query(
2382
        'SELECT ' . $fields . '
2383
        FROM ' . prefixTable($table)
2384
    );
2385
}
2386
2387
/**
2388
 * Undocumented function.
2389
 *
2390
 * @param int $bytes Size of file
2391
 *
2392
 * @return string
2393
 */
2394
function formatSizeUnits(int $bytes): string
2395
{
2396
    if ($bytes >= 1073741824) {
2397
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2398
    } elseif ($bytes >= 1048576) {
2399
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2400
    } elseif ($bytes >= 1024) {
2401
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2402
    } elseif ($bytes > 1) {
2403
        $bytes .= ' bytes';
2404
    } elseif ($bytes === 1) {
2405
        $bytes .= ' byte';
2406
    } else {
2407
        $bytes = '0 bytes';
2408
    }
2409
2410
    return $bytes;
2411
}
2412
2413
/**
2414
 * Generate user pair of keys.
2415
 *
2416
 * @param string $userPwd User password
2417
 *
2418
 * @return array
2419
 */
2420
function generateUserKeys(string $userPwd): array
2421
{
2422
    //if (WIP === false) {
2423
        // Load classes
2424
        $rsa = new Crypt_RSA();
2425
        $cipher = new Crypt_AES();
2426
        // Create the private and public key
2427
        $res = $rsa->createKey(4096);
2428
        // Encrypt the privatekey
2429
        $cipher->setPassword($userPwd);
2430
        $privatekey = $cipher->encrypt($res['privatekey']);
2431
        return [
2432
            'private_key' => base64_encode($privatekey),
2433
            'public_key' => base64_encode($res['publickey']),
2434
            'private_key_clear' => base64_encode($res['privatekey']),
2435
        ];
2436
    /*} else {
2437
        // Create the keys
2438
        $keys = RSA::createKey();
2439
2440
        return [
2441
            'private_key' => base64_encode($keys->withPassword($userPwd)->toString('PKCS8')),
2442
            'public_key' => base64_encode($keys->getPublicKey()),
2443
            'private_key_clear' => base64_encode($keys->toString('PKCS8')),
2444
        ];
2445
    }*/
2446
}
2447
2448
/**
2449
 * Permits to decrypt the user's privatekey.
2450
 *
2451
 * @param string $userPwd        User password
2452
 * @param string $userPrivateKey User private key
2453
 *
2454
 * @return string|object
2455
 */
2456
function decryptPrivateKey(string $userPwd, string $userPrivateKey)
2457
{
2458
    if (empty($userPwd) === false) {
2459
        //if (WIP === false) {
2460
            // Load classes
2461
            $cipher = new Crypt_AES();
2462
            // Encrypt the privatekey
2463
            $cipher->setPassword($userPwd);
2464
            try {
2465
                return base64_encode((string) $cipher->decrypt(base64_decode($userPrivateKey)));
2466
            } catch (Exception $e) {
2467
                return $e;
2468
            }
2469
        /*} else {
2470
            //echo $userPrivateKey." ;; ".($userPwd)." ;;";
2471
            // Load and decrypt the private key
2472
            try {
2473
                $privateKey = PublicKeyLoader::loadPrivateKey(base64_decode($userPrivateKey), $userPwd)->withHash('sha1')->withMGFHash('sha1');
2474
                print_r($privateKey);
2475
                return base64_encode((string) $$privateKey);
2476
            } catch (NoKeyLoadedException $e) {
2477
                print_r($e);
2478
                return $e;
2479
            }
2480
        }*/
2481
    }
2482
    return '';
2483
}
2484
2485
/**
2486
 * Permits to encrypt the user's privatekey.
2487
 *
2488
 * @param string $userPwd        User password
2489
 * @param string $userPrivateKey User private key
2490
 *
2491
 * @return string
2492
 */
2493
function encryptPrivateKey(string $userPwd, string $userPrivateKey): string
2494
{
2495
    if (empty($userPwd) === false) {
2496
        //if (WIP === false) {
2497
            // Load classes
2498
            $cipher = new Crypt_AES();
2499
            // Encrypt the privatekey
2500
            $cipher->setPassword($userPwd);        
2501
            try {
2502
                return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2503
            } catch (Exception $e) {
2504
                return $e;
2505
            }
2506
        /*} else {
2507
            // Load the private key
2508
            $privateKey = PublicKeyLoader::load(base64_decode($userPrivateKey));
2509
2510
            try {
2511
                return base64_encode($privateKey->withPassword($userPwd));
2512
            } catch (Exception $e) {
2513
                return $e;
2514
            }
2515
        }*/
2516
    }
2517
    return '';
2518
}
2519
2520
/**
2521
 * Encrypts a string using AES.
2522
 *
2523
 * @param string $data String to encrypt
2524
 * @param string $key
2525
 *
2526
 * @return array
2527
 */
2528
function doDataEncryption(string $data, string $key = NULL): array
2529
{
2530
    //if (WIP === false) {
2531
        // Load classes
2532
        $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2533
        // Generate an object key
2534
        $objectKey = is_null($key) === true ? uniqidReal(KEY_LENGTH) : $key;
2535
        // Set it as password
2536
        $cipher->setPassword($objectKey);
2537
        return [
2538
            'encrypted' => base64_encode($cipher->encrypt($data)),
2539
            'objectKey' => base64_encode($objectKey),
2540
        ];
2541
    /*} else {
2542
2543
    }*/
2544
}
2545
2546
/**
2547
 * Decrypts a string using AES.
2548
 *
2549
 * @param string $data Encrypted data
2550
 * @param string $key  Key to uncrypt
2551
 *
2552
 * @return string
2553
 */
2554
function doDataDecryption(string $data, string $key): string
2555
{
2556
    //if (WIP === false) {
2557
        // Load classes
2558
        $cipher = new Crypt_AES();
2559
        // Set the object key
2560
        $cipher->setPassword(base64_decode($key));
2561
        return base64_encode((string) $cipher->decrypt(base64_decode($data)));
2562
    /*} else {
2563
2564
    }*/
2565
}
2566
2567
/**
2568
 * Encrypts using RSA a string using a public key.
2569
 *
2570
 * @param string $key       Key to be encrypted
2571
 * @param string $publicKey User public key
2572
 *
2573
 * @return string
2574
 */
2575
function encryptUserObjectKey(string $key, string $publicKey): string
2576
{
2577
    //if (WIP === false) {
2578
        // Load classes
2579
        $rsa = new Crypt_RSA();
2580
        $rsa->loadKey(base64_decode($publicKey));
2581
        // Encrypt
2582
        return base64_encode($rsa->encrypt(base64_decode($key)));
2583
    /*} else {
2584
2585
    }*/
2586
}
2587
2588
/**
2589
 * Decrypts using RSA an encrypted string using a private key.
2590
 *
2591
 * @param string $key        Encrypted key
2592
 * @param string $privateKey User private key
2593
 *
2594
 * @return string
2595
 */
2596
function decryptUserObjectKey(string $key, string $privateKey): string
2597
{
2598
    //if (WIP === false) {
2599
        // Load classes
2600
        $rsa = new Crypt_RSA();
2601
        $rsa->loadKey(base64_decode($privateKey));
2602
        // Decrypt
2603
        try {
2604
            $tmpValue = $rsa->decrypt(base64_decode($key));
2605
            if (is_bool($tmpValue) === false) {
0 ignored issues
show
introduced by
The condition is_bool($tmpValue) === false is always true.
Loading history...
2606
                $ret = base64_encode((string) /** @scrutinizer ignore-type */$tmpValue);
2607
            } else {
2608
                $ret = '';
2609
            }
2610
        } catch (Exception $e) {
2611
            return $e;
2612
        }
2613
        /*} else {
2614
2615
        }*/
2616
2617
    return $ret;
2618
}
2619
2620
/**
2621
 * Encrypts a file.
2622
 *
2623
 * @param string $fileInName File name
2624
 * @param string $fileInPath Path to file
2625
 *
2626
 * @return array
2627
 */
2628
function encryptFile(string $fileInName, string $fileInPath): array
2629
{
2630
    if (defined('FILE_BUFFER_SIZE') === false) {
2631
        define('FILE_BUFFER_SIZE', 128 * 1024);
2632
    }
2633
    //if (WIP === false) {
2634
        // Load classes
2635
        $cipher = new Crypt_AES();
2636
        // Generate an object key
2637
        $objectKey = uniqidReal(32);
2638
        // Set it as password
2639
        $cipher->setPassword($objectKey);
2640
        // Prevent against out of memory
2641
        $cipher->enableContinuousBuffer();
2642
        //$cipher->disablePadding();
2643
2644
        // Encrypt the file content
2645
        $plaintext = file_get_contents(
2646
            filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL)
2647
        );
2648
        $ciphertext = $cipher->encrypt($plaintext);
2649
        // Save new file
2650
        $hash = md5($plaintext);
2651
        $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2652
        file_put_contents($fileOut, $ciphertext);
2653
        unlink($fileInPath . '/' . $fileInName);
2654
        return [
2655
            'fileHash' => base64_encode($hash),
2656
            'objectKey' => base64_encode($objectKey),
2657
        ];
2658
    /*} else {
2659
2660
    }*/
2661
}
2662
2663
/**
2664
 * Decrypt a file.
2665
 *
2666
 * @param string $fileName File name
2667
 * @param string $filePath Path to file
2668
 * @param string $key      Key to use
2669
 *
2670
 * @return string
2671
 */
2672
function decryptFile(string $fileName, string $filePath, string $key): string
2673
{
2674
    if (! defined('FILE_BUFFER_SIZE')) {
2675
        define('FILE_BUFFER_SIZE', 128 * 1024);
2676
    }
2677
    
2678
    // Get file name
2679
    $fileName = base64_decode($fileName);
2680
2681
    //if (WIP === false) {
2682
        // Load classes
2683
        $cipher = new Crypt_AES();
2684
        // Set the object key
2685
        $cipher->setPassword(base64_decode($key));
2686
        // Prevent against out of memory
2687
        $cipher->enableContinuousBuffer();
2688
        $cipher->disablePadding();
2689
        // Get file content
2690
        $ciphertext = file_get_contents($filePath . '/' . TP_FILE_PREFIX . $fileName);
2691
        // Decrypt file content and return
2692
        return base64_encode($cipher->decrypt($ciphertext));
2693
    /*} else {
2694
        
2695
    }*/
2696
}
2697
2698
/**
2699
 * Generate a simple password
2700
 *
2701
 * @param int $length Length of string
2702
 * @param bool $symbolsincluded Allow symbols
2703
 *
2704
 * @return string
2705
 */
2706
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2707
{
2708
    // Generate new user password
2709
    $small_letters = range('a', 'z');
2710
    $big_letters = range('A', 'Z');
2711
    $digits = range(0, 9);
2712
    $symbols = $symbolsincluded === true ?
2713
        ['#', '_', '-', '@', '$', '+', '!'] : [];
2714
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2715
    $count = count($res);
2716
    // first variant
2717
2718
    $random_string = '';
2719
    for ($i = 0; $i < $length; ++$i) {
2720
        $random_string .= $res[random_int(0, $count - 1)];
2721
    }
2722
2723
    return $random_string;
2724
}
2725
2726
/**
2727
 * Permit to store the sharekey of an object for users.
2728
 *
2729
 * @param string $object_name             Type for table selection
2730
 * @param int    $post_folder_is_personal Personal
2731
 * @param int    $post_folder_id          Folder
2732
 * @param int    $post_object_id          Object
2733
 * @param string $objectKey               Object key
2734
 * @param array  $SETTINGS                Teampass settings
2735
 * @param int    $user_id                 User ID if needed
2736
 * @param bool   $onlyForUser                 User ID if needed
2737
 * @param bool   $deleteAll                 User ID if needed
2738
 * @param array  $objectKeyArray                 User ID if needed
2739
 *
2740
 * @return void
2741
 */
2742
function storeUsersShareKey(
2743
    string $object_name,
2744
    int $post_folder_is_personal,
2745
    int $post_folder_id,
2746
    int $post_object_id,
2747
    string $objectKey,
2748
    array $SETTINGS,
2749
    bool $onlyForUser = false,
2750
    bool $deleteAll = true,
2751
    array $objectKeyArray = []
2752
): void {
2753
    
2754
    $session = SessionManager::getSession();
2755
    loadClasses('DB');
2756
2757
    // Delete existing entries for this object
2758
    if ($deleteAll === true) {
2759
        DB::delete(
2760
            $object_name,
2761
            'object_id = %i',
2762
            $post_object_id
2763
        );
2764
    }
2765
    
2766
    if (
2767
        $onlyForUser === true || (int) $post_folder_is_personal === 1
2768
    ) {
2769
        // Only create the sharekey for a user
2770
        $user = DB::queryFirstRow(
2771
            'SELECT public_key
2772
            FROM ' . prefixTable('users') . '
2773
            WHERE id = ' . (int) $session->get('user-id') . '
2774
            AND public_key != ""'
2775
        );
2776
2777
        if (empty($objectKey) === false) {
2778
            DB::insert(
2779
                $object_name,
2780
                [
2781
                    'object_id' => (int) $post_object_id,
2782
                    'user_id' => (int) $session->get('user-id'),
2783
                    'share_key' => encryptUserObjectKey(
2784
                        $objectKey,
2785
                        $user['public_key']
2786
                    ),
2787
                ]
2788
            );
2789
        } else if (count($objectKeyArray) > 0) {
2790
            foreach ($objectKeyArray as $object) {
2791
                DB::insert(
2792
                    $object_name,
2793
                    [
2794
                        'object_id' => (int) $object['objectId'],
2795
                        'user_id' => (int) $session->get('user-id'),
2796
                        'share_key' => encryptUserObjectKey(
2797
                            $object['objectKey'],
2798
                            $user['public_key']
2799
                        ),
2800
                    ]
2801
                );
2802
            }
2803
        }
2804
    } else {
2805
        // Create sharekey for each user
2806
        //DB::debugmode(true);
2807
        $users = DB::query(
2808
            'SELECT id, public_key
2809
            FROM ' . prefixTable('users') . '
2810
            WHERE ' . ($onlyForUser === true ? 
0 ignored issues
show
introduced by
The condition $onlyForUser === true is always false.
Loading history...
2811
                'id IN ("' . TP_USER_ID . '","' . $session->get('user-id') . '") ' : 
2812
                'id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '") ') . '
2813
            AND public_key != ""'
2814
        );
2815
        //DB::debugmode(false);
2816
        foreach ($users as $user) {
2817
            // Insert in DB the new object key for this item by user
2818
            if (count($objectKeyArray) === 0) {
2819
                DB::insert(
2820
                    $object_name,
2821
                    [
2822
                        'object_id' => $post_object_id,
2823
                        'user_id' => (int) $user['id'],
2824
                        'share_key' => encryptUserObjectKey(
2825
                            $objectKey,
2826
                            $user['public_key']
2827
                        ),
2828
                    ]
2829
                );
2830
            } else {
2831
                foreach ($objectKeyArray as $object) {
2832
                    DB::insert(
2833
                        $object_name,
2834
                        [
2835
                            'object_id' => (int) $object['objectId'],
2836
                            'user_id' => (int) $user['id'],
2837
                            'share_key' => encryptUserObjectKey(
2838
                                $object['objectKey'],
2839
                                $user['public_key']
2840
                            ),
2841
                        ]
2842
                    );
2843
                }
2844
            }
2845
        }
2846
    }
2847
}
2848
2849
/**
2850
 * Is this string base64 encoded?
2851
 *
2852
 * @param string $str Encoded string?
2853
 *
2854
 * @return bool
2855
 */
2856
function isBase64(string $str): bool
2857
{
2858
    $str = (string) trim($str);
2859
    if (! isset($str[0])) {
2860
        return false;
2861
    }
2862
2863
    $base64String = (string) base64_decode($str, true);
2864
    if ($base64String && base64_encode($base64String) === $str) {
2865
        return true;
2866
    }
2867
2868
    return false;
2869
}
2870
2871
/**
2872
 * Undocumented function
2873
 *
2874
 * @param string $field Parameter
2875
 *
2876
 * @return array|bool|resource|string
2877
 */
2878
function filterString(string $field)
2879
{
2880
    // Sanitize string
2881
    $field = filter_var(trim($field), FILTER_SANITIZE_FULL_SPECIAL_CHARS);
2882
    if (empty($field) === false) {
2883
        // Load AntiXSS
2884
        $antiXss = new AntiXSS();
2885
        // Return
2886
        return $antiXss->xss_clean($field);
2887
    }
2888
2889
    return false;
2890
}
2891
2892
/**
2893
 * CHeck if provided credentials are allowed on server
2894
 *
2895
 * @param string $login    User Login
2896
 * @param string $password User Pwd
2897
 * @param array  $SETTINGS Teampass settings
2898
 *
2899
 * @return bool
2900
 */
2901
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
2902
{
2903
    // Build ldap configuration array
2904
    $config = [
2905
        // Mandatory Configuration Options
2906
        'hosts' => [$SETTINGS['ldap_hosts']],
2907
        'base_dn' => $SETTINGS['ldap_bdn'],
2908
        'username' => $SETTINGS['ldap_username'],
2909
        'password' => $SETTINGS['ldap_password'],
2910
2911
        // Optional Configuration Options
2912
        'port' => $SETTINGS['ldap_port'],
2913
        'use_ssl' => (int) $SETTINGS['ldap_ssl'] === 1 ? true : false,
2914
        'use_tls' => (int) $SETTINGS['ldap_tls'] === 1 ? true : false,
2915
        'version' => 3,
2916
        'timeout' => 5,
2917
        'follow_referrals' => false,
2918
2919
        // Custom LDAP Options
2920
        'options' => [
2921
            // See: http://php.net/ldap_set_option
2922
            LDAP_OPT_X_TLS_REQUIRE_CERT => (isset($SETTINGS['ldap_tls_certiface_check']) ? $SETTINGS['ldap_tls_certiface_check'] : LDAP_OPT_X_TLS_HARD),
2923
        ],
2924
    ];
2925
    
2926
    $connection = new Connection($config);
2927
    // Connect to LDAP
2928
    try {
2929
        $connection->connect();
2930
    } catch (\LdapRecord\Auth\BindException $e) {
2931
        $error = $e->getDetailedError();
2932
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
2933
        return false;
2934
    }
2935
2936
    // Authenticate user
2937
    try {
2938
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory') {
2939
            $connection->auth()->attempt($login, $password, $stayAuthenticated = true);
2940
        } else {
2941
            $connection->auth()->attempt($SETTINGS['ldap_user_attribute'].'='.$login.','.(isset($SETTINGS['ldap_dn_additional_user_dn']) && !empty($SETTINGS['ldap_dn_additional_user_dn']) ? $SETTINGS['ldap_dn_additional_user_dn'].',' : '').$SETTINGS['ldap_bdn'], $password, $stayAuthenticated = true);
2942
        }
2943
    } catch (\LdapRecord\Auth\BindException $e) {
2944
        $error = $e->getDetailedError();
2945
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
2946
        return false;
2947
    }
2948
2949
    return true;
2950
}
2951
2952
/**
2953
 * Removes from DB all sharekeys of this user
2954
 *
2955
 * @param int $userId User's id
2956
 * @param array   $SETTINGS Teampass settings
2957
 *
2958
 * @return bool
2959
 */
2960
function deleteUserObjetsKeys(int $userId, array $SETTINGS = []): bool
2961
{
2962
    // Load class DB
2963
    loadClasses('DB');
2964
2965
    // Remove all item sharekeys items
2966
    // expect if personal item
2967
    DB::delete(
2968
        prefixTable('sharekeys_items'),
2969
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2970
        $userId
2971
    );
2972
    // Remove all item sharekeys files
2973
    DB::delete(
2974
        prefixTable('sharekeys_files'),
2975
        'user_id = %i AND object_id NOT IN (
2976
            SELECT f.id 
2977
            FROM ' . prefixTable('items') . ' AS i 
2978
            INNER JOIN ' . prefixTable('files') . ' AS f ON f.id_item = i.id
2979
            WHERE i.perso = 1
2980
        )',
2981
        $userId
2982
    );
2983
    // Remove all item sharekeys fields
2984
    DB::delete(
2985
        prefixTable('sharekeys_fields'),
2986
        'user_id = %i AND object_id NOT IN (
2987
            SELECT c.id 
2988
            FROM ' . prefixTable('items') . ' AS i 
2989
            INNER JOIN ' . prefixTable('categories_items') . ' AS c ON c.item_id = i.id
2990
            WHERE i.perso = 1
2991
        )',
2992
        $userId
2993
    );
2994
    // Remove all item sharekeys logs
2995
    DB::delete(
2996
        prefixTable('sharekeys_logs'),
2997
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2998
        $userId
2999
    );
3000
    // Remove all item sharekeys suggestions
3001
    DB::delete(
3002
        prefixTable('sharekeys_suggestions'),
3003
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
3004
        $userId
3005
    );
3006
    return false;
3007
}
3008
3009
/**
3010
 * Manage list of timezones   $SETTINGS Teampass settings
3011
 *
3012
 * @return array
3013
 */
3014
function timezone_list()
3015
{
3016
    static $timezones = null;
3017
    if ($timezones === null) {
3018
        $timezones = [];
3019
        $offsets = [];
3020
        $now = new DateTime('now', new DateTimeZone('UTC'));
3021
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
3022
            $now->setTimezone(new DateTimeZone($timezone));
3023
            $offsets[] = $offset = $now->getOffset();
3024
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
3025
        }
3026
3027
        array_multisort($offsets, $timezones);
3028
    }
3029
3030
    return $timezones;
3031
}
3032
3033
/**
3034
 * Provide timezone offset
3035
 *
3036
 * @param int $offset Timezone offset
3037
 *
3038
 * @return string
3039
 */
3040
function format_GMT_offset($offset): string
3041
{
3042
    $hours = intval($offset / 3600);
3043
    $minutes = abs(intval($offset % 3600 / 60));
3044
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
3045
}
3046
3047
/**
3048
 * Provides timezone name
3049
 *
3050
 * @param string $name Timezone name
3051
 *
3052
 * @return string
3053
 */
3054
function format_timezone_name($name): string
3055
{
3056
    $name = str_replace('/', ', ', $name);
3057
    $name = str_replace('_', ' ', $name);
3058
3059
    return str_replace('St ', 'St. ', $name);
3060
}
3061
3062
/**
3063
 * Provides info if user should use MFA based on roles
3064
 *
3065
 * @param string $userRolesIds  User roles ids
3066
 * @param string $mfaRoles      Roles for which MFA is requested
3067
 *
3068
 * @return bool
3069
 */
3070
function mfa_auth_requested_roles(string $userRolesIds, string $mfaRoles): bool
3071
{
3072
    if (empty($mfaRoles) === true) {
3073
        return true;
3074
    }
3075
3076
    $mfaRoles = array_values(json_decode($mfaRoles, true));
3077
    $userRolesIds = array_filter(explode(';', $userRolesIds));
3078
    if (count($mfaRoles) === 0 || count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
3079
        return true;
3080
    }
3081
3082
    return false;
3083
}
3084
3085
/**
3086
 * Permits to clean a string for export purpose
3087
 *
3088
 * @param string $text
3089
 * @param bool $emptyCheckOnly
3090
 * 
3091
 * @return string
3092
 */
3093
function cleanStringForExport(string $text, bool $emptyCheckOnly = false): string
3094
{
3095
    if (is_null($text) === true || empty($text) === true) {
3096
        return '';
3097
    }
3098
    // only expected to check if $text was empty
3099
    elseif ($emptyCheckOnly === true) {
3100
        return $text;
3101
    }
3102
3103
    return strip_tags(
3104
        cleanString(
3105
            html_entity_decode($text, ENT_QUOTES | ENT_XHTML, 'UTF-8'),
3106
            true)
3107
        );
3108
}
3109
3110
/**
3111
 * Permits to check if user ID is valid
3112
 *
3113
 * @param integer $post_user_id
3114
 * @return bool
3115
 */
3116
function isUserIdValid($userId): bool
3117
{
3118
    if (is_null($userId) === false
3119
        && isset($userId) === true
3120
        && empty($userId) === false
3121
    ) {
3122
        return true;
3123
    }
3124
    return false;
3125
}
3126
3127
/**
3128
 * Check if a key exists and if its value equal the one expected
3129
 *
3130
 * @param string $key
3131
 * @param integer|string $value
3132
 * @param array $array
3133
 * 
3134
 * @return boolean
3135
 */
3136
function isKeyExistingAndEqual(
3137
    string $key,
3138
    /*PHP8 - integer|string*/$value,
3139
    array $array
3140
): bool
3141
{
3142
    if (isset($array[$key]) === true
3143
        && (is_int($value) === true ?
3144
            (int) $array[$key] === $value :
3145
            (string) $array[$key] === $value)
3146
    ) {
3147
        return true;
3148
    }
3149
    return false;
3150
}
3151
3152
/**
3153
 * Check if a variable is not set or equal to a value
3154
 *
3155
 * @param string|null $var
3156
 * @param integer|string $value
3157
 * 
3158
 * @return boolean
3159
 */
3160
function isKeyNotSetOrEqual(
3161
    /*PHP8 - string|null*/$var,
3162
    /*PHP8 - integer|string*/$value
3163
): bool
3164
{
3165
    if (isset($var) === false
3166
        || (is_int($value) === true ?
3167
            (int) $var === $value :
3168
            (string) $var === $value)
3169
    ) {
3170
        return true;
3171
    }
3172
    return false;
3173
}
3174
3175
/**
3176
 * Check if a key exists and if its value < to the one expected
3177
 *
3178
 * @param string $key
3179
 * @param integer $value
3180
 * @param array $array
3181
 * 
3182
 * @return boolean
3183
 */
3184
function isKeyExistingAndInferior(string $key, int $value, array $array): bool
3185
{
3186
    if (isset($array[$key]) === true && (int) $array[$key] < $value) {
3187
        return true;
3188
    }
3189
    return false;
3190
}
3191
3192
/**
3193
 * Check if a key exists and if its value > to the one expected
3194
 *
3195
 * @param string $key
3196
 * @param integer $value
3197
 * @param array $array
3198
 * 
3199
 * @return boolean
3200
 */
3201
function isKeyExistingAndSuperior(string $key, int $value, array $array): bool
3202
{
3203
    if (isset($array[$key]) === true && (int) $array[$key] > $value) {
3204
        return true;
3205
    }
3206
    return false;
3207
}
3208
3209
/**
3210
 * Check if values in array are set
3211
 * Return true if all set
3212
 * Return false if one of them is not set
3213
 *
3214
 * @param array $arrayOfValues
3215
 * @return boolean
3216
 */
3217
function isSetArrayOfValues(array $arrayOfValues): bool
3218
{
3219
    foreach($arrayOfValues as $value) {
3220
        if (isset($value) === false) {
3221
            return false;
3222
        }
3223
    }
3224
    return true;
3225
}
3226
3227
/**
3228
 * Check if values in array are set
3229
 * Return true if all set
3230
 * Return false if one of them is not set
3231
 *
3232
 * @param array $arrayOfValues
3233
 * @param integer|string $value
3234
 * @return boolean
3235
 */
3236
function isArrayOfVarsEqualToValue(
3237
    array $arrayOfVars,
3238
    /*PHP8 - integer|string*/$value
3239
) : bool
3240
{
3241
    foreach($arrayOfVars as $variable) {
3242
        if ($variable !== $value) {
3243
            return false;
3244
        }
3245
    }
3246
    return true;
3247
}
3248
3249
/**
3250
 * Checks if at least one variable in array is equal to value
3251
 *
3252
 * @param array $arrayOfValues
3253
 * @param integer|string $value
3254
 * @return boolean
3255
 */
3256
function isOneVarOfArrayEqualToValue(
3257
    array $arrayOfVars,
3258
    /*PHP8 - integer|string*/$value
3259
) : bool
3260
{
3261
    foreach($arrayOfVars as $variable) {
3262
        if ($variable === $value) {
3263
            return true;
3264
        }
3265
    }
3266
    return false;
3267
}
3268
3269
/**
3270
 * Checks is value is null, not set OR empty
3271
 *
3272
 * @param string|int|null $value
3273
 * @return boolean
3274
 */
3275
function isValueSetNullEmpty(/*PHP8 - string|int|null*/ $value) : bool
3276
{
3277
    if (is_null($value) === true || isset($value) === false || empty($value) === true) {
3278
        return true;
3279
    }
3280
    return false;
3281
}
3282
3283
/**
3284
 * Checks if value is set and if empty is equal to passed boolean
3285
 *
3286
 * @param string|int $value
3287
 * @param boolean $boolean
3288
 * @return boolean
3289
 */
3290
function isValueSetEmpty($value, $boolean = true) : bool
3291
{
3292
    if (isset($value) === true && empty($value) === $boolean) {
3293
        return true;
3294
    }
3295
    return false;
3296
}
3297
3298
/**
3299
 * Ensure Complexity is translated
3300
 *
3301
 * @return void
3302
 */
3303
function defineComplexity() : void
3304
{
3305
    // Load user's language
3306
    $lang = new Language(); 
3307
    
3308
    if (defined('TP_PW_COMPLEXITY') === false) {
3309
        define(
3310
            'TP_PW_COMPLEXITY',
3311
            [
3312
                TP_PW_STRENGTH_1 => array(TP_PW_STRENGTH_1, $lang->get('complex_level1'), 'fas fa-thermometer-empty text-danger'),
3313
                TP_PW_STRENGTH_2 => array(TP_PW_STRENGTH_2, $lang->get('complex_level2'), 'fas fa-thermometer-quarter text-warning'),
3314
                TP_PW_STRENGTH_3 => array(TP_PW_STRENGTH_3, $lang->get('complex_level3'), 'fas fa-thermometer-half text-warning'),
3315
                TP_PW_STRENGTH_4 => array(TP_PW_STRENGTH_4, $lang->get('complex_level4'), 'fas fa-thermometer-three-quarters text-success'),
3316
                TP_PW_STRENGTH_5 => array(TP_PW_STRENGTH_5, $lang->get('complex_level5'), 'fas fa-thermometer-full text-success'),
3317
            ]
3318
        );
3319
    }
3320
}
3321
3322
/**
3323
 * Uses Sanitizer to perform data sanitization
3324
 *
3325
 * @param array     $data
3326
 * @param array     $filters
3327
 * @return array|string
3328
 */
3329
function dataSanitizer(array $data, array $filters): array|string
3330
{
3331
    // Load Sanitizer library
3332
    $sanitizer = new Sanitizer($data, $filters);
3333
3334
    // Load AntiXSS
3335
    $antiXss = new AntiXSS();
3336
3337
    // Sanitize post and get variables
3338
    return $antiXss->xss_clean($sanitizer->sanitize());
3339
}
3340
3341
/**
3342
 * Permits to manage the cache tree for a user
3343
 *
3344
 * @param integer $user_id
3345
 * @param string $data
3346
 * @param array $SETTINGS
3347
 * @param string $field_update
3348
 * @return void
3349
 */
3350
function cacheTreeUserHandler(int $user_id, string $data, array $SETTINGS, string $field_update = '')
3351
{
3352
    // Load class DB
3353
    loadClasses('DB');
3354
3355
    // Exists ?
3356
    $userCacheId = DB::queryfirstrow(
3357
        'SELECT increment_id
3358
        FROM ' . prefixTable('cache_tree') . '
3359
        WHERE user_id = %i',
3360
        $user_id
3361
    );
3362
    
3363
    if (is_null($userCacheId) === true || count($userCacheId) === 0) {
3364
        // insert in table
3365
        DB::insert(
3366
            prefixTable('cache_tree'),
3367
            array(
3368
                'data' => $data,
3369
                'timestamp' => time(),
3370
                'user_id' => $user_id,
3371
                'visible_folders' => '',
3372
            )
3373
        );
3374
    } else {
3375
        if (empty($field_update) === true) {
3376
            DB::update(
3377
                prefixTable('cache_tree'),
3378
                [
3379
                    'timestamp' => time(),
3380
                    'data' => $data,
3381
                ],
3382
                'increment_id = %i',
3383
                $userCacheId['increment_id']
3384
            );
3385
        /* USELESS
3386
        } else {
3387
            DB::update(
3388
                prefixTable('cache_tree'),
3389
                [
3390
                    $field_update => $data,
3391
                ],
3392
                'increment_id = %i',
3393
                $userCacheId['increment_id']
3394
            );*/
3395
        }
3396
    }
3397
}
3398
3399
/**
3400
 * Permits to calculate a %
3401
 *
3402
 * @param float $nombre
3403
 * @param float $total
3404
 * @param float $pourcentage
3405
 * @return float
3406
 */
3407
function pourcentage(float $nombre, float $total, float $pourcentage): float
3408
{ 
3409
    $resultat = ($nombre/$total) * $pourcentage;
3410
    return round($resultat);
3411
}
3412
3413
/**
3414
 * Load the folders list from the cache
3415
 *
3416
 * @param string $fieldName
3417
 * @param string $sessionName
3418
 * @param boolean $forceRefresh
3419
 * @return array
3420
 */
3421
function loadFoldersListByCache(
3422
    string $fieldName,
3423
    string $sessionName,
3424
    bool $forceRefresh = false
3425
): array
3426
{
3427
    // Case when refresh is EXPECTED / MANDATORY
3428
    if ($forceRefresh === true) {
3429
        return [
3430
            'state' => false,
3431
            'data' => [],
3432
        ];
3433
    }
3434
    
3435
    $session = SessionManager::getSession();
3436
3437
    // Get last folder update
3438
    $lastFolderChange = DB::queryfirstrow(
3439
        'SELECT valeur FROM ' . prefixTable('misc') . '
3440
        WHERE type = %s AND intitule = %s',
3441
        'timestamp',
3442
        'last_folder_change'
3443
    );
3444
    if (DB::count() === 0) {
3445
        $lastFolderChange['valeur'] = 0;
3446
    }
3447
3448
    // Case when an update in the tree has been done
3449
    // Refresh is then mandatory
3450
    if ((int) $lastFolderChange['valeur'] > (int) (null !== $session->get('user-tree_last_refresh_timestamp') ? $session->get('user-tree_last_refresh_timestamp') : 0)) {
3451
        return [
3452
            'state' => false,
3453
            'data' => [],
3454
        ];
3455
    }
3456
3457
    // Does this user has the tree structure in session?
3458
    // If yes then use it
3459
    if (count(null !== $session->get('user-folders_list') ? $session->get('user-folders_list') : []) > 0) {
3460
        return [
3461
            'state' => true,
3462
            'data' => json_encode($session->get('user-folders_list')),
3463
        ];
3464
    }
3465
3466
    // Does this user has a tree cache
3467
    $userCacheTree = DB::queryfirstrow(
3468
        'SELECT '.$fieldName.'
3469
        FROM ' . prefixTable('cache_tree') . '
3470
        WHERE user_id = %i',
3471
        $session->get('user-id')
3472
    );
3473
    if (empty($userCacheTree[$fieldName]) === false && $userCacheTree[$fieldName] !== '[]') {
3474
        SessionManager::addRemoveFromSessionAssociativeArray(
3475
            'user-folders_list',
3476
            [$userCacheTree[$fieldName]],
3477
            'add'
3478
        );
3479
        return [
3480
            'state' => true,
3481
            'data' => $userCacheTree[$fieldName],
3482
        ];
3483
    }
3484
3485
    return [
3486
        'state' => false,
3487
        'data' => [],
3488
    ];
3489
}
3490
3491
3492
/**
3493
 * Permits to refresh the categories of folders
3494
 *
3495
 * @param array $folderIds
3496
 * @return void
3497
 */
3498
function handleFoldersCategories(
3499
    array $folderIds
3500
)
3501
{
3502
    // Load class DB
3503
    loadClasses('DB');
3504
3505
    $arr_data = array();
3506
3507
    // force full list of folders
3508
    if (count($folderIds) === 0) {
3509
        $folderIds = DB::queryFirstColumn(
3510
            'SELECT id
3511
            FROM ' . prefixTable('nested_tree') . '
3512
            WHERE personal_folder=%i',
3513
            0
3514
        );
3515
    }
3516
3517
    // Get complexity
3518
    defineComplexity();
3519
3520
    // update
3521
    foreach ($folderIds as $folder) {
3522
        // Do we have Categories
3523
        // get list of associated Categories
3524
        $arrCatList = array();
3525
        $rows_tmp = DB::query(
3526
            'SELECT c.id, c.title, c.level, c.type, c.masked, c.order, c.encrypted_data, c.role_visibility, c.is_mandatory,
3527
            f.id_category AS category_id
3528
            FROM ' . prefixTable('categories_folders') . ' AS f
3529
            INNER JOIN ' . prefixTable('categories') . ' AS c ON (f.id_category = c.parent_id)
3530
            WHERE id_folder=%i',
3531
            $folder
3532
        );
3533
        if (DB::count() > 0) {
3534
            foreach ($rows_tmp as $row) {
3535
                $arrCatList[$row['id']] = array(
3536
                    'id' => $row['id'],
3537
                    'title' => $row['title'],
3538
                    'level' => $row['level'],
3539
                    'type' => $row['type'],
3540
                    'masked' => $row['masked'],
3541
                    'order' => $row['order'],
3542
                    'encrypted_data' => $row['encrypted_data'],
3543
                    'role_visibility' => $row['role_visibility'],
3544
                    'is_mandatory' => $row['is_mandatory'],
3545
                    'category_id' => $row['category_id'],
3546
                );
3547
            }
3548
        }
3549
        $arr_data['categories'] = $arrCatList;
3550
3551
        // Now get complexity
3552
        $valTemp = '';
3553
        $data = DB::queryFirstRow(
3554
            'SELECT valeur
3555
            FROM ' . prefixTable('misc') . '
3556
            WHERE type = %s AND intitule=%i',
3557
            'complex',
3558
            $folder
3559
        );
3560
        if (DB::count() > 0 && empty($data['valeur']) === false) {
3561
            $valTemp = array(
3562
                'value' => $data['valeur'],
3563
                'text' => TP_PW_COMPLEXITY[$data['valeur']][1],
3564
            );
3565
        }
3566
        $arr_data['complexity'] = $valTemp;
3567
3568
        // Now get Roles
3569
        $valTemp = '';
3570
        $rows_tmp = DB::query(
3571
            'SELECT t.title
3572
            FROM ' . prefixTable('roles_values') . ' as v
3573
            INNER JOIN ' . prefixTable('roles_title') . ' as t ON (v.role_id = t.id)
3574
            WHERE v.folder_id = %i
3575
            GROUP BY title',
3576
            $folder
3577
        );
3578
        foreach ($rows_tmp as $record) {
3579
            $valTemp .= (empty($valTemp) === true ? '' : ' - ') . $record['title'];
3580
        }
3581
        $arr_data['visibilityRoles'] = $valTemp;
3582
3583
        // now save in DB
3584
        DB::update(
3585
            prefixTable('nested_tree'),
3586
            array(
3587
                'categories' => json_encode($arr_data),
3588
            ),
3589
            'id = %i',
3590
            $folder
3591
        );
3592
    }
3593
}
3594
3595
/**
3596
 * List all users that have specific roles
3597
 *
3598
 * @param array $roles
3599
 * @return array
3600
 */
3601
function getUsersWithRoles(
3602
    array $roles
3603
): array
3604
{
3605
    $session = SessionManager::getSession();
3606
    $arrUsers = array();
3607
3608
    foreach ($roles as $role) {
3609
        // loop on users and check if user has this role
3610
        $rows = DB::query(
3611
            'SELECT id, fonction_id
3612
            FROM ' . prefixTable('users') . '
3613
            WHERE id != %i AND admin = 0 AND fonction_id IS NOT NULL AND fonction_id != ""',
3614
            $session->get('user-id')
3615
        );
3616
        foreach ($rows as $user) {
3617
            $userRoles = is_null($user['fonction_id']) === false && empty($user['fonction_id']) === false ? explode(';', $user['fonction_id']) : [];
3618
            if (in_array($role, $userRoles, true) === true) {
3619
                array_push($arrUsers, $user['id']);
3620
            }
3621
        }
3622
    }
3623
3624
    return $arrUsers;
3625
}
3626
3627
3628
/**
3629
 * Get all users informations
3630
 *
3631
 * @param integer $userId
3632
 * @return array
3633
 */
3634
function getFullUserInfos(
3635
    int $userId
3636
): array
3637
{
3638
    if (empty($userId) === true) {
3639
        return array();
3640
    }
3641
3642
    $val = DB::queryfirstrow(
3643
        'SELECT *
3644
        FROM ' . prefixTable('users') . '
3645
        WHERE id = %i',
3646
        $userId
3647
    );
3648
3649
    return $val;
3650
}
3651
3652
/**
3653
 * Is required an upgrade
3654
 *
3655
 * @return boolean
3656
 */
3657
function upgradeRequired(): bool
3658
{
3659
    // Get settings.php
3660
    include_once __DIR__. '/../includes/config/settings.php';
3661
3662
    // Get timestamp in DB
3663
    $val = DB::queryfirstrow(
3664
        'SELECT valeur
3665
        FROM ' . prefixTable('misc') . '
3666
        WHERE type = %s AND intitule = %s',
3667
        'admin',
3668
        'upgrade_timestamp'
3669
    );
3670
    
3671
    // if not exists then error
3672
    if (is_null($val) === true || count($val) === 0 || defined('UPGRADE_MIN_DATE') === false) return true;
3673
3674
    // if empty or too old then error
3675
    if (empty($val['valeur']) === true || (int) $val['valeur'] < (int) UPGRADE_MIN_DATE) {
3676
        return true;
3677
    }
3678
3679
    return false;
3680
}
3681
3682
/**
3683
 * Permits to change the user keys on his demand
3684
 *
3685
 * @param integer $userId
3686
 * @param string $passwordClear
3687
 * @param integer $nbItemsToTreat
3688
 * @param string $encryptionKey
3689
 * @param boolean $deleteExistingKeys
3690
 * @param boolean $sendEmailToUser
3691
 * @param boolean $encryptWithUserPassword
3692
 * @param boolean $generate_user_new_password
3693
 * @param string $emailBody
3694
 * @param boolean $user_self_change
3695
 * @param string $recovery_public_key
3696
 * @param string $recovery_private_key
3697
 * @return string
3698
 */
3699
function handleUserKeys(
3700
    int $userId,
3701
    string $passwordClear,
3702
    int $nbItemsToTreat,
3703
    string $encryptionKey = '',
3704
    bool $deleteExistingKeys = false,
3705
    bool $sendEmailToUser = true,
3706
    bool $encryptWithUserPassword = false,
3707
    bool $generate_user_new_password = false,
3708
    string $emailBody = '',
3709
    bool $user_self_change = false,
3710
    string $recovery_public_key = '',
3711
    string $recovery_private_key = ''
3712
): string
3713
{
3714
    $session = SessionManager::getSession();
3715
    $lang = new Language(); 
3716
3717
    // prepapre background tasks for item keys generation        
3718
    $userTP = DB::queryFirstRow(
3719
        'SELECT pw, public_key, private_key
3720
        FROM ' . prefixTable('users') . '
3721
        WHERE id = %i',
3722
        TP_USER_ID
3723
    );
3724
    if (DB::count() === 0) {
3725
        return prepareExchangedData(
3726
            array(
3727
                'error' => true,
3728
                'message' => 'User not exists',
3729
            ),
3730
            'encode'
3731
        );
3732
    }
3733
3734
    // Do we need to generate new user password
3735
    if ($generate_user_new_password === true) {
3736
        // Generate a new password
3737
        $passwordClear = GenerateCryptKey(20, false, true, true, false, true);
3738
    }
3739
3740
    // Hash the password
3741
    $pwdlib = new PasswordLib();
3742
    $hashedPassword = $pwdlib->createPasswordHash($passwordClear);
3743
    if ($pwdlib->verifyPasswordHash($passwordClear, $hashedPassword) === false) {
3744
        return prepareExchangedData(
3745
            array(
3746
                'error' => true,
3747
                'message' => $lang->get('pw_hash_not_correct'),
3748
            ),
3749
            'encode'
3750
        );
3751
    }
3752
3753
    // Generate new keys
3754
    if ($user_self_change === true && empty($recovery_public_key) === false && empty($recovery_private_key) === false){
3755
        $userKeys = [
3756
            'public_key' => $recovery_public_key,
3757
            'private_key_clear' => $recovery_private_key,
3758
            'private_key' => encryptPrivateKey($passwordClear, $recovery_private_key),
3759
        ];
3760
    } else {
3761
        $userKeys = generateUserKeys($passwordClear);
3762
    }
3763
3764
    // Save in DB
3765
    DB::update(
3766
        prefixTable('users'),
3767
        array(
3768
            'pw' => $hashedPassword,
3769
            'public_key' => $userKeys['public_key'],
3770
            'private_key' => $userKeys['private_key'],
3771
        ),
3772
        'id=%i',
3773
        $userId
3774
    );
3775
3776
    // update session too
3777
    if ($userId === $session->get('user-id')) {
3778
        $session->set('user-private_key', $userKeys['private_key_clear']);
3779
        $session->set('user-public_key', $userKeys['public_key']);
3780
    }
3781
3782
    // Manage empty encryption key
3783
    // Let's take the user's password if asked and if no encryption key provided
3784
    $encryptionKey = $encryptWithUserPassword === true && empty($encryptionKey) === true ? $passwordClear : $encryptionKey;
3785
3786
    // Create process
3787
    DB::insert(
3788
        prefixTable('processes'),
3789
        array(
3790
            'created_at' => time(),
3791
            'process_type' => 'create_user_keys',
3792
            'arguments' => json_encode([
3793
                'new_user_id' => (int) $userId,
3794
                'new_user_pwd' => cryption($passwordClear, '','encrypt')['string'],
3795
                'new_user_code' => cryption(empty($encryptionKey) === true ? uniqidReal(20) : $encryptionKey, '','encrypt')['string'],
3796
                'owner_id' => (int) TP_USER_ID,
3797
                'creator_pwd' => $userTP['pw'],
3798
                'send_email' => $sendEmailToUser === true ? 1 : 0,
3799
                'otp_provided_new_value' => 1,
3800
                'email_body' => empty($emailBody) === true ? '' : $lang->get($emailBody),
3801
                'user_self_change' => $user_self_change === true ? 1 : 0,
3802
            ]),
3803
            'updated_at' => '',
3804
            'finished_at' => '',
3805
            'output' => '',
3806
        )
3807
    );
3808
    $processId = DB::insertId();
3809
3810
    // Delete existing keys
3811
    if ($deleteExistingKeys === true) {
3812
        deleteUserObjetsKeys(
3813
            (int) $userId,
3814
        );
3815
    }
3816
3817
    // Create tasks
3818
    createUserTasks($processId, $nbItemsToTreat);
3819
3820
    // update user's new status
3821
    DB::update(
3822
        prefixTable('users'),
3823
        [
3824
            'is_ready_for_usage' => 0,
3825
            'otp_provided' => 1,
3826
            'ongoing_process_id' => $processId,
3827
            'special' => 'generate-keys',
3828
        ],
3829
        'id=%i',
3830
        $userId
3831
    );
3832
3833
    return prepareExchangedData(
3834
        array(
3835
            'error' => false,
3836
            'message' => '',
3837
        ),
3838
        'encode'
3839
    );
3840
}
3841
3842
/**
3843
 * Permits to generate a new password for a user
3844
 *
3845
 * @param integer $processId
3846
 * @param integer $nbItemsToTreat
3847
 * @return void
3848
 
3849
 */
3850
function createUserTasks($processId, $nbItemsToTreat): void
3851
{
3852
    DB::insert(
3853
        prefixTable('processes_tasks'),
3854
        array(
3855
            'process_id' => $processId,
3856
            'created_at' => time(),
3857
            'task' => json_encode([
3858
                'step' => 'step0',
3859
                'index' => 0,
3860
                'nb' => $nbItemsToTreat,
3861
            ]),
3862
        )
3863
    );
3864
3865
    DB::insert(
3866
        prefixTable('processes_tasks'),
3867
        array(
3868
            'process_id' => $processId,
3869
            'created_at' => time(),
3870
            'task' => json_encode([
3871
                'step' => 'step10',
3872
                'index' => 0,
3873
                'nb' => $nbItemsToTreat,
3874
            ]),
3875
        )
3876
    );
3877
3878
    DB::insert(
3879
        prefixTable('processes_tasks'),
3880
        array(
3881
            'process_id' => $processId,
3882
            'created_at' => time(),
3883
            'task' => json_encode([
3884
                'step' => 'step20',
3885
                'index' => 0,
3886
                'nb' => $nbItemsToTreat,
3887
            ]),
3888
        )
3889
    );
3890
3891
    DB::insert(
3892
        prefixTable('processes_tasks'),
3893
        array(
3894
            'process_id' => $processId,
3895
            'created_at' => time(),
3896
            'task' => json_encode([
3897
                'step' => 'step30',
3898
                'index' => 0,
3899
                'nb' => $nbItemsToTreat,
3900
            ]),
3901
        )
3902
    );
3903
3904
    DB::insert(
3905
        prefixTable('processes_tasks'),
3906
        array(
3907
            'process_id' => $processId,
3908
            'created_at' => time(),
3909
            'task' => json_encode([
3910
                'step' => 'step40',
3911
                'index' => 0,
3912
                'nb' => $nbItemsToTreat,
3913
            ]),
3914
        )
3915
    );
3916
3917
    DB::insert(
3918
        prefixTable('processes_tasks'),
3919
        array(
3920
            'process_id' => $processId,
3921
            'created_at' => time(),
3922
            'task' => json_encode([
3923
                'step' => 'step50',
3924
                'index' => 0,
3925
                'nb' => $nbItemsToTreat,
3926
            ]),
3927
        )
3928
    );
3929
3930
    DB::insert(
3931
        prefixTable('processes_tasks'),
3932
        array(
3933
            'process_id' => $processId,
3934
            'created_at' => time(),
3935
            'task' => json_encode([
3936
                'step' => 'step60',
3937
                'index' => 0,
3938
                'nb' => $nbItemsToTreat,
3939
            ]),
3940
        )
3941
    );
3942
}
3943
3944
/**
3945
 * Permeits to check the consistency of date versus columns definition
3946
 *
3947
 * @param string $table
3948
 * @param array $dataFields
3949
 * @return array
3950
 */
3951
function validateDataFields(
3952
    string $table,
3953
    array $dataFields
3954
): array
3955
{
3956
    // Get table structure
3957
    $result = DB::query(
3958
        "SELECT `COLUMN_NAME`, `CHARACTER_MAXIMUM_LENGTH` FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%l' AND TABLE_NAME = '%l';",
3959
        DB_NAME,
3960
        $table
3961
    );
3962
3963
    foreach ($result as $row) {
3964
        $field = $row['COLUMN_NAME'];
3965
        $maxLength = is_null($row['CHARACTER_MAXIMUM_LENGTH']) === false ? (int) $row['CHARACTER_MAXIMUM_LENGTH'] : '';
3966
3967
        if (isset($dataFields[$field]) === true && is_array($dataFields[$field]) === false && empty($maxLength) === false) {
3968
            if (strlen((string) $dataFields[$field]) > $maxLength) {
3969
                return [
3970
                    'state' => false,
3971
                    'field' => $field,
3972
                    'maxLength' => $maxLength,
3973
                    'currentLength' => strlen((string) $dataFields[$field]),
3974
                ];
3975
            }
3976
        }
3977
    }
3978
    
3979
    return [
3980
        'state' => true,
3981
        'message' => '',
3982
    ];
3983
}
3984
3985
/**
3986
 * Adapt special characters sanitized during filter_var with option FILTER_SANITIZE_SPECIAL_CHARS operation
3987
 *
3988
 * @param string $string
3989
 * @return string
3990
 */
3991
function filterVarBack(string $string): string
3992
{
3993
    $arr = [
3994
        '&#060;' => '<',
3995
        '&#062;' => '>',
3996
        '&#034;' => '"',
3997
        '&#039;' => "'",
3998
        '&#038;' => '&',
3999
    ];
4000
4001
    foreach ($arr as $key => $value) {
4002
        $string = str_replace($key, $value, $string);
4003
    }
4004
4005
    return $string;
4006
}
4007
4008
/**
4009
 * 
4010
 */
4011
function storeTask(
4012
    string $taskName,
4013
    int $user_id,
4014
    int $is_personal_folder,
4015
    int $folder_destination_id,
4016
    int $item_id,
4017
    string $object_keys
4018
)
4019
{
4020
    if (in_array($taskName, ['item_copy', 'new_item', 'update_item'])) {
4021
        // Create process
4022
        DB::insert(
4023
            prefixTable('processes'),
4024
            array(
4025
                'created_at' => time(),
4026
                'process_type' => $taskName,
4027
                'arguments' => json_encode([
4028
                    'item_id' => $item_id,
4029
                    'object_key' => $object_keys,
4030
                ]),
4031
                'updated_at' => '',
4032
                'finished_at' => '',
4033
                'output' => '',
4034
                'item_id' => $item_id,
4035
            )
4036
        );
4037
        $processId = DB::insertId();
4038
4039
        // Create tasks
4040
        // 1- Create password sharekeys for users of this new ITEM
4041
        DB::insert(
4042
            prefixTable('processes_tasks'),
4043
            array(
4044
                'process_id' => $processId,
4045
                'created_at' => time(),
4046
                'task' => json_encode([
4047
                    'step' => 'create_users_pwd_key',
4048
                    'index' => 0,
4049
                ]),
4050
            )
4051
        );
4052
4053
        // 2- Create fields sharekeys for users of this new ITEM
4054
        DB::insert(
4055
            prefixTable('processes_tasks'),
4056
            array(
4057
                'process_id' => $processId,
4058
                'created_at' => time(),
4059
                'task' => json_encode([
4060
                    'step' => 'create_users_fields_key',
4061
                    'index' => 0,
4062
                ]),
4063
            )
4064
        );
4065
4066
        // 3- Create files sharekeys for users of this new ITEM
4067
        DB::insert(
4068
            prefixTable('processes_tasks'),
4069
            array(
4070
                'process_id' => $processId,
4071
                'created_at' => time(),
4072
                'task' => json_encode([
4073
                    'step' => 'create_users_files_key',
4074
                    'index' => 0,
4075
                ]),
4076
            )
4077
        );
4078
    }
4079
}
4080
4081
/**
4082
 * Return PHP binary path
4083
 *
4084
 * @return string
4085
 */
4086
function getPHPBinary(): string
4087
{
4088
    // Get PHP binary path
4089
    $phpBinaryFinder = new PhpExecutableFinder();
4090
    $phpBinaryPath = $phpBinaryFinder->find();
4091
    return $phpBinaryPath === false ? 'false' : $phpBinaryPath;
4092
}
4093
4094
4095
4096
/**
4097
 * Delete unnecessary keys for personal items
4098
 *
4099
 * @param boolean $allUsers
4100
 * @param integer $user_id
4101
 * @return void
4102
 */
4103
function purgeUnnecessaryKeys(bool $allUsers = true, int $user_id=0)
4104
{
4105
    if ($allUsers === true) {
4106
        // Load class DB
4107
        if (class_exists('DB') === false) {
4108
            loadClasses('DB');
4109
        }
4110
4111
        $users = DB::query(
4112
            'SELECT id
4113
            FROM ' . prefixTable('users') . '
4114
            WHERE id NOT IN ('.OTV_USER_ID.', '.TP_USER_ID.', '.SSH_USER_ID.', '.API_USER_ID.')
4115
            ORDER BY login ASC'
4116
        );
4117
        foreach ($users as $user) {
4118
            purgeUnnecessaryKeysForUser((int) $user['id']);
4119
        }
4120
    } else {
4121
        purgeUnnecessaryKeysForUser((int) $user_id);
4122
    }
4123
}
4124
4125
/**
4126
 * Delete unnecessary keys for personal items
4127
 *
4128
 * @param integer $user_id
4129
 * @return void
4130
 */
4131
function purgeUnnecessaryKeysForUser(int $user_id=0)
4132
{
4133
    if ($user_id === 0) {
4134
        return;
4135
    }
4136
4137
    // Load class DB
4138
    loadClasses('DB');
4139
4140
    $personalItems = DB::queryFirstColumn(
4141
        'SELECT id
4142
        FROM ' . prefixTable('items') . ' AS i
4143
        INNER JOIN ' . prefixTable('log_items') . ' AS li ON li.id_item = i.id
4144
        WHERE i.perso = 1 AND li.action = "at_creation" AND li.id_user IN (%i, '.TP_USER_ID.')',
4145
        $user_id
4146
    );
4147
    if (count($personalItems) > 0) {
4148
        // Item keys
4149
        DB::delete(
4150
            prefixTable('sharekeys_items'),
4151
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4152
            $personalItems,
4153
            $user_id
4154
        );
4155
        // Files keys
4156
        DB::delete(
4157
            prefixTable('sharekeys_files'),
4158
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4159
            $personalItems,
4160
            $user_id
4161
        );
4162
        // Fields keys
4163
        DB::delete(
4164
            prefixTable('sharekeys_fields'),
4165
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4166
            $personalItems,
4167
            $user_id
4168
        );
4169
        // Logs keys
4170
        DB::delete(
4171
            prefixTable('sharekeys_logs'),
4172
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4173
            $personalItems,
4174
            $user_id
4175
        );
4176
    }
4177
}
4178
4179
/**
4180
 * Generate recovery keys file
4181
 *
4182
 * @param integer $userId
4183
 * @param array $SETTINGS
4184
 * @return string
4185
 */
4186
function handleUserRecoveryKeysDownload(int $userId, array $SETTINGS):string
4187
{
4188
    $session = SessionManager::getSession();
4189
    // Check if user exists
4190
    $userInfo = DB::queryFirstRow(
4191
        'SELECT pw, public_key, private_key, login, name
4192
        FROM ' . prefixTable('users') . '
4193
        WHERE id = %i',
4194
        $userId
4195
    );
4196
4197
    if (DB::count() > 0) {
4198
        $now = (int) time();
4199
4200
        // Prepare file content
4201
        $export_value = file_get_contents(__DIR__."/../includes/core/teampass_ascii.txt")."\n".
4202
            "Generation date: ".date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now)."\n\n".
4203
            "RECOVERY KEYS - Not to be shared - To be store safely\n\n".
4204
            "Public Key:\n".$userInfo['public_key']."\n\n".
4205
            "Private Key:\n".decryptPrivateKey($session->get('user-password'), $userInfo['private_key'])."\n\n";
4206
4207
        // Update user's keys_recovery_time
4208
        DB::update(
4209
            prefixTable('users'),
4210
            [
4211
                'keys_recovery_time' => $now,
4212
            ],
4213
            'id=%i',
4214
            $userId
4215
        );
4216
        $session->set('user-keys_recovery_time', $now);
4217
4218
        //Log into DB the user's disconnection
4219
        logEvents($SETTINGS, 'user_mngt', 'at_user_keys_download', (string) $userId, $userInfo['login']);
4220
        
4221
        // Return data
4222
        return prepareExchangedData(
4223
            array(
4224
                'error' => false,
4225
                'datetime' => date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now),
4226
                'timestamp' => $now,
4227
                'content' => base64_encode($export_value),
4228
                'login' => $userInfo['login'],
4229
            ),
4230
            'encode'
4231
        );
4232
    }
4233
4234
    return prepareExchangedData(
4235
        array(
4236
            'error' => true,
4237
            'datetime' => '',
4238
        ),
4239
        'encode'
4240
    );
4241
}
4242
4243
/**
4244
 * Permits to load expected classes
4245
 *
4246
 * @param string $className
4247
 * @return void
4248
 */
4249
function loadClasses(string $className = ''): void
4250
{
4251
    require_once __DIR__. '/../includes/config/include.php';
4252
    require_once __DIR__. '/../includes/config/settings.php';
4253
    require_once __DIR__.'/../vendor/autoload.php';
4254
4255
    if (defined('DB_PASSWD_CLEAR') === false) {
4256
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, []));
4257
    }
4258
4259
    if (empty($className) === false) {
4260
        // Load class DB
4261
        if ((string) $className === 'DB') {
4262
            //Connect to DB
4263
            DB::$host = DB_HOST;
4264
            DB::$user = DB_USER;
4265
            DB::$password = DB_PASSWD_CLEAR;
4266
            DB::$dbName = DB_NAME;
4267
            DB::$port = DB_PORT;
4268
            DB::$encoding = DB_ENCODING;
4269
            DB::$ssl = DB_SSL;
4270
            DB::$connect_options = DB_CONNECT_OPTIONS;
4271
        }
4272
    }
4273
4274
}
4275
4276
/**
4277
 * Returns the page the user is visiting.
4278
 *
4279
 * @return string The page name
4280
 */
4281
function getCurrectPage($SETTINGS)
4282
{
4283
    
4284
    $request = Request::createFromGlobals();
4285
4286
    // Parse the url
4287
    parse_str(
4288
        substr(
4289
            (string) $request->server->get('REQUEST_URI'),
4290
            strpos((string) $request->server->get('REQUEST_URI'), '?') + 1
4291
        ),
4292
        $result
4293
    );
4294
4295
    return $result['page'];
4296
}
4297
4298
/**
4299
 * Permits to return value if set
4300
 *
4301
 * @param string|int $value
4302
 * @param string|int|null $retFalse
4303
 * @param string|int $retTrue
4304
 * @return mixed
4305
 */
4306
function returnIfSet($value, $retFalse = '', $retTrue = null): mixed
4307
{
4308
4309
    return isset($value) === true ? ($retTrue === null ? $value : $retTrue) : $retFalse;
4310
}
4311