Passed
Branch wip_sessions (830972)
by Nils
07:55
created

bCrypt()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 16
rs 10
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\SuperGlobal\SuperGlobal;
34
use TeampassClasses\SessionManager\SessionManager;
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 PHPMailer\PHPMailer\PHPMailer;
43
use PasswordLib\PasswordLib;
44
use Symfony\Component\Process\Exception\ProcessFailedException;
45
use Symfony\Component\Process\Process;
46
use Symfony\Component\Process\PhpExecutableFinder;
47
use TeampassClasses\Encryption\Encryption;
48
49
50
// Load config if $SETTINGS not defined
51
if (isset($SETTINGS['cpassman_dir']) === false || empty($SETTINGS['cpassman_dir']) === true) {
52
    include_once __DIR__ . '/../includes/config/tp.config.php';
53
}
54
55
header('Content-type: text/html; charset=utf-8');
56
header('Cache-Control: no-cache, must-revalidate');
57
58
59
60
loadClasses('DB');
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
    $SETTINGS
1108
): void 
1109
{
1110
    DB::insert(
1111
        prefixTable('processes'),
1112
        array(
1113
            'created_at' => time(),
1114
            'process_type' => 'send_email',
1115
            'arguments' => json_encode([
1116
                'subject' => $subject,
1117
                'receivers' => $email,
1118
                'body' => $body,
1119
                'receiver_name' => $receiverName,
1120
            ], JSON_HEX_QUOT | JSON_HEX_TAG),
1121
            'updated_at' => '',
1122
            'finished_at' => '',
1123
            'output' => '',
1124
        )
1125
    );
1126
}
1127
1128
/**
1129
 * Permits to send an email.
1130
 *
1131
 * @param string $subject     email subject
1132
 * @param string $textMail    email message
1133
 * @param string $email       email
1134
 * @param array  $SETTINGS    settings
1135
 * @param string $textMailAlt email message alt
1136
 * @param bool   $silent      no errors
1137
 *
1138
 * @return string some json info
1139
 */
1140
function sendEmail(
1141
    $subject,
1142
    $textMail,
1143
    $email,
1144
    $SETTINGS,
1145
    $textMailAlt = null,
1146
    $silent = true,
1147
    $cron = false
1148
) {
1149
    $lang = new Language(); 
1150
1151
    // CAse where email not defined
1152
    if ($email === 'none' || empty($email) === true) {
1153
        return json_encode(
1154
            [
1155
                'error' => true,
1156
                'message' => $lang->get('forgot_my_pw_email_sent'),
1157
            ]
1158
        );
1159
    }
1160
1161
    // Build and send email
1162
    $email = buildEmail(
1163
        $subject,
1164
        $textMail,
1165
        $email,
1166
        $SETTINGS,
1167
        $textMailAlt = null,
1168
        $silent = true,
1169
        $cron
1170
    );
1171
1172
    if ($silent === false) {
0 ignored issues
show
introduced by
The condition $silent === false is always false.
Loading history...
1173
        return json_encode(
1174
            [
1175
                'error' => false,
1176
                'message' => $lang->get('forgot_my_pw_email_sent'),
1177
            ]
1178
        );
1179
    }
1180
    // Debug purpose
1181
    if ((int) $SETTINGS['email_debug_level'] !== 0 && $cron === false) {
1182
        return json_encode(
1183
            [
1184
                'error' => true,
1185
                'message' => isset($email['ErrorInfo']) === true ? $email['ErrorInfo'] : '',
1186
            ]
1187
        );
1188
    }
1189
    return json_encode(
1190
        [
1191
            'error' => false,
1192
            'message' => $lang->get('share_sent_ok'),
1193
        ]
1194
    );
1195
}
1196
1197
1198
function buildEmail(
1199
    $subject,
1200
    $textMail,
1201
    $email,
1202
    $SETTINGS,
1203
    $textMailAlt = null,
1204
    $silent = true,
1205
    $cron = false
1206
)
1207
{
1208
    // load PHPMailer
1209
    $mail = new PHPMailer(true);
1210
1211
    // send to user
1212
    $mail->setLanguage('en', $SETTINGS['cpassman_dir'] . '/vendor/phpmailer/phpmailer/language/');
1213
    $mail->SMTPDebug = isset($SETTINGS['email_debug_level']) === true && $cron === false && $silent === false ? $SETTINGS['email_debug_level'] : 0;
1214
    $mail->Port = (int) $SETTINGS['email_port'];
1215
    //COULD BE USED
1216
    $mail->CharSet = 'utf-8';
1217
    $mail->SMTPSecure = $SETTINGS['email_security'] !== 'none' ? $SETTINGS['email_security'] : '';
1218
    $mail->SMTPAutoTLS = $SETTINGS['email_security'] !== 'none' ? true : false;
1219
    $mail->SMTPOptions = [
1220
        'ssl' => [
1221
            'verify_peer' => false,
1222
            'verify_peer_name' => false,
1223
            'allow_self_signed' => true,
1224
        ],
1225
    ];
1226
    $mail->isSmtp();
1227
    // send via SMTP
1228
    $mail->Host = $SETTINGS['email_smtp_server'];
1229
    // SMTP servers
1230
    $mail->SMTPAuth = (int) $SETTINGS['email_smtp_auth'] === 1 ? true : false;
1231
    // turn on SMTP authentication
1232
    $mail->Username = $SETTINGS['email_auth_username'];
1233
    // SMTP username
1234
    $mail->Password = $SETTINGS['email_auth_pwd'];
1235
    // SMTP password
1236
    $mail->From = $SETTINGS['email_from'];
1237
    $mail->FromName = $SETTINGS['email_from_name'];
1238
    // Prepare for each person
1239
    foreach (array_filter(explode(',', $email)) as $dest) {
1240
        $mail->addAddress($dest);
1241
    }
1242
    
1243
    // Prepare HTML
1244
    $text_html = emailBody($textMail);
1245
    $mail->WordWrap = 80;
1246
    // set word wrap
1247
    $mail->isHtml(true);
1248
    // send as HTML
1249
    $mail->Subject = $subject;
1250
    $mail->Body = $text_html;
1251
    $mail->AltBody = is_null($textMailAlt) === false ? $textMailAlt : '';
1252
1253
    try {
1254
        // send email
1255
        $mail->send();
1256
    } catch (Exception $e) {
1257
        if ($silent === false || (int) $SETTINGS['email_debug_level'] !== 0) {
1258
            return json_encode(
1259
                [
1260
                    'error' => true,
1261
                    'errorInfo' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1262
                ]
1263
            );
1264
        }
1265
        return '';
1266
    }
1267
    $mail->smtpClose();
1268
1269
    return json_encode(
1270
        [
1271
            'error' => true,
1272
            'errorInfo' => str_replace(["\n", "\t", "\r"], '', $mail->ErrorInfo),
1273
        ]
1274
    );
1275
}
1276
1277
/**
1278
 * Returns the email body.
1279
 *
1280
 * @param string $textMail Text for the email
1281
 */
1282
function emailBody(string $textMail): string
1283
{
1284
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1285
    w3.org/TR/html4/loose.dtd"><html>
1286
    <head><title>Email Template</title>
1287
    <style type="text/css">
1288
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1289
    </style></head>
1290
    <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">
1291
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1292
    <tr><td style="border-collapse: collapse;"><br>
1293
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1294
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1295
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1296
        </td></tr></table></td>
1297
    </tr>
1298
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1299
        <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;">
1300
        <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;">
1301
        <br><div style="float:right;">' .
1302
        $textMail .
1303
        '<br><br></td></tr></table>
1304
    </td></tr></table>
1305
    <br></body></html>';
1306
}
1307
1308
/**
1309
 * Generate a Key.
1310
 * 
1311
 * @return string
1312
 */
1313
function generateKey(): string
1314
{
1315
    return substr(md5(rand() . rand()), 0, 15);
1316
}
1317
1318
/**
1319
 * Convert date to timestamp.
1320
 *
1321
 * @param string $date        The date
1322
 * @param string $date_format Date format
1323
 *
1324
 * @return int
1325
 */
1326
function dateToStamp(string $date, string $date_format): int
1327
{
1328
    $date = date_parse_from_format($date_format, $date);
1329
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1330
        return mktime(
1331
            empty($date['hour']) === false ? $date['hour'] : 23,
1332
            empty($date['minute']) === false ? $date['minute'] : 59,
1333
            empty($date['second']) === false ? $date['second'] : 59,
1334
            $date['month'],
1335
            $date['day'],
1336
            $date['year']
1337
        );
1338
    }
1339
    return 0;
1340
}
1341
1342
/**
1343
 * Is this a date.
1344
 *
1345
 * @param string $date Date
1346
 *
1347
 * @return bool
1348
 */
1349
function isDate(string $date): bool
1350
{
1351
    return strtotime($date) !== false;
1352
}
1353
1354
/**
1355
 * Check if isUTF8().
1356
 *
1357
 * @param string|array $string Is the string
1358
 *
1359
 * @return int is the string in UTF8 format
1360
 */
1361
function isUTF8($string): int
1362
{
1363
    if (is_array($string) === true) {
1364
        $string = $string['string'];
1365
    }
1366
1367
    return preg_match(
1368
        '%^(?:
1369
        [\x09\x0A\x0D\x20-\x7E] # ASCII
1370
        | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
1371
        | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
1372
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
1373
        | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
1374
        | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
1375
        | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
1376
        | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
1377
        )*$%xs',
1378
        $string
1379
    );
1380
}
1381
1382
/**
1383
 * Prepare an array to UTF8 format before JSON_encode.
1384
 *
1385
 * @param array $array Array of values
1386
 *
1387
 * @return array
1388
 */
1389
function utf8Converter(array $array): array
1390
{
1391
    array_walk_recursive(
1392
        $array,
1393
        static function (&$item): void {
1394
            if (mb_detect_encoding((string) $item, 'utf-8', true) === false) {
1395
                $item = mb_convert_encoding($item, 'ISO-8859-1', 'UTF-8');
1396
            }
1397
        }
1398
    );
1399
    return $array;
1400
}
1401
1402
/**
1403
 * Permits to prepare data to be exchanged.
1404
 *
1405
 * @param array|string $data Text
1406
 * @param string       $type Parameter
1407
 * @param string       $key  Optional key
1408
 *
1409
 * @return string|array
1410
 */
1411
function prepareExchangedData($data, string $type, ?string $key = null)
1412
{
1413
    $session = SessionManager::getSession();
1414
    
1415
    // get session
1416
    if ($key !== null) {
1417
        $session->set('key', $key);
1418
        $globalsKey = $key;
1419
    } else {
1420
        $globalsKey = $session->get('key');
1421
    }
1422
    
1423
    // Perform
1424
    if ($type === 'encode' && is_array($data) === true) {
1425
        // Now encode
1426
        return Encryption::encrypt(
1427
            json_encode(
1428
                $data,
1429
                JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1430
            ),
1431
            $globalsKey
1432
        );
1433
    }
1434
    if ($type === 'decode' && is_array($data) === false) {
1435
        // check if key exists
1436
        return json_decode(
1437
            (string) Encryption::decrypt(
1438
                (string) $data,
1439
                $globalsKey
1440
            ),
1441
            true
1442
        );
1443
    }
1444
    return '';
1445
}
1446
1447
1448
/**
1449
 * Create a thumbnail.
1450
 *
1451
 * @param string  $src           Source
1452
 * @param string  $dest          Destination
1453
 * @param int $desired_width Size of width
1454
 * 
1455
 * @return void|string|bool
1456
 */
1457
function makeThumbnail(string $src, string $dest, int $desired_width)
1458
{
1459
    /* read the source image */
1460
    if (is_file($src) === true && mime_content_type($src) === 'image/png') {
1461
        $source_image = imagecreatefrompng($src);
1462
        if ($source_image === false) {
1463
            return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1464
        }
1465
    } else {
1466
        return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1467
    }
1468
1469
    // Get height and width
1470
    $width = imagesx($source_image);
1471
    $height = imagesy($source_image);
1472
    /* find the "desired height" of this thumbnail, relative to the desired width  */
1473
    $desired_height = (int) floor($height * $desired_width / $width);
1474
    /* create a new, "virtual" image */
1475
    $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
1476
    if ($virtual_image === false) {
1477
        return false;
1478
    }
1479
    /* copy source image at a resized size */
1480
    imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
1481
    /* create the physical thumbnail image to its destination */
1482
    imagejpeg($virtual_image, $dest);
1483
}
1484
1485
/**
1486
 * Check table prefix in SQL query.
1487
 *
1488
 * @param string $table Table name
1489
 * 
1490
 * @return string
1491
 */
1492
function prefixTable(string $table): string
1493
{
1494
    $safeTable = htmlspecialchars(DB_PREFIX . $table);
1495
    if (empty($safeTable) === false) {
1496
        // sanitize string
1497
        return $safeTable;
1498
    }
1499
    // stop error no table
1500
    return 'table_not_exists';
1501
}
1502
1503
/**
1504
 * GenerateCryptKey
1505
 *
1506
 * @param int     $size      Length
1507
 * @param bool $secure Secure
1508
 * @param bool $numerals Numerics
1509
 * @param bool $uppercase Uppercase letters
1510
 * @param bool $symbols Symbols
1511
 * @param bool $lowercase Lowercase
1512
 * @param array   $SETTINGS  SETTINGS
1513
 * 
1514
 * @return string
1515
 */
1516
function GenerateCryptKey(
1517
    int $size = 20,
1518
    bool $secure = false,
1519
    bool $numerals = false,
1520
    bool $uppercase = false,
1521
    bool $symbols = false,
1522
    bool $lowercase = false,
1523
    array $SETTINGS = []
1524
): string {
1525
    $generator = new ComputerPasswordGenerator();
1526
    $generator->setRandomGenerator(new Php7RandomGenerator());
1527
    
1528
    // Manage size
1529
    $generator->setLength((int) $size);
1530
    if ($secure === true) {
1531
        $generator->setSymbols(true);
1532
        $generator->setLowercase(true);
1533
        $generator->setUppercase(true);
1534
        $generator->setNumbers(true);
1535
    } else {
1536
        $generator->setLowercase($lowercase);
1537
        $generator->setUppercase($uppercase);
1538
        $generator->setNumbers($numerals);
1539
        $generator->setSymbols($symbols);
1540
    }
1541
1542
    return $generator->generatePasswords()[0];
1543
}
1544
1545
/**
1546
 * Send sysLOG message
1547
 *
1548
 * @param string    $message
1549
 * @param string    $host
1550
 * @param int       $port
1551
 * @param string    $component
1552
 * 
1553
 * @return void
1554
*/
1555
function send_syslog($message, $host, $port, $component = 'teampass'): void
1556
{
1557
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1558
    $syslog_message = '<123>' . date('M d H:i:s ') . $component . ': ' . $message;
1559
    socket_sendto($sock, (string) $syslog_message, strlen($syslog_message), 0, (string) $host, (int) $port);
1560
    socket_close($sock);
1561
}
1562
1563
/**
1564
 * Permits to log events into DB
1565
 *
1566
 * @param array  $SETTINGS Teampass settings
1567
 * @param string $type     Type
1568
 * @param string $label    Label
1569
 * @param string $who      Who
1570
 * @param string $login    Login
1571
 * @param string|int $field_1  Field
1572
 * 
1573
 * @return void
1574
 */
1575
function logEvents(
1576
    array $SETTINGS, 
1577
    string $type, 
1578
    string $label, 
1579
    string $who, 
1580
    ?string $login = null, 
1581
    $field_1 = null
1582
): void
1583
{
1584
    if (empty($who)) {
1585
        $who = getClientIpServer();
1586
    }
1587
1588
    // Load class DB
1589
    loadClasses('DB');
1590
1591
    DB::insert(
1592
        prefixTable('log_system'),
1593
        [
1594
            'type' => $type,
1595
            'date' => time(),
1596
            'label' => $label,
1597
            'qui' => $who,
1598
            'field_1' => $field_1 === null ? '' : $field_1,
1599
        ]
1600
    );
1601
    // If SYSLOG
1602
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1603
        if ($type === 'user_mngt') {
1604
            send_syslog(
1605
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1606
                $SETTINGS['syslog_host'],
1607
                $SETTINGS['syslog_port'],
1608
                'teampass'
1609
            );
1610
        } else {
1611
            send_syslog(
1612
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1613
                $SETTINGS['syslog_host'],
1614
                $SETTINGS['syslog_port'],
1615
                'teampass'
1616
            );
1617
        }
1618
    }
1619
}
1620
1621
/**
1622
 * Log events.
1623
 *
1624
 * @param array  $SETTINGS        Teampass settings
1625
 * @param int    $item_id         Item id
1626
 * @param string $item_label      Item label
1627
 * @param int    $id_user         User id
1628
 * @param string $action          Code for reason
1629
 * @param string $login           User login
1630
 * @param string $raison          Code for reason
1631
 * @param string $encryption_type Encryption on
1632
 * @param string $time Encryption Time
1633
 * @param string $old_value       Old value
1634
 * 
1635
 * @return void
1636
 */
1637
function logItems(
1638
    array $SETTINGS,
1639
    int $item_id,
1640
    string $item_label,
1641
    int $id_user,
1642
    string $action,
1643
    ?string $login = null,
1644
    ?string $raison = null,
1645
    ?string $encryption_type = null,
1646
    ?string $time = null,
1647
    ?string $old_value = null
1648
): void {
1649
    // Load class DB
1650
    loadClasses('DB');
1651
1652
    // Insert log in DB
1653
    DB::insert(
1654
        prefixTable('log_items'),
1655
        [
1656
            'id_item' => $item_id,
1657
            'date' => is_null($time) === true ? time() : $time,
1658
            'id_user' => $id_user,
1659
            'action' => $action,
1660
            'raison' => $raison,
1661
            'old_value' => $old_value,
1662
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1663
        ]
1664
    );
1665
    // Timestamp the last change
1666
    if ($action === 'at_creation' || $action === 'at_modifiation' || $action === 'at_delete' || $action === 'at_import') {
1667
        DB::update(
1668
            prefixTable('misc'),
1669
            [
1670
                'valeur' => time(),
1671
            ],
1672
            'type = %s AND intitule = %s',
1673
            'timestamp',
1674
            'last_item_change'
1675
        );
1676
    }
1677
1678
    // SYSLOG
1679
    if (isset($SETTINGS['syslog_enable']) === true && $SETTINGS['syslog_enable'] === '1') {
1680
        // Extract reason
1681
        $attribute = is_null($raison) === true ? Array('') : explode(' : ', $raison);
1682
        // Get item info if not known
1683
        if (empty($item_label) === true) {
1684
            $dataItem = DB::queryfirstrow(
1685
                'SELECT id, id_tree, label
1686
                FROM ' . prefixTable('items') . '
1687
                WHERE id = %i',
1688
                $item_id
1689
            );
1690
            $item_label = $dataItem['label'];
1691
        }
1692
1693
        send_syslog(
1694
            'action=' . str_replace('at_', '', $action) .
1695
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1696
                ' itemno=' . $item_id .
1697
                ' user=' . is_null($login) === true ? '' : addslashes((string) $login) .
1698
                ' itemname="' . addslashes($item_label) . '"',
1699
            $SETTINGS['syslog_host'],
1700
            $SETTINGS['syslog_port'],
1701
            'teampass'
1702
        );
1703
    }
1704
1705
    // send notification if enabled
1706
    //notifyOnChange($item_id, $action, $SETTINGS);
1707
}
1708
1709
/**
1710
 * Prepare notification email to subscribers.
1711
 *
1712
 * @param int    $item_id  Item id
1713
 * @param string $label    Item label
1714
 * @param array  $changes  List of changes
1715
 * @param array  $SETTINGS Teampass settings
1716
 * 
1717
 * @return void
1718
 */
1719
function notifyChangesToSubscribers(int $item_id, string $label, array $changes, array $SETTINGS): void
1720
{
1721
    $session = SessionManager::getSession();
1722
    $lang = new Language(); 
1723
    $globalsUserId = $session->get('user-id');
1724
    $globalsLastname = $session->get('user-lastname');
1725
    $globalsName = $session->get('user-name');
1726
    // send email to user that what to be notified
1727
    $notification = DB::queryOneColumn(
1728
        'email',
1729
        'SELECT *
1730
        FROM ' . prefixTable('notification') . ' AS n
1731
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1732
        WHERE n.item_id = %i AND n.user_id != %i',
1733
        $item_id,
1734
        $globalsUserId
1735
    );
1736
    if (DB::count() > 0) {
1737
        // Prepare path
1738
        $path = geItemReadablePath($item_id, '', $SETTINGS);
1739
        // Get list of changes
1740
        $htmlChanges = '<ul>';
1741
        foreach ($changes as $change) {
1742
            $htmlChanges .= '<li>' . $change . '</li>';
1743
        }
1744
        $htmlChanges .= '</ul>';
1745
        // send email
1746
        DB::insert(
1747
            prefixTable('emails'),
1748
            [
1749
                'timestamp' => time(),
1750
                'subject' => $lang->get('email_subject_item_updated'),
1751
                'body' => str_replace(
1752
                    ['#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'],
1753
                    [$label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges],
1754
                    $lang->get('email_body_item_updated')
1755
                ),
1756
                'receivers' => implode(',', $notification),
1757
                'status' => '',
1758
            ]
1759
        );
1760
    }
1761
}
1762
1763
/**
1764
 * Returns the Item + path.
1765
 *
1766
 * @param int    $id_tree  Node id
1767
 * @param string $label    Label
1768
 * @param array  $SETTINGS TP settings
1769
 * 
1770
 * @return string
1771
 */
1772
function geItemReadablePath(int $id_tree, string $label, array $SETTINGS): string
1773
{
1774
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1775
    $arbo = $tree->getPath($id_tree, true);
1776
    $path = '';
1777
    foreach ($arbo as $elem) {
1778
        if (empty($path) === true) {
1779
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
1780
        } else {
1781
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
1782
        }
1783
    }
1784
1785
    // Build text to show user
1786
    if (empty($label) === false) {
1787
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
1788
    }
1789
    return empty($path) === true ? '' : $path;
1790
}
1791
1792
/**
1793
 * Get the client ip address.
1794
 *
1795
 * @return string IP address
1796
 */
1797
function getClientIpServer(): string
1798
{
1799
    if (getenv('HTTP_CLIENT_IP')) {
1800
        $ipaddress = getenv('HTTP_CLIENT_IP');
1801
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
1802
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
1803
    } elseif (getenv('HTTP_X_FORWARDED')) {
1804
        $ipaddress = getenv('HTTP_X_FORWARDED');
1805
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
1806
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
1807
    } elseif (getenv('HTTP_FORWARDED')) {
1808
        $ipaddress = getenv('HTTP_FORWARDED');
1809
    } elseif (getenv('REMOTE_ADDR')) {
1810
        $ipaddress = getenv('REMOTE_ADDR');
1811
    } else {
1812
        $ipaddress = 'UNKNOWN';
1813
    }
1814
1815
    return $ipaddress;
1816
}
1817
1818
/**
1819
 * Escape all HTML, JavaScript, and CSS.
1820
 *
1821
 * @param string $input    The input string
1822
 * @param string $encoding Which character encoding are we using?
1823
 * 
1824
 * @return string
1825
 */
1826
function noHTML(string $input, string $encoding = 'UTF-8'): string
1827
{
1828
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
1829
}
1830
1831
/**
1832
 * Permits to handle the Teampass config file
1833
 * $action accepts "rebuild" and "update"
1834
 *
1835
 * @param string $action   Action to perform
1836
 * @param array  $SETTINGS Teampass settings
1837
 * @param string $field    Field to refresh
1838
 * @param string $value    Value to set
1839
 *
1840
 * @return string|bool
1841
 */
1842
function handleConfigFile($action, $SETTINGS, $field = null, $value = null)
1843
{
1844
    $tp_config_file = $SETTINGS['cpassman_dir'] . '/includes/config/tp.config.php';
1845
1846
    // Load class DB
1847
    loadClasses('DB');
1848
1849
    if (file_exists($tp_config_file) === false || $action === 'rebuild') {
1850
        // perform a copy
1851
        if (file_exists($tp_config_file)) {
1852
            if (! copy($tp_config_file, $tp_config_file . '.' . date('Y_m_d_His', time()))) {
1853
                return "ERROR: Could not copy file '" . $tp_config_file . "'";
1854
            }
1855
        }
1856
1857
        // regenerate
1858
        $data = [];
1859
        $data[0] = "<?php\n";
1860
        $data[1] = "global \$SETTINGS;\n";
1861
        $data[2] = "\$SETTINGS = array (\n";
1862
        $rows = DB::query(
1863
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s',
1864
            'admin'
1865
        );
1866
        foreach ($rows as $record) {
1867
            array_push($data, "    '" . $record['intitule'] . "' => '" . htmlspecialchars_decode($record['valeur'], ENT_COMPAT) . "',\n");
1868
        }
1869
        array_push($data, ");\n");
1870
        $data = array_unique($data);
1871
    // ---
1872
    } elseif ($action === 'update' && empty($field) === false) {
1873
        $data = file($tp_config_file);
1874
        $inc = 0;
1875
        $bFound = false;
1876
        foreach ($data as $line) {
1877
            if (stristr($line, ');')) {
1878
                break;
1879
            }
1880
1881
            if (stristr($line, "'" . $field . "' => '")) {
1882
                $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode($value ?? '', ENT_COMPAT) . "',\n";
1883
                $bFound = true;
1884
                break;
1885
            }
1886
            ++$inc;
1887
        }
1888
        if ($bFound === false) {
1889
            $data[$inc] = "    '" . $field . "' => '" . htmlspecialchars_decode($value ?? '', ENT_COMPAT). "',\n);\n";
1890
        }
1891
    }
1892
1893
    // update file
1894
    file_put_contents($tp_config_file, implode('', $data ?? []));
1895
    return true;
1896
}
1897
1898
/**
1899
 * Permits to replace &#92; to permit correct display
1900
 *
1901
 * @param string $input Some text
1902
 * 
1903
 * @return string
1904
 */
1905
function handleBackslash(string $input): string
1906
{
1907
    return str_replace('&amp;#92;', '&#92;', $input);
1908
}
1909
1910
/**
1911
 * Permits to load settings
1912
 * 
1913
 * @return void
1914
*/
1915
function loadSettings(): void
1916
{
1917
    global $SETTINGS;
1918
    /* LOAD CPASSMAN SETTINGS */
1919
    if (! isset($SETTINGS['loaded']) || $SETTINGS['loaded'] !== 1) {
1920
        $SETTINGS = [];
1921
        $SETTINGS['duplicate_folder'] = 0;
1922
        //by default, this is set to 0;
1923
        $SETTINGS['duplicate_item'] = 0;
1924
        //by default, this is set to 0;
1925
        $SETTINGS['number_of_used_pw'] = 5;
1926
        //by default, this value is set to 5;
1927
        $settings = [];
1928
        $rows = DB::query(
1929
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
1930
            [
1931
                'type' => 'admin',
1932
                'type2' => 'settings',
1933
            ]
1934
        );
1935
        foreach ($rows as $record) {
1936
            if ($record['type'] === 'admin') {
1937
                $SETTINGS[$record['intitule']] = $record['valeur'];
1938
            } else {
1939
                $settings[$record['intitule']] = $record['valeur'];
1940
            }
1941
        }
1942
        $SETTINGS['loaded'] = 1;
1943
        $SETTINGS['default_session_expiration_time'] = 5;
1944
    }
1945
}
1946
1947
/**
1948
 * check if folder has custom fields.
1949
 * Ensure that target one also has same custom fields
1950
 * 
1951
 * @param int $source_id
1952
 * @param int $target_id 
1953
 * 
1954
 * @return bool
1955
*/
1956
function checkCFconsistency(int $source_id, int $target_id): bool
1957
{
1958
    $source_cf = [];
1959
    $rows = DB::QUERY(
1960
        'SELECT id_category
1961
            FROM ' . prefixTable('categories_folders') . '
1962
            WHERE id_folder = %i',
1963
        $source_id
1964
    );
1965
    foreach ($rows as $record) {
1966
        array_push($source_cf, $record['id_category']);
1967
    }
1968
1969
    $target_cf = [];
1970
    $rows = DB::QUERY(
1971
        'SELECT id_category
1972
            FROM ' . prefixTable('categories_folders') . '
1973
            WHERE id_folder = %i',
1974
        $target_id
1975
    );
1976
    foreach ($rows as $record) {
1977
        array_push($target_cf, $record['id_category']);
1978
    }
1979
1980
    $cf_diff = array_diff($source_cf, $target_cf);
1981
    if (count($cf_diff) > 0) {
1982
        return false;
1983
    }
1984
1985
    return true;
1986
}
1987
1988
/**
1989
 * Will encrypte/decrypt a fil eusing Defuse.
1990
 *
1991
 * @param string $type        can be either encrypt or decrypt
1992
 * @param string $source_file path to source file
1993
 * @param string $target_file path to target file
1994
 * @param array  $SETTINGS    Settings
1995
 * @param string $password    A password
1996
 *
1997
 * @return string|bool
1998
 */
1999
function prepareFileWithDefuse(
2000
    string $type,
2001
    string $source_file,
2002
    string $target_file,
2003
    array $SETTINGS,
2004
    string $password = null
2005
) {
2006
    // Load AntiXSS
2007
    $antiXss = new AntiXSS();
2008
    // Protect against bad inputs
2009
    if (is_array($source_file) === true || is_array($target_file) === true) {
2010
        return 'error_cannot_be_array';
2011
    }
2012
2013
    // Sanitize
2014
    $source_file = $antiXss->xss_clean($source_file);
2015
    $target_file = $antiXss->xss_clean($target_file);
2016
    if (empty($password) === true || is_null($password) === true) {
2017
        // get KEY to define password
2018
        $ascii_key = file_get_contents(SECUREPATH.'/'.SECUREFILE);
2019
        $password = Key::loadFromAsciiSafeString($ascii_key);
2020
    }
2021
2022
    $err = '';
2023
    if ($type === 'decrypt') {
2024
        // Decrypt file
2025
        $err = defuseFileDecrypt(
2026
            $source_file,
2027
            $target_file,
2028
            $SETTINGS, /** @scrutinizer ignore-type */
2029
            $password
2030
        );
2031
    } elseif ($type === 'encrypt') {
2032
        // Encrypt file
2033
        $err = defuseFileEncrypt(
2034
            $source_file,
2035
            $target_file,
2036
            $SETTINGS, /** @scrutinizer ignore-type */
2037
            $password
2038
        );
2039
    }
2040
2041
    // return error
2042
    return $err === true ? '' : $err;
2043
}
2044
2045
/**
2046
 * Encrypt a file with Defuse.
2047
 *
2048
 * @param string $source_file path to source file
2049
 * @param string $target_file path to target file
2050
 * @param array  $SETTINGS    Settings
2051
 * @param string $password    A password
2052
 *
2053
 * @return string|bool
2054
 */
2055
function defuseFileEncrypt(
2056
    string $source_file,
2057
    string $target_file,
2058
    array $SETTINGS,
2059
    string $password = null
2060
) {
2061
    try {
2062
        CryptoFile::encryptFileWithPassword(
2063
            $source_file,
2064
            $target_file,
2065
            $password
2066
        );
2067
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
2068
        $err = 'wrong_key';
2069
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
2070
        $err = $ex;
2071
    } catch (CryptoException\IOException $ex) {
2072
        $err = $ex;
2073
    }
2074
2075
    // return error
2076
    return empty($err) === false ? $err : true;
2077
}
2078
2079
/**
2080
 * Decrypt a file with Defuse.
2081
 *
2082
 * @param string $source_file path to source file
2083
 * @param string $target_file path to target file
2084
 * @param array  $SETTINGS    Settings
2085
 * @param string $password    A password
2086
 *
2087
 * @return string|bool
2088
 */
2089
function defuseFileDecrypt(
2090
    string $source_file,
2091
    string $target_file,
2092
    array $SETTINGS,
2093
    string $password = null
2094
) {
2095
    try {
2096
        CryptoFile::decryptFileWithPassword(
2097
            $source_file,
2098
            $target_file,
2099
            $password
2100
        );
2101
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
2102
        $err = 'wrong_key';
2103
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
2104
        $err = $ex;
2105
    } catch (CryptoException\IOException $ex) {
2106
        $err = $ex;
2107
    }
2108
2109
    // return error
2110
    return empty($err) === false ? $err : true;
2111
}
2112
2113
/*
2114
* NOT TO BE USED
2115
*/
2116
/**
2117
 * Undocumented function.
2118
 *
2119
 * @param string $text Text to debug
2120
 */
2121
function debugTeampass(string $text): void
2122
{
2123
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
2124
    if ($debugFile !== false) {
2125
        fputs($debugFile, $text);
2126
        fclose($debugFile);
2127
    }
2128
}
2129
2130
/**
2131
 * DELETE the file with expected command depending on server type.
2132
 *
2133
 * @param string $file     Path to file
2134
 * @param array  $SETTINGS Teampass settings
2135
 *
2136
 * @return void
2137
 */
2138
function fileDelete(string $file, array $SETTINGS): void
2139
{
2140
    // Load AntiXSS
2141
    $antiXss = new AntiXSS();
2142
    $file = $antiXss->xss_clean($file);
2143
    if (is_file($file)) {
2144
        unlink($file);
2145
    }
2146
}
2147
2148
/**
2149
 * Permits to extract the file extension.
2150
 *
2151
 * @param string $file File name
2152
 *
2153
 * @return string
2154
 */
2155
function getFileExtension(string $file): string
2156
{
2157
    if (strpos($file, '.') === false) {
2158
        return $file;
2159
    }
2160
2161
    return substr($file, strrpos($file, '.') + 1);
2162
}
2163
2164
/**
2165
 * Chmods files and folders with different permissions.
2166
 *
2167
 * This is an all-PHP alternative to using: \n
2168
 * <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2169
 * <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2170
 *
2171
 * @author Jeppe Toustrup (tenzer at tenzer dot dk)
2172
  *
2173
 * @param string $path      An either relative or absolute path to a file or directory which should be processed.
2174
 * @param int    $filePerm The permissions any found files should get.
2175
 * @param int    $dirPerm  The permissions any found folder should get.
2176
 *
2177
 * @return bool Returns TRUE if the path if found and FALSE if not.
2178
 *
2179
 * @warning The permission levels has to be entered in octal format, which
2180
 * normally means adding a zero ("0") in front of the permission level. \n
2181
 * More info at: http://php.net/chmod.
2182
*/
2183
2184
function recursiveChmod(
2185
    string $path,
2186
    int $filePerm = 0644,
2187
    int  $dirPerm = 0755
2188
) {
2189
    // Check if the path exists
2190
    if (! file_exists($path)) {
2191
        return false;
2192
    }
2193
2194
    // See whether this is a file
2195
    if (is_file($path)) {
2196
        // Chmod the file with our given filepermissions
2197
        try {
2198
            chmod($path, $filePerm);
2199
        } catch (Exception $e) {
2200
            return false;
2201
        }
2202
    // If this is a directory...
2203
    } elseif (is_dir($path)) {
2204
        // Then get an array of the contents
2205
        $foldersAndFiles = scandir($path);
2206
        // Remove "." and ".." from the list
2207
        $entries = array_slice($foldersAndFiles, 2);
2208
        // Parse every result...
2209
        foreach ($entries as $entry) {
2210
            // And call this function again recursively, with the same permissions
2211
            recursiveChmod($path.'/'.$entry, $filePerm, $dirPerm);
2212
        }
2213
2214
        // When we are done with the contents of the directory, we chmod the directory itself
2215
        try {
2216
            chmod($path, $filePerm);
2217
        } catch (Exception $e) {
2218
            return false;
2219
        }
2220
    }
2221
2222
    // Everything seemed to work out well, return true
2223
    return true;
2224
}
2225
2226
/**
2227
 * Check if user can access to this item.
2228
 *
2229
 * @param int   $item_id ID of item
2230
 * @param array $SETTINGS
2231
 *
2232
 * @return bool|string
2233
 */
2234
function accessToItemIsGranted(int $item_id, array $SETTINGS)
2235
{
2236
    
2237
    $session = SessionManager::getSession();
2238
    $session_groupes_visibles = $session->get('user-accessible_folders');
2239
    $session_list_restricted_folders_for_items = $session->set('system-list_restricted_folders_for_items');
2240
    // Load item data
2241
    $data = DB::queryFirstRow(
2242
        'SELECT id_tree
2243
        FROM ' . prefixTable('items') . '
2244
        WHERE id = %i',
2245
        $item_id
2246
    );
2247
    // Check if user can access this folder
2248
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2249
        // Now check if this folder is restricted to user
2250
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2251
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2252
        ) {
2253
            return 'ERR_FOLDER_NOT_ALLOWED';
2254
        }
2255
    }
2256
2257
    return true;
2258
}
2259
2260
/**
2261
 * Creates a unique key.
2262
 *
2263
 * @param int $lenght Key lenght
2264
 *
2265
 * @return string
2266
 */
2267
function uniqidReal(int $lenght = 13): string
2268
{
2269
    if (function_exists('random_bytes')) {
2270
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2271
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2272
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2273
    } else {
2274
        throw new Exception('no cryptographically secure random function available');
2275
    }
2276
2277
    return substr(bin2hex($bytes), 0, $lenght);
2278
}
2279
2280
/**
2281
 * Obfuscate an email.
2282
 *
2283
 * @param string $email Email address
2284
 *
2285
 * @return string
2286
 */
2287
function obfuscateEmail(string $email): string
2288
{
2289
    $email = explode("@", $email);
2290
    $name = $email[0];
2291
    if (strlen($name) > 3) {
2292
        $name = substr($name, 0, 2);
2293
        for ($i = 0; $i < strlen($email[0]) - 3; $i++) {
2294
            $name .= "*";
2295
        }
2296
        $name .= substr($email[0], -1, 1);
2297
    }
2298
    $host = explode(".", $email[1])[0];
2299
    if (strlen($host) > 3) {
2300
        $host = substr($host, 0, 1);
2301
        for ($i = 0; $i < strlen(explode(".", $email[1])[0]) - 2; $i++) {
2302
            $host .= "*";
2303
        }
2304
        $host .= substr(explode(".", $email[1])[0], -1, 1);
2305
    }
2306
    $email = $name . "@" . $host . "." . explode(".", $email[1])[1];
2307
    return $email;
2308
}
2309
2310
/**
2311
 * Perform a Query.
2312
 *
2313
 * @param array  $SETTINGS Teamapss settings
2314
 * @param string $fields   Fields to use
2315
 * @param string $table    Table to use
2316
 *
2317
 * @return array
2318
 */
2319
function performDBQuery(array $SETTINGS, string $fields, string $table): array
2320
{
2321
    // include librairies & connect to DB
2322
    //include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2323
2324
    // Load class DB
2325
    loadClasses('DB');
2326
    
2327
    // Insert log in DB
2328
    return DB::query(
2329
        'SELECT ' . $fields . '
2330
        FROM ' . prefixTable($table)
2331
    );
2332
}
2333
2334
/**
2335
 * Undocumented function.
2336
 *
2337
 * @param int $bytes Size of file
2338
 *
2339
 * @return string
2340
 */
2341
function formatSizeUnits(int $bytes): string
2342
{
2343
    if ($bytes >= 1073741824) {
2344
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2345
    } elseif ($bytes >= 1048576) {
2346
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2347
    } elseif ($bytes >= 1024) {
2348
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2349
    } elseif ($bytes > 1) {
2350
        $bytes .= ' bytes';
2351
    } elseif ($bytes === 1) {
2352
        $bytes .= ' byte';
2353
    } else {
2354
        $bytes = '0 bytes';
2355
    }
2356
2357
    return $bytes;
2358
}
2359
2360
/**
2361
 * Generate user pair of keys.
2362
 *
2363
 * @param string $userPwd User password
2364
 *
2365
 * @return array
2366
 */
2367
function generateUserKeys(string $userPwd): array
2368
{
2369
    //if (WIP === false) {
2370
        // Load classes
2371
        $rsa = new Crypt_RSA();
2372
        $cipher = new Crypt_AES();
2373
        // Create the private and public key
2374
        $res = $rsa->createKey(4096);
2375
        // Encrypt the privatekey
2376
        $cipher->setPassword($userPwd);
2377
        $privatekey = $cipher->encrypt($res['privatekey']);
2378
        return [
2379
            'private_key' => base64_encode($privatekey),
2380
            'public_key' => base64_encode($res['publickey']),
2381
            'private_key_clear' => base64_encode($res['privatekey']),
2382
        ];
2383
    /*} else {
2384
        // Create the keys
2385
        $keys = RSA::createKey();
2386
2387
        return [
2388
            'private_key' => base64_encode($keys->withPassword($userPwd)->toString('PKCS8')),
2389
            'public_key' => base64_encode($keys->getPublicKey()),
2390
            'private_key_clear' => base64_encode($keys->toString('PKCS8')),
2391
        ];
2392
    }*/
2393
}
2394
2395
/**
2396
 * Permits to decrypt the user's privatekey.
2397
 *
2398
 * @param string $userPwd        User password
2399
 * @param string $userPrivateKey User private key
2400
 *
2401
 * @return string|object
2402
 */
2403
function decryptPrivateKey(string $userPwd, string $userPrivateKey)
2404
{
2405
    if (empty($userPwd) === false) {
2406
        //if (WIP === false) {
2407
            // Load classes
2408
            $cipher = new Crypt_AES();
2409
            // Encrypt the privatekey
2410
            $cipher->setPassword($userPwd);
2411
            try {
2412
                return base64_encode((string) $cipher->decrypt(base64_decode($userPrivateKey)));
2413
            } catch (Exception $e) {
2414
                return $e;
2415
            }
2416
        /*} else {
2417
            //echo $userPrivateKey." ;; ".($userPwd)." ;;";
2418
            // Load and decrypt the private key
2419
            try {
2420
                $privateKey = PublicKeyLoader::loadPrivateKey(base64_decode($userPrivateKey), $userPwd)->withHash('sha1')->withMGFHash('sha1');
2421
                print_r($privateKey);
2422
                return base64_encode((string) $$privateKey);
2423
            } catch (NoKeyLoadedException $e) {
2424
                print_r($e);
2425
                return $e;
2426
            }
2427
        }*/
2428
    }
2429
    return '';
2430
}
2431
2432
/**
2433
 * Permits to encrypt the user's privatekey.
2434
 *
2435
 * @param string $userPwd        User password
2436
 * @param string $userPrivateKey User private key
2437
 *
2438
 * @return string
2439
 */
2440
function encryptPrivateKey(string $userPwd, string $userPrivateKey): string
2441
{
2442
    if (empty($userPwd) === false) {
2443
        //if (WIP === false) {
2444
            // Load classes
2445
            $cipher = new Crypt_AES();
2446
            // Encrypt the privatekey
2447
            $cipher->setPassword($userPwd);        
2448
            try {
2449
                return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2450
            } catch (Exception $e) {
2451
                return $e;
2452
            }
2453
        /*} else {
2454
            // Load the private key
2455
            $privateKey = PublicKeyLoader::load(base64_decode($userPrivateKey));
2456
2457
            try {
2458
                return base64_encode($privateKey->withPassword($userPwd));
2459
            } catch (Exception $e) {
2460
                return $e;
2461
            }
2462
        }*/
2463
    }
2464
    return '';
2465
}
2466
2467
/**
2468
 * Encrypts a string using AES.
2469
 *
2470
 * @param string $data String to encrypt
2471
 * @param string $key
2472
 *
2473
 * @return array
2474
 */
2475
function doDataEncryption(string $data, string $key = NULL): array
2476
{
2477
    //if (WIP === false) {
2478
        // Load classes
2479
        $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2480
        // Generate an object key
2481
        $objectKey = is_null($key) === true ? uniqidReal(KEY_LENGTH) : $key;
2482
        // Set it as password
2483
        $cipher->setPassword($objectKey);
2484
        return [
2485
            'encrypted' => base64_encode($cipher->encrypt($data)),
2486
            'objectKey' => base64_encode($objectKey),
2487
        ];
2488
    /*} else {
2489
2490
    }*/
2491
}
2492
2493
/**
2494
 * Decrypts a string using AES.
2495
 *
2496
 * @param string $data Encrypted data
2497
 * @param string $key  Key to uncrypt
2498
 *
2499
 * @return string
2500
 */
2501
function doDataDecryption(string $data, string $key): string
2502
{
2503
    //if (WIP === false) {
2504
        // Load classes
2505
        $cipher = new Crypt_AES();
2506
        // Set the object key
2507
        $cipher->setPassword(base64_decode($key));
2508
        return base64_encode((string) $cipher->decrypt(base64_decode($data)));
2509
    /*} else {
2510
2511
    }*/
2512
}
2513
2514
/**
2515
 * Encrypts using RSA a string using a public key.
2516
 *
2517
 * @param string $key       Key to be encrypted
2518
 * @param string $publicKey User public key
2519
 *
2520
 * @return string
2521
 */
2522
function encryptUserObjectKey(string $key, string $publicKey): string
2523
{
2524
    //if (WIP === false) {
2525
        // Load classes
2526
        $rsa = new Crypt_RSA();
2527
        $rsa->loadKey(base64_decode($publicKey));
0 ignored issues
show
Security Variable Injection introduced by
base64_decode($publicKey) can contain request data and is used in variable name context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. SessionInterface::get() returns request data
    in vendor/symfony/http-foundation/Session/Session.php on line 67
  2. encryptUserObjectKey() is called
    in sources/users.queries.php on line 2300
  3. Enters via parameter $publicKey
    in sources/main.functions.php on line 2522
  4. Data is passed through base64_decode(), and Crypt_RSA::loadKey() is called
    in sources/main.functions.php on line 2527
  5. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1815
  6. Data is passed through _parseKey(), and $this->_parseKey($key, $type) is assigned to $components
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1874
  7. $components['comment'] is assigned to property Crypt_RSA::$comment
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1897
  8. Read from property Crypt_RSA::$comment, and 'ssh-rsa ' . base64_encode($RSAPublicKey) . ' ' . $this->comment is assigned to $RSAPublicKey
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1095
  9. $RSAPublicKey is returned
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1097
  10. array('privatekey' => $this->_convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients), 'publickey' => $this->_convertPublicKey($n, $e), 'partialkey' => false) is returned
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 759
  11. $rsa->createKey(4096) is assigned to $res
    in sources/main.functions.php on line 2374
  12. Data is passed through base64_encode()
    in sources/main.functions.php on line 2380
  13. array('private_key' => base64_encode($privatekey), 'public_key' => base64_encode($res['publickey']), 'private_key_clear' => base64_encode($res['privatekey'])) is returned
    in sources/main.functions.php on line 2378
  14. generateUserKeys($passwordClear) is assigned to $userKeys
    in sources/identify.php on line 1528
  15. encryptUserObjectKey() is called
    in sources/identify.php on line 1569
  16. Enters via parameter $publicKey
    in sources/main.functions.php on line 2522
  17. Data is passed through base64_decode()
    in sources/main.functions.php on line 2527

Used in variable context

  1. Crypt_RSA::loadKey() is called
    in sources/main.functions.php on line 2527
  2. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1815
  3. Crypt_RSA::_parseKey() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1874
  4. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1153
  5. Data is passed through _extractBER()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1609
  6. $this->_extractBER($key) is assigned to $decoded
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1609
  7. Data is passed through _string_shift()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1614
  8. Data is passed through unpack()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1614
  9. extract() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1614

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
Security Variable Injection introduced by
base64_decode($publicKey) can contain request data and is used in variable name context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. SessionInterface::get() returns request data
    in vendor/symfony/http-foundation/Session/Session.php on line 67
  2. encryptUserObjectKey() is called
    in sources/users.queries.php on line 2300
  3. Enters via parameter $publicKey
    in sources/main.functions.php on line 2522
  4. Data is passed through base64_decode(), and Crypt_RSA::loadKey() is called
    in sources/main.functions.php on line 2527
  5. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1815
  6. Data is passed through _parseKey(), and $this->_parseKey($key, $type) is assigned to $components
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1874
  7. $components['comment'] is assigned to property Crypt_RSA::$comment
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1897
  8. Read from property Crypt_RSA::$comment, and 'ssh-rsa ' . base64_encode($RSAPublicKey) . ' ' . $this->comment is assigned to $RSAPublicKey
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1095
  9. $RSAPublicKey is returned
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1097
  10. array('privatekey' => $this->_convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients), 'publickey' => $this->_convertPublicKey($n, $e), 'partialkey' => false) is returned
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 759
  11. $rsa->createKey(4096) is assigned to $res
    in sources/main.functions.php on line 2374
  12. Data is passed through base64_encode()
    in sources/main.functions.php on line 2380
  13. array('private_key' => base64_encode($privatekey), 'public_key' => base64_encode($res['publickey']), 'private_key_clear' => base64_encode($res['privatekey'])) is returned
    in sources/main.functions.php on line 2378
  14. generateUserKeys($password) is assigned to $userKeys
    in sources/users.queries.php on line 2615
  15. encryptUserObjectKey() is called
    in sources/users.queries.php on line 2653
  16. Enters via parameter $publicKey
    in sources/main.functions.php on line 2522
  17. Data is passed through base64_decode()
    in sources/main.functions.php on line 2527

Used in variable context

  1. Crypt_RSA::loadKey() is called
    in sources/main.functions.php on line 2527
  2. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1815
  3. Crypt_RSA::_parseKey() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1874
  4. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1153
  5. Data is passed through _extractBER()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1609
  6. $this->_extractBER($key) is assigned to $decoded
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1609
  7. Data is passed through _string_shift()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1669
  8. $this->_string_shift($decoded, $length) is assigned to $paddedKey
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1669
  9. Data is passed through _string_shift()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1701
  10. Data is passed through unpack()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1701
  11. extract() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1701

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
Security Variable Injection introduced by
base64_decode($publicKey) can contain request data and is used in variable name context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. SessionInterface::get() returns request data
    in vendor/symfony/http-foundation/Session/Session.php on line 67
  2. encryptUserObjectKey() is called
    in sources/users.queries.php on line 2300
  3. Enters via parameter $publicKey
    in sources/main.functions.php on line 2522
  4. Data is passed through base64_decode()
    in sources/main.functions.php on line 2527

Used in variable context

  1. Crypt_RSA::loadKey() is called
    in sources/main.functions.php on line 2527
  2. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1815
  3. Crypt_RSA::_parseKey() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1874
  4. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1153
  5. Data is passed through preg_split()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1497
  6. preg_split('#\r\n|\r|\n#', $key) is assigned to $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1497
  7. Data is passed through array_slice()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1567
  8. Data is passed through array_map()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1567
  9. Data is passed through implode()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1567
  10. Data is passed through base64_decode()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1567
  11. base64_decode(implode('', array_map('trim', array_slice($key, $offset, $privateLength)))) is assigned to $private
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1567
  12. Data is passed through _string_shift()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1579
  13. Data is passed through unpack()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1579
  14. extract() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1579

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
Security Variable Injection introduced by
base64_decode($publicKey) can contain request data and is used in variable name context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. SessionInterface::get() returns request data
    in vendor/symfony/http-foundation/Session/Session.php on line 67
  2. encryptUserObjectKey() is called
    in sources/users.queries.php on line 2300
  3. Enters via parameter $publicKey
    in sources/main.functions.php on line 2522
  4. Data is passed through base64_decode(), and Crypt_RSA::loadKey() is called
    in sources/main.functions.php on line 2527
  5. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1815
  6. Data is passed through _parseKey(), and $this->_parseKey($key, $type) is assigned to $components
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1874
  7. $components['comment'] is assigned to property Crypt_RSA::$comment
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1897
  8. Read from property Crypt_RSA::$comment, and 'ssh-rsa ' . base64_encode($RSAPublicKey) . ' ' . $this->comment is assigned to $RSAPublicKey
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1095
  9. $RSAPublicKey is returned
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1097
  10. array('privatekey' => $this->_convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients), 'publickey' => $this->_convertPublicKey($n, $e), 'partialkey' => false) is returned
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 759
  11. $rsa->createKey(4096) is assigned to $res
    in sources/main.functions.php on line 2374
  12. Data is passed through base64_encode()
    in sources/main.functions.php on line 2380
  13. array('private_key' => base64_encode($privatekey), 'public_key' => base64_encode($res['publickey']), 'private_key_clear' => base64_encode($res['privatekey'])) is returned
    in sources/main.functions.php on line 2378
  14. generateUserKeys($password) is assigned to $userKeys
    in sources/users.queries.php on line 2615
  15. encryptUserObjectKey() is called
    in sources/users.queries.php on line 2653
  16. Enters via parameter $publicKey
    in sources/main.functions.php on line 2522
  17. Data is passed through base64_decode()
    in sources/main.functions.php on line 2527

Used in variable context

  1. Crypt_RSA::loadKey() is called
    in sources/main.functions.php on line 2527
  2. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1815
  3. Crypt_RSA::_parseKey() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1874
  4. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1153
  5. Data is passed through _extractBER()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1609
  6. $this->_extractBER($key) is assigned to $decoded
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1609
  7. Data is passed through _string_shift()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1628
  8. $this->_string_shift($decoded, $length) is assigned to $kdfoptions
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1628
  9. Data is passed through _string_shift()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1642
  10. Data is passed through unpack()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1642
  11. extract() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1642

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
Security Variable Injection introduced by
base64_decode($publicKey) can contain request data and is used in variable name context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. SessionInterface::get() returns request data
    in vendor/symfony/http-foundation/Session/Session.php on line 67
  2. encryptUserObjectKey() is called
    in sources/users.queries.php on line 2300
  3. Enters via parameter $publicKey
    in sources/main.functions.php on line 2522
  4. Data is passed through base64_decode()
    in sources/main.functions.php on line 2527

Used in variable context

  1. Crypt_RSA::loadKey() is called
    in sources/main.functions.php on line 2527
  2. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1815
  3. Crypt_RSA::_parseKey() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1874
  4. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1153
  5. Data is passed through preg_split()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1497
  6. preg_split('#\r\n|\r|\n#', $key) is assigned to $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1497
  7. Data is passed through array_slice()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1513
  8. Data is passed through array_map()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1513
  9. Data is passed through implode()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1513
  10. Data is passed through base64_decode()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1513
  11. base64_decode(implode('', array_map('trim', array_slice($key, 4, $publicLength)))) is assigned to $public
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1513
  12. Data is passed through substr()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1514
  13. substr($public, 11) is assigned to $public
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1514
  14. Data is passed through _string_shift()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1517
  15. Data is passed through unpack()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1517
  16. extract() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1517

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
Security Variable Injection introduced by
base64_decode($publicKey) can contain request data and is used in variable name context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. SessionInterface::get() returns request data
    in vendor/symfony/http-foundation/Session/Session.php on line 67
  2. encryptUserObjectKey() is called
    in sources/users.queries.php on line 2300
  3. Enters via parameter $publicKey
    in sources/main.functions.php on line 2522
  4. Data is passed through base64_decode()
    in sources/main.functions.php on line 2527

Used in variable context

  1. Crypt_RSA::loadKey() is called
    in sources/main.functions.php on line 2527
  2. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1815
  3. Crypt_RSA::_parseKey() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1874
  4. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1153
  5. Data is passed through _extractBER()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1609
  6. $this->_extractBER($key) is assigned to $decoded
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1609
  7. Data is passed through _string_shift()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1628
  8. $this->_string_shift($decoded, $length) is assigned to $kdfoptions
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1628
  9. Data is passed through _string_shift()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1642
  10. Data is passed through unpack()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1642
  11. extract() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1642

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
Security Variable Injection introduced by
base64_decode($publicKey) can contain request data and is used in variable name context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. SessionInterface::get() returns request data
    in vendor/symfony/http-foundation/Session/Session.php on line 67
  2. encryptUserObjectKey() is called
    in sources/users.queries.php on line 2300
  3. Enters via parameter $publicKey
    in sources/main.functions.php on line 2522
  4. Data is passed through base64_decode()
    in sources/main.functions.php on line 2527

Used in variable context

  1. Crypt_RSA::loadKey() is called
    in sources/main.functions.php on line 2527
  2. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1815
  3. Crypt_RSA::_parseKey() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1874
  4. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1153
  5. Data is passed through _extractBER()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1609
  6. $this->_extractBER($key) is assigned to $decoded
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1609
  7. Data is passed through _string_shift()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1614
  8. Data is passed through unpack()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1614
  9. extract() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1614

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
Security Variable Injection introduced by
base64_decode($publicKey) can contain request data and is used in variable name context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. SessionInterface::get() returns request data
    in vendor/symfony/http-foundation/Session/Session.php on line 67
  2. encryptUserObjectKey() is called
    in sources/users.queries.php on line 2300
  3. Enters via parameter $publicKey
    in sources/main.functions.php on line 2522
  4. Data is passed through base64_decode(), and Crypt_RSA::loadKey() is called
    in sources/main.functions.php on line 2527
  5. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1815
  6. Data is passed through _parseKey(), and $this->_parseKey($key, $type) is assigned to $components
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1874
  7. $components['comment'] is assigned to property Crypt_RSA::$comment
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1897
  8. Read from property Crypt_RSA::$comment, and 'ssh-rsa ' . base64_encode($RSAPublicKey) . ' ' . $this->comment is assigned to $RSAPublicKey
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1095
  9. $RSAPublicKey is returned
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1097
  10. array('privatekey' => $this->_convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients), 'publickey' => $this->_convertPublicKey($n, $e), 'partialkey' => false) is returned
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 759
  11. $rsa->createKey(4096) is assigned to $res
    in sources/main.functions.php on line 2374
  12. Data is passed through base64_encode()
    in sources/main.functions.php on line 2380
  13. array('private_key' => base64_encode($privatekey), 'public_key' => base64_encode($res['publickey']), 'private_key_clear' => base64_encode($res['privatekey'])) is returned
    in sources/main.functions.php on line 2378
  14. generateUserKeys($password) is assigned to $userKeys
    in sources/users.queries.php on line 180
  15. encryptUserObjectKey() is called
    in sources/users.queries.php on line 304
  16. Enters via parameter $publicKey
    in sources/main.functions.php on line 2522
  17. Data is passed through base64_decode()
    in sources/main.functions.php on line 2527

Used in variable context

  1. Crypt_RSA::loadKey() is called
    in sources/main.functions.php on line 2527
  2. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1815
  3. Crypt_RSA::_parseKey() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1874
  4. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1153
  5. Data is passed through _extractBER()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1609
  6. $this->_extractBER($key) is assigned to $decoded
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1609
  7. Data is passed through _string_shift()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1655
  8. Data is passed through unpack()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1655
  9. extract() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1655

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
Security Variable Injection introduced by
base64_decode($publicKey) can contain request data and is used in variable name context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. SessionInterface::get() returns request data
    in vendor/symfony/http-foundation/Session/Session.php on line 67
  2. encryptUserObjectKey() is called
    in sources/users.queries.php on line 2300
  3. Enters via parameter $publicKey
    in sources/main.functions.php on line 2522
  4. Data is passed through base64_decode()
    in sources/main.functions.php on line 2527

Used in variable context

  1. Crypt_RSA::loadKey() is called
    in sources/main.functions.php on line 2527
  2. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1815
  3. Crypt_RSA::_parseKey() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1874
  4. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1153
  5. Data is passed through _extractBER()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1609
  6. $this->_extractBER($key) is assigned to $decoded
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1609
  7. Data is passed through _string_shift()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1669
  8. $this->_string_shift($decoded, $length) is assigned to $paddedKey
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1669
  9. Data is passed through _string_shift()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1694
  10. Data is passed through unpack()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1694
  11. extract() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1694

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
Security Variable Injection introduced by
base64_decode($publicKey) can contain request data and is used in variable name context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. SessionInterface::get() returns request data
    in vendor/symfony/http-foundation/Session/Session.php on line 67
  2. encryptUserObjectKey() is called
    in sources/users.queries.php on line 2300
  3. Enters via parameter $publicKey
    in sources/main.functions.php on line 2522
  4. Data is passed through base64_decode()
    in sources/main.functions.php on line 2527

Used in variable context

  1. Crypt_RSA::loadKey() is called
    in sources/main.functions.php on line 2527
  2. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1815
  3. Crypt_RSA::_parseKey() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1874
  4. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1153
  5. Data is passed through explode()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1429
  6. explode(' ', $key, 3) is assigned to $parts
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1429
  7. Data is passed through base64_decode()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1431
  8. IssetNode ? base64_decode($parts[1]) : false is assigned to $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1431
  9. Data is passed through _string_shift()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1448
  10. Data is passed through unpack()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1448
  11. extract() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1448

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
Security Variable Injection introduced by
base64_decode($publicKey) can contain request data and is used in variable name context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. SessionInterface::get() returns request data
    in vendor/symfony/http-foundation/Session/Session.php on line 67
  2. encryptUserObjectKey() is called
    in sources/users.queries.php on line 2300
  3. Enters via parameter $publicKey
    in sources/main.functions.php on line 2522
  4. Data is passed through base64_decode(), and Crypt_RSA::loadKey() is called
    in sources/main.functions.php on line 2527
  5. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1815
  6. Data is passed through _parseKey(), and $this->_parseKey($key, $type) is assigned to $components
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1874
  7. $components['comment'] is assigned to property Crypt_RSA::$comment
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1897
  8. Read from property Crypt_RSA::$comment, and 'ssh-rsa ' . base64_encode($RSAPublicKey) . ' ' . $this->comment is assigned to $RSAPublicKey
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1095
  9. $RSAPublicKey is returned
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1097
  10. array('privatekey' => $this->_convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients), 'publickey' => $this->_convertPublicKey($n, $e), 'partialkey' => false) is returned
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 759
  11. $rsa->createKey(4096) is assigned to $res
    in sources/main.functions.php on line 2374
  12. Data is passed through base64_encode()
    in sources/main.functions.php on line 2380
  13. array('private_key' => base64_encode($privatekey), 'public_key' => base64_encode($res['publickey']), 'private_key_clear' => base64_encode($res['privatekey'])) is returned
    in sources/main.functions.php on line 2378
  14. generateUserKeys($passwordClear) is assigned to $userKeys
    in sources/identify.php on line 1528
  15. encryptUserObjectKey() is called
    in sources/identify.php on line 1569
  16. Enters via parameter $publicKey
    in sources/main.functions.php on line 2522
  17. Data is passed through base64_decode()
    in sources/main.functions.php on line 2527

Used in variable context

  1. Crypt_RSA::loadKey() is called
    in sources/main.functions.php on line 2527
  2. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1815
  3. Crypt_RSA::_parseKey() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1874
  4. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1153
  5. Data is passed through explode()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1429
  6. explode(' ', $key, 3) is assigned to $parts
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1429
  7. Data is passed through base64_decode()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1431
  8. IssetNode ? base64_decode($parts[1]) : false is assigned to $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1431
  9. Data is passed through _string_shift()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1455
  10. Data is passed through unpack()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1455
  11. extract() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1455

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
Security Variable Injection introduced by
base64_decode($publicKey) can contain request data and is used in variable name context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. SessionInterface::get() returns request data
    in vendor/symfony/http-foundation/Session/Session.php on line 67
  2. encryptUserObjectKey() is called
    in sources/users.queries.php on line 2300
  3. Enters via parameter $publicKey
    in sources/main.functions.php on line 2522
  4. Data is passed through base64_decode(), and Crypt_RSA::loadKey() is called
    in sources/main.functions.php on line 2527
  5. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1815
  6. Data is passed through _parseKey(), and $this->_parseKey($key, $type) is assigned to $components
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1874
  7. $components['comment'] is assigned to property Crypt_RSA::$comment
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1897
  8. Read from property Crypt_RSA::$comment, and 'ssh-rsa ' . base64_encode($RSAPublicKey) . ' ' . $this->comment is assigned to $RSAPublicKey
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1095
  9. $RSAPublicKey is returned
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1097
  10. array('privatekey' => $this->_convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients), 'publickey' => $this->_convertPublicKey($n, $e), 'partialkey' => false) is returned
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 759
  11. $rsa->createKey(4096) is assigned to $res
    in sources/main.functions.php on line 2374
  12. Data is passed through base64_encode()
    in sources/main.functions.php on line 2380
  13. array('private_key' => base64_encode($privatekey), 'public_key' => base64_encode($res['publickey']), 'private_key_clear' => base64_encode($res['privatekey'])) is returned
    in sources/main.functions.php on line 2378
  14. generateUserKeys($passwordClear) is assigned to $userKeys
    in sources/identify.php on line 1528
  15. encryptUserObjectKey() is called
    in sources/identify.php on line 1569
  16. Enters via parameter $publicKey
    in sources/main.functions.php on line 2522
  17. Data is passed through base64_decode()
    in sources/main.functions.php on line 2527

Used in variable context

  1. Crypt_RSA::loadKey() is called
    in sources/main.functions.php on line 2527
  2. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1815
  3. Crypt_RSA::_parseKey() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1874
  4. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1153
  5. Data is passed through _extractBER()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1609
  6. $this->_extractBER($key) is assigned to $decoded
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1609
  7. Data is passed through _string_shift()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1669
  8. $this->_string_shift($decoded, $length) is assigned to $paddedKey
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1669
  9. Data is passed through _string_shift()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1694
  10. Data is passed through unpack()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1694
  11. extract() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1694

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
2528
        // Encrypt
2529
        return base64_encode($rsa->encrypt(base64_decode($key)));
2530
    /*} else {
2531
2532
    }*/
2533
}
2534
2535
/**
2536
 * Decrypts using RSA an encrypted string using a private key.
2537
 *
2538
 * @param string $key        Encrypted key
2539
 * @param string $privateKey User private key
2540
 *
2541
 * @return string
2542
 */
2543
function decryptUserObjectKey(string $key, string $privateKey): string
2544
{
2545
    //if (WIP === false) {
2546
        // Load classes
2547
        $rsa = new Crypt_RSA();
2548
        $rsa->loadKey(base64_decode($privateKey));
0 ignored issues
show
Security Variable Injection introduced by
base64_decode($privateKey) can contain request data and is used in variable name context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. SessionInterface::get() returns request data
    in vendor/symfony/http-foundation/Session/Session.php on line 67
  2. encryptUserObjectKey() is called
    in sources/users.queries.php on line 2300
  3. Enters via parameter $publicKey
    in sources/main.functions.php on line 2522
  4. Data is passed through base64_decode(), and Crypt_RSA::loadKey() is called
    in sources/main.functions.php on line 2527
  5. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1815
  6. Data is passed through _parseKey(), and $this->_parseKey($key, $type) is assigned to $components
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1874
  7. $components['comment'] is assigned to property Crypt_RSA::$comment
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1897
  8. Read from property Crypt_RSA::$comment, and 'ssh-rsa ' . base64_encode($RSAPublicKey) . ' ' . $this->comment is assigned to $RSAPublicKey
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1095
  9. $RSAPublicKey is returned
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1097
  10. array('privatekey' => $this->_convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients), 'publickey' => $this->_convertPublicKey($n, $e), 'partialkey' => false) is returned
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 759
  11. $rsa->createKey(4096) is assigned to $res
    in sources/main.functions.php on line 2374
  12. Data is passed through base64_encode()
    in sources/main.functions.php on line 2380
  13. array('private_key' => base64_encode($privatekey), 'public_key' => base64_encode($res['publickey']), 'private_key_clear' => base64_encode($res['privatekey'])) is returned
    in sources/main.functions.php on line 2378
  14. generateUserKeys($passwordClear) is assigned to $userKeys
    in sources/identify.php on line 1065
  15. array('public_key' => $userKeys['public_key'], 'private_key_clear' => $userKeys['private_key_clear'], 'update_keys_in_db' => array('public_key' => $userKeys['public_key'], 'private_key' => $userKeys['private_key'])) is returned
    in sources/identify.php on line 1067
  16. prepareUserEncryptionKeys($userInfo, $passwordClear) is assigned to $returnKeys
    in sources/identify.php on line 515
  17. decryptUserObjectKey() is called
    in sources/identify.php on line 522
  18. Enters via parameter $privateKey
    in sources/main.functions.php on line 2543
  19. Data is passed through base64_decode()
    in sources/main.functions.php on line 2548

Used in variable context

  1. Crypt_RSA::loadKey() is called
    in sources/main.functions.php on line 2548
  2. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1815
  3. Crypt_RSA::_parseKey() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1874
  4. Enters via parameter $key
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1153
  5. Data is passed through _extractBER()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1609
  6. $this->_extractBER($key) is assigned to $decoded
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1609
  7. Data is passed through _string_shift()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1614
  8. Data is passed through unpack()
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1614
  9. extract() is called
    in vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php on line 1614

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
2549
        // Decrypt
2550
        try {
2551
            $tmpValue = $rsa->decrypt(base64_decode($key));
2552
            if (is_bool($tmpValue) === false) {
0 ignored issues
show
introduced by
The condition is_bool($tmpValue) === false is always true.
Loading history...
2553
                $ret = base64_encode((string) /** @scrutinizer ignore-type */$tmpValue);
2554
            } else {
2555
                $ret = '';
2556
            }
2557
        } catch (Exception $e) {
2558
            return $e;
2559
        }
2560
        /*} else {
2561
2562
        }*/
2563
2564
    return $ret;
2565
}
2566
2567
/**
2568
 * Encrypts a file.
2569
 *
2570
 * @param string $fileInName File name
2571
 * @param string $fileInPath Path to file
2572
 *
2573
 * @return array
2574
 */
2575
function encryptFile(string $fileInName, string $fileInPath): array
2576
{
2577
    if (defined('FILE_BUFFER_SIZE') === false) {
2578
        define('FILE_BUFFER_SIZE', 128 * 1024);
2579
    }
2580
    //if (WIP === false) {
2581
        // Load classes
2582
        $cipher = new Crypt_AES();
2583
        // Generate an object key
2584
        $objectKey = uniqidReal(32);
2585
        // Set it as password
2586
        $cipher->setPassword($objectKey);
2587
        // Prevent against out of memory
2588
        $cipher->enableContinuousBuffer();
2589
        //$cipher->disablePadding();
2590
2591
        // Encrypt the file content
2592
        $plaintext = file_get_contents(
2593
            filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL)
2594
        );
2595
        $ciphertext = $cipher->encrypt($plaintext);
2596
        // Save new file
2597
        $hash = md5($plaintext);
2598
        $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2599
        file_put_contents($fileOut, $ciphertext);
2600
        unlink($fileInPath . '/' . $fileInName);
2601
        return [
2602
            'fileHash' => base64_encode($hash),
2603
            'objectKey' => base64_encode($objectKey),
2604
        ];
2605
    /*} else {
2606
2607
    }*/
2608
}
2609
2610
/**
2611
 * Decrypt a file.
2612
 *
2613
 * @param string $fileName File name
2614
 * @param string $filePath Path to file
2615
 * @param string $key      Key to use
2616
 *
2617
 * @return string
2618
 */
2619
function decryptFile(string $fileName, string $filePath, string $key): string
2620
{
2621
    if (! defined('FILE_BUFFER_SIZE')) {
2622
        define('FILE_BUFFER_SIZE', 128 * 1024);
2623
    }
2624
    
2625
    // Get file name
2626
    $fileName = base64_decode($fileName);
2627
2628
    //if (WIP === false) {
2629
        // Load classes
2630
        $cipher = new Crypt_AES();
2631
        // Set the object key
2632
        $cipher->setPassword(base64_decode($key));
2633
        // Prevent against out of memory
2634
        $cipher->enableContinuousBuffer();
2635
        $cipher->disablePadding();
2636
        // Get file content
2637
        $ciphertext = file_get_contents($filePath . '/' . TP_FILE_PREFIX . $fileName);
2638
        // Decrypt file content and return
2639
        return base64_encode($cipher->decrypt($ciphertext));
2640
    /*} else {
2641
        
2642
    }*/
2643
}
2644
2645
/**
2646
 * Generate a simple password
2647
 *
2648
 * @param int $length Length of string
2649
 * @param bool $symbolsincluded Allow symbols
2650
 *
2651
 * @return string
2652
 */
2653
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2654
{
2655
    // Generate new user password
2656
    $small_letters = range('a', 'z');
2657
    $big_letters = range('A', 'Z');
2658
    $digits = range(0, 9);
2659
    $symbols = $symbolsincluded === true ?
2660
        ['#', '_', '-', '@', '$', '+', '!'] : [];
2661
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2662
    $count = count($res);
2663
    // first variant
2664
2665
    $random_string = '';
2666
    for ($i = 0; $i < $length; ++$i) {
2667
        $random_string .= $res[random_int(0, $count - 1)];
2668
    }
2669
2670
    return $random_string;
2671
}
2672
2673
/**
2674
 * Permit to store the sharekey of an object for users.
2675
 *
2676
 * @param string $object_name             Type for table selection
2677
 * @param int    $post_folder_is_personal Personal
2678
 * @param int    $post_folder_id          Folder
2679
 * @param int    $post_object_id          Object
2680
 * @param string $objectKey               Object key
2681
 * @param array  $SETTINGS                Teampass settings
2682
 * @param int    $user_id                 User ID if needed
2683
 * @param bool   $onlyForUser                 User ID if needed
2684
 * @param bool   $deleteAll                 User ID if needed
2685
 * @param array  $objectKeyArray                 User ID if needed
2686
 *
2687
 * @return void
2688
 */
2689
function storeUsersShareKey(
2690
    string $object_name,
2691
    int $post_folder_is_personal,
2692
    int $post_folder_id,
2693
    int $post_object_id,
2694
    string $objectKey,
2695
    array $SETTINGS,
2696
    bool $onlyForUser = false,
2697
    bool $deleteAll = true,
2698
    array $objectKeyArray = []
2699
): void {
2700
    
2701
    $session = SessionManager::getSession();
2702
    loadClasses('DB');
2703
2704
    // Delete existing entries for this object
2705
    if ($deleteAll === true) {
2706
        DB::delete(
2707
            $object_name,
2708
            'object_id = %i',
2709
            $post_object_id
2710
        );
2711
    }
2712
    
2713
    if (
2714
        //((int) $post_folder_is_personal === 1 && in_array($post_folder_id, $superGlobal->get('personal_folders', 'SESSION')) === true) ||
2715
        $onlyForUser === true || (int) $post_folder_is_personal === 1
2716
    ) {
2717
        // Only create the sharekey for a user
2718
        $user = DB::queryFirstRow(
2719
            'SELECT public_key
2720
            FROM ' . prefixTable('users') . '
2721
            WHERE id = ' . (int) $session->get('user-id') . '
2722
            AND public_key != ""'
2723
        );
2724
2725
        if (empty($objectKey) === false) {
2726
            DB::insert(
2727
                $object_name,
2728
                [
2729
                    'object_id' => (int) $post_object_id,
2730
                    'user_id' => (int) $session->get('user-id'),
2731
                    'share_key' => encryptUserObjectKey(
2732
                        $objectKey,
2733
                        $user['public_key']
2734
                    ),
2735
                ]
2736
            );
2737
        } else if (count($objectKeyArray) > 0) {
2738
            foreach ($objectKeyArray as $object) {
2739
                DB::insert(
2740
                    $object_name,
2741
                    [
2742
                        'object_id' => (int) $object['objectId'],
2743
                        'user_id' => (int) $session->get('user-id'),
2744
                        'share_key' => encryptUserObjectKey(
2745
                            $object['objectKey'],
2746
                            $user['public_key']
2747
                        ),
2748
                    ]
2749
                );
2750
            }
2751
        }
2752
    } else {
2753
        // Create sharekey for each user
2754
        //DB::debugmode(true);
2755
        $users = DB::query(
2756
            'SELECT id, public_key
2757
            FROM ' . prefixTable('users') . '
2758
            WHERE ' . ($onlyForUser === true ? 
0 ignored issues
show
introduced by
The condition $onlyForUser === true is always false.
Loading history...
2759
                'id IN ("' . TP_USER_ID . '","' . $session->get('user-id') . '") ' : 
2760
                'id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '") ') . '
2761
            AND public_key != ""'
2762
        );
2763
        //DB::debugmode(false);
2764
        foreach ($users as $user) {
2765
            // Insert in DB the new object key for this item by user
2766
            if (count($objectKeyArray) === 0) {
2767
                DB::insert(
2768
                    $object_name,
2769
                    [
2770
                        'object_id' => $post_object_id,
2771
                        'user_id' => (int) $user['id'],
2772
                        'share_key' => encryptUserObjectKey(
2773
                            $objectKey,
2774
                            $user['public_key']
2775
                        ),
2776
                    ]
2777
                );
2778
            } else {
2779
                foreach ($objectKeyArray as $object) {
2780
                    DB::insert(
2781
                        $object_name,
2782
                        [
2783
                            'object_id' => (int) $object['objectId'],
2784
                            'user_id' => (int) $user['id'],
2785
                            'share_key' => encryptUserObjectKey(
2786
                                $object['objectKey'],
2787
                                $user['public_key']
2788
                            ),
2789
                        ]
2790
                    );
2791
                }
2792
            }
2793
        }
2794
    }
2795
}
2796
2797
/**
2798
 * Is this string base64 encoded?
2799
 *
2800
 * @param string $str Encoded string?
2801
 *
2802
 * @return bool
2803
 */
2804
function isBase64(string $str): bool
2805
{
2806
    $str = (string) trim($str);
2807
    if (! isset($str[0])) {
2808
        return false;
2809
    }
2810
2811
    $base64String = (string) base64_decode($str, true);
2812
    if ($base64String && base64_encode($base64String) === $str) {
2813
        return true;
2814
    }
2815
2816
    return false;
2817
}
2818
2819
/**
2820
 * Undocumented function
2821
 *
2822
 * @param string $field Parameter
2823
 *
2824
 * @return array|bool|resource|string
2825
 */
2826
function filterString(string $field)
2827
{
2828
    // Sanitize string
2829
    $field = filter_var(trim($field), FILTER_SANITIZE_FULL_SPECIAL_CHARS);
2830
    if (empty($field) === false) {
2831
        // Load AntiXSS
2832
        $antiXss = new AntiXSS();
2833
        // Return
2834
        return $antiXss->xss_clean($field);
2835
    }
2836
2837
    return false;
2838
}
2839
2840
/**
2841
 * CHeck if provided credentials are allowed on server
2842
 *
2843
 * @param string $login    User Login
2844
 * @param string $password User Pwd
2845
 * @param array  $SETTINGS Teampass settings
2846
 *
2847
 * @return bool
2848
 */
2849
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
2850
{
2851
    // Build ldap configuration array
2852
    $config = [
2853
        // Mandatory Configuration Options
2854
        'hosts' => [$SETTINGS['ldap_hosts']],
2855
        'base_dn' => $SETTINGS['ldap_bdn'],
2856
        'username' => $SETTINGS['ldap_username'],
2857
        'password' => $SETTINGS['ldap_password'],
2858
2859
        // Optional Configuration Options
2860
        'port' => $SETTINGS['ldap_port'],
2861
        'use_ssl' => (int) $SETTINGS['ldap_ssl'] === 1 ? true : false,
2862
        'use_tls' => (int) $SETTINGS['ldap_tls'] === 1 ? true : false,
2863
        'version' => 3,
2864
        'timeout' => 5,
2865
        'follow_referrals' => false,
2866
2867
        // Custom LDAP Options
2868
        'options' => [
2869
            // See: http://php.net/ldap_set_option
2870
            LDAP_OPT_X_TLS_REQUIRE_CERT => (isset($SETTINGS['ldap_tls_certiface_check']) ? $SETTINGS['ldap_tls_certiface_check'] : LDAP_OPT_X_TLS_HARD),
2871
        ],
2872
    ];
2873
    
2874
    $connection = new Connection($config);
2875
    // Connect to LDAP
2876
    try {
2877
        $connection->connect();
2878
    } catch (\LdapRecord\Auth\BindException $e) {
2879
        $error = $e->getDetailedError();
2880
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
2881
        return false;
2882
    }
2883
2884
    // Authenticate user
2885
    try {
2886
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory') {
2887
            $connection->auth()->attempt($login, $password, $stayAuthenticated = true);
2888
        } else {
2889
            $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);
2890
        }
2891
    } catch (\LdapRecord\Auth\BindException $e) {
2892
        $error = $e->getDetailedError();
2893
        echo 'Error : '.$error->getErrorCode().' - '.$error->getErrorMessage(). '<br>'.$error->getDiagnosticMessage();
2894
        return false;
2895
    }
2896
2897
    return true;
2898
}
2899
2900
/**
2901
 * Removes from DB all sharekeys of this user
2902
 *
2903
 * @param int $userId User's id
2904
 * @param array   $SETTINGS Teampass settings
2905
 *
2906
 * @return bool
2907
 */
2908
function deleteUserObjetsKeys(int $userId, array $SETTINGS = []): bool
2909
{
2910
    // Load class DB
2911
    loadClasses('DB');
2912
2913
    // Remove all item sharekeys items
2914
    // expect if personal item
2915
    DB::delete(
2916
        prefixTable('sharekeys_items'),
2917
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2918
        $userId
2919
    );
2920
    // Remove all item sharekeys files
2921
    DB::delete(
2922
        prefixTable('sharekeys_files'),
2923
        'user_id = %i AND object_id NOT IN (
2924
            SELECT f.id 
2925
            FROM ' . prefixTable('items') . ' AS i 
2926
            INNER JOIN ' . prefixTable('files') . ' AS f ON f.id_item = i.id
2927
            WHERE i.perso = 1
2928
        )',
2929
        $userId
2930
    );
2931
    // Remove all item sharekeys fields
2932
    DB::delete(
2933
        prefixTable('sharekeys_fields'),
2934
        'user_id = %i AND object_id NOT IN (
2935
            SELECT c.id 
2936
            FROM ' . prefixTable('items') . ' AS i 
2937
            INNER JOIN ' . prefixTable('categories_items') . ' AS c ON c.item_id = i.id
2938
            WHERE i.perso = 1
2939
        )',
2940
        $userId
2941
    );
2942
    // Remove all item sharekeys logs
2943
    DB::delete(
2944
        prefixTable('sharekeys_logs'),
2945
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2946
        $userId
2947
    );
2948
    // Remove all item sharekeys suggestions
2949
    DB::delete(
2950
        prefixTable('sharekeys_suggestions'),
2951
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2952
        $userId
2953
    );
2954
    return false;
2955
}
2956
2957
/**
2958
 * Manage list of timezones   $SETTINGS Teampass settings
2959
 *
2960
 * @return array
2961
 */
2962
function timezone_list()
2963
{
2964
    static $timezones = null;
2965
    if ($timezones === null) {
2966
        $timezones = [];
2967
        $offsets = [];
2968
        $now = new DateTime('now', new DateTimeZone('UTC'));
2969
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
2970
            $now->setTimezone(new DateTimeZone($timezone));
2971
            $offsets[] = $offset = $now->getOffset();
2972
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
2973
        }
2974
2975
        array_multisort($offsets, $timezones);
2976
    }
2977
2978
    return $timezones;
2979
}
2980
2981
/**
2982
 * Provide timezone offset
2983
 *
2984
 * @param int $offset Timezone offset
2985
 *
2986
 * @return string
2987
 */
2988
function format_GMT_offset($offset): string
2989
{
2990
    $hours = intval($offset / 3600);
2991
    $minutes = abs(intval($offset % 3600 / 60));
2992
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
2993
}
2994
2995
/**
2996
 * Provides timezone name
2997
 *
2998
 * @param string $name Timezone name
2999
 *
3000
 * @return string
3001
 */
3002
function format_timezone_name($name): string
3003
{
3004
    $name = str_replace('/', ', ', $name);
3005
    $name = str_replace('_', ' ', $name);
3006
3007
    return str_replace('St ', 'St. ', $name);
3008
}
3009
3010
/**
3011
 * Provides info if user should use MFA based on roles
3012
 *
3013
 * @param string $userRolesIds  User roles ids
3014
 * @param string $mfaRoles      Roles for which MFA is requested
3015
 *
3016
 * @return bool
3017
 */
3018
function mfa_auth_requested_roles(string $userRolesIds, string $mfaRoles): bool
3019
{
3020
    if (empty($mfaRoles) === true) {
3021
        return true;
3022
    }
3023
3024
    $mfaRoles = array_values(json_decode($mfaRoles, true));
3025
    $userRolesIds = array_filter(explode(';', $userRolesIds));
3026
    if (count($mfaRoles) === 0 || count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
3027
        return true;
3028
    }
3029
3030
    return false;
3031
}
3032
3033
/**
3034
 * Permits to clean a string for export purpose
3035
 *
3036
 * @param string $text
3037
 * @param bool $emptyCheckOnly
3038
 * 
3039
 * @return string
3040
 */
3041
function cleanStringForExport(string $text, bool $emptyCheckOnly = false): string
3042
{
3043
    if (is_null($text) === true || empty($text) === true) {
3044
        return '';
3045
    }
3046
    // only expected to check if $text was empty
3047
    elseif ($emptyCheckOnly === true) {
3048
        return $text;
3049
    }
3050
3051
    return strip_tags(
3052
        cleanString(
3053
            html_entity_decode($text, ENT_QUOTES | ENT_XHTML, 'UTF-8'),
3054
            true)
3055
        );
3056
}
3057
3058
/**
3059
 * Permits to check if user ID is valid
3060
 *
3061
 * @param integer $post_user_id
3062
 * @return bool
3063
 */
3064
function isUserIdValid($userId): bool
3065
{
3066
    if (is_null($userId) === false
3067
        && isset($userId) === true
3068
        && empty($userId) === false
3069
    ) {
3070
        return true;
3071
    }
3072
    return false;
3073
}
3074
3075
/**
3076
 * Check if a key exists and if its value equal the one expected
3077
 *
3078
 * @param string $key
3079
 * @param integer|string $value
3080
 * @param array $array
3081
 * 
3082
 * @return boolean
3083
 */
3084
function isKeyExistingAndEqual(
3085
    string $key,
3086
    /*PHP8 - integer|string*/$value,
3087
    array $array
3088
): bool
3089
{
3090
    if (isset($array[$key]) === true
3091
        && (is_int($value) === true ?
3092
            (int) $array[$key] === $value :
3093
            (string) $array[$key] === $value)
3094
    ) {
3095
        return true;
3096
    }
3097
    return false;
3098
}
3099
3100
/**
3101
 * Check if a variable is not set or equal to a value
3102
 *
3103
 * @param string|null $var
3104
 * @param integer|string $value
3105
 * 
3106
 * @return boolean
3107
 */
3108
function isKeyNotSetOrEqual(
3109
    /*PHP8 - string|null*/$var,
3110
    /*PHP8 - integer|string*/$value
3111
): bool
3112
{
3113
    if (isset($var) === false
3114
        || (is_int($value) === true ?
3115
            (int) $var === $value :
3116
            (string) $var === $value)
3117
    ) {
3118
        return true;
3119
    }
3120
    return false;
3121
}
3122
3123
/**
3124
 * Check if a key exists and if its value < to the one expected
3125
 *
3126
 * @param string $key
3127
 * @param integer $value
3128
 * @param array $array
3129
 * 
3130
 * @return boolean
3131
 */
3132
function isKeyExistingAndInferior(string $key, int $value, array $array): bool
3133
{
3134
    if (isset($array[$key]) === true && (int) $array[$key] < $value) {
3135
        return true;
3136
    }
3137
    return false;
3138
}
3139
3140
/**
3141
 * Check if a key exists and if its value > to the one expected
3142
 *
3143
 * @param string $key
3144
 * @param integer $value
3145
 * @param array $array
3146
 * 
3147
 * @return boolean
3148
 */
3149
function isKeyExistingAndSuperior(string $key, int $value, array $array): bool
3150
{
3151
    if (isset($array[$key]) === true && (int) $array[$key] > $value) {
3152
        return true;
3153
    }
3154
    return false;
3155
}
3156
3157
/**
3158
 * Check if values in array are set
3159
 * Return true if all set
3160
 * Return false if one of them is not set
3161
 *
3162
 * @param array $arrayOfValues
3163
 * @return boolean
3164
 */
3165
function isSetArrayOfValues(array $arrayOfValues): bool
3166
{
3167
    foreach($arrayOfValues as $value) {
3168
        if (isset($value) === false) {
3169
            return false;
3170
        }
3171
    }
3172
    return true;
3173
}
3174
3175
/**
3176
 * Check if values in array are set
3177
 * Return true if all set
3178
 * Return false if one of them is not set
3179
 *
3180
 * @param array $arrayOfValues
3181
 * @param integer|string $value
3182
 * @return boolean
3183
 */
3184
function isArrayOfVarsEqualToValue(
3185
    array $arrayOfVars,
3186
    /*PHP8 - integer|string*/$value
3187
) : bool
3188
{
3189
    foreach($arrayOfVars as $variable) {
3190
        if ($variable !== $value) {
3191
            return false;
3192
        }
3193
    }
3194
    return true;
3195
}
3196
3197
/**
3198
 * Checks if at least one variable in array is equal to value
3199
 *
3200
 * @param array $arrayOfValues
3201
 * @param integer|string $value
3202
 * @return boolean
3203
 */
3204
function isOneVarOfArrayEqualToValue(
3205
    array $arrayOfVars,
3206
    /*PHP8 - integer|string*/$value
3207
) : bool
3208
{
3209
    foreach($arrayOfVars as $variable) {
3210
        if ($variable === $value) {
3211
            return true;
3212
        }
3213
    }
3214
    return false;
3215
}
3216
3217
/**
3218
 * Checks is value is null, not set OR empty
3219
 *
3220
 * @param string|int|null $value
3221
 * @return boolean
3222
 */
3223
function isValueSetNullEmpty(/*PHP8 - string|int|null*/ $value) : bool
3224
{
3225
    if (is_null($value) === true || isset($value) === false || empty($value) === true) {
3226
        return true;
3227
    }
3228
    return false;
3229
}
3230
3231
/**
3232
 * Checks if value is set and if empty is equal to passed boolean
3233
 *
3234
 * @param string|int $value
3235
 * @param boolean $boolean
3236
 * @return boolean
3237
 */
3238
function isValueSetEmpty($value, $boolean = true) : bool
3239
{
3240
    if (isset($value) === true && empty($value) === $boolean) {
3241
        return true;
3242
    }
3243
    return false;
3244
}
3245
3246
/**
3247
 * Ensure Complexity is translated
3248
 *
3249
 * @return void
3250
 */
3251
function defineComplexity() : void
3252
{
3253
    // Load user's language
3254
    $lang = new Language(); 
3255
    
3256
    if (defined('TP_PW_COMPLEXITY') === false) {
3257
        define(
3258
            'TP_PW_COMPLEXITY',
3259
            [
3260
                TP_PW_STRENGTH_1 => array(TP_PW_STRENGTH_1, $lang->get('complex_level1'), 'fas fa-thermometer-empty text-danger'),
3261
                TP_PW_STRENGTH_2 => array(TP_PW_STRENGTH_2, $lang->get('complex_level2'), 'fas fa-thermometer-quarter text-warning'),
3262
                TP_PW_STRENGTH_3 => array(TP_PW_STRENGTH_3, $lang->get('complex_level3'), 'fas fa-thermometer-half text-warning'),
3263
                TP_PW_STRENGTH_4 => array(TP_PW_STRENGTH_4, $lang->get('complex_level4'), 'fas fa-thermometer-three-quarters text-success'),
3264
                TP_PW_STRENGTH_5 => array(TP_PW_STRENGTH_5, $lang->get('complex_level5'), 'fas fa-thermometer-full text-success'),
3265
            ]
3266
        );
3267
    }
3268
}
3269
3270
/**
3271
 * Uses Sanitizer to perform data sanitization
3272
 *
3273
 * @param array     $data
3274
 * @param array     $filters
3275
 * @return array|string
3276
 */
3277
function dataSanitizer(array $data, array $filters): array|string
3278
{
3279
    // Load Sanitizer library
3280
    $sanitizer = new Sanitizer($data, $filters);
3281
3282
    // Load AntiXSS
3283
    $antiXss = new AntiXSS();
3284
3285
    // Sanitize post and get variables
3286
    return $antiXss->xss_clean($sanitizer->sanitize());
3287
}
3288
3289
/**
3290
 * Permits to manage the cache tree for a user
3291
 *
3292
 * @param integer $user_id
3293
 * @param string $data
3294
 * @param array $SETTINGS
3295
 * @param string $field_update
3296
 * @return void
3297
 */
3298
function cacheTreeUserHandler(int $user_id, string $data, array $SETTINGS, string $field_update = '')
3299
{
3300
    // Load class DB
3301
    loadClasses('DB');
3302
3303
    // Exists ?
3304
    $userCacheId = DB::queryfirstrow(
3305
        'SELECT increment_id
3306
        FROM ' . prefixTable('cache_tree') . '
3307
        WHERE user_id = %i',
3308
        $user_id
3309
    );
3310
    
3311
    if (is_null($userCacheId) === true || count($userCacheId) === 0) {
3312
        // insert in table
3313
        DB::insert(
3314
            prefixTable('cache_tree'),
3315
            array(
3316
                'data' => $data,
3317
                'timestamp' => time(),
3318
                'user_id' => $user_id,
3319
                'visible_folders' => '',
3320
            )
3321
        );
3322
    } else {
3323
        if (empty($field_update) === true) {
3324
            DB::update(
3325
                prefixTable('cache_tree'),
3326
                [
3327
                    'timestamp' => time(),
3328
                    'data' => $data,
3329
                ],
3330
                'increment_id = %i',
3331
                $userCacheId['increment_id']
3332
            );
3333
        /* USELESS
3334
        } else {
3335
            DB::update(
3336
                prefixTable('cache_tree'),
3337
                [
3338
                    $field_update => $data,
3339
                ],
3340
                'increment_id = %i',
3341
                $userCacheId['increment_id']
3342
            );*/
3343
        }
3344
    }
3345
}
3346
3347
/**
3348
 * Permits to calculate a %
3349
 *
3350
 * @param float $nombre
3351
 * @param float $total
3352
 * @param float $pourcentage
3353
 * @return float
3354
 */
3355
function pourcentage(float $nombre, float $total, float $pourcentage): float
3356
{ 
3357
    $resultat = ($nombre/$total) * $pourcentage;
3358
    return round($resultat);
3359
}
3360
3361
/**
3362
 * Load the folders list from the cache
3363
 *
3364
 * @param string $fieldName
3365
 * @param string $sessionName
3366
 * @param boolean $forceRefresh
3367
 * @return array
3368
 */
3369
function loadFoldersListByCache(
3370
    string $fieldName,
3371
    string $sessionName,
3372
    bool $forceRefresh = false
3373
): array
3374
{
3375
    // Case when refresh is EXPECTED / MANDATORY
3376
    if ($forceRefresh === true) {
3377
        return [
3378
            'state' => false,
3379
            'data' => [],
3380
        ];
3381
    }
3382
    
3383
    $session = SessionManager::getSession();
3384
3385
    // Get last folder update
3386
    $lastFolderChange = DB::queryfirstrow(
3387
        'SELECT valeur FROM ' . prefixTable('misc') . '
3388
        WHERE type = %s AND intitule = %s',
3389
        'timestamp',
3390
        'last_folder_change'
3391
    );
3392
    if (DB::count() === 0) {
3393
        $lastFolderChange['valeur'] = 0;
3394
    }
3395
3396
    // Case when an update in the tree has been done
3397
    // Refresh is then mandatory
3398
    if ((int) $lastFolderChange['valeur'] > (int) (null !== $session->get('user-tree_last_refresh_timestamp') ? $session->get('user-tree_last_refresh_timestamp') : 0)) {
3399
        return [
3400
            'state' => false,
3401
            'data' => [],
3402
        ];
3403
    }
3404
3405
    // Does this user has the tree structure in session?
3406
    // If yes then use it
3407
    if (count(null !== $session->get('user-folders_list') ? $session->get('user-folders_list') : []) > 0) {
3408
        return [
3409
            'state' => true,
3410
            'data' => json_encode($session->get('user-folders_list')),
3411
        ];
3412
    }
3413
3414
    // Does this user has a tree cache
3415
    $userCacheTree = DB::queryfirstrow(
3416
        'SELECT '.$fieldName.'
3417
        FROM ' . prefixTable('cache_tree') . '
3418
        WHERE user_id = %i',
3419
        $session->get('user-id')
3420
    );
3421
    if (empty($userCacheTree[$fieldName]) === false && $userCacheTree[$fieldName] !== '[]') {
3422
        SessionManager::addRemoveFromSessionAssociativeArray(
3423
            'user-folders_list',
3424
            [$userCacheTree[$fieldName]],
3425
            'add'
3426
        );
3427
        return [
3428
            'state' => true,
3429
            'data' => $userCacheTree[$fieldName],
3430
        ];
3431
    }
3432
3433
    return [
3434
        'state' => false,
3435
        'data' => [],
3436
    ];
3437
}
3438
3439
3440
/**
3441
 * Permits to refresh the categories of folders
3442
 *
3443
 * @param array $folderIds
3444
 * @return void
3445
 */
3446
function handleFoldersCategories(
3447
    array $folderIds
3448
)
3449
{
3450
    // Load class DB
3451
    loadClasses('DB');
3452
3453
    $arr_data = array();
3454
3455
    // force full list of folders
3456
    if (count($folderIds) === 0) {
3457
        $folderIds = DB::queryFirstColumn(
3458
            'SELECT id
3459
            FROM ' . prefixTable('nested_tree') . '
3460
            WHERE personal_folder=%i',
3461
            0
3462
        );
3463
    }
3464
3465
    // Get complexity
3466
    defineComplexity();
3467
3468
    // update
3469
    foreach ($folderIds as $folder) {
3470
        // Do we have Categories
3471
        // get list of associated Categories
3472
        $arrCatList = array();
3473
        $rows_tmp = DB::query(
3474
            'SELECT c.id, c.title, c.level, c.type, c.masked, c.order, c.encrypted_data, c.role_visibility, c.is_mandatory,
3475
            f.id_category AS category_id
3476
            FROM ' . prefixTable('categories_folders') . ' AS f
3477
            INNER JOIN ' . prefixTable('categories') . ' AS c ON (f.id_category = c.parent_id)
3478
            WHERE id_folder=%i',
3479
            $folder
3480
        );
3481
        if (DB::count() > 0) {
3482
            foreach ($rows_tmp as $row) {
3483
                $arrCatList[$row['id']] = array(
3484
                    'id' => $row['id'],
3485
                    'title' => $row['title'],
3486
                    'level' => $row['level'],
3487
                    'type' => $row['type'],
3488
                    'masked' => $row['masked'],
3489
                    'order' => $row['order'],
3490
                    'encrypted_data' => $row['encrypted_data'],
3491
                    'role_visibility' => $row['role_visibility'],
3492
                    'is_mandatory' => $row['is_mandatory'],
3493
                    'category_id' => $row['category_id'],
3494
                );
3495
            }
3496
        }
3497
        $arr_data['categories'] = $arrCatList;
3498
3499
        // Now get complexity
3500
        $valTemp = '';
3501
        $data = DB::queryFirstRow(
3502
            'SELECT valeur
3503
            FROM ' . prefixTable('misc') . '
3504
            WHERE type = %s AND intitule=%i',
3505
            'complex',
3506
            $folder
3507
        );
3508
        if (DB::count() > 0 && empty($data['valeur']) === false) {
3509
            $valTemp = array(
3510
                'value' => $data['valeur'],
3511
                'text' => TP_PW_COMPLEXITY[$data['valeur']][1],
3512
            );
3513
        }
3514
        $arr_data['complexity'] = $valTemp;
3515
3516
        // Now get Roles
3517
        $valTemp = '';
3518
        $rows_tmp = DB::query(
3519
            'SELECT t.title
3520
            FROM ' . prefixTable('roles_values') . ' as v
3521
            INNER JOIN ' . prefixTable('roles_title') . ' as t ON (v.role_id = t.id)
3522
            WHERE v.folder_id = %i
3523
            GROUP BY title',
3524
            $folder
3525
        );
3526
        foreach ($rows_tmp as $record) {
3527
            $valTemp .= (empty($valTemp) === true ? '' : ' - ') . $record['title'];
3528
        }
3529
        $arr_data['visibilityRoles'] = $valTemp;
3530
3531
        // now save in DB
3532
        DB::update(
3533
            prefixTable('nested_tree'),
3534
            array(
3535
                'categories' => json_encode($arr_data),
3536
            ),
3537
            'id = %i',
3538
            $folder
3539
        );
3540
    }
3541
}
3542
3543
/**
3544
 * List all users that have specific roles
3545
 *
3546
 * @param array $roles
3547
 * @return array
3548
 */
3549
function getUsersWithRoles(
3550
    array $roles
3551
): array
3552
{
3553
    $session = SessionManager::getSession();
3554
    $arrUsers = array();
3555
3556
    foreach ($roles as $role) {
3557
        // loop on users and check if user has this role
3558
        $rows = DB::query(
3559
            'SELECT id, fonction_id
3560
            FROM ' . prefixTable('users') . '
3561
            WHERE id != %i AND admin = 0 AND fonction_id IS NOT NULL AND fonction_id != ""',
3562
            $session->get('user-id')
3563
        );
3564
        foreach ($rows as $user) {
3565
            $userRoles = is_null($user['fonction_id']) === false && empty($user['fonction_id']) === false ? explode(';', $user['fonction_id']) : [];
3566
            if (in_array($role, $userRoles, true) === true) {
3567
                array_push($arrUsers, $user['id']);
3568
            }
3569
        }
3570
    }
3571
3572
    return $arrUsers;
3573
}
3574
3575
3576
/**
3577
 * Get all users informations
3578
 *
3579
 * @param integer $userId
3580
 * @return array
3581
 */
3582
function getFullUserInfos(
3583
    int $userId
3584
): array
3585
{
3586
    if (empty($userId) === true) {
3587
        return array();
3588
    }
3589
3590
    $val = DB::queryfirstrow(
3591
        'SELECT *
3592
        FROM ' . prefixTable('users') . '
3593
        WHERE id = %i',
3594
        $userId
3595
    );
3596
3597
    return $val;
3598
}
3599
3600
/**
3601
 * Is required an upgrade
3602
 *
3603
 * @return boolean
3604
 */
3605
function upgradeRequired(): bool
3606
{
3607
    // Get settings.php
3608
    include_once __DIR__. '/../includes/config/settings.php';
3609
3610
    // Get timestamp in DB
3611
    $val = DB::queryfirstrow(
3612
        'SELECT valeur
3613
        FROM ' . prefixTable('misc') . '
3614
        WHERE type = %s AND intitule = %s',
3615
        'admin',
3616
        'upgrade_timestamp'
3617
    );
3618
    
3619
    // if not exists then error
3620
    if (is_null($val) === true || count($val) === 0 || defined('UPGRADE_MIN_DATE') === false) return true;
3621
3622
    // if empty or too old then error
3623
    if (empty($val['valeur']) === true || (int) $val['valeur'] < (int) UPGRADE_MIN_DATE) {
3624
        return true;
3625
    }
3626
3627
    return false;
3628
}
3629
3630
/**
3631
 * Permits to change the user keys on his demand
3632
 *
3633
 * @param integer $userId
3634
 * @param string $passwordClear
3635
 * @param integer $nbItemsToTreat
3636
 * @param string $encryptionKey
3637
 * @param boolean $deleteExistingKeys
3638
 * @param boolean $sendEmailToUser
3639
 * @param boolean $encryptWithUserPassword
3640
 * @param boolean $generate_user_new_password
3641
 * @param string $emailBody
3642
 * @param boolean $user_self_change
3643
 * @param string $recovery_public_key
3644
 * @param string $recovery_private_key
3645
 * @return string
3646
 */
3647
function handleUserKeys(
3648
    int $userId,
3649
    string $passwordClear,
3650
    int $nbItemsToTreat,
3651
    string $encryptionKey = '',
3652
    bool $deleteExistingKeys = false,
3653
    bool $sendEmailToUser = true,
3654
    bool $encryptWithUserPassword = false,
3655
    bool $generate_user_new_password = false,
3656
    string $emailBody = '',
3657
    bool $user_self_change = false,
3658
    string $recovery_public_key = '',
3659
    string $recovery_private_key = ''
3660
): string
3661
{
3662
    $session = SessionManager::getSession();
3663
    $lang = new Language(); 
3664
3665
    // prepapre background tasks for item keys generation        
3666
    $userTP = DB::queryFirstRow(
3667
        'SELECT pw, public_key, private_key
3668
        FROM ' . prefixTable('users') . '
3669
        WHERE id = %i',
3670
        TP_USER_ID
3671
    );
3672
    if (DB::count() > 0) {
3673
        // Do we need to generate new user password
3674
        if ($generate_user_new_password === true) {
3675
            // Generate a new password
3676
            $passwordClear = GenerateCryptKey(20, false, true, true, false, true);
3677
        }
3678
3679
        // Hash the password
3680
        $pwdlib = new PasswordLib();
3681
        $hashedPassword = $pwdlib->createPasswordHash($passwordClear);
3682
        if ($pwdlib->verifyPasswordHash($passwordClear, $hashedPassword) === false) {
3683
            return prepareExchangedData(
3684
                array(
3685
                    'error' => true,
3686
                    'message' => $lang->get('pw_hash_not_correct'),
3687
                ),
3688
                'encode'
3689
            );
3690
        }
3691
3692
        // Generate new keys
3693
        if ($user_self_change === true && empty($recovery_public_key) === false && empty($recovery_private_key) === false){
3694
            $userKeys = [
3695
                'public_key' => $recovery_public_key,
3696
                'private_key_clear' => $recovery_private_key,
3697
                'private_key' => encryptPrivateKey($passwordClear, $recovery_private_key),
3698
            ];
3699
        } else {
3700
            $userKeys = generateUserKeys($passwordClear);
3701
        }
3702
3703
        // Save in DB
3704
        DB::update(
3705
            prefixTable('users'),
3706
            array(
3707
                'pw' => $hashedPassword,
3708
                'public_key' => $userKeys['public_key'],
3709
                'private_key' => $userKeys['private_key'],
3710
            ),
3711
            'id=%i',
3712
            $userId
3713
        );
3714
3715
        // update session too
3716
        if ($userId === $session->get('user-id')) {
3717
            $session->set('user-private_key', $userKeys['private_key_clear']);
3718
            $session->set('user-public_key', $userKeys['public_key']);
3719
        }
3720
3721
        // Manage empty encryption key
3722
        // Let's take the user's password if asked and if no encryption key provided
3723
        $encryptionKey = $encryptWithUserPassword === true && empty($encryptionKey) === true ? $passwordClear : $encryptionKey;
3724
3725
        // Create process
3726
        DB::insert(
3727
            prefixTable('processes'),
3728
            array(
3729
                'created_at' => time(),
3730
                'process_type' => 'create_user_keys',
3731
                'arguments' => json_encode([
3732
                    'new_user_id' => (int) $userId,
3733
                    'new_user_pwd' => cryption($passwordClear, '','encrypt')['string'],
3734
                    'new_user_code' => cryption(empty($encryptionKey) === true ? uniqidReal(20) : $encryptionKey, '','encrypt')['string'],
3735
                    'owner_id' => (int) TP_USER_ID,
3736
                    'creator_pwd' => $userTP['pw'],
3737
                    'send_email' => $sendEmailToUser === true ? 1 : 0,
3738
                    'otp_provided_new_value' => 1,
3739
                    'email_body' => empty($emailBody) === true ? '' : $lang->get($emailBody),
3740
                    'user_self_change' => $user_self_change === true ? 1 : 0,
3741
                ]),
3742
                'updated_at' => '',
3743
                'finished_at' => '',
3744
                'output' => '',
3745
            )
3746
        );
3747
        $processId = DB::insertId();
3748
3749
        // Delete existing keys
3750
        if ($deleteExistingKeys === true) {
3751
            deleteUserObjetsKeys(
3752
                (int) $userId,
3753
            );
3754
        }
3755
3756
        // Create tasks
3757
        DB::insert(
3758
            prefixTable('processes_tasks'),
3759
            array(
3760
                'process_id' => $processId,
3761
                'created_at' => time(),
3762
                'task' => json_encode([
3763
                    'step' => 'step0',
3764
                    'index' => 0,
3765
                    'nb' => $nbItemsToTreat,
3766
                ]),
3767
            )
3768
        );
3769
3770
        DB::insert(
3771
            prefixTable('processes_tasks'),
3772
            array(
3773
                'process_id' => $processId,
3774
                'created_at' => time(),
3775
                'task' => json_encode([
3776
                    'step' => 'step10',
3777
                    'index' => 0,
3778
                    'nb' => $nbItemsToTreat,
3779
                ]),
3780
            )
3781
        );
3782
3783
        DB::insert(
3784
            prefixTable('processes_tasks'),
3785
            array(
3786
                'process_id' => $processId,
3787
                'created_at' => time(),
3788
                'task' => json_encode([
3789
                    'step' => 'step20',
3790
                    'index' => 0,
3791
                    'nb' => $nbItemsToTreat,
3792
                ]),
3793
            )
3794
        );
3795
3796
        DB::insert(
3797
            prefixTable('processes_tasks'),
3798
            array(
3799
                'process_id' => $processId,
3800
                'created_at' => time(),
3801
                'task' => json_encode([
3802
                    'step' => 'step30',
3803
                    'index' => 0,
3804
                    'nb' => $nbItemsToTreat,
3805
                ]),
3806
            )
3807
        );
3808
3809
        DB::insert(
3810
            prefixTable('processes_tasks'),
3811
            array(
3812
                'process_id' => $processId,
3813
                'created_at' => time(),
3814
                'task' => json_encode([
3815
                    'step' => 'step40',
3816
                    'index' => 0,
3817
                    'nb' => $nbItemsToTreat,
3818
                ]),
3819
            )
3820
        );
3821
3822
        DB::insert(
3823
            prefixTable('processes_tasks'),
3824
            array(
3825
                'process_id' => $processId,
3826
                'created_at' => time(),
3827
                'task' => json_encode([
3828
                    'step' => 'step50',
3829
                    'index' => 0,
3830
                    'nb' => $nbItemsToTreat,
3831
                ]),
3832
            )
3833
        );
3834
3835
        DB::insert(
3836
            prefixTable('processes_tasks'),
3837
            array(
3838
                'process_id' => $processId,
3839
                'created_at' => time(),
3840
                'task' => json_encode([
3841
                    'step' => 'step60',
3842
                    'index' => 0,
3843
                    'nb' => $nbItemsToTreat,
3844
                ]),
3845
            )
3846
        );
3847
3848
        // update user's new status
3849
        DB::update(
3850
            prefixTable('users'),
3851
            [
3852
                'is_ready_for_usage' => 0,
3853
                'otp_provided' => 1,
3854
                'ongoing_process_id' => $processId,
3855
                'special' => 'generate-keys',
3856
            ],
3857
            'id=%i',
3858
            $userId
3859
        );
3860
    }
3861
3862
    return prepareExchangedData(
3863
        array(
3864
            'error' => false,
3865
            'message' => '',
3866
        ),
3867
        'encode'
3868
    );
3869
}
3870
3871
/**
3872
 * Permeits to check the consistency of date versus columns definition
3873
 *
3874
 * @param string $table
3875
 * @param array $dataFields
3876
 * @return array
3877
 */
3878
function validateDataFields(
3879
    string $table,
3880
    array $dataFields
3881
): array
3882
{
3883
    // Get table structure
3884
    $result = DB::query(
3885
        "SELECT `COLUMN_NAME`, `CHARACTER_MAXIMUM_LENGTH` FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%l' AND TABLE_NAME = '%l';",
3886
        DB_NAME,
3887
        $table
3888
    );
3889
3890
    foreach ($result as $row) {
3891
        $field = $row['COLUMN_NAME'];
3892
        $maxLength = is_null($row['CHARACTER_MAXIMUM_LENGTH']) === false ? (int) $row['CHARACTER_MAXIMUM_LENGTH'] : '';
3893
3894
        if (isset($dataFields[$field]) === true && is_array($dataFields[$field]) === false && empty($maxLength) === false) {
3895
            if (strlen((string) $dataFields[$field]) > $maxLength) {
3896
                return [
3897
                    'state' => false,
3898
                    'field' => $field,
3899
                    'maxLength' => $maxLength,
3900
                    'currentLength' => strlen((string) $dataFields[$field]),
3901
                ];
3902
            }
3903
        }
3904
    }
3905
    
3906
    return [
3907
        'state' => true,
3908
        'message' => '',
3909
    ];
3910
}
3911
3912
/**
3913
 * Adapt special characters sanitized during filter_var with option FILTER_SANITIZE_SPECIAL_CHARS operation
3914
 *
3915
 * @param string $string
3916
 * @return string
3917
 */
3918
function filterVarBack(string $string): string
3919
{
3920
    $arr = [
3921
        '&#060;' => '<',
3922
        '&#062;' => '>',
3923
        '&#034;' => '"',
3924
        '&#039;' => "'",
3925
        '&#038;' => '&',
3926
    ];
3927
3928
    foreach ($arr as $key => $value) {
3929
        $string = str_replace($key, $value, $string);
3930
    }
3931
3932
    return $string;
3933
}
3934
3935
/**
3936
 * 
3937
 */
3938
function storeTask(
3939
    string $taskName,
3940
    int $user_id,
3941
    int $is_personal_folder,
3942
    int $folder_destination_id,
3943
    int $item_id,
3944
    string $object_keys
3945
)
3946
{
3947
    if (in_array($taskName, ['item_copy', 'new_item', 'update_item'])) {
3948
        // Create process
3949
        DB::insert(
3950
            prefixTable('processes'),
3951
            array(
3952
                'created_at' => time(),
3953
                'process_type' => $taskName,
3954
                'arguments' => json_encode([
3955
                    'item_id' => $item_id,
3956
                    'object_key' => $object_keys,
3957
                ]),
3958
                'updated_at' => '',
3959
                'finished_at' => '',
3960
                'output' => '',
3961
                'item_id' => $item_id,
3962
            )
3963
        );
3964
        $processId = DB::insertId();
3965
3966
        // Create tasks
3967
        // 1- Create password sharekeys for users of this new ITEM
3968
        DB::insert(
3969
            prefixTable('processes_tasks'),
3970
            array(
3971
                'process_id' => $processId,
3972
                'created_at' => time(),
3973
                'task' => json_encode([
3974
                    'step' => 'create_users_pwd_key',
3975
                    'index' => 0,
3976
                ]),
3977
            )
3978
        );
3979
3980
        // 2- Create fields sharekeys for users of this new ITEM
3981
        DB::insert(
3982
            prefixTable('processes_tasks'),
3983
            array(
3984
                'process_id' => $processId,
3985
                'created_at' => time(),
3986
                'task' => json_encode([
3987
                    'step' => 'create_users_fields_key',
3988
                    'index' => 0,
3989
                ]),
3990
            )
3991
        );
3992
3993
        // 3- Create files sharekeys for users of this new ITEM
3994
        DB::insert(
3995
            prefixTable('processes_tasks'),
3996
            array(
3997
                'process_id' => $processId,
3998
                'created_at' => time(),
3999
                'task' => json_encode([
4000
                    'step' => 'create_users_files_key',
4001
                    'index' => 0,
4002
                ]),
4003
            )
4004
        );
4005
    }
4006
}
4007
4008
/**
4009
 * Return PHP binary path
4010
 *
4011
 * @return string
4012
 */
4013
function getPHPBinary(): string
4014
{
4015
    // Get PHP binary path
4016
    $phpBinaryFinder = new PhpExecutableFinder();
4017
    $phpBinaryPath = $phpBinaryFinder->find();
4018
    return $phpBinaryPath === false ? 'false' : $phpBinaryPath;
4019
}
4020
4021
4022
4023
/**
4024
 * Delete unnecessary keys for personal items
4025
 *
4026
 * @param boolean $allUsers
4027
 * @param integer $user_id
4028
 * @return void
4029
 */
4030
function purgeUnnecessaryKeys(bool $allUsers = true, int $user_id=0)
4031
{
4032
    if ($allUsers === true) {
4033
        // Load class DB
4034
        if (class_exists('DB') === false) {
4035
            loadClasses('DB');
4036
        }
4037
4038
        $users = DB::query(
4039
            'SELECT id
4040
            FROM ' . prefixTable('users') . '
4041
            WHERE id NOT IN ('.OTV_USER_ID.', '.TP_USER_ID.', '.SSH_USER_ID.', '.API_USER_ID.')
4042
            ORDER BY login ASC'
4043
        );
4044
        foreach ($users as $user) {
4045
            purgeUnnecessaryKeysForUser((int) $user['id']);
4046
        }
4047
    } else {
4048
        purgeUnnecessaryKeysForUser((int) $user_id);
4049
    }
4050
}
4051
4052
/**
4053
 * Delete unnecessary keys for personal items
4054
 *
4055
 * @param integer $user_id
4056
 * @return void
4057
 */
4058
function purgeUnnecessaryKeysForUser(int $user_id=0)
4059
{
4060
    if ($user_id === 0) {
4061
        return;
4062
    }
4063
4064
    // Load class DB
4065
    loadClasses('DB');
4066
4067
    $personalItems = DB::queryFirstColumn(
4068
        'SELECT id
4069
        FROM ' . prefixTable('items') . ' AS i
4070
        INNER JOIN ' . prefixTable('log_items') . ' AS li ON li.id_item = i.id
4071
        WHERE i.perso = 1 AND li.action = "at_creation" AND li.id_user IN (%i, '.TP_USER_ID.')',
4072
        $user_id
4073
    );
4074
    if (count($personalItems) > 0) {
4075
        // Item keys
4076
        DB::delete(
4077
            prefixTable('sharekeys_items'),
4078
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4079
            $personalItems,
4080
            $user_id
4081
        );
4082
        // Files keys
4083
        DB::delete(
4084
            prefixTable('sharekeys_files'),
4085
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4086
            $personalItems,
4087
            $user_id
4088
        );
4089
        // Fields keys
4090
        DB::delete(
4091
            prefixTable('sharekeys_fields'),
4092
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4093
            $personalItems,
4094
            $user_id
4095
        );
4096
        // Logs keys
4097
        DB::delete(
4098
            prefixTable('sharekeys_logs'),
4099
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4100
            $personalItems,
4101
            $user_id
4102
        );
4103
    }
4104
}
4105
4106
/**
4107
 * Generate recovery keys file
4108
 *
4109
 * @param integer $userId
4110
 * @param array $SETTINGS
4111
 * @return string
4112
 */
4113
function handleUserRecoveryKeysDownload(int $userId, array $SETTINGS):string
4114
{
4115
    $session = SessionManager::getSession();
4116
    // Check if user exists
4117
    $userInfo = DB::queryFirstRow(
4118
        'SELECT pw, public_key, private_key, login, name
4119
        FROM ' . prefixTable('users') . '
4120
        WHERE id = %i',
4121
        $userId
4122
    );
4123
4124
    if (DB::count() > 0) {
4125
        $now = (int) time();
4126
4127
        // Prepare file content
4128
        $export_value = file_get_contents(__DIR__."/../includes/core/teampass_ascii.txt")."\n".
4129
            "Generation date: ".date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now)."\n\n".
4130
            "RECOVERY KEYS - Not to be shared - To be store safely\n\n".
4131
            "Public Key:\n".$userInfo['public_key']."\n\n".
4132
            "Private Key:\n".decryptPrivateKey($session->get('user-password'), $userInfo['private_key'])."\n\n";
4133
4134
        // Update user's keys_recovery_time
4135
        DB::update(
4136
            prefixTable('users'),
4137
            [
4138
                'keys_recovery_time' => $now,
4139
            ],
4140
            'id=%i',
4141
            $userId
4142
        );
4143
        $session->set('user-keys_recovery_time', $now);
4144
4145
        //Log into DB the user's disconnection
4146
        logEvents($SETTINGS, 'user_mngt', 'at_user_keys_download', (string) $userId, $userInfo['login']);
4147
        
4148
        // Return data
4149
        return prepareExchangedData(
4150
            array(
4151
                'error' => false,
4152
                'datetime' => date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now),
4153
                'timestamp' => $now,
4154
                'content' => base64_encode($export_value),
4155
                'login' => $userInfo['login'],
4156
            ),
4157
            'encode'
4158
        );
4159
    }
4160
4161
    return prepareExchangedData(
4162
        array(
4163
            'error' => true,
4164
            'datetime' => '',
4165
        ),
4166
        'encode'
4167
    );
4168
}
4169
4170
/**
4171
 * Permits to load expected classes
4172
 *
4173
 * @param string $className
4174
 * @return void
4175
 */
4176
function loadClasses(string $className = ''): void
4177
{
4178
    require_once __DIR__. '/../includes/config/include.php';
4179
    require_once __DIR__. '/../includes/config/settings.php';
4180
    require_once __DIR__.'/../vendor/autoload.php';
4181
4182
    if (defined('DB_PASSWD_CLEAR') === false) {
4183
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, []));
4184
    }
4185
4186
    if (empty($className) === false) {
4187
        // Load class DB
4188
        if ((string) $className === 'DB') {
4189
            //Connect to DB
4190
            DB::$host = DB_HOST;
4191
            DB::$user = DB_USER;
4192
            DB::$password = DB_PASSWD_CLEAR;
4193
            DB::$dbName = DB_NAME;
4194
            DB::$port = DB_PORT;
4195
            DB::$encoding = DB_ENCODING;
4196
            DB::$ssl = DB_SSL;
4197
            DB::$connect_options = DB_CONNECT_OPTIONS;
4198
        }
4199
    }
4200
4201
}
4202
4203
/**
4204
 * Returns the page the user is visiting.
4205
 *
4206
 * @return string The page name
4207
 */
4208
function getCurrectPage($SETTINGS)
4209
{
4210
    $superGlobal = new SuperGlobal();
4211
4212
    // Parse the url
4213
    parse_str(
4214
        substr(
4215
            (string) $superGlobal->get('REQUEST_URI', 'SERVER'),
4216
            strpos((string) $superGlobal->get('REQUEST_URI', 'SERVER'), '?') + 1
4217
        ),
4218
        $result
4219
    );
4220
4221
    return $result['page'];
4222
}
4223
4224
/**
4225
 * Permits to return value if set
4226
 *
4227
 * @param string|int $value
4228
 * @param string|int|null $retFalse
4229
 * @param string|int $retTrue
4230
 * @return mixed
4231
 */
4232
function returnIfSet($value, $retFalse = '', $retTrue = null): mixed
4233
{
4234
4235
    return isset($value) === true ? ($retTrue === null ? $value : $retTrue) : $retFalse;
4236
}
4237