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-2023 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
			/* Do not test for empty here or removing final entry
232
			 * from a section will not work */
233
			if (!array_key_exists($section, $sections)) {
234
				continue;
235
			}
236

    
237
			config_set_path($section, array_get_path($sections, $section));
238
			array_del_path($sections, $section);
239
			$syncd_full_sections[] = $section;
240
		}
241

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
495
		/*
496
		 * At this point $l_rolls contains only items that are not
497
		 * present on primary node. They must be removed
498
		 */
499
		foreach ($l_rolls as $zone => $item) {
500
			foreach ($item as $idx) {
501
				config_del_path("voucher/{$zone}/{$idx}");
502
			}
503
		}
504

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

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

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

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

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

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

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

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

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

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

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

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

    
641
		return true;
642
	}
643

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

    
655
		global $config;
656

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

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

    
669
		return true;
670
	}
671

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

    
683
		global $config;
684

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

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

    
697
		return true;
698
	}
699

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

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

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

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

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

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

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

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

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

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

    
839
		return true;
840
	}
841

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

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

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

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

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

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

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

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

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

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

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

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

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

    
952
		interfaces_vips_configure();
953

    
954
		return true;
955
	}
956

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

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

    
967
		return true;
968
	}
969
}
970

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

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

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

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

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

    
989
unlock($xmlrpclockkey);
990

    
991
?>
(228-228/228)