1
|
<?php
|
2
|
/*
|
3
|
* autoconfigbackup.inc
|
4
|
*
|
5
|
* part of pfSense (https://www.pfsense.org)
|
6
|
* Copyright (c) 2008-2013 BSD Perimeter
|
7
|
* Copyright (c) 2013-2016 Electric Sheep Fencing
|
8
|
* Copyright (c) 2014-2025 Rubicon Communications, LLC (Netgate)
|
9
|
* All rights reserved.
|
10
|
*
|
11
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
12
|
* you may not use this file except in compliance with the License.
|
13
|
* You may obtain a copy of the License at
|
14
|
*
|
15
|
* http://www.apache.org/licenses/LICENSE-2.0
|
16
|
*
|
17
|
* Unless required by applicable law or agreed to in writing, software
|
18
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
19
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
20
|
* See the License for the specific language governing permissions and
|
21
|
* limitations under the License.
|
22
|
*/
|
23
|
|
24
|
require_once("config.inc");
|
25
|
require_once("filter.inc");
|
26
|
require_once("notices.inc");
|
27
|
|
28
|
global $acb_base_url;
|
29
|
$acb_base_url = "https://acb.netgate.com";
|
30
|
|
31
|
global $acb_last_backup_file;
|
32
|
$acb_last_backup_file = "/cf/conf/lastACBentry.txt";
|
33
|
|
34
|
global $acb_force_file;
|
35
|
$acb_force_file = "/tmp/forceacb";
|
36
|
|
37
|
/* Set up time zones for conversion. See #5250 */
|
38
|
global $acb_server_tz;
|
39
|
$acb_server_tz = new DateTimeZone('America/Chicago');
|
40
|
|
41
|
/* Backup reason strings for which ACB will not create remote backup entries */
|
42
|
global $acb_ignore_reasons;
|
43
|
$acb_ignore_reasons = [
|
44
|
'snort',
|
45
|
'pfblocker',
|
46
|
'minicron',
|
47
|
'merged in config',
|
48
|
'intermediate config write during package',
|
49
|
'acbupload.php',
|
50
|
'execacb.php'
|
51
|
];
|
52
|
|
53
|
/* Check a string to determine if it is a valid device key */
|
54
|
function is_valid_acb_device_key($dk) {
|
55
|
$dk = trim($dk);
|
56
|
if (!is_null($dk) &&
|
57
|
!empty($dk) &&
|
58
|
(strlen($dk) == 64) &&
|
59
|
ctype_xdigit($dk)) {
|
60
|
return true;
|
61
|
}
|
62
|
return false;
|
63
|
}
|
64
|
|
65
|
/* Check a string to determine if it is a valid revision identifier */
|
66
|
function is_valid_acb_revision($revision) {
|
67
|
/* The revision must be a valid date string */
|
68
|
/* Ensure this returns boolean true/false not a timestamp when true */
|
69
|
return (strtotime($revision) !== false);
|
70
|
}
|
71
|
|
72
|
/* Check if a reason string should be ignored by ACB. */
|
73
|
function is_acb_ignored_reason($reason) {
|
74
|
global $acb_ignore_reasons;
|
75
|
foreach ($acb_ignore_reasons as $term) {
|
76
|
if (stripos($reason, $term) !== false) {
|
77
|
return true;
|
78
|
}
|
79
|
}
|
80
|
return false;
|
81
|
}
|
82
|
|
83
|
/* Generate a new random device key */
|
84
|
function acb_generate_device_key() {
|
85
|
$keyoutput = "";
|
86
|
$keystatus = "";
|
87
|
exec("/bin/dd status=none if=/dev/random bs=4096 count=1 | /usr/bin/openssl sha256 | /usr/bin/cut -f2 -d' '", $keyoutput, $keystatus);
|
88
|
if (($keystatus == 0) &&
|
89
|
is_array($keyoutput)) {
|
90
|
$keyoutput = trim($keyoutput[0]);
|
91
|
|
92
|
if (is_valid_acb_device_key($keyoutput)) {
|
93
|
return $keyoutput;
|
94
|
}
|
95
|
}
|
96
|
return null;
|
97
|
}
|
98
|
|
99
|
/* Locate a legacy ACB key for a device, which is derived from the SSH key */
|
100
|
function get_acb_legacy_device_key() {
|
101
|
if (file_exists('/etc/ssh/ssh_host_ed25519_key.pub')) {
|
102
|
$pkey = file_get_contents("/etc/ssh/ssh_host_ed25519_key.pub");
|
103
|
// Check that the SSH key looks reasonable
|
104
|
if (substr($pkey, 0, 3) == "ssh") {
|
105
|
return hash("sha256", $pkey);
|
106
|
}
|
107
|
}
|
108
|
return null;
|
109
|
}
|
110
|
|
111
|
/* Locate and return the ACB device key for this installation. If there is no
|
112
|
* viable key, generate and store a new key. */
|
113
|
function get_acb_device_key() {
|
114
|
$config_device_key = config_get_path('system/acb/device_key');
|
115
|
|
116
|
/* If there is no device key in the configuration, check for a legacy key */
|
117
|
if (!is_valid_acb_device_key($config_device_key) &&
|
118
|
acb_enabled() &&
|
119
|
file_exists('/etc/ssh/ssh_host_ed25519_key.pub')) {
|
120
|
$config_device_key = get_acb_legacy_device_key();
|
121
|
}
|
122
|
|
123
|
/* Still no key, so generate a new random key */
|
124
|
if (!is_valid_acb_device_key($config_device_key)) {
|
125
|
$config_device_key = acb_generate_device_key();
|
126
|
/* Only store the key if it's valid */
|
127
|
if (is_valid_acb_device_key($config_device_key)) {
|
128
|
config_set_path('system/acb/device_key', $config_device_key);
|
129
|
write_config(gettext('Generated new randomized AutoConfigBackup device key'));
|
130
|
}
|
131
|
}
|
132
|
|
133
|
/* Still no valid key, something went wrong */
|
134
|
if (!is_valid_acb_device_key($config_device_key)) {
|
135
|
log_error(gettext('Unable to locate or generate a valid AutoConfigBackup device key'));
|
136
|
return null;
|
137
|
} else {
|
138
|
return $config_device_key;
|
139
|
}
|
140
|
}
|
141
|
|
142
|
/* Check whether ACB is enabled */
|
143
|
function acb_enabled() {
|
144
|
return (config_get_path('system/acb/enable', '') == "yes");
|
145
|
}
|
146
|
|
147
|
/* Check if this device can resolve the ACB hostname via DNS. */
|
148
|
function acb_check_dns() {
|
149
|
global $acb_base_url;
|
150
|
|
151
|
if (!resolve_address($acb_base_url)) {
|
152
|
acb_error_log(sprintf(gettext('Unable to resolve %s'),
|
153
|
parse_url($acb_base_url, PHP_URL_HOST)));
|
154
|
return false;
|
155
|
} else {
|
156
|
return true;
|
157
|
}
|
158
|
}
|
159
|
|
160
|
/* Change the time zone to reflect local time of ACB revisions.
|
161
|
* See Redmine #5250 */
|
162
|
function acb_time_shift($revision, $format = DATE_RFC2822) {
|
163
|
global $acbtz;
|
164
|
$budate = new DateTime($revision, $acbtz);
|
165
|
$mytz = new DateTimeZone(date_default_timezone_get());
|
166
|
$budate->setTimezone($mytz);
|
167
|
return htmlspecialchars($budate->format($format));
|
168
|
}
|
169
|
|
170
|
/*
|
171
|
* Query the ACB server via cURL and return the data
|
172
|
*
|
173
|
* Parameters:
|
174
|
* endpoint:
|
175
|
* Relative URL endpoint on the ACB service, not including the base
|
176
|
* hostname.
|
177
|
* postfields:
|
178
|
* Array containing post fields and their values to submit.
|
179
|
* multipart:
|
180
|
* True when submitting multi-part form data (e.g. save/upload)
|
181
|
*
|
182
|
* Returns:
|
183
|
* data:
|
184
|
* Content returned from the server
|
185
|
* httpcode:
|
186
|
* HTTP code returned by the server
|
187
|
*/
|
188
|
function acb_query_service($endpoint, $post_fields, $multipart = false) {
|
189
|
global $acb_base_url;
|
190
|
$url = "{$acb_base_url}/{$endpoint}";
|
191
|
|
192
|
/* Bail if passed invalid data */
|
193
|
if (empty($endpoint) ||
|
194
|
empty($post_fields) ||
|
195
|
!is_array($post_fields)) {
|
196
|
return [null, null, 1];
|
197
|
}
|
198
|
|
199
|
/* Add UID */
|
200
|
$post_fields['uid'] = system_get_uniqueid();
|
201
|
|
202
|
/* Store this now as it may be lost in the next step. */
|
203
|
$post_fields_count = count($post_fields);
|
204
|
if (!$multipart) {
|
205
|
$post_fields = http_build_query($post_fields);
|
206
|
}
|
207
|
|
208
|
$curl_session = curl_init();
|
209
|
curl_setopt($curl_session, CURLOPT_URL, $url);
|
210
|
curl_setopt($curl_session, CURLOPT_POST, $post_fields_count);
|
211
|
curl_setopt($curl_session, CURLOPT_POSTFIELDS, $post_fields);
|
212
|
curl_setopt($curl_session, CURLOPT_RETURNTRANSFER, 1);
|
213
|
curl_setopt($curl_session, CURLOPT_SSL_VERIFYPEER, 1);
|
214
|
curl_setopt($curl_session, CURLOPT_CONNECTTIMEOUT, 55);
|
215
|
curl_setopt($curl_session, CURLOPT_TIMEOUT, 30);
|
216
|
curl_setopt($curl_session, CURLOPT_USERAGENT, g_get('product_label') . '/' . rtrim(file_get_contents("/etc/version")));
|
217
|
// Proxy
|
218
|
set_curlproxy($curl_session);
|
219
|
|
220
|
$data = curl_exec($curl_session);
|
221
|
$httpcode = curl_getinfo($curl_session, CURLINFO_RESPONSE_CODE);
|
222
|
$errno = curl_errno($curl_session);
|
223
|
|
224
|
if ($errno) {
|
225
|
$fd = fopen("/tmp/acb_debug.txt", "w");
|
226
|
fwrite($fd, $url . "\n\n");
|
227
|
fwrite($fd, var_export($post_fields, true));
|
228
|
fwrite($fd, $data);
|
229
|
fwrite($fd, curl_error($curl_session));
|
230
|
fclose($fd);
|
231
|
} else {
|
232
|
curl_close($curl_session);
|
233
|
}
|
234
|
return [$data, $httpcode, $errno];
|
235
|
}
|
236
|
|
237
|
/* Check if a backup is necessary (has config changed since last upload) */
|
238
|
function is_acb_upload_needed() {
|
239
|
global $acb_last_backup_file;
|
240
|
|
241
|
if (file_exists($acb_last_backup_file)) {
|
242
|
$last_backup_date = trim(file_get_contents($acb_last_backup_file));
|
243
|
} else {
|
244
|
$last_backup_date = "";
|
245
|
}
|
246
|
|
247
|
return ($last_backup_date <> config_get_path('revision/time'));
|
248
|
}
|
249
|
|
250
|
/* Stage a config backup for uploading which will be picked up later by the
|
251
|
* acbupload.php cron job which performs the actual upload process.
|
252
|
*/
|
253
|
function acb_backup_stage_upload($manual = false) {
|
254
|
global $acb_base_url;
|
255
|
|
256
|
/* Do nothing when booting or when not enabled */
|
257
|
if (is_platform_booting() ||
|
258
|
!acb_enabled()) {
|
259
|
return;
|
260
|
}
|
261
|
|
262
|
/* Define required variables */
|
263
|
$userkey = get_acb_device_key();
|
264
|
$hostname = config_get_path('system/hostname') . "." . config_get_path('system/domain');
|
265
|
$reason = config_get_path('revision/description');
|
266
|
$manmax = config_get_path('system/acb/numman', '0');
|
267
|
$encryptpw = config_get_path('system/acb/encryption_password');
|
268
|
|
269
|
if (is_acb_ignored_reason($reason)) {
|
270
|
log_error(sprintf(gettext('Skipping staging AutoConfigBackup entry for ignored reason: %s.'), $reason));
|
271
|
return;
|
272
|
}
|
273
|
|
274
|
if (!$encryptpw) {
|
275
|
if (!file_exists("/cf/conf/autoconfigback.notice")) {
|
276
|
$notice_text = gettext('The Automatic Configuration Backup Encryption Password is not set. ' .
|
277
|
'Configure the Encryption Password at Services > AutoConfigBackup > Settings.');
|
278
|
log_error($notice_text);
|
279
|
file_notice("AutoConfigBackup", $notice_text, $notice_text, "");
|
280
|
touch("/cf/conf/autoconfigback.notice");
|
281
|
}
|
282
|
} else {
|
283
|
/* If the configuration has changed, upload to ACB service */
|
284
|
if (is_acb_upload_needed()) {
|
285
|
$notice_text = sprintf(gettext('Staging AutoConfigBackup encrypted configuration backup for deferred upload to %s'), $acb_base_url);
|
286
|
log_error($notice_text);
|
287
|
update_filter_reload_status($notice_text);
|
288
|
|
289
|
/* Encrypt config.xml contents */
|
290
|
$data = file_get_contents("/cf/conf/config.xml");
|
291
|
$raw_config_sha256_hash = trim(shell_exec("/sbin/sha256 /cf/conf/config.xml | /usr/bin/awk '{ print $4 }'"));
|
292
|
$data = encrypt_data($data, $encryptpw);
|
293
|
$tmpname = "/tmp/" . $raw_config_sha256_hash . ".tmp";
|
294
|
tagfile_reformat($data, $data, "config.xml");
|
295
|
file_put_contents($tmpname, $data);
|
296
|
|
297
|
/* Define backup metadata */
|
298
|
$post_fields = array(
|
299
|
'reason' => substr(htmlspecialchars($reason), 0, 1024),
|
300
|
'file' => curl_file_create($tmpname, 'image/jpg', 'config.jpg'),
|
301
|
'userkey' => htmlspecialchars($userkey),
|
302
|
'sha256_hash' => $raw_config_sha256_hash,
|
303
|
'version' => g_get('product_version'),
|
304
|
'hint' => substr(config_get_path('system/acb/hint'), 0, 255),
|
305
|
'manmax' => (int)$manmax
|
306
|
);
|
307
|
|
308
|
unlink_if_exists($tmpname);
|
309
|
|
310
|
/* Location to stage backup file pairs */
|
311
|
$acbuploadpath = g_get('acbbackuppath');
|
312
|
|
313
|
if (!is_dir($acbuploadpath)) {
|
314
|
mkdir($acbuploadpath);
|
315
|
}
|
316
|
|
317
|
file_put_contents($acbuploadpath . $post_fields['sha256_hash'] . ".form", json_encode($post_fields));
|
318
|
file_put_contents($acbuploadpath . $post_fields['sha256_hash'] . ".data", $data);
|
319
|
} else {
|
320
|
/* Debugging */
|
321
|
//log_error(gettext('No AutoConfigBackup action required.'));
|
322
|
}
|
323
|
}
|
324
|
}
|
325
|
|
326
|
/* Upload all backup entries staged by acb_backup_stage_upload(). */
|
327
|
function acb_backup_upload($basename) {
|
328
|
global $acb_base_url, $acb_last_backup_file;
|
329
|
|
330
|
/* Location of staged backup file pairs */
|
331
|
$acbuploadpath = g_get('acbbackuppath');
|
332
|
|
333
|
/* If the ACB service cannot be resolved, remove staged backup files
|
334
|
* and exit.
|
335
|
* The check function logs an error, no need to log an error manually.
|
336
|
*/
|
337
|
if (!acb_check_dns()) {
|
338
|
unlink_if_exists($acbuploadpath . $basename . ".data");
|
339
|
unlink_if_exists($acbuploadpath . $basename . ".form");
|
340
|
return;
|
341
|
}
|
342
|
|
343
|
/* Read the staged form file containing backup metadata */
|
344
|
$formdata = file_get_contents($acbuploadpath . $basename . ".form");
|
345
|
$post_fields = json_decode($formdata, true);
|
346
|
|
347
|
/* Check backup reason in metadata against ignore list */
|
348
|
if (is_acb_ignored_reason($post_fields['reason'])) {
|
349
|
log_error(sprintf(gettext('Skipping staged AutoConfigBackup entry for ignored reason: %s.'), $post_fields['reason']));
|
350
|
/* Delete the staged backup files */
|
351
|
unlink_if_exists($acbuploadpath . $basename . ".data");
|
352
|
unlink_if_exists($acbuploadpath . $basename . ".form");
|
353
|
return;
|
354
|
}
|
355
|
|
356
|
/* Add the encrytped backup data */
|
357
|
$post_fields['file'] = curl_file_create($acbuploadpath . $basename . ".data", 'image/jpg', 'config.jpg');
|
358
|
|
359
|
/* Upload encrypted backup entry and its metadata to the ACB service */
|
360
|
[$data, $httpcode, $errno] = acb_query_service("save", $post_fields, true);
|
361
|
|
362
|
/* Delete the staged backup files no matter the outcome */
|
363
|
unlink_if_exists($acbuploadpath . $basename . ".data");
|
364
|
unlink_if_exists($acbuploadpath . $basename . ".form");
|
365
|
|
366
|
if (strpos(strval($httpcode), '20') === false) {
|
367
|
if (empty($data) && $errno) {
|
368
|
$data = $errno;
|
369
|
} else {
|
370
|
$data = "Unknown error";
|
371
|
}
|
372
|
acb_error_log($data);
|
373
|
} else {
|
374
|
/* Update last ACB backup time */
|
375
|
$fd = fopen($acb_last_backup_file, "w");
|
376
|
fwrite($fd, config_get_path('revision/time'));
|
377
|
fclose($fd);
|
378
|
$notice_text = sprintf(gettext('Completed AutoConfigBackup encrypted configuration backup upload to %s (success)'), $acb_base_url);
|
379
|
log_error($notice_text);
|
380
|
update_filter_reload_status($notice_text);
|
381
|
}
|
382
|
}
|
383
|
|
384
|
/* Get a specific backup entry from the ACB service */
|
385
|
function acb_backup_get($userkey, $revision) {
|
386
|
$post_fields = [
|
387
|
'userkey' => $userkey,
|
388
|
'revision' => $revision,
|
389
|
'version' => g_get('product_version'),
|
390
|
];
|
391
|
return acb_query_service("getbkp", $post_fields);
|
392
|
}
|
393
|
|
394
|
/* Get metadata for a specific backup entry from the list as this
|
395
|
* metadata is not included when using the getbkp endpoint.
|
396
|
*/
|
397
|
function acb_backup_get_metadata($userkey, $revision) {
|
398
|
/* Reverse the list since the ACB server getbkp returns last match,
|
399
|
* otherwise if two entries have the same revision the metadata will
|
400
|
* not match. */
|
401
|
$backups = array_reverse(acb_backup_list($userkey));
|
402
|
foreach ($backups as $b) {
|
403
|
if ($b['time'] == $revision) {
|
404
|
return $b;
|
405
|
}
|
406
|
}
|
407
|
return [];
|
408
|
}
|
409
|
|
410
|
/* Decrypt the configuration data from an ACB service backup entry. */
|
411
|
function acb_backup_decrypt($data, $password) {
|
412
|
$errors = [];
|
413
|
|
414
|
$data_split = explode('++++', $data);
|
415
|
$sha256 = trim($data_split[0]);
|
416
|
$encrypted = $data_split[1];
|
417
|
|
418
|
if (!tagfile_deformat($encrypted, $encrypted, "config.xml")) {
|
419
|
$errors[] = gettext('The fetched backup entry does not appear to contain an encrypted configuration.');
|
420
|
}
|
421
|
$decrypted = decrypt_data($encrypted, $password);
|
422
|
if (!strstr($decrypted, "pfsense") ||
|
423
|
(strlen($decrypted) < 50)) {
|
424
|
$errors[] = gettext('Could not decrypt the fetched configuration backup entry. Check the encryption key and try again.');
|
425
|
} else {
|
426
|
$pos = stripos($decrypted, "</pfsense>");
|
427
|
$decrypted = substr($decrypted, 0, $pos);
|
428
|
$decrypted .= "</pfsense>\n";
|
429
|
}
|
430
|
|
431
|
return [$decrypted, $encrypted, $sha256, $errors];
|
432
|
}
|
433
|
|
434
|
/* Fetch a list of backups stored for a given device key on the ACB service */
|
435
|
function acb_backup_list($userkey) {
|
436
|
/* Separator used during client / server communications */
|
437
|
$oper_sep = "\|\|";
|
438
|
|
439
|
$backups = [];
|
440
|
|
441
|
$post_fields = [
|
442
|
'userkey' => $userkey,
|
443
|
'version' => g_get('product_version')
|
444
|
];
|
445
|
/* Fetch backup data for this device key from the ACB service */
|
446
|
[$data, $httpcode, $errno] = acb_query_service("list", $post_fields);
|
447
|
|
448
|
/* Loop through fetched data and create a backup list */
|
449
|
foreach (explode("\n", $data) as $ds) {
|
450
|
$ds_split = [];
|
451
|
preg_match("/^(.*?){$oper_sep}(.*){$oper_sep}(.*)/", $ds, $ds_split);
|
452
|
|
453
|
$tmp_array = [
|
454
|
'username' => $ds_split[1],
|
455
|
'reason' => $ds_split[2],
|
456
|
'time' => $ds_split[3],
|
457
|
'localtime' => acb_time_shift($ds_split[3])
|
458
|
];
|
459
|
|
460
|
if ($ds_split[3] && $ds_split[1]) {
|
461
|
$backups[] = $tmp_array;
|
462
|
}
|
463
|
}
|
464
|
|
465
|
return $backups;
|
466
|
}
|
467
|
|
468
|
/* Delete a specific backup entry from the ACB service */
|
469
|
function acb_backup_delete($userkey, $revision) {
|
470
|
global $acb_base_url;
|
471
|
|
472
|
$savemsg = "";
|
473
|
|
474
|
$post_fields = [
|
475
|
'userkey' => $userkey,
|
476
|
'revision' => $revision,
|
477
|
'version' => g_get('product_version'),
|
478
|
];
|
479
|
[$data, $httpcode, $errno] = acb_query_service("rmbkp", $post_fields);
|
480
|
|
481
|
if ($errno) {
|
482
|
$savemsg = sprintf(gettext('An error occurred while trying to remove the backup revision from %s'), $acb_base_url);
|
483
|
} else {
|
484
|
$savemsg = sprintf(gettext('Backup revision %s has been removed.'), acb_time_shift($revision));
|
485
|
}
|
486
|
return $savemsg;
|
487
|
}
|
488
|
|
489
|
/* Save the ACB configuration.
|
490
|
* Creates or removes ACB crontab entry for scheduled backups when necessary.
|
491
|
*/
|
492
|
function setup_ACB($enable, $hint, $frequency, $minute, $hours, $month, $day, $dow, $numman, $reverse, $pwd) {
|
493
|
/* Randomize the minutes if not specified */
|
494
|
if (!isset($minute) || strlen($minute) == 0 || $minute == "0") {
|
495
|
$minute = rand(1, 59);
|
496
|
}
|
497
|
|
498
|
config_set_path('system/acb/enable', $enable);
|
499
|
config_set_path('system/acb/hint', $hint);
|
500
|
config_set_path('system/acb/frequency', $frequency);
|
501
|
config_set_path('system/acb/minute', $minute);
|
502
|
config_set_path('system/acb/hour', $hours);
|
503
|
config_set_path('system/acb/month', $month);
|
504
|
config_set_path('system/acb/day', $day);
|
505
|
config_set_path('system/acb/dow', $dow);
|
506
|
config_set_path('system/acb/numman', $numman);
|
507
|
config_set_path('system/acb/reverse', $reverse);
|
508
|
if (strlen($pwd) >= 8) {
|
509
|
config_set_path('system/acb/encryption_password', $pwd);
|
510
|
}
|
511
|
|
512
|
/* Install or remove cron job for scheduled periodic backups. */
|
513
|
install_cron_job("/usr/bin/nice -n20 /usr/local/bin/php /usr/local/sbin/execacb.php",
|
514
|
($frequency == "cron"),
|
515
|
$minute,
|
516
|
is_numeric($hours) ? $hours : "*",
|
517
|
is_numeric($day) ? $day : "*",
|
518
|
is_numeric($month) ? $month : "*",
|
519
|
is_numeric($dow) ? $dow : "*"
|
520
|
);
|
521
|
|
522
|
/* Install or remove cron job for uploading staged backups */
|
523
|
install_cron_job("/usr/bin/nice -n20 /usr/local/bin/php /usr/local/sbin/acbupload.php",
|
524
|
($enable == "yes"),
|
525
|
"*");
|
526
|
|
527
|
write_config("AutoConfigBackup settings updated");
|
528
|
|
529
|
return config_get_path('system/acb');
|
530
|
}
|
531
|
|
532
|
/* Log ACB errors when necessary. */
|
533
|
function acb_error_log($data) {
|
534
|
global $acb_base_url;
|
535
|
$notice_text = sprintf(
|
536
|
gettext("An error occurred while uploading the encrypted %s configuration backup to %s (%s)"),
|
537
|
g_get('product_label'),
|
538
|
$acb_base_url,
|
539
|
htmlspecialchars($data));
|
540
|
log_error($notice_text . " - " . $data);
|
541
|
file_notice("AutoConfigBackup", $notice_text);
|
542
|
update_filter_reload_status($notice_text);
|
543
|
}
|
544
|
|
545
|
/* Generate a self-contained HTML download link for a device key string. */
|
546
|
function acb_key_download_link($name, $key) {
|
547
|
$hostname = config_get_path('system/hostname') . "." . config_get_path('system/domain');
|
548
|
$dltext = gettext('Download This Key');
|
549
|
$keystring = base64_encode($key . "\n");
|
550
|
|
551
|
return <<<EOL
|
552
|
<a download="acb_{$hostname}_{$name}_key.txt"
|
553
|
title="{$dltext}"
|
554
|
href="data:text/plain;base64,{$keystring}">
|
555
|
<i class="fa-solid fa-download"></i></a>
|
556
|
EOL;
|
557
|
|
558
|
}
|