Project

General

Profile

Download (18.7 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" && (substr($vip['interface'], 0, 4) == '_vip' || strpos($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') && !(substr($vip['interface'], 0, 4) == '_vip') || strpos($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
			$oldvipif = get_real_interface($oldvipar['interface']);
287
			if (!empty($oldvipif)) {
288
				if (is_ipaddrv6($oldvipar['subnet']))
289
					 mwexec("/sbin/ifconfig " . escapeshellarg($oldvipif) . " inet6 " . escapeshellarg($oldvipar['subnet']) . " delete");
290
				else
291
					pfSense_interface_deladdress($oldvipif, $oldvipar['subnet']);
292
			}
293
		}
294
		if ($carp_setuped == true)
295
			interfaces_sync_setup();
296
		if ($anyproxyarp == true)
297
			interface_proxyarp_configure();
298
	}
299

    
300
	if (isset($old_config['ipsec']['enable']) !== isset($config['ipsec']['enable']))
301
		vpn_ipsec_configure();
302

    
303
	unset($old_config);
304

    
305
	return $xmlrpc_g['return']['true'];
306
}
307

    
308
/*****************************/
309
$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.");
310
$merge_config_section_sig = array(
311
	array(
312
		$XML_RPC_Boolean,
313
		$XML_RPC_String,
314
		$XML_RPC_Struct
315
	)
316
);
317

    
318
function merge_installedpackages_section_xmlrpc($raw_params) {
319
	global $config, $xmlrpc_g;
320

    
321
	if (xmlrpc_loop_detect()) {
322
		log_error("Disallowing CARP sync loop");
323
		return;
324
	}
325

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

    
335
	return $xmlrpc_g['return']['true'];
336
}
337

    
338
/*****************************/
339
$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.");
340
$merge_config_section_sig = array(
341
	array(
342
		$XML_RPC_Boolean,
343
		$XML_RPC_String,
344
		$XML_RPC_Struct
345
	)
346
);
347

    
348
function merge_config_section_xmlrpc($raw_params) {
349
	global $config, $xmlrpc_g;
350

    
351
	if (xmlrpc_loop_detect()) {
352
		log_error("Disallowing CARP sync loop");
353
		return;
354
	}
355

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

    
368
/*****************************/
369
$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.");
370
$filter_configure_sig = array(
371
	array(
372
		$XML_RPC_Boolean,
373
		$XML_RPC_String
374
	)
375
);
376

    
377
function filter_configure_xmlrpc($raw_params) {
378
	global $xmlrpc_g, $config;
379

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

    
402
	return $xmlrpc_g['return']['true'];
403
}
404

    
405
/*****************************/
406
$carp_configure_doc = gettext("Basic XMLRPC wrapper for configuring CARP interfaces.");
407
$carp_configure_sig = array(
408
	array(
409
		$XML_RPC_Boolean,
410
		$XML_RPC_String
411
	)
412
);
413

    
414
function interfaces_carp_configure_xmlrpc($raw_params) {
415
	global $xmlrpc_g;
416

    
417
	if (xmlrpc_loop_detect()) {
418
		log_error("Disallowing CARP sync loop");
419
		return;
420
	}
421

    
422
	$params = xmlrpc_params_to_php($raw_params);
423
	if(!xmlrpc_auth($params)) {
424
		xmlrpc_authfail();
425
		return $xmlrpc_g['return']['authfail'];
426
	}
427
	interfaces_vips_configure();
428

    
429
	return $xmlrpc_g['return']['true'];
430
}
431

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

    
435
$check_firmware_version_sig = array(
436
	array(
437
		$XML_RPC_String,
438
		$XML_RPC_String
439
	)
440
);
441

    
442
function check_firmware_version_xmlrpc($raw_params) {
443
	global $xmlrpc_g, $XML_RPC_String;
444

    
445
	$params = xmlrpc_params_to_php($raw_params);
446
	if(!xmlrpc_auth($params)) {
447
		xmlrpc_authfail();
448
		return $xmlrpc_g['return']['authfail'];
449
	}
450
	return new XML_RPC_Response(new XML_RPC_Value(check_firmware_version(false), $XML_RPC_String));
451
}
452

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

    
456
$pfsense_firmware_version_sig = array (
457
        array (
458
                $XML_RPC_Struct,
459
                $XML_RPC_String
460
        )
461
);
462

    
463
function pfsense_firmware_version_xmlrpc($raw_params) {
464
        global $xmlrpc_g;
465

    
466
        $params = xmlrpc_params_to_php($raw_params);
467
        if(!xmlrpc_auth($params)) {
468
			xmlrpc_authfail();
469
			return $xmlrpc_g['return']['authfail'];
470
		}
471
        return new XML_RPC_Response(XML_RPC_encode(host_firmware_version()));
472
}
473

    
474
/*****************************/
475
$reboot_doc = gettext("Basic XMLRPC wrapper for rc.reboot.");
476
$reboot_sig = array(array($XML_RPC_Boolean, $XML_RPC_String));
477
function reboot_xmlrpc($raw_params) {
478
	global $xmlrpc_g;
479

    
480
	$params = xmlrpc_params_to_php($raw_params);
481
	if(!xmlrpc_auth($params)) {
482
		xmlrpc_authfail();
483
		return $xmlrpc_g['return']['authfail'];
484
	}
485
	mwexec_bg("/etc/rc.reboot");
486

    
487
	return $xmlrpc_g['return']['true'];
488
}
489

    
490
/*****************************/
491
$get_notices_sig = array(
492
	array(
493
		$XML_RPC_Array,
494
		$XML_RPC_String
495
	),
496
	array(
497
		$XML_RPC_Array
498
	)
499
);
500

    
501
function get_notices_xmlrpc($raw_params) {
502
	global $g, $xmlrpc_g;
503

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

    
518
	return $response;
519
}
520

    
521
$xmlrpclockkey = lock('xmlrpc', LOCK_EX);
522

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

    
563
unlock($xmlrpclockkey);
564

    
565
    function array_overlay($a1,$a2)
566
    {
567
        foreach($a1 as $k => $v) {
568
            if(!array_key_exists($k,$a2)) continue;
569
            if(is_array($v) && is_array($a2[$k])){
570
                $a1[$k] = array_overlay($v,$a2[$k]);
571
            }else{
572
                $a1[$k] = $a2[$k];
573
            }
574
        }
575
        return $a1;
576
    }
577

    
578
?>
(252-252/252)