Passed
Push — master ( d42221...e5e375 )
by Nils
04:54
created

bCrypt()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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

Used in path-write context

  1. $text_html is assigned to property PHPMailer::$Body
    in sources/main.functions.php on line 1266
  2. Read from property PHPMailer::$Body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  3. Data is passed through encodeString()
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  4. $this->encodeString($this->Body, $this->Encoding) is assigned to $body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  5. $body is returned
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3135
  6. $this->createBody() is assigned to property PHPMailer::$MIMEBody
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1622
  7. Read from property PHPMailer::$MIMEBody
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1687
  8. PHPMailer::sendmailSend() is called
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1687
  9. Enters via parameter $body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1726
  10. 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
$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 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 4397
  10. sendEmail() is called
    in sources/main.functions.php on line 4414
  11. Enters via parameter $textMail
    in sources/main.functions.php on line 1143
  12. buildEmail() is called
    in sources/main.functions.php on line 1166
  13. Enters via parameter $textMail
    in sources/main.functions.php on line 1202
  14. Data is passed through emailBody(), and emailBody($textMail) is assigned to $text_html
    in sources/main.functions.php on line 1258
  15. Data is passed through xss_clean(), and $antiXss->xss_clean($text_html) is assigned to $text_html
    in sources/main.functions.php on line 1259
  16. Data is passed through htmlspecialchars(), and htmlspecialchars($text_html, ENT_QUOTES, 'UTF-8') is assigned to $text_html
    in sources/main.functions.php on line 1260
  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 4397
  10. sendEmail() is called
    in sources/main.functions.php on line 4414
  11. Enters via parameter $textMail
    in sources/main.functions.php on line 1143
  12. buildEmail() is called
    in sources/main.functions.php on line 1166
  13. Enters via parameter $textMail
    in sources/main.functions.php on line 1202
  14. Data is passed through emailBody(), and emailBody($textMail) is assigned to $text_html
    in sources/main.functions.php on line 1258
  15. Data is passed through xss_clean(), and $antiXss->xss_clean($text_html) is assigned to $text_html
    in sources/main.functions.php on line 1259
  16. Data is passed through htmlspecialchars(), and htmlspecialchars($text_html, ENT_QUOTES, 'UTF-8') is assigned to $text_html
    in sources/main.functions.php on line 1260
  3. 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 4397
  10. sendEmail() is called
    in sources/main.functions.php on line 4414
  11. Enters via parameter $textMail
    in sources/main.functions.php on line 1143
  12. buildEmail() is called
    in sources/main.functions.php on line 1166
  13. Enters via parameter $textMail
    in sources/main.functions.php on line 1202
  14. Data is passed through emailBody(), and emailBody($textMail) is assigned to $text_html
    in sources/main.functions.php on line 1258
  15. Data is passed through xss_clean(), and $antiXss->xss_clean($text_html) is assigned to $text_html
    in sources/main.functions.php on line 1259
  16. Data is passed through htmlspecialchars(), and htmlspecialchars($text_html, ENT_QUOTES, 'UTF-8') is assigned to $text_html
    in sources/main.functions.php on line 1260
  4. 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 4397
  10. sendEmail() is called
    in sources/main.functions.php on line 4414
  11. Enters via parameter $textMail
    in sources/main.functions.php on line 1143
  12. buildEmail() is called
    in sources/main.functions.php on line 1166
  13. Enters via parameter $textMail
    in sources/main.functions.php on line 1202
  14. Data is passed through emailBody(), and emailBody($textMail) is assigned to $text_html
    in sources/main.functions.php on line 1258
  15. Data is passed through xss_clean(), and $antiXss->xss_clean($text_html) is assigned to $text_html
    in sources/main.functions.php on line 1259
  16. Data is passed through htmlspecialchars(), and htmlspecialchars($text_html, ENT_QUOTES, 'UTF-8') is assigned to $text_html
    in sources/main.functions.php on line 1260
  5. 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 4397
  10. sendEmail() is called
    in sources/main.functions.php on line 4414
  11. Enters via parameter $textMail
    in sources/main.functions.php on line 1143
  12. buildEmail() is called
    in sources/main.functions.php on line 1166
  13. Enters via parameter $textMail
    in sources/main.functions.php on line 1202
  14. Data is passed through emailBody(), and emailBody($textMail) is assigned to $text_html
    in sources/main.functions.php on line 1258
  15. Data is passed through xss_clean(), and $antiXss->xss_clean($text_html) is assigned to $text_html
    in sources/main.functions.php on line 1259
  16. Data is passed through htmlspecialchars(), and htmlspecialchars($text_html, ENT_QUOTES, 'UTF-8') is assigned to $text_html
    in sources/main.functions.php on line 1260
  6. 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 4397
  10. sendEmail() is called
    in sources/main.functions.php on line 4414
  11. Enters via parameter $textMail
    in sources/main.functions.php on line 1143
  12. buildEmail() is called
    in sources/main.functions.php on line 1166
  13. Enters via parameter $textMail
    in sources/main.functions.php on line 1202
  14. Data is passed through emailBody(), and emailBody($textMail) is assigned to $text_html
    in sources/main.functions.php on line 1258
  15. Data is passed through xss_clean(), and $antiXss->xss_clean($text_html) is assigned to $text_html
    in sources/main.functions.php on line 1259
  16. Data is passed through htmlspecialchars(), and htmlspecialchars($text_html, ENT_QUOTES, 'UTF-8') is assigned to $text_html
    in sources/main.functions.php on line 1260

Used in path-write context

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

4405
            array_keys(/** @scrutinizer ignore-type */ $post_replace),
Loading history...
4406
            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

4406
            array_values(/** @scrutinizer ignore-type */ $post_replace),
Loading history...
4407
            $post_body
4408
        );
4409
    }
4410
4411
    if ($immediate_email === true) {
4412
        $ret = sendEmail(
4413
            $post_subject,
4414
            $post_body,
4415
            $post_receipt,
4416
            $SETTINGS,
4417
            '',
4418
            false
4419
        );
4420
    
4421
        $ret = json_decode($ret, true);
4422
    
4423
        return prepareExchangedData(
4424
            array(
4425
                'error' => empty($ret['error']) === true ? false : true,
4426
                'message' => $ret['message'],
4427
            ),
4428
            'encode'
4429
        );
4430
    } else {
4431
        // Send through task handler
4432
        prepareSendingEmail(
4433
            $post_subject,
4434
            $post_body,
4435
            $post_receipt,
4436
            ""
4437
        );
4438
    }
4439
4440
    return null;
4441
}
4442
4443
/**
4444
 * Converts a password strengh value to zxcvbn level
4445
 * 
4446
 * @param integer $passwordStrength
4447
 * 
4448
 * @return integer
4449
 */
4450
function convertPasswordStrength($passwordStrength): int
4451
{
4452
    if ($passwordStrength === 0) {
4453
        return TP_PW_STRENGTH_1;
4454
    } else if ($passwordStrength === 1) {
4455
        return TP_PW_STRENGTH_2;
4456
    } else if ($passwordStrength === 2) {
4457
        return TP_PW_STRENGTH_3;
4458
    } else if ($passwordStrength === 3) {
4459
        return TP_PW_STRENGTH_4;
4460
    } else {
4461
        return TP_PW_STRENGTH_5;
4462
    }
4463
}