Passed
Push — teampass_3.0 ( 8a3983...68cd42 )
by Nils
04:35
created

testHex2Bin()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 7
rs 10
1
<?php
2
3
/**
4
 * Teampass - a collaborative passwords manager.
5
 * ---
6
 * This library is distributed in the hope that it will be useful,
7
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
9
 * ---
10
 * @project   Teampass
11
 * @file      main.functions.php
12
 * ---
13
 * @author    Nils Laumaillé ([email protected])
14
 * @copyright 2009-2019 Teampass.net
15
 * @license   https://spdx.org/licenses/GPL-3.0-only.html#licenseText GPL-3.0
16
 * ---
17
 * @see       https://www.teampass.net
18
 */
19
20
use LdapRecord\Connection;
21
22
23
if (isset($_SESSION['CPM']) === false || (int) $_SESSION['CPM'] !== 1) {
24
    die('Hacking attempt...');
25
}
26
27
// Load config if $SETTINGS not defined
28
if (isset($SETTINGS['cpassman_dir']) === false || empty($SETTINGS['cpassman_dir']) === true) {
29
    if (file_exists('../includes/config/tp.config.php')) {
30
        include_once '../includes/config/tp.config.php';
31
    } elseif (file_exists('./includes/config/tp.config.php')) {
32
        include_once './includes/config/tp.config.php';
33
    } elseif (file_exists('../../includes/config/tp.config.php')) {
34
        include_once '../../includes/config/tp.config.php';
35
    } else {
36
        throw new Exception("Error file '/includes/config/tp.config.php' not exists", 1);
37
    }
38
}
39
40
header('Content-type: text/html; charset=utf-8');
41
header('Cache-Control: no-cache, must-revalidate');
42
43
/**
44
 * Convert language code to string.
45
 *
46
 * @param string $string String to get
47
 *
48
 * @return string
49
 */
50
function langHdl($string)
51
{
52
    if (empty($string) === true) {
53
        // Manage error
54
        return 'ERROR in language strings!';
55
    }
56
57
    // Load superglobal
58
    if (file_exists('../includes/libraries/protect/SuperGlobal/SuperGlobal.php')) {
59
        include_once '../includes/libraries/protect/SuperGlobal/SuperGlobal.php';
60
    } elseif (file_exists('./includes/libraries/protect/SuperGlobal/SuperGlobal.php')) {
61
        include_once './includes/libraries/protect/SuperGlobal/SuperGlobal.php';
62
    } elseif (file_exists('../../includes/libraries/protect/SuperGlobal/SuperGlobal.php')) {
63
        include_once '../../includes/libraries/protect/SuperGlobal/SuperGlobal.php';
64
    } else {
65
        throw new Exception("Error file '/includes/libraries/protect/SuperGlobal/SuperGlobal.php' not exists", 1);
66
    }
67
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
68
69
    // Get language string
70
    $session_language = $superGlobal->get(trim($string), 'SESSION', 'lang');
71
72
    if (isset($session_language) === false) {
73
        // Manage error
74
        return 'ERROR in language strings!';
75
    } else {
76
        return str_replace(
77
            array('"', "'"),
78
            array('&quot;', '&apos;'),
79
            $session_language
80
        );
81
    }
82
}
83
84
85
86
87
/**
88
 * genHash().
89
 *
90
 * Generate a hash for user login
91
 *
92
 * @param string $password
93
 */
94
function bCrypt($password, $cost)
95
{
96
    $salt = sprintf('$2y$%02d$', $cost);
97
    if (function_exists('openssl_random_pseudo_bytes')) {
98
        $salt .= bin2hex(openssl_random_pseudo_bytes(11));
99
    } else {
100
        $chars = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
101
        for ($i = 0; $i < 22; ++$i) {
102
            $salt .= $chars[mt_rand(0, 63)];
103
        }
104
    }
105
106
    return crypt($password, $salt);
107
}
108
109
110
/**
111
 * Defuse cryption function.
112
 *
113
 * @param string $message   what to de/crypt
114
 * @param string $ascii_key key to use
115
 * @param string $type      operation to perform
116
 * @param array  $SETTINGS  Teampass settings
117
 *
118
 * @return array
119
 */
120
function cryption($message, $ascii_key, $type, $SETTINGS)
121
{
122
    $ascii_key = (empty($ascii_key) === true) ? file_get_contents(SECUREPATH . '/teampass-seckey.txt') : $ascii_key;
123
    $err = false;
124
125
    // load PhpEncryption library
126
    if (isset($SETTINGS['cpassman_dir']) === false || empty($SETTINGS['cpassman_dir']) === true) {
127
        $path = '../includes/libraries/Encryption/Encryption/';
128
    } else {
129
        $path = $SETTINGS['cpassman_dir'] . '/includes/libraries/Encryption/Encryption/';
130
    }
131
132
    include_once $path . 'Crypto.php';
133
    include_once $path . 'Encoding.php';
134
    include_once $path . 'DerivedKeys.php';
135
    include_once $path . 'Key.php';
136
    include_once $path . 'KeyOrPassword.php';
137
    include_once $path . 'File.php';
138
    include_once $path . 'RuntimeTests.php';
139
    include_once $path . 'KeyProtectedByPassword.php';
140
    include_once $path . 'Core.php';
141
142
    // convert KEY
143
    $key = \Defuse\Crypto\Key::loadFromAsciiSafeString($ascii_key);
144
145
    try {
146
        if ($type === 'encrypt') {
147
            $text = \Defuse\Crypto\Crypto::encrypt($message, $key);
148
        } else if ($type === 'decrypt') {
149
            $text = \Defuse\Crypto\Crypto::decrypt($message, $key);
150
        }
151
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
152
        $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.';
153
    } catch (Defuse\Crypto\Exception\BadFormatException $ex) {
154
        $err = $ex;
155
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
156
        $err = $ex;
157
    } catch (Defuse\Crypto\Exception\CryptoException $ex) {
158
        $err = $ex;
159
    } catch (Defuse\Crypto\Exception\IOException $ex) {
160
        $err = $ex;
161
    }
162
    //echo \Defuse\Crypto\Crypto::decrypt($message, $key).' ## ';
163
164
    return array(
165
        'string' => isset($text) ? $text : '',
166
        'error' => $err,
167
    );
168
}
169
170
/**
171
 * Generating a defuse key.
172
 *
173
 * @return string
174
 */
175
function defuse_generate_key()
176
{
177
    // load PhpEncryption library
178
    if (file_exists('../includes/config/tp.config.php') === true) {
179
        $path = '../includes/libraries/Encryption/Encryption/';
180
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
181
        $path = './includes/libraries/Encryption/Encryption/';
182
    } else {
183
        $path = '../includes/libraries/Encryption/Encryption/';
184
    }
185
186
    include_once $path . 'Crypto.php';
187
    include_once $path . 'Encoding.php';
188
    include_once $path . 'DerivedKeys.php';
189
    include_once $path . 'Key.php';
190
    include_once $path . 'KeyOrPassword.php';
191
    include_once $path . 'File.php';
192
    include_once $path . 'RuntimeTests.php';
193
    include_once $path . 'KeyProtectedByPassword.php';
194
    include_once $path . 'Core.php';
195
196
    $key = \Defuse\Crypto\Key::createNewRandomKey();
197
    $key = $key->saveToAsciiSafeString();
198
199
    return $key;
200
}
201
202
/**
203
 * Generate a Defuse personal key.
204
 *
205
 * @param string $psk psk used
206
 *
207
 * @return string
208
 */
209
function defuse_generate_personal_key($psk)
210
{
211
    // load PhpEncryption library
212
    if (file_exists('../includes/config/tp.config.php') === true) {
213
        $path = '../includes/libraries/Encryption/Encryption/';
214
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
215
        $path = './includes/libraries/Encryption/Encryption/';
216
    } else {
217
        $path = '../includes/libraries/Encryption/Encryption/';
218
    }
219
220
    include_once $path . 'Crypto.php';
221
    include_once $path . 'Encoding.php';
222
    include_once $path . 'DerivedKeys.php';
223
    include_once $path . 'Key.php';
224
    include_once $path . 'KeyOrPassword.php';
225
    include_once $path . 'File.php';
226
    include_once $path . 'RuntimeTests.php';
227
    include_once $path . 'KeyProtectedByPassword.php';
228
    include_once $path . 'Core.php';
229
230
    $protected_key = \Defuse\Crypto\KeyProtectedByPassword::createRandomPasswordProtectedKey($psk);
231
    $protected_key_encoded = $protected_key->saveToAsciiSafeString();
232
233
    return $protected_key_encoded; // save this in user table
234
}
235
236
/**
237
 * Validate persoanl key with defuse.
238
 *
239
 * @param string $psk                   the user's psk
240
 * @param string $protected_key_encoded special key
241
 *
242
 * @return string
243
 */
244
function defuse_validate_personal_key($psk, $protected_key_encoded)
245
{
246
    // load PhpEncryption library
247
    if (file_exists('../includes/config/tp.config.php') === true) {
248
        $path = '../includes/libraries/Encryption/Encryption/';
249
    } elseif (file_exists('./includes/config/tp.config.php') === true) {
250
        $path = './includes/libraries/Encryption/Encryption/';
251
    } else {
252
        $path = '../includes/libraries/Encryption/Encryption/';
253
    }
254
255
    include_once $path . 'Crypto.php';
256
    include_once $path . 'Encoding.php';
257
    include_once $path . 'DerivedKeys.php';
258
    include_once $path . 'Key.php';
259
    include_once $path . 'KeyOrPassword.php';
260
    include_once $path . 'File.php';
261
    include_once $path . 'RuntimeTests.php';
262
    include_once $path . 'KeyProtectedByPassword.php';
263
    include_once $path . 'Core.php';
264
265
    try {
266
        $protected_key = \Defuse\Crypto\KeyProtectedByPassword::loadFromAsciiSafeString($protected_key_encoded);
267
        $user_key = $protected_key->unlockKey($psk);
268
        $user_key_encoded = $user_key->saveToAsciiSafeString();
269
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
270
        return 'Error - Major issue as the encryption is broken.';
271
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
272
        return 'Error - The saltkey is not the correct one.';
273
    }
274
275
    return $user_key_encoded; // store it in session once user has entered his psk
276
}
277
278
/**
279
 * Decrypt a defuse string if encrypted.
280
 *
281
 * @param string $value Encrypted string
282
 *
283
 * @return string Decrypted string
284
 */
285
function defuseReturnDecrypted($value, $SETTINGS)
286
{
287
    if (substr($value, 0, 3) === 'def') {
288
        $value = cryption($value, '', 'decrypt', $SETTINGS)['string'];
289
    }
290
291
    return $value;
292
}
293
294
/**
295
 * Trims a string depending on a specific string.
296
 *
297
 * @param string|array $chaine  what to trim
298
 * @param string       $element trim on what
299
 *
300
 * @return string
301
 */
302
function trimElement($chaine, $element)
303
{
304
    if (!empty($chaine)) {
305
        if (is_array($chaine) === true) {
306
            $chaine = implode(';', $chaine);
307
        }
308
        $chaine = trim($chaine);
309
        if (substr($chaine, 0, 1) === $element) {
310
            $chaine = substr($chaine, 1);
311
        }
312
        if (substr($chaine, strlen($chaine) - 1, 1) === $element) {
313
            $chaine = substr($chaine, 0, strlen($chaine) - 1);
314
        }
315
    }
316
317
    return $chaine;
318
}
319
320
/**
321
 * Permits to suppress all "special" characters from string.
322
 *
323
 * @param string $string  what to clean
324
 * @param bool   $special use of special chars?
325
 *
326
 * @return string
327
 */
328
function cleanString($string, $special = false)
329
{
330
    // Create temporary table for special characters escape
331
    $tabSpecialChar = array();
332
    for ($i = 0; $i <= 31; ++$i) {
333
        $tabSpecialChar[] = chr($i);
334
    }
335
    array_push($tabSpecialChar, '<br />');
336
    if ((int) $special === 1) {
337
        $tabSpecialChar = array_merge($tabSpecialChar, array('</li>', '<ul>', '<ol>'));
338
    }
339
340
    return str_replace($tabSpecialChar, "\n", $string);
341
}
342
343
/**
344
 * Erro manager for DB.
345
 *
346
 * @param array $params output from query
347
 */
348
function db_error_handler($params)
349
{
350
    echo 'Error: ' . $params['error'] . "<br>\n";
351
    echo 'Query: ' . $params['query'] . "<br>\n";
352
    throw new Exception('Error - Query', 1);
353
}
354
355
/**
356
 * [identifyUserRights description].
357
 *
358
 * @param string $groupesVisiblesUser  [description]
359
 * @param string $groupesInterditsUser [description]
360
 * @param string $isAdmin              [description]
361
 * @param string $idFonctions          [description]
362
 *
363
 * @return string [description]
364
 */
365
function identifyUserRights(
366
    $groupesVisiblesUser,
367
    $groupesInterditsUser,
368
    $isAdmin,
369
    $idFonctions,
370
    $SETTINGS
371
) {
372
    //load ClassLoader
373
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
374
375
    // Load superglobal
376
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
377
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
378
379
    //Connect to DB
380
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
381
    if (defined('DB_PASSWD_CLEAR') === false) {
382
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
383
    }
384
    DB::$host = DB_HOST;
385
    DB::$user = DB_USER;
386
    DB::$password = DB_PASSWD_CLEAR;
387
    DB::$dbName = DB_NAME;
388
    DB::$port = DB_PORT;
389
    DB::$encoding = DB_ENCODING;
390
391
    //Build tree
392
    $tree = new SplClassLoader('Tree\NestedTree', $SETTINGS['cpassman_dir'] . '/includes/libraries');
393
    $tree->register();
394
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
395
396
    // Check if user is ADMINISTRATOR
397
    if ((int) $isAdmin === 1) {
398
        identAdmin(
399
            $idFonctions,
400
            $SETTINGS,
401
            /** @scrutinizer ignore-type */ $tree
402
        );
403
    } else {
404
        identUser(
405
            $groupesVisiblesUser,
406
            $groupesInterditsUser,
407
            $idFonctions,
408
            $SETTINGS,
409
            /** @scrutinizer ignore-type */ $tree
410
        );
411
    }
412
413
    // update user's timestamp
414
    DB::update(
415
        prefixTable('users'),
416
        array(
417
            'timestamp' => time(),
418
        ),
419
        'id=%i',
420
        $superGlobal->get('user_id', 'SESSION')
421
    );
422
}
423
424
/**
425
 * Identify administrator.
426
 *
427
 * @param string $idFonctions Roles of user
428
 * @param array  $SETTINGS    Teampass settings
429
 * @param array  $tree        Tree of folders
430
 */
431
function identAdmin($idFonctions, $SETTINGS, $tree)
432
{
433
    // Load superglobal
434
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
435
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
436
437
    // Init
438
    $groupesVisibles = array();
439
    $superGlobal->put('personal_folders', array(), 'SESSION');
440
    $superGlobal->put('groupes_visibles', array(), 'SESSION');
441
    $superGlobal->put('no_access_folders', array(), 'SESSION');
442
    $superGlobal->put('personal_visible_groups', array(), 'SESSION');
443
    $superGlobal->put('read_only_folders', array(), 'SESSION');
444
    $superGlobal->put('list_restricted_folders_for_items', array(), 'SESSION');
445
    $superGlobal->put('list_folders_editable_by_role', array(), 'SESSION');
446
    $superGlobal->put('list_folders_limited', array(), 'SESSION');
447
    $superGlobal->put('no_access_folders', array(), 'SESSION');
448
    $superGlobal->put('forbiden_pfs', array(), 'SESSION');
449
450
    // Get superglobals
451
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
452
    $globalsVisibleFolders = $superGlobal->get('groupes_visibles', 'SESSION');
453
    $globalsPersonalVisibleFolders = $superGlobal->get('personal_visible_groups', 'SESSION');
454
455
    // Get list of Folders
456
    $rows = DB::query('SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i', 0);
457
    foreach ($rows as $record) {
458
        array_push($groupesVisibles, $record['id']);
459
    }
460
    $superGlobal->put('groupes_visibles', $groupesVisibles, 'SESSION');
461
    $superGlobal->put('all_non_personal_folders', $groupesVisibles, 'SESSION');
462
463
    // Exclude all PF
464
    $where = new WhereClause('and'); // create a WHERE statement of pieces joined by ANDs
465
    $where->add('personal_folder=%i', 1);
466
    if (
467
        isset($SETTINGS['enable_pf_feature']) === true
468
        && (int) $SETTINGS['enable_pf_feature'] === 1
469
    ) {
470
        $where->add('title=%s', $globalsUserId);
471
        $where->negateLast();
472
    }
473
    // Get ID of personal folder
474
    $persfld = DB::queryfirstrow(
475
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE title = %s',
476
        $globalsUserId
477
    );
478
    if (empty($persfld['id']) === false) {
479
        if (in_array($persfld['id'], $globalsVisibleFolders) === false) {
480
            array_push($globalsVisibleFolders, $persfld['id']);
481
            array_push($globalsPersonalVisibleFolders, $persfld['id']);
482
            // get all descendants
483
            $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
484
            $tree->rebuild();
485
            $tst = $tree->getDescendants($persfld['id']);
486
            foreach ($tst as $t) {
487
                array_push($globalsVisibleFolders, $t->id);
488
                array_push($globalsPersonalVisibleFolders, $t->id);
489
            }
490
        }
491
    }
492
493
    // get complete list of ROLES
494
    $tmp = explode(';', $idFonctions);
495
    $rows = DB::query(
496
        'SELECT * FROM ' . prefixTable('roles_title') . '
497
        ORDER BY title ASC'
498
    );
499
    foreach ($rows as $record) {
500
        if (!empty($record['id']) && !in_array($record['id'], $tmp)) {
501
            array_push($tmp, $record['id']);
502
        }
503
    }
504
    $superGlobal->put('fonction_id', implode(';', $tmp), 'SESSION');
505
    $superGlobal->put('is_admin', 1, 'SESSION');
506
507
    // Check if admin has created Folders and Roles
508
    DB::query('SELECT * FROM ' . prefixTable('nested_tree') . '');
509
    $superGlobal->put('nb_folders', DB::count(), 'SESSION');
510
    DB::query('SELECT * FROM ' . prefixTable('roles_title'));
511
    $superGlobal->put('nb_roles', DB::count(), 'SESSION');
512
}
513
514
/**
515
 * Permits to convert an element to array.
516
 *
517
 * @param string|array $element Any value to be returned as array
518
 *
519
 * @return array
520
 */
521
function convertToArray($element)
522
{
523
    if (is_string($element) === true) {
524
        if (empty($element) === true) {
525
            return array();
526
        } else {
527
            return explode(
528
                ';',
529
                trimElement($element, ';')
530
            );
531
        }
532
    } else {
533
        return $element;
534
    }
535
}
536
537
/**
538
 * Defines the rights the user has.
539
 *
540
 * @param string|array $allowedFolders  Allowed folders
541
 * @param string|array $noAccessFolders Not allowed folders
542
 * @param string|array $userRoles       Roles of user
543
 * @param array        $SETTINGS        Teampass settings
544
 * @param object       $tree            Tree of folders
545
 */
546
function identUser(
547
    $allowedFolders,
548
    $noAccessFolders,
549
    $userRoles,
550
    $SETTINGS,
551
    $tree
552
) {
553
    // Load superglobal
554
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
555
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
556
557
    // Init
558
    $superGlobal->put('groupes_visibles', array(), 'SESSION');
559
    $superGlobal->put('personal_folders', array(), 'SESSION');
560
    $superGlobal->put('no_access_folders', array(), 'SESSION');
561
    $superGlobal->put('personal_visible_groups', array(), 'SESSION');
562
    $superGlobal->put('read_only_folders', array(), 'SESSION');
563
    $superGlobal->put('fonction_id', $userRoles, 'SESSION');
564
    $superGlobal->put('is_admin', 0, 'SESSION');
565
566
    // init
567
    $personalFolders = array();
568
    $readOnlyFolders = array();
569
    $noAccessPersonalFolders = array();
570
    $restrictedFoldersForItems = array();
571
    $foldersLimited = array();
572
    $foldersLimitedFull = array();
573
    $allowedFoldersByRoles = array();
574
575
    // Get superglobals
576
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
577
    $globalsPersonalFolders = $superGlobal->get('personal_folder', 'SESSION');
578
579
    // Ensure consistency in array format
580
    $noAccessFolders = convertToArray($noAccessFolders);
581
    $userRoles = convertToArray($userRoles);
582
    $allowedFolders = convertToArray($allowedFolders);
583
584
    // Get list of folders depending on Roles
585
    $rows = DB::query(
586
        'SELECT *
587
        FROM ' . prefixTable('roles_values') . '
588
        WHERE role_id IN %li AND type IN %ls',
589
        $userRoles,
590
        array('W', 'ND', 'NE', 'NDNE', 'R')
591
    );
592
    foreach ($rows as $record) {
593
        if ($record['type'] === 'R') {
594
            array_push($readOnlyFolders, $record['folder_id']);
595
        } elseif (in_array($record['folder_id'], $allowedFolders) === false) {
596
            array_push($allowedFoldersByRoles, $record['folder_id']);
597
        }
598
    }
599
    $allowedFoldersByRoles = array_unique($allowedFoldersByRoles);
600
    $readOnlyFolders = array_unique($readOnlyFolders);
601
602
    // Clean arrays
603
    foreach ($allowedFoldersByRoles as $value) {
604
        if (($key = array_search($value, $readOnlyFolders)) !== false) {
605
            unset($readOnlyFolders[$key]);
606
        }
607
    }
608
609
    // Does this user is allowed to see other items
610
    $inc = 0;
611
    $rows = DB::query(
612
        'SELECT id, id_tree FROM ' . prefixTable('items') . '
613
        WHERE restricted_to LIKE %ss AND inactif = %s',
614
        $globalsUserId . ';',
615
        '0'
616
    );
617
    foreach ($rows as $record) {
618
        // Exclude restriction on item if folder is fully accessible
619
        if (in_array($record['id_tree'], $allowedFolders) === false) {
620
            $restrictedFoldersForItems[$record['id_tree']][$inc] = $record['id'];
621
            ++$inc;
622
        }
623
    }
624
625
    // Check for the users roles if some specific rights exist on items
626
    $rows = DB::query(
627
        'SELECT i.id_tree, r.item_id
628
        FROM ' . prefixTable('items') . ' as i
629
        INNER JOIN ' . prefixTable('restriction_to_roles') . ' as r ON (r.item_id=i.id)
630
        WHERE r.role_id IN %li
631
        ORDER BY i.id_tree ASC',
632
        $userRoles
633
    );
634
    $inc = 0;
635
    foreach ($rows as $record) {
636
        if (isset($record['id_tree'])) {
637
            $foldersLimited[$record['id_tree']][$inc] = $record['item_id'];
638
            array_push($foldersLimitedFull, $record['item_id']);
639
            ++$inc;
640
        }
641
    }
642
643
    // Get list of Personal Folders
644
    if (
645
        isset($SETTINGS['enable_pf_feature']) === true && (int) $SETTINGS['enable_pf_feature'] === 1
646
        && isset($globalsPersonalFolders) === true && (int) $globalsPersonalFolders === 1
647
    ) {
648
        $persoFld = DB::queryfirstrow(
649
            'SELECT id
650
            FROM ' . prefixTable('nested_tree') . '
651
            WHERE title = %s AND personal_folder = %i',
652
            $globalsUserId,
653
            1
654
        );
655
        if (empty($persoFld['id']) === false) {
656
            if (in_array($persoFld['id'], $allowedFolders) === false) {
657
                array_push($personalFolders, $persoFld['id']);
658
                array_push($allowedFolders, $persoFld['id']);
659
660
                // get all descendants
661
                $ids = $tree->getChildren($persoFld['id'], false);
662
                foreach ($ids as $ident) {
663
                    if ((int) $ident->personal_folder === 1) {
664
                        array_push($allowedFolders, $ident->id);
665
                        array_push($personalFolders, $ident->id);
666
                    }
667
                }
668
            }
669
        }
670
    }
671
672
    // Exclude all other PF
673
    $where = new WhereClause('and');
674
    $where->add('personal_folder=%i', 1);
675
    if (
676
        isset($SETTINGS['enable_pf_feature']) === true && (int) $SETTINGS['enable_pf_feature'] === 1
677
        && isset($globalsPersonalFolders) === true && (int) $globalsPersonalFolders === 1
678
    ) {
679
        $where->add('title=%s', $globalsUserId);
680
        $where->negateLast();
681
    }
682
    $persoFlds = DB::query(
683
        'SELECT id
684
        FROM ' . prefixTable('nested_tree') . '
685
        WHERE %l',
686
        $where
687
    );
688
    foreach ($persoFlds as $persoFldId) {
689
        array_push($noAccessPersonalFolders, $persoFldId['id']);
690
    }
691
692
    // All folders visibles
693
    $allowedFolders = array_merge(
694
        $foldersLimitedFull,
695
        $allowedFoldersByRoles,
696
        $restrictedFoldersForItems,
697
        $readOnlyFolders
698
    );
699
700
    // Exclude from allowed folders all the specific user forbidden folders
701
    if (count($noAccessFolders) > 0) {
702
        $allowedFolders = array_diff($allowedFolders, $noAccessFolders);
703
    }
704
705
    // Return data
706
    $superGlobal->put('all_non_personal_folders', $allowedFolders, 'SESSION');
707
    $superGlobal->put('groupes_visibles', array_merge($allowedFolders, $personalFolders), 'SESSION');
708
    $superGlobal->put('read_only_folders', $readOnlyFolders, 'SESSION');
709
    $superGlobal->put('no_access_folders', $noAccessFolders, 'SESSION');
710
    $superGlobal->put('personal_folders', $personalFolders, 'SESSION');
711
    $superGlobal->put('list_folders_limited', $foldersLimited, 'SESSION');
712
    $superGlobal->put('list_folders_editable_by_role', $allowedFoldersByRoles, 'SESSION');
713
    $superGlobal->put('list_restricted_folders_for_items', $restrictedFoldersForItems, 'SESSION');
714
    $superGlobal->put('forbiden_pfs', $noAccessPersonalFolders, 'SESSION');
715
    $superGlobal->put(
716
        'all_folders_including_no_access',
717
        array_merge(
718
            $allowedFolders,
719
            $personalFolders,
720
            $noAccessFolders,
721
            $readOnlyFolders
722
        ),
723
        'SESSION'
724
    );
725
726
    // Folders and Roles numbers
727
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('nested_tree') . '');
728
    $superGlobal->put('nb_folders', DB::count(), 'SESSION');
729
    DB::queryfirstrow('SELECT id FROM ' . prefixTable('roles_title'));
730
    $superGlobal->put('nb_roles', DB::count(), 'SESSION');
731
732
    // check if change proposals on User's items
733
    if (isset($SETTINGS['enable_suggestion']) === true && (int) $SETTINGS['enable_suggestion'] === 1) {
734
        DB::query(
735
            'SELECT *
736
            FROM ' . prefixTable('items_change') . ' AS c
737
            LEFT JOIN ' . prefixTable('log_items') . ' AS i ON (c.item_id = i.id_item)
738
            WHERE i.action = %s AND i.id_user = %i',
739
            'at_creation',
740
            $globalsUserId
741
        );
742
        $superGlobal->put('nb_item_change_proposals', DB::count(), 'SESSION');
743
    } else {
744
        $superGlobal->put('nb_item_change_proposals', 0, 'SESSION');
745
    }
746
747
    return true;
748
}
749
750
/**
751
 * Update the CACHE table.
752
 *
753
 * @param string $action   What to do
754
 * @param array  $SETTINGS Teampass settings
755
 * @param string $ident    Ident format
756
 */
757
function updateCacheTable($action, $SETTINGS, $ident = null)
758
{
759
    if ($action === 'reload') {
760
        // Rebuild full cache table
761
        cacheTableRefresh($SETTINGS);
762
    } elseif ($action === 'update_value' && is_null($ident) === false) {
763
        // UPDATE an item
764
        cacheTableUpdate($SETTINGS, $ident);
765
    } elseif ($action === 'add_value' && is_null($ident) === false) {
766
        // ADD an item
767
        cacheTableAdd($SETTINGS, $ident);
768
    } elseif ($action === 'delete_value' && is_null($ident) === false) {
769
        // DELETE an item
770
        DB::delete(prefixTable('cache'), 'id = %i', $ident);
771
    }
772
}
773
774
/**
775
 * Cache table - refresh.
776
 *
777
 * @param array $SETTINGS Teampass settings
778
 */
779
function cacheTableRefresh($SETTINGS)
780
{
781
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
782
783
    //Connect to DB
784
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
785
    if (defined('DB_PASSWD_CLEAR') === false) {
786
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
787
    }
788
    DB::$host = DB_HOST;
789
    DB::$user = DB_USER;
790
    DB::$password = DB_PASSWD_CLEAR;
791
    DB::$dbName = DB_NAME;
792
    DB::$port = DB_PORT;
793
    DB::$encoding = DB_ENCODING;
794
795
    //Load Tree
796
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
797
    $tree->register();
798
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
799
800
    // truncate table
801
    DB::query('TRUNCATE TABLE ' . prefixTable('cache'));
802
803
    // reload date
804
    $rows = DB::query(
805
        'SELECT *
806
        FROM ' . prefixTable('items') . ' as i
807
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
808
        AND l.action = %s
809
        AND i.inactif = %i',
810
        'at_creation',
811
        0
812
    );
813
    foreach ($rows as $record) {
814
        if (empty($record['id_tree']) === false) {
815
            // Get all TAGS
816
            $tags = '';
817
            $itemTags = DB::query(
818
                'SELECT tag
819
                FROM ' . prefixTable('tags') . '
820
                WHERE item_id = %i AND tag != ""',
821
                $record['id']
822
            );
823
            foreach ($itemTags as $itemTag) {
824
                $tags .= $itemTag['tag'] . ' ';
825
            }
826
827
            // Get renewal period
828
            $resNT = DB::queryfirstrow(
829
                'SELECT renewal_period
830
                FROM ' . prefixTable('nested_tree') . '
831
                WHERE id = %i',
832
                $record['id_tree']
833
            );
834
835
            // form id_tree to full foldername
836
            $folder = array();
837
            $arbo = $tree->getPath($record['id_tree'], true);
838
            foreach ($arbo as $elem) {
839
                // Check if title is the ID of a user
840
                if (is_numeric($elem->title) === true) {
841
                    // Is this a User id?
842
                    $user = DB::queryfirstrow(
843
                        'SELECT id, login
844
                        FROM ' . prefixTable('users') . '
845
                        WHERE id = %i',
846
                        $elem->title
847
                    );
848
                    if (count($user) > 0) {
849
                        $elem->title = $user['login'];
850
                    }
851
                }
852
                // Build path
853
                array_push($folder, stripslashes($elem->title));
854
            }
855
            // store data
856
            DB::insert(
857
                prefixTable('cache'),
858
                array(
859
                    'id' => $record['id'],
860
                    'label' => $record['label'],
861
                    'description' => isset($record['description']) ? $record['description'] : '',
862
                    'url' => (isset($record['url']) && !empty($record['url'])) ? $record['url'] : '0',
863
                    'tags' => $tags,
864
                    'id_tree' => $record['id_tree'],
865
                    'perso' => $record['perso'],
866
                    'restricted_to' => (isset($record['restricted_to']) && !empty($record['restricted_to'])) ? $record['restricted_to'] : '0',
867
                    'login' => isset($record['login']) ? $record['login'] : '',
868
                    'folder' => implode(' > ', $folder),
869
                    'author' => $record['id_user'],
870
                    'renewal_period' => isset($resNT['renewal_period']) ? $resNT['renewal_period'] : '0',
871
                    'timestamp' => $record['date'],
872
                )
873
            );
874
        }
875
    }
876
}
877
878
/**
879
 * Cache table - update existing value.
880
 *
881
 * @param array  $SETTINGS Teampass settings
882
 * @param string $ident    Ident format
883
 */
884
function cacheTableUpdate($SETTINGS, $ident = null)
885
{
886
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
887
888
    // Load superglobal
889
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
890
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
891
892
    //Connect to DB
893
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
894
    if (defined('DB_PASSWD_CLEAR') === false) {
895
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
896
    }
897
    DB::$host = DB_HOST;
898
    DB::$user = DB_USER;
899
    DB::$password = DB_PASSWD_CLEAR;
900
    DB::$dbName = DB_NAME;
901
    DB::$port = DB_PORT;
902
    DB::$encoding = DB_ENCODING;
903
904
    //Load Tree
905
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
906
    $tree->register();
907
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
908
909
    // get new value from db
910
    $data = DB::queryfirstrow(
911
        'SELECT label, description, id_tree, perso, restricted_to, login, url
912
        FROM ' . prefixTable('items') . '
913
        WHERE id=%i',
914
        $ident
915
    );
916
    // Get all TAGS
917
    $tags = '';
918
    $itemTags = DB::query(
919
        'SELECT tag
920
        FROM ' . prefixTable('tags') . '
921
        WHERE item_id = %i AND tag != ""',
922
        $ident
923
    );
924
    foreach ($itemTags as $itemTag) {
925
        $tags .= $itemTag['tag'] . ' ';
926
    }
927
    // form id_tree to full foldername
928
    $folder = array();
929
    $arbo = $tree->getPath($data['id_tree'], true);
930
    foreach ($arbo as $elem) {
931
        // Check if title is the ID of a user
932
        if (is_numeric($elem->title) === true) {
933
            // Is this a User id?
934
            $user = DB::queryfirstrow(
935
                'SELECT id, login
936
                FROM ' . prefixTable('users') . '
937
                WHERE id = %i',
938
                $elem->title
939
            );
940
            if (count($user) > 0) {
941
                $elem->title = $user['login'];
942
            }
943
        }
944
        // Build path
945
        array_push($folder, stripslashes($elem->title));
946
    }
947
    // finaly update
948
    DB::update(
949
        prefixTable('cache'),
950
        array(
951
            'label' => $data['label'],
952
            'description' => $data['description'],
953
            'tags' => $tags,
954
            'url' => (isset($data['url']) && !empty($data['url'])) ? $data['url'] : '0',
955
            'id_tree' => $data['id_tree'],
956
            'perso' => $data['perso'],
957
            'restricted_to' => (isset($data['restricted_to']) && !empty($data['restricted_to'])) ? $data['restricted_to'] : '0',
958
            'login' => isset($data['login']) ? $data['login'] : '',
959
            'folder' => implode(' » ', $folder),
960
            'author' => $superGlobal->get('user_id', 'SESSION'),
961
        ),
962
        'id = %i',
963
        $ident
964
    );
965
}
966
967
/**
968
 * Cache table - add new value.
969
 *
970
 * @param array  $SETTINGS Teampass settings
971
 * @param string $ident    Ident format
972
 */
973
function cacheTableAdd($SETTINGS, $ident = null)
974
{
975
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
976
977
    // Load superglobal
978
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
979
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
980
981
    // Get superglobals
982
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
983
984
    //Connect to DB
985
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
986
    if (defined('DB_PASSWD_CLEAR') === false) {
987
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
988
    }
989
    DB::$host = DB_HOST;
990
    DB::$user = DB_USER;
991
    DB::$password = DB_PASSWD_CLEAR;
992
    DB::$dbName = DB_NAME;
993
    DB::$port = DB_PORT;
994
    DB::$encoding = DB_ENCODING;
995
996
    //Load Tree
997
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
998
    $tree->register();
999
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1000
1001
    // get new value from db
1002
    $data = DB::queryFirstRow(
1003
        'SELECT i.label, i.description, i.id_tree as id_tree, i.perso, i.restricted_to, i.id, i.login, i.url, l.date
1004
        FROM ' . prefixTable('items') . ' as i
1005
        INNER JOIN ' . prefixTable('log_items') . ' as l ON (l.id_item = i.id)
1006
        WHERE i.id = %i
1007
        AND l.action = %s',
1008
        $ident,
1009
        'at_creation'
1010
    );
1011
    // Get all TAGS
1012
    $tags = '';
1013
    $itemTags = DB::query(
1014
        'SELECT tag
1015
        FROM ' . prefixTable('tags') . '
1016
        WHERE item_id = %i AND tag != ""',
1017
        $ident
1018
    );
1019
    foreach ($itemTags as $itemTag) {
1020
        $tags .= $itemTag['tag'] . ' ';
1021
    }
1022
    // form id_tree to full foldername
1023
    $folder = array();
1024
    $arbo = $tree->getPath($data['id_tree'], true);
1025
    foreach ($arbo as $elem) {
1026
        // Check if title is the ID of a user
1027
        if (is_numeric($elem->title) === true) {
1028
            // Is this a User id?
1029
            $user = DB::queryfirstrow(
1030
                'SELECT id, login
1031
                FROM ' . prefixTable('users') . '
1032
                WHERE id = %i',
1033
                $elem->title
1034
            );
1035
            if (count($user) > 0) {
1036
                $elem->title = $user['login'];
1037
            }
1038
        }
1039
        // Build path
1040
        array_push($folder, stripslashes($elem->title));
1041
    }
1042
    // finaly update
1043
    DB::insert(
1044
        prefixTable('cache'),
1045
        array(
1046
            'id' => $data['id'],
1047
            'label' => $data['label'],
1048
            'description' => $data['description'],
1049
            'tags' => (isset($tags) && empty($tags) === false) ? $tags : 'None',
1050
            'url' => (isset($data['url']) && !empty($data['url'])) ? $data['url'] : '0',
1051
            'id_tree' => $data['id_tree'],
1052
            'perso' => (isset($data['perso']) && empty($data['perso']) === false && $data['perso'] !== 'None') ? $data['perso'] : '0',
1053
            'restricted_to' => (isset($data['restricted_to']) && empty($data['restricted_to']) === false) ? $data['restricted_to'] : '0',
1054
            'login' => isset($data['login']) ? $data['login'] : '',
1055
            'folder' => implode(' » ', $folder),
1056
            'author' => $globalsUserId,
1057
            'timestamp' => $data['date'],
1058
        )
1059
    );
1060
}
1061
1062
/**
1063
 * Do statistics.
1064
 *
1065
 * @param array $SETTINGS Teampass settings
1066
 *
1067
 * @return array
1068
 */
1069
function getStatisticsData($SETTINGS)
1070
{
1071
    DB::query(
1072
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1073
        0
1074
    );
1075
    $counter_folders = DB::count();
1076
1077
    DB::query(
1078
        'SELECT id FROM ' . prefixTable('nested_tree') . ' WHERE personal_folder = %i',
1079
        1
1080
    );
1081
    $counter_folders_perso = DB::count();
1082
1083
    DB::query(
1084
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1085
        0
1086
    );
1087
    $counter_items = DB::count();
1088
1089
    DB::query(
1090
        'SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i',
1091
        1
1092
    );
1093
    $counter_items_perso = DB::count();
1094
1095
    DB::query(
1096
        'SELECT id FROM ' . prefixTable('users') . ''
1097
    );
1098
    $counter_users = DB::count();
1099
1100
    DB::query(
1101
        'SELECT id FROM ' . prefixTable('users') . ' WHERE admin = %i',
1102
        1
1103
    );
1104
    $admins = DB::count();
1105
1106
    DB::query(
1107
        'SELECT id FROM ' . prefixTable('users') . ' WHERE gestionnaire = %i',
1108
        1
1109
    );
1110
    $managers = DB::count();
1111
1112
    DB::query(
1113
        'SELECT id FROM ' . prefixTable('users') . ' WHERE read_only = %i',
1114
        1
1115
    );
1116
    $readOnly = DB::count();
1117
1118
    // list the languages
1119
    $usedLang = [];
1120
    $tp_languages = DB::query(
1121
        'SELECT name FROM ' . prefixTable('languages')
1122
    );
1123
    foreach ($tp_languages as $tp_language) {
1124
        DB::query(
1125
            'SELECT * FROM ' . prefixTable('users') . ' WHERE user_language = %s',
1126
            $tp_language['name']
1127
        );
1128
        $usedLang[$tp_language['name']] = round((DB::count() * 100 / $counter_users), 0);
1129
    }
1130
1131
    // get list of ips
1132
    $usedIp = [];
1133
    $tp_ips = DB::query(
1134
        'SELECT user_ip FROM ' . prefixTable('users')
1135
    );
1136
    foreach ($tp_ips as $ip) {
1137
        if (array_key_exists($ip['user_ip'], $usedIp)) {
1138
            $usedIp[$ip['user_ip']] = $usedIp[$ip['user_ip']] + 1;
1139
        } elseif (!empty($ip['user_ip']) && $ip['user_ip'] !== 'none') {
1140
            $usedIp[$ip['user_ip']] = 1;
1141
        }
1142
    }
1143
1144
    return array(
1145
        'error' => '',
1146
        'stat_phpversion' => phpversion(),
1147
        'stat_folders' => $counter_folders,
1148
        'stat_folders_shared' => intval($counter_folders) - intval($counter_folders_perso),
1149
        'stat_items' => $counter_items,
1150
        'stat_items_shared' => intval($counter_items) - intval($counter_items_perso),
1151
        'stat_users' => $counter_users,
1152
        'stat_admins' => $admins,
1153
        'stat_managers' => $managers,
1154
        'stat_ro' => $readOnly,
1155
        'stat_kb' => $SETTINGS['enable_kb'],
1156
        'stat_pf' => $SETTINGS['enable_pf_feature'],
1157
        'stat_fav' => $SETTINGS['enable_favourites'],
1158
        'stat_teampassversion' => TP_VERSION_FULL,
1159
        'stat_ldap' => $SETTINGS['ldap_mode'],
1160
        'stat_agses' => $SETTINGS['agses_authentication_enabled'],
1161
        'stat_duo' => $SETTINGS['duo'],
1162
        'stat_suggestion' => $SETTINGS['enable_suggestion'],
1163
        'stat_api' => $SETTINGS['api'],
1164
        'stat_customfields' => $SETTINGS['item_extra_fields'],
1165
        'stat_syslog' => $SETTINGS['syslog_enable'],
1166
        'stat_2fa' => $SETTINGS['google_authentication'],
1167
        'stat_stricthttps' => $SETTINGS['enable_sts'],
1168
        'stat_mysqlversion' => DB::serverVersion(),
1169
        'stat_languages' => $usedLang,
1170
        'stat_country' => $usedIp,
1171
    );
1172
}
1173
1174
/**
1175
 * Permits to send an email.
1176
 *
1177
 * @param string $subject     email subject
1178
 * @param string $textMail    email message
1179
 * @param string $email       email
1180
 * @param array  $SETTINGS    settings
1181
 * @param string $textMailAlt email message alt
1182
 * @param bool   $silent      no errors
1183
 *
1184
 * @return string some json info
1185
 */
1186
function sendEmail(
1187
    $subject,
1188
    $textMail,
1189
    $email,
1190
    $SETTINGS,
1191
    $textMailAlt = null,
1192
    $silent = true
1193
) {
1194
    // CAse where email not defined
1195
    if ($email === 'none' || empty($email) === true) {
1196
        return '"error":"" , "message":"' . langHdl('forgot_my_pw_email_sent') . '"';
1197
    }
1198
1199
    // Load settings
1200
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
1201
1202
    // Load superglobal
1203
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1204
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1205
1206
    // Get user language
1207
    include_once $SETTINGS['cpassman_dir'] . '/includes/language/' . $superGlobal->get('user_language', 'SESSION') . '.php';
1208
1209
    // Load library
1210
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1211
1212
    // load PHPMailer
1213
    $mail = new SplClassLoader('PHPMailer\PHPMailer', '../includes/libraries');
1214
    $mail->register();
1215
    $mail = new PHPMailer\PHPMailer\PHPMailer(true);
1216
    try {
1217
        // send to user
1218
        $mail->setLanguage('en', $SETTINGS['cpassman_dir'] . '/includes/libraries/PHPMailer/PHPMailer/language/');
1219
        $mail->SMTPDebug = 0; //value 1 can be used to debug - 4 for debuging connections
1220
        $mail->Port = $SETTINGS['email_port']; //COULD BE USED
1221
        $mail->CharSet = 'utf-8';
1222
        $mail->SMTPSecure = ($SETTINGS['email_security'] === 'tls'
1223
            || $SETTINGS['email_security'] === 'ssl') ? $SETTINGS['email_security'] : '';
1224
        $mail->SMTPAutoTLS = ($SETTINGS['email_security'] === 'tls'
1225
            || $SETTINGS['email_security'] === 'ssl') ? true : false;
1226
        $mail->SMTPOptions = array(
1227
            'ssl' => array(
1228
                'verify_peer' => false,
1229
                'verify_peer_name' => false,
1230
                'allow_self_signed' => true,
1231
            ),
1232
        );
1233
        $mail->isSmtp(); // send via SMTP
1234
        $mail->Host = $SETTINGS['email_smtp_server']; // SMTP servers
1235
        $mail->SMTPAuth = (int) $SETTINGS['email_smtp_auth'] === 1 ? true : false; // turn on SMTP authentication
1236
        $mail->Username = $SETTINGS['email_auth_username']; // SMTP username
1237
        $mail->Password = $SETTINGS['email_auth_pwd']; // SMTP password
1238
        $mail->From = $SETTINGS['email_from'];
1239
        $mail->FromName = $SETTINGS['email_from_name'];
1240
1241
        // Prepare for each person
1242
        foreach (array_filter(explode(',', $email)) as $dest) {
1243
            $mail->addAddress($dest);
1244
        }
1245
1246
        // Prepare HTML
1247
        $text_html = emailBody($textMail);
1248
1249
        $mail->WordWrap = 80; // set word wrap
1250
        $mail->isHtml(true); // send as HTML
1251
        $mail->Subject = $subject;
1252
        $mail->Body = $text_html;
1253
        $mail->AltBody = (is_null($textMailAlt) === false) ? $textMailAlt : '';
1254
        // send email
1255
        $mail->send();
1256
        $mail->smtpClose();
1257
        if ($silent === false) {
1258
            return json_encode(
1259
                array(
1260
                    'error' => false,
1261
                    'message' => langHdl('forgot_my_pw_email_sent'),
1262
                )
1263
            );
1264
        }
1265
    } catch (Exception $e) {
1266
        if ($silent === false) {
1267
            return json_encode(
1268
                array(
1269
                    'error' => true,
1270
                    'message' => str_replace(array("\n", "\t", "\r"), '', $mail->ErrorInfo),
1271
                )
1272
            );
1273
        }
1274
    }
1275
}
1276
1277
/**
1278
 * Returns the email body.
1279
 *
1280
 * @param string $textMail Text for the email
1281
 *
1282
 * @return string
1283
 */
1284
function emailBody($textMail)
1285
{
1286
    return '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.=
1287
    w3.org/TR/html4/loose.dtd"><html>
1288
    <head><title>Email Template</title>
1289
    <style type="text/css">
1290
    body { background-color: #f0f0f0; padding: 10px 0; margin:0 0 10px =0; }
1291
    </style></head>
1292
    <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">
1293
    <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" bgcolor="#f0f0f0" style="border-spacing: 0;">
1294
    <tr><td style="border-collapse: collapse;"><br>
1295
        <table border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="#17357c" style="border-spacing: 0; margin-bottom: 25px;">
1296
        <tr><td style="border-collapse: collapse; padding: 11px 20px;">
1297
            <div style="max-width:150px; max-height:34px; color:#f0f0f0; font-weight:bold;">Teampass</div>
1298
        </td></tr></table></td>
1299
    </tr>
1300
    <tr><td align="center" valign="top" bgcolor="#f0f0f0" style="border-collapse: collapse; background-color: #f0f0f0;">
1301
        <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;">
1302
        <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;">
1303
        <br><div style="float:right;">' .
1304
        $textMail .
1305
        '<br><br></td></tr></table>
1306
    </td></tr></table>
1307
    <br></body></html>';
1308
}
1309
1310
/**
1311
 * Generate a Key.
1312
 *
1313
 * @return string
1314
 */
1315
function generateKey()
1316
{
1317
    return substr(md5(rand() . rand()), 0, 15);
1318
}
1319
1320
/**
1321
 * Convert date to timestamp.
1322
 *
1323
 * @param string $date     The date
1324
 * @param array  $SETTINGS Teampass settings
1325
 *
1326
 * @return string
1327
 */
1328
function dateToStamp($date, $SETTINGS)
1329
{
1330
    $date = date_parse_from_format($SETTINGS['date_format'], $date);
1331
    if ((int) $date['warning_count'] === 0 && (int) $date['error_count'] === 0) {
1332
        return mktime(23, 59, 59, $date['month'], $date['day'], $date['year']);
1333
    } else {
1334
        return '';
1335
    }
1336
}
1337
1338
/**
1339
 * Is this a date.
1340
 *
1341
 * @param string $date Date
1342
 *
1343
 * @return bool
1344
 */
1345
function isDate($date)
1346
{
1347
    return strtotime($date) !== false;
1348
}
1349
1350
/**
1351
 * Check if isUTF8().
1352
 *
1353
 * @return int is the string in UTF8 format
1354
 */
1355
function isUTF8($string)
1356
{
1357
    if (is_array($string) === true) {
1358
        $string = $string['string'];
1359
    }
1360
1361
    return preg_match(
1362
        '%^(?:
1363
        [\x09\x0A\x0D\x20-\x7E] # ASCII
1364
        | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
1365
        | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
1366
        | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
1367
        | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
1368
        | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
1369
        | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
1370
        | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
1371
        )*$%xs',
1372
        $string
1373
    );
1374
}
1375
1376
/**
1377
 * Prepare an array to UTF8 format before JSON_encode.
1378
 *
1379
 * @param array $array Array of values
1380
 *
1381
 * @return array
1382
 */
1383
function utf8Converter($array)
1384
{
1385
    array_walk_recursive(
1386
        $array,
1387
        function (&$item) {
1388
            if (mb_detect_encoding($item, 'utf-8', true) === false) {
1389
                $item = utf8_encode($item);
1390
            }
1391
        }
1392
    );
1393
1394
    return $array;
1395
}
1396
1397
/**
1398
 * Permits to prepare data to be exchanged.
1399
 *
1400
 * @param array|string $data Text
1401
 * @param string       $type Parameter
1402
 * @param string       $key  Optional key
1403
 *
1404
 * @return resource|string|array
1405
 */
1406
function prepareExchangedData($data, $type, $key = null)
1407
{
1408
    if (file_exists('../includes/config/tp.config.php')) {
1409
        include '../includes/config/tp.config.php';
1410
    } elseif (file_exists('./includes/config/tp.config.php')) {
1411
        include './includes/config/tp.config.php';
1412
    } elseif (file_exists('../../includes/config/tp.config.php')) {
1413
        include '../../includes/config/tp.config.php';
1414
    } else {
1415
        throw new Exception("Error file '/includes/config/tp.config.php' not exists", 1);
1416
    }
1417
1418
    if (isset($SETTINGS) === false) {
1419
        return 'ERROR';
1420
    }
1421
1422
    // Load superglobal
1423
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1424
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1425
1426
    // Get superglobals
1427
    if ($key !== null) {
1428
        $superGlobal->put('key', $key, 'SESSION');
1429
        $globalsKey = $key;
1430
    } else {
1431
        $globalsKey = $superGlobal->get('key', 'SESSION');
1432
    }
1433
1434
    //load ClassLoader
1435
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1436
    //Load AES
1437
    $aes = new SplClassLoader('Encryption\Crypt', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1438
    $aes->register();
1439
1440
    if ($type === 'encode' && is_array($data) === true) {
1441
        // Ensure UTF8 format
1442
        $data = utf8Converter($data);
1443
        // Now encode
1444
        if (isset($SETTINGS['encryptClientServer'])
1445
            && $SETTINGS['encryptClientServer'] === '0'
1446
        ) {
1447
            return json_encode(
1448
                $data,
1449
                JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1450
            );
1451
        } else {
1452
            return Encryption\Crypt\aesctr::encrypt(
1453
                json_encode(
1454
                    $data,
1455
                    JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
1456
                ),
1457
                $globalsKey,
1458
                256
1459
            );
1460
        }
1461
    } elseif ($type === 'decode' && is_array($data) === false) {
1462
        if (isset($SETTINGS['encryptClientServer'])
1463
            && $SETTINGS['encryptClientServer'] === '0'
1464
        ) {
1465
            return json_decode(
1466
                /** @scrutinizer ignore-type */ (string) $data,
1467
                true
1468
            );
1469
        } else {
1470
            return json_decode(
1471
                Encryption\Crypt\aesctr::decrypt(
1472
                    /** @scrutinizer ignore-type */ (string) $data,
1473
                    $globalsKey,
1474
                    256
1475
                ),
1476
                true
1477
            );
1478
        }
1479
    }
1480
}
1481
1482
/**
1483
 * Create a thumbnail.
1484
 *
1485
 * @param string  $src           Source
1486
 * @param string  $dest          Destination
1487
 * @param integer $desired_width Size of width
1488
 */
1489
function makeThumbnail($src, $dest, $desired_width)
1490
{
1491
    /* read the source image */
1492
    if(is_file($src) === true && mime_content_type($src) === 'image/png'){
1493
        $source_image = imagecreatefrompng($src);
1494
        if ($source_image === false) {
1495
            return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1496
        }
1497
    } else {
1498
        return "Error: Not a valid PNG file! It's type is ".mime_content_type($src);
1499
    }
1500
    
1501
    // Get height and width
1502
    $width = imagesx($source_image);
1503
    $height = imagesy($source_image);
1504
1505
    /* find the "desired height" of this thumbnail, relative to the desired width  */
1506
    $desired_height = (int) floor($height * ($desired_width / $width));
1507
1508
    /* create a new, "virtual" image */
1509
    $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
1510
    if ($virtual_image === false) {
1511
        return false;
1512
    }
1513
    /* copy source image at a resized size */
1514
    imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
1515
1516
    /* create the physical thumbnail image to its destination */
1517
    imagejpeg($virtual_image, $dest);
1518
}
1519
1520
/**
1521
 * Check table prefix in SQL query.
1522
 *
1523
 * @param string $table Table name
1524
 *
1525
 * @return string
1526
 */
1527
function prefixTable($table)
1528
{
1529
    $safeTable = htmlspecialchars(DB_PREFIX . $table);
1530
    if (!empty($safeTable)) {
1531
        // sanitize string
1532
        return $safeTable;
1533
    } else {
1534
        // stop error no table
1535
        return 'table_not_exists';
1536
    }
1537
}
1538
1539
1540
/**
1541
 * GenerateCryptKey
1542
 *
1543
 * @param int     $size      Length
1544
 * @param boolean $secure    Secure
1545
 * @param boolean $numerals  Numerics
1546
 * @param boolean $uppercase Uppercase letters
1547
 * @param boolean $symbols   Symbols
1548
 * @param boolean $lowercase Lowercase
1549
 * @param array   $SETTINGS  SETTINGS
1550
 * @return string
1551
 */
1552
function GenerateCryptKey(
1553
    $size = 10,
1554
    $secure = false,
1555
    $numerals = false,
1556
    $uppercase = false,
1557
    $symbols = false,
1558
    $lowercase = false,
1559
    $SETTINGS = array()
1560
) {
1561
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1562
    $generator = new SplClassLoader('PasswordGenerator\Generator', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1563
    $generator->register();
1564
    $generator = new PasswordGenerator\Generator\ComputerPasswordGenerator();
1565
1566
    // Is PHP7 being used?
1567
    if (version_compare(PHP_VERSION, '7.0.0', '>=')) {
1568
        $php7generator = new SplClassLoader('PasswordGenerator\RandomGenerator', $SETTINGS['cpassman_dir'] . '/includes/libraries');
1569
        $php7generator->register();
1570
        $generator->setRandomGenerator(new PasswordGenerator\RandomGenerator\Php7RandomGenerator());
1571
    }
1572
1573
    // Manage size
1574
    $generator->setLength((int) $size);
1575
1576
    if ($secure === true) {
1577
        $generator->setSymbols(true);
1578
        $generator->setLowercase(true);
1579
        $generator->setUppercase(true);
1580
        $generator->setNumbers(true);
1581
    } else {
1582
        $generator->setLowercase($lowercase);
1583
        $generator->setUppercase($uppercase);
1584
        $generator->setNumbers($numerals);
1585
        $generator->setSymbols($symbols);
1586
    }
1587
1588
    return $generator->generatePasswords()[0];
1589
}
1590
1591
/*
1592
* Send sysLOG message
1593
* @param string $message
1594
* @param string $host
1595
*/
1596
function send_syslog($message, $host, $port, $component = 'teampass')
1597
{
1598
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1599
    $syslog_message = '<123>' . date('M d H:i:s ') . $component . ': ' . $message;
1600
    socket_sendto($sock, $syslog_message, strlen($syslog_message), 0, $host, $port);
1601
    socket_close($sock);
1602
}
1603
1604
/**
1605
 * Permits to log events into DB
1606
 *
1607
 * @param array  $SETTINGS Teampass settings
1608
 * @param string $type     Type
1609
 * @param string $label    Label
1610
 * @param string $who      Who
1611
 * @param string $login    Login
1612
 * @param string $field_1  Field
1613
 *
1614
 * @return void
1615
 */
1616
function logEvents($SETTINGS, $type, $label, $who, $login = null, $field_1 = null)
1617
{
1618
    if (empty($who)) {
1619
        $who = getClientIpServer();
1620
    }
1621
1622
    // include librairies & connect to DB
1623
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1624
    if (defined('DB_PASSWD_CLEAR') === false) {
1625
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1626
    }
1627
    DB::$host = DB_HOST;
1628
    DB::$user = DB_USER;
1629
    DB::$password = DB_PASSWD_CLEAR;
1630
    DB::$dbName = DB_NAME;
1631
    DB::$port = DB_PORT;
1632
    DB::$encoding = DB_ENCODING;
1633
1634
    DB::insert(
1635
        prefixTable('log_system'),
1636
        array(
1637
            'type' => $type,
1638
            'date' => time(),
1639
            'label' => $label,
1640
            'qui' => $who,
1641
            'field_1' => $field_1 === null ? '' : $field_1,
1642
        )
1643
    );
1644
1645
    // If SYSLOG
1646
    if (isset($SETTINGS['syslog_enable']) === true && (int) $SETTINGS['syslog_enable'] === 1) {
1647
        if ($type === 'user_mngt') {
1648
            send_syslog(
1649
                'action=' . str_replace('at_', '', $label) . ' attribute=user user=' . $who . ' userid="' . $login . '" change="' . $field_1 . '" ',
1650
                $SETTINGS['syslog_host'],
1651
                $SETTINGS['syslog_port'],
1652
                'teampass'
1653
            );
1654
        } else {
1655
            send_syslog(
1656
                'action=' . $type . ' attribute=' . $label . ' user=' . $who . ' userid="' . $login . '" ',
1657
                $SETTINGS['syslog_host'],
1658
                $SETTINGS['syslog_port'],
1659
                'teampass'
1660
            );
1661
        }
1662
    }
1663
}
1664
1665
/**
1666
 * Log events.
1667
 *
1668
 * @param array  $SETTINGS        Teampass settings
1669
 * @param int    $item_id         Item id
1670
 * @param string $item_label      Item label
1671
 * @param int    $id_user         User id
1672
 * @param string $action          Code for reason
1673
 * @param string $login           User login
1674
 * @param string $raison          Code for reason
1675
 * @param string $encryption_type Encryption on
1676
 */
1677
function logItems(
1678
    $SETTINGS,
1679
    $item_id,
1680
    $item_label,
1681
    $id_user,
1682
    $action,
1683
    $login = null,
1684
    $raison = null,
1685
    $encryption_type = null
1686
) {
1687
    // include librairies & connect to DB
1688
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1689
    if (defined('DB_PASSWD_CLEAR') === false) {
1690
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1691
    }
1692
    DB::$host = DB_HOST;
1693
    DB::$user = DB_USER;
1694
    DB::$password = DB_PASSWD_CLEAR;
1695
    DB::$dbName = DB_NAME;
1696
    DB::$port = DB_PORT;
1697
    DB::$encoding = DB_ENCODING;
1698
1699
    // Insert log in DB
1700
    DB::insert(
1701
        prefixTable('log_items'),
1702
        array(
1703
            'id_item' => $item_id,
1704
            'date' => time(),
1705
            'id_user' => $id_user,
1706
            'action' => $action,
1707
            'raison' => $raison,
1708
            'raison_iv' => '',
1709
            'encryption_type' => is_null($encryption_type) === true ? TP_ENCRYPTION_NAME : $encryption_type,
1710
        )
1711
    );
1712
    // Timestamp the last change
1713
    if ($action === 'at_creation' || $action === 'at_modifiation' || $action === 'at_delete' || $action === 'at_import') {
1714
        DB::update(
1715
            prefixTable('misc'),
1716
            array(
1717
                'valeur' => time(),
1718
            ),
1719
            'type = %s AND intitule = %s',
1720
            'timestamp',
1721
            'last_item_change'
1722
        );
1723
    }
1724
1725
    // SYSLOG
1726
    if (isset($SETTINGS['syslog_enable']) === true && $SETTINGS['syslog_enable'] === '1') {
1727
        // Extract reason
1728
        $attribute = is_null($raison) === true ? '' : explode(' : ', $raison);
1729
1730
        // Get item info if not known
1731
        if (empty($item_label) === true) {
1732
            $dataItem = DB::queryfirstrow(
1733
                'SELECT id, id_tree, label
1734
                FROM ' . prefixTable('items') . '
1735
                WHERE id = %i',
1736
                $item_id
1737
            );
1738
1739
            $item_label = $dataItem['label'];
1740
        }
1741
1742
        send_syslog(
1743
            'action=' . str_replace('at_', '', $action) .
1744
                ' attribute=' . str_replace('at_', '', $attribute[0]) .
1745
                ' itemno=' . $item_id .
1746
                ' user=' . is_null($login) === true ? '' : addslashes((string) $login) .
1747
                ' itemname="' . addslashes($item_label) . '"',
1748
            $SETTINGS['syslog_host'],
1749
            $SETTINGS['syslog_port'],
1750
            'teampass'
1751
        );
1752
    }
1753
1754
    // send notification if enabled
1755
    notifyOnChange($item_id, $action, $SETTINGS);
1756
}
1757
1758
/**
1759
 * If enabled, then notify admin/manager.
1760
 *
1761
 * @param int    $item_id  Item id
1762
 * @param string $action   Action to do
1763
 * @param array  $SETTINGS Teampass settings
1764
 */
1765
function notifyOnChange($item_id, $action, $SETTINGS)
1766
{
1767
    if (
1768
        isset($SETTINGS['enable_email_notification_on_item_shown']) === true
1769
        && (int) $SETTINGS['enable_email_notification_on_item_shown'] === 1
1770
        && $action === 'at_shown'
1771
    ) {
1772
        // Load superglobal
1773
        include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1774
        $superGlobal = new protect\SuperGlobal\SuperGlobal();
1775
1776
        // Get superglobals
1777
        $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1778
        $globalsName = $superGlobal->get('name', 'SESSION');
1779
        $globalsNotifiedEmails = $superGlobal->get('listNotificationEmails', 'SESSION');
1780
1781
        // Get info about item
1782
        $dataItem = DB::queryfirstrow(
1783
            'SELECT id, id_tree, label
1784
            FROM ' . prefixTable('items') . '
1785
            WHERE id = %i',
1786
            $item_id
1787
        );
1788
        $item_label = $dataItem['label'];
1789
1790
        // send back infos
1791
        DB::insert(
1792
            prefixTable('emails'),
1793
            array(
1794
                'timestamp' => time(),
1795
                'subject' => langHdl('email_on_open_notification_subject'),
1796
                'body' => str_replace(
1797
                    array('#tp_user#', '#tp_item#', '#tp_link#'),
1798
                    array(
1799
                        addslashes($globalsName . ' ' . $globalsLastname),
1800
                        addslashes($item_label),
1801
                        $SETTINGS['cpassman_url'] . '/index.php?page=items&group=' . $dataItem['id_tree'] . '&id=' . $item_id,
1802
                    ),
1803
                    langHdl('email_on_open_notification_mail')
1804
                ),
1805
                'receivers' => $globalsNotifiedEmails,
1806
                'status' => '',
1807
            )
1808
        );
1809
    }
1810
}
1811
1812
/**
1813
 * Prepare notification email to subscribers.
1814
 *
1815
 * @param int    $item_id  Item id
1816
 * @param string $label    Item label
1817
 * @param array  $changes  List of changes
1818
 * @param array  $SETTINGS Teampass settings
1819
 */
1820
function notifyChangesToSubscribers($item_id, $label, $changes, $SETTINGS)
1821
{
1822
    // Load superglobal
1823
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
1824
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
1825
1826
    // Get superglobals
1827
    $globalsUserId = $superGlobal->get('user_id', 'SESSION');
1828
    $globalsLastname = $superGlobal->get('lastname', 'SESSION');
1829
    $globalsName = $superGlobal->get('name', 'SESSION');
1830
1831
    // send email to user that what to be notified
1832
    $notification = DB::queryOneColumn(
1833
        'email',
1834
        'SELECT *
1835
        FROM ' . prefixTable('notification') . ' AS n
1836
        INNER JOIN ' . prefixTable('users') . ' AS u ON (n.user_id = u.id)
1837
        WHERE n.item_id = %i AND n.user_id != %i',
1838
        $item_id,
1839
        $globalsUserId
1840
    );
1841
1842
    if (DB::count() > 0) {
1843
        // Prepare path
1844
        $path = geItemReadablePath($item_id, '', $SETTINGS);
1845
1846
        // Get list of changes
1847
        $htmlChanges = '<ul>';
1848
        foreach ($changes as $change) {
1849
            $htmlChanges .= '<li>' . $change . '</li>';
1850
        }
1851
        $htmlChanges .= '</ul>';
1852
1853
        // send email
1854
        DB::insert(
1855
            prefixTable('emails'),
1856
            array(
1857
                'timestamp' => time(),
1858
                'subject' => langHdl('email_subject_item_updated'),
1859
                'body' => str_replace(
1860
                    array('#item_label#', '#folder_name#', '#item_id#', '#url#', '#name#', '#lastname#', '#changes#'),
1861
                    array($label, $path, $item_id, $SETTINGS['cpassman_url'], $globalsName, $globalsLastname, $htmlChanges),
1862
                    langHdl('email_body_item_updated')
1863
                ),
1864
                'receivers' => implode(',', $notification),
1865
                'status' => '',
1866
            )
1867
        );
1868
    }
1869
}
1870
1871
/**
1872
 * Returns the Item + path.
1873
 *
1874
 * @param int    $id_tree  Node id
1875
 * @param string $label    Label
1876
 * @param array  $SETTINGS TP settings
1877
 *
1878
 * @return string
1879
 */
1880
function geItemReadablePath($id_tree, $label, $SETTINGS)
1881
{
1882
    // Class loader
1883
    include_once $SETTINGS['cpassman_dir'] . '/sources/SplClassLoader.php';
1884
1885
    //Load Tree
1886
    $tree = new SplClassLoader('Tree\NestedTree', '../includes/libraries');
1887
    $tree->register();
1888
    $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
1889
1890
    $arbo = $tree->getPath($id_tree, true);
1891
    $path = '';
1892
    foreach ($arbo as $elem) {
1893
        if (empty($path) === true) {
1894
            $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES) . ' ';
1895
        } else {
1896
            $path .= '&#8594; ' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
1897
        }
1898
    }
1899
1900
    // Build text to show user
1901
    if (empty($label) === false) {
1902
        return empty($path) === true ? addslashes($label) : addslashes($label) . ' (' . $path . ')';
1903
    } else {
1904
        return empty($path) === true ? '' : $path;
1905
    }
1906
}
1907
1908
/**
1909
 * Get the client ip address.
1910
 *
1911
 * @return string IP address
1912
 */
1913
function getClientIpServer()
1914
{
1915
    if (getenv('HTTP_CLIENT_IP')) {
1916
        $ipaddress = getenv('HTTP_CLIENT_IP');
1917
    } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
1918
        $ipaddress = getenv('HTTP_X_FORWARDED_FOR');
1919
    } elseif (getenv('HTTP_X_FORWARDED')) {
1920
        $ipaddress = getenv('HTTP_X_FORWARDED');
1921
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
1922
        $ipaddress = getenv('HTTP_FORWARDED_FOR');
1923
    } elseif (getenv('HTTP_FORWARDED')) {
1924
        $ipaddress = getenv('HTTP_FORWARDED');
1925
    } elseif (getenv('REMOTE_ADDR')) {
1926
        $ipaddress = getenv('REMOTE_ADDR');
1927
    } else {
1928
        $ipaddress = 'UNKNOWN';
1929
    }
1930
1931
    return $ipaddress;
1932
}
1933
1934
/**
1935
 * Escape all HTML, JavaScript, and CSS.
1936
 *
1937
 * @param string $input    The input string
1938
 * @param string $encoding Which character encoding are we using?
1939
 *
1940
 * @return string
1941
 */
1942
function noHTML($input, $encoding = 'UTF-8')
1943
{
1944
    return htmlspecialchars($input, ENT_QUOTES | ENT_XHTML, $encoding, false);
1945
}
1946
1947
/**
1948
 * 
1949
 * Permits to handle the Teampass config file
1950
 * $action accepts "rebuild" and "update"
1951
 *
1952
 * @param string $action   Action to perform
1953
 * @param array  $SETTINGS Teampass settings
1954
 * @param string $field    Field to refresh
1955
 * @param string $value    Value to set
1956
 *
1957
 * @return string|boolean
1958
 */
1959
function handleConfigFile($action, $SETTINGS, $field = null, $value = null)
1960
{
1961
    $tp_config_file = $SETTINGS['cpassman_dir'] . '/includes/config/tp.config.php';
1962
1963
    // include librairies & connect to DB
1964
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
1965
    if (defined('DB_PASSWD_CLEAR') === false) {
1966
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
1967
    }
1968
    DB::$host = DB_HOST;
1969
    DB::$user = DB_USER;
1970
    DB::$password = DB_PASSWD_CLEAR;
1971
    DB::$dbName = DB_NAME;
1972
    DB::$port = DB_PORT;
1973
    DB::$encoding = DB_ENCODING;
1974
1975
    if (file_exists($tp_config_file) === false || $action === 'rebuild') {
1976
        // perform a copy
1977
        if (file_exists($tp_config_file)) {
1978
            if (!copy($tp_config_file, $tp_config_file . '.' . date('Y_m_d_His', time()))) {
1979
                return "ERROR: Could not copy file '" . $tp_config_file . "'";
1980
            }
1981
        }
1982
1983
1984
        // regenerate
1985
        $data = array();
1986
        $data[0] = "<?php\n";
1987
        $data[1] = "global \$SETTINGS;\n";
1988
        $data[2] = "\$SETTINGS = array (\n";
1989
        $rows = DB::query(
1990
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s',
1991
            'admin'
1992
        );
1993
        foreach ($rows as $record) {
1994
            array_push($data, "    '" . $record['intitule'] . "' => '" . $record['valeur'] . "',\n");
1995
        }
1996
        array_push($data, ");\n");
1997
        $data = array_unique($data);
1998
        // ---
1999
    } elseif ($action === 'update' && empty($field) === false) {
2000
        $data = file($tp_config_file);
2001
        $inc = 0;
2002
        $bFound = false;
2003
        foreach ($data as $line) {
2004
            if (stristr($line, ');')) {
2005
                break;
2006
            }
2007
2008
            if (stristr($line, "'" . $field . "' => '")) {
2009
                $data[$inc] = "    '" . $field . "' => '" . filter_var($value, FILTER_SANITIZE_STRING) . "',\n";
2010
                $bFound = true;
2011
                break;
2012
            }
2013
            ++$inc;
2014
        }
2015
        if ($bFound === false) {
2016
            $data[($inc)] = "    '" . $field . "' => '" . filter_var($value, FILTER_SANITIZE_STRING) . "',\n);\n";
2017
        }
2018
    }
2019
2020
    // update file
2021
    file_put_contents($tp_config_file, implode('', isset($data) ? $data : array()));
2022
2023
    return true;
2024
}
2025
2026
2027
/**
2028
 * Permits to replace &#92; to permit correct display
2029
 *
2030
 * @param string $input Some text
2031
 *
2032
 * @return string
2033
 */
2034
function handleBackslash($input)
2035
{
2036
    return str_replace('&amp;#92;', '&#92;', $input);
2037
}
2038
2039
/*
2040
** Permits to loas settings
2041
*/
2042
function loadSettings()
2043
{
2044
    global $SETTINGS;
2045
2046
    /* LOAD CPASSMAN SETTINGS */
2047
    if (!isset($SETTINGS['loaded']) || $SETTINGS['loaded'] != 1) {
2048
        $SETTINGS['duplicate_folder'] = 0; //by default, this is set to 0;
2049
        $SETTINGS['duplicate_item'] = 0; //by default, this is set to 0;
2050
        $SETTINGS['number_of_used_pw'] = 5; //by default, this value is set to 5;
2051
        $settings = array();
2052
2053
        $rows = DB::query(
2054
            'SELECT * FROM ' . prefixTable('misc') . ' WHERE type=%s_type OR type=%s_type2',
2055
            array(
2056
                'type' => 'admin',
2057
                'type2' => 'settings',
2058
            )
2059
        );
2060
        foreach ($rows as $record) {
2061
            if ($record['type'] === 'admin') {
2062
                $SETTINGS[$record['intitule']] = $record['valeur'];
2063
            } else {
2064
                $settings[$record['intitule']] = $record['valeur'];
2065
            }
2066
        }
2067
        $SETTINGS['loaded'] = 1;
2068
        $SETTINGS['default_session_expiration_time'] = 5;
2069
    }
2070
}
2071
2072
/*
2073
** check if folder has custom fields.
2074
** Ensure that target one also has same custom fields
2075
*/
2076
function checkCFconsistency($source_id, $target_id)
2077
{
2078
    $source_cf = array();
2079
    $rows = DB::QUERY(
2080
        'SELECT id_category
2081
        FROM ' . prefixTable('categories_folders') . '
2082
        WHERE id_folder = %i',
2083
        $source_id
2084
    );
2085
    foreach ($rows as $record) {
2086
        array_push($source_cf, $record['id_category']);
2087
    }
2088
2089
    $target_cf = array();
2090
    $rows = DB::QUERY(
2091
        'SELECT id_category
2092
        FROM ' . prefixTable('categories_folders') . '
2093
        WHERE id_folder = %i',
2094
        $target_id
2095
    );
2096
    foreach ($rows as $record) {
2097
        array_push($target_cf, $record['id_category']);
2098
    }
2099
2100
    $cf_diff = array_diff($source_cf, $target_cf);
2101
    if (count($cf_diff) > 0) {
2102
        return false;
2103
    }
2104
2105
    return true;
2106
}
2107
2108
/**
2109
 * Will encrypte/decrypt a fil eusing Defuse.
2110
 *
2111
 * @param string $type        can be either encrypt or decrypt
2112
 * @param string $source_file path to source file
2113
 * @param string $target_file path to target file
2114
 * @param array  $SETTINGS    Settings
2115
 * @param string $password    A password
2116
 *
2117
 * @return string|boolean
2118
 */
2119
function prepareFileWithDefuse(
2120
    $type,
2121
    $source_file,
2122
    $target_file,
2123
    $SETTINGS,
2124
    $password = null
2125
) {
2126
    // Load AntiXSS
2127
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/AntiXSS.php';
2128
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/ASCII.php';
2129
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/UTF8.php';
2130
    $antiXss = new voku\helper\AntiXSS();
2131
2132
    // Protect against bad inputs
2133
    if (is_array($source_file) === true || is_array($target_file) === true) {
2134
        return 'error_cannot_be_array';
2135
    }
2136
2137
    // Sanitize
2138
    $source_file = $antiXss->xss_clean($source_file);
2139
    $target_file = $antiXss->xss_clean($target_file);
2140
2141
    if (empty($password) === true || is_null($password) === true) {
2142
        // get KEY to define password
2143
        $ascii_key = file_get_contents(SECUREPATH . '/teampass-seckey.txt');
2144
        $password = \Defuse\Crypto\Key::loadFromAsciiSafeString($ascii_key);
2145
    }
2146
2147
    $err = '';
2148
    if ($type === 'decrypt') {
2149
        // Decrypt file
2150
        $err = defuseFileDecrypt(
2151
            $source_file,
2152
            $target_file,
2153
            $SETTINGS,
2154
            /** @scrutinizer ignore-type */ $password
2155
        );
2156
        // ---
2157
    } elseif ($type === 'encrypt') {
2158
        // Encrypt file
2159
        $err = defuseFileEncrypt(
2160
            $source_file,
2161
            $target_file,
2162
            $SETTINGS,
2163
            /** @scrutinizer ignore-type */ $password
2164
        );
2165
    }
2166
2167
    // return error
2168
    return empty($err) === false ? $err : "";
2169
}
2170
2171
/**
2172
 * Encrypt a file with Defuse.
2173
 *
2174
 * @param string $source_file path to source file
2175
 * @param string $target_file path to target file
2176
 * @param array  $SETTINGS    Settings
2177
 * @param string $password    A password
2178
 *
2179
 * @return string|bool
2180
 */
2181
function defuseFileEncrypt(
2182
    $source_file,
2183
    $target_file,
2184
    $SETTINGS,
2185
    $password = null
2186
) {
2187
    // load PhpEncryption library
2188
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2189
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2190
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2191
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2192
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2193
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2194
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2195
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2196
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2197
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2198
2199
    try {
2200
        \Defuse\Crypto\File::encryptFileWithPassword(
2201
            $source_file,
2202
            $target_file,
2203
            $password
2204
        );
2205
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2206
        $err = 'wrong_key';
2207
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2208
        $err = $ex;
2209
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2210
        $err = $ex;
2211
    }
2212
2213
    // return error
2214
    return empty($err) === false ? $err : true;
2215
}
2216
2217
/**
2218
 * Decrypt a file with Defuse.
2219
 *
2220
 * @param string $source_file path to source file
2221
 * @param string $target_file path to target file
2222
 * @param array  $SETTINGS    Settings
2223
 * @param string $password    A password
2224
 *
2225
 * @return string|bool
2226
 */
2227
function defuseFileDecrypt(
2228
    $source_file,
2229
    $target_file,
2230
    $SETTINGS,
2231
    $password = null
2232
) {
2233
    // load PhpEncryption library
2234
    $path_to_encryption = '/includes/libraries/Encryption/Encryption/';
2235
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Crypto.php';
2236
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Encoding.php';
2237
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'DerivedKeys.php';
2238
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Key.php';
2239
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyOrPassword.php';
2240
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'File.php';
2241
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'RuntimeTests.php';
2242
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'KeyProtectedByPassword.php';
2243
    include_once $SETTINGS['cpassman_dir'] . $path_to_encryption . 'Core.php';
2244
2245
    try {
2246
        \Defuse\Crypto\File::decryptFileWithPassword(
2247
            $source_file,
2248
            $target_file,
2249
            $password
2250
        );
2251
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
2252
        $err = 'wrong_key';
2253
    } catch (Defuse\Crypto\Exception\EnvironmentIsBrokenException $ex) {
2254
        $err = $ex;
2255
    } catch (Defuse\Crypto\Exception\IOException $ex) {
2256
        $err = $ex;
2257
    }
2258
2259
    // return error
2260
    return empty($err) === false ? $err : true;
2261
}
2262
2263
/*
2264
* NOT TO BE USED
2265
*/
2266
/**
2267
 * Undocumented function.
2268
 *
2269
 * @param string $text Text to debug
2270
 */
2271
function debugTeampass($text)
2272
{
2273
    $debugFile = fopen('D:/wamp64/www/TeamPass/debug.txt', 'r+');
2274
    if ($debugFile !== false) {
2275
        fputs($debugFile, $text);
2276
        fclose($debugFile);
2277
    }    
2278
}
2279
2280
/**
2281
 * DELETE the file with expected command depending on server type.
2282
 *
2283
 * @param string $file     Path to file
2284
 * @param array  $SETTINGS Teampass settings
2285
 */
2286
function fileDelete($file, $SETTINGS)
2287
{
2288
    // Load AntiXSS
2289
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/ASCII.php';
2290
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/UTF8.php';
2291
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/voku/helper/AntiXSS.php';
2292
    $antiXss = new voku\helper\AntiXSS();
2293
2294
    $file = $antiXss->xss_clean($file);
2295
    if (is_file($file)) {
2296
        unlink($file);
2297
    }
2298
}
2299
2300
/**
2301
 * Permits to extract the file extension.
2302
 *
2303
 * @param string $file File name
2304
 *
2305
 * @return string
2306
 */
2307
function getFileExtension($file)
2308
{
2309
    if (strpos($file, '.') === false) {
2310
        return $file;
2311
    }
2312
2313
    return substr($file, strrpos($file, '.') + 1);
2314
}
2315
2316
/**
2317
*    Chmods files and folders with different permissions.
2318
*
2319
*    This is an all-PHP alternative to using: \n
2320
*    <tt>exec("find ".$path." -type f -exec chmod 644 {} \;");</tt> \n
2321
*    <tt>exec("find ".$path." -type d -exec chmod 755 {} \;");</tt>
2322
*
2323
*    @author Jeppe Toustrup (tenzer at tenzer dot dk)
2324
*    @param $path An either relative or absolute path to a file or directory
2325
*    which should be processed.
2326
*    @param $filePerm The permissions any found files should get.
0 ignored issues
show
Bug introduced by
The type The was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
2327
*    @param $dirPerm The permissions any found folder should get.
2328
*    @return Returns TRUE if the path if found and FALSE if not.
0 ignored issues
show
Bug introduced by
The type Returns was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
2329
*    @warning The permission levels has to be entered in octal format, which
2330
*    normally means adding a zero ("0") in front of the permission level. \n
2331
*    More info at: http://php.net/chmod.
2332
*/
2333
2334
function recursiveChmod($path, $filePerm=0644, $dirPerm=0755) {
2335
    // Check if the path exists
2336
    if (!file_exists($path)) {
2337
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type Returns.
Loading history...
2338
    }
2339
2340
    // See whether this is a file
2341
    if (is_file($path)) {
2342
        // Chmod the file with our given filepermissions
2343
        chmod($path, $filePerm);
2344
2345
    // If this is a directory...
2346
    } elseif (is_dir($path)) {
2347
        // Then get an array of the contents
2348
        $foldersAndFiles = scandir($path);
2349
2350
        // Remove "." and ".." from the list
2351
        $entries = array_slice($foldersAndFiles, 2);
2352
2353
        // Parse every result...
2354
        foreach ($entries as $entry) {
2355
            // And call this function again recursively, with the same permissions
2356
            recursiveChmod($path."/".$entry, $filePerm, $dirPerm);
2357
        }
2358
2359
        // When we are done with the contents of the directory, we chmod the directory itself
2360
        chmod($path, $dirPerm);
2361
    }
2362
2363
    // Everything seemed to work out well, return true
2364
    return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the documented return type Returns.
Loading history...
2365
}
2366
2367
/**
2368
 * Check if user can access to this item.
2369
 *
2370
 * @param int $item_id ID of item
2371
 */
2372
function accessToItemIsGranted($item_id, $SETTINGS)
2373
{
2374
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2375
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2376
2377
    // Prepare superGlobal variables
2378
    $session_groupes_visibles = $superGlobal->get('groupes_visibles', 'SESSION');
2379
    $session_list_restricted_folders_for_items = $superGlobal->get('list_restricted_folders_for_items', 'SESSION');
2380
2381
    // Load item data
2382
    $data = DB::queryFirstRow(
2383
        'SELECT id_tree
2384
        FROM ' . prefixTable('items') . '
2385
        WHERE id = %i',
2386
        $item_id
2387
    );
2388
2389
    // Check if user can access this folder
2390
    if (in_array($data['id_tree'], $session_groupes_visibles) === false) {
2391
        // Now check if this folder is restricted to user
2392
        if (isset($session_list_restricted_folders_for_items[$data['id_tree']]) === true
2393
            && in_array($item_id, $session_list_restricted_folders_for_items[$data['id_tree']]) === false
2394
        ) {
2395
            return 'ERR_FOLDER_NOT_ALLOWED';
2396
        }
2397
    }
2398
2399
    return true;
2400
}
2401
2402
/**
2403
 * Creates a unique key.
2404
 *
2405
 * @param integer $lenght Key lenght
2406
 *
2407
 * @return string
2408
 */
2409
function uniqidReal($lenght = 13)
2410
{
2411
    if (function_exists('random_bytes')) {
2412
        $bytes = random_bytes(intval(ceil($lenght / 2)));
2413
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
2414
        $bytes = openssl_random_pseudo_bytes(intval(ceil($lenght / 2)));
2415
    } else {
2416
        throw new Exception('no cryptographically secure random function available');
2417
    }
2418
2419
    return substr(bin2hex($bytes), 0, $lenght);
2420
}
2421
2422
/**
2423
 * Obfuscate an email.
2424
 *
2425
 * @param string $email Email address
2426
 *
2427
 * @return string
2428
 */
2429
function obfuscateEmail($email)
2430
{
2431
    $prop = 2;
2432
    $start = '';
2433
    $end = '';
2434
    $domain = substr(strrchr($email, '@'), 1);
2435
    $mailname = str_replace($domain, '', $email);
2436
    $name_l = strlen($mailname);
2437
    $domain_l = strlen($domain);
2438
    for ($i = 0; $i <= $name_l / $prop - 1; ++$i) {
2439
        $start .= 'x';
2440
    }
2441
2442
    for ($i = 0; $i <= $domain_l / $prop - 1; ++$i) {
2443
        $end .= 'x';
2444
    }
2445
2446
    return (string) substr_replace($mailname, $start, 2, $name_l / $prop)
2447
        . (string) substr_replace($domain, $end, 2, $domain_l / $prop);
2448
}
2449
2450
2451
2452
2453
2454
/**
2455
 * Perform a Query.
2456
 *
2457
 * @param array  $SETTINGS Teamapss settings
2458
 * @param string $fields   Fields to use
2459
 * @param string $table    Table to use
2460
 *
2461
 * @return array
2462
 */
2463
function performDBQuery($SETTINGS, $fields, $table)
2464
{
2465
    // include librairies & connect to DB
2466
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2467
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2468
    if (defined('DB_PASSWD_CLEAR') === false) {
2469
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2470
    }
2471
    DB::$host = DB_HOST;
2472
    DB::$user = DB_USER;
2473
    DB::$password = DB_PASSWD_CLEAR;
2474
    DB::$dbName = DB_NAME;
2475
    DB::$port = DB_PORT;
2476
    DB::$encoding = DB_ENCODING;
2477
2478
    // Insert log in DB
2479
    return DB::query(
2480
        'SELECT ' . $fields . '
2481
        FROM ' . prefixTable($table)
2482
    );
2483
}
2484
2485
/**
2486
 * Undocumented function.
2487
 *
2488
 * @param int $bytes Size of file
2489
 *
2490
 * @return string
2491
 */
2492
function formatSizeUnits($bytes)
2493
{
2494
    if ($bytes >= 1073741824) {
2495
        $bytes = number_format($bytes / 1073741824, 2) . ' GB';
2496
    } elseif ($bytes >= 1048576) {
2497
        $bytes = number_format($bytes / 1048576, 2) . ' MB';
2498
    } elseif ($bytes >= 1024) {
2499
        $bytes = number_format($bytes / 1024, 2) . ' KB';
2500
    } elseif ($bytes > 1) {
2501
        $bytes = $bytes . ' bytes';
2502
    } elseif ($bytes == 1) {
2503
        $bytes = $bytes . ' byte';
2504
    } else {
2505
        $bytes = '0 bytes';
2506
    }
2507
2508
    return $bytes;
2509
}
2510
2511
/**
2512
 * Generate user pair of keys.
2513
 *
2514
 * @param string $userPwd User password
2515
 *
2516
 * @return array
2517
 */
2518
function generateUserKeys($userPwd)
2519
{
2520
    // include library
2521
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2522
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2523
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2524
2525
    // Load classes
2526
    $rsa = new Crypt_RSA();
2527
    $cipher = new Crypt_AES();
2528
2529
    // Create the private and public key
2530
    $res = $rsa->createKey(4096);
2531
2532
    // Encrypt the privatekey
2533
    $cipher->setPassword($userPwd);
2534
    $privatekey = $cipher->encrypt($res['privatekey']);
2535
2536
    return array(
2537
        'private_key' => base64_encode($privatekey),
2538
        'public_key' => base64_encode($res['publickey']),
2539
        'private_key_clear' => base64_encode($res['privatekey']),
2540
    );
2541
}
2542
2543
/**
2544
 * Permits to decrypt the user's privatekey.
2545
 *
2546
 * @param string $userPwd        User password
2547
 * @param string $userPrivateKey User private key
2548
 *
2549
 * @return string
2550
 */
2551
function decryptPrivateKey($userPwd, $userPrivateKey)
2552
{
2553
    if (empty($userPwd) === false) {
2554
        include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2555
2556
        // Load classes
2557
        $cipher = new Crypt_AES();
2558
2559
        // Encrypt the privatekey
2560
        $cipher->setPassword($userPwd);
2561
2562
        return base64_encode($cipher->decrypt(base64_decode($userPrivateKey)));
2563
    }
2564
}
2565
2566
/**
2567
 * Permits to encrypt the user's privatekey.
2568
 *
2569
 * @param string $userPwd        User password
2570
 * @param string $userPrivateKey User private key
2571
 *
2572
 * @return string
2573
 */
2574
function encryptPrivateKey($userPwd, $userPrivateKey)
2575
{
2576
    if (empty($userPwd) === false) {
2577
        include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2578
2579
        // Load classes
2580
        $cipher = new Crypt_AES();
2581
2582
        // Encrypt the privatekey
2583
        $cipher->setPassword($userPwd);
2584
2585
        return base64_encode($cipher->encrypt(base64_decode($userPrivateKey)));
2586
    }
2587
}
2588
2589
2590
/**
2591
 * Encrypts a string using AES.
2592
 *
2593
 * @param string $data String to encrypt
2594
 *
2595
 * @return array
2596
 */
2597
function doDataEncryption($data)
2598
{
2599
    // Includes
2600
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2601
2602
    // Load classes
2603
    $cipher = new Crypt_AES(CRYPT_AES_MODE_CBC);
2604
2605
    // Generate an object key
2606
    $objectKey = uniqidReal(32);
2607
2608
    // Set it as password
2609
    $cipher->setPassword($objectKey);
2610
2611
    return array(
2612
        'encrypted' => base64_encode($cipher->encrypt($data)),
2613
        'objectKey' => base64_encode($objectKey),
2614
    );
2615
}
2616
2617
/**
2618
 * Decrypts a string using AES.
2619
 *
2620
 * @param string $data Encrypted data
2621
 * @param string $key  Key to uncrypt
2622
 *
2623
 * @return string
2624
 */
2625
function doDataDecryption($data, $key)
2626
{
2627
    // Includes
2628
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2629
2630
    // Load classes
2631
    $cipher = new Crypt_AES();
2632
2633
    // Set the object key
2634
    $cipher->setPassword(base64_decode($key));
2635
2636
    return base64_encode($cipher->decrypt(base64_decode($data)));
2637
}
2638
2639
/**
2640
 * Encrypts using RSA a string using a public key.
2641
 *
2642
 * @param string $key       Key to be encrypted
2643
 * @param string $publicKey User public key
2644
 *
2645
 * @return string
2646
 */
2647
function encryptUserObjectKey($key, $publicKey)
2648
{
2649
    // Includes
2650
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2651
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2652
2653
    // Load classes
2654
    $rsa = new Crypt_RSA();
2655
    $rsa->loadKey(base64_decode($publicKey));
2656
2657
    // Encrypt
2658
    return base64_encode($rsa->encrypt(base64_decode($key)));
2659
}
2660
2661
/**
2662
 * Decrypts using RSA an encrypted string using a private key.
2663
 *
2664
 * @param string $key        Encrypted key
2665
 * @param string $privateKey User private key
2666
 *
2667
 * @return string
2668
 */
2669
function decryptUserObjectKey($key, $privateKey)
2670
{
2671
    // Includes
2672
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2673
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2674
2675
    // Load classes
2676
    $rsa = new Crypt_RSA();
2677
    $rsa->loadKey(base64_decode($privateKey));
2678
    
2679
    
2680
    // Decrypt
2681
    try {
2682
        $ret = base64_encode($rsa->decrypt(base64_decode($key)));
2683
    } catch(Exception $e) {
2684
        return $e;
2685
    }
2686
2687
    return $ret;
2688
}
2689
2690
/**
2691
 * Encrypts a file.
2692
 *
2693
 * @param string $fileInName File name
2694
 * @param string $fileInPath Path to file
2695
 *
2696
 * @return array
2697
 */
2698
function encryptFile($fileInName, $fileInPath)
2699
{
2700
    if (defined('FILE_BUFFER_SIZE') === false) {
2701
        define('FILE_BUFFER_SIZE', 128 * 1024);
2702
    }
2703
2704
    // Includes
2705
    include_once '../includes/config/include.php';
2706
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2707
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2708
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2709
2710
    // Load classes
2711
    $cipher = new Crypt_AES();
2712
2713
    // Generate an object key
2714
    $objectKey = uniqidReal(32);
2715
2716
    // Set it as password
2717
    $cipher->setPassword($objectKey);
2718
2719
    // Prevent against out of memory
2720
    $cipher->enableContinuousBuffer();
2721
    //$cipher->disablePadding();
2722
2723
    // Encrypt the file content
2724
    $plaintext = file_get_contents(
2725
        filter_var($fileInPath . '/' . $fileInName, FILTER_SANITIZE_URL)
2726
    );
2727
2728
    $ciphertext = $cipher->encrypt($plaintext);
2729
2730
    // Save new file
2731
    $hash = md5($plaintext);
2732
    $fileOut = $fileInPath . '/' . TP_FILE_PREFIX . $hash;
2733
    file_put_contents($fileOut, $ciphertext);
2734
    unlink($fileInPath . '/' . $fileInName);
2735
2736
    return array(
2737
        'fileHash' => base64_encode($hash),
2738
        'objectKey' => base64_encode($objectKey),
2739
    );
2740
}
2741
2742
/**
2743
 * Decrypt a file.
2744
 *
2745
 * @param string $fileName File name
2746
 * @param string $filePath Path to file
2747
 * @param string $key      Key to use
2748
 *
2749
 * @return string
2750
 */
2751
function decryptFile($fileName, $filePath, $key)
2752
{
2753
    if (!defined('FILE_BUFFER_SIZE')) {
2754
        define('FILE_BUFFER_SIZE', 128 * 1024);
2755
    }
2756
2757
    // Includes
2758
    include_once '../includes/config/include.php';
2759
    include_once '../includes/libraries/Encryption/phpseclib/Math/BigInteger.php';
2760
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/RSA.php';
2761
    include_once '../includes/libraries/Encryption/phpseclib/Crypt/AES.php';
2762
2763
    // Get file name
2764
    $fileName = base64_decode($fileName);
2765
2766
    // Load classes
2767
    $cipher = new Crypt_AES();
2768
2769
    // Set the object key
2770
    $cipher->setPassword(base64_decode($key));
2771
2772
    // Prevent against out of memory
2773
    $cipher->enableContinuousBuffer();
2774
    $cipher->disablePadding();
2775
2776
    // Get file content
2777
    $ciphertext = file_get_contents($filePath . '/' . TP_FILE_PREFIX . $fileName);
2778
2779
    // Decrypt file content and return
2780
    return base64_encode($cipher->decrypt($ciphertext));
2781
}
2782
2783
/**
2784
 * Generate a simple password
2785
 *
2786
 * @param integer $length          Length of string
2787
 * @param boolean $symbolsincluded Allow symbols
2788
 *
2789
 * @return string
2790
 */
2791
function generateQuickPassword($length = 16, $symbolsincluded = true)
2792
{
2793
    // Generate new user password
2794
    $small_letters = range('a', 'z');
2795
    $big_letters = range('A', 'Z');
2796
    $digits = range(0, 9);
2797
    $symbols = $symbolsincluded === true ?
2798
        array('#', '_', '-', '@', '$', '+', '&') : array();
2799
2800
    $res = array_merge($small_letters, $big_letters, $digits, $symbols);
2801
    $count = count($res);
2802
    // first variant
2803
2804
    $random_string = '';
2805
    for ($i = 0; $i < $length; ++$i) {
2806
        $random_string .= $res[random_int(0, $count - 1)];
2807
    }
2808
2809
    return $random_string;
2810
}
2811
2812
/**
2813
 * Permit to store the sharekey of an object for users.
2814
 *
2815
 * @param string $object_name             Type for table selection
2816
 * @param int    $post_folder_is_personal Personal
2817
 * @param int    $post_folder_id          Folder
2818
 * @param int    $post_object_id          Object
2819
 * @param string $objectKey               Object key
2820
 * @param array  $SETTINGS                Teampass settings
2821
 *
2822
 * @return void
2823
 */
2824
function storeUsersShareKey(
2825
    $object_name,
2826
    $post_folder_is_personal,
2827
    $post_folder_id,
2828
    $post_object_id,
2829
    $objectKey,
2830
    $SETTINGS
2831
) {
2832
    // include librairies & connect to DB
2833
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
2834
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
2835
    if (defined('DB_PASSWD_CLEAR') === false) {
2836
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
2837
    }
2838
    DB::$host = DB_HOST;
2839
    DB::$user = DB_USER;
2840
    DB::$password = DB_PASSWD_CLEAR;
2841
    DB::$dbName = DB_NAME;
2842
    DB::$port = DB_PORT;
2843
    DB::$encoding = DB_ENCODING;
2844
2845
    // Delete existing entries for this object
2846
    DB::delete(
2847
        $object_name,
2848
        'object_id = %i',
2849
        $post_object_id
2850
    );
2851
2852
    // Superglobals
2853
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/protect/SuperGlobal/SuperGlobal.php';
2854
    $superGlobal = new protect\SuperGlobal\SuperGlobal();
2855
2856
    // Prepare superGlobal variables
2857
    $sessionPpersonaFolders = $superGlobal->get('personal_folders', 'SESSION');
2858
    $sessionUserId = $superGlobal->get('user_id', 'SESSION');
2859
    $sessionUserPublicKey = $superGlobal->get('public_key', 'SESSION', 'user');
2860
2861
    if (
2862
        (int) $post_folder_is_personal === 1
2863
        && in_array($post_folder_id, $sessionPpersonaFolders) === true
2864
    ) {
2865
        // If this is a personal object
2866
        // Only create the sharekey for user
2867
        DB::insert(
2868
            $object_name,
2869
            array(
2870
                'object_id' => $post_object_id,
2871
                'user_id' => $sessionUserId,
2872
                'share_key' => encryptUserObjectKey($objectKey, $sessionUserPublicKey),
2873
            )
2874
        );
2875
    } else {
2876
        // This is a public object
2877
        // Create sharekey for each user
2878
        $users = DB::query(
2879
            'SELECT id, public_key
2880
            FROM ' . prefixTable('users') . '
2881
            WHERE id NOT IN ("' . OTV_USER_ID . '","' . SSH_USER_ID . '","' . API_USER_ID . '")
2882
            AND public_key != ""'
2883
        );
2884
        foreach ($users as $user) {
2885
            // Insert in DB the new object key for this item by user
2886
            DB::insert(
2887
                $object_name,
2888
                array(
2889
                    'object_id' => $post_object_id,
2890
                    'user_id' => $user['id'],
2891
                    'share_key' => encryptUserObjectKey(
2892
                        $objectKey,
2893
                        $user['public_key']
2894
                    ),
2895
                )
2896
            );
2897
        }
2898
    }
2899
}
2900
2901
/**
2902
 * Is this string base64 encoded?
2903
 *
2904
 * @param string $str Encoded string?
2905
 *
2906
 * @return bool
2907
 */
2908
function isBase64($str)
2909
{
2910
    $str = (string) trim($str);
2911
2912
    if (!isset($str[0])) {
2913
        return false;
2914
    }
2915
2916
    $base64String = (string) base64_decode($str, true);
2917
    if ($base64String && base64_encode($base64String) === $str) {
2918
        return true;
2919
    }
2920
2921
    return false;
2922
}
2923
2924
/**
2925
 * Undocumented function
2926
 *
2927
 * @param string $field Parameter
2928
 *
2929
 * @return array|bool|resource|string
2930
 */
2931
function filterString($field)
2932
{
2933
    // Sanitize string
2934
    $field = filter_var(trim($field), FILTER_SANITIZE_STRING);
2935
    if (empty($field) === false) {
2936
        // Load AntiXSS
2937
        include_once '../includes/libraries/voku/helper/AntiXSS.php';
2938
        $antiXss = new voku\helper\AntiXSS();
2939
        // Return
2940
        return $antiXss->xss_clean($field);
2941
    }
2942
2943
    return false;
2944
}
2945
2946
2947
/**
2948
 * CHeck if provided credentials are allowed on server
2949
 *
2950
 * @param string $login    User Login
2951
 * @param string $password User Pwd
2952
 * @param array  $SETTINGS Teampass settings
2953
 *
2954
 * @return bool
2955
 */
2956
function ldapCheckUserPassword($login, $password, $SETTINGS)
2957
{
2958
    // Build ldap configuration array
2959
    $config = [
2960
        // Mandatory Configuration Options
2961
        'hosts'            => [$SETTINGS['ldap_hosts']],
2962
        'base_dn'          => $SETTINGS['ldap_bdn'],
2963
        'username'         => $SETTINGS['ldap_username'],
2964
        'password'         => $SETTINGS['ldap_password'],
2965
2966
        // Optional Configuration Options
2967
        'port'             => $SETTINGS['ldap_port'],
2968
        'use_ssl'          => $SETTINGS['ldap_ssl'] === 1 ? true : false,
2969
        'use_tls'          => $SETTINGS['ldap_tls'] === 1 ? true : false,
2970
        'version'          => 3,
2971
        'timeout'          => 5,
2972
        'follow_referrals' => false,
2973
2974
        // Custom LDAP Options
2975
        'options' => [
2976
            // See: http://php.net/ldap_set_option
2977
            LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_HARD
2978
        ]
2979
    ];
2980
2981
    // Load expected libraries
2982
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Traits/Macroable.php';
2983
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Tightenco/Collect/Support/Arr.php';
2984
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/DetectsErrors.php';
2985
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Connection.php';
2986
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/LdapInterface.php';
2987
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/LdapBase.php';
2988
    require_once $SETTINGS['cpassman_dir'] . '/includes/libraries/LdapRecord/Ldap.php';
2989
2990
    $ad = new SplClassLoader('LdapRecord', '../includes/libraries');
2991
    $ad->register();
2992
    $connection = new Connection($config);
2993
2994
    // COnnect to LDAP
2995
    try {
2996
        $connection->connect();
2997
2998
    } catch (\LdapRecord\Auth\BindException $e) {
2999
        $error = $e->getDetailedError();
3000
3001
        echo "Error : ".$error->getErrorCode()." - ".$error->getErrorMessage(). "<br>".$error->getDiagnosticMessage();
3002
        return false;
3003
    }
3004
3005
    // Authenticate user
3006
    try {
3007
        $connection->auth()->attempt($SETTINGS['ldap_user_attribute']."=".$login.",".$SETTINGS['ldap_bdn'], $password, $stayAuthenticated = true);
3008
3009
    } catch (\LdapRecord\Auth\BindException $e) {
3010
        $error = $e->getDetailedError();
3011
        
3012
        echo "Error : ".$error->getErrorCode()." - ".$error->getErrorMessage(). "<br>".$error->getDiagnosticMessage();
3013
        return false;
3014
    }
3015
3016
    return true;
3017
}
3018
3019
3020
3021
3022
/**
3023
 * Removes from DB all sharekeys of this user
3024
 *
3025
 * @param integer $userId   User's id
3026
 * @param array   $SETTINGS Teampass settings
3027
 *
3028
 * @return void|bool
3029
 */
3030
function deleteUserObjetsKeys($userId, $SETTINGS)
3031
{
3032
    // include librairies & connect to DB
3033
    include_once $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
3034
    include_once $SETTINGS['cpassman_dir'] . '/includes/libraries/Database/Meekrodb/db.class.php';
3035
    if (defined('DB_PASSWD_CLEAR') === false) {
3036
        define('DB_PASSWD_CLEAR', defuseReturnDecrypted(DB_PASSWD, $SETTINGS));
3037
    }
3038
    DB::$host = DB_HOST;
3039
    DB::$user = DB_USER;
3040
    DB::$password = DB_PASSWD_CLEAR;
3041
    DB::$dbName = DB_NAME;
3042
    DB::$port = DB_PORT;
3043
    DB::$encoding = DB_ENCODING;
3044
3045
    // Remove all item sharekeys items
3046
    DB::delete(
3047
        prefixTable('sharekeys_items'),
3048
        'user_id = %i',
3049
        $userId
3050
    );
3051
3052
    // Remove all item sharekeys files
3053
    DB::delete(
3054
        prefixTable('sharekeys_files'),
3055
        'user_id = %i',
3056
        $userId
3057
    );
3058
3059
    // Remove all item sharekeys fields
3060
    DB::delete(
3061
        prefixTable('sharekeys_fields'),
3062
        'user_id = %i',
3063
        $userId
3064
    );
3065
3066
    // Remove all item sharekeys logs
3067
    DB::delete(
3068
        prefixTable('sharekeys_logs'),
3069
        'user_id = %i',
3070
        $userId
3071
    );
3072
3073
    // Remove all item sharekeys suggestions
3074
    DB::delete(
3075
        prefixTable('sharekeys_suggestions'),
3076
        'user_id = %i',
3077
        $userId
3078
    );
3079
3080
    return false;
3081
}
3082
3083
// Manage list of timezones
3084
function timezone_list() {
3085
    static $timezones = null;
3086
3087
    if ($timezones === null) {
3088
        $timezones = [];
3089
        $offsets = [];
3090
        $now = new DateTime('now', new DateTimeZone('UTC'));
3091
3092
        foreach (DateTimeZone::listIdentifiers() as $timezone) {
3093
            $now->setTimezone(new DateTimeZone($timezone));
3094
            $offsets[] = $offset = $now->getOffset();
3095
            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
3096
        }
3097
3098
        array_multisort($offsets, $timezones);
3099
    }
3100
3101
    return $timezones;
3102
}
3103
3104
function format_GMT_offset($offset) {
3105
    $hours = intval($offset / 3600);
3106
    $minutes = abs(intval($offset % 3600 / 60));
3107
    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
3108
}
3109
3110
function format_timezone_name($name) {
3111
    $name = str_replace('/', ', ', $name);
3112
    $name = str_replace('_', ' ', $name);
3113
    $name = str_replace('St ', 'St. ', $name);
3114
    return $name;
3115
}
3116
3117
3118
/**
3119
 * Provides info about if user should use MFA
3120
 *
3121
 * @param string $userRolesIds  User roles ids
3122
 * @param string $mfaRoles      Roles for which MFA is requested
3123
 *
3124
 * @return bool
3125
 */
3126
function mfa_auth_requested($userRolesIds, $mfaRoles)
3127
{
3128
	if (is_null($mfaRoles) === true) {
1 ignored issue
show
introduced by
The condition is_null($mfaRoles) === true is always false.
Loading history...
3129
		return false;
3130
	}
3131
	
3132
    $mfaRoles = array_values(json_decode($mfaRoles, true));
3133
    $userRolesIds = array_filter(explode(';' , $userRolesIds));
3134
3135
    if (count($mfaRoles) === 0 || count($mfaRoles) === 0) {
3136
        return true;
3137
    }
3138
3139
    if (count(array_intersect($mfaRoles, $userRolesIds)) > 0) {
3140
        return true;
3141
    } else {
3142
        return false;
3143
    }
3144
}