Issues (15)

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

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