Issues (14)

Security Analysis    no vulnerabilities found

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  Header Injection
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

sources/admin.queries.php (1 issue)

Labels
Severity
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Teampass - a collaborative passwords manager.
7
 * ---
8
 * This file is part of the TeamPass project.
9
 * 
10
 * TeamPass is free software: you can redistribute it and/or modify it
11
 * under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, version 3 of the License.
13
 * 
14
 * TeamPass is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU General Public License for more details.
18
 * 
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21
 * 
22
 * Certain components of this file may be under different licenses. For
23
 * details, see the `licenses` directory or individual file headers.
24
 * ---
25
 * @file      admin.queries.php
26
 * @author    Nils Laumaillé ([email protected])
27
 * @copyright 2009-2025 Teampass.net
28
 * @license   GPL-3.0
29
 * @see       https://www.teampass.net
30
 */
31
32
use TeampassClasses\SessionManager\SessionManager;
33
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
34
use TeampassClasses\Language\Language;
35
use TeampassClasses\PerformChecks\PerformChecks;
36
use TeampassClasses\ConfigManager\ConfigManager;
37
use TeampassClasses\NestedTree\NestedTree;
38
use Duo\DuoUniversal\Client;
39
use Duo\DuoUniversal\DuoException;
40
use TeampassClasses\EmailService\EmailSettings;
41
use TeampassClasses\EmailService\EmailService;
42
use TeampassClasses\PasswordManager\PasswordManager;
43
44
// Load functions
45
require_once 'main.functions.php';
46
$session = SessionManager::getSession();
47
$request = SymfonyRequest::createFromGlobals();
48
loadClasses('DB');
49
$lang = new Language($session->get('user-language') ?? 'english');
50
51
// Load config
52
$configManager = new ConfigManager();
53
$SETTINGS = $configManager->getAllSettings();
54
55
// Do checks
56
// Instantiate the class with posted data
57
$checkUserAccess = new PerformChecks(
58
    dataSanitizer(
59
        [
60
            'type' => htmlspecialchars($request->request->get('type', ''), ENT_QUOTES, 'UTF-8'),
61
        ],
62
        [
63
            'type' => 'trim|escape',
64
        ],
65
    ),
66
    [
67
        'user_id' => returnIfSet($session->get('user-id'), null),
68
        'user_key' => returnIfSet($session->get('key'), null),
69
    ]
70
);
71
// Handle the case
72
echo $checkUserAccess->caseHandler();
73
if ($checkUserAccess->checkSession() === false || $checkUserAccess->userAccessPage('admin') === false) {
74
    // Not allowed page
75
    $session->set('system-error_code', ERR_NOT_ALLOWED);
76
    include $SETTINGS['cpassman_dir'] . '/error.php';
77
    exit;
78
}
79
80
// Define Timezone
81
date_default_timezone_set($SETTINGS['timezone'] ?? 'UTC');
82
83
// Set header properties
84
header('Content-type: text/html; charset=utf-8');
85
header('Cache-Control: no-cache, no-store, must-revalidate');
86
87
// --------------------------------- //
88
89
90
// Load tree
91
$tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
92
93
// Prepare POST variables
94
$post_type = filter_input(INPUT_POST, 'type', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
95
$post_data = filter_input(INPUT_POST, 'data', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
96
$post_key = filter_input(INPUT_POST, 'key', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
97
$post_id = filter_input(INPUT_POST, 'id', FILTER_SANITIZE_NUMBER_INT);
98
$post_status = filter_input(INPUT_POST, 'status', FILTER_SANITIZE_NUMBER_INT);
99
$post_label = filter_input(INPUT_POST, 'label', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
100
$post_action = filter_input(INPUT_POST, 'action', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
101
$post_cpt = filter_input(INPUT_POST, 'cpt', FILTER_SANITIZE_NUMBER_INT);
102
$post_object = filter_input(INPUT_POST, 'object', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
103
$post_start = filter_input(INPUT_POST, 'start', FILTER_SANITIZE_NUMBER_INT);
104
$post_length = filter_input(INPUT_POST, 'length', FILTER_SANITIZE_NUMBER_INT);
105
$post_option = filter_input(INPUT_POST, 'option', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
106
$post_nbItems = filter_input(INPUT_POST, 'nbItems', FILTER_SANITIZE_NUMBER_INT);
107
$post_counter = filter_input(INPUT_POST, 'counter', FILTER_SANITIZE_NUMBER_INT);
108
$post_list = filter_input(INPUT_POST, 'list', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
109
110
switch ($post_type) {
111
    //##########################################################
112
    //CASE for creating a DB backup
113
    case 'admin_action_db_backup':
114
        // Check KEY
115
        if ($post_key !== $session->get('key')) {
116
            echo prepareExchangedData(
117
                array(
118
                    'error' => true,
119
                    'message' => $lang->get('key_is_not_correct'),
120
                ),
121
                'encode'
122
            );
123
            break;
124
        }
125
        // Is admin?
126
        if ($session->get('user-admin') === 1) {
127
            echo prepareExchangedData(
128
                array(
129
                    'error' => true,
130
                    'message' => $lang->get('error_not_allowed_to'),
131
                ),
132
                'encode'
133
            );
134
            break;
135
        }
136
137
        require_once $SETTINGS['cpassman_dir'] . '/sources/main.functions.php';
138
        $return = '';
139
140
        //Get all tables
141
        $tables = array();
142
        $result = DB::query('SHOW TABLES');
143
        foreach ($result as $row) {
144
            $tables[] = $row['Tables_in_' . DB_NAME];
145
        }
146
147
        //cycle through
148
        foreach ($tables as $table) {
149
            if (defined('DB_PREFIX') || substr_count($table, DB_PREFIX) > 0) {
150
                $table = (is_string($table) ? $table : strval($table));
151
                // Do query
152
                $result = DB::query('SELECT * FROM ' . $table);
153
                DB::query(
154
                    'SELECT *
155
                    FROM INFORMATION_SCHEMA.COLUMNS
156
                    WHERE table_schema = %s
157
                    AND table_name = %s',
158
                    DB_NAME,
159
                    $table
160
                );
161
                $numFields = DB::count();
162
163
                // prepare a drop table
164
                $return .= 'DROP TABLE ' . $table . ';';
165
                $row2 = DB::queryFirstRow('SHOW CREATE TABLE ' . $table);
166
                $return .= "\n\n" . strval($row2['Create Table']) . ";\n\n";
167
168
                //prepare all fields and datas
169
                for ($i = 0; $i < $numFields; ++$i) {
170
                    if (is_object($result)) {
171
                        while ($row = $result->fetch_row()) {
172
                            $return .= 'INSERT INTO ' . $table . ' VALUES(';
173
                            for ($j = 0; $j < $numFields; ++$j) {
174
                                $row[$j] = addslashes($row[$j]);
175
                                $row[$j] = preg_replace("/\n/", '\\n', $row[$j]);
176
                                if (isset($row[$j])) {
177
                                    $return .= '"' . $row[$j] . '"';
178
                                } else {
179
                                    $return .= 'NULL';
180
                                }
181
                                if ($j < ($numFields - 1)) {
182
                                    $return .= ',';
183
                                }
184
                            }
185
                            $return .= ");\n";
186
                        }
187
                    }
188
                }
189
                $return .= "\n\n\n";
190
            }
191
        }
192
193
        if (!empty($return)) {
194
            // get a token// Generate password
195
            $passwordManager = new PasswordManager();
196
            $newPassword = $passwordManager->generatePassword(20, false, true, true, false, true);
197
198
            //save file
199
            $filename = time() . '-' . $token . '.sql';
200
            $handle = fopen($SETTINGS['path_to_files_folder'] . '/' . $filename, 'w+');
201
            if ($handle !== false) {
202
                //write file
203
                fwrite($handle, $return);
204
                fclose($handle);
205
            }
206
207
            // Encrypt the file
208
            if (empty($post_option) === false) {
209
                // Encrypt the file
210
                prepareFileWithDefuse(
211
                    'encrypt',
212
                    $SETTINGS['path_to_files_folder'] . '/' . $filename,
213
                    $SETTINGS['path_to_files_folder'] . '/defuse_temp_' . $filename,
214
                    $post_option
215
                );
216
217
                // Do clean
218
                unlink($SETTINGS['path_to_files_folder'] . '/' . $filename);
219
                rename(
220
                    $SETTINGS['path_to_files_folder'] . '/defuse_temp_' . $filename,
221
                    $SETTINGS['path_to_files_folder'] . '/' . $filename
222
                );
223
            }
224
225
            //generate 2d key
226
            $passwordManager = new PasswordManager();
227
            $newPassword = $passwordManager->generatePassword(20, false, true, true, false, true);
228
            $session->set('user-key_tmp', $newPassword);
229
230
            //update LOG
231
            logEvents($SETTINGS, 'admin_action', 'dataBase backup', (string) $session->get('user-id'), $session->get('user-login'));
232
233
            echo '[{"result":"db_backup" , "href":"sources/downloadFile.php?name=' . urlencode($filename) . '&sub=files&file=' . $filename . '&type=sql&key=' . $session->get('key') . '&key_tmp=' . $session->get('user-key_tmp') . '&pathIsFiles=1"}]';
234
        }
235
        break;
236
237
        //##########################################################
238
        //CASE for restoring a DB backup
239
    case 'admin_action_db_restore':
240
        // Check KEY
241
        if ($post_key !== $session->get('key')) {
242
            echo prepareExchangedData(
243
                array(
244
                    'error' => true,
245
                    'message' => $lang->get('key_is_not_correct'),
246
                ),
247
                'encode'
248
            );
249
            break;
250
        }
251
        // Is admin?
252
        if ($session->get('user-admin') === 1) {
253
            echo prepareExchangedData(
254
                array(
255
                    'error' => true,
256
                    'message' => $lang->get('error_not_allowed_to'),
257
                ),
258
                'encode'
259
            );
260
            break;
261
        }
262
        include_once $SETTINGS['cpassman_dir'] . '/sources/main.functions.php';
263
264
        $dataPost = explode('&', $post_option);
265
        $file = htmlspecialchars($dataPost[0]);
266
        $key = htmlspecialchars($dataPost[1]);
267
268
        // Get filename from database
269
        $data = DB::queryFirstRow(
270
            'SELECT valeur
271
            FROM ' . prefixTable('misc') . '
272
            WHERE increment_id = %i',
273
            $file
274
        );
275
276
        $file = is_string($data['valeur']) ? $data['valeur'] : '';
277
278
        // Delete operation id
279
        DB::delete(
280
            prefixTable('misc'),
281
            'increment_id = %i',
282
            $file
283
        );
284
285
        // Undecrypt the file
286
        if (empty($key) === false) {
287
            // Decrypt the file
288
            $ret = prepareFileWithDefuse(
289
                'decrypt',
290
                $SETTINGS['path_to_files_folder'] . '/' . $file,
291
                $SETTINGS['path_to_files_folder'] . '/defuse_temp_' . $file,
292
                $key
293
            );
294
295
            if (empty($ret) === false) {
296
                // deepcode ignore ServerLeak: $ret can only be an error string, so no important data here to be sent to client
297
                echo '[{"result":"db_restore" , "message":"An error occurred ('.(string) $ret.')"}]';
298
                break;
299
            }
300
301
            // Do clean
302
            fileDelete($SETTINGS['path_to_files_folder'] . '/' . $file, $SETTINGS);
303
            $file = $SETTINGS['path_to_files_folder'] . '/defuse_temp_' . $file;
304
        } else {
305
            $file = $SETTINGS['path_to_files_folder'] . '/' . $file;
306
        }
307
308
        //read sql file
309
        $handle = fopen($file, 'r');
310
        $query = '';
311
        while (!feof($handle)) {
312
            $query .= fgets($handle, 4096);
313
            if (substr(rtrim($query), -1) === ';') {
314
                //launch query
315
                DB::query($query);
316
                $query = '';
317
            }
318
        }
319
        fclose($handle);
320
321
        //delete file
322
        unlink($SETTINGS['path_to_files_folder'] . '/' . $file);
323
324
        //Show done
325
        echo '[{"result":"db_restore" , "message":""}]';
326
        break;
327
328
        //##########################################################
329
        //CASE for optimizing the DB
330
    case 'admin_action_db_optimize':
331
        // Check KEY
332
        if ($post_key !== $session->get('key')) {
333
            echo prepareExchangedData(
334
                array(
335
                    'error' => true,
336
                    'message' => $lang->get('key_is_not_correct'),
337
                ),
338
                'encode'
339
            );
340
            break;
341
        }
342
        // Is admin?
343
        if ($session->get('user-admin') === 1) {
344
            echo prepareExchangedData(
345
                array(
346
                    'error' => true,
347
                    'message' => $lang->get('error_not_allowed_to'),
348
                ),
349
                'encode'
350
            );
351
            break;
352
        }
353
354
        //Get all tables
355
        $alltables = DB::query('SHOW TABLES');
356
        foreach ($alltables as $table) {
357
            foreach ($table as $i => $tablename) {
358
                $tablename = (is_string($tablename) ? $tablename : strval($tablename));
359
                if (substr_count($tablename, DB_PREFIX) > 0) {
360
                    // launch optimization quieries
361
                    DB::query('ANALYZE TABLE `' . $tablename . '`');
362
                    DB::query('OPTIMIZE TABLE `' . $tablename . '`');
363
                }
364
            }
365
        }
366
367
        //Clean up LOG_ITEMS table
368
        $rows = DB::query(
369
            'SELECT id
370
            FROM ' . prefixTable('items') . '
371
            ORDER BY id ASC'
372
        );
373
        foreach ($rows as $item) {
374
            DB::query(
375
                'SELECT * FROM ' . prefixTable('log_items') . ' WHERE id_item = %i AND action = %s',
376
                $item['id'],
377
                'at_creation'
378
            );
379
            $counter = DB::count();
380
            if ($counter === 0) {
381
                //Create new at_creation entry
382
                $rowTmp = DB::queryFirstRow(
383
                    'SELECT date, id_user FROM ' . prefixTable('log_items') . ' WHERE id_item=%i ORDER BY date ASC',
384
                    $item['id']
385
                );
386
                DB::insert(
387
                    prefixTable('log_items'),
388
                    array(
389
                        'id_item' => $item['id'],
390
                        'date' => $rowTmp['date'] - 1,
391
                        'id_user' => empty($rowTmp['id_user']) === true ? 1 : $rowTmp['id_user'],
392
                        'action' => 'at_creation',
393
                        'raison' => '',
394
                    )
395
                );
396
            }
397
        }
398
399
        // Log
400
        logEvents(
401
            $SETTINGS,
402
            'system',
403
            'admin_action_db_optimize',
404
            (string) $session->get('user-id'),
405
            $session->get('user-login'),
406
            'success'
407
        );
408
409
        //Show done
410
        echo prepareExchangedData(
411
            array(
412
                'error' => false,
413
                'message' => $lang->get('last_execution') . ' ' .
414
                    date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], (int) time()) .
415
                    '<i class="fas fa-check text-success ml-2"></i>',
416
            ),
417
            'encode'
418
        );
419
        break;
420
421
        
422
423
        /*
424
    * Reload the Cache table
425
    */
426
    case 'admin_action_reload_cache_table':
427
        // Check KEY
428
        if ($post_key !== $session->get('key')) {
429
            echo prepareExchangedData(
430
                array(
431
                    'error' => true,
432
                    'message' => $lang->get('key_is_not_correct'),
433
                ),
434
                'encode'
435
            );
436
            break;
437
        }
438
        // Is admin?
439
        if ($session->get('user-admin') === 1) {
440
            echo prepareExchangedData(
441
                array(
442
                    'error' => true,
443
                    'message' => $lang->get('error_not_allowed_to'),
444
                ),
445
                'encode'
446
            );
447
            break;
448
        }
449
450
        require_once $SETTINGS['cpassman_dir'] . '/sources/main.functions.php';
451
        updateCacheTable('reload', NULL);
452
453
        // Log
454
        logEvents(
455
            $SETTINGS,
456
            'system',
457
            'admin_action_reload_cache_table',
458
            (string) $session->get('user-id'),
459
            $session->get('user-login'),
460
            'success'
461
        );
462
463
        echo prepareExchangedData(
464
            [
465
                'error' => false,
466
                'message' => $lang->get('last_execution') . ' ' .
467
                    date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], (int) time()) .
468
                    '<i class="fas fa-check text-success mr-2"></i>',
469
            ],
470
            'encode'
471
        );
472
        break;
473
474
475
    /*
476
    * Change SALT Key START
477
    */
478
    case 'admin_action_change_salt_key___start':
479
        // Check KEY
480
        if ($post_key !== $session->get('key')) {
481
            echo prepareExchangedData(
482
                array(
483
                    'error' => true,
484
                    'message' => $lang->get('key_is_not_correct'),
485
                ),
486
                'encode'
487
            );
488
            break;
489
        }
490
        // Is admin?
491
        if ($session->get('user-admin') === 1) {
492
            echo prepareExchangedData(
493
                array(
494
                    'error' => true,
495
                    'message' => $lang->get('error_not_allowed_to'),
496
                ),
497
                'encode'
498
            );
499
            break;
500
        }
501
502
        $error = '';
503
        require_once 'main.functions.php';
504
505
        // store old sk
506
        $session->set('user-reencrypt_old_salt', file_get_contents(SECUREPATH.'/'.SECUREFILE));
507
508
        // generate new saltkey
509
        $old_sk_filename = SECUREPATH.'/'.SECUREFILE . date('Y_m_d', mktime(0, 0, 0, (int) date('m'), (int) date('d'), (int) date('y'))) . '.' . time();
510
        copy(
511
            SECUREPATH.'/'.SECUREFILE,
512
            $old_sk_filename
513
        );
514
        $new_key = defuse_generate_key();
515
        file_put_contents(
516
            SECUREPATH.'/'.SECUREFILE,
517
            $new_key
518
        );
519
520
        // store new sk
521
        $session->set('user-reencrypt_new_salt', file_get_contents(SECUREPATH.'/'.SECUREFILE));
522
523
        //put tool in maintenance.
524
        DB::update(
525
            prefixTable('misc'),
526
            array(
527
                'valeur' => '1',
528
                'updated_at' => time(),
529
            ),
530
            'intitule = %s AND type= %s',
531
            'maintenance_mode',
532
            'admin'
533
        );
534
        //log
535
        logEvents($SETTINGS, 'system', 'change_salt_key', (string) $session->get('user-id'), $session->get('user-login'));
536
537
        // get number of items to change
538
        DB::query('SELECT id FROM ' . prefixTable('items') . ' WHERE perso = %i', 0);
539
        $nb_of_items = DB::count();
540
541
        // create backup table
542
        DB::query('DROP TABLE IF EXISTS ' . prefixTable('sk_reencrypt_backup'));
543
        DB::query(
544
            'CREATE TABLE `' . prefixTable('sk_reencrypt_backup') . '` (
545
            `id` int(12) NOT null AUTO_INCREMENT,
546
            `current_table` varchar(100) NOT NULL,
547
            `current_field` varchar(500) NOT NULL,
548
            `value_id` varchar(500) NOT NULL,
549
            `value` text NOT NULL,
550
            `value2` varchar(500) NOT NULL,
551
            `current_sql` text NOT NULL,
552
            `result` text NOT NULL,
553
            PRIMARY KEY (`id`)
554
            ) CHARSET=utf8;'
555
        );
556
557
        // store old SK in backup table
558
        DB::insert(
559
            prefixTable('sk_reencrypt_backup'),
560
            array(
561
                'current_table' => 'old_sk',
562
                'current_field' => 'old_sk',
563
                'value_id' => 'old_sk',
564
                'value' => $session->get('user-reencrypt_old_salt'),
565
                'current_sql' => 'old_sk',
566
                'value2' => $old_sk_filename,
567
                'result' => 'none',
568
            )
569
        );
570
571
        // delete previous backup files
572
        $files = glob($SETTINGS['path_to_upload_folder'] . '/*'); // get all file names
573
        foreach ($files as $file) { // iterate files
574
            if (is_file($file)) {
575
                $file_parts = pathinfo($file);
576
                if (strpos($file_parts['filename'], '.bck-change-sk') !== false) {
577
                    unlink($file); // delete file
578
                }
579
            }
580
        }
581
582
        // Send back
583
        echo prepareExchangedData(
584
            array(
585
                'error' => false,
586
                'message' => '',
587
                'nextAction' => 'encrypt_items',
588
                'nbOfItems' => $nb_of_items,
589
            ),
590
            'encode'
591
        );
592
        break;
593
594
        /*
595
    * Change SALT Key - ENCRYPT
596
    */
597
    case 'admin_action_change_salt_key___encrypt':
598
        // Check KEY
599
        if ($post_key !== $session->get('key')) {
600
            echo prepareExchangedData(
601
                array(
602
                    'error' => true,
603
                    'message' => $lang->get('key_is_not_correct'),
604
                    'nextAction' => '',
605
                    'nbOfItems' => '',
606
                ),
607
                'encode'
608
            );
609
            break;
610
        }
611
        // Is admin?
612
        if ($session->get('user-admin') === 1) {
613
            echo prepareExchangedData(
614
                array(
615
                    'error' => true,
616
                    'message' => $lang->get('error_not_allowed_to'),
617
                ),
618
                'encode'
619
            );
620
            break;
621
        }
622
623
        $error = '';
624
        require_once 'main.functions.php';
625
626
        // prepare SK
627
        if (empty($session->get('user-reencrypt_new_salt')) === true || empty($session->get('user-reencrypt_old_salt')) === true) {
628
            // SK is not correct
629
            echo prepareExchangedData(
630
                array(
631
                    'error' => true,
632
                    'message' => 'saltkeys are empty???',
633
                    'nbOfItems' => '',
634
                    'nextAction' => '',
635
                ),
636
                'encode'
637
            );
638
            break;
639
        }
640
641
        // Do init
642
        $nb_of_items = 0;
643
        $error = $nextStart = '';
644
645
646
        // what objects to treat
647
        if (empty($post_object) === true) {
648
            // no more object to treat
649
            $nextAction = 'finishing';
650
        } else {
651
            // manage list of objects
652
            $objects = explode(',', $post_object);
653
654
            // Allowed values for $_POST['object'] : "items,logs,files,categories"
655
            if (in_array($objects[0], array('items', 'logs', 'files', 'categories')) === false) {
656
                echo prepareExchangedData(
657
                    array(
658
                        'error' => true,
659
                        'message' => 'Input `' . $objects[0] . '` is not allowed',
660
                        'nbOfItems' => '',
661
                        'nextAction' => '',
662
                    ),
663
                    'encode'
664
                );
665
                break;
666
            }
667
668
            if ($objects[0] === 'items') {
669
                //change all encrypted data in Items (passwords)
670
                $rows = DB::query(
671
                    'SELECT id, pw, pw_iv
672
                    FROM ' . prefixTable('items') . '
673
                    WHERE perso = %s
674
                    LIMIT ' . $post_start . ', ' . $post_length,
675
                    '0'
676
                );
677
                foreach ($rows as $record) {
678
                    // backup data
679
                    DB::insert(
680
                        prefixTable('sk_reencrypt_backup'),
681
                        array(
682
                            'current_table' => 'items',
683
                            'current_field' => 'pw',
684
                            'value_id' => $record['id'],
685
                            'value' => $record['pw'],
686
                            'current_sql' => 'UPDATE ' . prefixTable('items') . " SET pw = '" . $record['pw'] . "' WHERE id = '" . $record['id'] . "';",
687
                            'value2' => 'none',
688
                            'result' => 'none',
689
                        )
690
                    );
691
                    $newID = DB::insertId();
692
693
                    $pw = cryption(
694
                        $record['pw'],
695
                        $session->get('user-reencrypt_old_salt'),
696
                        'decrypt',
697
                        $SETTINGS
698
                    );
699
                    //encrypt with new SALT
700
                    $encrypt = cryption(
701
                        $pw['string'],
702
                        $session->get('user-reencrypt_new_salt'),
703
                        'encrypt',
704
                        $SETTINGS
705
                    );
706
707
                    //save in DB
708
                    DB::update(
709
                        prefixTable('items'),
710
                        array(
711
                            'pw' => $encrypt['string'],
712
                            'pw_iv' => '',
713
                        ),
714
                        'id = %i',
715
                        $record['id']
716
                    );
717
718
                    // update backup table
719
                    DB::update(
720
                        prefixTable('sk_reencrypt_backup'),
721
                        array(
722
                            'result' => 'ok',
723
                        ),
724
                        'id=%i',
725
                        $newID
726
                    );
727
                }
728
                // ---
729
                // CASE OF LOGS
730
                // ---
731
            } elseif ($objects[0] === 'logs') {
732
                //change all encrypted data in Logs (passwords)
733
                $rows = DB::query(
734
                    'SELECT raison, increment_id
735
                    FROM ' . prefixTable('log_items') . "
736
                    WHERE action = %s AND raison LIKE 'at_pw :%'
737
                    LIMIT " . $post_start . ', ' . $post_length,
738
                    'at_modification'
739
                );
740
                foreach ($rows as $record) {
741
                    // backup data
742
                    DB::insert(
743
                        prefixTable('sk_reencrypt_backup'),
744
                        array(
745
                            'current_table' => 'log_items',
746
                            'current_field' => 'raison',
747
                            'value_id' => $record['increment_id'],
748
                            'value' => $record['raison'],
749
                            'current_sql' => 'UPDATE ' . prefixTable('log_items') . " SET raison = '" . $record['raison'] . "' WHERE increment_id = '" . $record['increment_id'] . "';",
750
                            'value2' => 'none',
751
                            'result' => 'none',
752
                        )
753
                    );
754
                    $newID = DB::insertId();
755
756
                    // extract the pwd
757
                    $tmp = explode('at_pw :', $record['raison']);
758
                    if (!empty($tmp[1])) {
759
                        $pw = cryption(
760
                            $tmp[1],
761
                            $session->get('user-reencrypt_old_salt'),
762
                            'decrypt',
763
                            $SETTINGS
764
                        );
765
                        //encrypt with new SALT
766
                        $encrypt = cryption(
767
                            $pw['string'],
768
                            $session->get('user-reencrypt_new_salt'),
769
                            'encrypt',
770
                            $SETTINGS
771
                        );
772
773
                        // save in DB
774
                        DB::update(
775
                            prefixTable('log_items'),
776
                            array(
777
                                'raison' => 'at_pw :' . $encrypt['string'],
778
                                'encryption_type' => 'defuse',
779
                            ),
780
                            'increment_id = %i',
781
                            $record['increment_id']
782
                        );
783
784
                        // update backup table
785
                        DB::update(
786
                            prefixTable('sk_reencrypt_backup'),
787
                            array(
788
                                'result' => 'ok',
789
                            ),
790
                            'id=%i',
791
                            $newID
792
                        );
793
                    }
794
                }
795
                // ---
796
                // CASE OF CATEGORIES
797
                // ---
798
            } elseif ($objects[0] === 'categories') {
799
                //change all encrypted data in CATEGORIES (passwords)
800
                $rows = DB::query(
801
                    'SELECT id, data
802
                    FROM ' . prefixTable('categories_items') . '
803
                    LIMIT ' . $post_start . ', ' . $post_length
804
                );
805
                foreach ($rows as $record) {
806
                    // backup data
807
                    DB::insert(
808
                        prefixTable('sk_reencrypt_backup'),
809
                        array(
810
                            'current_table' => 'categories_items',
811
                            'current_field' => 'data',
812
                            'value_id' => $record['id'],
813
                            'value' => $record['data'],
814
                            'current_sql' => 'UPDATE ' . prefixTable('categories_items') . " SET data = '" . $record['data'] . "' WHERE id = '" . $record['id'] . "';",
815
                            'value2' => 'none',
816
                            'result' => 'none',
817
                        )
818
                    );
819
                    $newID = DB::insertId();
820
821
                    $pw = cryption(
822
                        $record['data'],
823
                        $session->get('user-reencrypt_old_salt'),
824
                        'decrypt',
825
                        $SETTINGS
826
                    );
827
                    //encrypt with new SALT
828
                    $encrypt = cryption(
829
                        $pw['string'],
830
                        $session->get('user-reencrypt_new_salt'),
831
                        'encrypt',
832
                        $SETTINGS
833
                    );
834
                    // save in DB
835
                    DB::update(
836
                        prefixTable('categories_items'),
837
                        array(
838
                            'data' => $encrypt['string'],
839
                            'encryption_type' => 'defuse',
840
                        ),
841
                        'id = %i',
842
                        $record['id']
843
                    );
844
845
                    // update backup table
846
                    DB::update(
847
                        prefixTable('sk_reencrypt_backup'),
848
                        array(
849
                            'result' => 'ok',
850
                        ),
851
                        'id=%i',
852
                        $newID
853
                    );
854
                }
855
                // ---
856
                // CASE OF FILES
857
                // ---
858
            } elseif ($objects[0] === 'files') {
859
                // Change all encrypted data in FILES (passwords)
860
                $rows = DB::query(
861
                    'SELECT id, file, status
862
                    FROM ' . prefixTable('files') . "
863
                    WHERE status = 'encrypted'
864
                    LIMIT " . $post_start . ', ' . $post_length
865
                );
866
                foreach ($rows as $record) {
867
                    // backup data
868
                    DB::insert(
869
                        prefixTable('sk_reencrypt_backup'),
870
                        array(
871
                            'current_table' => 'files',
872
                            'current_field' => 'file',
873
                            'value_id' => $record['id'],
874
                            'value' => $record['file'],
875
                            'current_sql' => 'no_query',
876
                            'value2' => 'none',
877
                            'result' => 'none',
878
                        )
879
                    );
880
                    $newID = DB::insertId();
881
882
                    if (file_exists($SETTINGS['path_to_upload_folder'] . '/' . $record['file'])) {
883
                        // make a copy of file
884
                        if (!copy(
885
                            $SETTINGS['path_to_upload_folder'] . '/' . $record['file'],
886
                            $SETTINGS['path_to_upload_folder'] . '/' . $record['file'] . '.copy'
887
                        )) {
888
                            $error = 'Copy not possible';
889
                            exit;
890
                        } else {
891
                            // prepare a bck of file (that will not be deleted)
892
                            $backup_filename = $record['file'] . '.bck-change-sk.' . time();
893
                            copy(
894
                                $SETTINGS['path_to_upload_folder'] . '/' . $record['file'],
895
                                $SETTINGS['path_to_upload_folder'] . '/' . $backup_filename
896
                            );
897
                        }
898
899
                        // Treat the file
900
                        // STEP1 - Do decryption
901
                        prepareFileWithDefuse(
902
                            'decrypt',
903
                            $SETTINGS['path_to_upload_folder'] . '/' . $record['file'],
904
                            $SETTINGS['path_to_upload_folder'] . '/' . $record['file'] . '_encrypted'
905
                        );
906
907
                        // Do cleanup of files
908
                        unlink($SETTINGS['path_to_upload_folder'] . '/' . $record['file']);
909
910
                        // STEP2 - Do encryption
911
                        prepareFileWithDefuse(
912
                            'encryp',
913
                            $SETTINGS['path_to_upload_folder'] . '/' . $record['file'] . '_encrypted',
914
                            $SETTINGS['path_to_upload_folder'] . '/' . $record['file']
915
                        );
916
917
                        // Do cleanup of files
918
                        unlink($SETTINGS['path_to_upload_folder'] . '/' . $record['file'] . '_encrypted');
919
920
                        // Update backup table
921
                        DB::update(
922
                            prefixTable('sk_reencrypt_backup'),
923
                            array(
924
                                'value2' => $backup_filename,
925
                                'result' => 'ok',
926
                            ),
927
                            'id=%i',
928
                            $newID
929
                        );
930
                    }
931
                }
932
            }
933
934
            $nextStart = intval($post_start) + intval($post_length);
935
936
            // check if last item to change has been treated
937
            if ($nextStart >= intval($post_nbItems)) {
938
                array_shift($objects);
939
                $nextAction = implode(',', $objects); // remove first object of the list
940
941
                // do some things for new object
942
                if (isset($objects[0])) {
943
                    if ($objects[0] === 'logs') {
944
                        DB::query('SELECT increment_id FROM ' . prefixTable('log_items') . " WHERE action = %s AND raison LIKE 'at_pw :%'", 'at_modification');
945
                    } elseif ($objects[0] === 'files') {
946
                        DB::query('SELECT id FROM ' . prefixTable('files'));
947
                    } elseif ($objects[0] === 'categories') {
948
                        DB::query('SELECT id FROM ' . prefixTable('categories_items'));
949
                    } elseif ($objects[0] === 'custfields') {
950
                        DB::query('SELECT raison FROM ' . prefixTable('log_items') . " WHERE action = %s AND raison LIKE 'at_pw :%'", 'at_modification');
951
                    }
952
                    $nb_of_items = DB::count();
953
                } else {
954
                    // now finishing
955
                    $nextAction = 'finishing';
956
                }
957
            } else {
958
                $nextAction = $post_object;
959
                $nb_of_items = 0;
960
            }
961
        }
962
963
        // Send back
964
        echo prepareExchangedData(
965
            array(
966
                'error' => false,
967
                'message' => '',
968
                'nextAction' => $nextAction,
969
                'nextStart' => (int) $nextStart,
970
                'nbOfItems' => $nb_of_items,
971
                'oldsk' => $session->get('user-reencrypt_old_salt'),
972
                'newsk' => $session->get('user-reencrypt_new_salt'),
973
            ),
974
            'encode'
975
        );
976
        break;
977
978
        /*
979
    * Change SALT Key - END
980
    */
981
    case 'admin_action_change_salt_key___end':
982
        // Check KEY
983
        if ($post_key !== $session->get('key')) {
984
            echo prepareExchangedData(
985
                array(
986
                    'error' => true,
987
                    'message' => $lang->get('key_is_not_correct'),
988
                ),
989
                'encode'
990
            );
991
            break;
992
        }
993
        // Is admin?
994
        if ($session->get('user-admin') === 1) {
995
            echo prepareExchangedData(
996
                array(
997
                    'error' => true,
998
                    'message' => $lang->get('error_not_allowed_to'),
999
                ),
1000
                'encode'
1001
            );
1002
            break;
1003
        }
1004
        $error = '';
1005
1006
        // quit maintenance mode.
1007
        DB::update(
1008
            prefixTable('misc'),
1009
            array(
1010
                'valeur' => '0',
1011
                'updated_at' => time(),
1012
            ),
1013
            'intitule = %s AND type= %s',
1014
            'maintenance_mode',
1015
            'admin'
1016
        );
1017
1018
        // Send back
1019
        echo prepareExchangedData(
1020
            array(
1021
                'error' => false,
1022
                'message' => '',
1023
                'nextAction' => 'done',
1024
            ),
1025
            'encode'
1026
        );
1027
        break;
1028
1029
        /*
1030
    * Change SALT Key - Restore BACKUP data
1031
    */
1032
    case 'admin_action_change_salt_key___restore_backup':
1033
        // Check KEY
1034
        if ($post_key !== $session->get('key')) {
1035
            echo prepareExchangedData(
1036
                array(
1037
                    'error' => true,
1038
                    'message' => $lang->get('key_is_not_correct'),
1039
                ),
1040
                'encode'
1041
            );
1042
            break;
1043
        }
1044
        // Is admin?
1045
        if ($session->get('user-admin') === 1) {
1046
            echo prepareExchangedData(
1047
                array(
1048
                    'error' => true,
1049
                    'message' => $lang->get('error_not_allowed_to'),
1050
                ),
1051
                'encode'
1052
            );
1053
            break;
1054
        }
1055
1056
        // delete files
1057
        $previous_saltkey_filename = '';
1058
        $rows = DB::query(
1059
            'SELECT current_table, value, value2, current_sql
1060
            FROM ' . prefixTable('sk_reencrypt_backup')
1061
        );
1062
        foreach ($rows as $record) {
1063
            if ($record['current_table'] === 'items' || $record['current_table'] === 'logs' || $record['current_table'] === 'categories') {
1064
                // excute query
1065
                DB::query(
1066
                    str_replace("\'", "'", $record['current_sql'])
1067
                );
1068
            } elseif ($record['current_table'] === 'files') {
1069
                // restore backup file
1070
                if (file_exists($SETTINGS['path_to_upload_folder'] . '/' . $record['value'])) {
1071
                    unlink($SETTINGS['path_to_upload_folder'] . '/' . $record['value']);
1072
                    if (file_exists($SETTINGS['path_to_upload_folder'] . '/' . $record['value2'])) {
1073
                        rename(
1074
                            $SETTINGS['path_to_upload_folder'] . '/' . $record['value2'],
1075
                            $SETTINGS['path_to_upload_folder'] . '/' . $record['value']
1076
                        );
1077
                    }
1078
                }
1079
            } elseif ($record['current_table'] === 'old_sk') {
1080
                $previous_saltkey_filename = $record['value2'];
1081
            }
1082
        }
1083
1084
        // restore saltkey file
1085
        if (file_exists($previous_saltkey_filename)) {
1086
            unlink(SECUREPATH.'/'.SECUREFILE);
1087
            rename(
1088
                $previous_saltkey_filename,
1089
                SECUREPATH.'/'.SECUREFILE
1090
            );
1091
        }
1092
1093
        // drop table
1094
        DB::query('DROP TABLE IF EXISTS ' . prefixTable('sk_reencrypt_backup'));
1095
1096
        // Send back
1097
        echo prepareExchangedData(
1098
            array(
1099
                'error' => false,
1100
                'message' => '',
1101
            ),
1102
            'encode'
1103
        );
1104
1105
        break;
1106
1107
        /*
1108
    * Change SALT Key - Delete BACKUP data
1109
    */
1110
    case 'admin_action_change_salt_key___delete_backup':
1111
        // Check KEY
1112
        if ($post_key !== $session->get('key')) {
1113
            echo prepareExchangedData(
1114
                array(
1115
                    'error' => true,
1116
                    'message' => $lang->get('key_is_not_correct'),
1117
                ),
1118
                'encode'
1119
            );
1120
            break;
1121
        }
1122
        // Is admin?
1123
        if ($session->get('user-admin') === 1) {
1124
            echo prepareExchangedData(
1125
                array(
1126
                    'error' => true,
1127
                    'message' => $lang->get('error_not_allowed_to'),
1128
                ),
1129
                'encode'
1130
            );
1131
            break;
1132
        }
1133
1134
        // delete files
1135
        $rows = DB::query(
1136
            'SELECT value, value2
1137
            FROM ' . prefixTable('sk_reencrypt_backup') . "
1138
            WHERE current_table = 'files'"
1139
        );
1140
        foreach ($rows as $record) {
1141
            if (file_exists($SETTINGS['path_to_upload_folder'] . '/' . $record['value2'])) {
1142
                unlink($SETTINGS['path_to_upload_folder'] . '/' . $record['value2']);
1143
            }
1144
        }
1145
1146
        // drop table
1147
        DB::query('DROP TABLE IF EXISTS ' . prefixTable('sk_reencrypt_backup'));
1148
1149
        echo '[{"status":"done"}]';
1150
        break;
1151
1152
        /*
1153
    * Test the email configuraiton
1154
    */
1155
    case 'admin_email_test_configuration':
1156
        // Check KEY
1157
        if ($post_key !== $session->get('key')) {
1158
            echo prepareExchangedData(
1159
                array(
1160
                    'error' => true,
1161
                    'message' => $lang->get('key_is_not_correct'),
1162
                ),
1163
                'encode'
1164
            );
1165
            break;
1166
        }
1167
1168
        // User has an email set?
1169
        if (empty($session->get('user-email')) === true) {
1170
            echo prepareExchangedData(
1171
                array(
1172
                    'error' => true,
1173
                    'message' => $lang->get('no_email_set'),
1174
                ),
1175
                'encode'
1176
            );
1177
        } else {
1178
            require_once $SETTINGS['cpassman_dir'] . '/sources/main.functions.php';
1179
1180
            //send email
1181
            $emailSettings = new EmailSettings($SETTINGS);
1182
            $emailService = new EmailService();
1183
            $emailService->sendMail(
1184
                $lang->get('admin_email_test_subject'),
1185
                $lang->get('admin_email_test_body'),
1186
                $session->get('user-email'),
1187
                $emailSettings
1188
            );
1189
            
1190
            echo prepareExchangedData(
1191
                array(
1192
                    'error' => false,
1193
                    'message' => '',
1194
                ),
1195
                'encode'
1196
            );
1197
        }
1198
        break;
1199
1200
        /*
1201
    * Send emails in backlog
1202
    */
1203
    case 'admin_email_send_backlog':
1204
        // Check KEY
1205
        if ($post_key !== $session->get('key')) {
1206
            echo prepareExchangedData(
1207
                array(
1208
                    'error' => true,
1209
                    'message' => $lang->get('key_is_not_correct'),
1210
                ),
1211
                'encode'
1212
            );
1213
            break;
1214
        }
1215
1216
        include_once $SETTINGS['cpassman_dir'] . '/sources/main.functions.php';
1217
        $emailSettings = new EmailSettings($SETTINGS);
1218
        $emailService = new EmailService();
1219
1220
        $rows = DB::query(
1221
            'SELECT *
1222
            FROM ' . prefixTable('emails') . '
1223
            WHERE status = %s OR status = %s',
1224
            'not_sent',
1225
            ''
1226
        );
1227
        $counter = DB::count();
1228
        $error = false;
1229
        $message = '';
1230
1231
        if ($counter > 0) {
1232
            // Only treat first email
1233
            foreach ($rows as $record) {
1234
                //send email
1235
                $email = $emailService->sendMail(
1236
                    $record['subject'],
1237
                    $record['body'],
1238
                    $record['receivers'],
1239
                    $emailSettings
1240
                );
1241
                $ret = json_decode(
1242
                    $email,
1243
                    true
1244
                );
1245
1246
                if (empty($ret['error']) === false) {
1247
                    //update item_id in files table
1248
                    DB::update(
1249
                        prefixTable('emails'),
1250
                        array(
1251
                            'status' => 'not_sent',
1252
                        ),
1253
                        'timestamp = %s',
1254
                        $record['timestamp']
1255
                    );
1256
1257
                    $error = true;
1258
                    $message = $ret['message'];
1259
                } else {
1260
                    //delete from DB
1261
                    DB::delete(
1262
                        prefixTable('emails'),
1263
                        'timestamp = %s',
1264
                        $record['timestamp']
1265
                    );
1266
1267
                    //update LOG
1268
                    logEvents(
1269
                        $SETTINGS,
1270
                        'admin_action',
1271
                        'Emails backlog',
1272
                        (string) $session->get('user-id'),
1273
                        $session->get('user-login')
1274
                    );
1275
                }
1276
1277
                // Exit loop
1278
                break;
1279
            }
1280
        }
1281
1282
        echo prepareExchangedData(
1283
            array(
1284
                'error' => $error,
1285
                'message' => $message,
1286
                'counter' => $counter,
1287
            ),
1288
            'encode'
1289
        );
1290
        break;
1291
1292
        /*
1293
    * Send emails in backlog
1294
    */
1295
    case 'admin_email_send_backlog_old':
1296
        // Check KEY
1297
        if ($post_key !== $session->get('key')) {
1298
            echo prepareExchangedData(
1299
                array(
1300
                    'error' => true,
1301
                    'message' => $lang->get('key_is_not_correct'),
1302
                ),
1303
                'encode'
1304
            );
1305
            break;
1306
        }
1307
1308
        include_once $SETTINGS['cpassman_dir'] . '/sources/main.functions.php';
1309
1310
        // Instatiate email settings and service
1311
        $emailSettings = new EmailSettings($SETTINGS);
1312
        $emailService = new EmailService();
1313
1314
        $rows = DB::query('SELECT * FROM ' . prefixTable('emails') . ' WHERE status = %s OR status = %s', 'not_sent', '');
1315
        foreach ($rows as $record) {
1316
            //send email
1317
            $email = $emailService->sendMail(
1318
                $record['subject'],
1319
                $record['body'],
1320
                $record['receivers'],
1321
                $emailSettings
1322
            );
1323
            $ret = json_decode(
1324
                $email,
1325
                true
1326
            );
1327
1328
            if (empty($ret['error']) === false) {
1329
                //update item_id in files table
1330
                DB::update(
1331
                    prefixTable('emails'),
1332
                    array(
1333
                        'status' => 'not_sent',
1334
                    ),
1335
                    'timestamp = %s',
1336
                    $record['timestamp']
1337
                );
1338
            } else {
1339
                //delete from DB
1340
                DB::delete(prefixTable('emails'), 'timestamp = %s', $record['timestamp']);
1341
            }
1342
        }
1343
1344
        //update LOG
1345
        logEvents($SETTINGS, 'admin_action', 'Emails backlog', (string) $session->get('user-id'), $session->get('user-login'));
1346
1347
        echo prepareExchangedData(
1348
            array(
1349
                'error' => false,
1350
                'message' => '',
1351
            ),
1352
            'encode'
1353
        );
1354
        break;
1355
1356
        /*
1357
    * Attachments encryption
1358
    */
1359
    case 'admin_action_attachments_cryption':
1360
        // Check KEY
1361
        if ($post_key !== $session->get('key')) {
1362
            echo prepareExchangedData(
1363
                array(
1364
                    'error' => true,
1365
                    'message' => $lang->get('key_is_not_correct'),
1366
                ),
1367
                'encode'
1368
            );
1369
            break;
1370
        }
1371
        // Is admin?
1372
        if ($session->get('user-admin') === 1) {
1373
            echo prepareExchangedData(
1374
                array(
1375
                    'error' => true,
1376
                    'message' => $lang->get('error_not_allowed_to'),
1377
                ),
1378
                'encode'
1379
            );
1380
            break;
1381
        }
1382
1383
        require_once $SETTINGS['cpassman_dir'] . '/sources/main.functions.php';
1384
1385
        // init
1386
        $filesList = array();
1387
1388
        // get through files
1389
        if (null !== $post_option && empty($post_option) === false) {
1390
            // Loop on files
1391
            $rows = DB::query(
1392
                'SELECT id, file, status
1393
                FROM ' . prefixTable('files')
1394
            );
1395
            foreach ($rows as $record) {
1396
                if (is_file($SETTINGS['path_to_upload_folder'] . '/' . $record['file'])) {
1397
                    $addFile = false;
1398
                    if (($post_option === 'attachments-decrypt' && $record['status'] === 'encrypted')
1399
                        || ($post_option === 'attachments-encrypt' && $record['status'] === 'clear')
1400
                    ) {
1401
                        $addFile = true;
1402
                    }
1403
1404
                    if ($addFile === true) {
1405
                        array_push($filesList, $record['id']);
1406
                    }
1407
                }
1408
            }
1409
        } else {
1410
            echo prepareExchangedData(
1411
                array(
1412
                    'error' => true,
1413
                    'message' => $lang->get('error_not_allowed_to'),
1414
                ),
1415
                'encode'
1416
            );
1417
        }
1418
1419
        echo prepareExchangedData(
1420
            array(
1421
                'error' => false,
1422
                'message' => '',
1423
                'list' => $filesList,
1424
                'counter' => 0,
1425
            ),
1426
            'encode'
1427
        );
1428
        break;
1429
1430
        /*
1431
     * Attachments encryption - Treatment in several loops
1432
     */
1433
    case 'admin_action_attachments_cryption_continu':
1434
        // Check KEY
1435
        if ($post_key !== $session->get('key')) {
1436
            echo prepareExchangedData(
1437
                array(
1438
                    'error' => true,
1439
                    'message' => $lang->get('key_is_not_correct'),
1440
                ),
1441
                'encode'
1442
            );
1443
            break;
1444
        }
1445
        // Is admin?
1446
        if ($session->get('user-admin') === 1) {
1447
            echo prepareExchangedData(
1448
                array(
1449
                    'error' => true,
1450
                    'message' => $lang->get('error_not_allowed_to'),
1451
                ),
1452
                'encode'
1453
            );
1454
            break;
1455
        }
1456
1457
        // Prepare variables
1458
        $post_list = filter_var_array($post_list, FILTER_SANITIZE_FULL_SPECIAL_CHARS);
1459
        $post_counter = filter_var($post_counter, FILTER_SANITIZE_NUMBER_INT);
1460
1461
        include $SETTINGS['cpassman_dir'] . '/includes/config/settings.php';
1462
        include_once $SETTINGS['cpassman_dir'] . '/sources/main.functions.php';
1463
1464
        $cpt = 0;
1465
        $continu = true;
1466
        $newFilesList = array();
1467
        $message = '';
1468
1469
        // treat 10 files
1470
        foreach ($post_list as $file) {
1471
            if ($cpt < 5) {
1472
                // Get file name
1473
                $file_info = DB::queryFirstRow(
1474
                    'SELECT file
1475
                    FROM ' . prefixTable('files') . '
1476
                    WHERE id = %i',
1477
                    $file
1478
                );
1479
1480
                // skip file is Coherancey not respected
1481
                if (is_file($SETTINGS['path_to_upload_folder'] . '/' . $file_info['file'])) {
1482
                    // Case where we want to decrypt
1483
                    if ($post_option === 'decrypt') {
1484
                        prepareFileWithDefuse(
1485
                            'decrypt',
1486
                            $SETTINGS['path_to_upload_folder'] . '/' . $file_info['file'],
1487
                            $SETTINGS['path_to_upload_folder'] . '/defuse_temp_' . $file_info['file'],
1488
                        );
1489
                        // Case where we want to encrypt
1490
                    } elseif ($post_option === 'encrypt') {
1491
                        prepareFileWithDefuse(
1492
                            'encrypt',
1493
                            $SETTINGS['path_to_upload_folder'] . '/' . $file_info['file'],
1494
                            $SETTINGS['path_to_upload_folder'] . '/defuse_temp_' . $file_info['file'],
1495
                        );
1496
                    }
1497
                    // Do file cleanup
1498
                    fileDelete($SETTINGS['path_to_upload_folder'] . '/' . $file_info['file'], $SETTINGS);
1499
                    rename(
1500
                        $SETTINGS['path_to_upload_folder'] . '/defuse_temp_' . $file_info['file'],
1501
                        $SETTINGS['path_to_upload_folder'] . '/' . $file_info['file']
1502
                    );
1503
1504
                    // store in DB
1505
                    DB::update(
1506
                        prefixTable('files'),
1507
                        array(
1508
                            'status' => $post_option === 'attachments-decrypt' ? 'clear' : 'encrypted',
1509
                        ),
1510
                        'id = %i',
1511
                        $file
1512
                    );
1513
1514
                    ++$cpt;
1515
                }
1516
            } else {
1517
                // build list
1518
                array_push($newFilesList, $file);
1519
            }
1520
        }
1521
1522
        // Should we stop
1523
        if (count($newFilesList) === 0) {
1524
            $continu = false;
1525
1526
            //update LOG
1527
            logEvents(
1528
                $SETTINGS,
1529
                'admin_action',
1530
                'attachments_encryption_changed',
1531
                (string) $session->get('user-id'),
1532
                $session->get('user-login'),
1533
                $post_option === 'attachments-decrypt' ? 'clear' : 'encrypted'
1534
            );
1535
1536
            $message = $lang->get('last_execution') . ' ' .
1537
                date($SETTINGS['date_format'] . ' ' . $SETTINGS['time_format'], (int) time()) .
1538
                '<i class="fas fa-check text-success ml-2 mr-3"></i>';
1539
        }
1540
1541
        echo prepareExchangedData(
1542
            array(
1543
                'error' => false,
1544
                'message' => $message,
1545
                'list' => $newFilesList,
1546
                'counter' => $post_cpt + $cpt,
1547
                'continu' => $continu,
1548
            ),
1549
            'encode'
1550
        );
1551
        break;
1552
1553
        /*
1554
     * API save key
1555
     */
1556
    case 'admin_action_api_save_key':
1557
        // Check KEY
1558
        if ($post_key !== $session->get('key')) {
1559
            echo prepareExchangedData(
1560
                array(
1561
                    'error' => true,
1562
                    'message' => $lang->get('key_is_not_correct'),
1563
                ),
1564
                'encode'
1565
            );
1566
            break;
1567
        }
1568
        // Is admin?
1569
        if ($session->get('user-admin') === 1) {
1570
            echo prepareExchangedData(
1571
                array(
1572
                    'error' => true,
1573
                    'message' => $lang->get('error_not_allowed_to'),
1574
                ),
1575
                'encode'
1576
            );
1577
            break;
1578
        }
1579
1580
        // decrypt and retrieve data in JSON format
1581
        $dataReceived = prepareExchangedData(
1582
            $post_data,
1583
            'decode'
1584
        );
1585
1586
        $post_label = isset($dataReceived['label']) === true ? filter_var($dataReceived['label'], FILTER_SANITIZE_FULL_SPECIAL_CHARS) : '';
1587
        $post_action = filter_var($dataReceived['action'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
1588
        $timestamp = time();
1589
1590
        // add new key
1591
        if (null !== $post_action && $post_action === 'add') {
1592
            // Generate KEY
1593
            require_once 'main.functions.php';
1594
            $passwordManager = new PasswordManager();
1595
            $key = $passwordManager->generatePassword(39, false, true, true, false, true);
1596
1597
            // Generate objectKey
1598
            //$object = doDataEncryption($key, SECUREFILE.':'.$timestamp);
1599
            
1600
            // Save in DB
1601
            DB::insert(
1602
                prefixTable('api'),
1603
                array(
1604
                    'increment_id' => null,
1605
                    'type' => 'key',
1606
                    'label' => $post_label,
1607
                    'value' => $key, //$object['encrypted'],
1608
                    'timestamp' => $timestamp,
1609
                    //'user_id' => -1,
1610
                )
1611
            );
1612
1613
            $post_id = DB::insertId();
1614
            // Update existing key
1615
        } elseif (null !== $post_action && $post_action === 'update') {
1616
            $post_id = filter_var($dataReceived['id'], FILTER_SANITIZE_NUMBER_INT);
1617
1618
            DB::update(
1619
                prefixTable('api'),
1620
                array(
1621
                    'label' => $post_label,
1622
                    'timestamp' => $timestamp,
1623
                ),
1624
                'increment_id=%i',
1625
                $post_id
1626
            );
1627
            // Delete existing key
1628
        } elseif (null !== $post_action && $post_action === 'delete') {
1629
            $post_id = filter_var($dataReceived['id'], FILTER_SANITIZE_NUMBER_INT);
1630
1631
            DB::query(
1632
                'DELETE FROM ' . prefixTable('api') . ' WHERE increment_id = %i',
1633
                $post_id
1634
            );
1635
        }
1636
1637
        // send data
1638
        echo prepareExchangedData(
1639
            array(
1640
                'error' => false,
1641
                'message' => '',
1642
                'keyId' => $post_id,
1643
                'key' => isset($key) === true ? $key : '',
1644
            ),
1645
            'encode'
1646
        );
1647
        break;
1648
1649
        /*
1650
       * API save key
1651
    */
1652
    case 'admin_action_api_save_ip':
1653
        // Check KEY
1654
        if ($post_key !== $session->get('key')) {
1655
            echo prepareExchangedData(
1656
                array(
1657
                    'error' => true,
1658
                    'message' => $lang->get('key_is_not_correct'),
1659
                ),
1660
                'encode'
1661
            );
1662
            break;
1663
        }
1664
        // Is admin?
1665
        if ($session->get('user-admin') === 1) {
1666
            echo prepareExchangedData(
1667
                array(
1668
                    'error' => true,
1669
                    'message' => $lang->get('error_not_allowed_to'),
1670
                ),
1671
                'encode'
1672
            );
1673
            break;
1674
        }
1675
1676
        // decrypt and retrieve data in JSON format
1677
        $dataReceived = prepareExchangedData(
1678
            $post_data,
1679
            'decode'
1680
        );
1681
1682
        $post_action = filter_var($dataReceived['action'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
1683
1684
        // add new key
1685
        if (null !== $post_action && $post_action === 'add') {
1686
            $post_label = filter_var($dataReceived['label'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
1687
            $post_ip = filter_var($dataReceived['ip'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
1688
1689
            // Store in DB
1690
            DB::insert(
1691
                prefixTable('api'),
1692
                array(
1693
                    'increment_id' => null,
1694
                    'type' => 'ip',
1695
                    'label' => $post_label,
1696
                    'value' => $post_ip,
1697
                    'timestamp' => time(),
1698
                )
1699
            );
1700
1701
            $post_id = DB::insertId();
1702
            // Update existing key
1703
        } elseif (null !== $post_action && $post_action === 'update') {
1704
            $post_id = filter_var($dataReceived['id'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
1705
            $post_field = filter_var($dataReceived['field'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
1706
            $post_value = filter_var($dataReceived['value'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
1707
            if ($post_field === 'value') {
1708
                $arr = array(
1709
                    'value' => $post_value,
1710
                    'timestamp' => time(),
1711
                );
1712
            } else {
1713
                $arr = array(
1714
                    'label' => $post_value,
1715
                    'timestamp' => time(),
1716
                );
1717
            }
1718
            DB::update(
1719
                prefixTable('api'),
1720
                $arr,
1721
                'increment_id=%i',
1722
                $post_id
1723
            );
1724
            // Delete existing key
1725
        } elseif (null !== $post_action && $post_action === 'delete') {
1726
            $post_id = filter_var($dataReceived['id'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
1727
            DB::query('DELETE FROM ' . prefixTable('api') . ' WHERE increment_id=%i', $post_id);
1728
        }
1729
1730
        echo prepareExchangedData(
1731
            array(
1732
                'error' => false,
1733
                'message' => '',
1734
                'ipId' => $post_id,
1735
            ),
1736
            'encode'
1737
        );
1738
        break;
1739
1740
    case 'save_api_status':
1741
        // Do query
1742
        DB::query('SELECT * FROM ' . prefixTable('misc') . ' WHERE type = %s AND intitule = %s', 'admin', 'api');
1743
        $counter = DB::count();
1744
        if ($counter === 0) {
1745
            DB::insert(
1746
                prefixTable('misc'),
1747
                array(
1748
                    'type' => 'admin',
1749
                    'intitule' => 'api',
1750
                    'valeur' => $post_status,
1751
                    'created_at' => time(),
1752
                )
1753
            );
1754
        } else {
1755
            DB::update(
1756
                prefixTable('misc'),
1757
                array(
1758
                    'valeur' => $post_status,
1759
                    'updated_at' => time(),
1760
                ),
1761
                'type = %s AND intitule = %s',
1762
                'admin',
1763
                'api'
1764
            );
1765
        }
1766
        $SETTINGS['api'] = $post_status;
1767
        break;
1768
1769
    case 'run_duo_config_check':
1770
        //Libraries call
1771
        require_once $SETTINGS['cpassman_dir'] . '/sources/main.functions.php';
1772
        // Check KEY
1773
        if ($post_key !== $session->get('key')) {
1774
            echo prepareExchangedData(
1775
                array(
1776
                    'error' => true,
1777
                    'message' => $lang->get('key_is_not_correct'),
1778
                ),
1779
                'encode'
1780
            );
1781
            break;
1782
        }
1783
1784
        // decrypt and retreive data in JSON format
1785
        $dataReceived = prepareExchangedData(
1786
            $post_data,
1787
            'decode'
1788
        );
1789
1790
        // Check if we have what we need first
1791
        if (empty($dataReceived['duo_ikey']) || empty($dataReceived['duo_skey']) || empty($dataReceived['duo_host'])) {
1792
            echo prepareExchangedData(
1793
                array(
1794
                    'error' => true,
1795
                    'message' => $lang->get('data_are_missing'),
1796
                ),
1797
                'encode'
1798
            );
1799
            break;
1800
        }
1801
1802
        // Run Duo Config Check
1803
        try {
1804
            $duo_client = new Client(
1805
                $dataReceived['duo_ikey'],
1806
                $dataReceived['duo_skey'],
1807
                $dataReceived['duo_host'],
1808
                $SETTINGS['cpassman_url'].'/'.DUO_CALLBACK
1809
            );
1810
        } catch (DuoException $e) {
1811
            if (defined('LOG_TO_SERVER') && LOG_TO_SERVER === true) {
1812
                error_log('TEAMPASS Error - duo config - '.$e->getMessage());
1813
            }
1814
            // deepcode ignore ServerLeak: Data is encrypted before being sent
1815
            echo prepareExchangedData(
1816
                    array(
1817
                        'error' => true,
1818
                        'message' => $lang->get('duo_config_error'),
1819
                    ),
1820
                    'encode'
1821
            );
1822
            break;
1823
        }
1824
1825
        // Run healthcheck against Duo with the config
1826
        try {
1827
            $duo_client->healthCheck();
1828
        } catch (DuoException $e) {
1829
            if (defined('LOG_TO_SERVER') && LOG_TO_SERVER === true) {
1830
                error_log('TEAMPASS Error - duo config - '.$e->getMessage());
1831
            }
1832
            // deepcode ignore ServerLeak: Data is encrypted before being sent
1833
            echo prepareExchangedData(
1834
                    array(
1835
                        'error' => true,
1836
                        'message' => $lang->get('duo_error_check_config'),
1837
                    ),
1838
                    'encode'
1839
            );
1840
            break;
1841
        }
1842
1843
        // send data
1844
        echo prepareExchangedData(
1845
            array(
1846
                'error' => false,
1847
                'message' => '',
1848
            ),
1849
            'encode'
1850
        );
1851
        break;
1852
1853
    case 'save_google_options':
1854
        // Check KEY and rights
1855
        if ($post_key !== $session->get('key')) {
1856
            echo prepareExchangedData(
1857
                array(
1858
                    'error' => true,
1859
                    'message' => $lang->get('key_is_not_correct'),
1860
                ),
1861
                'encode'
1862
            );
1863
            break;
1864
        }
1865
        // decrypt and retreive data in JSON format
1866
        $dataReceived = prepareExchangedData(
1867
            $post_data,
1868
            'decode'
1869
        );
1870
1871
        // Google Authentication
1872
        if (htmlspecialchars_decode($dataReceived['google_authentication']) === 'false') {
1873
            $tmp = 0;
1874
        } else {
1875
            $tmp = 1;
1876
        }
1877
        DB::query('SELECT * FROM ' . prefixTable('misc') . ' WHERE type = %s AND intitule = %s', 'admin', 'google_authentication');
1878
        $counter = DB::count();
1879
        if ($counter === 0) {
1880
            DB::insert(
1881
                prefixTable('misc'),
1882
                array(
1883
                    'type' => 'admin',
1884
                    'intitule' => 'google_authentication',
1885
                    'valeur' => $tmp,
1886
                    'created_at' => time(),
1887
                )
1888
            );
1889
        } else {
1890
            DB::update(
1891
                prefixTable('misc'),
1892
                array(
1893
                    'valeur' => $tmp,
1894
                    'updated_at' => time(),
1895
                ),
1896
                'type = %s AND intitule = %s',
1897
                'admin',
1898
                'google_authentication'
1899
            );
1900
        }
1901
        $SETTINGS['google_authentication'] = htmlspecialchars_decode($dataReceived['google_authentication']);
1902
1903
        // ga_website_name
1904
        if (is_null($dataReceived['ga_website_name']) === false) {
1905
            DB::query('SELECT * FROM ' . prefixTable('misc') . ' WHERE type = %s AND intitule = %s', 'admin', 'ga_website_name');
1906
            $counter = DB::count();
1907
            if ($counter === 0) {
1908
                DB::insert(
1909
                    prefixTable('misc'),
1910
                    array(
1911
                        'type' => 'admin',
1912
                        'intitule' => 'ga_website_name',
1913
                        'valeur' => htmlspecialchars_decode($dataReceived['ga_website_name']),
1914
                        'created_at' => time(),
1915
                    )
1916
                );
1917
            } else {
1918
                DB::update(
1919
                    prefixTable('misc'),
1920
                    array(
1921
                        'valeur' => htmlspecialchars_decode($dataReceived['ga_website_name']),
1922
                        'updated_at' => time(),
1923
                    ),
1924
                    'type = %s AND intitule = %s',
1925
                    'admin',
1926
                    'ga_website_name'
1927
                );
1928
            }
1929
            $SETTINGS['ga_website_name'] = htmlspecialchars_decode($dataReceived['ga_website_name']);
1930
        } else {
1931
            $SETTINGS['ga_website_name'] = '';
1932
        }
1933
1934
        // send data
1935
        echo '[{"result" : "' . addslashes($lang['done']) . '" , "error" : ""}]';
1936
        break;
1937
1938
    case 'save_agses_options':
1939
        // Check KEY and rights
1940
        if ($post_key !== $session->get('key')) {
1941
            echo prepareExchangedData(
1942
                array(
1943
                    'error' => true,
1944
                    'message' => $lang->get('key_is_not_correct'),
1945
                ),
1946
                'encode'
1947
            );
1948
            break;
1949
        }
1950
        // decrypt and retreive data in JSON format
1951
        $dataReceived = prepareExchangedData(
1952
            $post_data,
1953
            'decode'
1954
        );
1955
1956
        // agses_hosted_url
1957
        if (!is_null($dataReceived['agses_hosted_url'])) {
1958
            DB::query('SELECT * FROM ' . prefixTable('misc') . ' WHERE type = %s AND intitule = %s', 'admin', 'agses_hosted_url');
1959
            $counter = DB::count();
1960
            if ($counter === 0) {
1961
                DB::insert(
1962
                    prefixTable('misc'),
1963
                    array(
1964
                        'type' => 'admin',
1965
                        'intitule' => 'agses_hosted_url',
1966
                        'valeur' => htmlspecialchars_decode($dataReceived['agses_hosted_url']),
1967
                        'created_at' => time(),
1968
                    )
1969
                );
1970
            } else {
1971
                DB::update(
1972
                    prefixTable('misc'),
1973
                    array(
1974
                        'valeur' => htmlspecialchars_decode($dataReceived['agses_hosted_url']),
1975
                        'updated_at' => time(),
1976
                    ),
1977
                    'type = %s AND intitule = %s',
1978
                    'admin',
1979
                    'agses_hosted_url'
1980
                );
1981
            }
1982
            $SETTINGS['agses_hosted_url'] = htmlspecialchars_decode($dataReceived['agses_hosted_url']);
1983
        } else {
1984
            $SETTINGS['agses_hosted_url'] = '';
1985
        }
1986
1987
        // agses_hosted_id
1988
        if (!is_null($dataReceived['agses_hosted_id'])) {
1989
            DB::query('SELECT * FROM ' . prefixTable('misc') . ' WHERE type = %s AND intitule = %s', 'admin', 'agses_hosted_id');
1990
            $counter = DB::count();
1991
            if ($counter === 0) {
1992
                DB::insert(
1993
                    prefixTable('misc'),
1994
                    array(
1995
                        'type' => 'admin',
1996
                        'intitule' => 'agses_hosted_id',
1997
                        'valeur' => htmlspecialchars_decode($dataReceived['agses_hosted_id']),
1998
                        'created_at' => time(),
1999
                    )
2000
                );
2001
            } else {
2002
                DB::update(
2003
                    prefixTable('misc'),
2004
                    array(
2005
                        'valeur' => htmlspecialchars_decode($dataReceived['agses_hosted_id']),
2006
                        'updated_at' => time(),
2007
                    ),
2008
                    'type = %s AND intitule = %s',
2009
                    'admin',
2010
                    'agses_hosted_id'
2011
                );
2012
            }
2013
            $SETTINGS['agses_hosted_id'] = htmlspecialchars_decode($dataReceived['agses_hosted_id']);
2014
        } else {
2015
            $SETTINGS['agses_hosted_id'] = '';
2016
        }
2017
2018
        // agses_hosted_apikey
2019
        if (!is_null($dataReceived['agses_hosted_apikey'])) {
2020
            DB::query('SELECT * FROM ' . prefixTable('misc') . ' WHERE type = %s AND intitule = %s', 'admin', 'agses_hosted_apikey');
2021
            $counter = DB::count();
2022
            if ($counter === 0) {
2023
                DB::insert(
2024
                    prefixTable('misc'),
2025
                    array(
2026
                        'type' => 'admin',
2027
                        'intitule' => 'agses_hosted_apikey',
2028
                        'valeur' => htmlspecialchars_decode($dataReceived['agses_hosted_apikey']),
2029
                        'created_at' => time(),
2030
                    )
2031
                );
2032
            } else {
2033
                DB::update(
2034
                    prefixTable('misc'),
2035
                    array(
2036
                        'valeur' => htmlspecialchars_decode($dataReceived['agses_hosted_apikey']),
2037
                        'updated_at' => time(),
2038
                    ),
2039
                    'type = %s AND intitule = %s',
2040
                    'admin',
2041
                    'agses_hosted_apikey'
2042
                );
2043
            }
2044
            $SETTINGS['agses_hosted_apikey'] = htmlspecialchars_decode($dataReceived['agses_hosted_apikey']);
2045
        } else {
2046
            $SETTINGS['agses_hosted_apikey'] = '';
2047
        }
2048
2049
        // send data
2050
        echo '[{"result" : "' . addslashes($lang['done']) . '" , "error" : ""}]';
2051
        break;
2052
2053
    case 'save_option_change':
2054
        // Check KEY and rights
2055
        if ($post_key !== $session->get('key')) {
2056
            echo prepareExchangedData(
2057
                array(
2058
                    'error' => true,
2059
                    'message' => $lang->get('key_is_not_correct'),
2060
                ),
2061
                'encode'
2062
            );
2063
            break;
2064
        }
2065
        
2066
        // decrypt and retreive data in JSON format
2067
        $dataReceived = prepareExchangedData(
2068
            $post_data,
2069
            'decode'
2070
        );
2071
        
2072
        // prepare data
2073
        $post_value = filter_var($dataReceived['value'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
2074
        $post_field = filter_var($dataReceived['field'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
2075
        $post_translate = isset($dataReceived['translate']) === true ? filter_var($dataReceived['translate'], FILTER_SANITIZE_FULL_SPECIAL_CHARS) : '';
2076
        
2077
        require_once 'main.functions.php';
2078
2079
        // In case of key, then encrypt it
2080
        if ($post_field === 'bck_script_passkey') {
2081
            $post_value = cryption(
2082
                $post_value,
2083
                '',
2084
                'encrypt',
2085
                $SETTINGS
2086
            )['string'];
2087
        }
2088
2089
        // Check if setting is already in DB. If NO then insert, if YES then update.
2090
        $data = DB::query(
2091
            'SELECT * FROM ' . prefixTable('misc') . '
2092
            WHERE type = %s AND intitule = %s',
2093
            'admin',
2094
            $post_field
2095
        );
2096
        $counter = DB::count();
2097
        if ($counter === 0) {
2098
            DB::insert(
2099
                prefixTable('misc'),
2100
                array(
2101
                    'valeur' => $post_value,
2102
                    'type' => 'admin',
2103
                    'intitule' => $post_field,
2104
                    'created_at' => time(),
2105
                )
2106
            );
2107
            // in case of stats enabled, add the actual time
2108
            if ($post_field === 'send_stats') {
2109
                DB::insert(
2110
                    prefixTable('misc'),
2111
                    array(
2112
                        'valeur' => time(),
2113
                        'type' => 'admin',
2114
                        'intitule' => $post_field . '_time',
2115
                        'updated_at' => time(),
2116
                    )
2117
                );
2118
            }
2119
        } else {
2120
            // Update DB settings
2121
            DB::update(
2122
                prefixTable('misc'),
2123
                array(
2124
                    'valeur' => $post_value,
2125
                    'updated_at' => time(),
2126
                ),
2127
                'type = %s AND intitule = %s',
2128
                'admin',
2129
                $post_field
2130
            );
2131
2132
            // in case of stats enabled, update the actual time
2133
            if ($post_field === 'send_stats') {
2134
                // Check if previous time exists, if not them insert this value in DB
2135
                DB::query(
2136
                    'SELECT * FROM ' . prefixTable('misc') . '
2137
                    WHERE type = %s AND intitule = %s',
2138
                    'admin',
2139
                    $post_field . '_time'
2140
                );
2141
                $counter = DB::count();
2142
                if ($counter === 0) {
2143
                    DB::insert(
2144
                        prefixTable('misc'),
2145
                        array(
2146
                            'valeur' => 0,
2147
                            'type' => 'admin',
2148
                            'intitule' => $post_field . '_time',
2149
                            'created_at' => time(),
2150
                        )
2151
                    );
2152
                } else {
2153
                    DB::update(
2154
                        prefixTable('misc'),
2155
                        array(
2156
                            'valeur' => 0,
2157
                            'updated_at' => time(),
2158
                        ),
2159
                        'type = %s AND intitule = %s',
2160
                        'admin',
2161
                        $post_field
2162
                    );
2163
                }
2164
            }
2165
        }
2166
2167
        // special Cases
2168
        if ($post_field === 'cpassman_url') {
2169
            // update also jsUrl for CSFP protection
2170
            $jsUrl = $post_value . '/includes/libraries/csrfp/js/csrfprotector.js';
2171
            $csrfp_file = '../includes/libraries/csrfp/libs/csrfp.config.php';
2172
            $data = file_get_contents($csrfp_file);
2173
            $posJsUrl = strpos($data, '"jsUrl" => "');
2174
            $posEndLine = strpos($data, '",', $posJsUrl);
2175
            $line = substr($data, $posJsUrl, ($posEndLine - $posJsUrl + 2));
2176
            $newdata = str_replace($line, '"jsUrl" => "' . filter_var($jsUrl, FILTER_SANITIZE_FULL_SPECIAL_CHARS) . '",', $data);
2177
            file_put_contents($csrfp_file, $newdata);
2178
        } elseif ($post_field === 'restricted_to_input' && (int) $post_value === 0) {
2179
            DB::update(
2180
                prefixTable('misc'),
2181
                array(
2182
                    'valeur' => 0,
2183
                    'updated_at' => time(),
2184
                ),
2185
                'type = %s AND intitule = %s',
2186
                'admin',
2187
                'restricted_to_roles'
2188
            );
2189
        }
2190
2191
        // Update last settings change timestamp
2192
        // This to ensure that the settings are refreshed in the session
2193
        $settings = $session->get('teampass-settings');
2194
        $settings['timestamp'] = time();
2195
        $session->set('teampass-settings', $settings);
2196
2197
        // Encrypt data to return
2198
        echo prepareExchangedData(
2199
            array(
2200
                'error' => false,
2201
                'misc' => $counter . ' ; ' . $SETTINGS[$post_field],
2202
                'message' => empty($post_translate) === false ? $lang->get($post_translate) : '',
2203
            ),
2204
            'encode'
2205
        );
2206
        break;
2207
2208
    case 'get_values_for_statistics':
2209
        // Check KEY and rights
2210
        if ($post_key !== $session->get('key')) {
2211
            echo prepareExchangedData(
2212
                array(
2213
                    'error' => true,
2214
                    'message' => $lang->get('key_is_not_correct'),
2215
                ),
2216
                'encode'
2217
            );
2218
            break;
2219
        }
2220
2221
        // Encrypt data to return
2222
        echo prepareExchangedData(
2223
            getStatisticsData($SETTINGS),
2224
            'encode'
2225
        );
2226
2227
        break;
2228
2229
    case 'save_sending_statistics':
2230
        // Check KEY and rights
2231
        if ($post_key !== $session->get('key')) {
2232
            echo prepareExchangedData(
2233
                array(
2234
                    'error' => true,
2235
                    'message' => $lang->get('key_is_not_correct'),
2236
                ),
2237
                'encode'
2238
            );
2239
            break;
2240
        }
2241
2242
        // send statistics
2243
        if (null !== $post_status) {
2244
            DB::query('SELECT * FROM ' . prefixTable('misc') . ' WHERE type = %s AND intitule = %s', 'admin', 'send_stats');
2245
            $counter = DB::count();
2246
            if ($counter === 0) {
2247
                DB::insert(
2248
                    prefixTable('misc'),
2249
                    array(
2250
                        'type' => 'admin',
2251
                        'intitule' => 'send_stats',
2252
                        'valeur' => $post_status,
2253
                        'created_at' => time(),
2254
                    )
2255
                );
2256
            } else {
2257
                DB::update(
2258
                    prefixTable('misc'),
2259
                    array(
2260
                        'valeur' => $post_status,
2261
                        'updated_at' => time(),
2262
                    ),
2263
                    'type = %s AND intitule = %s',
2264
                    'admin',
2265
                    'send_stats'
2266
                );
2267
            }
2268
            $SETTINGS['send_stats'] = $post_status;
2269
        } else {
2270
            $SETTINGS['send_stats'] = '0';
2271
        }
2272
2273
        // send statistics items
2274
        if (null !== $post_list) {
2275
            DB::query('SELECT * FROM ' . prefixTable('misc') . ' WHERE type = %s AND intitule = %s', 'admin', 'send_statistics_items');
2276
            $counter = DB::count();
2277
            if ($counter === 0) {
2278
                DB::insert(
2279
                    prefixTable('misc'),
2280
                    array(
2281
                        'type' => 'admin',
2282
                        'intitule' => 'send_statistics_items',
2283
                        'valeur' => $post_list,
2284
                        'created_at' => time(),
2285
                    )
2286
                );
2287
            } else {
2288
                DB::update(
2289
                    prefixTable('misc'),
2290
                    array(
2291
                        'valeur' => $post_list,
2292
                        'updated_at' => time(),
2293
                    ),
2294
                    'type = %s AND intitule = %s',
2295
                    'admin',
2296
                    'send_statistics_items'
2297
                );
2298
            }
2299
            $SETTINGS['send_statistics_items'] = $post_list;
2300
        } else {
2301
            $SETTINGS['send_statistics_items'] = '';
2302
        }
2303
2304
        // send data
2305
        echo '[{"error" : false}]';
2306
        break;
2307
2308
    case 'is_backup_table_existing':
2309
        // Check KEY and rights
2310
        if ($post_key !== $session->get('key')) {
2311
            echo prepareExchangedData(
2312
                array(
2313
                    'error' => true,
2314
                    'message' => $lang->get('key_is_not_correct'),
2315
                ),
2316
                'encode'
2317
            );
2318
            break;
2319
        }
2320
2321
        if (DB::query("SHOW TABLES LIKE '" . prefixTable('sk_reencrypt_backup') . "'")) {
2322
            if (DB::count() === 1) {
2323
                echo 1;
2324
            } else {
2325
                echo 0;
2326
            }
2327
        } else {
2328
            echo 0;
2329
        }
2330
2331
        break;
2332
2333
    case 'get_list_of_roles':
2334
        // Check KEY and rights
2335
        if ($post_key !== $session->get('key')) {
2336
            echo prepareExchangedData(
2337
                array(
2338
                    'error' => true,
2339
                    'message' => $lang->get('key_is_not_correct'),
2340
                ),
2341
                'encode'
2342
            );
2343
            break;
2344
        }
2345
2346
        // decrypt and retreive data in JSON format
2347
        $dataReceived = prepareExchangedData(
2348
            $post_data,
2349
            'decode'
2350
        );
2351
2352
        // prepare data
2353
        $sourcePage = filter_var($dataReceived['source_page'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
2354
        if ($sourcePage === 'ldap') {
2355
            $selected_administrated_by = isset($SETTINGS['ldap_new_user_is_administrated_by']) && $SETTINGS['ldap_new_user_is_administrated_by'] === '0' ? 1 : 0;
2356
            $selected_new_user_role = isset($SETTINGS['ldap_new_user_role']) && $SETTINGS['ldap_new_user_role'] === '0' ? 1 : 0;
2357
        } elseif ($sourcePage === 'oauth') {
2358
            $selected_administrated_by = isset($SETTINGS['oauth_new_user_is_administrated_by']) && $SETTINGS['oauth_new_user_is_administrated_by'] === '0' ? 1 : 0;
2359
            $selected_new_user_role = isset($SETTINGS['oauth_selfregistered_user_belongs_to_role']) && $SETTINGS['oauth_selfregistered_user_belongs_to_role'] === '0' ? 1 : '';
2360
        } else {
2361
            echo prepareExchangedData(
2362
                [], 
2363
                'encode'
2364
            );
2365
    
2366
            break;
2367
        }
2368
2369
        $json = array();
2370
        array_push(
2371
            $json,
2372
            array(
2373
                'id' => '0',
2374
                'title' => $lang->get('god'),
2375
                'selected_administrated_by' => $selected_administrated_by,
2376
                'selected_role' => $selected_new_user_role,
2377
            )
2378
        );
2379
2380
        $rows = DB::query(
2381
            'SELECT id, title
2382
                FROM ' . prefixTable('roles_title') . '
2383
                ORDER BY title ASC'
2384
        );
2385
        foreach ($rows as $record) {
2386
            if ($sourcePage === 'ldap') {
2387
                $selected_administrated_by = isset($SETTINGS['ldap_new_user_is_administrated_by']) && $SETTINGS['ldap_new_user_is_administrated_by'] === $record['id'] ? 1 : 0;
2388
                $selected_new_user_role = isset($SETTINGS['ldap_new_user_role']) && $SETTINGS['ldap_new_user_role'] === $record['id'] ? 1 : 0;
2389
            } elseif ($sourcePage === 'oauth') {
2390
                $selected_administrated_by = isset($SETTINGS['oauth_new_user_is_administrated_by']) && $SETTINGS['oauth_new_user_is_administrated_by'] === $record['id'] ? 1 : 0;
2391
                $selected_new_user_role = isset($SETTINGS['oauth_selfregistered_user_belongs_to_role']) && $SETTINGS['oauth_selfregistered_user_belongs_to_role'] === $record['id'] ? 1 : 0;
2392
            }
2393
            array_push(
2394
                $json,
2395
                array(
2396
                    'id' => $record['id'],
2397
                    'title' => addslashes($record['title']),
2398
                    'selected_administrated_by' => $selected_administrated_by,
2399
                    'selected_role' => $selected_new_user_role,
2400
                )
2401
            );
2402
        }
2403
2404
        echo prepareExchangedData(
2405
            $json, 
2406
            'encode'
2407
        );
2408
2409
        break;
2410
2411
    case 'save_user_change':
2412
        // Check KEY and rights
2413
        if ($post_key !== $session->get('key')) {
2414
            echo prepareExchangedData(
2415
                array(
2416
                    'error' => true,
2417
                    'message' => $lang->get('key_is_not_correct'),
2418
                ),
2419
                'encode'
2420
            );
2421
            break;
2422
        }
2423
2424
        // decrypt and retrieve data in JSON format
2425
        $dataReceived = prepareExchangedData(
2426
            $post_data,
2427
            'decode'
2428
        );
2429
2430
        $post_increment_id = isset($dataReceived['increment_id']) === true ? filter_var($dataReceived['increment_id'], FILTER_SANITIZE_NUMBER_INT) : '';
2431
        $post_field = filter_var($dataReceived['field'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
2432
        $post_value = filter_var($dataReceived['value'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
2433
2434
        if (is_numeric($post_increment_id) === false) {
2435
            echo prepareExchangedData(
2436
                array(
2437
                    'error' => true,
2438
                    'message' => $lang->get('no_user'),
2439
                ),
2440
                'encode'
2441
            );
2442
            break;
2443
        }
2444
        
2445
        //update.
2446
        DB::debugMode(false);
2447
        DB::update(
2448
            prefixTable('api'),
2449
            array(
2450
                $post_field => $post_value,
2451
            ),
2452
            'increment_id = %i',
2453
            (int) $post_increment_id
2454
        );
2455
        DB::debugMode(false);
2456
        //log
2457
        logEvents($SETTINGS, 'system', 'api_user_readonly', (string) $session->get('user-id'), $session->get('user-login'));
2458
2459
        echo prepareExchangedData(
2460
            array(
2461
                'error' => false,
2462
                'message' => '',
2463
            ),
2464
            'encode'
2465
        );
2466
2467
        break;
2468
    
2469
    case "tablesIntegrityCheck":
2470
        // Check KEY and rights
2471
        if ($post_key !== $session->get('key')) {
2472
            echo prepareExchangedData(
2473
                array(
2474
                    'error' => true,
2475
                    'message' => $lang->get('key_is_not_correct'),
2476
                ),
2477
                'encode'
2478
            );
2479
            break;
2480
        }
2481
2482
        $ret = tablesIntegrityCheck();
2483
        
2484
        echo prepareExchangedData(
2485
            array(
2486
                'error' => $ret['error'],
2487
                'message' => $ret['message'],
2488
                'tables' => json_encode($ret['array'], JSON_FORCE_OBJECT),
2489
            ),
2490
            'encode'
2491
        );
2492
2493
        break;
2494
2495
    case "filesIntegrityCheck":
2496
        // Check KEY and rights
2497
        if ($post_key !== $session->get('key')) {
2498
            echo prepareExchangedData(
2499
                array(
2500
                    'error' => true,
2501
                    'message' => $lang->get('key_is_not_correct'),
2502
                ),
2503
                'encode'
2504
            );
2505
            break;
2506
        }
2507
2508
        // New hash-based integrity check
2509
        $SETTINGS['cpassman_dir'] = rtrim($SETTINGS['cpassman_dir'], '/');
2510
        $ret = verifyFileHashes($SETTINGS['cpassman_dir'], __DIR__.'/../files_reference.txt');
2511
2512
        $ignoredFiles = DB::queryFirstField(
2513
            'SELECT valeur 
2514
            FROM ' . prefixTable('misc') . ' 
2515
            WHERE type = %s AND intitule = %s',
2516
            'admin',
2517
            'ignored_unknown_files'
2518
        );
2519
        $ignoredFilesKeys = !is_null($ignoredFiles) && !empty($ignoredFiles) ? json_decode($ignoredFiles) : [];
2520
        
2521
        echo prepareExchangedData(
2522
            array(
2523
                'error' => $ret['error'],
2524
                'message' => $ret['message'],
2525
                'files' => json_encode($ret['array'], JSON_FORCE_OBJECT),
2526
                'ignoredNumber' => count($ignoredFilesKeys),
2527
            ),
2528
            'encode'
2529
        );
2530
2531
        break;
2532
2533
    case "ignoreFile":
2534
        // Check KEY and rights
2535
        if ($post_key !== $session->get('key')) {
2536
            echo prepareExchangedData(
2537
                array(
2538
                    'error' => true,
2539
                    'message' => $lang->get('key_is_not_correct'),
2540
                ),
2541
                'encode'
2542
            );
2543
            break;
2544
        }
2545
2546
        // decrypt and retrieve data in JSON format
2547
        $dataReceived = prepareExchangedData(
2548
            $post_data,
2549
            'decode'
2550
        );
2551
2552
        $post_id = isset($dataReceived['id']) === true ? filter_var($dataReceived['id'], FILTER_SANITIZE_NUMBER_INT) : '';
2553
2554
        // Get ignored unknown files
2555
        $existingData = DB::queryFirstRow(
2556
            'SELECT valeur 
2557
            FROM ' . prefixTable('misc') . ' 
2558
            WHERE type = %s AND intitule = %s',
2559
            'admin',
2560
            'ignored_unknown_files'
2561
        );
2562
2563
        // Get the json list ignored unknown files
2564
        $unknownFilesArray = [];
2565
        if (!empty($existingData) && !empty($existingData['valeur'])) {
2566
            $unknownFilesArray = json_decode($existingData['valeur'], true) ?: [];
2567
        }
2568
2569
        // Add the new file to the list
2570
        $unknownFilesArray[] = $post_id;
2571
2572
        // Save the new list
2573
        DB::insertUpdate(
2574
            prefixTable('misc'),
2575
            [
2576
                'type' => 'admin',
2577
                'intitule' => 'ignored_unknown_files',
2578
                'valeur' => json_encode($unknownFilesArray),
2579
                'created_at' => time(),
2580
            ],
2581
            [
2582
                'valeur' => json_encode($unknownFilesArray),
2583
                'updated_at' => time(),
2584
            ]
2585
        );
2586
2587
        
2588
        echo prepareExchangedData(
2589
            array(
2590
                'error' => false,
2591
                'message' => '',
2592
            ),
2593
            'encode'
2594
        );
2595
2596
        break;
2597
2598
    case "deleteFilesIntegrityCheck":
2599
        // Check KEY and rights
2600
        if ($post_key !== $session->get('key')) {
2601
            echo prepareExchangedData(
2602
                array(
2603
                    'error' => true,
2604
                    'message' => $lang->get('key_is_not_correct'),
2605
                ),
2606
                'encode'
2607
            );
2608
            break;
2609
        }
2610
2611
        // Get the list of files to delete
2612
        $filesToDelete = DB::queryFirstField(
2613
            'SELECT valeur 
2614
            FROM ' . prefixTable('misc') . ' 
2615
            WHERE type = %s AND intitule = %s',
2616
            'admin',
2617
            'unknown_files'
2618
        );
2619
2620
        if (is_null($filesToDelete)) {
2621
            echo prepareExchangedData(
2622
                array(
2623
                    'deletionResults' => null,
2624
                ),
2625
                'encode'
2626
            );
2627
            break;
2628
        }
2629
        $referenceFiles = (array) json_decode($filesToDelete);
2630
        
2631
        //  Launch
2632
        $ret = deleteFiles($referenceFiles, true);
2633
        
2634
        echo prepareExchangedData(
2635
            array(
2636
                'deletionResults' => $ret,
2637
            ),
2638
            'encode'
2639
        );
2640
2641
        break;
2642
    
2643
}
2644
2645
/**
2646
 * Delete multiple files with cross-platform compatibility
2647
 * 
2648
 * This function deletes a list of files while ensuring compatibility
2649
 * across different operating systems (Windows, Linux, etc.)
2650
 * 
2651
 * @param array $files Array of file paths to delete
2652
 * @param bool $ignoreErrors If true, continues execution even if a file cannot be deleted
2653
 * @return array Results of deletion operations (success/failure for each file)
2654
 */
2655
function deleteFiles(array $files, bool $ignoreErrors = false): array
2656
{
2657
    $session = SessionManager::getSession();
2658
    $lang = new Language($session->get('user-language') ?? 'english');
2659
2660
    $results = [];
2661
    $fullPath = __DIR__ . '/../';
2662
    
2663
    foreach ($files as $file) {
2664
        // Normalize path separators for cross-platform compatibility
2665
        $normalizedPath = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $fullPath.$file);
2666
        
2667
        // Check if file exists
2668
        if (!file_exists($normalizedPath)) {
2669
            $results[$normalizedPath] = [
2670
                'success' => false,
2671
                'error' => $lang->get('file_not_exists')
2672
            ];
2673
            
2674
            if (!$ignoreErrors) {
2675
                return $results;
2676
            }
2677
            
2678
            continue;
2679
        }
2680
        
2681
        // Check if path is actually a file (not a directory)
2682
        if (!is_file($normalizedPath)) {
2683
            $results[$normalizedPath] = [
2684
                'success' => false,
2685
                'error' => $lang->get('path_not_a_file')
2686
            ];
2687
            
2688
            if (!$ignoreErrors) {
2689
                return $results;
2690
            }
2691
            
2692
            continue;
2693
        }
2694
        
2695
        // Check if file is writable (can be deleted)
2696
        if (!is_writable($normalizedPath)) {
2697
            $results[$normalizedPath] = [
2698
                'success' => false,
2699
                'error' => $lang->get('file_not_writable')
2700
            ];
2701
            
2702
            if (!$ignoreErrors) {
2703
                return $results;
2704
            }
2705
            
2706
            continue;
2707
        }
2708
        
2709
        // Try to delete the file
2710
        $deleteResult = '';//@unlink($normalizedPath);
2711
        
2712
        if ($deleteResult) {
2713
            $results[$normalizedPath] = [
2714
                'success' => true,
2715
                'error' => '',
2716
            ];
2717
        } else {
2718
            $results[$normalizedPath] = [
2719
                'success' => false,
2720
                'error' => $lang->get('failed_to_delete')
2721
            ];
2722
            
2723
            if (!$ignoreErrors) {
2724
                return $results;
2725
            }
2726
        }
2727
    }
2728
    
2729
    return $results;
2730
}
2731
2732
/**
2733
 * Check the integrity of the files
2734
 * 
2735
 * @param string $baseDir
2736
 * @return array
2737
 */
2738
function filesIntegrityCheck($baseDir): array
2739
{
2740
    $referenceFile = __DIR__ . '/../files_reference.txt';
2741
2742
    $unknownFiles = findUnknownFiles($baseDir, $referenceFile);
2743
2744
    if (empty($unknownFiles)) {
2745
        return [
2746
            'error' => false,
2747
            'array' => [],
2748
            'message' => ''
2749
        ];
2750
    }
2751
2752
    // Check if the files are in the integrity file
2753
    return [
2754
        'error' => true,
2755
        'array' => $unknownFiles,
2756
        'message' => ''
2757
    ];
2758
}
2759
2760
/*
2761
 * Get all files in a directory
2762
 * 
2763
 * @param string $dir
2764
 * @return array
2765
 */
2766
function getAllFiles($dir): array
2767
{
2768
    $files = [];
2769
    $excludeDirs = ['upload', 'files', 'install', '_tools', 'random_compat', 'avatars']; // Répertoires à exclure
2770
    $excludeFilePrefixes = ['csrfp.config.php', 'settings.php', 'version-commit.php', 'phpstan.neon']; // Fichiers à exclure par préfixe
2771
2772
    $iterator = new RecursiveIteratorIterator(
2773
        new RecursiveCallbackFilterIterator(
2774
            new RecursiveDirectoryIterator(
2775
                $dir,
2776
                FilesystemIterator::SKIP_DOTS
2777
            ),
2778
            function ($current, $key, $iterator) {
0 ignored issues
show
function(...) { /* ... */ } of type callable is incompatible with the type string expected by parameter $callback of RecursiveCallbackFilterIterator::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2778
            /** @scrutinizer ignore-type */ function ($current, $key, $iterator) {
Loading history...
2779
                // Ignore hidden files and folders
2780
                if ($current->getFilename()[0] === '.') {
2781
                    return false;
2782
                }
2783
                return true;
2784
            }
2785
        ),
2786
        RecursiveIteratorIterator::SELF_FIRST
2787
    );
2788
2789
    foreach ($iterator as $file) {
2790
        try {
2791
            if ($file->isFile()) {
2792
                $relativePath = str_replace($dir . DIRECTORY_SEPARATOR, '', $file->getPathname());
2793
                $relativePath = str_replace('\\', '/', $relativePath); // Normalisation Windows/Linux
2794
2795
                // Split relatif path into parts
2796
                $pathParts = explode('/', $relativePath);
2797
2798
                // Check if any part of the path is in the excludeDirs array
2799
                foreach ($pathParts as $part) {
2800
                    if (in_array($part, $excludeDirs, true)) {
2801
                        continue 2; // Skip the file
2802
                    }
2803
                }
2804
2805
                // Check if the file name starts with any of the prefixes in excludeFilePrefixes
2806
                $filename = basename($relativePath);
2807
                foreach ($excludeFilePrefixes as $prefix) {
2808
                    if (strpos($filename, $prefix) === 0) {
2809
                        continue 2; // Skip the file
2810
                    }
2811
                }
2812
2813
                // If OK then add to the list
2814
                $files[] = $relativePath;
2815
            }
2816
        } catch (UnexpectedValueException $e) {
2817
            continue; // Ignore the file if an error occurs
2818
        }
2819
    }
2820
2821
    return $files;
2822
}
2823
2824
/**
2825
 * Find unknown files
2826
 * 
2827
 * @param string $baseDir
2828
 * @param string $referenceFile
2829
 * @return array
2830
 */
2831
function findUnknownFiles($baseDir, $referenceFile): array
2832
{
2833
    $currentFiles = getAllFiles($baseDir);
2834
    $referenceFiles = file($referenceFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2835
2836
    // Remove empty lines and comments from the reference file
2837
    $unknownFiles = array_diff($currentFiles, $referenceFiles);
2838
2839
    // Save list to DB
2840
    DB::insertUpdate(
2841
        prefixTable('misc'),
2842
        array(
2843
            'type' => 'admin',
2844
            'intitule' => 'unknown_files',
2845
            'valeur' => json_encode($unknownFiles),
2846
            'created_at' => time(),
2847
        ),
2848
        [
2849
            'valeur' => json_encode($unknownFiles),
2850
            'updated_at' => time(),
2851
        ]
2852
    );
2853
2854
    // NOw remove ignored files from the list returned to user
2855
    // Get ignored files
2856
    $ignoredFiles = DB::queryFirstField(
2857
        'SELECT valeur 
2858
        FROM ' . prefixTable('misc') . ' 
2859
        WHERE type = %s AND intitule = %s',
2860
        'admin',
2861
        'ignored_unknown_files'
2862
    );
2863
    $ignoredFilesKeys = !is_null($ignoredFiles) && !empty($ignoredFiles) ? array_flip(json_decode($ignoredFiles)) : [];
2864
    $unknownFiles = array_diff_key($unknownFiles, $ignoredFilesKeys);
2865
2866
    return $unknownFiles;
2867
}
2868
2869
/**
2870
 * Check the integrity of the tables
2871
 * 
2872
 * @return array
2873
 */
2874
function tablesIntegrityCheck(): array
2875
{
2876
    // Get integrity tables file
2877
    $integrityTablesFile = TEAMPASS_ROOT_PATH . '/includes/tables_integrity.json';
2878
    if (file_exists($integrityTablesFile) === false) {
2879
        return [
2880
            'error' => true,
2881
            'message' => "Integrity file has not been found."
2882
        ];
2883
    }
2884
    // Convert json to array
2885
    $integrityTables = json_decode(file_get_contents($integrityTablesFile), true);
2886
    if (json_last_error() !== JSON_ERROR_NONE) {
2887
        return [
2888
            'error' => true,
2889
            'message' => "Integrity file is corrupted"
2890
        ];
2891
    }
2892
    // Get all tables
2893
    $tables = [];
2894
    foreach (DB::queryFirstColumn("SHOW TABLES") as $table) {
2895
        $tables[] = str_replace(DB_PREFIX, "", $table);;
2896
    }
2897
    // Prepare the integrity check
2898
    $tablesInError = [];
2899
    // Check if the tables are in the integrity file
2900
    foreach ($tables as $table) {
2901
        $tableFound = false;
2902
        $tableHash = "";
2903
        foreach ($integrityTables as $integrityTable) {
2904
            if ($integrityTable["table_name"] === $table) {
2905
                $tableFound = true;
2906
                $tableHash = $integrityTable["structure_hash"];
2907
                break; // Sortir de la boucle si la table est trouvée
2908
            }
2909
        }
2910
2911
        if ($tableFound === true) {
2912
            $createTable = DB::queryFirstRow("SHOW CREATE TABLE ".DB_PREFIX."$table");
2913
            $currentHash = hash('sha256', $createTable['Create Table']);
2914
            if ($currentHash !== $tableHash) {
2915
                $tablesInError[] = DB_PREFIX.$table;
2916
            }            
2917
        }
2918
    }
2919
    return [
2920
        'error' => false,
2921
        'array' => $tablesInError,
2922
        'message' => ""
2923
    ];
2924
}
2925
2926
/**
2927
 * Verify file integrity by checking presence & MD5 hashes.
2928
 *
2929
 * This function compares the current files in the project directory against a reference file
2930
 * containing expected file paths and their MD5 hashes. It reports any missing files or files
2931
 * whose hashes do not match the reference.
2932
 *
2933
 * Steps:
2934
 *  - Load reference file data (file => hash)
2935
 *  - Get all current files in the base directory
2936
 *  - For each file, check if it exists in the reference; if so, compare hashes
2937
 *  - Collect issues for missing or changed files
2938
 *
2939
 * @param string $baseDir        Base directory to scan for files
2940
 * @param string $referenceFile  Path to reference file with known hashes
2941
 * @return array                 Result with 'error', 'array' of issues, and 'message'
2942
 */
2943
function verifyFileHashes($baseDir, $referenceFile): array
2944
{
2945
    // Load reference data (file => hash)
2946
    $referenceData = parseReferenceFile($referenceFile);
2947
    // Get list of all current files in the project
2948
    $allFiles = getAllFiles($baseDir);
2949
    $issues = [];
2950
2951
    // Compare current files to reference
2952
    foreach ($allFiles as $file) {
2953
        // Check if file exists in reference list
2954
        if (!isset($referenceData[$file])) {
2955
            $issues[] = "<i>$file</i> is not listed in reference file";
2956
            continue;
2957
        }
2958
2959
        // Compare hashes
2960
        $expectedHash = $referenceData[$file];
2961
        $actualHash = md5_file($baseDir . '/' . $file);
2962
2963
        if ($expectedHash !== $actualHash) {
2964
            $issues[] = "$file (expected: <i>$expectedHash</i>, actual: <i>$actualHash</i>)";
2965
        }
2966
    }
2967
2968
    // Return summary
2969
    return [
2970
        'error' => !empty($issues),
2971
        'array' => $issues,
2972
        'message' => empty($issues) ? 'Project files integrity check is successful.' : 'Integrity issues found.'
2973
    ];
2974
}
2975
2976
/**
2977
 * Parse the reference file into an associative array.
2978
 *
2979
 * Each line in the reference file should be of the form: "path hash".
2980
 * The function returns an array mapping file paths to their expected MD5 hashes.
2981
 *
2982
 * @param string $referenceFile  Path to reference file
2983
 * @return array                 [ 'file/path' => 'md5hash' ]
2984
 */
2985
function parseReferenceFile($referenceFile) {
2986
    $data = [];
2987
    $lines = file($referenceFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2988
    foreach ($lines as $line) {
2989
        [$path, $hash] = explode(' ', $line, 2);
2990
        $data[$path] = trim($hash);
2991
    }
2992
    return $data;
2993
}
2994
2995
/**
2996
 * Get all files in a directory with their md5 hashes.
2997
 *
2998
 * Recursively scans a directory, returning an associative array of file paths
2999
 * (relative to $dir) mapped to their MD5 hashes. Paths are normalized for cross-platform compatibility.
3000
 *
3001
 * @param string $dir    Directory to scan
3002
 * @return array         [ 'file/path' => 'md5hash' ]
3003
 */
3004
function getAllFilesWithHashes(string $dir): array
3005
{
3006
    $files = [];
3007
    $iterator = new RecursiveIteratorIterator(
3008
        new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS)
3009
    );
3010
3011
    foreach ($iterator as $file) {
3012
        if ($file->isFile()) {
3013
            // Build relative path
3014
            $relativePath = str_replace($dir . DIRECTORY_SEPARATOR, '', $file->getPathname());
3015
            $relativePath = str_replace('\\', '/', $relativePath); // Normalize for Windows
3016
            // Calculate hash
3017
            $files[$relativePath] = md5_file($file->getPathname());
3018
        }
3019
    }
3020
    return $files;
3021
}