Project

General

Profile

Download (18.9 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	xmlrpc.php
4
	Copyright (C) 2013-2015 Electric Sheep Fencing, LP
5
	Copyright (C) 2009, 2010 Scott Ullrich
6
	Copyright (C) 2005 Colin Smith
7
	All rights reserved.
8

    
9
	Redistribution and use in source and binary forms, with or without
10
	modification, are permitted provided that the following conditions are met:
11

    
12
	1. Redistributions of source code must retain the above copyright notice,
13
	   this list of conditions and the following disclaimer.
14

    
15
	2. Redistributions in binary form must reproduce the above copyright
16
	   notice, this list of conditions and the following disclaimer in the
17
	   documentation and/or other materials provided with the distribution.
18

    
19
	THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
20
	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
21
	AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22
	AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
23
	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
	POSSIBILITY OF SUCH DAMAGE.
29
*/
30

    
31
##|+PRIV
32
##|*IDENT=page-xmlrpclibrary
33
##|*NAME=XMLRPC Library page
34
##|*DESCR=Allow access to the 'XMLRPC Library' page.
35
##|*MATCH=xmlrpc.php*
36
##|-PRIV
37

    
38
require("config.inc");
39
require("functions.inc");
40
require_once("filter.inc");
41
require("ipsec.inc");
42
require("vpn.inc");
43
require("shaper.inc");
44
require("xmlrpc_server.inc");
45
require("xmlrpc.inc");
46
require("array_intersect_key.inc");
47

    
48
function xmlrpc_loop_detect() {
49
	global $config;
50

    
51
	/* grab sync to ip if enabled */
52
	if ($config['hasync'])
53
		$synchronizetoip = $config['hasync']['synchronizetoip'];
54
	if($synchronizetoip) {
55
		if($synchronizetoip == $_SERVER['REMOTE_ADDR'])
56
			return true;	
57
	}
58

    
59
	return false;
60
}
61

    
62
$xmlrpc_g = array(
63
	"return" => array(
64
		"true" => new XML_RPC_Response(new XML_RPC_Value(true, $XML_RPC_Boolean)),
65
		"false" => new XML_RPC_Response(new XML_RPC_Value(false, $XML_RPC_Boolean)),
66
		"authfail" => new XML_RPC_Response(new XML_RPC_Value(gettext("Authentication failed"), $XML_RPC_String))
67
	)
68
);
69

    
70
/*
71
 *   pfSense XMLRPC errors
72
 *   $XML_RPC_erruser + 1 = Auth failure
73
 */
74
$XML_RPC_erruser = 200;
75

    
76
/* EXPOSED FUNCTIONS */
77
$exec_php_doc = gettext("XMLRPC wrapper for eval(). This method must be called with two parameters: a string containing the local system\'s password followed by the PHP code to evaluate.");
78
$exec_php_sig = array(
79
	array(
80
		$XML_RPC_Boolean, // First signature element is return value.
81
		$XML_RPC_String, // password
82
		$XML_RPC_String, // shell code to exec
83
	)
84
);
85

    
86
function xmlrpc_authfail() {
87
	log_auth("webConfigurator authentication error for 'admin' from {$_SERVER['REMOTE_ADDR']}");
88
}
89

    
90
function exec_php_xmlrpc($raw_params) {
91
	global $config, $xmlrpc_g;
92

    
93
	$params = xmlrpc_params_to_php($raw_params);
94
	if(!xmlrpc_auth($params)) {
95
		xmlrpc_authfail();
96
		return $xmlrpc_g['return']['authfail'];
97
	}
98
	$exec_php = $params[0];
99
	eval($exec_php);
100
	if($toreturn) {
101
		$response = XML_RPC_encode($toreturn);
102
		return new XML_RPC_Response($response);
103
	} else
104
		return $xmlrpc_g['return']['true'];
105
}
106

    
107
/*****************************/
108
$exec_shell_doc = gettext("XMLRPC wrapper for mwexec(). This method must be called with two parameters: a string containing the local system\'s password followed by an shell command to execute.");
109
$exec_shell_sig = array(
110
	array(
111
		$XML_RPC_Boolean, // First signature element is return value.
112
		$XML_RPC_String, // password
113
		$XML_RPC_String, // shell code to exec
114
	)
115
);
116

    
117
function exec_shell_xmlrpc($raw_params) {
118
	global $config, $xmlrpc_g;
119

    
120
	$params = xmlrpc_params_to_php($raw_params);
121
	if(!xmlrpc_auth($params)) {
122
		xmlrpc_authfail();
123
		return $xmlrpc_g['return']['authfail'];
124
	}
125
	$shell_cmd = $params[0];
126
	mwexec($shell_cmd);
127

    
128
	return $xmlrpc_g['return']['true'];
129
}
130

    
131
/*****************************/
132
$backup_config_section_doc = gettext("XMLRPC wrapper for backup_config_section. This method must be called with two parameters: a string containing the local system\'s password followed by an array containing the keys to be backed up.");
133
$backup_config_section_sig = array(
134
	array(
135
		$XML_RPC_Struct, // First signature element is return value.
136
		$XML_RPC_String,
137
		$XML_RPC_Array
138
	)
139
);
140

    
141
function backup_config_section_xmlrpc($raw_params) {
142
	global $config, $xmlrpc_g;
143

    
144
	if (xmlrpc_loop_detect()) {
145
		log_error("Disallowing CARP sync loop");
146
		return;
147
	}
148

    
149
	$params = xmlrpc_params_to_php($raw_params);
150
	if(!xmlrpc_auth($params)) {
151
		xmlrpc_authfail();
152
		return $xmlrpc_g['return']['authfail'];
153
	}
154
	$val = array_intersect_key($config, array_flip($params[0]));
155

    
156
	return new XML_RPC_Response(XML_RPC_encode($val));
157
}
158

    
159
/*****************************/
160
$restore_config_section_doc = gettext("XMLRPC wrapper for restore_config_section. This method must be called with two parameters: a string containing the local system\'s password and an array to merge into the system\'s config. This function returns true upon completion.");
161
$restore_config_section_sig = array(
162
	array(
163
		$XML_RPC_Boolean,
164
		$XML_RPC_String,
165
		$XML_RPC_Struct
166
	)
167
);
168

    
169
function restore_config_section_xmlrpc($raw_params) {
170
	global $config, $xmlrpc_g;
171

    
172
	$old_config = $config;
173

    
174
	if (xmlrpc_loop_detect()) {
175
		log_error("Disallowing CARP sync loop");
176
		return;
177
	}
178

    
179
	$params = xmlrpc_params_to_php($raw_params);
180
	if(!xmlrpc_auth($params)) {
181
		xmlrpc_authfail();
182
		return $xmlrpc_g['return']['authfail'];
183
	}
184

    
185
	/*
186
	 * Make sure it doesn't end up with both dnsmasq and unbound enabled
187
	 * simultaneously in secondary
188
	 * */
189
	if (isset($params[0]['unbound']['enable']) && isset($config['dnsmasq']['enable'])) {
190
		unset($config['dnsmasq']['enable']);
191
		services_dnsmasq_configure();
192
	} else if (isset($params[0]['dnsmasq']['enable']) && isset($config['unbound']['enable'])) {
193
		unset($config['unbound']['enable']);
194
		services_unbound_configure();
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
	$sync_full = array('dnsmasq', 'unbound', 'ipsec', 'aliases', 'wol', 'load_balancer', 'openvpn', 'cert', 'ca', 'crl', 'schedules', 'filter', 'nat', 'dhcpd', 'dhcpv6');
200
	$sync_full_done = array();
201
	foreach ($sync_full as $syncfull) {
202
		if (isset($params[0][$syncfull])) {
203
			$config[$syncfull] = $params[0][$syncfull];
204
			unset($params[0][$syncfull]);
205
			$sync_full_done[] = $syncfull;
206
		}
207
	}
208

    
209
	$vipbackup = array();
210
	$oldvips = array();
211
	if (isset($params[0]['virtualip'])) {
212
		if (is_array($config['virtualip']['vip'])) {
213
			foreach ($config['virtualip']['vip'] as $vipindex => $vip) {
214
				if ($vip['mode'] == "carp") {
215
					$oldvips["{$vip['interface']}_vip{$vip['vhid']}"]['content'] = "{$vip['password']}{$vip['advskew']}{$vip['subnet']}{$vip['subnet_bits']}{$vip['advbase']}";
216
					$oldvips["{$vip['interface']}_vip{$vip['vhid']}"]['interface'] = $vip['interface'];
217
					$oldvips["{$vip['interface']}_vip{$vip['vhid']}"]['subnet'] = $vip['subnet'];
218
				} else if ($vip['mode'] == "ipalias" && (strstr($vip['interface'], "_vip") || strstr($vip['interface'], "lo0"))) {
219
					$oldvips[$vip['subnet']]['content'] = "{$vip['interface']}{$vip['subnet']}{$vip['subnet_bits']}";
220
					$oldvips[$vip['subnet']]['interface'] = $vip['interface'];
221
					$oldvips[$vip['subnet']]['subnet'] = $vip['subnet'];
222
				} else if (($vip['mode'] == "ipalias" || $vip['mode'] == 'proxyarp') && !(strstr($vip['interface'], "_vip") || strstr($vip['interface'], "lo0"))) {
223
					$vipbackup[] = $vip;
224
				}
225
			}
226
		}
227
	}
228

    
229
        // For vip section, first keep items sent from the master
230
	$config = array_merge_recursive_unique($config, $params[0]);
231

    
232
        /* Then add ipalias and proxyarp types already defined on the backup */
233
	if (is_array($vipbackup) && !empty($vipbackup)) {
234
		if (!is_array($config['virtualip']))
235
			$config['virtualip'] = array();
236
		if (!is_array($config['virtualip']['vip']))
237
			$config['virtualip']['vip'] = array();
238
		foreach ($vipbackup as $vip)
239
			array_unshift($config['virtualip']['vip'], $vip);
240
	}
241

    
242
	/* Log what happened */
243
	$mergedkeys = implode(",", array_merge(array_keys($params[0]), $sync_full_done));
244
	write_config(sprintf(gettext("Merged in config (%s sections) from XMLRPC client."),$mergedkeys));
245

    
246
	/* 
247
	 * The real work on handling the vips specially
248
	 * This is a copy of intefaces_vips_configure with addition of not reloading existing/not changed carps
249
	 */
250
	if (isset($params[0]['virtualip']) && is_array($config['virtualip']) && is_array($config['virtualip']['vip'])) {
251
		$carp_setuped = false;
252
		$anyproxyarp = false;
253
		foreach ($config['virtualip']['vip'] as $vip) {
254
			if ($vip['mode'] == "carp" && isset($oldvips["{$vip['interface']}_vip{$vip['vhid']}"])) {
255
				if ($oldvips["{$vip['interface']}_vip{$vip['vhid']}"]['content'] == "{$vip['password']}{$vip['advskew']}{$vip['subnet']}{$vip['subnet_bits']}{$vip['advbase']}") {
256
					if (does_vip_exist($vip)) {
257
						unset($oldvips["{$vip['interface']}_vip{$vip['vhid']}"]);
258
						continue; // Skip reconfiguring this vips since nothing has changed.
259
					}
260
				}
261
			} else if ($vip['mode'] == "ipalias" && strstr($vip['interface'], "_vip") && isset($oldvips[$vip['subnet']])) {
262
				if ($oldvips[$vip['subnet']]['content'] == "{$vip['interface']}{$vip['subnet']}{$vip['subnet_bits']}") {
263
					if (does_vip_exist($vip)) {
264
						unset($oldvips[$vip['subnet']]);
265
						continue; // Skip reconfiguring this vips since nothing has changed.
266
					}
267
				}
268
				unset($oldvips[$vip['subnet']]);
269
			}
270

    
271
			switch ($vip['mode']) {
272
			case "proxyarp":
273
				$anyproxyarp = true;
274
				break;
275
			case "ipalias":
276
				interface_ipalias_configure($vip);
277
				break;
278
			case "carp":
279
				if ($carp_setuped == false)
280
                                        $carp_setuped = true;
281
				interface_carp_configure($vip);
282
				break;
283
			}
284
		}
285
		/* Cleanup remaining old carps */
286
		foreach ($oldvips as $oldvipar) {
287
			if (strstr($oldvipar['interface'], '_vip')) {
288
				list($oldvipif, $vhid) = explode('_vip', $oldvipar['interface']);
289
				$oldvipif = get_real_interface($oldvipif);
290
			} else {
291
				$oldvipif = get_real_interface($oldvipar['interface']);
292
			}
293
			if (!empty($oldvipif)) {
294
				if (is_ipaddrv6($oldvipar['subnet']))
295
					 mwexec("/sbin/ifconfig " . escapeshellarg($oldvipif) . " inet6 " . escapeshellarg($oldvipar['subnet']) . " delete");
296
				else
297
					pfSense_interface_deladdress($oldvipif, $oldvipar['subnet']);
298
			}
299
		}
300
		if ($carp_setuped == true)
301
			interfaces_sync_setup();
302
		if ($anyproxyarp == true)
303
			interface_proxyarp_configure();
304
	}
305

    
306
	if (isset($old_config['ipsec']['enable']) !== isset($config['ipsec']['enable']))
307
		vpn_ipsec_configure();
308

    
309
	unset($old_config);
310

    
311
	return $xmlrpc_g['return']['true'];
312
}
313

    
314
/*****************************/
315
$merge_config_section_doc = gettext("XMLRPC wrapper for merging package sections. This method must be called with two parameters: a string containing the local system\'s password and an array to merge into the system\'s config. This function returns true upon completion.");
316
$merge_config_section_sig = array(
317
	array(
318
		$XML_RPC_Boolean,
319
		$XML_RPC_String,
320
		$XML_RPC_Struct
321
	)
322
);
323

    
324
function merge_installedpackages_section_xmlrpc($raw_params) {
325
	global $config, $xmlrpc_g;
326

    
327
	if (xmlrpc_loop_detect()) {
328
		log_error("Disallowing CARP sync loop");
329
		return;
330
	}
331

    
332
	$params = xmlrpc_params_to_php($raw_params);
333
	if(!xmlrpc_auth($params)) {
334
		xmlrpc_authfail();
335
		return $xmlrpc_g['return']['authfail'];
336
	}
337
	$config['installedpackages'] = array_merge($config['installedpackages'], $params[0]);
338
	$mergedkeys = implode(",", array_keys($params[0]));
339
	write_config(sprintf(gettext("Merged in config (%s sections) from XMLRPC client."),$mergedkeys));
340

    
341
	return $xmlrpc_g['return']['true'];
342
}
343

    
344
/*****************************/
345
$merge_config_section_doc = gettext("XMLRPC wrapper for merge_config_section. This method must be called with two parameters: a string containing the local system\'s password and an array to merge into the system\'s config. This function returns true upon completion.");
346
$merge_config_section_sig = array(
347
	array(
348
		$XML_RPC_Boolean,
349
		$XML_RPC_String,
350
		$XML_RPC_Struct
351
	)
352
);
353

    
354
function merge_config_section_xmlrpc($raw_params) {
355
	global $config, $xmlrpc_g;
356

    
357
	if (xmlrpc_loop_detect()) {
358
		log_error("Disallowing CARP sync loop");
359
		return;
360
	}
361

    
362
	$params = xmlrpc_params_to_php($raw_params);
363
	if(!xmlrpc_auth($params)) {
364
		xmlrpc_authfail();
365
		return $xmlrpc_g['return']['authfail'];
366
	}
367
	$config_new = array_overlay($config, $params[0]);
368
	$config = $config_new;
369
	$mergedkeys = implode(",", array_keys($params[0]));
370
	write_config(sprintf(gettext("Merged in config (%s sections) from XMLRPC client."), $mergedkeys));
371
	return $xmlrpc_g['return']['true'];
372
}
373

    
374
/*****************************/
375
$filter_configure_doc = gettext("Basic XMLRPC wrapper for filter_configure. This method must be called with one paramater: a string containing the local system\'s password. This function returns true upon completion.");
376
$filter_configure_sig = array(
377
	array(
378
		$XML_RPC_Boolean,
379
		$XML_RPC_String
380
	)
381
);
382

    
383
function filter_configure_xmlrpc($raw_params) {
384
	global $xmlrpc_g, $config;
385

    
386
	$params = xmlrpc_params_to_php($raw_params);
387
	if(!xmlrpc_auth($params)) {
388
		xmlrpc_authfail();
389
		return $xmlrpc_g['return']['authfail'];
390
	}
391
	filter_configure();
392
	system_routing_configure();
393
	setup_gateways_monitor();
394
	relayd_configure();
395
	require_once("openvpn.inc");
396
	openvpn_resync_all();
397
	if (isset($config['dnsmasq']['enable']))
398
		services_dnsmasq_configure();
399
	elseif (isset($config['unbound']['enable']))
400
		services_unbound_configure();
401
	else
402
		# Both calls above run services_dhcpd_configure(), then we just
403
		# need to call it when them are not called to avoid restart dhcpd
404
		# twice, as described on ticket #3797
405
		services_dhcpd_configure();
406
	local_sync_accounts();
407

    
408
	return $xmlrpc_g['return']['true'];
409
}
410

    
411
/*****************************/
412
$carp_configure_doc = gettext("Basic XMLRPC wrapper for configuring CARP interfaces.");
413
$carp_configure_sig = array(
414
	array(
415
		$XML_RPC_Boolean,
416
		$XML_RPC_String
417
	)
418
);
419

    
420
function interfaces_carp_configure_xmlrpc($raw_params) {
421
	global $xmlrpc_g;
422

    
423
	if (xmlrpc_loop_detect()) {
424
		log_error("Disallowing CARP sync loop");
425
		return;
426
	}
427

    
428
	$params = xmlrpc_params_to_php($raw_params);
429
	if(!xmlrpc_auth($params)) {
430
		xmlrpc_authfail();
431
		return $xmlrpc_g['return']['authfail'];
432
	}
433
	interfaces_vips_configure();
434

    
435
	return $xmlrpc_g['return']['true'];
436
}
437

    
438
/*****************************/
439
$check_firmware_version_doc = gettext("Basic XMLRPC wrapper for check_firmware_version. This function will return the output of check_firmware_version upon completion.");
440

    
441
$check_firmware_version_sig = array(
442
	array(
443
		$XML_RPC_String,
444
		$XML_RPC_String
445
	)
446
);
447

    
448
function check_firmware_version_xmlrpc($raw_params) {
449
	global $xmlrpc_g, $XML_RPC_String;
450

    
451
	$params = xmlrpc_params_to_php($raw_params);
452
	if(!xmlrpc_auth($params)) {
453
		xmlrpc_authfail();
454
		return $xmlrpc_g['return']['authfail'];
455
	}
456
	return new XML_RPC_Response(new XML_RPC_Value(check_firmware_version(false), $XML_RPC_String));
457
}
458

    
459
/*****************************/
460
$pfsense_firmware_version_doc = gettext("Basic XMLRPC wrapper for check_firmware_version. This function will return the output of check_firmware_version upon completion.");
461

    
462
$pfsense_firmware_version_sig = array (
463
        array (
464
                $XML_RPC_Struct,
465
                $XML_RPC_String
466
        )
467
);
468

    
469
function pfsense_firmware_version_xmlrpc($raw_params) {
470
        global $xmlrpc_g;
471

    
472
        $params = xmlrpc_params_to_php($raw_params);
473
        if(!xmlrpc_auth($params)) {
474
			xmlrpc_authfail();
475
			return $xmlrpc_g['return']['authfail'];
476
		}
477
        return new XML_RPC_Response(XML_RPC_encode(host_firmware_version()));
478
}
479

    
480
/*****************************/
481
$reboot_doc = gettext("Basic XMLRPC wrapper for rc.reboot.");
482
$reboot_sig = array(array($XML_RPC_Boolean, $XML_RPC_String));
483
function reboot_xmlrpc($raw_params) {
484
	global $xmlrpc_g;
485

    
486
	$params = xmlrpc_params_to_php($raw_params);
487
	if(!xmlrpc_auth($params)) {
488
		xmlrpc_authfail();
489
		return $xmlrpc_g['return']['authfail'];
490
	}
491
	mwexec_bg("/etc/rc.reboot");
492

    
493
	return $xmlrpc_g['return']['true'];
494
}
495

    
496
/*****************************/
497
$get_notices_sig = array(
498
	array(
499
		$XML_RPC_Array,
500
		$XML_RPC_String
501
	),
502
	array(
503
		$XML_RPC_Array
504
	)
505
);
506

    
507
function get_notices_xmlrpc($raw_params) {
508
	global $g, $xmlrpc_g;
509

    
510
	$params = xmlrpc_params_to_php($raw_params);
511
	if(!xmlrpc_auth($params)) {
512
		xmlrpc_authfail();
513
		return $xmlrpc_g['return']['authfail'];
514
	}
515
	if(!function_exists("get_notices"))
516
		require("notices.inc");
517
	if(!$params) {
518
		$toreturn = get_notices();
519
	} else {
520
		$toreturn = get_notices($params);
521
	}
522
	$response = new XML_RPC_Response(XML_RPC_encode($toreturn));
523

    
524
	return $response;
525
}
526

    
527
$xmlrpclockkey = lock('xmlrpc', LOCK_EX);
528

    
529
/*****************************/
530
$server = new XML_RPC_Server(
531
        array(
532
		'pfsense.exec_shell' => array('function' => 'exec_shell_xmlrpc',
533
			'signature' => $exec_shell_sig,
534
			'docstring' => $exec_shell_doc),
535
		'pfsense.exec_php' => array('function' => 'exec_php_xmlrpc',
536
			'signature' => $exec_php_sig,
537
			'docstring' => $exec_php_doc),	
538
		'pfsense.filter_configure' => array('function' => 'filter_configure_xmlrpc',
539
			'signature' => $filter_configure_sig,
540
			'docstring' => $filter_configure_doc),
541
		'pfsense.interfaces_carp_configure' => array('function' => 'interfaces_carp_configure_xmlrpc',
542
			'docstring' => $carp_configure_sig),
543
		'pfsense.backup_config_section' => array('function' => 'backup_config_section_xmlrpc',
544
			'signature' => $backup_config_section_sig,
545
			'docstring' => $backup_config_section_doc),
546
		'pfsense.restore_config_section' => array('function' => 'restore_config_section_xmlrpc',
547
			'signature' => $restore_config_section_sig,
548
			'docstring' => $restore_config_section_doc),
549
		'pfsense.merge_config_section' => array('function' => 'merge_config_section_xmlrpc',
550
			'signature' => $merge_config_section_sig,
551
			'docstring' => $merge_config_section_doc),
552
		'pfsense.merge_installedpackages_section_xmlrpc' => array('function' => 'merge_installedpackages_section_xmlrpc',
553
			'signature' => $merge_config_section_sig,
554
			'docstring' => $merge_config_section_doc),							
555
		'pfsense.check_firmware_version' => array('function' => 'check_firmware_version_xmlrpc',
556
			'signature' => $check_firmware_version_sig,
557
			'docstring' => $check_firmware_version_doc),
558
		'pfsense.host_firmware_version' => array('function' => 'pfsense_firmware_version_xmlrpc',
559
			'signature' => $pfsense_firmware_version_sig,
560
			'docstring' => $host_firmware_version_doc),
561
		'pfsense.reboot' => array('function' => 'reboot_xmlrpc',
562
			'signature' => $reboot_sig,
563
			'docstring' => $reboot_doc),
564
		'pfsense.get_notices' => array('function' => 'get_notices_xmlrpc',
565
			'signature' => $get_notices_sig)
566
        )
567
);
568

    
569
unlock($xmlrpclockkey);
570

    
571
    function array_overlay($a1,$a2)
572
    {
573
        foreach($a1 as $k => $v) {
574
            if(!array_key_exists($k,$a2)) continue;
575
            if(is_array($v) && is_array($a2[$k])){
576
                $a1[$k] = array_overlay($v,$a2[$k]);
577
            }else{
578
                $a1[$k] = $a2[$k];
579
            }
580
        }
581
        return $a1;
582
    }
583

    
584
?>
(256-256/256)