Passed
Pull Request — master (#4372)
by
unknown
05:37
created

rebuildConfigFile()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 13
c 0
b 0
f 0
nc 5
nop 2
dl 0
loc 24
rs 9.8333
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This file is part of the TeamPass project.
9
 * 
10
 * TeamPass is free software: you can redistribute it and/or modify it
11
 * under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, version 3 of the License.
13
 * 
14
 * TeamPass is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU General Public License for more details.
18
 * 
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21
 * 
22
 * Certain components of this file may be under different licenses. For
23
 * details, see the `licenses` directory or individual file headers.
24
 * ---
25
 * @file      main.functions.php
26
 * @author    Nils Laumaillé ([email protected])
27
 * @copyright 2009-2024 Teampass.net
28
 * @license   GPL-3.0
29
 * @see       https://www.teampass.net
30
 */
31
32
use LdapRecord\Connection;
33
use ForceUTF8\Encoding;
34
use Elegant\Sanitizer\Sanitizer;
35
use voku\helper\AntiXSS;
36
use Hackzilla\PasswordGenerator\Generator\ComputerPasswordGenerator;
37
use Hackzilla\PasswordGenerator\RandomGenerator\Php7RandomGenerator;
38
use TeampassClasses\SessionManager\SessionManager;
39
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
40
use TeampassClasses\Language\Language;
41
use TeampassClasses\NestedTree\NestedTree;
42
use Defuse\Crypto\Key;
43
use Defuse\Crypto\Crypto;
44
use Defuse\Crypto\KeyProtectedByPassword;
45
use Defuse\Crypto\File as CryptoFile;
46
use Defuse\Crypto\Exception as CryptoException;
47
use Elegant\Sanitizer\Filters\Uppercase;
48
use PHPMailer\PHPMailer\PHPMailer;
49
use TeampassClasses\PasswordManager\PasswordManager;
50
use Symfony\Component\Process\Exception\ProcessFailedException;
51
use Symfony\Component\Process\Process;
52
use Symfony\Component\Process\PhpExecutableFinder;
53
use TeampassClasses\Encryption\Encryption;
54
use TeampassClasses\ConfigManager\ConfigManager;
55
use TeampassClasses\EmailService\EmailService;
56
use TeampassClasses\EmailService\EmailSettings;
57
58
header('Content-type: text/html; charset=utf-8');
59
header('Cache-Control: no-cache, must-revalidate');
60
61
loadClasses('DB');
62
$session = SessionManager::getSession();
63
64
// Load config if $SETTINGS not defined
65
$configManager = new ConfigManager($session);
66
$SETTINGS = $configManager->getAllSettings();
67
68
/**
69
 * genHash().
70
 *
71
 * Generate a hash for user login
72
 *
73
 * @param string $password What password
74
 * @param string $cost     What cost
75
 *
76
 * @return string|void
77
 */
78
/* TODO - Remove this function
79
function bCrypt(
80
    string $password,
81
    string $cost
82
): ?string
83
{
84
    $salt = sprintf('$2y$%02d$', $cost);
85
    if (function_exists('openssl_random_pseudo_bytes')) {
86
        $salt .= bin2hex(openssl_random_pseudo_bytes(11));
87
    } else {
88
        $chars = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
89
        for ($i = 0; $i < 22; ++$i) {
90
            $salt .= $chars[mt_rand(0, 63)];
91
        }
92
    }
93
94
    return crypt($password, $salt);
95
}
96
*/
97
98
/**
99
 * Checks if a string is hex encoded
100
 *
101
 * @param string $str
102
 * @return boolean
103
 */
104
function isHex(string $str): bool
105
{
106
    if (str_starts_with(strtolower($str), '0x')) {
107
        $str = substr($str, 2);
108
    }
109
110
    return ctype_xdigit($str);
111
}
112
113
/**
114
 * Defuse cryption function.
115
 *
116
 * @param string $message   what to de/crypt
117
 * @param string $ascii_key key to use
118
 * @param string $type      operation to perform
119
 * @param array  $SETTINGS  Teampass settings
120
 *
121
 * @return array
122
 */
123
function cryption(string $message, string $ascii_key, string $type, ?array $SETTINGS = []): array
124
{
125
    $ascii_key = empty($ascii_key) === true ? file_get_contents(SECUREPATH.'/'.SECUREFILE) : $ascii_key;
126
    $err = false;
127
    
128
    // convert KEY
129
    $key = Key::loadFromAsciiSafeString($ascii_key);
130
    try {
131
        if ($type === 'encrypt') {
132
            $text = Crypto::encrypt($message, $key);
133
        } elseif ($type === 'decrypt') {
134
            $text = Crypto::decrypt($message, $key);
135
        }
136
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
137
        $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.';
138
    } catch (CryptoException\BadFormatException $ex) {
139
        $err = $ex;
140
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
141
        $err = $ex;
142
    } catch (CryptoException\CryptoException $ex) {
143
        $err = $ex;
144
    } catch (CryptoException\IOException $ex) {
145
        $err = $ex;
146
    }
147
148
    return [
149
        'string' => $text ?? '',
150
        'error' => $err,
151
    ];
152
}
153
154
/**
155
 * Generating a defuse key.
156
 *
157
 * @return string
158
 */
159
function defuse_generate_key()
160
{
161
    $key = Key::createNewRandomKey();
162
    $key = $key->saveToAsciiSafeString();
163
    return $key;
164
}
165
166
/**
167
 * Generate a Defuse personal key.
168
 *
169
 * @param string $psk psk used
170
 *
171
 * @return string
172
 */
173
function defuse_generate_personal_key(string $psk): string
174
{
175
    $protected_key = KeyProtectedByPassword::createRandomPasswordProtectedKey($psk);
176
    return $protected_key->saveToAsciiSafeString(); // save this in user table
177
}
178
179
/**
180
 * Validate persoanl key with defuse.
181
 *
182
 * @param string $psk                   the user's psk
183
 * @param string $protected_key_encoded special key
184
 *
185
 * @return string
186
 */
187
function defuse_validate_personal_key(string $psk, string $protected_key_encoded): string
188
{
189
    try {
190
        $protected_key_encoded = KeyProtectedByPassword::loadFromAsciiSafeString($protected_key_encoded);
191
        $user_key = $protected_key_encoded->unlockKey($psk);
192
        $user_key_encoded = $user_key->saveToAsciiSafeString();
193
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
194
        return 'Error - Major issue as the encryption is broken.';
195
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
196
        return 'Error - The saltkey is not the correct one.';
197
    }
198
199
    return $user_key_encoded;
200
    // store it in session once user has entered his psk
201
}
202
203
/**
204
 * Decrypt a defuse string if encrypted.
205
 *
206
 * @param string $value Encrypted string
207
 *
208
 * @return string Decrypted string
209
 */
210
function defuseReturnDecrypted(string $value, $SETTINGS): string
211
{
212
    if (substr($value, 0, 3) === 'def') {
213
        $value = cryption($value, '', 'decrypt', $SETTINGS)['string'];
214
    }
215
216
    return $value;
217
}
218
219
/**
220
 * Trims a string depending on a specific string.
221
 *
222
 * @param string|array $chaine  what to trim
223
 * @param string       $element trim on what
224
 *
225
 * @return string
226
 */
227
function trimElement($chaine, string $element): string
228
{
229
    if (! empty($chaine)) {
230
        if (is_array($chaine) === true) {
231
            $chaine = implode(';', $chaine);
232
        }
233
        $chaine = trim($chaine);
234
        if (substr($chaine, 0, 1) === $element) {
235
            $chaine = substr($chaine, 1);
236
        }
237
        if (substr($chaine, strlen($chaine) - 1, 1) === $element) {
238
            $chaine = substr($chaine, 0, strlen($chaine) - 1);
239
        }
240
    }
241
242
    return $chaine;
243
}
244
245
/**
246
 * Permits to suppress all "special" characters from string.
247
 *
248
 * @param string $string  what to clean
249
 * @param bool   $special use of special chars?
250
 *
251
 * @return string
252
 */
253
function cleanString(string $string, bool $special = false): string
254
{
255
    // Create temporary table for special characters escape
256
    $tabSpecialChar = [];
257
    for ($i = 0; $i <= 31; ++$i) {
258
        $tabSpecialChar[] = chr($i);
259
    }
260
    array_push($tabSpecialChar, '<br />');
261
    if ((int) $special === 1) {
262
        $tabSpecialChar = array_merge($tabSpecialChar, ['</li>', '<ul>', '<ol>']);
263
    }
264
265
    return str_replace($tabSpecialChar, "\n", $string);
266
}
267
268
/**
269
 * Erro manager for DB.
270
 *
271
 * @param array $params output from query
272
 *
273
 * @return void
274
 */
275
function db_error_handler(array $params): void
276
{
277
    echo 'Error: ' . $params['error'] . "<br>\n";
278
    echo 'Query: ' . $params['query'] . "<br>\n";
279
    throw new Exception('Error - Query', 1);
280
}
281
282
/**
283
 * Identify user's rights
284
 *
285
 * @param string|array $groupesVisiblesUser  [description]
286
 * @param string|array $groupesInterditsUser [description]
287
 * @param string       $isAdmin              [description]
288
 * @param string       $idFonctions          [description]
289
 *
290
 * @return bool
291
 */
292
function identifyUserRights(
293
    $groupesVisiblesUser,
294
    $groupesInterditsUser,
295
    $isAdmin,
296
    $idFonctions,
297
    $SETTINGS
298
) {
299
    $session = SessionManager::getSession();
300
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
301
302
    // Check if user is ADMINISTRATOR    
303
    (int) $isAdmin === 1 ?
304
        identAdmin(
305
            $idFonctions,
306
            $SETTINGS, /** @scrutinizer ignore-type */
307
            $tree
308
        )
309
        :
310
        identUser(
311
            $groupesVisiblesUser,
312
            $groupesInterditsUser,
313
            $idFonctions,
314
            $SETTINGS, /** @scrutinizer ignore-type */
315
            $tree
316
        );
317
318
    // update user's timestamp
319
    DB::update(
320
        prefixTable('users'),
321
        [
322
            'timestamp' => time(),
323
        ],
324
        'id=%i',
325
        $session->get('user-id')
326
    );
327
328
    return true;
329
}
330
331
/**
332
 * Identify administrator.
333
 *
334
 * @param string $idFonctions Roles of user
335
 * @param array  $SETTINGS    Teampass settings
336
 * @param object $tree        Tree of folders
337
 *
338
 * @return bool
339
 */
340
function identAdmin($idFonctions, $SETTINGS, $tree)
341
{
342
    
343
    $session = SessionManager::getSession();
344
    $groupesVisibles = [];
345
    $session->set('user-personal_folders', []);
346
    $session->set('user-accessible_folders', []);
347
    $session->set('user-no_access_folders', []);
348
    $session->set('user-personal_visible_folders', []);
349
    $session->set('user-read_only_folders', []);
350
    $session->set('system-list_restricted_folders_for_items', []);
351
    $session->set('system-list_folders_editable_by_role', []);
352
    $session->set('user-list_folders_limited', []);
353
    $session->set('user-forbiden_personal_folders', []);
354
    $globalsUserId = $session->get('user-id');
355
    $globalsVisibleFolders = $session->get('user-accessible_folders');
356
    $globalsPersonalVisibleFolders = $session->get('user-personal_visible_folders');
357
    // Get list of Folders
358
    $rows = DB::query('SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i', 0);
359
    foreach ($rows as $record) {
360
        array_push($groupesVisibles, $record['id']);
361
    }
362
    $session->set('user-accessible_folders', $groupesVisibles);
363
    $session->set('user-all_non_personal_folders', $groupesVisibles);
364
    // Exclude all PF
365
    $where = new WhereClause('and');
366
    // create a WHERE statement of pieces joined by ANDs
367
    $where->add('personal_folder=%i', 1);
368
    if (
369
        isset($SETTINGS['enable_pf_feature']) === true
370
        && (int) $SETTINGS['enable_pf_feature'] === 1
371
    ) {
372
        $where->add('title=%s', $globalsUserId);
373
        $where->negateLast();
374
    }
375
    // Get ID of personal folder
376
    $persfld = DB::queryfirstrow(
377
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE title = %s',
378
        $globalsUserId
379
    );
380
    if (empty($persfld['id']) === false) {
381
        if (in_array($persfld['id'], $globalsVisibleFolders) === false) {
382
            array_push($globalsVisibleFolders, $persfld['id']);
383
            array_push($globalsPersonalVisibleFolders, $persfld['id']);
384
            // get all descendants
385
            $tree->rebuild();
386
            $tst = $tree->getDescendants($persfld['id']);
387
            foreach ($tst as $t) {
388
                array_push($globalsVisibleFolders, $t->id);
389
                array_push($globalsPersonalVisibleFolders, $t->id);
390
            }
391
        }
392
    }
393
394
    // get complete list of ROLES
395
    $tmp = explode(';', $idFonctions);
396
    $rows = DB::query(
397
        'SELECT * FROM ' . prefixTable('roles_title') . '
398
        ORDER BY title ASC'
399
    );
400
    foreach ($rows as $record) {
401
        if (! empty($record['id']) && ! in_array($record['id'], $tmp)) {
402
            array_push($tmp, $record['id']);
403
        }
404
    }
405
    $session->set('user-roles', implode(';', $tmp));
406
    $session->set('user-admin', 1);
407
    // Check if admin has created Folders and Roles
408
    DB::query('SELECT * FROM ' . prefixTable('nested_tree') . '');
409
    $session->set('user-nb_folders', DB::count());
410
    DB::query('SELECT * FROM ' . prefixTable('roles_title'));
411
    $session->set('user-nb_roles', DB::count());
412
413
    return true;
414
}
415
416
/**
417
 * Permits to convert an element to array.
418
 *
419
 * @param string|array $element Any value to be returned as array
420
 *
421
 * @return array
422
 */
423
function convertToArray($element): ?array
424
{
425
    if (is_string($element) === true) {
426
        if (empty($element) === true) {
427
            return [];
428
        }
429
        return explode(
430
            ';',
431
            trimElement($element, ';')
432
        );
433
    }
434
    return $element;
435
}
436
437
/**
438
 * Defines the rights the user has.
439
 *
440
 * @param string|array $allowedFolders  Allowed folders
441
 * @param string|array $noAccessFolders Not allowed folders
442
 * @param string|array $userRoles       Roles of user
443
 * @param array        $SETTINGS        Teampass settings
444
 * @param object       $tree            Tree of folders
445
 * 
446
 * @return bool
447
 */
448
function identUser(
449
    $allowedFolders,
450
    $noAccessFolders,
451
    $userRoles,
452
    array $SETTINGS,
453
    object $tree
454
) {
455
    
456
    $session = SessionManager::getSession();
457
    // Init
458
    $session->set('user-accessible_folders', []);
459
    $session->set('user-personal_folders', []);
460
    $session->set('user-no_access_folders', []);
461
    $session->set('user-personal_visible_folders', []);
462
    $session->set('user-read_only_folders', []);
463
    $session->set('user-user-roles', $userRoles);
464
    $session->set('user-admin', 0);
465
    // init
466
    $personalFolders = [];
467
    $readOnlyFolders = [];
468
    $noAccessPersonalFolders = [];
469
    $restrictedFoldersForItems = [];
470
    $foldersLimited = [];
471
    $foldersLimitedFull = [];
472
    $allowedFoldersByRoles = [];
473
    $globalsUserId = $session->get('user-id');
474
    $globalsPersonalFolders = $session->get('user-personal_folder_enabled');
475
    // Ensure consistency in array format
476
    $noAccessFolders = convertToArray($noAccessFolders);
477
    $userRoles = convertToArray($userRoles);
478
    $allowedFolders = convertToArray($allowedFolders);
479
    
480
    // Get list of folders depending on Roles
481
    $arrays = identUserGetFoldersFromRoles(
482
        $userRoles,
483
        $allowedFoldersByRoles,
484
        $readOnlyFolders,
485
        $allowedFolders
486
    );
487
    $allowedFoldersByRoles = $arrays['allowedFoldersByRoles'];
488
    $readOnlyFolders = $arrays['readOnlyFolders'];
489
490
    // Does this user is allowed to see other items
491
    $inc = 0;
492
    $rows = DB::query(
493
        'SELECT id, id_tree FROM ' . prefixTable('items') . '
494
            WHERE restricted_to LIKE %ss AND inactif = %s'.
495
            (count($allowedFolders) > 0 ? ' AND id_tree NOT IN ('.implode(',', $allowedFolders).')' : ''),
496
        $globalsUserId,
497
        '0'
498
    );
499
    foreach ($rows as $record) {
500
        // Exclude restriction on item if folder is fully accessible
501
        //if (in_array($record['id_tree'], $allowedFolders) === false) {
502
            $restrictedFoldersForItems[$record['id_tree']][$inc] = $record['id'];
503
            ++$inc;
504
        //}
505
    }
506
507
    // Check for the users roles if some specific rights exist on items
508
    $rows = DB::query(
509
        'SELECT i.id_tree, r.item_id
510
        FROM ' . prefixTable('items') . ' as i
511
        INNER JOIN ' . prefixTable('restriction_to_roles') . ' as r ON (r.item_id=i.id)
512
        WHERE i.id_tree <> "" '.
513
        (count($userRoles) > 0 ? 'AND r.role_id IN %li ' : '').
514
        'ORDER BY i.id_tree ASC',
515
        $userRoles
516
    );
517
    $inc = 0;
518
    foreach ($rows as $record) {
519
        //if (isset($record['id_tree'])) {
520
            $foldersLimited[$record['id_tree']][$inc] = $record['item_id'];
521
            array_push($foldersLimitedFull, $record['id_tree']);
522
            ++$inc;
523
        //}
524
    }
525
526
    // Get list of Personal Folders
527
    $arrays = identUserGetPFList(
528
        $globalsPersonalFolders,
529
        $allowedFolders,
530
        $globalsUserId,
531
        $personalFolders,
532
        $noAccessPersonalFolders,
533
        $foldersLimitedFull,
534
        $allowedFoldersByRoles,
535
        array_keys($restrictedFoldersForItems),
536
        $readOnlyFolders,
537
        $noAccessFolders,
538
        isset($SETTINGS['enable_pf_feature']) === true ? $SETTINGS['enable_pf_feature'] : 0,
539
        $tree
540
    );
541
    $allowedFolders = $arrays['allowedFolders'];
542
    $personalFolders = $arrays['personalFolders'];
543
    $noAccessPersonalFolders = $arrays['noAccessPersonalFolders'];
544
545
    // Return data
546
    $session->set('user-all_non_personal_folders', $allowedFolders);
547
    $session->set('user-accessible_folders', array_unique(array_merge($allowedFolders, $personalFolders), SORT_NUMERIC));
548
    $session->set('user-read_only_folders', $readOnlyFolders);
549
    $session->set('user-no_access_folders', $noAccessFolders);
550
    $session->set('user-personal_folders', $personalFolders);
551
    $session->set('user-list_folders_limited', $foldersLimited);
552
    $session->set('system-list_folders_editable_by_role', $allowedFoldersByRoles, 'SESSION');
553
    $session->set('system-list_restricted_folders_for_items', $restrictedFoldersForItems);
554
    $session->set('user-forbiden_personal_folders', $noAccessPersonalFolders);
555
    $session->set(
556
        'all_folders_including_no_access',
557
        array_unique(array_merge(
558
            $allowedFolders,
559
            $personalFolders,
560
            $noAccessFolders,
561
            $readOnlyFolders
562
        ), SORT_NUMERIC)
563
    );
564
    // Folders and Roles numbers
565
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('nested_tree') . '');
566
    $session->set('user-nb_folders', DB::count());
567
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('roles_title'));
568
    $session->set('user-nb_roles', DB::count());
569
    // check if change proposals on User's items
570
    if (isset($SETTINGS['enable_suggestion']) === true && (int) $SETTINGS['enable_suggestion'] === 1) {
571
        $countNewItems = DB::query(
572
            'SELECT COUNT(*)
573
            FROM ' . prefixTable('items_change') . ' AS c
574
            LEFT JOIN ' . prefixTable('log_items') . ' AS i ON (c.item_id = i.id_item)
575
            WHERE i.action = %s AND i.id_user = %i',
576
            'at_creation',
577
            $globalsUserId
578
        );
579
        $session->set('user-nb_item_change_proposals', $countNewItems);
580
    } else {
581
        $session->set('user-nb_item_change_proposals', 0);
582
    }
583
584
    return true;
585
}
586
587
/**
588
 * Get list of folders depending on Roles
589
 * 
590
 * @param array $userRoles
591
 * @param array $allowedFoldersByRoles
592
 * @param array $readOnlyFolders
593
 * @param array $allowedFolders
594
 * 
595
 * @return array
596
 */
597
function identUserGetFoldersFromRoles($userRoles, $allowedFoldersByRoles, $readOnlyFolders, $allowedFolders) : array
598
{
599
    $rows = DB::query(
600
        'SELECT *
601
        FROM ' . prefixTable('roles_values') . '
602
        WHERE type IN %ls'.(count($userRoles) > 0 ? ' AND role_id IN %li' : ''),
603
        ['W', 'ND', 'NE', 'NDNE', 'R'],
604
        $userRoles,
605
    );
606
    foreach ($rows as $record) {
607
        if ($record['type'] === 'R') {
608
            array_push($readOnlyFolders, $record['folder_id']);
609
        } elseif (in_array($record['folder_id'], $allowedFolders) === false) {
610
            array_push($allowedFoldersByRoles, $record['folder_id']);
611
        }
612
    }
613
    $allowedFoldersByRoles = array_unique($allowedFoldersByRoles);
614
    $readOnlyFolders = array_unique($readOnlyFolders);
615
    
616
    // Clean arrays
617
    foreach ($allowedFoldersByRoles as $value) {
618
        $key = array_search($value, $readOnlyFolders);
619
        if ($key !== false) {
620
            unset($readOnlyFolders[$key]);
621
        }
622
    }
623
    return [
624
        'readOnlyFolders' => $readOnlyFolders,
625
        'allowedFoldersByRoles' => $allowedFoldersByRoles
626
    ];
627
}
628
629
/**
630
 * Get list of Personal Folders
631
 * 
632
 * @param int $globalsPersonalFolders
633
 * @param array $allowedFolders
634
 * @param int $globalsUserId
635
 * @param array $personalFolders
636
 * @param array $noAccessPersonalFolders
637
 * @param array $foldersLimitedFull
638
 * @param array $allowedFoldersByRoles
639
 * @param array $restrictedFoldersForItems
640
 * @param array $readOnlyFolders
641
 * @param array $noAccessFolders
642
 * @param int $enablePfFeature
643
 * @param object $tree
644
 * 
645
 * @return array
646
 */
647
function identUserGetPFList(
648
    $globalsPersonalFolders,
649
    $allowedFolders,
650
    $globalsUserId,
651
    $personalFolders,
652
    $noAccessPersonalFolders,
653
    $foldersLimitedFull,
654
    $allowedFoldersByRoles,
655
    $restrictedFoldersForItems,
656
    $readOnlyFolders,
657
    $noAccessFolders,
658
    $enablePfFeature,
659
    $tree
660
)
661
{
662
    if (
663
        (int) $enablePfFeature === 1
664
        && (int) $globalsPersonalFolders === 1
665
    ) {
666
        $persoFld = DB::queryfirstrow(
667
            'SELECT id
668
            FROM ' . prefixTable('nested_tree') . '
669
            WHERE title = %s AND personal_folder = %i'.
670
            (count($allowedFolders) > 0 ? ' AND id NOT IN ('.implode(',', $allowedFolders).')' : ''),
671
            $globalsUserId,
672
            1
673
        );
674
        if (empty($persoFld['id']) === false) {
675
            array_push($personalFolders, $persoFld['id']);
676
            array_push($allowedFolders, $persoFld['id']);
677
            // get all descendants
678
            $ids = $tree->getDescendants($persoFld['id'], false, false, true);
679
            foreach ($ids as $id) {
680
                //array_push($allowedFolders, $id);
681
                array_push($personalFolders, $id);
682
            }
683
        }
684
    }
685
    
686
    // Exclude all other PF
687
    $where = new WhereClause('and');
688
    $where->add('personal_folder=%i', 1);
689
    if (count($personalFolders) > 0) {
690
        $where->add('id NOT IN ('.implode(',', $personalFolders).')');
691
    }
692
    if (
693
        (int) $enablePfFeature === 1
694
        && (int) $globalsPersonalFolders === 1
695
    ) {
696
        $where->add('title=%s', $globalsUserId);
697
        $where->negateLast();
698
    }
699
    $persoFlds = DB::query(
700
        'SELECT id
701
        FROM ' . prefixTable('nested_tree') . '
702
        WHERE %l',
703
        $where
704
    );
705
    foreach ($persoFlds as $persoFldId) {
706
        array_push($noAccessPersonalFolders, $persoFldId['id']);
707
    }
708
709
    // All folders visibles
710
    $allowedFolders = array_unique(array_merge(
711
        $allowedFolders,
712
        $foldersLimitedFull,
713
        $allowedFoldersByRoles,
714
        $restrictedFoldersForItems,
715
        $readOnlyFolders
716
    ), SORT_NUMERIC);
717
    // Exclude from allowed folders all the specific user forbidden folders
718
    if (count($noAccessFolders) > 0) {
719
        $allowedFolders = array_diff($allowedFolders, $noAccessFolders);
720
    }
721
722
    return [
723
        'allowedFolders' => array_diff(array_diff($allowedFolders, $noAccessPersonalFolders), $personalFolders),
724
        'personalFolders' => $personalFolders,
725
        'noAccessPersonalFolders' => $noAccessPersonalFolders
726
    ];
727
}
728
729
730
/**
731
 * Update the CACHE table.
732
 *
733
 * @param string $action   What to do
734
 * @param array  $SETTINGS Teampass settings
735
 * @param int    $ident    Ident format
736
 * 
737
 * @return void
738
 */
739
function updateCacheTable(string $action, ?int $ident = null): void
740
{
741
    if ($action === 'reload') {
742
        // Rebuild full cache table
743
        cacheTableRefresh();
744
    } elseif ($action === 'update_value' && is_null($ident) === false) {
745
        // UPDATE an item
746
        cacheTableUpdate($ident);
747
    } elseif ($action === 'add_value' && is_null($ident) === false) {
748
        // ADD an item
749
        cacheTableAdd($ident);
750
    } elseif ($action === 'delete_value' && is_null($ident) === false) {
751
        // DELETE an item
752
        DB::delete(prefixTable('cache'), 'id = %i', $ident);
753
    }
754
}
755
756
/**
757
 * Cache table - refresh.
758
 *
759
 * @return void
760
 */
761
function cacheTableRefresh(): void
762
{
763
    // Load class DB
764
    loadClasses('DB');
765
766
    //Load Tree
767
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
768
    // truncate table
769
    DB::query('TRUNCATE TABLE ' . prefixTable('cache'));
770
    // reload date
771
    $rows = DB::query(
772
        'SELECT *
773
        FROM ' . prefixTable('items') . ' as i
774
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
775
        AND l.action = %s
776
        AND i.inactif = %i',
777
        'at_creation',
778
        0
779
    );
780
    foreach ($rows as $record) {
781
        if (empty($record['id_tree']) === false) {
782
            // Get all TAGS
783
            $tags = '';
784
            $itemTags = DB::query(
785
                'SELECT tag
786
                FROM ' . prefixTable('tags') . '
787
                WHERE item_id = %i AND tag != ""',
788
                $record['id']
789
            );
790
            foreach ($itemTags as $itemTag) {
791
                $tags .= $itemTag['tag'] . ' ';
792
            }
793
794
            // Get renewal period
795
            $resNT = DB::queryfirstrow(
796
                'SELECT renewal_period
797
                FROM ' . prefixTable('nested_tree') . '
798
                WHERE id = %i',
799
                $record['id_tree']
800
            );
801
            // form id_tree to full foldername
802
            $folder = [];
803
            $arbo = $tree->getPath($record['id_tree'], true);
804
            foreach ($arbo as $elem) {
805
                // Check if title is the ID of a user
806
                if (is_numeric($elem->title) === true) {
807
                    // Is this a User id?
808
                    $user = DB::queryfirstrow(
809
                        'SELECT id, login
810
                        FROM ' . prefixTable('users') . '
811
                        WHERE id = %i',
812
                        $elem->title
813
                    );
814
                    if (count($user) > 0) {
815
                        $elem->title = $user['login'];
816
                    }
817
                }
818
                // Build path
819
                array_push($folder, stripslashes($elem->title));
820
            }
821
            // store data
822
            DB::insert(
823
                prefixTable('cache'),
824
                [
825
                    'id' => $record['id'],
826
                    'label' => $record['label'],
827
                    'description' => $record['description'] ?? '',
828
                    'url' => isset($record['url']) && ! empty($record['url']) ? $record['url'] : '0',
829
                    'tags' => $tags,
830
                    'id_tree' => $record['id_tree'],
831
                    'perso' => $record['perso'],
832
                    'restricted_to' => isset($record['restricted_to']) && ! empty($record['restricted_to']) ? $record['restricted_to'] : '0',
833
                    'login' => $record['login'] ?? '',
834
                    'folder' => implode(' > ', $folder),
835
                    'author' => $record['id_user'],
836
                    'renewal_period' => $resNT['renewal_period'] ?? '0',
837
                    'timestamp' => $record['date'],
838
                ]
839
            );
840
        }
841
    }
842
}
843
844
/**
845
 * Cache table - update existing value.
846
 *
847
 * @param int    $ident    Ident format
848
 * 
849
 * @return void
850
 */
851
function cacheTableUpdate(?int $ident = null): void
852
{
853
    $session = SessionManager::getSession();
854
    loadClasses('DB');
855
856
    //Load Tree
857
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
858
    // get new value from db
859
    $data = DB::queryfirstrow(
860
        'SELECT label, description, id_tree, perso, restricted_to, login, url
861
        FROM ' . prefixTable('items') . '
862
        WHERE id=%i',
863
        $ident
864
    );
865
    // Get all TAGS
866
    $tags = '';
867
    $itemTags = DB::query(
868
        'SELECT tag
869
            FROM ' . prefixTable('tags') . '
870
            WHERE item_id = %i AND tag != ""',
871
        $ident
872
    );
873
    foreach ($itemTags as $itemTag) {
874
        $tags .= $itemTag['tag'] . ' ';
875
    }
876
    // form id_tree to full foldername
877
    $folder = [];
878
    $arbo = $tree->getPath($data['id_tree'], true);
879
    foreach ($arbo as $elem) {
880
        // Check if title is the ID of a user
881
        if (is_numeric($elem->title) === true) {
882
            // Is this a User id?
883
            $user = DB::queryfirstrow(
884
                'SELECT id, login
885
                FROM ' . prefixTable('users') . '
886
                WHERE id = %i',
887
                $elem->title
888
            );
889
            if (count($user) > 0) {
890
                $elem->title = $user['login'];
891
            }
892
        }
893
        // Build path
894
        array_push($folder, stripslashes($elem->title));
895
    }
896
    // finaly update
897
    DB::update(
898
        prefixTable('cache'),
899
        [
900
            'label' => $data['label'],
901
            'description' => $data['description'],
902
            'tags' => $tags,
903
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
904
            'id_tree' => $data['id_tree'],
905
            'perso' => $data['perso'],
906
            'restricted_to' => isset($data['restricted_to']) && ! empty($data['restricted_to']) ? $data['restricted_to'] : '0',
907
            'login' => $data['login'] ?? '',
908
            'folder' => implode(' » ', $folder),
909
            'author' => $session->get('user-id'),
910
        ],
911
        'id = %i',
912
        $ident
913
    );
914
}
915
916
/**
917
 * Cache table - add new value.
918
 *
919
 * @param int    $ident    Ident format
920
 * 
921
 * @return void
922
 */
923
function cacheTableAdd(?int $ident = null): void
924
{
925
    $session = SessionManager::getSession();
926
    $globalsUserId = $session->get('user-id');
927
928
    // Load class DB
929
    loadClasses('DB');
930
931
    //Load Tree
932
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
933
    // get new value from db
934
    $data = DB::queryFirstRow(
935
        'SELECT i.label, i.description, i.id_tree as id_tree, i.perso, i.restricted_to, i.id, i.login, i.url, l.date
936
        FROM ' . prefixTable('items') . ' as i
937
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
938
        WHERE i.id = %i
939
        AND l.action = %s',
940
        $ident,
941
        'at_creation'
942
    );
943
    // Get all TAGS
944
    $tags = '';
945
    $itemTags = DB::query(
946
        'SELECT tag
947
            FROM ' . prefixTable('tags') . '
948
            WHERE item_id = %i AND tag != ""',
949
        $ident
950
    );
951
    foreach ($itemTags as $itemTag) {
952
        $tags .= $itemTag['tag'] . ' ';
953
    }
954
    // form id_tree to full foldername
955
    $folder = [];
956
    $arbo = $tree->getPath($data['id_tree'], true);
957
    foreach ($arbo as $elem) {
958
        // Check if title is the ID of a user
959
        if (is_numeric($elem->title) === true) {
960
            // Is this a User id?
961
            $user = DB::queryfirstrow(
962
                'SELECT id, login
963
                FROM ' . prefixTable('users') . '
964
                WHERE id = %i',
965
                $elem->title
966
            );
967
            if (count($user) > 0) {
968
                $elem->title = $user['login'];
969
            }
970
        }
971
        // Build path
972
        array_push($folder, stripslashes($elem->title));
973
    }
974
    // finaly update
975
    DB::insert(
976
        prefixTable('cache'),
977
        [
978
            'id' => $data['id'],
979
            'label' => $data['label'],
980
            'description' => $data['description'],
981
            'tags' => isset($tags) && empty($tags) === false ? $tags : 'None',
982
            'url' => isset($data['url']) && ! empty($data['url']) ? $data['url'] : '0',
983
            'id_tree' => $data['id_tree'],
984
            'perso' => isset($data['perso']) && empty($data['perso']) === false && $data['perso'] !== 'None' ? $data['perso'] : '0',
985
            'restricted_to' => isset($data['restricted_to']) && empty($data['restricted_to']) === false ? $data['restricted_to'] : '0',
986
            'login' => $data['login'] ?? '',
987
            'folder' => implode(' » ', $folder),
988
            'author' => $globalsUserId,
989
            'timestamp' => $data['date'],
990
        ]
991
    );
992
}
993
994
/**
995
 * Do statistics.
996
 *
997
 * @param array $SETTINGS Teampass settings
998
 *
999
 * @return array
1000
 */
1001
function getStatisticsData(array $SETTINGS): array
1002
{
1003
    DB::query(
1004
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1005
        0
1006
    );
1007
    $counter_folders = DB::count();
1008
    DB::query(
1009
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1010
        1
1011
    );
1012
    $counter_folders_perso = DB::count();
1013
    DB::query(
1014
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1015
        0
1016
    );
1017
    $counter_items = DB::count();
1018
        DB::query(
1019
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1020
        1
1021
    );
1022
    $counter_items_perso = DB::count();
1023
        DB::query(
1024
        'SELECT id FROM ' . prefixTable('users') . ' WHERE login NOT IN (%s, %s, %s)',
1025
        'OTV', 'TP', 'API'
1026
    );
1027
    $counter_users = DB::count();
1028
        DB::query(
1029
        'SELECT id FROM ' . prefixTable('users') . ' WHERE admin = %i',
1030
        1
1031
    );
1032
    $admins = DB::count();
1033
    DB::query(
1034
        'SELECT id FROM ' . prefixTable('users') . ' WHERE gestionnaire = %i',
1035
        1
1036
    );
1037
    $managers = DB::count();
1038
    DB::query(
1039
        'SELECT id FROM ' . prefixTable('users') . ' WHERE read_only = %i',
1040
        1
1041
    );
1042
    $readOnly = DB::count();
1043
    // list the languages
1044
    $usedLang = [];
1045
    $tp_languages = DB::query(
1046
        'SELECT name FROM ' . prefixTable('languages')
1047
    );
1048
    foreach ($tp_languages as $tp_language) {
1049
        DB::query(
1050
            'SELECT * FROM ' . prefixTable('users') . ' WHERE user_language = %s',
1051
            $tp_language['name']
1052
        );
1053
        $usedLang[$tp_language['name']] = round((DB::count() * 100 / $counter_users), 0);
1054
    }
1055
1056
    // get list of ips
1057
    $usedIp = [];
1058
    $tp_ips = DB::query(
1059
        'SELECT user_ip FROM ' . prefixTable('users')
1060
    );
1061
    foreach ($tp_ips as $ip) {
1062
        if (array_key_exists($ip['user_ip'], $usedIp)) {
1063
            $usedIp[$ip['user_ip']] += $usedIp[$ip['user_ip']];
1064
        } elseif (! empty($ip['user_ip']) && $ip['user_ip'] !== 'none') {
1065
            $usedIp[$ip['user_ip']] = 1;
1066
        }
1067
    }
1068
1069
    return [
1070
        'error' => '',
1071
        'stat_phpversion' => phpversion(),
1072
        'stat_folders' => $counter_folders,
1073
        'stat_folders_shared' => intval($counter_folders) - intval($counter_folders_perso),
1074
        'stat_items' => $counter_items,
1075
        'stat_items_shared' => intval($counter_items) - intval($counter_items_perso),
1076
        'stat_users' => $counter_users,
1077
        'stat_admins' => $admins,
1078
        'stat_managers' => $managers,
1079
        'stat_ro' => $readOnly,
1080
        'stat_kb' => $SETTINGS['enable_kb'],
1081
        'stat_pf' => $SETTINGS['enable_pf_feature'],
1082
        'stat_fav' => $SETTINGS['enable_favourites'],
1083
        'stat_teampassversion' => TP_VERSION,
1084
        'stat_ldap' => $SETTINGS['ldap_mode'],
1085
        'stat_agses' => $SETTINGS['agses_authentication_enabled'],
1086
        'stat_duo' => $SETTINGS['duo'],
1087
        'stat_suggestion' => $SETTINGS['enable_suggestion'],
1088
        'stat_api' => $SETTINGS['api'],
1089
        'stat_customfields' => $SETTINGS['item_extra_fields'],
1090
        'stat_syslog' => $SETTINGS['syslog_enable'],
1091
        'stat_2fa' => $SETTINGS['google_authentication'],
1092
        'stat_stricthttps' => $SETTINGS['enable_sts'],
1093
        'stat_mysqlversion' => DB::serverVersion(),
1094
        'stat_languages' => $usedLang,
1095
        'stat_country' => $usedIp,
1096
    ];
1097
}
1098
1099
/**
1100
 * Permits to prepare the way to send the email
1101
 * 
1102
 * @param string $subject       email subject
1103
 * @param string $body          email message
1104
 * @param string $email         email
1105
 * @param string $receiverName  Receiver name
1106
 * @param array  $SETTINGS      settings
1107
 *
1108
 * @return void
1109
 */
1110
function prepareSendingEmail(
1111
    $subject,
1112
    $body,
1113
    $email,
1114
    $receiverName = ''
1115
): void 
1116
{
1117
    DB::insert(
1118
        prefixTable('background_tasks'),
1119
        array(
1120
            'created_at' => time(),
1121
            'process_type' => 'send_email',
1122
            'arguments' => json_encode([
1123
                'subject' => $subject,
1124
                'receivers' => $email,
1125
                'body' => $body,
1126
                'receiver_name' => $receiverName,
1127
            ], JSON_HEX_QUOT | JSON_HEX_TAG),
1128
        )
1129
    );
1130
}
1131
1132
/**
1133
 * Returns the email body.
1134
 *
1135
 * @param string $textMail Text for the email
1136
 */
1137
function emailBody(string $textMail): string
1138
{
1139
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1140
    w3.org/TR/html4/loose.dtd"><html>
1141
    <head><title>Email Template</title>
1142
    <style type="text/css">
1143
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1144
    </style></head>
1145
    <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">
1146
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1147
    <tr><td style="border-collapse: collapse;"><br>
1148
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1149
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1150
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1151
        </td></tr></table></td>
1152
    </tr>
1153
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1154
        <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;">
1155
        <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;">
1156
        <br><div style="float:right;">' .
1157
        $textMail .
1158
        '<br><br></td></tr></table>
1159
    </td></tr></table>
1160
    <br></body></html>';
1161
}
1162
1163
/**
1164
 * Convert date to timestamp.
1165
 *
1166
 * @param string $date        The date
1167
 * @param string $date_format Date format
1168
 *
1169
 * @return int
1170
 */
1171
function dateToStamp(string $date, string $date_format): int
1172
{
1173
    $date = date_parse_from_format($date_format, $date);
1174
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1175
        return mktime(
1176
            empty($date['hour']) === false ? $date['hour'] : 23,
1177
            empty($date['minute']) === false ? $date['minute'] : 59,
1178
            empty($date['second']) === false ? $date['second'] : 59,
1179
            $date['month'],
1180
            $date['day'],
1181
            $date['year']
1182
        );
1183
    }
1184
    return 0;
1185
}
1186
1187
/**
1188
 * Is this a date.
1189
 *
1190
 * @param string $date Date
1191
 *
1192
 * @return bool
1193
 */
1194
function isDate(string $date): bool
1195
{
1196
    return strtotime($date) !== false;
1197
}
1198
1199
/**
1200
 * Check if isUTF8().
1201
 *
1202
 * @param string|array $string Is the string
1203
 *
1204
 * @return int is the string in UTF8 format
1205
 */
1206
function isUTF8($string): int
1207
{
1208
    if (is_array($string) === true) {
1209
        $string = $string['string'];
1210
    }
1211
1212
    return preg_match(
1213
        '%^(?:
1214
        [\x09\x0A\x0D\x20-\x7E] # ASCII
1215
        | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
1216
        | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
1217
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
1218
        | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
1219
        | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
1220
        | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
1221
        | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
1222
        )*$%xs',
1223
        $string
1224
    );
1225
}
1226
1227
/**
1228
 * Prepare an array to UTF8 format before JSON_encode.
1229
 *
1230
 * @param array $array Array of values
1231
 *
1232
 * @return array
1233
 */
1234
function utf8Converter(array $array): array
1235
{
1236
    array_walk_recursive(
1237
        $array,
1238
        static function (&$item): void {
1239
            if (mb_detect_encoding((string) $item, 'utf-8', true) === false) {
1240
                $item = mb_convert_encoding($item, 'ISO-8859-1', 'UTF-8');
1241
            }
1242
        }
1243
    );
1244
    return $array;
1245
}
1246
1247
/**
1248
 * Permits to prepare data to be exchanged.
1249
 *
1250
 * @param array|string $data Text
1251
 * @param string       $type Parameter
1252
 * @param string       $key  Optional key
1253
 *
1254
 * @return string|array
1255
 */
1256
function prepareExchangedData($data, string $type, ?string $key = null)
1257
{
1258
    $session = SessionManager::getSession();
1259
    
1260
    // Perform
1261
    if ($type === 'encode' && is_array($data) === true) {
1262
        // json encoding
1263
        $data = json_encode(
1264
            $data,
1265
            JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1266
        );
1267
1268
        // Now encrypt
1269
        if ($session->get('encryptClientServer') === 1) {
1270
            $data = Encryption::encrypt(
1271
                $data,
1272
                $session->get('key')
1273
            );
1274
        }
1275
1276
        return $data;
1277
    }
1278
    if ($type === 'decode' && is_array($data) === false) {
1279
        // Decrypt if needed
1280
        if ($session->get('encryptClientServer') === 1) {
1281
            $data = (string) Encryption::decrypt(
1282
                (string) $data,
1283
                $session->get('key')
1284
            );
1285
        } else {
1286
            // Double html encoding received
1287
            $data = html_entity_decode(html_entity_decode($data));
0 ignored issues
show
Bug introduced by
$data of type array is incompatible with the type string expected by parameter $string of html_entity_decode(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1287
            $data = html_entity_decode(html_entity_decode(/** @scrutinizer ignore-type */ $data));
Loading history...
1288
        }
1289
1290
        // Return data array
1291
        return json_decode($data, true);
1292
    }
1293
    return '';
1294
}
1295
1296
1297
/**
1298
 * Create a thumbnail.
1299
 *
1300
 * @param string  $src           Source
1301
 * @param string  $dest          Destination
1302
 * @param int $desired_width Size of width
1303
 * 
1304
 * @return void|string|bool
1305
 */
1306
function makeThumbnail(string $src, string $dest, int $desired_width)
1307
{
1308
    /* read the source image */
1309
    if (is_file($src) === true && mime_content_type($src) === 'image/png') {
1310
        $source_image = imagecreatefrompng($src);
1311
        if ($source_image === false) {
1312
            return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1313
        }
1314
    } else {
1315
        return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1316
    }
1317
1318
    // Get height and width
1319
    $width = imagesx($source_image);
1320
    $height = imagesy($source_image);
1321
    /* find the "desired height" of this thumbnail, relative to the desired width  */
1322
    $desired_height = (int) floor($height * $desired_width / $width);
1323
    /* create a new, "virtual" image */
1324
    $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
1325
    if ($virtual_image === false) {
1326
        return false;
1327
    }
1328
    /* copy source image at a resized size */
1329
    imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
1330
    /* create the physical thumbnail image to its destination */
1331
    imagejpeg($virtual_image, $dest);
1332
}
1333
1334
/**
1335
 * Check table prefix in SQL query.
1336
 *
1337
 * @param string $table Table name
1338
 * 
1339
 * @return string
1340
 */
1341
function prefixTable(string $table): string
1342
{
1343
    $safeTable = htmlspecialchars(DB_PREFIX . $table);
1344
    if (empty($safeTable) === false) {
1345
        // sanitize string
1346
        return $safeTable;
1347
    }
1348
    // stop error no table
1349
    return 'table_not_exists';
1350
}
1351
1352
/**
1353
 * GenerateCryptKey
1354
 *
1355
 * @param int     $size      Length
1356
 * @param bool $secure Secure
1357
 * @param bool $numerals Numerics
1358
 * @param bool $uppercase Uppercase letters
1359
 * @param bool $symbols Symbols
1360
 * @param bool $lowercase Lowercase
1361
 * 
1362
 * @return string
1363
 */
1364
function GenerateCryptKey(
1365
    int $size = 20,
1366
    bool $secure = false,
1367
    bool $numerals = false,
1368
    bool $uppercase = false,
1369
    bool $symbols = false,
1370
    bool $lowercase = false
1371
): string {
1372
    $generator = new ComputerPasswordGenerator();
1373
    $generator->setRandomGenerator(new Php7RandomGenerator());
1374
    
1375
    // Manage size
1376
    $generator->setLength((int) $size);
1377
    if ($secure === true) {
1378
        $generator->setSymbols(true);
1379
        $generator->setLowercase(true);
1380
        $generator->setUppercase(true);
1381
        $generator->setNumbers(true);
1382
    } else {
1383
        $generator->setLowercase($lowercase);
1384
        $generator->setUppercase($uppercase);
1385
        $generator->setNumbers($numerals);
1386
        $generator->setSymbols($symbols);
1387
    }
1388
1389
    return $generator->generatePasswords()[0];
1390
}
1391
1392
/**
1393
 * GenerateGenericPassword
1394
 *
1395
 * @param int     $size      Length
1396
 * @param bool $secure Secure
1397
 * @param bool $numerals Numerics
1398
 * @param bool $uppercase Uppercase letters
1399
 * @param bool $symbols Symbols
1400
 * @param bool $lowercase Lowercase
1401
 * @param array   $SETTINGS  SETTINGS
1402
 * 
1403
 * @return string
1404
 */
1405
function generateGenericPassword(
1406
    int $size,
1407
    bool $secure,
1408
    bool $lowercase,
1409
    bool $capitalize,
1410
    bool $numerals,
1411
    bool $symbols,
1412
    array $SETTINGS
1413
): string
1414
{
1415
    if ((int) $size > (int) $SETTINGS['pwd_maximum_length']) {
1416
        return prepareExchangedData(
1417
            array(
1418
                'error_msg' => 'Password length is too long! ',
1419
                'error' => 'true',
1420
            ),
1421
            'encode'
1422
        );
1423
    }
1424
    // Load libraries
1425
    $generator = new ComputerPasswordGenerator();
1426
    $generator->setRandomGenerator(new Php7RandomGenerator());
1427
1428
    // Manage size
1429
    $generator->setLength(($size <= 0) ? 10 : $size);
1430
1431
    if ($secure === true) {
1432
        $generator->setSymbols(true);
1433
        $generator->setLowercase(true);
1434
        $generator->setUppercase(true);
1435
        $generator->setNumbers(true);
1436
    } else {
1437
        $generator->setLowercase($lowercase);
1438
        $generator->setUppercase($capitalize);
1439
        $generator->setNumbers($numerals);
1440
        $generator->setSymbols($symbols);
1441
    }
1442
1443
    return prepareExchangedData(
1444
        array(
1445
            'key' => $generator->generatePasswords(),
1446
            'error' => '',
1447
        ),
1448
        'encode'
1449
    );
1450
}
1451
1452
/**
1453
 * Send sysLOG message
1454
 *
1455
 * @param string    $message
1456
 * @param string    $host
1457
 * @param int       $port
1458
 * @param string    $component
1459
 * 
1460
 * @return void
1461
*/
1462
function send_syslog($message, $host, $port, $component = 'teampass'): void
1463
{
1464
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1465
    $syslog_message = '<123>' . date('M d H:i:s ') . $component . ': ' . $message;
1466
    socket_sendto($sock, (string) $syslog_message, strlen($syslog_message), 0, (string) $host, (int) $port);
1467
    socket_close($sock);
1468
}
1469
1470
/**
1471
 * Permits to log events into DB
1472
 *
1473
 * @param array  $SETTINGS Teampass settings
1474
 * @param string $type     Type
1475
 * @param string $label    Label
1476
 * @param string $who      Who
1477
 * @param string $login    Login
1478
 * @param string|int $field_1  Field
1479
 * 
1480
 * @return void
1481
 */
1482
function logEvents(
1483
    array $SETTINGS, 
1484
    string $type, 
1485
    string $label, 
1486
    string $who, 
1487
    ?string $login = null, 
1488
    $field_1 = null
1489
): void
1490
{
1491
    if (empty($who)) {
1492
        $who = getClientIpServer();
1493
    }
1494
1495
    // Load class DB
1496
    loadClasses('DB');
1497
1498
    DB::insert(
1499
        prefixTable('log_system'),
1500
        [
1501
            'type' => $type,
1502
            'date' => time(),
1503
            'label' => $label,
1504
            'qui' => $who,
1505
            'field_1' => $field_1 === null ? '' : $field_1,
1506
        ]
1507
    );
1508
    // If SYSLOG
1509
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1510
        if ($type === 'user_mngt') {
1511
            send_syslog(
1512
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1513
                $SETTINGS['syslog_host'],
1514
                $SETTINGS['syslog_port'],
1515
                'teampass'
1516
            );
1517
        } else {
1518
            send_syslog(
1519
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1520
                $SETTINGS['syslog_host'],
1521
                $SETTINGS['syslog_port'],
1522
                'teampass'
1523
            );
1524
        }
1525
    }
1526
}
1527
1528
/**
1529
 * Log events.
1530
 *
1531
 * @param array  $SETTINGS        Teampass settings
1532
 * @param int    $item_id         Item id
1533
 * @param string $item_label      Item label
1534
 * @param int    $id_user         User id
1535
 * @param string $action          Code for reason
1536
 * @param string $login           User login
1537
 * @param string $raison          Code for reason
1538
 * @param string $encryption_type Encryption on
1539
 * @param string $time Encryption Time
1540
 * @param string $old_value       Old value
1541
 * 
1542
 * @return void
1543
 */
1544
function logItems(
1545
    array $SETTINGS,
1546
    int $item_id,
1547
    string $item_label,
1548
    int $id_user,
1549
    string $action,
1550
    ?string $login = null,
1551
    ?string $raison = null,
1552
    ?string $encryption_type = null,
1553
    ?string $time = null,
1554
    ?string $old_value = null
1555
): void {
1556
    // Load class DB
1557
    loadClasses('DB');
1558
1559
    // Insert log in DB
1560
    DB::insert(
1561
        prefixTable('log_items'),
1562
        [
1563
            'id_item' => $item_id,
1564
            'date' => is_null($time) === true ? time() : $time,
1565
            'id_user' => $id_user,
1566
            'action' => $action,
1567
            'raison' => $raison,
1568
            'old_value' => $old_value,
1569
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1570
        ]
1571
    );
1572
    // Timestamp the last change
1573
    if (in_array($action, ['at_creation', 'at_modifiation', 'at_delete', 'at_import'], true)) {
1574
        DB::update(
1575
            prefixTable('misc'),
1576
            [
1577
                'valeur' => time(),
1578
                'updated_at' => time(),
1579
            ],
1580
            'type = %s AND intitule = %s',
1581
            'timestamp',
1582
            'last_item_change'
1583
        );
1584
    }
1585
1586
    // SYSLOG
1587
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1588
        // Extract reason
1589
        $attribute = is_null($raison) === true ? Array('') : explode(' : ', $raison);
1590
        // Get item info if not known
1591
        if (empty($item_label) === true) {
1592
            $dataItem = DB::queryfirstrow(
1593
                'SELECT id, id_tree, label
1594
                FROM ' . prefixTable('items') . '
1595
                WHERE id = %i',
1596
                $item_id
1597
            );
1598
            $item_label = $dataItem['label'];
1599
        }
1600
1601
        send_syslog(
1602
            'action=' . str_replace('at_', '', $action) .
1603
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1604
                ' itemno=' . $item_id .
1605
                ' user=' . (is_null($login) === true ? '' : addslashes((string) $login)) .
1606
                ' itemname="' . addslashes($item_label) . '"',
1607
            $SETTINGS['syslog_host'],
1608
            $SETTINGS['syslog_port'],
1609
            'teampass'
1610
        );
1611
    }
1612
1613
    // send notification if enabled
1614
    //notifyOnChange($item_id, $action, $SETTINGS);
1615
}
1616
1617
/**
1618
 * Prepare notification email to subscribers.
1619
 *
1620
 * @param int    $item_id  Item id
1621
 * @param string $label    Item label
1622
 * @param array  $changes  List of changes
1623
 * @param array  $SETTINGS Teampass settings
1624
 * 
1625
 * @return void
1626
 */
1627
function notifyChangesToSubscribers(int $item_id, string $label, array $changes, array $SETTINGS): void
1628
{
1629
    $session = SessionManager::getSession();
1630
    $lang = new Language($session->get('user-language') ?? 'english');
1631
    $globalsUserId = $session->get('user-id');
1632
    $globalsLastname = $session->get('user-lastname');
1633
    $globalsName = $session->get('user-name');
1634
    // send email to user that what to be notified
1635
    $notification = DB::queryOneColumn(
1636
        'email',
1637
        'SELECT *
1638
        FROM ' . prefixTable('notification') . ' AS n
1639
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1640
        WHERE n.item_id = %i AND n.user_id != %i',
1641
        $item_id,
1642
        $globalsUserId
1643
    );
1644
    if (DB::count() > 0) {
1645
        // Prepare path
1646
        $path = geItemReadablePath($item_id, '', $SETTINGS);
1647
        // Get list of changes
1648
        $htmlChanges = '<ul>';
1649
        foreach ($changes as $change) {
1650
            $htmlChanges .= '<li>' . $change . '</li>';
1651
        }
1652
        $htmlChanges .= '</ul>';
1653
        // send email
1654
        DB::insert(
1655
            prefixTable('emails'),
1656
            [
1657
                'timestamp' => time(),
1658
                'subject' => $lang->get('email_subject_item_updated'),
1659
                'body' => str_replace(
1660
                    ['#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'],
1661
                    [$label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges],
1662
                    $lang->get('email_body_item_updated')
1663
                ),
1664
                'receivers' => implode(',', $notification),
1665
                'status' => '',
1666
            ]
1667
        );
1668
    }
1669
}
1670
1671
/**
1672
 * Returns the Item + path.
1673
 *
1674
 * @param int    $id_tree  Node id
1675
 * @param string $label    Label
1676
 * @param array  $SETTINGS TP settings
1677
 * 
1678
 * @return string
1679
 */
1680
function geItemReadablePath(int $id_tree, string $label, array $SETTINGS): string
1681
{
1682
    $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1683
    $arbo = $tree->getPath($id_tree, true);
1684
    $path = '';
1685
    foreach ($arbo as $elem) {
1686
        if (empty($path) === true) {
1687
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
1688
        } else {
1689
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
1690
        }
1691
    }
1692
1693
    // Build text to show user
1694
    if (empty($label) === false) {
1695
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
1696
    }
1697
    return empty($path) === true ? '' : $path;
1698
}
1699
1700
/**
1701
 * Get the client ip address.
1702
 *
1703
 * @return string IP address
1704
 */
1705
function getClientIpServer(): string
1706
{
1707
    if (getenv('HTTP_CLIENT_IP')) {
1708
        $ipaddress = getenv('HTTP_CLIENT_IP');
1709
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
1710
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
1711
    } elseif (getenv('HTTP_X_FORWARDED')) {
1712
        $ipaddress = getenv('HTTP_X_FORWARDED');
1713
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
1714
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
1715
    } elseif (getenv('HTTP_FORWARDED')) {
1716
        $ipaddress = getenv('HTTP_FORWARDED');
1717
    } elseif (getenv('REMOTE_ADDR')) {
1718
        $ipaddress = getenv('REMOTE_ADDR');
1719
    } else {
1720
        $ipaddress = 'UNKNOWN';
1721
    }
1722
1723
    return $ipaddress;
1724
}
1725
1726
/**
1727
 * Escape all HTML, JavaScript, and CSS.
1728
 *
1729
 * @param string $input    The input string
1730
 * @param string $encoding Which character encoding are we using?
1731
 * 
1732
 * @return string
1733
 */
1734
function noHTML(string $input, string $encoding = 'UTF-8'): string
1735
{
1736
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
1737
}
1738
1739
/**
1740
 * Rebuilds the Teampass config file.
1741
 *
1742
 * @param string $configFilePath Path to the config file.
1743
 * @param array  $settings       Teampass settings.
1744
 *
1745
 * @return string|bool
1746
 */
1747
function rebuildConfigFile(string $configFilePath, array $settings)
1748
{
1749
    // Perform a copy if the file exists
1750
    if (file_exists($configFilePath)) {
1751
        $backupFilePath = $configFilePath . '.' . date('Y_m_d_His', time());
1752
        if (!copy($configFilePath, $backupFilePath)) {
1753
            return "ERROR: Could not copy file '$configFilePath'";
1754
        }
1755
    }
1756
1757
    // Regenerate the config file
1758
    $data = ["<?php\n", "global \$SETTINGS;\n", "\$SETTINGS = array (\n"];
1759
    $rows = DB::query('SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s', 'admin');
1760
    foreach ($rows as $record) {
1761
        $value = getEncryptedValue($record['valeur'], $record['is_encrypted']);
1762
        $data[] = "    '{$record['intitule']}' => '". htmlspecialchars_decode($value, ENT_COMPAT) . "',\n";
1763
    }
1764
    $data[] = ");\n";
1765
    $data = array_unique($data);
1766
1767
    // Update the file
1768
    file_put_contents($configFilePath, implode('', $data));
1769
1770
    return true;
1771
}
1772
1773
/**
1774
 * Returns the encrypted value if needed.
1775
 *
1776
 * @param string $value       Value to encrypt.
1777
 * @param int   $isEncrypted Is the value encrypted?
1778
 *
1779
 * @return string
1780
 */
1781
function getEncryptedValue(string $value, int $isEncrypted): string
1782
{
1783
    return $isEncrypted ? cryption($value, '', 'encrypt')['string'] : $value;
1784
}
1785
1786
/**
1787
 * Permits to replace &#92; to permit correct display
1788
 *
1789
 * @param string $input Some text
1790
 * 
1791
 * @return string
1792
 */
1793
function handleBackslash(string $input): string
1794
{
1795
    return str_replace('&amp;#92;', '&#92;', $input);
1796
}
1797
1798
/**
1799
 * Permits to load settings
1800
 * 
1801
 * @return void
1802
*/
1803
function loadSettings(): void
1804
{
1805
    global $SETTINGS;
1806
    /* LOAD CPASSMAN SETTINGS */
1807
    if (! isset($SETTINGS['loaded']) || $SETTINGS['loaded'] !== 1) {
1808
        $SETTINGS = [];
1809
        $SETTINGS['duplicate_folder'] = 0;
1810
        //by default, this is set to 0;
1811
        $SETTINGS['duplicate_item'] = 0;
1812
        //by default, this is set to 0;
1813
        $SETTINGS['number_of_used_pw'] = 5;
1814
        //by default, this value is set to 5;
1815
        $settings = [];
1816
        $rows = DB::query(
1817
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
1818
            [
1819
                'type' => 'admin',
1820
                'type2' => 'settings',
1821
            ]
1822
        );
1823
        foreach ($rows as $record) {
1824
            if ($record['type'] === 'admin') {
1825
                $SETTINGS[$record['intitule']] = $record['valeur'];
1826
            } else {
1827
                $settings[$record['intitule']] = $record['valeur'];
1828
            }
1829
        }
1830
        $SETTINGS['loaded'] = 1;
1831
        $SETTINGS['default_session_expiration_time'] = 5;
1832
    }
1833
}
1834
1835
/**
1836
 * check if folder has custom fields.
1837
 * Ensure that target one also has same custom fields
1838
 * 
1839
 * @param int $source_id
1840
 * @param int $target_id 
1841
 * 
1842
 * @return bool
1843
*/
1844
function checkCFconsistency(int $source_id, int $target_id): bool
1845
{
1846
    $source_cf = [];
1847
    $rows = DB::QUERY(
1848
        'SELECT id_category
1849
            FROM ' . prefixTable('categories_folders') . '
1850
            WHERE id_folder = %i',
1851
        $source_id
1852
    );
1853
    foreach ($rows as $record) {
1854
        array_push($source_cf, $record['id_category']);
1855
    }
1856
1857
    $target_cf = [];
1858
    $rows = DB::QUERY(
1859
        'SELECT id_category
1860
            FROM ' . prefixTable('categories_folders') . '
1861
            WHERE id_folder = %i',
1862
        $target_id
1863
    );
1864
    foreach ($rows as $record) {
1865
        array_push($target_cf, $record['id_category']);
1866
    }
1867
1868
    $cf_diff = array_diff($source_cf, $target_cf);
1869
    if (count($cf_diff) > 0) {
1870
        return false;
1871
    }
1872
1873
    return true;
1874
}
1875
1876
/**
1877
 * Will encrypte/decrypt a fil eusing Defuse.
1878
 *
1879
 * @param string $type        can be either encrypt or decrypt
1880
 * @param string $source_file path to source file
1881
 * @param string $target_file path to target file
1882
 * @param array  $SETTINGS    Settings
1883
 * @param string $password    A password
1884
 *
1885
 * @return string|bool
1886
 */
1887
function prepareFileWithDefuse(
1888
    string $type,
1889
    string $source_file,
1890
    string $target_file,
1891
    array $SETTINGS,
1892
    string $password = null
1893
) {
1894
    // Load AntiXSS
1895
    $antiXss = new AntiXSS();
1896
    // Protect against bad inputs
1897
    if (is_array($source_file) === true || is_array($target_file) === true) {
1898
        return 'error_cannot_be_array';
1899
    }
1900
1901
    // Sanitize
1902
    $source_file = $antiXss->xss_clean($source_file);
1903
    $target_file = $antiXss->xss_clean($target_file);
1904
    if (empty($password) === true || is_null($password) === true) {
1905
        // get KEY to define password
1906
        $ascii_key = file_get_contents(SECUREPATH.'/'.SECUREFILE);
1907
        $password = Key::loadFromAsciiSafeString($ascii_key);
1908
    }
1909
1910
    $err = '';
1911
    if ($type === 'decrypt') {
1912
        // Decrypt file
1913
        $err = defuseFileDecrypt(
1914
            $source_file,
1915
            $target_file,
1916
            $SETTINGS, /** @scrutinizer ignore-type */
1917
            $password
1918
        );
1919
    } elseif ($type === 'encrypt') {
1920
        // Encrypt file
1921
        $err = defuseFileEncrypt(
1922
            $source_file,
1923
            $target_file,
1924
            $SETTINGS, /** @scrutinizer ignore-type */
1925
            $password
1926
        );
1927
    }
1928
1929
    // return error
1930
    return $err === true ? $err : '';
1931
}
1932
1933
/**
1934
 * Encrypt a file with Defuse.
1935
 *
1936
 * @param string $source_file path to source file
1937
 * @param string $target_file path to target file
1938
 * @param array  $SETTINGS    Settings
1939
 * @param string $password    A password
1940
 *
1941
 * @return string|bool
1942
 */
1943
function defuseFileEncrypt(
1944
    string $source_file,
1945
    string $target_file,
1946
    array $SETTINGS,
1947
    string $password = null
1948
) {
1949
    try {
1950
        CryptoFile::encryptFileWithPassword(
1951
            $source_file,
1952
            $target_file,
1953
            $password
1954
        );
1955
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
1956
        $err = 'wrong_key';
1957
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
1958
        $err = $ex;
1959
    } catch (CryptoException\IOException $ex) {
1960
        $err = $ex;
1961
    }
1962
1963
    // return error
1964
    return empty($err) === false ? $err : true;
1965
}
1966
1967
/**
1968
 * Decrypt a file with Defuse.
1969
 *
1970
 * @param string $source_file path to source file
1971
 * @param string $target_file path to target file
1972
 * @param array  $SETTINGS    Settings
1973
 * @param string $password    A password
1974
 *
1975
 * @return string|bool
1976
 */
1977
function defuseFileDecrypt(
1978
    string $source_file,
1979
    string $target_file,
1980
    array $SETTINGS,
1981
    string $password = null
1982
) {
1983
    try {
1984
        CryptoFile::decryptFileWithPassword(
1985
            $source_file,
1986
            $target_file,
1987
            $password
1988
        );
1989
    } catch (CryptoException\WrongKeyOrModifiedCiphertextException $ex) {
1990
        $err = 'wrong_key';
1991
    } catch (CryptoException\EnvironmentIsBrokenException $ex) {
1992
        $err = $ex;
1993
    } catch (CryptoException\IOException $ex) {
1994
        $err = $ex;
1995
    }
1996
1997
    // return error
1998
    return empty($err) === false ? $err : true;
1999
}
2000
2001
/*
2002
* NOT TO BE USED
2003
*/
2004
/**
2005
 * Undocumented function.
2006
 *
2007
 * @param string $text Text to debug
2008
 */
2009
function debugTeampass(string $text): void
2010
{
2011
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
2012
    if ($debugFile !== false) {
2013
        fputs($debugFile, $text);
2014
        fclose($debugFile);
2015
    }
2016
}
2017
2018
/**
2019
 * DELETE the file with expected command depending on server type.
2020
 *
2021
 * @param string $file     Path to file
2022
 * @param array  $SETTINGS Teampass settings
2023
 *
2024
 * @return void
2025
 */
2026
function fileDelete(string $file, array $SETTINGS): void
2027
{
2028
    // Load AntiXSS
2029
    $antiXss = new AntiXSS();
2030
    $file = $antiXss->xss_clean($file);
2031
    if (is_file($file)) {
2032
        unlink($file);
2033
    }
2034
}
2035
2036
/**
2037
 * Permits to extract the file extension.
2038
 *
2039
 * @param string $file File name
2040
 *
2041
 * @return string
2042
 */
2043
function getFileExtension(string $file): string
2044
{
2045
    if (strpos($file, '.') === false) {
2046
        return $file;
2047
    }
2048
2049
    return substr($file, strrpos($file, '.') + 1);
2050
}
2051
2052
/**
2053
 * Chmods files and folders with different permissions.
2054
 *
2055
 * This is an all-PHP alternative to using: \n
2056
 * <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2057
 * <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2058
 *
2059
 * @author Jeppe Toustrup (tenzer at tenzer dot dk)
2060
  *
2061
 * @param string $path      An either relative or absolute path to a file or directory which should be processed.
2062
 * @param int    $filePerm The permissions any found files should get.
2063
 * @param int    $dirPerm  The permissions any found folder should get.
2064
 *
2065
 * @return bool Returns TRUE if the path if found and FALSE if not.
2066
 *
2067
 * @warning The permission levels has to be entered in octal format, which
2068
 * normally means adding a zero ("0") in front of the permission level. \n
2069
 * More info at: http://php.net/chmod.
2070
*/
2071
2072
function recursiveChmod(
2073
    string $path,
2074
    int $filePerm = 0644,
2075
    int  $dirPerm = 0755
2076
) {
2077
    // Check if the path exists
2078
    $path = basename($path);
2079
    if (! file_exists($path)) {
2080
        return false;
2081
    }
2082
2083
    // See whether this is a file
2084
    if (is_file($path)) {
2085
        // Chmod the file with our given filepermissions
2086
        try {
2087
            chmod($path, $filePerm);
2088
        } catch (Exception $e) {
2089
            return false;
2090
        }
2091
    // If this is a directory...
2092
    } elseif (is_dir($path)) {
2093
        // Then get an array of the contents
2094
        $foldersAndFiles = scandir($path);
2095
        // Remove "." and ".." from the list
2096
        $entries = array_slice($foldersAndFiles, 2);
2097
        // Parse every result...
2098
        foreach ($entries as $entry) {
2099
            // And call this function again recursively, with the same permissions
2100
            recursiveChmod($path.'/'.$entry, $filePerm, $dirPerm);
2101
        }
2102
2103
        // When we are done with the contents of the directory, we chmod the directory itself
2104
        try {
2105
            chmod($path, $filePerm);
2106
        } catch (Exception $e) {
2107
            return false;
2108
        }
2109
    }
2110
2111
    // Everything seemed to work out well, return true
2112
    return true;
2113
}
2114
2115
/**
2116
 * Check if user can access to this item.
2117
 *
2118
 * @param int   $item_id ID of item
2119
 * @param array $SETTINGS
2120
 *
2121
 * @return bool|string
2122
 */
2123
function accessToItemIsGranted(int $item_id, array $SETTINGS)
2124
{
2125
    
2126
    $session = SessionManager::getSession();
2127
    $session_groupes_visibles = $session->get('user-accessible_folders');
2128
    $session_list_restricted_folders_for_items = $session->get('system-list_restricted_folders_for_items');
2129
    // Load item data
2130
    $data = DB::queryFirstRow(
2131
        'SELECT id_tree
2132
        FROM ' . prefixTable('items') . '
2133
        WHERE id = %i',
2134
        $item_id
2135
    );
2136
    // Check if user can access this folder
2137
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2138
        // Now check if this folder is restricted to user
2139
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2140
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2141
        ) {
2142
            return 'ERR_FOLDER_NOT_ALLOWED';
2143
        }
2144
    }
2145
2146
    return true;
2147
}
2148
2149
/**
2150
 * Creates a unique key.
2151
 *
2152
 * @param int $lenght Key lenght
2153
 *
2154
 * @return string
2155
 */
2156
function uniqidReal(int $lenght = 13): string
2157
{
2158
    if (function_exists('random_bytes')) {
2159
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2160
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2161
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2162
    } else {
2163
        throw new Exception('no cryptographically secure random function available');
2164
    }
2165
2166
    return substr(bin2hex($bytes), 0, $lenght);
2167
}
2168
2169
/**
2170
 * Obfuscate an email.
2171
 *
2172
 * @param string $email Email address
2173
 *
2174
 * @return string
2175
 */
2176
function obfuscateEmail(string $email): string
2177
{
2178
    $email = explode("@", $email);
2179
    $name = $email[0];
2180
    if (strlen($name) > 3) {
2181
        $name = substr($name, 0, 2);
2182
        for ($i = 0; $i < strlen($email[0]) - 3; $i++) {
2183
            $name .= "*";
2184
        }
2185
        $name .= substr($email[0], -1, 1);
2186
    }
2187
    $host = explode(".", $email[1])[0];
2188
    if (strlen($host) > 3) {
2189
        $host = substr($host, 0, 1);
2190
        for ($i = 0; $i < strlen(explode(".", $email[1])[0]) - 2; $i++) {
2191
            $host .= "*";
2192
        }
2193
        $host .= substr(explode(".", $email[1])[0], -1, 1);
2194
    }
2195
    $email = $name . "@" . $host . "." . explode(".", $email[1])[1];
2196
    return $email;
2197
}
2198
2199
/**
2200
 * Perform a Query.
2201
 *
2202
 * @param array  $SETTINGS Teamapss settings
2203
 * @param string $fields   Fields to use
2204
 * @param string $table    Table to use
2205
 *
2206
 * @return array
2207
 */
2208
function performDBQuery(array $SETTINGS, string $fields, string $table): array
2209
{
2210
    // include librairies & connect to DB
2211
    //include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2212
2213
    // Load class DB
2214
    loadClasses('DB');
2215
    
2216
    // Insert log in DB
2217
    return DB::query(
2218
        'SELECT ' . $fields . '
2219
        FROM ' . prefixTable($table)
2220
    );
2221
}
2222
2223
/**
2224
 * Undocumented function.
2225
 *
2226
 * @param int $bytes Size of file
2227
 *
2228
 * @return string
2229
 */
2230
function formatSizeUnits(int $bytes): string
2231
{
2232
    if ($bytes >= 1073741824) {
2233
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2234
    } elseif ($bytes >= 1048576) {
2235
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2236
    } elseif ($bytes >= 1024) {
2237
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2238
    } elseif ($bytes > 1) {
2239
        $bytes .= ' bytes';
2240
    } elseif ($bytes === 1) {
2241
        $bytes .= ' byte';
2242
    } else {
2243
        $bytes = '0 bytes';
2244
    }
2245
2246
    return $bytes;
2247
}
2248
2249
/**
2250
 * Generate user pair of keys.
2251
 *
2252
 * @param string $userPwd User password
2253
 *
2254
 * @return array
2255
 */
2256
function generateUserKeys(string $userPwd): array
2257
{
2258
    // Sanitize
2259
    $antiXss = new AntiXSS();
2260
    $userPwd = $antiXss->xss_clean($userPwd);
2261
    // Load classes
2262
    $rsa = new Crypt_RSA();
2263
    $cipher = new Crypt_AES();
2264
    // Create the private and public key
2265
    $res = $rsa->createKey(4096);
2266
    // Encrypt the privatekey
2267
    $cipher->setPassword($userPwd);
2268
    $privatekey = $cipher->encrypt($res['privatekey']);
2269
    return [
2270
        'private_key' => base64_encode($privatekey),
2271
        'public_key' => base64_encode($res['publickey']),
2272
        'private_key_clear' => base64_encode($res['privatekey']),
2273
    ];
2274
}
2275
2276
/**
2277
 * Permits to decrypt the user's privatekey.
2278
 *
2279
 * @param string $userPwd        User password
2280
 * @param string $userPrivateKey User private key
2281
 *
2282
 * @return string|object
2283
 */
2284
function decryptPrivateKey(string $userPwd, string $userPrivateKey)
2285
{
2286
    // Sanitize
2287
    $antiXss = new AntiXSS();
2288
    $userPwd = $antiXss->xss_clean($userPwd);
2289
    $userPrivateKey = $antiXss->xss_clean($userPrivateKey);
2290
2291
    if (empty($userPwd) === false) {
2292
        // Load classes
2293
        $cipher = new Crypt_AES();
2294
        // Encrypt the privatekey
2295
        $cipher->setPassword($userPwd);
2296
        try {
2297
            return base64_encode((string) $cipher->decrypt(base64_decode($userPrivateKey)));
2298
        } catch (Exception $e) {
2299
            return $e;
2300
        }
2301
    }
2302
    return '';
2303
}
2304
2305
/**
2306
 * Permits to encrypt the user's privatekey.
2307
 *
2308
 * @param string $userPwd        User password
2309
 * @param string $userPrivateKey User private key
2310
 *
2311
 * @return string
2312
 */
2313
function encryptPrivateKey(string $userPwd, string $userPrivateKey): string
2314
{
2315
    // Sanitize
2316
    $antiXss = new AntiXSS();
2317
    $userPwd = $antiXss->xss_clean($userPwd);
2318
    $userPrivateKey = $antiXss->xss_clean($userPrivateKey);
2319
2320
    if (empty($userPwd) === false) {
2321
        // Load classes
2322
        $cipher = new Crypt_AES();
2323
        // Encrypt the privatekey
2324
        $cipher->setPassword($userPwd);        
2325
        try {
2326
            return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2327
        } catch (Exception $e) {
2328
            return $e;
2329
        }
2330
    }
2331
    return '';
2332
}
2333
2334
/**
2335
 * Encrypts a string using AES.
2336
 *
2337
 * @param string $data String to encrypt
2338
 * @param string $key
2339
 *
2340
 * @return array
2341
 */
2342
function doDataEncryption(string $data, string $key = NULL): array
2343
{
2344
    // Sanitize
2345
    $antiXss = new AntiXSS();
2346
    $data = $antiXss->xss_clean($data);
2347
    
2348
    // Load classes
2349
    $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2350
    // Generate an object key
2351
    $objectKey = is_null($key) === true ? uniqidReal(KEY_LENGTH) : $antiXss->xss_clean($key);
2352
    // Set it as password
2353
    $cipher->setPassword($objectKey);
2354
    return [
2355
        'encrypted' => base64_encode($cipher->encrypt($data)),
2356
        'objectKey' => base64_encode($objectKey),
2357
    ];
2358
}
2359
2360
/**
2361
 * Decrypts a string using AES.
2362
 *
2363
 * @param string $data Encrypted data
2364
 * @param string $key  Key to uncrypt
2365
 *
2366
 * @return string
2367
 */
2368
function doDataDecryption(string $data, string $key): string
2369
{
2370
    // Sanitize
2371
    $antiXss = new AntiXSS();
2372
    $data = $antiXss->xss_clean($data);
2373
    $key = $antiXss->xss_clean($key);
2374
2375
    // Load classes
2376
    $cipher = new Crypt_AES();
2377
    // Set the object key
2378
    $cipher->setPassword(base64_decode($key));
2379
    return base64_encode((string) $cipher->decrypt(base64_decode($data)));
2380
}
2381
2382
/**
2383
 * Encrypts using RSA a string using a public key.
2384
 *
2385
 * @param string $key       Key to be encrypted
2386
 * @param string $publicKey User public key
2387
 *
2388
 * @return string
2389
 */
2390
function encryptUserObjectKey(string $key, string $publicKey): string
2391
{
2392
    // Sanitize
2393
    $antiXss = new AntiXSS();
2394
    $publicKey = $antiXss->xss_clean($publicKey);
2395
    // Load classes
2396
    $rsa = new Crypt_RSA();
2397
    // Load the public key
2398
    $decodedPublicKey = base64_decode($publicKey, true);
2399
    if ($decodedPublicKey === false) {
2400
        throw new InvalidArgumentException("Error while decoding key.");
2401
    }
2402
    $rsa->loadKey($decodedPublicKey);
2403
    // Encrypt
2404
    $encrypted = $rsa->encrypt(base64_decode($key));
2405
    if ($encrypted === false) {
0 ignored issues
show
introduced by
The condition $encrypted === false is always false.
Loading history...
2406
        throw new RuntimeException("Error while encrypting key.");
2407
    }
2408
    // Return
2409
    return base64_encode($encrypted);
2410
}
2411
2412
/**
2413
 * Decrypts using RSA an encrypted string using a private key.
2414
 *
2415
 * @param string $key        Encrypted key
2416
 * @param string $privateKey User private key
2417
 *
2418
 * @return string
2419
 */
2420
function decryptUserObjectKey(string $key, string $privateKey): string
2421
{
2422
    // Sanitize
2423
    $antiXss = new AntiXSS();
2424
    $privateKey = $antiXss->xss_clean($privateKey);
2425
2426
    // Load classes
2427
    $rsa = new Crypt_RSA();
2428
    // Load the private key
2429
    $decodedPrivateKey = base64_decode($privateKey, true);
2430
    if ($decodedPrivateKey === false) {
2431
        throw new InvalidArgumentException("Error while decoding private key.");
2432
    }
2433
2434
    $rsa->loadKey($decodedPrivateKey);
2435
2436
    // Decrypt
2437
    try {
2438
        $decodedKey = base64_decode($key, true);
2439
        if ($decodedKey === false) {
2440
            throw new InvalidArgumentException("Error while decoding key.");
2441
        }
2442
2443
        $tmpValue = $rsa->decrypt($decodedKey);
2444
        if ($tmpValue !== false) {
0 ignored issues
show
introduced by
The condition $tmpValue !== false is always true.
Loading history...
2445
            return base64_encode($tmpValue);
2446
        } else {
2447
            return '';
2448
        }
2449
    } catch (Exception $e) {
2450
        error_log('TEAMPASS Error - ldap - '.$e->getMessage());
2451
        return 'Exception: could not decrypt object';
2452
    }
2453
}
2454
2455
/**
2456
 * Encrypts a file.
2457
 *
2458
 * @param string $fileInName File name
2459
 * @param string $fileInPath Path to file
2460
 *
2461
 * @return array
2462
 */
2463
function encryptFile(string $fileInName, string $fileInPath): array
2464
{
2465
    if (defined('FILE_BUFFER_SIZE') === false) {
2466
        define('FILE_BUFFER_SIZE', 128 * 1024);
2467
    }
2468
2469
    // Load classes
2470
    $cipher = new Crypt_AES();
2471
2472
    // Generate an object key
2473
    $objectKey = uniqidReal(32);
2474
    // Set it as password
2475
    $cipher->setPassword($objectKey);
2476
    // Prevent against out of memory
2477
    $cipher->enableContinuousBuffer();
2478
2479
    // Encrypt the file content
2480
    $filePath = filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL);
2481
    $fileContent = file_get_contents($filePath);
2482
    $plaintext = $fileContent;
2483
    $ciphertext = $cipher->encrypt($plaintext);
2484
2485
    // Save new file
2486
    // deepcode ignore InsecureHash: is simply used to get a unique name
2487
    $hash = md5($plaintext);
2488
    $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2489
    file_put_contents($fileOut, $ciphertext);
2490
    unlink($fileInPath . '/' . $fileInName);
2491
    return [
2492
        'fileHash' => base64_encode($hash),
2493
        'objectKey' => base64_encode($objectKey),
2494
    ];
2495
}
2496
2497
/**
2498
 * Decrypt a file.
2499
 *
2500
 * @param string $fileName File name
2501
 * @param string $filePath Path to file
2502
 * @param string $key      Key to use
2503
 *
2504
 * @return string
2505
 */
2506
function decryptFile(string $fileName, string $filePath, string $key): string
2507
{
2508
    if (! defined('FILE_BUFFER_SIZE')) {
2509
        define('FILE_BUFFER_SIZE', 128 * 1024);
2510
    }
2511
    
2512
    // Load classes
2513
    $cipher = new Crypt_AES();
2514
    $antiXSS = new AntiXSS();
2515
    
2516
    // Get file name
2517
    $safeFileName = $antiXSS->xss_clean(base64_decode($fileName));
2518
2519
    // Set the object key
2520
    $cipher->setPassword(base64_decode($key));
2521
    // Prevent against out of memory
2522
    $cipher->enableContinuousBuffer();
2523
    $cipher->disablePadding();
2524
    // Get file content
2525
    $safeFilePath = realpath($filePath . '/' . TP_FILE_PREFIX . $safeFileName);
2526
    $ciphertext = file_get_contents(filter_var($safeFilePath, FILTER_SANITIZE_URL));
2527
2528
    if (WIP) error_log('DEBUG: File image url -> '.filter_var($safeFilePath, FILTER_SANITIZE_URL));
2529
2530
    // Decrypt file content and return
2531
    return base64_encode($cipher->decrypt($ciphertext));
2532
}
2533
2534
/**
2535
 * Generate a simple password
2536
 *
2537
 * @param int $length Length of string
2538
 * @param bool $symbolsincluded Allow symbols
2539
 *
2540
 * @return string
2541
 */
2542
function generateQuickPassword(int $length = 16, bool $symbolsincluded = true): string
2543
{
2544
    // Generate new user password
2545
    $small_letters = range('a', 'z');
2546
    $big_letters = range('A', 'Z');
2547
    $digits = range(0, 9);
2548
    $symbols = $symbolsincluded === true ?
2549
        ['#', '_', '-', '@', '$', '+', '!'] : [];
2550
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2551
    $count = count($res);
2552
    // first variant
2553
2554
    $random_string = '';
2555
    for ($i = 0; $i < $length; ++$i) {
2556
        $random_string .= $res[random_int(0, $count - 1)];
2557
    }
2558
2559
    return $random_string;
2560
}
2561
2562
/**
2563
 * Permit to store the sharekey of an object for users.
2564
 *
2565
 * @param string $object_name             Type for table selection
2566
 * @param int    $post_folder_is_personal Personal
2567
 * @param int    $post_folder_id          Folder
2568
 * @param int    $post_object_id          Object
2569
 * @param string $objectKey               Object key
2570
 * @param array  $SETTINGS                Teampass settings
2571
 * @param int    $user_id                 User ID if needed
2572
 * @param bool   $onlyForUser                 User ID if needed
2573
 * @param bool   $deleteAll                 User ID if needed
2574
 * @param array  $objectKeyArray                 User ID if needed
2575
 * @param int    $all_users_except_id                 User ID if needed
2576
 * @param int   $apiUserId                 User ID if needed
2577
 *
2578
 * @return void
2579
 */
2580
function storeUsersShareKey(
2581
    string $object_name,
2582
    int $post_folder_is_personal,
2583
    int $post_folder_id,
2584
    int $post_object_id,
2585
    string $objectKey,
2586
    bool $onlyForUser = false,
2587
    bool $deleteAll = true,
2588
    array $objectKeyArray = [],
2589
    int $all_users_except_id = -1,
2590
    int $apiUserId = -1
2591
): void {
2592
    
2593
    $session = SessionManager::getSession();
2594
    loadClasses('DB');
2595
2596
    // Delete existing entries for this object
2597
    if ($deleteAll === true) {
2598
        DB::delete(
2599
            $object_name,
2600
            'object_id = %i',
2601
            $post_object_id
2602
        );
2603
    }
2604
    
2605
    if (
2606
        $onlyForUser === true || (int) $post_folder_is_personal === 1
2607
    ) {
2608
        // Only create the sharekey for a user
2609
        $user = DB::queryFirstRow(
2610
            'SELECT public_key
2611
            FROM ' . prefixTable('users') . '
2612
            WHERE id = ' . ($apiUserId === -1 ? (int) $session->get('user-id') : $apiUserId) . '
2613
            AND public_key != ""'
2614
        );
2615
2616
        if (empty($objectKey) === false) {
2617
            DB::insert(
2618
                $object_name,
2619
                [
2620
                    'object_id' => (int) $post_object_id,
2621
                    'user_id' => (int) ($apiUserId === -1 ? (int) $session->get('user-id') : $apiUserId),
2622
                    'share_key' => encryptUserObjectKey(
2623
                        $objectKey,
2624
                        $user['public_key']
2625
                    ),
2626
                ]
2627
            );
2628
        } else if (count($objectKeyArray) > 0) {
2629
            foreach ($objectKeyArray as $object) {
2630
                DB::insert(
2631
                    $object_name,
2632
                    [
2633
                        'object_id' => (int) $object['objectId'],
2634
                        'user_id' => (int) ($apiUserId === -1 ? (int) $session->get('user-id') : $apiUserId),
2635
                        'share_key' => encryptUserObjectKey(
2636
                            $object['objectKey'],
2637
                            $user['public_key']
2638
                        ),
2639
                    ]
2640
                );
2641
            }
2642
        }
2643
    } else {
2644
        // Create sharekey for each user
2645
        //error_log('Building QUERY - all_users_except_id: '. $all_users_except_id);
2646
        //DB::debugmode(true);
2647
        $users = DB::query(
2648
            'SELECT id, public_key
2649
            FROM ' . prefixTable('users') . '
2650
            WHERE ' . ($onlyForUser === true ? 
0 ignored issues
show
introduced by
The condition $onlyForUser === true is always false.
Loading history...
2651
                'id IN ("' . TP_USER_ID . '","' . ($apiUserId === -1 ? (int) $session->get('user-id') : $apiUserId) . '") ' : 
2652
                'id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '"'.($all_users_except_id === -1 ? '' : ', "'.$all_users_except_id.'"').') ') . '
2653
            AND public_key != ""'
2654
        );
2655
        //DB::debugmode(false);
2656
        foreach ($users as $user) {
2657
            // Insert in DB the new object key for this item by user
2658
            if (count($objectKeyArray) === 0) {
2659
                if (WIP === true) error_log('TEAMPASS Debug - storeUsersShareKey case1 - ' . $object_name . ' - ' . $post_object_id . ' - ' . $user['id'] . ' - ' . $objectKey);
2660
                DB::insert(
2661
                    $object_name,
2662
                    [
2663
                        'object_id' => $post_object_id,
2664
                        'user_id' => (int) $user['id'],
2665
                        'share_key' => encryptUserObjectKey(
2666
                            $objectKey,
2667
                            $user['public_key']
2668
                        ),
2669
                    ]
2670
                );
2671
            } else {
2672
                foreach ($objectKeyArray as $object) {
2673
                    if (WIP === true) error_log('TEAMPASS Debug - storeUsersShareKey case2 - ' . $object_name . ' - ' . $object['objectId'] . ' - ' . $user['id'] . ' - ' . $object['objectKey']);
2674
                    DB::insert(
2675
                        $object_name,
2676
                        [
2677
                            'object_id' => (int) $object['objectId'],
2678
                            'user_id' => (int) $user['id'],
2679
                            'share_key' => encryptUserObjectKey(
2680
                                $object['objectKey'],
2681
                                $user['public_key']
2682
                            ),
2683
                        ]
2684
                    );
2685
                }
2686
            }
2687
        }
2688
    }
2689
}
2690
2691
/**
2692
 * Is this string base64 encoded?
2693
 *
2694
 * @param string $str Encoded string?
2695
 *
2696
 * @return bool
2697
 */
2698
function isBase64(string $str): bool
2699
{
2700
    $str = (string) trim($str);
2701
    if (! isset($str[0])) {
2702
        return false;
2703
    }
2704
2705
    $base64String = (string) base64_decode($str, true);
2706
    if ($base64String && base64_encode($base64String) === $str) {
2707
        return true;
2708
    }
2709
2710
    return false;
2711
}
2712
2713
/**
2714
 * Undocumented function
2715
 *
2716
 * @param string $field Parameter
2717
 *
2718
 * @return array|bool|resource|string
2719
 */
2720
function filterString(string $field)
2721
{
2722
    // Sanitize string
2723
    $field = filter_var(trim($field), FILTER_SANITIZE_FULL_SPECIAL_CHARS);
2724
    if (empty($field) === false) {
2725
        // Load AntiXSS
2726
        $antiXss = new AntiXSS();
2727
        // Return
2728
        return $antiXss->xss_clean($field);
2729
    }
2730
2731
    return false;
2732
}
2733
2734
/**
2735
 * CHeck if provided credentials are allowed on server
2736
 *
2737
 * @param string $login    User Login
2738
 * @param string $password User Pwd
2739
 * @param array  $SETTINGS Teampass settings
2740
 *
2741
 * @return bool
2742
 */
2743
function ldapCheckUserPassword(string $login, string $password, array $SETTINGS): bool
2744
{
2745
    // Build ldap configuration array
2746
    $config = [
2747
        // Mandatory Configuration Options
2748
        'hosts' => [$SETTINGS['ldap_hosts']],
2749
        'base_dn' => $SETTINGS['ldap_bdn'],
2750
        'username' => $SETTINGS['ldap_username'],
2751
        'password' => $SETTINGS['ldap_password'],
2752
2753
        // Optional Configuration Options
2754
        'port' => $SETTINGS['ldap_port'],
2755
        'use_ssl' => (int) $SETTINGS['ldap_ssl'] === 1 ? true : false,
2756
        'use_tls' => (int) $SETTINGS['ldap_tls'] === 1 ? true : false,
2757
        'version' => 3,
2758
        'timeout' => 5,
2759
        'follow_referrals' => false,
2760
2761
        // Custom LDAP Options
2762
        'options' => [
2763
            // See: http://php.net/ldap_set_option
2764
            LDAP_OPT_X_TLS_REQUIRE_CERT => (isset($SETTINGS['ldap_tls_certiface_check']) ? $SETTINGS['ldap_tls_certiface_check'] : LDAP_OPT_X_TLS_HARD),
2765
        ],
2766
    ];
2767
    
2768
    $connection = new Connection($config);
2769
    // Connect to LDAP
2770
    try {
2771
        $connection->connect();
2772
    } catch (\LdapRecord\Auth\BindException $e) {
2773
        $error = $e->getDetailedError();
2774
        if ($error) {
2775
            error_log('TEAMPASS Error - LDAP - '.$error->getErrorCode()." - ".$error->getErrorMessage(). " - ".$error->getDiagnosticMessage());
2776
        } else {
2777
            error_log('TEAMPASS Error - LDAP - Code: '.$e->getCode().' - Message: '.$e->getMessage());
2778
        }
2779
        // deepcode ignore ServerLeak: No important data is sent
2780
        echo 'An error occurred.';
2781
        return false;
2782
    }
2783
2784
    // Authenticate user
2785
    try {
2786
        if ($SETTINGS['ldap_type'] === 'ActiveDirectory') {
2787
            $connection->auth()->attempt($login, $password, $stayAuthenticated = true);
2788
        } else {
2789
            $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);
2790
        }
2791
    } catch (\LdapRecord\Auth\BindException $e) {
2792
        $error = $e->getDetailedError();
2793
        if ($error) {
2794
            error_log('TEAMPASS Error - LDAP - '.$error->getErrorCode()." - ".$error->getErrorMessage(). " - ".$error->getDiagnosticMessage());
2795
        } else {
2796
            error_log('TEAMPASS Error - LDAP - Code: '.$e->getCode().' - Message: '.$e->getMessage());
2797
        }
2798
        // deepcode ignore ServerLeak: No important data is sent
2799
        echo 'An error occurred.';
2800
        return false;
2801
    }
2802
2803
    return true;
2804
}
2805
2806
/**
2807
 * Removes from DB all sharekeys of this user
2808
 *
2809
 * @param int $userId User's id
2810
 * @param array   $SETTINGS Teampass settings
2811
 *
2812
 * @return bool
2813
 */
2814
function deleteUserObjetsKeys(int $userId, array $SETTINGS = []): bool
2815
{
2816
    // Load class DB
2817
    loadClasses('DB');
2818
2819
    // Remove all item sharekeys items
2820
    // expect if personal item
2821
    DB::delete(
2822
        prefixTable('sharekeys_items'),
2823
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2824
        $userId
2825
    );
2826
    // Remove all item sharekeys files
2827
    DB::delete(
2828
        prefixTable('sharekeys_files'),
2829
        'user_id = %i AND object_id NOT IN (
2830
            SELECT f.id 
2831
            FROM ' . prefixTable('items') . ' AS i 
2832
            INNER JOIN ' . prefixTable('files') . ' AS f ON f.id_item = i.id
2833
            WHERE i.perso = 1
2834
        )',
2835
        $userId
2836
    );
2837
    // Remove all item sharekeys fields
2838
    DB::delete(
2839
        prefixTable('sharekeys_fields'),
2840
        'user_id = %i AND object_id NOT IN (
2841
            SELECT c.id 
2842
            FROM ' . prefixTable('items') . ' AS i 
2843
            INNER JOIN ' . prefixTable('categories_items') . ' AS c ON c.item_id = i.id
2844
            WHERE i.perso = 1
2845
        )',
2846
        $userId
2847
    );
2848
    // Remove all item sharekeys logs
2849
    DB::delete(
2850
        prefixTable('sharekeys_logs'),
2851
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2852
        $userId
2853
    );
2854
    // Remove all item sharekeys suggestions
2855
    DB::delete(
2856
        prefixTable('sharekeys_suggestions'),
2857
        'user_id = %i AND object_id NOT IN (SELECT i.id FROM ' . prefixTable('items') . ' AS i WHERE i.perso = 1)',
2858
        $userId
2859
    );
2860
    return false;
2861
}
2862
2863
/**
2864
 * Manage list of timezones   $SETTINGS Teampass settings
2865
 *
2866
 * @return array
2867
 */
2868
function timezone_list()
2869
{
2870
    static $timezones = null;
2871
    if ($timezones === null) {
2872
        $timezones = [];
2873
        $offsets = [];
2874
        $now = new DateTime('now', new DateTimeZone('UTC'));
2875
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
2876
            $now->setTimezone(new DateTimeZone($timezone));
2877
            $offsets[] = $offset = $now->getOffset();
2878
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
2879
        }
2880
2881
        array_multisort($offsets, $timezones);
2882
    }
2883
2884
    return $timezones;
2885
}
2886
2887
/**
2888
 * Provide timezone offset
2889
 *
2890
 * @param int $offset Timezone offset
2891
 *
2892
 * @return string
2893
 */
2894
function format_GMT_offset($offset): string
2895
{
2896
    $hours = intval($offset / 3600);
2897
    $minutes = abs(intval($offset % 3600 / 60));
2898
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
2899
}
2900
2901
/**
2902
 * Provides timezone name
2903
 *
2904
 * @param string $name Timezone name
2905
 *
2906
 * @return string
2907
 */
2908
function format_timezone_name($name): string
2909
{
2910
    $name = str_replace('/', ', ', $name);
2911
    $name = str_replace('_', ' ', $name);
2912
2913
    return str_replace('St ', 'St. ', $name);
2914
}
2915
2916
/**
2917
 * Provides info if user should use MFA based on roles
2918
 *
2919
 * @param string $userRolesIds  User roles ids
2920
 * @param string $mfaRoles      Roles for which MFA is requested
2921
 *
2922
 * @return bool
2923
 */
2924
function mfa_auth_requested_roles(string $userRolesIds, string $mfaRoles): bool
2925
{
2926
    if (empty($mfaRoles) === true) {
2927
        return true;
2928
    }
2929
2930
    $mfaRoles = array_values(json_decode($mfaRoles, true));
2931
    $userRolesIds = array_filter(explode(';', $userRolesIds));
2932
    if (count($mfaRoles) === 0 || count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
2933
        return true;
2934
    }
2935
2936
    return false;
2937
}
2938
2939
/**
2940
 * Permits to clean a string for export purpose
2941
 *
2942
 * @param string $text
2943
 * @param bool $emptyCheckOnly
2944
 * 
2945
 * @return string
2946
 */
2947
function cleanStringForExport(string $text, bool $emptyCheckOnly = false): string
2948
{
2949
    if (is_null($text) === true || empty($text) === true) {
2950
        return '';
2951
    }
2952
    // only expected to check if $text was empty
2953
    elseif ($emptyCheckOnly === true) {
2954
        return $text;
2955
    }
2956
2957
    return strip_tags(
2958
        cleanString(
2959
            html_entity_decode($text, ENT_QUOTES | ENT_XHTML, 'UTF-8'),
2960
            true)
2961
        );
2962
}
2963
2964
/**
2965
 * Permits to check if user ID is valid
2966
 *
2967
 * @param integer $post_user_id
2968
 * @return bool
2969
 */
2970
function isUserIdValid($userId): bool
2971
{
2972
    if (is_null($userId) === false
2973
        && isset($userId) === true
2974
        && empty($userId) === false
2975
    ) {
2976
        return true;
2977
    }
2978
    return false;
2979
}
2980
2981
/**
2982
 * Check if a key exists and if its value equal the one expected
2983
 *
2984
 * @param string $key
2985
 * @param integer|string $value
2986
 * @param array $array
2987
 * 
2988
 * @return boolean
2989
 */
2990
function isKeyExistingAndEqual(
2991
    string $key,
2992
    /*PHP8 - integer|string*/$value,
2993
    array $array
2994
): bool
2995
{
2996
    if (isset($array[$key]) === true
2997
        && (is_int($value) === true ?
2998
            (int) $array[$key] === $value :
2999
            (string) $array[$key] === $value)
3000
    ) {
3001
        return true;
3002
    }
3003
    return false;
3004
}
3005
3006
/**
3007
 * Check if a variable is not set or equal to a value
3008
 *
3009
 * @param string|null $var
3010
 * @param integer|string $value
3011
 * 
3012
 * @return boolean
3013
 */
3014
function isKeyNotSetOrEqual(
3015
    /*PHP8 - string|null*/$var,
3016
    /*PHP8 - integer|string*/$value
3017
): bool
3018
{
3019
    if (isset($var) === false
3020
        || (is_int($value) === true ?
3021
            (int) $var === $value :
3022
            (string) $var === $value)
3023
    ) {
3024
        return true;
3025
    }
3026
    return false;
3027
}
3028
3029
/**
3030
 * Check if a key exists and if its value < to the one expected
3031
 *
3032
 * @param string $key
3033
 * @param integer $value
3034
 * @param array $array
3035
 * 
3036
 * @return boolean
3037
 */
3038
function isKeyExistingAndInferior(string $key, int $value, array $array): bool
3039
{
3040
    if (isset($array[$key]) === true && (int) $array[$key] < $value) {
3041
        return true;
3042
    }
3043
    return false;
3044
}
3045
3046
/**
3047
 * Check if a key exists and if its value > to the one expected
3048
 *
3049
 * @param string $key
3050
 * @param integer $value
3051
 * @param array $array
3052
 * 
3053
 * @return boolean
3054
 */
3055
function isKeyExistingAndSuperior(string $key, int $value, array $array): bool
3056
{
3057
    if (isset($array[$key]) === true && (int) $array[$key] > $value) {
3058
        return true;
3059
    }
3060
    return false;
3061
}
3062
3063
/**
3064
 * Check if values in array are set
3065
 * Return true if all set
3066
 * Return false if one of them is not set
3067
 *
3068
 * @param array $arrayOfValues
3069
 * @return boolean
3070
 */
3071
function isSetArrayOfValues(array $arrayOfValues): bool
3072
{
3073
    foreach($arrayOfValues as $value) {
3074
        if (isset($value) === false) {
3075
            return false;
3076
        }
3077
    }
3078
    return true;
3079
}
3080
3081
/**
3082
 * Check if values in array are set
3083
 * Return true if all set
3084
 * Return false if one of them is not set
3085
 *
3086
 * @param array $arrayOfValues
3087
 * @param integer|string $value
3088
 * @return boolean
3089
 */
3090
function isArrayOfVarsEqualToValue(
3091
    array $arrayOfVars,
3092
    /*PHP8 - integer|string*/$value
3093
) : bool
3094
{
3095
    foreach($arrayOfVars as $variable) {
3096
        if ($variable !== $value) {
3097
            return false;
3098
        }
3099
    }
3100
    return true;
3101
}
3102
3103
/**
3104
 * Checks if at least one variable in array is equal to value
3105
 *
3106
 * @param array $arrayOfValues
3107
 * @param integer|string $value
3108
 * @return boolean
3109
 */
3110
function isOneVarOfArrayEqualToValue(
3111
    array $arrayOfVars,
3112
    /*PHP8 - integer|string*/$value
3113
) : bool
3114
{
3115
    foreach($arrayOfVars as $variable) {
3116
        if ($variable === $value) {
3117
            return true;
3118
        }
3119
    }
3120
    return false;
3121
}
3122
3123
/**
3124
 * Checks is value is null, not set OR empty
3125
 *
3126
 * @param string|int|null $value
3127
 * @return boolean
3128
 */
3129
function isValueSetNullEmpty(/*PHP8 - string|int|null*/ $value) : bool
3130
{
3131
    if (is_null($value) === true || isset($value) === false || empty($value) === true) {
3132
        return true;
3133
    }
3134
    return false;
3135
}
3136
3137
/**
3138
 * Checks if value is set and if empty is equal to passed boolean
3139
 *
3140
 * @param string|int $value
3141
 * @param boolean $boolean
3142
 * @return boolean
3143
 */
3144
function isValueSetEmpty($value, $boolean = true) : bool
3145
{
3146
    if (isset($value) === true && empty($value) === $boolean) {
3147
        return true;
3148
    }
3149
    return false;
3150
}
3151
3152
/**
3153
 * Ensure Complexity is translated
3154
 *
3155
 * @return void
3156
 */
3157
function defineComplexity() : void
3158
{
3159
    // Load user's language
3160
    $session = SessionManager::getSession();
3161
    $lang = new Language($session->get('user-language') ?? 'english');
3162
    
3163
    if (defined('TP_PW_COMPLEXITY') === false) {
3164
        define(
3165
            'TP_PW_COMPLEXITY',
3166
            [
3167
                TP_PW_STRENGTH_1 => array(TP_PW_STRENGTH_1, $lang->get('complex_level1'), 'fas fa-thermometer-empty text-danger'),
3168
                TP_PW_STRENGTH_2 => array(TP_PW_STRENGTH_2, $lang->get('complex_level2'), 'fas fa-thermometer-quarter text-warning'),
3169
                TP_PW_STRENGTH_3 => array(TP_PW_STRENGTH_3, $lang->get('complex_level3'), 'fas fa-thermometer-half text-warning'),
3170
                TP_PW_STRENGTH_4 => array(TP_PW_STRENGTH_4, $lang->get('complex_level4'), 'fas fa-thermometer-three-quarters text-success'),
3171
                TP_PW_STRENGTH_5 => array(TP_PW_STRENGTH_5, $lang->get('complex_level5'), 'fas fa-thermometer-full text-success'),
3172
            ]
3173
        );
3174
    }
3175
}
3176
3177
/**
3178
 * Uses Sanitizer to perform data sanitization
3179
 *
3180
 * @param array     $data
3181
 * @param array     $filters
3182
 * @return array|string
3183
 */
3184
function dataSanitizer(array $data, array $filters): array|string
3185
{
3186
    // Load Sanitizer library
3187
    $sanitizer = new Sanitizer($data, $filters);
3188
3189
    // Load AntiXSS
3190
    $antiXss = new AntiXSS();
3191
3192
    // Sanitize post and get variables
3193
    return $antiXss->xss_clean($sanitizer->sanitize());
3194
}
3195
3196
/**
3197
 * Permits to manage the cache tree for a user
3198
 *
3199
 * @param integer $user_id
3200
 * @param string $data
3201
 * @param array $SETTINGS
3202
 * @param string $field_update
3203
 * @return void
3204
 */
3205
function cacheTreeUserHandler(int $user_id, string $data, array $SETTINGS, string $field_update = '')
3206
{
3207
    // Load class DB
3208
    loadClasses('DB');
3209
3210
    // Exists ?
3211
    $userCacheId = DB::queryfirstrow(
3212
        'SELECT increment_id
3213
        FROM ' . prefixTable('cache_tree') . '
3214
        WHERE user_id = %i',
3215
        $user_id
3216
    );
3217
    
3218
    if (is_null($userCacheId) === true || count($userCacheId) === 0) {
3219
        // insert in table
3220
        DB::insert(
3221
            prefixTable('cache_tree'),
3222
            array(
3223
                'data' => $data,
3224
                'timestamp' => time(),
3225
                'user_id' => $user_id,
3226
                'visible_folders' => '',
3227
            )
3228
        );
3229
    } else {
3230
        if (empty($field_update) === true) {
3231
            DB::update(
3232
                prefixTable('cache_tree'),
3233
                [
3234
                    'timestamp' => time(),
3235
                    'data' => $data,
3236
                ],
3237
                'increment_id = %i',
3238
                $userCacheId['increment_id']
3239
            );
3240
        /* USELESS
3241
        } else {
3242
            DB::update(
3243
                prefixTable('cache_tree'),
3244
                [
3245
                    $field_update => $data,
3246
                ],
3247
                'increment_id = %i',
3248
                $userCacheId['increment_id']
3249
            );*/
3250
        }
3251
    }
3252
}
3253
3254
/**
3255
 * Permits to calculate a %
3256
 *
3257
 * @param float $nombre
3258
 * @param float $total
3259
 * @param float $pourcentage
3260
 * @return float
3261
 */
3262
function pourcentage(float $nombre, float $total, float $pourcentage): float
3263
{ 
3264
    $resultat = ($nombre/$total) * $pourcentage;
3265
    return round($resultat);
3266
}
3267
3268
/**
3269
 * Load the folders list from the cache
3270
 *
3271
 * @param string $fieldName
3272
 * @param string $sessionName
3273
 * @param boolean $forceRefresh
3274
 * @return array
3275
 */
3276
function loadFoldersListByCache(
3277
    string $fieldName,
3278
    string $sessionName,
3279
    bool $forceRefresh = false
3280
): array
3281
{
3282
    // Case when refresh is EXPECTED / MANDATORY
3283
    if ($forceRefresh === true) {
3284
        return [
3285
            'state' => false,
3286
            'data' => [],
3287
        ];
3288
    }
3289
    
3290
    $session = SessionManager::getSession();
3291
3292
    // Get last folder update
3293
    $lastFolderChange = DB::queryfirstrow(
3294
        'SELECT valeur FROM ' . prefixTable('misc') . '
3295
        WHERE type = %s AND intitule = %s',
3296
        'timestamp',
3297
        'last_folder_change'
3298
    );
3299
    if (DB::count() === 0) {
3300
        $lastFolderChange['valeur'] = 0;
3301
    }
3302
3303
    // Case when an update in the tree has been done
3304
    // Refresh is then mandatory
3305
    if ((int) $lastFolderChange['valeur'] > (int) (null !== $session->get('user-tree_last_refresh_timestamp') ? $session->get('user-tree_last_refresh_timestamp') : 0)) {
3306
        return [
3307
            'state' => false,
3308
            'data' => [],
3309
        ];
3310
    }
3311
3312
    // Does this user has the tree structure in session?
3313
    // If yes then use it
3314
    if (count(null !== $session->get('user-folders_list') ? $session->get('user-folders_list') : []) > 0) {
3315
        return [
3316
            'state' => true,
3317
            'data' => json_encode($session->get('user-folders_list')[0]),
3318
            'extra' => 'to_be_parsed',
3319
        ];
3320
    }
3321
    
3322
    // Does this user has a tree cache
3323
    $userCacheTree = DB::queryfirstrow(
3324
        'SELECT '.$fieldName.'
3325
        FROM ' . prefixTable('cache_tree') . '
3326
        WHERE user_id = %i',
3327
        $session->get('user-id')
3328
    );
3329
    if (empty($userCacheTree[$fieldName]) === false && $userCacheTree[$fieldName] !== '[]') {
3330
        SessionManager::addRemoveFromSessionAssociativeArray(
3331
            'user-folders_list',
3332
            [$userCacheTree[$fieldName]],
3333
            'add'
3334
        );
3335
        return [
3336
            'state' => true,
3337
            'data' => $userCacheTree[$fieldName],
3338
            'extra' => '',
3339
        ];
3340
    }
3341
3342
    return [
3343
        'state' => false,
3344
        'data' => [],
3345
    ];
3346
}
3347
3348
3349
/**
3350
 * Permits to refresh the categories of folders
3351
 *
3352
 * @param array $folderIds
3353
 * @return void
3354
 */
3355
function handleFoldersCategories(
3356
    array $folderIds
3357
)
3358
{
3359
    // Load class DB
3360
    loadClasses('DB');
3361
3362
    $arr_data = array();
3363
3364
    // force full list of folders
3365
    if (count($folderIds) === 0) {
3366
        $folderIds = DB::queryFirstColumn(
3367
            'SELECT id
3368
            FROM ' . prefixTable('nested_tree') . '
3369
            WHERE personal_folder=%i',
3370
            0
3371
        );
3372
    }
3373
3374
    // Get complexity
3375
    defineComplexity();
3376
3377
    // update
3378
    foreach ($folderIds as $folder) {
3379
        // Do we have Categories
3380
        // get list of associated Categories
3381
        $arrCatList = array();
3382
        $rows_tmp = DB::query(
3383
            'SELECT c.id, c.title, c.level, c.type, c.masked, c.order, c.encrypted_data, c.role_visibility, c.is_mandatory,
3384
            f.id_category AS category_id
3385
            FROM ' . prefixTable('categories_folders') . ' AS f
3386
            INNER JOIN ' . prefixTable('categories') . ' AS c ON (f.id_category = c.parent_id)
3387
            WHERE id_folder=%i',
3388
            $folder
3389
        );
3390
        if (DB::count() > 0) {
3391
            foreach ($rows_tmp as $row) {
3392
                $arrCatList[$row['id']] = array(
3393
                    'id' => $row['id'],
3394
                    'title' => $row['title'],
3395
                    'level' => $row['level'],
3396
                    'type' => $row['type'],
3397
                    'masked' => $row['masked'],
3398
                    'order' => $row['order'],
3399
                    'encrypted_data' => $row['encrypted_data'],
3400
                    'role_visibility' => $row['role_visibility'],
3401
                    'is_mandatory' => $row['is_mandatory'],
3402
                    'category_id' => $row['category_id'],
3403
                );
3404
            }
3405
        }
3406
        $arr_data['categories'] = $arrCatList;
3407
3408
        // Now get complexity
3409
        $valTemp = '';
3410
        $data = DB::queryFirstRow(
3411
            'SELECT valeur
3412
            FROM ' . prefixTable('misc') . '
3413
            WHERE type = %s AND intitule=%i',
3414
            'complex',
3415
            $folder
3416
        );
3417
        if (DB::count() > 0 && empty($data['valeur']) === false) {
3418
            $valTemp = array(
3419
                'value' => $data['valeur'],
3420
                'text' => TP_PW_COMPLEXITY[$data['valeur']][1],
3421
            );
3422
        }
3423
        $arr_data['complexity'] = $valTemp;
3424
3425
        // Now get Roles
3426
        $valTemp = '';
3427
        $rows_tmp = DB::query(
3428
            'SELECT t.title
3429
            FROM ' . prefixTable('roles_values') . ' as v
3430
            INNER JOIN ' . prefixTable('roles_title') . ' as t ON (v.role_id = t.id)
3431
            WHERE v.folder_id = %i
3432
            GROUP BY title',
3433
            $folder
3434
        );
3435
        foreach ($rows_tmp as $record) {
3436
            $valTemp .= (empty($valTemp) === true ? '' : ' - ') . $record['title'];
3437
        }
3438
        $arr_data['visibilityRoles'] = $valTemp;
3439
3440
        // now save in DB
3441
        DB::update(
3442
            prefixTable('nested_tree'),
3443
            array(
3444
                'categories' => json_encode($arr_data),
3445
            ),
3446
            'id = %i',
3447
            $folder
3448
        );
3449
    }
3450
}
3451
3452
/**
3453
 * List all users that have specific roles
3454
 *
3455
 * @param array $roles
3456
 * @return array
3457
 */
3458
function getUsersWithRoles(
3459
    array $roles
3460
): array
3461
{
3462
    $session = SessionManager::getSession();
3463
    $arrUsers = array();
3464
3465
    foreach ($roles as $role) {
3466
        // loop on users and check if user has this role
3467
        $rows = DB::query(
3468
            'SELECT id, fonction_id
3469
            FROM ' . prefixTable('users') . '
3470
            WHERE id != %i AND admin = 0 AND fonction_id IS NOT NULL AND fonction_id != ""',
3471
            $session->get('user-id')
3472
        );
3473
        foreach ($rows as $user) {
3474
            $userRoles = is_null($user['fonction_id']) === false && empty($user['fonction_id']) === false ? explode(';', $user['fonction_id']) : [];
3475
            if (in_array($role, $userRoles, true) === true) {
3476
                array_push($arrUsers, $user['id']);
3477
            }
3478
        }
3479
    }
3480
3481
    return $arrUsers;
3482
}
3483
3484
3485
/**
3486
 * Get all users informations
3487
 *
3488
 * @param integer $userId
3489
 * @return array
3490
 */
3491
function getFullUserInfos(
3492
    int $userId
3493
): array
3494
{
3495
    if (empty($userId) === true) {
3496
        return array();
3497
    }
3498
3499
    $val = DB::queryfirstrow(
3500
        'SELECT *
3501
        FROM ' . prefixTable('users') . '
3502
        WHERE id = %i',
3503
        $userId
3504
    );
3505
3506
    return $val;
3507
}
3508
3509
/**
3510
 * Is required an upgrade
3511
 *
3512
 * @return boolean
3513
 */
3514
function upgradeRequired(): bool
3515
{
3516
    // Get settings.php
3517
    include_once __DIR__. '/../includes/config/settings.php';
3518
3519
    // Get timestamp in DB
3520
    $val = DB::queryfirstrow(
3521
        'SELECT valeur
3522
        FROM ' . prefixTable('misc') . '
3523
        WHERE type = %s AND intitule = %s',
3524
        'admin',
3525
        'upgrade_timestamp'
3526
    );
3527
    
3528
    // if not exists then error
3529
    if (is_null($val) === true || count($val) === 0 || defined('UPGRADE_MIN_DATE') === false) return true;
3530
3531
    // if empty or too old then error
3532
    if (empty($val['valeur']) === true || (int) $val['valeur'] < (int) UPGRADE_MIN_DATE) {
3533
        return true;
3534
    }
3535
3536
    return false;
3537
}
3538
3539
/**
3540
 * Permits to change the user keys on his demand
3541
 *
3542
 * @param integer $userId
3543
 * @param string $passwordClear
3544
 * @param integer $nbItemsToTreat
3545
 * @param string $encryptionKey
3546
 * @param boolean $deleteExistingKeys
3547
 * @param boolean $sendEmailToUser
3548
 * @param boolean $encryptWithUserPassword
3549
 * @param boolean $generate_user_new_password
3550
 * @param string $emailBody
3551
 * @param boolean $user_self_change
3552
 * @param string $recovery_public_key
3553
 * @param string $recovery_private_key
3554
 * @return string
3555
 */
3556
function handleUserKeys(
3557
    int $userId,
3558
    string $passwordClear,
3559
    int $nbItemsToTreat,
3560
    string $encryptionKey = '',
3561
    bool $deleteExistingKeys = false,
3562
    bool $sendEmailToUser = true,
3563
    bool $encryptWithUserPassword = false,
3564
    bool $generate_user_new_password = false,
3565
    string $emailBody = '',
3566
    bool $user_self_change = false,
3567
    string $recovery_public_key = '',
3568
    string $recovery_private_key = ''
3569
): string
3570
{
3571
    $session = SessionManager::getSession();
3572
    $lang = new Language($session->get('user-language') ?? 'english');
3573
3574
    // prepapre background tasks for item keys generation        
3575
    $userTP = DB::queryFirstRow(
3576
        'SELECT pw, public_key, private_key
3577
        FROM ' . prefixTable('users') . '
3578
        WHERE id = %i',
3579
        TP_USER_ID
3580
    );
3581
    if (DB::count() === 0) {
3582
        return prepareExchangedData(
3583
            array(
3584
                'error' => true,
3585
                'message' => 'User not exists',
3586
            ),
3587
            'encode'
3588
        );
3589
    }
3590
3591
    // Do we need to generate new user password
3592
    if ($generate_user_new_password === true) {
3593
        // Generate a new password
3594
        $passwordClear = GenerateCryptKey(20, false, true, true, false, true);
3595
    }
3596
3597
    // Create password hash
3598
    $passwordManager = new PasswordManager();
3599
    $hashedPassword = $passwordManager->hashPassword($passwordClear);
3600
    if ($passwordManager->verifyPassword($hashedPassword, $passwordClear) === false) {
3601
        return prepareExchangedData(
3602
            array(
3603
                'error' => true,
3604
                'message' => $lang->get('pw_hash_not_correct'),
3605
            ),
3606
            'encode'
3607
        );
3608
    }
3609
3610
    // Check if valid public/private keys
3611
    if ($recovery_public_key !== '' && $recovery_private_key !== '') {
3612
        try {
3613
            // Generate random string
3614
            $random_str = generateQuickPassword(12, false);
3615
            // Encrypt random string with user publick key
3616
            $encrypted = encryptUserObjectKey($random_str, $recovery_public_key);
3617
            // Decrypt $encrypted with private key
3618
            $decrypted = decryptUserObjectKey($encrypted, $recovery_private_key);
3619
            // Check if decryptUserObjectKey returns our random string
3620
            if ($decrypted !== $random_str) {
3621
                throw new Exception('Public/Private keypair invalid.');
3622
            }
3623
        } catch (Exception $e) {
3624
            // Show error message to user and log event
3625
            error_log('ERROR: User '.$userId.' - '.$e->getMessage());
3626
3627
            return prepareExchangedData([
3628
                    'error' => true,
3629
                    'message' => $lang->get('pw_encryption_error'),
3630
                ],
3631
                'encode'
3632
            );
3633
        }
3634
    }
3635
3636
    // Generate new keys
3637
    if ($user_self_change === true && empty($recovery_public_key) === false && empty($recovery_private_key) === false){
3638
        $userKeys = [
3639
            'public_key' => $recovery_public_key,
3640
            'private_key_clear' => $recovery_private_key,
3641
            'private_key' => encryptPrivateKey($passwordClear, $recovery_private_key),
3642
        ];
3643
    } else {
3644
        $userKeys = generateUserKeys($passwordClear);
3645
    }
3646
3647
    // Save in DB
3648
    DB::update(
3649
        prefixTable('users'),
3650
        array(
3651
            'pw' => $hashedPassword,
3652
            'public_key' => $userKeys['public_key'],
3653
            'private_key' => $userKeys['private_key'],
3654
        ),
3655
        'id=%i',
3656
        $userId
3657
    );
3658
3659
    // update session too
3660
    if ($userId === $session->get('user-id')) {
3661
        $session->set('user-private_key', $userKeys['private_key_clear']);
3662
        $session->set('user-public_key', $userKeys['public_key']);
3663
    }
3664
3665
    // Manage empty encryption key
3666
    // Let's take the user's password if asked and if no encryption key provided
3667
    $encryptionKey = $encryptWithUserPassword === true && empty($encryptionKey) === true ? $passwordClear : $encryptionKey;
3668
3669
    // Create process
3670
    DB::insert(
3671
        prefixTable('background_tasks'),
3672
        array(
3673
            'created_at' => time(),
3674
            'process_type' => 'create_user_keys',
3675
            'arguments' => json_encode([
3676
                'new_user_id' => (int) $userId,
3677
                'new_user_pwd' => cryption($passwordClear, '','encrypt')['string'],
3678
                'new_user_code' => cryption(empty($encryptionKey) === true ? uniqidReal(20) : $encryptionKey, '','encrypt')['string'],
3679
                'owner_id' => (int) TP_USER_ID,
3680
                'creator_pwd' => $userTP['pw'],
3681
                'send_email' => $sendEmailToUser === true ? 1 : 0,
3682
                'otp_provided_new_value' => 1,
3683
                'email_body' => empty($emailBody) === true ? '' : $lang->get($emailBody),
3684
                'user_self_change' => $user_self_change === true ? 1 : 0,
3685
            ]),
3686
        )
3687
    );
3688
    $processId = DB::insertId();
3689
3690
    // Delete existing keys
3691
    if ($deleteExistingKeys === true) {
3692
        deleteUserObjetsKeys(
3693
            (int) $userId,
3694
        );
3695
    }
3696
3697
    // Create tasks
3698
    createUserTasks($processId, $nbItemsToTreat);
3699
3700
    // update user's new status
3701
    DB::update(
3702
        prefixTable('users'),
3703
        [
3704
            'is_ready_for_usage' => 0,
3705
            'otp_provided' => 1,
3706
            'ongoing_process_id' => $processId,
3707
            'special' => 'generate-keys',
3708
        ],
3709
        'id=%i',
3710
        $userId
3711
    );
3712
3713
    return prepareExchangedData(
3714
        array(
3715
            'error' => false,
3716
            'message' => '',
3717
            'user_password' => $generate_user_new_password === true ? $passwordClear : '',
3718
        ),
3719
        'encode'
3720
    );
3721
}
3722
3723
/**
3724
 * Permits to generate a new password for a user
3725
 *
3726
 * @param integer $processId
3727
 * @param integer $nbItemsToTreat
3728
 * @return void
3729
 
3730
 */
3731
function createUserTasks($processId, $nbItemsToTreat): void
3732
{
3733
    DB::insert(
3734
        prefixTable('background_subtasks'),
3735
        array(
3736
            'task_id' => $processId,
3737
            'created_at' => time(),
3738
            'task' => json_encode([
3739
                'step' => 'step0',
3740
                'index' => 0,
3741
                'nb' => $nbItemsToTreat,
3742
            ]),
3743
        )
3744
    );
3745
3746
    DB::insert(
3747
        prefixTable('background_subtasks'),
3748
        array(
3749
            'task_id' => $processId,
3750
            'created_at' => time(),
3751
            'task' => json_encode([
3752
                'step' => 'step10',
3753
                'index' => 0,
3754
                'nb' => $nbItemsToTreat,
3755
            ]),
3756
        )
3757
    );
3758
3759
    DB::insert(
3760
        prefixTable('background_subtasks'),
3761
        array(
3762
            'task_id' => $processId,
3763
            'created_at' => time(),
3764
            'task' => json_encode([
3765
                'step' => 'step20',
3766
                'index' => 0,
3767
                'nb' => $nbItemsToTreat,
3768
            ]),
3769
        )
3770
    );
3771
3772
    DB::insert(
3773
        prefixTable('background_subtasks'),
3774
        array(
3775
            'task_id' => $processId,
3776
            'created_at' => time(),
3777
            'task' => json_encode([
3778
                'step' => 'step30',
3779
                'index' => 0,
3780
                'nb' => $nbItemsToTreat,
3781
            ]),
3782
        )
3783
    );
3784
3785
    DB::insert(
3786
        prefixTable('background_subtasks'),
3787
        array(
3788
            'task_id' => $processId,
3789
            'created_at' => time(),
3790
            'task' => json_encode([
3791
                'step' => 'step40',
3792
                'index' => 0,
3793
                'nb' => $nbItemsToTreat,
3794
            ]),
3795
        )
3796
    );
3797
3798
    DB::insert(
3799
        prefixTable('background_subtasks'),
3800
        array(
3801
            'task_id' => $processId,
3802
            'created_at' => time(),
3803
            'task' => json_encode([
3804
                'step' => 'step50',
3805
                'index' => 0,
3806
                'nb' => $nbItemsToTreat,
3807
            ]),
3808
        )
3809
    );
3810
3811
    DB::insert(
3812
        prefixTable('background_subtasks'),
3813
        array(
3814
            'task_id' => $processId,
3815
            'created_at' => time(),
3816
            'task' => json_encode([
3817
                'step' => 'step60',
3818
                'index' => 0,
3819
                'nb' => $nbItemsToTreat,
3820
            ]),
3821
        )
3822
    );
3823
}
3824
3825
/**
3826
 * Permeits to check the consistency of date versus columns definition
3827
 *
3828
 * @param string $table
3829
 * @param array $dataFields
3830
 * @return array
3831
 */
3832
function validateDataFields(
3833
    string $table,
3834
    array $dataFields
3835
): array
3836
{
3837
    // Get table structure
3838
    $result = DB::query(
3839
        "SELECT `COLUMN_NAME`, `CHARACTER_MAXIMUM_LENGTH` FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%l' AND TABLE_NAME = '%l';",
3840
        DB_NAME,
3841
        $table
3842
    );
3843
3844
    foreach ($result as $row) {
3845
        $field = $row['COLUMN_NAME'];
3846
        $maxLength = is_null($row['CHARACTER_MAXIMUM_LENGTH']) === false ? (int) $row['CHARACTER_MAXIMUM_LENGTH'] : '';
3847
3848
        if (isset($dataFields[$field]) === true && is_array($dataFields[$field]) === false && empty($maxLength) === false) {
3849
            if (strlen((string) $dataFields[$field]) > $maxLength) {
3850
                return [
3851
                    'state' => false,
3852
                    'field' => $field,
3853
                    'maxLength' => $maxLength,
3854
                    'currentLength' => strlen((string) $dataFields[$field]),
3855
                ];
3856
            }
3857
        }
3858
    }
3859
    
3860
    return [
3861
        'state' => true,
3862
        'message' => '',
3863
    ];
3864
}
3865
3866
/**
3867
 * Adapt special characters sanitized during filter_var with option FILTER_SANITIZE_SPECIAL_CHARS operation
3868
 *
3869
 * @param string $string
3870
 * @return string
3871
 */
3872
function filterVarBack(string $string): string
3873
{
3874
    $arr = [
3875
        '&#060;' => '<',
3876
        '&#062;' => '>',
3877
        '&#034;' => '"',
3878
        '&#039;' => "'",
3879
        '&#038;' => '&',
3880
    ];
3881
3882
    foreach ($arr as $key => $value) {
3883
        $string = str_replace($key, $value, $string);
3884
    }
3885
3886
    return $string;
3887
}
3888
3889
/**
3890
 * 
3891
 */
3892
function storeTask(
3893
    string $taskName,
3894
    int $user_id,
3895
    int $is_personal_folder,
3896
    int $folder_destination_id,
3897
    int $item_id,
3898
    string $object_keys,
3899
    array $fields_keys = [],
3900
    array $files_keys = []
3901
)
3902
{
3903
    if (in_array($taskName, ['item_copy', 'new_item', 'update_item'])) {
3904
        // Create process
3905
        DB::insert(
3906
            prefixTable('background_tasks'),
3907
            array(
3908
                'created_at' => time(),
3909
                'process_type' => $taskName,
3910
                'arguments' => json_encode([
3911
                    'item_id' => $item_id,
3912
                    'object_key' => $object_keys,
3913
                ]),
3914
                'item_id' => $item_id,
3915
            )
3916
        );
3917
        $processId = DB::insertId();
3918
3919
        // Create tasks
3920
        // 1- Create password sharekeys for users of this new ITEM
3921
        DB::insert(
3922
            prefixTable('background_subtasks'),
3923
            array(
3924
                'task_id' => $processId,
3925
                'created_at' => time(),
3926
                'task' => json_encode([
3927
                    'step' => 'create_users_pwd_key',
3928
                    'index' => 0,
3929
                ]),
3930
            )
3931
        );
3932
3933
        // 2- Create fields sharekeys for users of this new ITEM
3934
        DB::insert(
3935
            prefixTable('background_subtasks'),
3936
            array(
3937
                'task_id' => $processId,
3938
                'created_at' => time(),
3939
                'task' => json_encode([
3940
                    'step' => 'create_users_fields_key',
3941
                    'index' => 0,
3942
                    'fields_keys' => $fields_keys,
3943
                ]),
3944
            )
3945
        );
3946
3947
        // 3- Create files sharekeys for users of this new ITEM
3948
        DB::insert(
3949
            prefixTable('background_subtasks'),
3950
            array(
3951
                'task_id' => $processId,
3952
                'created_at' => time(),
3953
                'task' => json_encode([
3954
                    'step' => 'create_users_files_key',
3955
                    'index' => 0,
3956
                    'files_keys' => $files_keys,
3957
                ]),
3958
            )
3959
        );
3960
    }
3961
}
3962
3963
/**
3964
 * 
3965
 */
3966
function createTaskForItem(
3967
    string $processType,
3968
    string|array $taskName,
3969
    int $itemId,
3970
    int $userId,
3971
    string $objectKey,
3972
    int $parentId = -1,
3973
    array $fields_keys = [],
3974
    array $files_keys = []
3975
)
3976
{
3977
    // 1- Create main process
3978
    // ---
3979
    
3980
    // Create process
3981
    DB::insert(
3982
        prefixTable('background_tasks'),
3983
        array(
3984
            'created_at' => time(),
3985
            'process_type' => $processType,
3986
            'arguments' => json_encode([
3987
                'all_users_except_id' => (int) $userId,
3988
                'item_id' => (int) $itemId,
3989
                'object_key' => $objectKey,
3990
                'author' => (int) $userId,
3991
            ]),
3992
            'item_id' => (int) $parentId !== -1 ?  $parentId : null,
3993
        )
3994
    );
3995
    $processId = DB::insertId();
3996
3997
    // 2- Create expected tasks
3998
    // ---
3999
    if (is_array($taskName) === false) {
0 ignored issues
show
introduced by
The condition is_array($taskName) === false is always false.
Loading history...
4000
        $taskName = [$taskName];
4001
    }
4002
    foreach($taskName as $task) {
4003
        error_log('createTaskForItem - task: '.$task);
4004
        switch ($task) {
4005
            case 'item_password':
4006
                
4007
                DB::insert(
4008
                    prefixTable('background_subtasks'),
4009
                    array(
4010
                        'task_id' => $processId,
4011
                        'created_at' => time(),
4012
                        'task' => json_encode([
4013
                            'step' => 'create_users_pwd_key',
4014
                            'index' => 0,
4015
                        ]),
4016
                    )
4017
                );
4018
4019
                break;
4020
            case 'item_field':
4021
                
4022
                DB::insert(
4023
                    prefixTable('background_subtasks'),
4024
                    array(
4025
                        'task_id' => $processId,
4026
                        'created_at' => time(),
4027
                        'task' => json_encode([
4028
                            'step' => 'create_users_fields_key',
4029
                            'index' => 0,
4030
                            'fields_keys' => $fields_keys,
4031
                        ]),
4032
                    )
4033
                );
4034
4035
                break;
4036
            case 'item_file':
4037
4038
                DB::insert(
4039
                    prefixTable('background_subtasks'),
4040
                    array(
4041
                        'task_id' => $processId,
4042
                        'created_at' => time(),
4043
                        'task' => json_encode([
4044
                            'step' => 'create_users_files_key',
4045
                            'index' => 0,
4046
                            'fields_keys' => $files_keys,
4047
                        ]),
4048
                    )
4049
                );
4050
                break;
4051
            default:
4052
                # code...
4053
                break;
4054
        }
4055
    }
4056
}
4057
4058
4059
function deleteProcessAndRelatedTasks(int $processId)
4060
{
4061
    // Delete process
4062
    DB::delete(
4063
        prefixTable('background_tasks'),
4064
        'id=%i',
4065
        $processId
4066
    );
4067
4068
    // Delete tasks
4069
    DB::delete(
4070
        prefixTable('background_subtasks'),
4071
        'task_id=%i',
4072
        $processId
4073
    );
4074
4075
}
4076
4077
/**
4078
 * Return PHP binary path
4079
 *
4080
 * @return string
4081
 */
4082
function getPHPBinary(): string
4083
{
4084
    // Get PHP binary path
4085
    $phpBinaryFinder = new PhpExecutableFinder();
4086
    $phpBinaryPath = $phpBinaryFinder->find();
4087
    return $phpBinaryPath === false ? 'false' : $phpBinaryPath;
4088
}
4089
4090
4091
4092
/**
4093
 * Delete unnecessary keys for personal items
4094
 *
4095
 * @param boolean $allUsers
4096
 * @param integer $user_id
4097
 * @return void
4098
 */
4099
function purgeUnnecessaryKeys(bool $allUsers = true, int $user_id=0)
4100
{
4101
    if ($allUsers === true) {
4102
        // Load class DB
4103
        if (class_exists('DB') === false) {
4104
            loadClasses('DB');
4105
        }
4106
4107
        $users = DB::query(
4108
            'SELECT id
4109
            FROM ' . prefixTable('users') . '
4110
            WHERE id NOT IN ('.OTV_USER_ID.', '.TP_USER_ID.', '.SSH_USER_ID.', '.API_USER_ID.')
4111
            ORDER BY login ASC'
4112
        );
4113
        foreach ($users as $user) {
4114
            purgeUnnecessaryKeysForUser((int) $user['id']);
4115
        }
4116
    } else {
4117
        purgeUnnecessaryKeysForUser((int) $user_id);
4118
    }
4119
}
4120
4121
/**
4122
 * Delete unnecessary keys for personal items
4123
 *
4124
 * @param integer $user_id
4125
 * @return void
4126
 */
4127
function purgeUnnecessaryKeysForUser(int $user_id=0)
4128
{
4129
    if ($user_id === 0) {
4130
        return;
4131
    }
4132
4133
    // Load class DB
4134
    loadClasses('DB');
4135
4136
    $personalItems = DB::queryFirstColumn(
4137
        'SELECT id
4138
        FROM ' . prefixTable('items') . ' AS i
4139
        INNER JOIN ' . prefixTable('log_items') . ' AS li ON li.id_item = i.id
4140
        WHERE i.perso = 1 AND li.action = "at_creation" AND li.id_user IN (%i, '.TP_USER_ID.')',
4141
        $user_id
4142
    );
4143
    if (count($personalItems) > 0) {
4144
        // Item keys
4145
        DB::delete(
4146
            prefixTable('sharekeys_items'),
4147
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4148
            $personalItems,
4149
            $user_id
4150
        );
4151
        // Files keys
4152
        DB::delete(
4153
            prefixTable('sharekeys_files'),
4154
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4155
            $personalItems,
4156
            $user_id
4157
        );
4158
        // Fields keys
4159
        DB::delete(
4160
            prefixTable('sharekeys_fields'),
4161
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4162
            $personalItems,
4163
            $user_id
4164
        );
4165
        // Logs keys
4166
        DB::delete(
4167
            prefixTable('sharekeys_logs'),
4168
            'object_id IN %li AND user_id NOT IN (%i, '.TP_USER_ID.')',
4169
            $personalItems,
4170
            $user_id
4171
        );
4172
    }
4173
}
4174
4175
/**
4176
 * Generate recovery keys file
4177
 *
4178
 * @param integer $userId
4179
 * @param array $SETTINGS
4180
 * @return string
4181
 */
4182
function handleUserRecoveryKeysDownload(int $userId, array $SETTINGS):string
4183
{
4184
    $session = SessionManager::getSession();
4185
    // Check if user exists
4186
    $userInfo = DB::queryFirstRow(
4187
        'SELECT pw, public_key, private_key, login, name
4188
        FROM ' . prefixTable('users') . '
4189
        WHERE id = %i',
4190
        $userId
4191
    );
4192
4193
    if (DB::count() > 0) {
4194
        $now = (int) time();
4195
        // Prepare file content
4196
        $export_value = file_get_contents(__DIR__."/../includes/core/teampass_ascii.txt")."\n".
4197
            "Generation date: ".date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now)."\n\n".
4198
            "RECOVERY KEYS - Not to be shared - To be store safely\n\n".
4199
            "Public Key:\n".$userInfo['public_key']."\n\n".
4200
            "Private Key:\n".decryptPrivateKey($session->get('user-password'), $userInfo['private_key'])."\n\n";
4201
4202
        // Update user's keys_recovery_time
4203
        DB::update(
4204
            prefixTable('users'),
4205
            [
4206
                'keys_recovery_time' => $now,
4207
            ],
4208
            'id=%i',
4209
            $userId
4210
        );
4211
        $session->set('user-keys_recovery_time', $now);
4212
4213
        //Log into DB the user's disconnection
4214
        logEvents($SETTINGS, 'user_mngt', 'at_user_keys_download', (string) $userId, $userInfo['login']);
4215
        
4216
        // Return data
4217
        return prepareExchangedData(
4218
            array(
4219
                'error' => false,
4220
                'datetime' => date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], $now),
4221
                'timestamp' => $now,
4222
                'content' => base64_encode($export_value),
4223
                'login' => $userInfo['login'],
4224
            ),
4225
            'encode'
4226
        );
4227
    }
4228
4229
    return prepareExchangedData(
4230
        array(
4231
            'error' => true,
4232
            'datetime' => '',
4233
        ),
4234
        'encode'
4235
    );
4236
}
4237
4238
/**
4239
 * Permits to load expected classes
4240
 *
4241
 * @param string $className
4242
 * @return void
4243
 */
4244
function loadClasses(string $className = ''): void
4245
{
4246
    require_once __DIR__. '/../includes/config/include.php';
4247
    require_once __DIR__. '/../includes/config/settings.php';
4248
    require_once __DIR__.'/../vendor/autoload.php';
4249
4250
    if (defined('DB_PASSWD_CLEAR') === false) {
4251
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, []));
4252
    }
4253
4254
    if (empty($className) === false) {
4255
        // Load class DB
4256
        if ((string) $className === 'DB') {
4257
            //Connect to DB
4258
            DB::$host = DB_HOST;
4259
            DB::$user = DB_USER;
4260
            DB::$password = DB_PASSWD_CLEAR;
4261
            DB::$dbName = DB_NAME;
4262
            DB::$port = DB_PORT;
4263
            DB::$encoding = DB_ENCODING;
4264
            DB::$ssl = DB_SSL;
4265
            DB::$connect_options = DB_CONNECT_OPTIONS;
4266
        }
4267
    }
4268
}
4269
4270
/**
4271
 * Returns the page the user is visiting.
4272
 *
4273
 * @return string The page name
4274
 */
4275
function getCurrectPage($SETTINGS)
4276
{
4277
    
4278
    $request = SymfonyRequest::createFromGlobals();
4279
4280
    // Parse the url
4281
    parse_str(
4282
        substr(
4283
            (string) $request->getRequestUri(),
4284
            strpos((string) $request->getRequestUri(), '?') + 1
4285
        ),
4286
        $result
4287
    );
4288
4289
    return $result['page'];
4290
}
4291
4292
/**
4293
 * Permits to return value if set
4294
 *
4295
 * @param string|int $value
4296
 * @param string|int|null $retFalse
4297
 * @param string|int $retTrue
4298
 * @return mixed
4299
 */
4300
function returnIfSet($value, $retFalse = '', $retTrue = null): mixed
4301
{
4302
4303
    return isset($value) === true ? ($retTrue === null ? $value : $retTrue) : $retFalse;
4304
}
4305
4306
4307
/**
4308
 * SEnd email to user
4309
 *
4310
 * @param string $post_receipt
4311
 * @param string $post_body
4312
 * @param string $post_subject
4313
 * @param array $post_replace
4314
 * @param boolean $immediate_email
4315
 * @return string
4316
 */
4317
function sendMailToUser(
4318
    string $post_receipt,
4319
    string $post_body,
4320
    string $post_subject,
4321
    array $post_replace,
4322
    bool $immediate_email = false
4323
): ?string {
4324
    global $SETTINGS;
4325
    $emailSettings = new EmailSettings($SETTINGS);
4326
    $emailService = new EmailService();
4327
4328
    if (count($post_replace) > 0 && is_null($post_replace) === false) {
4329
        $post_body = str_replace(
4330
            array_keys($post_replace),
0 ignored issues
show
Bug introduced by
$post_replace of type null is incompatible with the type array expected by parameter $array of array_keys(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4330
            array_keys(/** @scrutinizer ignore-type */ $post_replace),
Loading history...
4331
            array_values($post_replace),
0 ignored issues
show
Bug introduced by
$post_replace of type null is incompatible with the type array expected by parameter $array of array_values(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4331
            array_values(/** @scrutinizer ignore-type */ $post_replace),
Loading history...
4332
            $post_body
4333
        );
4334
    }
4335
4336
    if ($immediate_email === true) {
4337
        
4338
        $ret = $emailService->sendMail(
4339
            $post_subject,
4340
            $post_body,
0 ignored issues
show
Security File Manipulation introduced by
$post_body can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

6 paths for user data to reach this point

  1. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 66
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 66
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4319
  2. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4319
  3. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 70
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 70
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4319
  4. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 69
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 69
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4319
  5. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 67
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 67
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4319
  6. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4319

Used in path-write context

  1. EmailService::sendMail() is called
    in sources/main.functions.php on line 4554
  2. Enters via parameter $textMail
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 94
  3. Data is passed through sanitizeEmailBody()
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 109
  4. $this->sanitizeEmailBody($textMail) is assigned to $textMail
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 109
  5. Data is passed through emailBody()
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 115
  6. emailBody($textMail) is assigned to property PHPMailer::$Body
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 115
  7. Read from property PHPMailer::$Body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  8. Data is passed through encodeString()
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  9. $this->encodeString($this->Body, $this->Encoding) is assigned to $body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  10. $body is returned
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3135
  11. $this->createBody() is assigned to property PHPMailer::$MIMEBody
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1622
  12. Read from property PHPMailer::$MIMEBody
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1687
  13. PHPMailer::sendmailSend() is called
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1687
  14. Enters via parameter $body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1726
  15. fwrite() is called
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1779

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 File Manipulation introduced by
$post_body can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

6 paths for user data to reach this point

  1. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 70
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 70
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4319
  2. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4319
  3. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 66
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 66
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4319
  4. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 67
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 67
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4319
  5. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 69
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 69
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4319
  6. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4319

Used in path-write context

  1. EmailService::sendMail() is called
    in sources/main.functions.php on line 4554
  2. Enters via parameter $textMail
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 94
  3. Data is passed through sanitizeEmailBody()
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 109
  4. $this->sanitizeEmailBody($textMail) is assigned to $textMail
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 109
  5. Data is passed through emailBody()
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 115
  6. emailBody($textMail) is assigned to property PHPMailer::$Body
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 115
  7. Read from property PHPMailer::$Body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  8. Data is passed through encodeString()
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  9. $this->encodeString($this->Body, $this->Encoding) is assigned to $body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  10. $body is returned
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3135
  11. $this->createBody() is assigned to property PHPMailer::$MIMEBody
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1622
  12. Read from property PHPMailer::$MIMEBody
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1689
  13. PHPMailer::smtpSend() is called
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1689
  14. Enters via parameter $body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 2058
  15. SMTP::data() is called
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 2096
  16. Enters via parameter $msg_data
    in vendor/phpmailer/phpmailer/src/SMTP.php on line 728
  17. Data is passed through str_replace()
    in vendor/phpmailer/phpmailer/src/SMTP.php on line 744
  18. Data is passed through explode()
    in vendor/phpmailer/phpmailer/src/SMTP.php on line 744
  19. ``explode(' ', str_replace(array(' ', ' '), ' ', $msg_data))`` is assigned to $lines
    in vendor/phpmailer/phpmailer/src/SMTP.php on line 744
  20. $lines is assigned to $line
    in vendor/phpmailer/phpmailer/src/SMTP.php on line 757
  21. $line is assigned to $lines_out
    in vendor/phpmailer/phpmailer/src/SMTP.php on line 785
  22. $lines_out is assigned to $line_out
    in vendor/phpmailer/phpmailer/src/SMTP.php on line 788
  23. SMTP::client_send() is called
    in vendor/phpmailer/phpmailer/src/SMTP.php on line 794
  24. Enters via parameter $data
    in vendor/phpmailer/phpmailer/src/SMTP.php on line 1153
  25. fwrite() is called
    in vendor/phpmailer/phpmailer/src/SMTP.php on line 1166

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 File Manipulation introduced by
$post_body can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

6 paths for user data to reach this point

  1. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 67
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 67
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4319
  2. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 68
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4319
  3. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 69
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 69
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4319
  4. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 65
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4319
  5. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 66
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 66
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4319
  6. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 70
  1. Read from $_SERVER
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 70
  2. array('subTaskId' => $_SERVER['argv'][1], 'index' => $_SERVER['argv'][2], 'nb' => $_SERVER['argv'][3], 'step' => $_SERVER['argv'][4], 'taskArguments' => $_SERVER['argv'][5], 'taskId' => $_SERVER['argv'][6]) is assigned to $inputData
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 64
  3. Data is passed through json_decode(), and json_decode($inputData['taskArguments'], true) is assigned to $taskArgs
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 82
  4. performUserCreationKeys() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 107
  5. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 141
  6. cronContinueReEncryptingUserSharekeysStep10() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 174
  7. Enters via parameter $extra_arguments
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 830
  8. sendMailToUser() is called
    in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 871
  9. Enters via parameter $post_body
    in sources/main.functions.php on line 4319

Used in path-write context

  1. EmailService::sendMail() is called
    in sources/main.functions.php on line 4554
  2. Enters via parameter $textMail
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 94
  3. Data is passed through sanitizeEmailBody()
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 109
  4. $this->sanitizeEmailBody($textMail) is assigned to $textMail
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 109
  5. Data is passed through emailBody()
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 115
  6. emailBody($textMail) is assigned to property PHPMailer::$Body
    in vendor/teampassclasses/emailservice/src/EmailService.php on line 115
  7. Read from property PHPMailer::$Body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  8. Data is passed through encodeString()
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  9. $this->encodeString($this->Body, $this->Encoding) is assigned to $body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  10. file_put_contents() is called
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3092

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...
4341
            $post_receipt,
4342
            $emailSettings,
4343
            '',
4344
            false
4345
        );
4346
    
4347
        $ret = json_decode($ret, true);
4348
    
4349
        return prepareExchangedData(
4350
            array(
4351
                'error' => empty($ret['error']) === true ? false : true,
4352
                'message' => $ret['message'],
4353
            ),
4354
            'encode'
4355
        );
4356
    } else {
4357
        // Send through task handler
4358
        prepareSendingEmail(
4359
            $post_subject,
4360
            $post_body,
4361
            $post_receipt,
4362
            ""
4363
        );
4364
    }
4365
4366
    return null;
4367
}
4368
4369
/**
4370
 * Converts a password strengh value to zxcvbn level
4371
 * 
4372
 * @param integer $passwordStrength
4373
 * 
4374
 * @return integer
4375
 */
4376
function convertPasswordStrength($passwordStrength): int
4377
{
4378
    if ($passwordStrength === 0) {
4379
        return TP_PW_STRENGTH_1;
4380
    } else if ($passwordStrength === 1) {
4381
        return TP_PW_STRENGTH_2;
4382
    } else if ($passwordStrength === 2) {
4383
        return TP_PW_STRENGTH_3;
4384
    } else if ($passwordStrength === 3) {
4385
        return TP_PW_STRENGTH_4;
4386
    } else {
4387
        return TP_PW_STRENGTH_5;
4388
    }
4389
}
4390
4391
/**
4392
 * Vérifie si les IDs d'un tableau existent bien dans la table.
4393
 *
4394
 * @param array $ids - Tableau d'IDs à vérifier
4395
 * @param string $tableName - Nom de la table dans laquelle vérifier les IDs
4396
 * @param string $fieldName - Nom du champ dans lequel vérifier les IDs
4397
 * @return array - IDs qui n'existent pas dans la table
4398
 */
4399
function checkIdsExist(array $ids, string $tableName, string $fieldName) : array
4400
{
4401
    // Assure-toi que le tableau d'IDs n'est pas vide
4402
    if (empty($ids)) {
4403
        return [];
4404
    }
4405
4406
    // Nettoyage des IDs pour éviter les injections SQL
4407
    $ids = array_map('intval', $ids);  // Assure que chaque ID est un entier
4408
4409
    // Construction de la requête SQL pour vérifier les IDs dans la table
4410
    $result = DB::query('SELECT id FROM ' . prefixTable($tableName) . ' WHERE ' . $fieldName . ' IN %li', $ids);
4411
4412
    // Extraire les IDs existants de la table
4413
    $existingIds = array_column($result, 'id');
4414
4415
    // Trouver les IDs manquants en comparant les deux tableaux
4416
    $missingIds = array_diff($ids, $existingIds);
4417
4418
    return $missingIds; // Renvoie les IDs qui n'existent pas dans la table
4419
}
4420