Project

General

Profile

Download (18.8 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

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

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

    
58
	return false;
59
}
60

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

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

    
75
/* EXPOSED FUNCTIONS */
76
$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.");
77
$exec_php_sig = array(
78
	array(
79
		$XML_RPC_Boolean, // First signature element is return value.
80
		$XML_RPC_String, // password
81
		$XML_RPC_String, // shell code to exec
82
	)
83
);
84

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

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

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

    
106
/*****************************/
107
$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.");
108
$exec_shell_sig = array(
109
	array(
110
		$XML_RPC_Boolean, // First signature element is return value.
111
		$XML_RPC_String, // password
112
		$XML_RPC_String, // shell code to exec
113
	)
114
);
115

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

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

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

    
130
/*****************************/
131
$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.");
132
$backup_config_section_sig = array(
133
	array(
134
		$XML_RPC_Struct, // First signature element is return value.
135
		$XML_RPC_String,
136
		$XML_RPC_Array
137
	)
138
);
139

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

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

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

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

    
158
/*****************************/
159
$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.");
160
$restore_config_section_sig = array(
161
	array(
162
		$XML_RPC_Boolean,
163
		$XML_RPC_String,
164
		$XML_RPC_Struct
165
	)
166
);
167

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

    
171
	$old_config = $config;
172

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

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

    
184
	/*
185
	 * Make sure it doesn't end up with both dnsmasq and unbound enabled
186
	 * simultaneously in secondary
187
	 * */
188
	if (isset($params[0]['unbound']['enable']) && isset($config['dnsmasq']['enable'])) {
189
		unset($config['dnsmasq']['enable']);
190
		services_dnsmasq_configure();
191
	} else if (isset($params[0]['dnsmasq']['enable']) && isset($config['unbound']['enable'])) {
192
		unset($config['unbound']['enable']);
193
		services_unbound_configure();
194
	}
195

    
196
	// Some sections should just be copied and not merged or we end
197
	//   up unable to sync the deletion of the last item in a section
198
	$sync_full = array('dnsmasq', 'unbound', 'ipsec', 'aliases', 'wol', 'load_balancer', 'openvpn', 'cert', 'ca', 'crl', 'schedules', 'filter', 'nat', 'dhcpd', 'dhcpv6');
199
	$sync_full_done = array();
200
	foreach ($sync_full as $syncfull) {
201
		if (isset($params[0][$syncfull])) {
202
			$config[$syncfull] = $params[0][$syncfull];
203
			unset($params[0][$syncfull]);
204
			$sync_full_done[] = $syncfull;
205
		}
206
	}
207

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

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

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

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

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

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

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

    
308
	unset($old_config);
309

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

    
313
/*****************************/
314
$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.");
315
$merge_config_section_sig = array(
316
	array(
317
		$XML_RPC_Boolean,
318
		$XML_RPC_String,
319
		$XML_RPC_Struct
320
	)
321
);
322

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

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

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

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

    
343
/*****************************/
344
$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.");
345
$merge_config_section_sig = array(
346
	array(
347
		$XML_RPC_Boolean,
348
		$XML_RPC_String,
349
		$XML_RPC_Struct
350
	)
351
);
352

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

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

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

    
373
/*****************************/
374
$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.");
375
$filter_configure_sig = array(
376
	array(
377
		$XML_RPC_Boolean,
378
		$XML_RPC_String
379
	)
380
);
381

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
523
	return $response;
524
}
525

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

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

    
568
unlock($xmlrpclockkey);
569

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

    
583
?>
(256-256/256)