Project

General

Profile

Download (29.2 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * xmlrpc.php
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-2013 BSD Perimeter
7
 * Copyright (c) 2013-2016 Electric Sheep Fencing
8
 * Copyright (c) 2014-2024 Rubicon Communications, LLC (Netgate)
9
 * Copyright (c) 2005 Colin Smith
10
 * All rights reserved.
11
 *
12
 * Licensed under the Apache License, Version 2.0 (the "License");
13
 * you may not use this file except in compliance with the License.
14
 * You may obtain a copy of the License at
15
 *
16
 * http://www.apache.org/licenses/LICENSE-2.0
17
 *
18
 * Unless required by applicable law or agreed to in writing, software
19
 * distributed under the License is distributed on an "AS IS" BASIS,
20
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
 * See the License for the specific language governing permissions and
22
 * limitations under the License.
23
 */
24

    
25
##|+PRIV
26
##|*IDENT=page-xmlrpclibrary
27
##|*NAME=XMLRPC Library
28
##|*DESCR=Allow access to the 'XMLRPC Library' page.
29
##|*MATCH=xmlrpc.php*
30
##|-PRIV
31

    
32
require_once("config.inc");
33
require_once("functions.inc");
34
require_once("auth.inc");
35
require_once("filter.inc");
36
require_once("ipsec.inc");
37
require_once("vpn.inc");
38
require_once("openvpn.inc");
39
require_once("captiveportal.inc");
40
require_once("shaper.inc");
41
require_once("XML/RPC2/Server.php");
42

    
43
class pfsense_xmlrpc_server {
44

    
45
	private $loop_detected = false;
46
	private $remote_addr;
47

    
48
	private function auth() {
49
		global $userindex;
50
		$userindex = index_users();
51

    
52
		$username = $_SERVER['PHP_AUTH_USER'];
53
		$password = $_SERVER['PHP_AUTH_PW'];
54

    
55
		$login_ok = false;
56
		if (!empty($username) && !empty($password)) {
57
			$attributes = array();
58
			$authcfg = auth_get_authserver(config_get_path('system/webgui/authmode'));
59

    
60
			if (authenticate_user($username, $password,
61
			    $authcfg, $attributes) ||
62
			    authenticate_user($username, $password)) {
63
				$login_ok = true;
64
			}
65
		}
66

    
67
		if (!$login_ok) {
68
			log_auth(sprintf(gettext("webConfigurator authentication error for user '%1\$s' from: %2\$s"),
69
			    $username,
70
			    $this->remote_addr));
71

    
72
			require_once("XML/RPC2/Exception.php");
73
			throw new XML_RPC2_FaultException(gettext(
74
			    'Authentication failed: Invalid username or password'),
75
			    -1);
76
		}
77

    
78
		$user_entry = getUserEntry($username);
79
		$user_entry = $user_entry['item'];
80
		/*
81
		 * admin (uid = 0) is allowed
82
		 * or regular user with necessary privilege
83
		 */
84
		if (isset($user_entry['uid']) && $user_entry['uid'] != '0' &&
85
		    !userHasPrivilege($user_entry, 'system-xmlrpc-ha-sync')) {
86
			log_auth("webConfigurator authentication error for '" .
87
			    $username . "' from " . $this->remote_addr .
88
			    " not enough privileges");
89

    
90
			require_once("XML/RPC2/Exception.php");
91
			throw new XML_RPC2_FaultException(gettext(
92
			    'Authentication failed: not enough privileges'),
93
			    -2);
94
		}
95

    
96
		return;
97
	}
98

    
99
	private function array_overlay($a1, $a2) {
100
		foreach ($a1 as $k => $v) {
101
			if (!array_key_exists($k, $a2)) {
102
				continue;
103
			}
104
			if (is_array($v) && is_array($a2[$k])) {
105
				$a1[$k] = $this->array_overlay($v, $a2[$k]);
106
			} else {
107
				$a1[$k] = $a2[$k];
108
			}
109
		}
110

    
111
		return $a1;
112
	}
113

    
114
	public function __construct() {
115
		$this->remote_addr = $_SERVER['REMOTE_ADDR'];
116

    
117
		/* grab sync to ip if enabled */
118
		if ((config_get_path('hasync/synchronizetoip') !== null) &&
119
		    config_get_path('hasync/synchronizetoip') == $this->remote_addr) {
120
			$this->loop_detected = true;
121
		}
122
	}
123

    
124
	/**
125
	 * Get host version information
126
	 *
127
	 * @return array
128
	 */
129
	public function host_firmware_version($dummy, $timeout) {
130
		ini_set('default_socket_timeout', $timeout);
131
		$this->auth();
132
		return host_firmware_version();
133
	}
134

    
135
	/**
136
	 * Executes a PHP block of code
137
	 *
138
	 * @param string $code
139
	 *
140
	 * @return bool
141
	 */
142
	public function exec_php($code) {
143
		$this->auth();
144

    
145
		eval($code);
146
		if ($toreturn) {
147
			return $toreturn;
148
		}
149

    
150
		return true;
151
	}
152

    
153
	/**
154
	 * Executes shell commands
155
	 *
156
	 * @param string $code
157
	 *
158
	 * @return bool
159
	 */
160
	public function exec_shell($code) {
161
		$this->auth();
162

    
163
		mwexec($code);
164
		return true;
165
	}
166

    
167
	/**
168
	 * Backup chosen config sections
169
	 *
170
	 * @param array $section
171
	 *
172
	 * @return array
173
	 */
174
	public function backup_config_section($section) {
175
		$this->auth();
176
		return array_intersect_key(config_get_path(''), array_flip($section));
177
	}
178

    
179
	/**
180
	 * Restore defined config section into local config
181
	 *
182
	 * @param array $sections
183
	 *
184
	 * @return bool
185
	 */
186
	public function restore_config_section($sections, $timeout) {
187
		ini_set('default_socket_timeout', $timeout);
188
		$this->auth();
189

    
190
		global $cpzone, $cpzoneid, $old_config;
191

    
192
		$old_config = config_get_path('');
193

    
194
		if ($this->loop_detected) {
195
			log_error("Disallowing CARP sync loop");
196
			return true;
197
		}
198

    
199
		/*
200
		 * Some sections should just be copied and not merged or we end
201
		 * up unable to sync the deletion of the last item in a section
202
		 */
203
		$sync_full_sections = array(
204
			'aliases',
205
			'ca',
206
			'cert',
207
			'crl',
208
			'dhcpd',
209
			'dhcrelay',
210
			'dhcrelay6',
211
			'dnshaper',
212
			'dnsmasq',
213
			'filter',
214
			'ipsec',
215
			'nat',
216
			'openvpn',
217
			'schedules',
218
			'shaper',
219
			'unbound',
220
			'wol',
221
		);
222

    
223
		$syncd_full_sections = array();
224

    
225
		foreach ($sync_full_sections as $section) {
226
			/* Do not test for empty here or removing final entry
227
			 * from a section will not work */
228
			if (!array_key_exists($section, $sections)) {
229
				continue;
230
			}
231

    
232
			config_set_path($section, array_get_path($sections, $section));
233
			array_del_path($sections, $section);
234
			$syncd_full_sections[] = $section;
235
		}
236

    
237
		/* If captive portal sync is enabled on primary node, remove local CP on the secondary */
238
		if (is_array($sections['captiveportal'])) {
239
			foreach (config_get_path('captiveportal', []) as $zone => $item) {
240
				if (!isset($sections['captiveportal'][$zone])) {
241
					$cpzone = $zone;
242
					config_del_path("captiveportal/{$cpzone}/enable");
243
					captiveportal_configure_zone(config_get_path("captiveportal/{$cpzone}"));
244
					config_del_path("captiveportal/{$cpzone}");
245
					config_del_path("voucher/{$cpzone}");
246
					unlink_if_exists("/var/db/captiveportal{$cpzone}.db");
247
					unlink_if_exists("/var/db/captiveportal_usedmacs_{$cpzone}.db");
248
					unlink_if_exists("/var/db/voucher_{$cpzone}_*.db");
249
				}
250
			}
251
		}
252

    
253
		$group_config = config_get_path('system/group', []);
254
		/* Only touch users if users are set to synchronize from the primary node
255
		 * See https://redmine.pfsense.org/issues/8450
256
		 */
257
		if ($sections['system']['user'] && $sections['system']['group']) {
258
			$g2add = array();
259
			$g2del = array();
260
			$g2del_idx = array();
261
			$g2keep = array();
262
			if (is_array($sections['system']['group'])) {
263
				$local_groups = $group_config;
264

    
265
				foreach ($sections['system']['group'] as $group) {
266
					$idx = array_search($group['name'],
267
					    array_column($local_groups, 'name'));
268

    
269
					if ($idx === false) {
270
						// section config group not found in local config
271
						$g2add[] = $group;
272
					} else if ($group['gid'] < 2000) {
273
						// section config group found in local config and is a special group
274
						$g2keep[] = $idx;
275
					} else if ($group != $local_groups[$idx]) {
276
						// section config group found in local config with different settings
277
						$g2add[] = $group;
278
						$g2del[] = $group;
279
						$g2del_idx[] = $idx;
280
					} else {
281
						// section config group found in local config and its settings are synced
282
						$g2keep[] = $idx;
283
					}
284
				}
285
			}
286
			if (is_array($group_config)) {
287
				foreach ($group_config as $idx => $group) {
288
					if (array_search($idx, $g2keep) === false &&
289
					    array_search($idx, $g2del_idx) === false) {
290
						// local config group not in section config group
291
						$g2del[] = $group;
292
						$g2del_idx[] = $idx;
293
					}
294
				}
295
			}
296
			unset($sections['system']['group'], $g2keep, $g2del_idx);
297

    
298
			$u2add = array();
299
			$u2del = array();
300
			$u2del_idx = array();
301
			$u2keep = array();
302
			$user_config = config_get_path('system/user', []);
303
			if (is_array($sections['system']['user'])) {
304
				$local_users = $user_config;
305

    
306
				foreach ($sections['system']['user'] as $user) {
307
					$idx = array_search($user['name'],
308
					    array_column($local_users, 'name'));
309

    
310
					if ($idx === false) {
311
						$u2add[] = $user;
312
					} else if (($user['uid'] < 2000) && ($sections['hasync']['adminsync'] != 'on')) {
313
						$u2keep[] = $idx;
314
					} else if ($user != $local_users[$idx]) {
315
						$u2add[] = $user;
316
						$u2del[] = $user;
317
						$u2del_idx[] = $idx;
318
					} else {
319
						$u2keep[] = $idx;
320
					}
321
				}
322
			}
323
			if (is_array($user_config)) {
324
				foreach ($user_config as $idx => $user) {
325
					if (array_search($idx, $u2keep) === false &&
326
					    array_search($idx, $u2del_idx) === false) {
327
						$u2del[] = $user;
328
						$u2del_idx[] = $idx;
329
					}
330
				}
331
			}
332
			unset($sections['system']['user'], $u2keep, $u2del_idx);
333
		}
334

    
335
		$voucher = array();
336
		if (is_array($sections['voucher'])) {
337
			/* Save voucher rolls to process after merge */
338
			$voucher = $sections['voucher'];
339

    
340
			foreach($sections['voucher'] as $zone => $item) {
341
				unset($sections['voucher'][$zone]['roll']);
342
				// Note : This code can be safely deleted once #97 fix has been applied and deployed to pfSense stable release.
343
				// Please do not delete this code before
344
				if (config_get_path("voucher/{$zone}/vouchersyncdbip") !== null) {
345
					$sections['voucher'][$zone]['vouchersyncdbip'] =
346
					    config_get_path("voucher/{$zone}/vouchersyncdbip");
347
				} else {
348
					unset($sections['voucher'][$zone]['vouchersyncdbip']);
349
				}
350
				if (config_get_path("voucher/{$zone}/vouchersyncusername") !== null) {
351
					$sections['voucher'][$zone]['vouchersyncusername'] =
352
					    config_get_path("voucher/{$zone}/vouchersyncusername");
353
				} else {
354
					unset($sections['voucher'][$zone]['vouchersyncusername']);
355
				}
356
				if (config_get_path("voucher/{$zone}/vouchersyncpass") !== null) {
357
					$sections['voucher'][$zone]['vouchersyncpass'] =
358
					    config_get_path("voucher/{$zone}/vouchersyncpass");
359
				} else {
360
					unset($sections['voucher'][$zone]['vouchersyncpass']);
361
				}
362
				// End note.
363
			}
364
		}
365

    
366
		if (is_array($sections['captiveportal'])) {
367
			// Captiveportal : Backward HA settings should remain local.
368
			foreach ($sections['captiveportal'] as $zone => $cp) {
369
				if (config_path_enabled("captiveportal/{$zone}", "enablebackwardsync")) {
370
					$sections['captiveportal'][$zone]['enablebackwardsync'] = config_get_path("captiveportal/{$zone}/enablebackwardsync");
371
				} else {
372
					unset($sections['captiveportal'][$zone]['enablebackwardsync']);
373
				}
374
				if (config_get_path("captiveportal/{$zone}/backwardsyncip") !== null) {
375
					$sections['captiveportal'][$zone]['backwardsyncip'] = config_get_path("captiveportal/{$zone}/backwardsyncip");
376
				} else {
377
					unset($sections['captiveportal'][$zone]['backwardsyncip']);
378
				}
379
				if (config_get_path("captiveportal/{$zone}/backwardsyncuser") !== null) {
380
					$sections['captiveportal'][$zone]['backwardsyncuser'] = config_get_path("captiveportal/{$zone}/backwardsyncuser");
381
				} else {
382
					unset($sections['captiveportal'][$zone]['backwardsyncuser']);
383
				}
384
				if (config_get_path("captiveportal/{$zone}/backwardsyncpassword") !== null) {
385
					$sections['captiveportal'][$zone]['backwardsyncpassword'] = config_get_path("captiveportal/{$zone}/backwardsyncpassword");
386
				} else {
387
					unset($sections['captiveportal'][$zone]['vouchersyncpass']);
388
				}
389
			}
390
			config_set_path('captiveportal', $sections['captiveportal']);
391
			unset($sections['captiveportal']);
392
		}
393

    
394
		$vipbackup = array();
395
		$oldvips = array();
396
		if (array_key_exists('virtualip', $sections)) {
397
			foreach (config_get_path('virtualip/vip', []) as $vip) {
398
				if (empty($vip)) {
399
					continue;
400
				}
401
				if ($vip['mode'] == "carp") {
402
					$key = $vip['interface'] .
403
					    "_vip" . $vip['vhid'];
404

    
405
					$oldvips[$key]['content'] =
406
					    $vip['password'] .
407
					    $vip['advskew'] .
408
					    $vip['subnet'] .
409
					    $vip['subnet_bits'] .
410
					    $vip['advbase'];
411
					$oldvips[$key]['interface'] =
412
					    $vip['interface'];
413
					$oldvips[$key]['subnet'] =
414
					    $vip['subnet'];
415
				} else if ($vip['mode'] == "ipalias" &&
416
				    (substr($vip['interface'], 0, 4) == '_vip'
417
				    || strstr($vip['interface'], "lo0"))) {
418
					$oldvips[$vip['subnet']]['content'] =
419
					    $vip['interface'] .
420
					    $vip['subnet'] .
421
					    $vip['subnet_bits'];
422
					$oldvips[$vip['subnet']]['interface'] =
423
					    $vip['interface'];
424
					$oldvips[$vip['subnet']]['subnet'] =
425
					    $vip['subnet'];
426
				} else if (($vip['mode'] == "ipalias" ||
427
				    $vip['mode'] == 'proxyarp') &&
428
				    !(substr($vip['interface'], 0, 4) == '_vip')
429
				    || strstr($vip['interface'], "lo0")) {
430
					$vipbackup[] = $vip;
431
				}
432
			}
433
		}
434

    
435
		/* For vip section, first keep items sent from the master */
436
		config_set_path('', array_merge_recursive_unique(config_get_path(''), $sections));
437

    
438
		/* Special handling for Kea HA, skip receiving certain settings from master */
439
		foreach (['kea', 'kea6'] as $kea) {
440
			if (array_path_enabled($old_config, $kea.'/ha', 'tls')) {
441
				config_set_path($kea.'/ha/tls', true);
442
				if ($value = array_get_path($old_config, $kea.'/ha/scertref')) {
443
					config_set_path($kea.'/ha/scertref', $value);
444
				} else {
445
					config_del_path($kea.'/ha/scertref');
446
				}
447
				if (array_path_enabled($old_config, $kea.'/ha', 'mutualtls')) {
448
					config_set_path($kea.'/ha/mutualtls', true);
449
					if ($value = array_get_path($old_config, $kea.'/ha/ccertref')) {
450
						config_set_path($kea.'/ha/ccertref', $value);
451
					} else {
452
						config_del_path($kea.'/ha/ccertref');
453
					}
454
				}
455
			} else {
456
				config_del_path($kea.'/ha/tls');
457
				config_del_path($kea.'/ha/scertref');
458
				config_del_path($kea.'/ha/mutualtls');
459
				config_del_path($kea.'/ha/ccertref');
460
			}
461

    
462
			if ($value = array_get_path($old_config, $kea.'/ha/localname')) {
463
				config_set_path($kea.'/ha/localname', $value);
464
			} else {
465
				config_del_path($kea.'/ha/localname');
466
			}
467
		}
468

    
469
		/* Remove locally items removed remote */
470
		foreach ($voucher as $zone => $item) {
471
			/* No rolls on master, delete local ones */
472
			if (!is_array($item['roll'])) {
473
				config_del_path("voucher/{$zone}/roll");
474
			}
475
		}
476

    
477
		$l_rolls = array();
478
		if (is_array(config_get_path('voucher'))) {
479
			foreach (config_get_path('voucher', []) as $zone => $item) {
480
				if (!is_array($item['roll'])) {
481
					continue;
482
				}
483
				foreach ($item['roll'] as $idx => $roll) {
484
					/* Make it easy to find roll by # */
485
					$l_rolls[$zone][$roll['number']] = $idx;
486
				}
487
			}
488
		}
489

    
490
		/*
491
		 * Process vouchers sent by primary node and:
492
		 * - Add new items
493
		 * - Update existing items based on 'lastsync' field
494
		 */
495
		foreach ($voucher as $zone => $item) {
496
			if (!is_array($item['roll'])) {
497
				continue;
498
			}
499
			config_init_path("voucher/{$zone}");
500
			$l_vouchers = config_get_path("voucher/{$zone}");
501
			foreach ($item['roll'] as $roll) {
502
				if (!isset($l_rolls[$zone][$roll['number']])) {
503
					$l_vouchers['roll'][] = $roll;
504
					continue;
505
				}
506
				$l_roll_idx = $l_rolls[$zone][$roll['number']];
507
				$l_roll = $l_vouchers['roll'][$l_roll_idx];
508
				if (!isset($l_roll['lastsync'])) {
509
					$l_roll['lastsync'] = 0;
510
				}
511

    
512
				if (isset($roll['lastsync']) &&
513
				    $roll['lastsync'] != $l_roll['lastsync']) {
514
					$l_vouchers['roll'][$l_roll_idx] =
515
					    $roll;
516
					unset($l_rolls[$zone][$roll['number']]);
517
				}
518
			}
519
			config_set_path("voucher/{$zone}", $l_vouchers);
520
		}
521

    
522
		/*
523
		 * At this point $l_rolls contains only items that are not
524
		 * present on primary node. They must be removed
525
		 */
526
		foreach ($l_rolls as $zone => $item) {
527
			foreach ($item as $idx) {
528
				config_del_path("voucher/{$zone}/{$idx}");
529
			}
530
		}
531

    
532
		/*
533
		 * Then add ipalias and proxyarp types already defined
534
		 * on the backup
535
		 */
536
		if (is_array($vipbackup) && !empty($vipbackup)) {
537
			$vips = config_get_path('virtualip/vip', []);
538
			foreach ($vipbackup as $vip) {
539
				array_unshift($vips, $vip);
540
			}
541
			config_set_path('virtualip/vip', $vips);
542
		}
543

    
544
		/* Log what happened */
545
		$mergedkeys = implode(", ", array_merge(array_keys($sections),
546
		    $syncd_full_sections));
547
		write_config(sprintf(gettext(
548
		    "Merged in config (%s sections) from XMLRPC client."),
549
		    $mergedkeys));
550

    
551
		/*
552
		 * The real work on handling the vips specially
553
		 * This is a copy of interfaces_vips_configure with addition of
554
		 * not reloading existing/not changed carps
555
		 */
556
		$force_filterconfigure = false;
557
		if (isset($sections['virtualip']) &&
558
		    is_array(config_get_path('virtualip/vip'))) {
559
			$carp_setuped = false;
560
			$anyproxyarp = false;
561

    
562
			foreach (config_get_path('virtualip/vip', []) as $vip) {
563
				$key = "{$vip['interface']}_vip{$vip['vhid']}";
564

    
565
				if ($vip['mode'] == "carp" &&
566
				    isset($oldvips[$key])) {
567
					if ($oldvips[$key]['content'] ==
568
					    $vip['password'] .
569
					    $vip['advskew'] .
570
					    $vip['subnet'] .
571
					    $vip['subnet_bits'] .
572
					    $vip['advbase'] &&
573
					    does_vip_exist($vip)) {
574
						unset($oldvips[$key]);
575
						/*
576
						 * Skip reconfiguring this vips
577
						 * since nothing has changed.
578
						 */
579
						continue;
580
					}
581

    
582
				} elseif ($vip['mode'] == "ipalias" &&
583
				    (substr($vip['interface'], 0, 4) == '_vip'
584
				    || strstr($vip['interface'], "lo0")) &&
585
				    isset($oldvips[$vip['subnet']])) {
586
					$key = $vip['subnet'];
587
					if ($oldvips[$key]['content'] ==
588
					    $vip['interface'] .
589
					    $vip['subnet'] .
590
					    $vip['subnet_bits'] &&
591
					    does_vip_exist($vip)) {
592
						unset($oldvips[$key]);
593
						/*
594
						 * Skip reconfiguring this vips
595
						 * since nothing has changed.
596
						 */
597
						continue;
598
					}
599
					unset($oldvips[$key]);
600
				}
601

    
602
				switch ($vip['mode']) {
603
				case "proxyarp":
604
					$anyproxyarp = true;
605
					break;
606
				case "ipalias":
607
					interface_ipalias_configure($vip);
608
					break;
609
				case "carp":
610
					$carp_setuped = true;
611
					if (does_vip_exist($vip) && isset($oldvips[$key]['vhid']) &&
612
					    ($oldvips[$key]['vhid'] ^ $vip['vhid'])) {
613
						/* properly remove the old VHID
614
						 * see https://redmine.pfsense.org/issues/12202 */
615
						$realif = get_real_interface($vip['interface']);
616
						mwexec("/sbin/ifconfig {$realif} " .
617
							escapeshellarg($vip['subnet']) . " -alias");
618
						$ipalias_reload = true;
619
					} else {
620
						$ipalias_reload = false;
621
					}
622
					interface_carp_configure($vip, false, $ipalias_reload);
623
					break;
624
				}
625
				$force_filterconfigure = true;
626
			}
627

    
628
			/* Cleanup remaining old carps */
629
			foreach ($oldvips as $oldvipar) {
630
				$oldvipif = get_real_interface(
631
				    $oldvipar['interface']);
632

    
633
				if (empty($oldvipif)) {
634
					continue;
635
				}
636

    
637
				/* do not remove VIP if the IP address remains the same */
638
				foreach (config_get_path('virtualip/vip', []) as $vip) {
639
					if ($vip['subnet'] == $oldvipar['subnet']) {
640
						continue 2;
641
					}
642
				}
643

    
644
				if (is_ipaddrv6($oldvipar['subnet'])) {
645
					 mwexec("/sbin/ifconfig " .
646
					     escapeshellarg($oldvipif) .
647
					     " inet6 " .
648
					     escapeshellarg($oldvipar['subnet']) .
649
					     " delete");
650
				} else {
651
					pfSense_interface_deladdress($oldvipif,
652
					    $oldvipar['subnet']);
653
				}
654
			}
655
			if ($carp_setuped == true) {
656
				interfaces_sync_setup();
657
			}
658
			if ($anyproxyarp == true) {
659
				interface_proxyarp_configure();
660
			}
661
		}
662

    
663
		local_sync_accounts($u2add, $u2del, $g2add, $g2del);
664
		$this->filter_configure(false, $force_filterconfigure);
665
		unset($old_config);
666

    
667
		return true;
668
	}
669

    
670
	/**
671
	 * Merge items into installedpackages config section
672
	 *
673
	 * @param array $section
674
	 *
675
	 * @return bool
676
	 */
677
	public function merge_installedpackages_section($section, $timeout) {
678
		ini_set('default_socket_timeout', $timeout);
679
		$this->auth();
680

    
681
		if ($this->loop_detected) {
682
			log_error("Disallowing CARP sync loop");
683
			return true;
684
		}
685

    
686
		config_set_path('installedpackages', array_merge(
687
		    config_get_path('installedpackages'), $section));
688
		$mergedkeys = implode(", ", array_keys($section));
689
		write_config(sprintf(gettext(
690
		    "Merged in config (%s sections) from XMLRPC client."),
691
		    $mergedkeys));
692

    
693
		return true;
694
	}
695

    
696
	/**
697
	 * Merge items into config
698
	 *
699
	 * @param array $section
700
	 *
701
	 * @return bool
702
	 */
703
	public function merge_config_section($section, $timeout) {
704
		ini_set('default_socket_timeout', $timeout);
705
		$this->auth();
706

    
707
		if ($this->loop_detected) {
708
			log_error("Disallowing CARP sync loop");
709
			return true;
710
		}
711

    
712
		config_set_path('', $this->array_overlay(config_get_path(''), $section));
713
		$mergedkeys = implode(", ", array_keys($section));
714
		write_config(sprintf(gettext(
715
		    "Merged in config (%s sections) from XMLRPC client."),
716
		    $mergedkeys));
717

    
718
		return true;
719
	}
720

    
721
	/**
722
	 * Wrapper for filter_configure()
723
	 *
724
	 * @return bool
725
	 */
726
	private function filter_configure($reset_accounts = true, $force = false) {
727
		global $g, $old_config;
728

    
729
		filter_configure();
730
		system_routing_configure();
731
		setup_gateways_monitor();
732

    
733
		/* do not restart unchanged services on XMLRPC sync,
734
		 * see https://redmine.pfsense.org/issues/11082 
735
		 */
736
		if (is_array(config_get_path('openvpn')) || is_array($old_config['openvpn'])) {
737
			foreach (array("server", "client") as $type) {
738
				$remove_id = array();
739
				if (is_array(array_get_path($old_config, "openvpn/openvpn-{$type}"))) {
740
					foreach ($old_config['openvpn']["openvpn-{$type}"] as $old_settings) {
741
						$remove_id[] = $old_settings['vpnid'];
742
					}
743
				}
744
				if (!is_array(config_get_path("openvpn/openvpn-{$type}"))) {
745
					continue;
746
				}
747
				foreach (config_get_path("openvpn/openvpn-{$type}", []) as $settings) {
748
					$new_instance = true;
749
					if (in_array($settings['vpnid'], $remove_id)) {
750
						$remove_id = array_diff($remove_id, array($settings['vpnid']));
751
					}
752
					if (is_array(array_get_path($old_config, "openvpn/openvpn-{$type}"))) {
753
						foreach ($old_config['openvpn']["openvpn-{$type}"] as $old_settings) {
754
							if ($settings['vpnid'] == $old_settings['vpnid']) {
755
								$new_instance = false;
756
								if (($settings != $old_settings) || $force) {
757
									/* restart changed openvpn instance */
758
									openvpn_resync($type, $settings);
759
									break;
760
								}
761
							}
762
						}
763
					}
764
					if ($new_instance) {
765
						/* start new openvpn instance */
766
						openvpn_resync($type, $settings);
767
					}
768
				}
769
				if (!empty($remove_id)) {
770
					foreach ($remove_id as $id) {
771
						/* stop/delete removed openvpn instances */
772
						openvpn_delete($type, array('vpnid' => $id));
773
					}
774
				}
775
			}
776
			/* no service restart required */
777
			openvpn_resync_csc_all();
778
		}
779

    
780
		/* run ipsec_configure() on any IPsec change, see https://redmine.pfsense.org/issues/12075 */
781
		if (((is_array(config_get_path('ipsec')) || is_array($old_config['ipsec'])) &&
782
		    (config_get_path('ipsec') != $old_config['ipsec'])) ||
783
		    $force) {
784
			ipsec_configure();
785
		}
786

    
787
		/*
788
		 * The DNS Resolver and the DNS Forwarder may both be active so
789
		 * long as * they are running on different ports.
790
		 * See ticket #5882
791
		 */
792
		if (((is_array(config_get_path('dnsmasq')) || is_array($old_config['dnsmasq'])) &&
793
		    (config_get_path('dnsmasq') != $old_config['dnsmasq'])) ||
794
		    $force) {
795
			if (config_path_enabled('dnsmasq')) {
796
				/* Configure dnsmasq but tell it NOT to restart DHCP */
797
				services_dnsmasq_configure(false);
798
			} else {
799
				/* kill any running dnsmasq instance */
800
				if (isvalidpid("{$g['varrun_path']}/dnsmasq.pid")) {
801
					sigkillbypid("{$g['varrun_path']}/dnsmasq.pid",
802
					    "TERM");
803
				}
804
			}
805
		}
806
		if (((is_array(config_get_path('unbound')) || is_array($old_config['unbound'])) &&
807
		    (config_get_path('unbound') != $old_config['unbound'])) ||
808
		    $force) {
809
			if (config_path_enabled('unbound')) {
810
				/* Configure unbound but tell it NOT to restart DHCP */
811
				services_unbound_configure(false);
812
			} else {
813
				/* kill any running Unbound instance */
814
				if (isvalidpid("{$g['varrun_path']}/unbound.pid")) {
815
					sigkillbypid("{$g['varrun_path']}/unbound.pid",
816
					    "TERM");
817
				}
818
			}
819
		}
820

    
821
		/*
822
		 * Call this separately since the above are manually set to
823
		 * skip the DHCP restart they normally perform.
824
		 * This avoids restarting dhcpd twice as described on
825
		 * ticket #3797
826
		 */
827
		$called = [];
828
		foreach ([
829
			'dhcpd'			=> 'services_dhcpd_configure',
830
			'dhcpdv6'		=> 'services_dhcpd_configure',
831
			'kea'			=> 'services_dhcpd_configure',
832
			'kea6'			=> 'services_dhcpd_configure',
833
			'dhcrelay'		=> 'services_dhcrelay_configure',
834
			'dhcrelay6'		=> 'services_dhcrelay6_configure',
835
			'captiveportal'	=> 'captiveportal_configure',
836
			'voucher'		=> 'voucher_configure'
837
		] as $path => $fn) {
838
			if (!array_key_exists($fn, $called)) {
839
				if (((is_array(config_get_path($path)) || is_array($old_config[$path])) &&
840
			        (config_get_path($path) !== array_get_path($old_config, $path))) || $force) {
841
					if (is_callable($fn)) {
842
						$fn();
843
					}
844
					$called[$fn] = true;
845
				}
846
			}
847
		}
848

    
849
		if ($reset_accounts) {
850
			local_reset_accounts();
851
		}
852

    
853
		return true;
854
	}
855

    
856
	/**
857
	 * Wrapper for captiveportal connected users and
858
	 * active/expired vouchers synchronization
859
	 *
860
	 * @param array $arguments
861
	 *
862
	 * @return array|bool|null
863
	 */
864
	public function captive_portal_sync($arguments, $timeout) {
865
		ini_set('default_socket_timeout', $timeout);
866
		$this->auth();
867
		// Note : no protection against CARP loop is done here, and this is in purpose.
868
		// This function is used for bi-directionnal sync, which is precisely what CARP loop protection is supposed to prevent.
869
		// CARP loop has to be managed within functions using captive_portal_sync()
870
		global $g, $cpzone;
871

    
872
		if (empty($arguments['op']) || empty($arguments['zone']) || empty(config_get_path("captiveportal/{$arguments['zone']}"))) {
873
			return false;
874
		}
875
		$cpzone = $arguments['zone'];
876

    
877
		if ($arguments['op'] === 'get_databases') {
878
			$active_vouchers = [];
879
			$expired_vouchers = [];
880
			$usedmacs = [];
881

    
882
			foreach(config_get_path("voucher/{$cpzone}/roll", []) as $roll) {
883
				$expired_vouchers[$roll['number']] = base64_encode(voucher_read_used_db($roll['number']));
884
				$active_vouchers[$roll['number']] = voucher_read_active_db($roll['number']);
885
			}
886
			if (!empty(config_get_path("captiveportal/{$cpzone}/freelogins_count")) &&
887
			    !empty(config_get_path("captiveportal/{$cpzone}/freelogins_resettimeout"))) {
888
				$usedmacs = captiveportal_read_usedmacs_db();
889
			}
890
			// base64 is here for safety reasons, as we don't fully control
891
			// the content of these arrays.
892
			$returndata = array('connected_users' => base64_encode(serialize(captiveportal_read_db())),
893
			'active_vouchers' => base64_encode(serialize($active_vouchers)),
894
			'expired_vouchers' => base64_encode(serialize($expired_vouchers)),
895
			'usedmacs' => base64_encode(serialize($usedmacs)));
896

    
897
			return $returndata;
898
		} elseif ($arguments['op'] === 'connect_user') {
899
			$user = unserialize_data(base64_decode($arguments['user']), []);
900
			$user['attributes']['allow_time'] = $user['allow_time'];
901

    
902
			// pipeno might be different between primary and secondary
903
			$pipeno = captiveportal_get_next_dn_ruleno('auth');
904
			return portal_allow($user['clientip'], $user['clientmac'], $user['username'], $user['password'], null,
905
			    $user['attributes'], $pipeno, $user['authmethod'], $user['context'], $user['sessionid']);
906
		} elseif ($arguments['op'] === 'disconnect_user') {
907
			$session = unserialize_data(base64_decode($arguments['session']), []);
908
			/* read database again, as pipeno might be different between primary & secondary */
909
			$sessionid = SQLite3::escapeString($session['sessionid']);
910
			$local_dbentry = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
911

    
912
			if (!empty($local_dbentry) && count($local_dbentry) == 1) {
913
				return captiveportal_disconnect($local_dbentry[0], $session['term_cause'], $session['stop_time'], true);
914
			} else {
915
				return false;
916
			}
917
		} elseif ($arguments['op'] === 'remove_entries') {
918
			$entries = unserialize_data(base64_decode($arguments['entries']), []);
919

    
920
			return captiveportal_remove_entries($entries, true);
921
		} elseif ($arguments['op'] === 'disconnect_all') {
922
			$arguments = unserialize_data(base64_decode($arguments['arguments']), []);
923

    
924
			return captiveportal_disconnect_all($arguments['term_cause'], $arguments['logout_reason'], true);
925
		} elseif ($arguments['op'] === 'write_vouchers') {
926
			$arguments = unserialize_data(base64_decode($arguments['arguments']), []);
927

    
928
			if (is_array($arguments['active_and_used_vouchers_bitmasks'])) {
929
				foreach ($arguments['active_and_used_vouchers_bitmasks'] as $roll => $used) {
930
					if (is_array($used)) {
931
						foreach ($used as $u) {
932
							voucher_write_used_db($roll, base64_encode($u));
933
						}
934
					} else {
935
						voucher_write_used_db($roll, base64_encode($used));
936
					}
937
				}
938
			}
939
			foreach ($arguments['active_vouchers'] as $roll => $active_vouchers) {
940
				voucher_write_active_db($roll, $active_vouchers);
941
			}
942
			return true;
943
		} elseif ($arguments['op'] === 'write_usedmacs') {
944
			$arguments = unserialize_data(base64_decode($arguments['arguments']), []);
945

    
946
			captiveportal_write_usedmacs_db($arguments['usedmacs']); 
947
			return true;
948
		}
949
	}
950

    
951
	/**
952
	 * Wrapper for configuring CARP interfaces
953
	 *
954
	 * @return bool
955
	 */
956
	public function interfaces_carp_configure() {
957
		$this->auth();
958

    
959
		if ($this->loop_detected) {
960
			log_error("Disallowing CARP sync loop");
961
			return true;
962
		}
963

    
964
		interfaces_vips_configure();
965

    
966
		return true;
967
	}
968

    
969
	/**
970
	 * Wrapper for rc.reboot
971
	 *
972
	 * @return bool
973
	 */
974
	public function reboot() {
975
		$this->auth();
976

    
977
		mwexec_bg("/etc/rc.reboot");
978

    
979
		return true;
980
	}
981
}
982

    
983
// run script until its done and can 'unlock' the xmlrpc.lock, this prevents hanging php-fpm / webgui
984
ignore_user_abort(true);
985
set_time_limit(0);
986

    
987
$xmlrpclockkey = lock('xmlrpc', LOCK_EX);
988

    
989
XML_RPC2_Backend::setBackend('php');
990
$HTTP_RAW_POST_DATA = file_get_contents('php://input');
991

    
992
$options = array(
993
	'prefix' => 'pfsense.',
994
	'encoding' => 'utf-8',
995
	'autoDocument' => false,
996
);
997

    
998
$server = XML_RPC2_Server::create(new pfsense_xmlrpc_server(), $options);
999
$server->handleCall();
1000

    
1001
unlock($xmlrpclockkey);
1002

    
1003
?>
(232-232/232)