Project

General

Profile

Download (30 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
		/* Extract and save any package sections before merging other sections */
436
		$pkg_sections = ['installedpackages' => array_get_path($sections, 'installedpackages', [])];
437
		array_del_path($sections, 'installedpackages');
438

    
439
		/* For vip section, first keep items sent from the master */
440
		config_set_path('', array_merge_recursive_unique(config_get_path(''), $sections));
441

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

    
466
			if ($value = array_get_path($old_config, $kea.'/ha/localname')) {
467
				config_set_path($kea.'/ha/localname', $value);
468
			} else {
469
				config_del_path($kea.'/ha/localname');
470
			}
471
		}
472

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

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

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

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

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

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

    
548
		/* xmlrpc_recv plugin expects path => value pairs of changed nodes, not an associative tree */
549
		$pkg_merged_paths = pkg_call_plugins("plugin_xmlrpc_recv", $pkg_sections);
550
		foreach ($pkg_merged_paths as $pkg => $sections) {
551
			if (!is_array($sections)) {
552
				log_error('Package {$pkg} xmlrpc_recv plugin returned invalid value.');
553
				continue;
554
			}
555
			foreach ($sections as $path => $section) {
556
				if (is_null(config_set_path($path, $section))) {
557
					log_error('Could not write section {$path} supplied by package {$pkg} xmlrpc_recv plugin');
558
				}
559
			}
560
		}
561

    
562
		/* Log what happened */
563
		$mergedkeys = implode(", ", array_merge(array_keys($sections),
564
		    $syncd_full_sections));
565
		write_config(sprintf(gettext(
566
		    "Merged in config (%s sections) from XMLRPC client."),
567
		    $mergedkeys));
568

    
569
		/*
570
		 * The real work on handling the vips specially
571
		 * This is a copy of interfaces_vips_configure with addition of
572
		 * not reloading existing/not changed carps
573
		 */
574
		$force_filterconfigure = false;
575
		if (isset($sections['virtualip']) &&
576
		    is_array(config_get_path('virtualip/vip'))) {
577
			$carp_setuped = false;
578
			$anyproxyarp = false;
579

    
580
			foreach (config_get_path('virtualip/vip', []) as $vip) {
581
				$key = "{$vip['interface']}_vip{$vip['vhid']}";
582

    
583
				if ($vip['mode'] == "carp" &&
584
				    isset($oldvips[$key])) {
585
					if ($oldvips[$key]['content'] ==
586
					    $vip['password'] .
587
					    $vip['advskew'] .
588
					    $vip['subnet'] .
589
					    $vip['subnet_bits'] .
590
					    $vip['advbase'] &&
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

    
600
				} elseif ($vip['mode'] == "ipalias" &&
601
				    (substr($vip['interface'], 0, 4) == '_vip'
602
				    || strstr($vip['interface'], "lo0")) &&
603
				    isset($oldvips[$vip['subnet']])) {
604
					$key = $vip['subnet'];
605
					if ($oldvips[$key]['content'] ==
606
					    $vip['interface'] .
607
					    $vip['subnet'] .
608
					    $vip['subnet_bits'] &&
609
					    does_vip_exist($vip)) {
610
						unset($oldvips[$key]);
611
						/*
612
						 * Skip reconfiguring this vips
613
						 * since nothing has changed.
614
						 */
615
						continue;
616
					}
617
					unset($oldvips[$key]);
618
				}
619

    
620
				switch ($vip['mode']) {
621
				case "proxyarp":
622
					$anyproxyarp = true;
623
					break;
624
				case "ipalias":
625
					interface_ipalias_configure($vip);
626
					break;
627
				case "carp":
628
					$carp_setuped = true;
629
					if (does_vip_exist($vip) && isset($oldvips[$key]['vhid']) &&
630
					    ($oldvips[$key]['vhid'] ^ $vip['vhid'])) {
631
						/* properly remove the old VHID
632
						 * see https://redmine.pfsense.org/issues/12202 */
633
						$realif = get_real_interface($vip['interface']);
634
						mwexec("/sbin/ifconfig {$realif} " .
635
							escapeshellarg($vip['subnet']) . " -alias");
636
						$ipalias_reload = true;
637
					} else {
638
						$ipalias_reload = false;
639
					}
640
					interface_carp_configure($vip, false, $ipalias_reload);
641
					break;
642
				}
643
				$force_filterconfigure = true;
644
			}
645

    
646
			/* Cleanup remaining old carps */
647
			foreach ($oldvips as $oldvipar) {
648
				$oldvipif = get_real_interface(
649
				    $oldvipar['interface']);
650

    
651
				if (empty($oldvipif)) {
652
					continue;
653
				}
654

    
655
				/* do not remove VIP if the IP address remains the same */
656
				foreach (config_get_path('virtualip/vip', []) as $vip) {
657
					if ($vip['subnet'] == $oldvipar['subnet']) {
658
						continue 2;
659
					}
660
				}
661

    
662
				if (is_ipaddrv6($oldvipar['subnet'])) {
663
					 mwexec("/sbin/ifconfig " .
664
					     escapeshellarg($oldvipif) .
665
					     " inet6 " .
666
					     escapeshellarg($oldvipar['subnet']) .
667
					     " delete");
668
				} else {
669
					pfSense_interface_deladdress($oldvipif,
670
					    $oldvipar['subnet']);
671
				}
672
			}
673
			if ($carp_setuped == true) {
674
				interfaces_sync_setup();
675
			}
676
			if ($anyproxyarp == true) {
677
				interface_proxyarp_configure();
678
			}
679
		}
680

    
681
		local_sync_accounts($u2add, $u2del, $g2add, $g2del);
682
		$this->filter_configure(false, $force_filterconfigure);
683
		unset($old_config);
684

    
685
		pkg_call_plugins('plugin_xmlrpc_recv_done', []);
686
		return true;
687
	}
688

    
689
	/**
690
	 * Merge items into installedpackages config section
691
	 *
692
	 * @param array $section
693
	 *
694
	 * @return bool
695
	 */
696
	public function merge_installedpackages_section($section, $timeout) {
697
		ini_set('default_socket_timeout', $timeout);
698
		$this->auth();
699

    
700
		if ($this->loop_detected) {
701
			log_error("Disallowing CARP sync loop");
702
			return true;
703
		}
704

    
705
		config_set_path('installedpackages', array_merge(
706
		    config_get_path('installedpackages'), $section));
707
		$mergedkeys = implode(", ", array_keys($section));
708
		write_config(sprintf(gettext(
709
		    "Merged in config (%s sections) from XMLRPC client."),
710
		    $mergedkeys));
711

    
712
		return true;
713
	}
714

    
715
	/**
716
	 * Merge items into config
717
	 *
718
	 * @param array $section
719
	 *
720
	 * @return bool
721
	 */
722
	public function merge_config_section($section, $timeout) {
723
		ini_set('default_socket_timeout', $timeout);
724
		$this->auth();
725

    
726
		if ($this->loop_detected) {
727
			log_error("Disallowing CARP sync loop");
728
			return true;
729
		}
730

    
731
		config_set_path('', $this->array_overlay(config_get_path(''), $section));
732
		$mergedkeys = implode(", ", array_keys($section));
733
		write_config(sprintf(gettext(
734
		    "Merged in config (%s sections) from XMLRPC client."),
735
		    $mergedkeys));
736

    
737
		return true;
738
	}
739

    
740
	/**
741
	 * Wrapper for filter_configure()
742
	 *
743
	 * @return bool
744
	 */
745
	private function filter_configure($reset_accounts = true, $force = false) {
746
		global $g, $old_config;
747

    
748
		filter_configure();
749
		system_routing_configure();
750
		setup_gateways_monitor();
751

    
752
		/* do not restart unchanged services on XMLRPC sync,
753
		 * see https://redmine.pfsense.org/issues/11082 
754
		 */
755
		if (is_array(config_get_path('openvpn')) || is_array($old_config['openvpn'])) {
756
			foreach (array("server", "client") as $type) {
757
				$remove_id = array();
758
				if (is_array(array_get_path($old_config, "openvpn/openvpn-{$type}"))) {
759
					foreach ($old_config['openvpn']["openvpn-{$type}"] as $old_settings) {
760
						$remove_id[] = $old_settings['vpnid'];
761
					}
762
				}
763
				if (!is_array(config_get_path("openvpn/openvpn-{$type}"))) {
764
					continue;
765
				}
766
				foreach (config_get_path("openvpn/openvpn-{$type}", []) as $settings) {
767
					$new_instance = true;
768
					if (in_array($settings['vpnid'], $remove_id)) {
769
						$remove_id = array_diff($remove_id, array($settings['vpnid']));
770
					}
771
					if (is_array(array_get_path($old_config, "openvpn/openvpn-{$type}"))) {
772
						foreach ($old_config['openvpn']["openvpn-{$type}"] as $old_settings) {
773
							if ($settings['vpnid'] == $old_settings['vpnid']) {
774
								$new_instance = false;
775
								if (($settings != $old_settings) || $force) {
776
									/* restart changed openvpn instance */
777
									openvpn_resync($type, $settings);
778
									break;
779
								}
780
							}
781
						}
782
					}
783
					if ($new_instance) {
784
						/* start new openvpn instance */
785
						openvpn_resync($type, $settings);
786
					}
787
				}
788
				if (!empty($remove_id)) {
789
					foreach ($remove_id as $id) {
790
						/* stop/delete removed openvpn instances */
791
						openvpn_delete($type, array('vpnid' => $id));
792
					}
793
				}
794
			}
795
			/* no service restart required */
796
			openvpn_resync_csc_all();
797
		}
798

    
799
		/* run ipsec_configure() on any IPsec change, see https://redmine.pfsense.org/issues/12075 */
800
		if (((is_array(config_get_path('ipsec')) || is_array($old_config['ipsec'])) &&
801
		    (config_get_path('ipsec') != $old_config['ipsec'])) ||
802
		    $force) {
803
			ipsec_configure();
804
		}
805

    
806
		/*
807
		 * The DNS Resolver and the DNS Forwarder may both be active so
808
		 * long as * they are running on different ports.
809
		 * See ticket #5882
810
		 */
811
		if (((is_array(config_get_path('dnsmasq')) || is_array($old_config['dnsmasq'])) &&
812
		    (config_get_path('dnsmasq') != $old_config['dnsmasq'])) ||
813
		    $force) {
814
			if (config_path_enabled('dnsmasq')) {
815
				/* Configure dnsmasq but tell it NOT to restart DHCP */
816
				services_dnsmasq_configure(false);
817
			} else {
818
				/* kill any running dnsmasq instance */
819
				if (isvalidpid("{$g['varrun_path']}/dnsmasq.pid")) {
820
					sigkillbypid("{$g['varrun_path']}/dnsmasq.pid",
821
					    "TERM");
822
				}
823
			}
824
		}
825
		if (((is_array(config_get_path('unbound')) || is_array($old_config['unbound'])) &&
826
		    (config_get_path('unbound') != $old_config['unbound'])) ||
827
		    $force) {
828
			if (config_path_enabled('unbound')) {
829
				/* Configure unbound but tell it NOT to restart DHCP */
830
				services_unbound_configure(false);
831
			} else {
832
				/* kill any running Unbound instance */
833
				if (isvalidpid("{$g['varrun_path']}/unbound.pid")) {
834
					sigkillbypid("{$g['varrun_path']}/unbound.pid",
835
					    "TERM");
836
				}
837
			}
838
		}
839

    
840
		/*
841
		 * Call this separately since the above are manually set to
842
		 * skip the DHCP restart they normally perform.
843
		 * This avoids restarting dhcpd twice as described on
844
		 * ticket #3797
845
		 */
846
		$called = [];
847
		foreach ([
848
			'dhcpd'			=> 'services_dhcpd_configure',
849
			'dhcpdv6'		=> 'services_dhcpd_configure',
850
			'kea'			=> 'services_dhcpd_configure',
851
			'kea6'			=> 'services_dhcpd_configure',
852
			'dhcrelay'		=> 'services_dhcrelay_configure',
853
			'dhcrelay6'		=> 'services_dhcrelay6_configure',
854
			'captiveportal'	=> 'captiveportal_configure',
855
			'voucher'		=> 'voucher_configure'
856
		] as $path => $fn) {
857
			if (!array_key_exists($fn, $called)) {
858
				if (((is_array(config_get_path($path)) || is_array($old_config[$path])) &&
859
			        (config_get_path($path) !== array_get_path($old_config, $path))) || $force) {
860
					if (is_callable($fn)) {
861
						$fn();
862
					}
863
					$called[$fn] = true;
864
				}
865
			}
866
		}
867

    
868
		if ($reset_accounts) {
869
			local_reset_accounts();
870
		}
871

    
872
		return true;
873
	}
874

    
875
	/**
876
	 * Wrapper for captiveportal connected users and
877
	 * active/expired vouchers synchronization
878
	 *
879
	 * @param array $arguments
880
	 *
881
	 * @return array|bool|null
882
	 */
883
	public function captive_portal_sync($arguments, $timeout) {
884
		ini_set('default_socket_timeout', $timeout);
885
		$this->auth();
886
		// Note : no protection against CARP loop is done here, and this is in purpose.
887
		// This function is used for bi-directionnal sync, which is precisely what CARP loop protection is supposed to prevent.
888
		// CARP loop has to be managed within functions using captive_portal_sync()
889
		global $g, $cpzone;
890

    
891
		if (empty($arguments['op']) || empty($arguments['zone']) || empty(config_get_path("captiveportal/{$arguments['zone']}"))) {
892
			return false;
893
		}
894
		$cpzone = $arguments['zone'];
895

    
896
		if ($arguments['op'] === 'get_databases') {
897
			$active_vouchers = [];
898
			$expired_vouchers = [];
899
			$usedmacs = [];
900

    
901
			foreach(config_get_path("voucher/{$cpzone}/roll", []) as $roll) {
902
				$expired_vouchers[$roll['number']] = base64_encode(voucher_read_used_db($roll['number']));
903
				$active_vouchers[$roll['number']] = voucher_read_active_db($roll['number']);
904
			}
905
			if (!empty(config_get_path("captiveportal/{$cpzone}/freelogins_count")) &&
906
			    !empty(config_get_path("captiveportal/{$cpzone}/freelogins_resettimeout"))) {
907
				$usedmacs = captiveportal_read_usedmacs_db();
908
			}
909
			// base64 is here for safety reasons, as we don't fully control
910
			// the content of these arrays.
911
			$returndata = array('connected_users' => base64_encode(serialize(captiveportal_read_db())),
912
			'active_vouchers' => base64_encode(serialize($active_vouchers)),
913
			'expired_vouchers' => base64_encode(serialize($expired_vouchers)),
914
			'usedmacs' => base64_encode(serialize($usedmacs)));
915

    
916
			return $returndata;
917
		} elseif ($arguments['op'] === 'connect_user') {
918
			$user = unserialize_data(base64_decode($arguments['user']), []);
919
			$user['attributes']['allow_time'] = $user['allow_time'];
920

    
921
			// pipeno might be different between primary and secondary
922
			$pipeno = captiveportal_get_next_dn_ruleno('auth');
923
			return portal_allow($user['clientip'], $user['clientmac'], $user['username'], $user['password'], null,
924
			    $user['attributes'], $pipeno, $user['authmethod'], $user['context'], $user['sessionid']);
925
		} elseif ($arguments['op'] === 'disconnect_user') {
926
			$session = unserialize_data(base64_decode($arguments['session']), []);
927
			/* read database again, as pipeno might be different between primary & secondary */
928
			$sessionid = SQLite3::escapeString($session['sessionid']);
929
			$local_dbentry = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
930

    
931
			if (!empty($local_dbentry) && count($local_dbentry) == 1) {
932
				return captiveportal_disconnect($local_dbentry[0], $session['term_cause'], $session['stop_time'], true);
933
			} else {
934
				return false;
935
			}
936
		} elseif ($arguments['op'] === 'remove_entries') {
937
			$entries = unserialize_data(base64_decode($arguments['entries']), []);
938

    
939
			return captiveportal_remove_entries($entries, true);
940
		} elseif ($arguments['op'] === 'disconnect_all') {
941
			$arguments = unserialize_data(base64_decode($arguments['arguments']), []);
942

    
943
			return captiveportal_disconnect_all($arguments['term_cause'], $arguments['logout_reason'], true);
944
		} elseif ($arguments['op'] === 'write_vouchers') {
945
			$arguments = unserialize_data(base64_decode($arguments['arguments']), []);
946

    
947
			if (is_array($arguments['active_and_used_vouchers_bitmasks'])) {
948
				foreach ($arguments['active_and_used_vouchers_bitmasks'] as $roll => $used) {
949
					if (is_array($used)) {
950
						foreach ($used as $u) {
951
							voucher_write_used_db($roll, base64_encode($u));
952
						}
953
					} else {
954
						voucher_write_used_db($roll, base64_encode($used));
955
					}
956
				}
957
			}
958
			foreach ($arguments['active_vouchers'] as $roll => $active_vouchers) {
959
				voucher_write_active_db($roll, $active_vouchers);
960
			}
961
			return true;
962
		} elseif ($arguments['op'] === 'write_usedmacs') {
963
			$arguments = unserialize_data(base64_decode($arguments['arguments']), []);
964

    
965
			captiveportal_write_usedmacs_db($arguments['usedmacs']); 
966
			return true;
967
		}
968
	}
969

    
970
	/**
971
	 * Wrapper for configuring CARP interfaces
972
	 *
973
	 * @return bool
974
	 */
975
	public function interfaces_carp_configure() {
976
		$this->auth();
977

    
978
		if ($this->loop_detected) {
979
			log_error("Disallowing CARP sync loop");
980
			return true;
981
		}
982

    
983
		interfaces_vips_configure();
984

    
985
		return true;
986
	}
987

    
988
	/**
989
	 * Wrapper for rc.reboot
990
	 *
991
	 * @return bool
992
	 */
993
	public function reboot() {
994
		$this->auth();
995

    
996
		mwexec_bg("/etc/rc.reboot");
997

    
998
		return true;
999
	}
1000
}
1001

    
1002
// run script until its done and can 'unlock' the xmlrpc.lock, this prevents hanging php-fpm / webgui
1003
ignore_user_abort(true);
1004
set_time_limit(0);
1005

    
1006
$xmlrpclockkey = lock('xmlrpc', LOCK_EX);
1007

    
1008
XML_RPC2_Backend::setBackend('php');
1009
$HTTP_RAW_POST_DATA = file_get_contents('php://input');
1010

    
1011
$options = array(
1012
	'prefix' => 'pfsense.',
1013
	'encoding' => 'utf-8',
1014
	'autoDocument' => false,
1015
);
1016

    
1017
$server = XML_RPC2_Server::create(new pfsense_xmlrpc_server(), $options);
1018
$server->handleCall();
1019

    
1020
unlock($xmlrpclockkey);
1021

    
1022
?>
(232-232/232)