Passed
Push — master ( 00ce58...d851f1 )
by Nils
10:01 queued 04:53
created

buildEmail()   F

Complexity

Conditions 10
Paths 804

Size

Total Lines 74
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 10
eloc 43
c 3
b 0
f 0
nc 804
nop 7
dl 0
loc 74
rs 3.7722

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

6 paths for user data to reach this point

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

Used in path-write context

  1. $text_html is assigned to property PHPMailer::$Body
    in sources/main.functions.php on line 1262
  2. Read from property PHPMailer::$Body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  3. Data is passed through encodeString()
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  4. $this->encodeString($this->Body, $this->Encoding) is assigned to $body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  5. file_put_contents() is called
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3092

General Strategies to prevent injection

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

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

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

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

6 paths for user data to reach this point

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

Used in path-write context

  1. $text_html is assigned to property PHPMailer::$Body
    in sources/main.functions.php on line 1262
  2. Read from property PHPMailer::$Body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  3. Data is passed through encodeString()
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  4. $this->encodeString($this->Body, $this->Encoding) is assigned to $body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3075
  5. $body is returned
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 3135
  6. $this->createBody() is assigned to property PHPMailer::$MIMEBody
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1622
  7. Read from property PHPMailer::$MIMEBody
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1687
  8. PHPMailer::sendmailSend() is called
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1687
  9. Enters via parameter $body
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1726
  10. fwrite() is called
    in vendor/phpmailer/phpmailer/src/PHPMailer.php on line 1803

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

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

4457
            array_values(/** @scrutinizer ignore-type */ $post_replace),
Loading history...
4458
            $post_body
4459
        );
4460
    }
4461
4462
    if ($immediate_email === true) {
4463
        $ret = sendEmail(
4464
            $post_subject,
4465
            $post_body,
4466
            $post_receipt,
4467
            $SETTINGS,
4468
            '',
4469
            false
4470
        );
4471
    
4472
        $ret = json_decode($ret, true);
4473
    
4474
        return prepareExchangedData(
4475
            array(
4476
                'error' => empty($ret['error']) === true ? false : true,
4477
                'message' => $ret['message'],
4478
            ),
4479
            'encode'
4480
        );
4481
    } else {
4482
        // Send through task handler
4483
        prepareSendingEmail(
4484
            $post_subject,
4485
            $post_body,
4486
            $post_receipt,
4487
            ""
4488
        );
4489
    }
4490
4491
    return null;
4492
}
4493
4494
/**
4495
 * Converts a password strengh value to zxcvbn level
4496
 * 
4497
 * @param integer $passwordStrength
4498
 * 
4499
 * @return integer
4500
 */
4501
function convertPasswordStrength($passwordStrength): int
4502
{
4503
    if ($passwordStrength === 0) {
4504
        return TP_PW_STRENGTH_1;
4505
    } else if ($passwordStrength === 1) {
4506
        return TP_PW_STRENGTH_2;
4507
    } else if ($passwordStrength === 2) {
4508
        return TP_PW_STRENGTH_3;
4509
    } else if ($passwordStrength === 3) {
4510
        return TP_PW_STRENGTH_4;
4511
    } else {
4512
        return TP_PW_STRENGTH_5;
4513
    }
4514
}