Project

General

Profile

Download (28 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-2022 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 $config, $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(
59
			    $config['system']['webgui']['authmode']);
60

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

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

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

    
79
		$user_entry = getUserEntry($username);
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
		global $config;
116

    
117
		$this->remote_addr = $_SERVER['REMOTE_ADDR'];
118

    
119
		/* grab sync to ip if enabled */
120
		if (isset($config['hasync']['synchronizetoip']) &&
121
		    $config['hasync']['synchronizetoip'] == $this->remote_addr) {
122
			$this->loop_detected = true;
123
		}
124
	}
125

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

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

    
147
		eval($code);
148
		if ($toreturn) {
149
			return $toreturn;
150
		}
151

    
152
		return true;
153
	}
154

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

    
165
		mwexec($code);
166
		return true;
167
	}
168

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

    
179
		global $config;
180

    
181
		return array_intersect_key($config, array_flip($section));
182
	}
183

    
184
	/**
185
	 * Restore defined config section into local config
186
	 *
187
	 * @param array $sections
188
	 *
189
	 * @return bool
190
	 */
191
	public function restore_config_section($sections, $timeout) {
192
		ini_set('default_socket_timeout', $timeout);
193
		$this->auth();
194

    
195
		global $config, $cpzone, $cpzoneid, $old_config;
196

    
197
		$old_config = $config;
198

    
199
		if ($this->loop_detected) {
200
			log_error("Disallowing CARP sync loop");
201
			return true;
202
		}
203

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

    
228
		$syncd_full_sections = array();
229

    
230
		foreach ($sync_full_sections as $section) {
231
			if (!isset($sections[$section])) {
232
				continue;
233
			}
234

    
235
			$config[$section] = $sections[$section];
236
			unset($sections[$section]);
237
			$syncd_full_sections[] = $section;
238
		}
239

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

    
258
		/* Only touch users if users are set to synchronize from the primary node
259
		 * See https://redmine.pfsense.org/issues/8450
260
		 */
261
		if ($sections['system']['user'] && $sections['system']['group']) {
262
			$g2add = array();
263
			$g2del = array();
264
			$g2del_idx = array();
265
			$g2keep = array();
266
			if (is_array($sections['system']['group'])) {
267
				$local_groups = isset($config['system']['group'])
268
				    ? $config['system']['group']
269
				    : array();
270

    
271
				foreach ($sections['system']['group'] as $group) {
272
					$idx = array_search($group['name'],
273
					    array_column($local_groups, 'name'));
274

    
275
					if ($idx === false) {
276
						$g2add[] = $group;
277
					} else if ($group['gid'] < 1999) {
278
						$g2keep[] = $idx;
279
					} else if ($group != $local_groups[$idx]) {
280
						$g2add[] = $group;
281
						$g2del[] = $group;
282
						$g2del_idx[] = $idx;
283
					} else {
284
						$g2keep[] = $idx;
285
					}
286
				}
287
			}
288
			if (is_array($config['system']['group'])) {
289
				foreach ($config['system']['group'] as $idx => $group) {
290
					if (array_search($idx, $g2keep) === false &&
291
					    array_search($idx, $g2del_idx) === false) {
292
						$g2del[] = $group;
293
						$g2del_idx[] = $idx;
294
					}
295
				}
296
			}
297
			unset($sections['system']['group'], $g2keep, $g2del_idx);
298

    
299
			$u2add = array();
300
			$u2del = array();
301
			$u2del_idx = array();
302
			$u2keep = array();
303
			if (is_array($sections['system']['user'])) {
304
				$local_users = isset($config['system']['user'])
305
				    ? $config['system']['user']
306
				    : array();
307

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

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

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

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

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

    
396
		$vipbackup = array();
397
		$oldvips = array();
398
		if (isset($sections['virtualip']) &&
399
		    is_array($config['virtualip']['vip'])) {
400
			foreach ($config['virtualip']['vip'] as $vip) {
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 = array_merge_recursive_unique($config, $sections);
437

    
438

    
439
		/* Remove locally items removed remote */
440
		foreach ($voucher as $zone => $item) {
441
			/* No rolls on master, delete local ones */
442
			if (!is_array($item['roll'])) {
443
				unset($config['voucher'][$zone]['roll']);
444
			}
445
		}
446

    
447
		$l_rolls = array();
448
		if (is_array($config['voucher'])) {
449
			foreach ($config['voucher'] as $zone => $item) {
450
				if (!is_array($item['roll'])) {
451
					continue;
452
				}
453
				foreach ($item['roll'] as $idx => $roll) {
454
					/* Make it easy to find roll by # */
455
					$l_rolls[$zone][$roll['number']] = $idx;
456
				}
457
			}
458
		}
459

    
460
		/*
461
		 * Process vouchers sent by primary node and:
462
		 * - Add new items
463
		 * - Update existing items based on 'lastsync' field
464
		 */
465
		foreach ($voucher as $zone => $item) {
466
			if (!is_array($item['roll'])) {
467
				continue;
468
			}
469
			foreach ($item['roll'] as $roll) {
470
				if (!isset($l_rolls[$zone][$roll['number']])) {
471
					$config['voucher'][$zone]['roll'][] =
472
					    $roll;
473
					continue;
474
				}
475
				$l_roll_idx = $l_rolls[$zone][$roll['number']];
476
				init_config_arr(array('voucher', $zone));
477
				$l_vouchers = &$config['voucher'][$zone];
478
				$l_roll = $l_vouchers['roll'][$l_roll_idx];
479
				if (!isset($l_roll['lastsync'])) {
480
					$l_roll['lastsync'] = 0;
481
				}
482

    
483
				if (isset($roll['lastsync']) &&
484
				    $roll['lastsync'] != $l_roll['lastsync']) {
485
					$l_vouchers['roll'][$l_roll_idx] =
486
					    $roll;
487
					unset($l_rolls[$zone][$roll['number']]);
488
				}
489
			}
490
		}
491

    
492
		/*
493
		 * At this point $l_rolls contains only items that are not
494
		 * present on primary node. They must be removed
495
		 */
496
		foreach ($l_rolls as $zone => $item) {
497
			foreach ($item as $idx) {
498
				unset($config['voucher'][$zone][$idx]);
499
			}
500
		}
501

    
502
		/*
503
		 * Then add ipalias and proxyarp types already defined
504
		 * on the backup
505
		 */
506
		if (is_array($vipbackup) && !empty($vipbackup)) {
507
			if (!is_array($config['virtualip'])) {
508
				$config['virtualip'] = array();
509
			}
510
			if (!is_array($config['virtualip']['vip'])) {
511
				$config['virtualip']['vip'] = array();
512
			}
513
			foreach ($vipbackup as $vip) {
514
				array_unshift($config['virtualip']['vip'], $vip);
515
			}
516
		}
517

    
518
		/* Log what happened */
519
		$mergedkeys = implode(", ", array_merge(array_keys($sections),
520
		    $syncd_full_sections));
521
		write_config(sprintf(gettext(
522
		    "Merged in config (%s sections) from XMLRPC client."),
523
		    $mergedkeys));
524

    
525
		/*
526
		 * The real work on handling the vips specially
527
		 * This is a copy of interfaces_vips_configure with addition of
528
		 * not reloading existing/not changed carps
529
		 */
530
		$force_filterconfigure = false;
531
		if (isset($sections['virtualip']) &&
532
		    is_array($config['virtualip']) &&
533
		    is_array($config['virtualip']['vip'])) {
534
			$carp_setuped = false;
535
			$anyproxyarp = false;
536

    
537
			foreach ($config['virtualip']['vip'] as $vip) {
538
				$key = "{$vip['interface']}_vip{$vip['vhid']}";
539

    
540
				if ($vip['mode'] == "carp" &&
541
				    isset($oldvips[$key])) {
542
					if ($oldvips[$key]['content'] ==
543
					    $vip['password'] .
544
					    $vip['advskew'] .
545
					    $vip['subnet'] .
546
					    $vip['subnet_bits'] .
547
					    $vip['advbase'] &&
548
					    does_vip_exist($vip)) {
549
						unset($oldvips[$key]);
550
						/*
551
						 * Skip reconfiguring this vips
552
						 * since nothing has changed.
553
						 */
554
						continue;
555
					}
556

    
557
				} elseif ($vip['mode'] == "ipalias" &&
558
				    (substr($vip['interface'], 0, 4) == '_vip'
559
				    || strstr($vip['interface'], "lo0")) &&
560
				    isset($oldvips[$vip['subnet']])) {
561
					$key = $vip['subnet'];
562
					if ($oldvips[$key]['content'] ==
563
					    $vip['interface'] .
564
					    $vip['subnet'] .
565
					    $vip['subnet_bits'] &&
566
					    does_vip_exist($vip)) {
567
						unset($oldvips[$key]);
568
						/*
569
						 * Skip reconfiguring this vips
570
						 * since nothing has changed.
571
						 */
572
						continue;
573
					}
574
					unset($oldvips[$key]);
575
				}
576

    
577
				switch ($vip['mode']) {
578
				case "proxyarp":
579
					$anyproxyarp = true;
580
					break;
581
				case "ipalias":
582
					interface_ipalias_configure($vip);
583
					break;
584
				case "carp":
585
					$carp_setuped = true;
586
					if (does_vip_exist($vip) && isset($oldvips[$key]['vhid']) &&
587
					    ($oldvips[$key]['vhid'] ^ $vip['vhid'])) {
588
						/* properly remove the old VHID
589
						 * see https://redmine.pfsense.org/issues/12202 */
590
						$realif = get_real_interface($vip['interface']);
591
						mwexec("/sbin/ifconfig {$realif} " .
592
							escapeshellarg($vip['subnet']) . " -alias");
593
						$ipalias_reload = true;
594
					} else {
595
						$ipalias_reload = false;
596
					}
597
					interface_carp_configure($vip, false, $ipalias_reload);
598
					break;
599
				}
600
				$force_filterconfigure = true;
601
			}
602

    
603
			/* Cleanup remaining old carps */
604
			foreach ($oldvips as $oldvipar) {
605
				$oldvipif = get_real_interface(
606
				    $oldvipar['interface']);
607

    
608
				if (empty($oldvipif)) {
609
					continue;
610
				}
611

    
612
				/* do not remove VIP if the IP address remains the same */
613
				foreach ($config['virtualip']['vip'] as $vip) {
614
					if ($vip['subnet'] == $oldvipar['subnet']) {
615
						continue 2;
616
					}
617
				}
618

    
619
				if (is_ipaddrv6($oldvipar['subnet'])) {
620
					 mwexec("/sbin/ifconfig " .
621
					     escapeshellarg($oldvipif) .
622
					     " inet6 " .
623
					     escapeshellarg($oldvipar['subnet']) .
624
					     " delete");
625
				} else {
626
					pfSense_interface_deladdress($oldvipif,
627
					    $oldvipar['subnet']);
628
				}
629
			}
630
			if ($carp_setuped == true) {
631
				interfaces_sync_setup();
632
			}
633
			if ($anyproxyarp == true) {
634
				interface_proxyarp_configure();
635
			}
636
		}
637

    
638
		local_sync_accounts($u2add, $u2del, $g2add, $g2del);
639
		$this->filter_configure(false, $force_filterconfigure);
640
		unset($old_config);
641

    
642
		return true;
643
	}
644

    
645
	/**
646
	 * Merge items into installedpackages config section
647
	 *
648
	 * @param array $section
649
	 *
650
	 * @return bool
651
	 */
652
	public function merge_installedpackages_section($section, $timeout) {
653
		ini_set('default_socket_timeout', $timeout);
654
		$this->auth();
655

    
656
		global $config;
657

    
658
		if ($this->loop_detected) {
659
			log_error("Disallowing CARP sync loop");
660
			return true;
661
		}
662

    
663
		$config['installedpackages'] = array_merge(
664
		    $config['installedpackages'], $section);
665
		$mergedkeys = implode(", ", array_keys($section));
666
		write_config(sprintf(gettext(
667
		    "Merged in config (%s sections) from XMLRPC client."),
668
		    $mergedkeys));
669

    
670
		return true;
671
	}
672

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

    
684
		global $config;
685

    
686
		if ($this->loop_detected) {
687
			log_error("Disallowing CARP sync loop");
688
			return true;
689
		}
690

    
691
		$config_new = $this->array_overlay($config, $section);
692
		$config = $config_new;
693
		$mergedkeys = implode(", ", array_keys($section));
694
		write_config(sprintf(gettext(
695
		    "Merged in config (%s sections) from XMLRPC client."),
696
		    $mergedkeys));
697

    
698
		return true;
699
	}
700

    
701
	/**
702
	 * Wrapper for filter_configure()
703
	 *
704
	 * @return bool
705
	 */
706
	private function filter_configure($reset_accounts = true, $force = false) {
707
		global $g, $config, $old_config;
708

    
709
		filter_configure();
710
		system_routing_configure();
711
		setup_gateways_monitor();
712

    
713
		/* do not restart unchanged services on XMLRPC sync,
714
		 * see https://redmine.pfsense.org/issues/11082 
715
		 */
716
		if (is_array($config['openvpn']) || is_array($old_config['openvpn'])) {
717
			foreach (array("server", "client") as $type) {
718
				$remove_id = array();
719
				if (is_array($old_config['openvpn']["openvpn-{$type}"])) {
720
					foreach ($old_config['openvpn']["openvpn-{$type}"] as & $old_settings) {
721
						$remove_id[] = $old_settings['vpnid'];
722
					}
723
				}
724
				if (!is_array($config['openvpn']["openvpn-{$type}"])) {
725
					continue;
726
				}
727
				foreach ($config['openvpn']["openvpn-{$type}"] as & $settings) {
728
					$new_instance = true;
729
					if (in_array($settings['vpnid'], $remove_id)) {
730
						$remove_id = array_diff($remove_id, array($settings['vpnid']));
731
					}
732
					if (is_array($old_config['openvpn']["openvpn-{$type}"])) {
733
						foreach ($old_config['openvpn']["openvpn-{$type}"] as & $old_settings) {
734
							if ($settings['vpnid'] == $old_settings['vpnid']) {
735
								$new_instance = false;
736
								if (($settings != $old_settings) || $force) {
737
									/* restart changed openvpn instance */
738
									openvpn_resync($type, $settings);
739
									break;
740
								}
741
							}
742
						}
743
					}
744
					if ($new_instance) {
745
						/* start new openvpn instance */
746
						openvpn_resync($type, $settings);
747
					}
748
				}
749
				if (!empty($remove_id)) {
750
					foreach ($remove_id as $id) {
751
						/* stop/delete removed openvpn instances */
752
						openvpn_delete($type, array('vpnid' => $id));
753
					}
754
				}
755
			}
756
			/* no service restart required */
757
			openvpn_resync_csc_all();
758
		}
759

    
760
		/* run ipsec_configure() on any IPsec change, see https://redmine.pfsense.org/issues/12075 */
761
		if (((is_array($config['ipsec']) || is_array($old_config['ipsec'])) &&
762
		    ($config['ipsec'] != $old_config['ipsec'])) ||
763
		    $force) {
764
			ipsec_configure();
765
		}
766

    
767
		/*
768
		 * The DNS Resolver and the DNS Forwarder may both be active so
769
		 * long as * they are running on different ports.
770
		 * See ticket #5882
771
		 */
772
		if (((is_array($config['dnsmasq']) || is_array($old_config['dnsmasq'])) &&
773
		    ($config['dnsmasq'] != $old_config['dnsmasq'])) ||
774
		    $force) {
775
			if (isset($config['dnsmasq']['enable'])) {
776
				/* Configure dnsmasq but tell it NOT to restart DHCP */
777
				services_dnsmasq_configure(false);
778
			} else {
779
				/* kill any running dnsmasq instance */
780
				if (isvalidpid("{$g['varrun_path']}/dnsmasq.pid")) {
781
					sigkillbypid("{$g['varrun_path']}/dnsmasq.pid",
782
					    "TERM");
783
				}
784
			}
785
		}
786
		if (((is_array($config['unbound']) || is_array($old_config['unbound'])) &&
787
		    ($config['unbound'] != $old_config['unbound'])) ||
788
		    $force) {
789
			if (isset($config['unbound']['enable'])) {
790
				/* Configure unbound but tell it NOT to restart DHCP */
791
				services_unbound_configure(false);
792
			} else {
793
				/* kill any running Unbound instance */
794
				if (isvalidpid("{$g['varrun_path']}/unbound.pid")) {
795
					sigkillbypid("{$g['varrun_path']}/unbound.pid",
796
					    "TERM");
797
				}
798
			}
799
		}
800

    
801
		/*
802
		 * Call this separately since the above are manually set to
803
		 * skip the DHCP restart they normally perform.
804
		 * This avoids restarting dhcpd twice as described on
805
		 * ticket #3797
806
		 */
807
		if (((is_array($config['dhcpd']) || is_array($old_config['dhcpd'])) &&
808
		    ($config['dhcpd'] != $old_config['dhcpd'])) ||
809
		    $force) {
810
			services_dhcpd_configure();
811
		}
812

    
813
		if (((is_array($config['dhcrelay']) || is_array($old_config['dhcrelay'])) &&
814
		    ($config['dhcrelay'] != $old_config['dhcrelay'])) ||
815
		    $force) {
816
			services_dhcrelay_configure();
817
		}
818

    
819
		if (((is_array($config['dhcrelay6']) || is_array($old_config['dhcrelay6'])) &&
820
		    ($config['dhcrelay6'] != $old_config['dhcrelay6'])) ||
821
		    $force) {
822
			services_dhcrelay6_configure();
823
		}
824

    
825
		if ($reset_accounts) {
826
			local_reset_accounts();
827
		}
828

    
829
		if ((is_array($config['captiveportal']) || is_array($old_config['captiveportal']) &&
830
		    ($config['captiveportal'] != $old_config['captiveportal'])) ||
831
		    $force) {
832
			captiveportal_configure();
833
		}
834
		if ((is_array($config['voucher']) || is_array($old_config['voucher']) &&
835
		    ($config['voucher'] != $old_config['voucher'])) ||
836
		    $force) {
837
			voucher_configure();
838
		}
839

    
840
		return true;
841
	}
842

    
843
	/**
844
	 * Wrapper for captiveportal connected users and
845
	 * active/expired vouchers synchronization
846
	 *
847
	 * @param array $arguments
848
	 *
849
	 * @return array
850
	 */
851
	public function captive_portal_sync($arguments, $timeout) {
852
		ini_set('default_socket_timeout', $timeout);
853
		$this->auth();
854
		// Note : no protection against CARP loop is done here, and this is in purpose.
855
		// This function is used for bi-directionnal sync, which is precisely what CARP loop protection is supposed to prevent.
856
		// CARP loop has to be managed within functions using captive_portal_sync()
857
		global $g, $config, $cpzone;
858

    
859
		if (empty($arguments['op']) || empty($arguments['zone']) || empty($config['captiveportal'][$arguments['zone']])) {
860
			return false;
861
		}
862
		$cpzone = $arguments['zone'];
863

    
864
		if ($arguments['op'] === 'get_databases') {
865
			$active_vouchers = array();
866
			$expired_vouchers = array();
867
			$usedmacs = '';
868

    
869
			if (is_array($config['voucher'][$cpzone]['roll'])) {
870
				foreach($config['voucher'][$cpzone]['roll'] as $roll) {
871
					$expired_vouchers[$roll['number']] = base64_encode(voucher_read_used_db($roll['number']));
872
					$active_vouchers[$roll['number']] = voucher_read_active_db($roll['number']);
873
				}
874
			}
875
			if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) &&
876
			    !empty($config['captiveportal'][$cpzone]['freelogins_resettimeout'])) {
877
				$usedmacs = captiveportal_read_usedmacs_db();
878
			}
879
			// base64 is here for safety reasons, as we don't fully control
880
			// the content of these arrays.
881
			$returndata = array('connected_users' => base64_encode(serialize(captiveportal_read_db())),
882
			'active_vouchers' => base64_encode(serialize($active_vouchers)),
883
			'expired_vouchers' => base64_encode(serialize($expired_vouchers)),
884
			'usedmacs' => base64_encode(serialize($usedmacs)));
885

    
886
			return $returndata;
887
		} elseif ($arguments['op'] === 'connect_user') {
888
			$user = unserialize(base64_decode($arguments['user']));
889
			$user['attributes']['allow_time'] = $user['allow_time'];
890

    
891
			// pipeno might be different between primary and secondary
892
			$pipeno = captiveportal_get_next_dn_ruleno('auth');
893
			return portal_allow($user['clientip'], $user['clientmac'], $user['username'], $user['password'], null,
894
			    $user['attributes'], $pipeno, $user['authmethod'], $user['context'], $user['sessionid']);
895
		} elseif ($arguments['op'] === 'disconnect_user') {
896
			$session = unserialize(base64_decode($arguments['session']));
897
			/* read database again, as pipeno might be different between primary & secondary */
898
			$sessionid = SQLite3::escapeString($session['sessionid']);
899
			$local_dbentry = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
900

    
901
			if (!empty($local_dbentry) && count($local_dbentry) == 1) {
902
				return captiveportal_disconnect($local_dbentry[0], $session['term_cause'], $session['stop_time'], true);
903
			} else {
904
				return false;
905
			}
906
		} elseif ($arguments['op'] === 'remove_entries') {
907
			$entries = unserialize(base64_decode($arguments['entries']));
908

    
909
			return captiveportal_remove_entries($entries, true);
910
		} elseif ($arguments['op'] === 'disconnect_all') {
911
			$arguments = unserialize(base64_decode($arguments['arguments']));
912

    
913
			return captiveportal_disconnect_all($arguments['term_cause'], $arguments['logout_reason'], true);
914
		} elseif ($arguments['op'] === 'write_vouchers') {
915
			$arguments = unserialize(base64_decode($arguments['arguments']));
916

    
917
			if (is_array($arguments['active_and_used_vouchers_bitmasks'])) {
918
				foreach ($arguments['active_and_used_vouchers_bitmasks'] as $roll => $used) {
919
					if (is_array($used)) {
920
						foreach ($used as $u) {
921
							voucher_write_used_db($roll, base64_encode($u));
922
						}
923
					} else {
924
						voucher_write_used_db($roll, base64_encode($used));
925
					}
926
				}
927
			}
928
			foreach ($arguments['active_vouchers'] as $roll => $active_vouchers) {
929
				voucher_write_active_db($roll, $active_vouchers);
930
			}
931
			return true;
932
		} elseif ($arguments['op'] === 'write_usedmacs') {
933
			$arguments = unserialize(base64_decode($arguments['arguments']));
934

    
935
			captiveportal_write_usedmacs_db($arguments['usedmacs']); 
936
			return true;
937
		}
938
	}
939

    
940
	/**
941
	 * Wrapper for configuring CARP interfaces
942
	 *
943
	 * @return bool
944
	 */
945
	public function interfaces_carp_configure() {
946
		$this->auth();
947

    
948
		if ($this->loop_detected) {
949
			log_error("Disallowing CARP sync loop");
950
			return true;
951
		}
952

    
953
		interfaces_vips_configure();
954

    
955
		return true;
956
	}
957

    
958
	/**
959
	 * Wrapper for rc.reboot
960
	 *
961
	 * @return bool
962
	 */
963
	public function reboot() {
964
		$this->auth();
965

    
966
		mwexec_bg("/etc/rc.reboot");
967

    
968
		return true;
969
	}
970
}
971

    
972
// run script until its done and can 'unlock' the xmlrpc.lock, this prevents hanging php-fpm / webgui
973
ignore_user_abort(true);
974
set_time_limit(0);
975

    
976
$xmlrpclockkey = lock('xmlrpc', LOCK_EX);
977

    
978
XML_RPC2_Backend::setBackend('php');
979
$HTTP_RAW_POST_DATA = file_get_contents('php://input');
980

    
981
$options = array(
982
	'prefix' => 'pfsense.',
983
	'encoding' => 'utf-8',
984
	'autoDocument' => false,
985
);
986

    
987
$server = XML_RPC2_Server::create(new pfsense_xmlrpc_server(), $options);
988
$server->handleCall();
989

    
990
unlock($xmlrpclockkey);
991

    
992
?>
(228-228/228)