Passed
Push — master ( aae623...5d2677 )
by Nils
05:40
created

configureMailer()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 22
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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

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

4535
            array_values(/** @scrutinizer ignore-type */ $post_replace),
Loading history...
4536
            $post_body
4537
        );
4538
    }
4539
4540
    if ($immediate_email === true) {
4541
        $ret = sendEmail(
4542
            $post_subject,
4543
            $post_body,
4544
            $post_receipt,
4545
            $SETTINGS,
4546
            '',
4547
            false
4548
        );
4549
    
4550
        $ret = json_decode($ret, true);
4551
    
4552
        return prepareExchangedData(
4553
            array(
4554
                'error' => empty($ret['error']) === true ? false : true,
4555
                'message' => $ret['message'],
4556
            ),
4557
            'encode'
4558
        );
4559
    } else {
4560
        // Send through task handler
4561
        prepareSendingEmail(
4562
            $post_subject,
4563
            $post_body,
4564
            $post_receipt,
4565
            ""
4566
        );
4567
    }
4568
4569
    return null;
4570
}
4571
4572
/**
4573
 * Converts a password strengh value to zxcvbn level
4574
 * 
4575
 * @param integer $passwordStrength
4576
 * 
4577
 * @return integer
4578
 */
4579
function convertPasswordStrength($passwordStrength): int
4580
{
4581
    if ($passwordStrength === 0) {
4582
        return TP_PW_STRENGTH_1;
4583
    } else if ($passwordStrength === 1) {
4584
        return TP_PW_STRENGTH_2;
4585
    } else if ($passwordStrength === 2) {
4586
        return TP_PW_STRENGTH_3;
4587
    } else if ($passwordStrength === 3) {
4588
        return TP_PW_STRENGTH_4;
4589
    } else {
4590
        return TP_PW_STRENGTH_5;
4591
    }
4592
}