Passed
Push — master ( 981f6d...40bfd0 )
by Nils
05:40
created

EmailSettings1   A

Complexity

Total Complexity 1

Size/Duplication

Total Lines 25
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 21
dl 0
loc 25
rs 10
c 0
b 0
f 0
wmc 1

1 Method

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

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

4545
            array_values(/** @scrutinizer ignore-type */ $post_replace),
Loading history...
4546
            $post_body
4547
        );
4548
    }
4549
4550
    if ($immediate_email === true) {
4551
        
4552
        $ret = $emailService->sendMail(
4553
            $post_subject,
4554
            $post_body,
0 ignored issues
show
Security File Manipulation introduced by
$post_body can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

6 paths for user data to reach this point

  1. Path: Read from $_SERVER in scripts/background_tasks___userKeysCreation_subtaskHdl.php on line 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 4533
  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 4533
  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 4533
  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 4533
  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 4533
  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 4533

Used in path-write context

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

General Strategies to prevent injection

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

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

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

$sanitized = (integer) $tainted;
Loading history...
Security File Manipulation introduced by
$post_body can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

6 paths for user data to reach this point

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

Used in path-write context

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

General Strategies to prevent injection

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

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

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

$sanitized = (integer) $tainted;
Loading history...
Security File Manipulation introduced by
$post_body can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

6 paths for user data to reach this point

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

Used in path-write context

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

General Strategies to prevent injection

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

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

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

$sanitized = (integer) $tainted;
Loading history...
4555
            $post_receipt,
4556
            $emailSettings,
4557
            '',
4558
            false
4559
        );
4560
    
4561
        $ret = json_decode($ret, true);
4562
    
4563
        return prepareExchangedData(
4564
            array(
4565
                'error' => empty($ret['error']) === true ? false : true,
4566
                'message' => $ret['message'],
4567
            ),
4568
            'encode'
4569
        );
4570
    } else {
4571
        // Send through task handler
4572
        prepareSendingEmail(
4573
            $post_subject,
4574
            $post_body,
4575
            $post_receipt,
4576
            ""
4577
        );
4578
    }
4579
4580
    return null;
4581
}
4582
4583
/**
4584
 * Converts a password strengh value to zxcvbn level
4585
 * 
4586
 * @param integer $passwordStrength
4587
 * 
4588
 * @return integer
4589
 */
4590
function convertPasswordStrength($passwordStrength): int
4591
{
4592
    if ($passwordStrength === 0) {
4593
        return TP_PW_STRENGTH_1;
4594
    } else if ($passwordStrength === 1) {
4595
        return TP_PW_STRENGTH_2;
4596
    } else if ($passwordStrength === 2) {
4597
        return TP_PW_STRENGTH_3;
4598
    } else if ($passwordStrength === 3) {
4599
        return TP_PW_STRENGTH_4;
4600
    } else {
4601
        return TP_PW_STRENGTH_5;
4602
    }
4603
}