Project

General

Profile

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

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

    
30
require_once("config.inc");
31
require_once("functions.inc");
32
require_once("auth.inc");
33
require_once("filter.inc");
34
require_once("ipsec.inc");
35
require_once("vpn.inc");
36
require_once("shaper.inc");
37
require_once("XML/RPC2/Server.php");
38

    
39
class pfsense_xmlrpc_server {
40

    
41
	private $loop_detected = false;
42
	private $remote_addr;
43

    
44
	private function auth() {
45
		global $config;
46
		$username = $_SERVER['PHP_AUTH_USER'];
47
		$password = $_SERVER['PHP_AUTH_PW'];
48

    
49
		$login_ok = false;
50
		if (!empty($username) && !empty($password)) {
51
			$attributes = array();
52
			$authcfg = auth_get_authserver(
53
			    $config['system']['webgui']['authmode']);
54

    
55
			if (authenticate_user($username, $password,
56
			    $authcfg, $attributes) ||
57
			    authenticate_user($username, $password)) {
58
				$login_ok = true;
59
			}
60
		}
61

    
62
		if (!$login_ok) {
63
			log_auth("webConfigurator authentication error for '" .
64
			    $username . "' from " . $this->remote_addr);
65

    
66
			require_once("XML/RPC2/Exception.php");
67
			throw new XML_RPC2_FaultException(gettext(
68
			    'Authentication failed: Invalid username or password'),
69
			    -1);
70
		}
71

    
72
		$user_entry = getUserEntry($username);
73
		/*
74
		 * admin (uid = 0) is allowed
75
		 * or regular user with necessary privilege
76
		 */
77
		if (isset($user_entry['uid']) && $user_entry['uid'] != '0' &&
78
		    !userHasPrivilege($user_entry, 'system-xmlrpc-ha-sync')) {
79
			log_auth("webConfigurator authentication error for '" .
80
			    $username . "' from " . $this->remote_addr .
81
			    " not enough privileges");
82

    
83
			require_once("XML/RPC2/Exception.php");
84
			throw new XML_RPC2_FaultException(gettext(
85
			    'Authentication failed: not enough privileges'),
86
			    -2);
87
		}
88

    
89
		return;
90
	}
91

    
92
	private function array_overlay($a1, $a2) {
93
		foreach ($a1 as $k => $v) {
94
			if (!array_key_exists($k, $a2)) {
95
				continue;
96
			}
97
			if (is_array($v) && is_array($a2[$k])) {
98
				$a1[$k] = $this->array_overlay($v, $a2[$k]);
99
			} else {
100
				$a1[$k] = $a2[$k];
101
			}
102
		}
103

    
104
		return $a1;
105
	}
106

    
107
	public function __construct() {
108
		global $config;
109

    
110
		$this->remote_addr = $_SERVER['REMOTE_ADDR'];
111

    
112
		/* grab sync to ip if enabled */
113
		if (isset($config['hasync']['synchronizetoip']) &&
114
		    $config['hasync']['synchronizetoip'] == $this->remote_addr) {
115
			$this->loop_detected = true;
116
		}
117
	}
118

    
119
	/**
120
	 * Get host version information
121
	 *
122
	 * @return array
123
	 */
124
	public function host_firmware_version($dummy = 1) {
125
		$this->auth();
126
		return host_firmware_version();
127
	}
128

    
129
	/**
130
	 * Executes a PHP block of code
131
	 *
132
	 * @param string $code
133
	 *
134
	 * @return bool
135
	 */
136
	public function exec_php($code) {
137
		$this->auth();
138

    
139
		eval($code);
140
		if ($toreturn) {
141
			return $toreturn;
142
		}
143

    
144
		return true;
145
	}
146

    
147
	/**
148
	 * Executes shell commands
149
	 *
150
	 * @param string $code
151
	 *
152
	 * @return bool
153
	 */
154
	public function exec_shell($code) {
155
		$this->auth();
156

    
157
		mwexec($code);
158
		return true;
159
	}
160

    
161
	/**
162
	 * Backup chosen config sections
163
	 *
164
	 * @param array $section
165
	 *
166
	 * @return array
167
	 */
168
	public function backup_config_section($section) {
169
		$this->auth();
170

    
171
		global $config;
172

    
173
		return array_intersect_key($config, array_flip($section));
174
	}
175

    
176
	/**
177
	 * Restore defined config section into local config
178
	 *
179
	 * @param array $sections
180
	 *
181
	 * @return bool
182
	 */
183
	public function restore_config_section($sections) {
184
		$this->auth();
185

    
186
		global $config;
187

    
188
		$old_config = $config;
189
		$old_ipsec_enabled = ipsec_enabled();
190

    
191
		if ($this->loop_detected) {
192
			log_error("Disallowing CARP sync loop");
193
			return true;
194
		}
195

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

    
218
		$syncd_full_sections = array();
219

    
220
		foreach ($sync_full_sections as $section) {
221
			if (!isset($sections[$section])) {
222
				continue;
223
			}
224

    
225
			$config[$section] = $sections[$section];
226
			unset($sections[$section]);
227
			$syncd_full_sections[] = $section;
228
		}
229

    
230
		$g2add = array();
231
		$g2del = array();
232
		$g2del_idx = array();
233
		$g2keep = array();
234
		if (is_array($sections['system']['group'])) {
235
			$local_groups = isset($config['system']['group'])
236
			    ? $config['system']['group']
237
			    : array();
238

    
239
			foreach ($sections['system']['group'] as $group) {
240
				$idx = array_search($group['name'],
241
				    array_column($local_groups, 'name'));
242

    
243
				if ($idx === false) {
244
					$g2add[] = $group;
245
				} else if ($group['gid'] < 1999) {
246
					$g2keep[] = $idx;
247
				} else if ($group != $local_groups[$idx]) {
248
					$g2add[] = $group;
249
					$g2del[] = $group;
250
					$g2del_idx[] = $idx;
251
				} else {
252
					$g2keep[] = $idx;
253
				}
254
			}
255
		}
256
		if (is_array($config['system']['group'])) {
257
			foreach ($config['system']['group'] as $idx => $group) {
258
				if (array_search($idx, $g2keep) === false &&
259
				    array_search($idx, $g2del_idx) === false) {
260
					$g2del[] = $group;
261
					$g2del_idx[] = $idx;
262
				}
263
			}
264
		}
265
		unset($sections['system']['group'], $g2keep, $g2del_idx);
266

    
267
		$u2add = array();
268
		$u2del = array();
269
		$u2del_idx = array();
270
		$u2keep = array();
271
		if (is_array($sections['system']['user'])) {
272
			$local_users = isset($config['system']['user'])
273
			    ? $config['system']['user']
274
			    : array();
275

    
276
			foreach ($sections['system']['user'] as $user) {
277
				$idx = array_search($user['name'],
278
				    array_column($local_users, 'name'));
279

    
280
				if ($idx === false) {
281
					$u2add[] = $user;
282
				} else if ($user['uid'] < 2000) {
283
					$u2keep[] = $idx;
284
				} else if ($user != $local_users[$idx]) {
285
					$u2add[] = $user;
286
					$u2del[] = $user;
287
					$u2del_idx[] = $idx;
288
				} else {
289
					$u2keep[] = $idx;
290
				}
291
			}
292
		}
293
		if (is_array($config['system']['user'])) {
294
			foreach ($config['system']['user'] as $idx => $user) {
295
				if (array_search($idx, $u2keep) === false &&
296
				    array_search($idx, $u2del_idx) === false) {
297
					$u2del[] = $user;
298
					$u2del_idx[] = $idx;
299
				}
300
			}
301
		}
302
		unset($sections['system']['user'], $u2keep, $u2del_idx);
303

    
304
		$voucher = array();
305
		if (is_array($sections['voucher'])) {
306
			/* Save voucher rolls to process after merge */
307
			$voucher = $sections['voucher'];
308

    
309
			foreach($sections['voucher'] as $zone => $item) {
310
				unset($sections['voucher'][$zone]['roll']);
311
				if (isset($config['voucher'][$zone]['vouchersyncdbip'])) {
312
					$sections['voucher'][$zone]['vouchersyncdbip'] =
313
					    $config['voucher'][$zone]['vouchersyncdbip'];
314
				} else {
315
					unset($sections['voucher'][$zone]['vouchersyncdbip']);
316
				}
317
				if (isset($config['voucher'][$zone]['vouchersyncport'])) {
318
					$sections['voucher'][$zone]['vouchersyncport'] =
319
					    $config['voucher'][$zone]['vouchersyncport'];
320
				} else {
321
					unset($sections['voucher'][$zone]['vouchersyncport']);
322
				}
323
				if (isset($config['voucher'][$zone]['vouchersyncusername'])) {
324
					$sections['voucher'][$zone]['vouchersyncusername'] =
325
					    $config['voucher'][$zone]['vouchersyncusername'];
326
				} else {
327
					unset($sections['voucher'][$zone]['vouchersyncusername']);
328
				}
329
				if (isset($config['voucher'][$zone]['vouchersyncpass'])) {
330
					$sections['voucher'][$zone]['vouchersyncpass'] =
331
					    $config['voucher'][$zone]['vouchersyncpass'];
332
				} else {
333
					unset($sections['voucher'][$zone]['vouchersyncpass']);
334
				}
335
			}
336
		}
337

    
338
		$vipbackup = array();
339
		$oldvips = array();
340
		if (isset($sections['virtualip']) &&
341
		    is_array($config['virtualip']['vip'])) {
342
			foreach ($config['virtualip']['vip'] as $vip) {
343
				if ($vip['mode'] == "carp") {
344
					$key = $vip['interface'] .
345
					    "_vip" . $vip['vhid'];
346

    
347
					$oldvips[$key]['content'] =
348
					    $vip['password'] .
349
					    $vip['advskew'] .
350
					    $vip['subnet'] .
351
					    $vip['subnet_bits'] .
352
					    $vip['advbase'];
353
					$oldvips[$key]['interface'] =
354
					    $vip['interface'];
355
					$oldvips[$key]['subnet'] =
356
					    $vip['subnet'];
357
				} else if ($vip['mode'] == "ipalias" &&
358
				    (substr($vip['interface'], 0, 4) == '_vip'
359
				    || strstr($vip['interface'], "lo0"))) {
360
					$oldvips[$vip['subnet']]['content'] =
361
					    $vip['interface'] .
362
					    $vip['subnet'] .
363
					    $vip['subnet_bits'];
364
					$oldvips[$vip['subnet']]['interface'] =
365
					    $vip['interface'];
366
					$oldvips[$vip['subnet']]['subnet'] =
367
					    $vip['subnet'];
368
				} else if (($vip['mode'] == "ipalias" ||
369
				    $vip['mode'] == 'proxyarp') &&
370
				    !(substr($vip['interface'], 0, 4) == '_vip')
371
				    || strstr($vip['interface'], "lo0")) {
372
					$vipbackup[] = $vip;
373
				}
374
			}
375
		}
376

    
377
		/* For vip section, first keep items sent from the master */
378
		$config = array_merge_recursive_unique($config, $sections);
379

    
380
		/* Remove locally items removed remote */
381
		foreach ($voucher as $zone => $item) {
382
			/* Zone was deleted on master, delete its vouchers */
383
			if (!isset($config['captiveportal'][$zone])) {
384
				unset($config['voucher'][$zone]);
385
				continue;
386
			}
387
			/* No rolls on master, delete local ones */
388
			if (!is_array($item['roll'])) {
389
				unset($config['voucher'][$zone]['roll']);
390
				continue;
391
			}
392
		}
393

    
394
		$l_rolls = array();
395
		if (is_array($config['voucher'])) {
396
			foreach ($config['voucher'] as $zone => $item) {
397
				if (!is_array($item['roll'])) {
398
					continue;
399
				}
400
				foreach ($item['roll'] as $idx => $roll) {
401
					/* Make it easy to find roll by # */
402
					$l_rolls[$zone][$roll['number']] = $idx;
403
				}
404
			}
405
		}
406

    
407
		/*
408
		 * Process vouchers sent by primary node and:
409
		 * - Add new items
410
		 * - Update existing items based on 'lastsync' field
411
		 */
412
		foreach ($voucher as $zone => $item) {
413
			if (!is_array($item['roll'])) {
414
				continue;
415
			}
416
			foreach ($item['roll'] as $idx => $roll) {
417
				if (!isset($l_rolls[$zone][$roll['number']])) {
418
					$config['voucher'][$zone]['roll'][] =
419
					    $roll;
420
					continue;
421
				}
422
				$l_roll_idx = $l_rolls[$zone][$roll['number']];
423
				$l_vouchers = &$config['voucher'][$zone];
424
				$l_roll = $l_vouchers['roll'][$l_roll_idx];
425
				if (!isset($l_roll['lastsync'])) {
426
					$l_roll['lastsync'] = 0;
427
				}
428

    
429
				if (isset($roll['lastsync']) &&
430
				    $roll['lastsync'] != $l_roll['lastsync']) {
431
					$l_vouchers['roll'][$l_roll_idx] =
432
					    $roll;
433
					unset($l_rolls[$zone][$roll['number']]);
434
				}
435
			}
436
		}
437

    
438
		/*
439
		 * At this point $l_rolls contains only items that are not
440
		 * present on primary node. They must be removed
441
		 */
442
		foreach ($l_rolls as $zone => $item) {
443
			foreach ($item as $number => $idx) {
444
				unset($config['voucher'][$zone][$idx]);
445
			}
446
		}
447

    
448
		/*
449
		 * Then add ipalias and proxyarp types already defined
450
		 * on the backup
451
		 */
452
		if (is_array($vipbackup) && !empty($vipbackup)) {
453
			if (!is_array($config['virtualip'])) {
454
				$config['virtualip'] = array();
455
			}
456
			if (!is_array($config['virtualip']['vip'])) {
457
				$config['virtualip']['vip'] = array();
458
			}
459
			foreach ($vipbackup as $vip) {
460
				array_unshift($config['virtualip']['vip'], $vip);
461
			}
462
		}
463

    
464
		/* Log what happened */
465
		$mergedkeys = implode(", ", array_merge(array_keys($sections),
466
		    $syncd_full_sections));
467
		write_config(sprintf(gettext(
468
		    "Merged in config (%s sections) from XMLRPC client."),
469
		    $mergedkeys));
470

    
471
		/*
472
		 * The real work on handling the vips specially
473
		 * This is a copy of intefaces_vips_configure with addition of
474
		 * not reloading existing/not changed carps
475
		 */
476
		if (isset($sections['virtualip']) &&
477
		    is_array($config['virtualip']) &&
478
		    is_array($config['virtualip']['vip'])) {
479
			$carp_setuped = false;
480
			$anyproxyarp = false;
481

    
482
			foreach ($config['virtualip']['vip'] as $vip) {
483
				$key = "{$vip['interface']}_vip{$vip['vhid']}";
484

    
485
				if ($vip['mode'] == "carp" &&
486
				    isset($oldvips[$key])) {
487
					if ($oldvips[$key]['content'] ==
488
					    $vip['password'] .
489
					    $vip['advskew'] .
490
					    $vip['subnet'] .
491
					    $vip['subnet_bits'] .
492
					    $vip['advbase'] &&
493
					    does_vip_exist($vip)) {
494
						unset($oldvips[$key]);
495
						/*
496
						 * Skip reconfiguring this vips
497
						 * since nothing has changed.
498
						 */
499
						continue;
500
					}
501

    
502
				} elseif ($vip['mode'] == "ipalias" &&
503
				    (substr($vip['interface'], 0, 4) == '_vip'
504
				    || strstr($vip['interface'], "lo0")) &&
505
				    isset($oldvips[$vip['subnet']])) {
506
					$key = $vip['subnet'];
507
					if ($oldvips[$key]['content'] ==
508
					    $vip['interface'] .
509
					    $vip['subnet'] .
510
					    $vip['subnet_bits'] &&
511
					    does_vip_exist($vip)) {
512
						unset($oldvips[$key]);
513
						/*
514
						 * Skip reconfiguring this vips
515
						 * since nothing has changed.
516
						 */
517
						continue;
518
					}
519
					unset($oldvips[$key]);
520
				}
521

    
522
				switch ($vip['mode']) {
523
				case "proxyarp":
524
					$anyproxyarp = true;
525
					break;
526
				case "ipalias":
527
					interface_ipalias_configure($vip);
528
					break;
529
				case "carp":
530
					$carp_setuped = true;
531
					interface_carp_configure($vip);
532
					break;
533
				}
534
			}
535

    
536
			/* Cleanup remaining old carps */
537
			foreach ($oldvips as $oldvipar) {
538
				$oldvipif = get_real_interface(
539
				    $oldvipar['interface']);
540

    
541
				if (empty($oldvipif)) {
542
					continue;
543
				}
544

    
545
				if (is_ipaddrv6($oldvipar['subnet'])) {
546
					 mwexec("/sbin/ifconfig " .
547
					     escapeshellarg($oldvipif) .
548
					     " inet6 " .
549
					     escapeshellarg($oldvipar['subnet']) .
550
					     " delete");
551
				} else {
552
					pfSense_interface_deladdress($oldvipif,
553
					    $oldvipar['subnet']);
554
				}
555
			}
556
			if ($carp_setuped == true) {
557
				interfaces_sync_setup();
558
			}
559
			if ($anyproxyarp == true) {
560
				interface_proxyarp_configure();
561
			}
562
		}
563

    
564
		if ($old_ipsec_enabled !== ipsec_enabled()) {
565
			vpn_ipsec_configure();
566
		}
567

    
568
		unset($old_config);
569

    
570
		local_sync_accounts($u2add, $u2del, $g2add, $g2del);
571
		filter_configure(false);
572

    
573
		return true;
574
	}
575

    
576
	/**
577
	 * Merge items into installedpackages config section
578
	 *
579
	 * @param array $section
580
	 *
581
	 * @return bool
582
	 */
583
	public function merge_installedpackages_section($section) {
584
		$this->auth();
585

    
586
		global $config;
587

    
588
		if ($this->loop_detected) {
589
			log_error("Disallowing CARP sync loop");
590
			return true;
591
		}
592

    
593
		$config['installedpackages'] = array_merge(
594
		    $config['installedpackages'], $section);
595
		$mergedkeys = implode(", ", array_keys($section));
596
		write_config(sprintf(gettext(
597
		    "Merged in config (%s sections) from XMLRPC client."),
598
		    $mergedkeys));
599

    
600
		return true;
601
	}
602

    
603
	/**
604
	 * Merge items into config
605
	 *
606
	 * @param array $section
607
	 *
608
	 * @return bool
609
	 */
610
	public function merge_config_section($section) {
611
		$this->auth();
612

    
613
		global $config;
614

    
615
		if ($this->loop_detected) {
616
			log_error("Disallowing CARP sync loop");
617
			return true;
618
		}
619

    
620
		$config_new = $this->array_overlay($config, $section);
621
		$config = $config_new;
622
		$mergedkeys = implode(", ", array_keys($section));
623
		write_config(sprintf(gettext(
624
		    "Merged in config (%s sections) from XMLRPC client."),
625
		    $mergedkeys));
626

    
627
		return true;
628
	}
629

    
630
	/**
631
	 * Wrapper for filter_configure()
632
	 *
633
	 * @return bool
634
	 */
635
	public function filter_configure($reset_accounts = true) {
636
		$this->auth();
637

    
638
		global $g, $config;
639

    
640
		filter_configure();
641
		system_routing_configure();
642
		setup_gateways_monitor();
643
		relayd_configure();
644
		require_once("openvpn.inc");
645
		openvpn_resync_all();
646

    
647
		/*
648
		 * The DNS Resolver and the DNS Forwarder may both be active so
649
		 * long as * they are running on different ports.
650
		 * See ticket #5882
651
		 */
652
		if (isset($config['dnsmasq']['enable'])) {
653
			/* Configure dnsmasq but tell it NOT to restart DHCP */
654
			services_dnsmasq_configure(false);
655
		} else {
656
			/* kill any running dnsmasq instance */
657
			if (isvalidpid("{$g['varrun_path']}/dnsmasq.pid")) {
658
				sigkillbypid("{$g['varrun_path']}/dnsmasq.pid",
659
				    "TERM");
660
			}
661
		}
662
		if (isset($config['unbound']['enable'])) {
663
			/* Configure unbound but tell it NOT to restart DHCP */
664
			services_unbound_configure(false);
665
		} else {
666
			/* kill any running Unbound instance */
667
			if (isvalidpid("{$g['varrun_path']}/unbound.pid")) {
668
				sigkillbypid("{$g['varrun_path']}/unbound.pid",
669
				    "TERM");
670
			}
671
		}
672

    
673
		/*
674
		 * Call this separately since the above are manually set to
675
		 * skip the DHCP restart they normally perform.
676
		 * This avoids restarting dhcpd twice as described on
677
		 * ticket #3797
678
		 */
679
		services_dhcpd_configure();
680

    
681
		if ($reset_accounts) {
682
			local_reset_accounts();
683
		}
684

    
685
		return true;
686
	}
687

    
688
	/**
689
	 * Wrapper for configuring CARP interfaces
690
	 *
691
	 * @return bool
692
	 */
693
	public function interfaces_carp_configure() {
694
		$this->auth();
695

    
696
		if ($this->loop_detected) {
697
			log_error("Disallowing CARP sync loop");
698
			return true;
699
		}
700

    
701
		interfaces_vips_configure();
702

    
703
		return true;
704
	}
705

    
706
	/**
707
	 * Wrapper for rc.reboot
708
	 *
709
	 * @return bool
710
	 */
711
	public function reboot() {
712
		$this->auth();
713

    
714
		mwexec_bg("/etc/rc.reboot");
715

    
716
		return true;
717
	}
718
}
719

    
720
// run script untill its done and can 'unlock' the xmlrpc.lock, this prevents hanging php-fpm / webgui 
721
ignore_user_abort(true); 
722
set_time_limit(0);
723

    
724
$xmlrpclockkey = lock('xmlrpc', LOCK_EX);
725

    
726
XML_RPC2_Backend::setBackend('php');
727
$HTTP_RAW_POST_DATA = file_get_contents('php://input');
728

    
729
$options = array(
730
	'prefix' => 'pfsense.',
731
	'encoding' => 'utf-8',
732
	'autoDocument' => false,
733
);
734

    
735
$server = XML_RPC2_Server::create(new pfsense_xmlrpc_server(), $options);
736
$server->handleCall();
737

    
738
unlock($xmlrpclockkey);
739

    
740
?>
(232-232/232)