Project

General

Profile

Download (37.9 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php 
2
/*
3
	util.inc
4
	part of the pfSense project (http://www.pfsense.com)
5

    
6
	originally part of m0n0wall (http://m0n0.ch/wall)
7
	Copyright (C) 2003-2004 Manuel Kasper <mk@neon1.net>.
8
	All rights reserved.
9

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

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

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

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

    
32
/*
33
	pfSense_BUILDER_BINARIES:	/bin/ps	/bin/kill	/usr/bin/killall	/sbin/ifconfig	/usr/bin/netstat
34
	pfSense_BUILDER_BINARIES:	/usr/bin/awk	/sbin/dmesg		/sbin/ping /usr/local/sbin/gzsig	/usr/sbin/arp
35
	pfSense_BUILDER_BINARIES:	/sbin/conscontrol	/sbin/devd	/bin/ps
36
	pfSense_MODULE:	utils
37
*/
38

    
39
/* kill a process by pid file */
40
function killbypid($pidfile) {
41
	sigkillbypid($pidfile, "TERM");
42
}
43

    
44
function isvalidpid($pid) {
45
	$output = "";
46
	exec("/bin/pgrep -F {$pid}", $output, $retval);
47

    
48
        return (intval($retval) == 0);
49
}
50

    
51
function is_process_running($process) {
52
	$output = "";
53
	exec("/bin/pgrep -x {$process}", $output, $retval);
54

    
55
        return (intval($retval) == 0);
56
}
57

    
58
function isvalidproc($proc) {
59
	return is_process_running($proc);
60
}
61

    
62
/* sigkill a process by pid file */
63
/* return 1 for success and 0 for a failure */
64
function sigkillbypid($pidfile, $sig) {
65
	if (is_file($pidfile))
66
		return mwexec("/bin/pkill -{$sig} -F {$pidfile}", true);
67

    
68
	return 0;
69
}
70

    
71
/* kill a process by name */
72
function sigkillbyname($procname, $sig) {
73
	if(isvalidproc($procname))
74
		return mwexec("/usr/bin/killall -{$sig} " . escapeshellarg($procname), true);
75
}
76

    
77
/* kill a process by name */
78
function killbyname($procname) {
79
	if(isvalidproc($procname)) 
80
		mwexec("/usr/bin/killall " . escapeshellarg($procname));
81
}
82

    
83
function is_subsystem_dirty($subsystem = "") {
84
	global $g;
85

    
86
	if ($subsystem == "")
87
		return false;
88

    
89
	if (file_exists("{$g['varrun_path']}/{$subsystem}.dirty"))
90
		return true;
91

    
92
	return false;
93
}
94

    
95
function mark_subsystem_dirty($subsystem = "") {
96
	global $g;
97

    
98
	if (!file_put_contents("{$g['varrun_path']}/{$subsystem}.dirty", "DIRTY"))
99
		log_error("WARNING: Could not mark subsystem: {$subsytem} dirty");
100
}
101

    
102
function clear_subsystem_dirty($subsystem = "") {
103
	global $g;
104

    
105
	@unlink("{$g['varrun_path']}/{$subsystem}.dirty");
106
}
107

    
108
function config_lock() {
109
	return;
110
}
111
function config_unlock() {
112
	return;
113
}
114

    
115
/* lock configuration file */
116
function lock($lock, $op = LOCK_SH) {
117
	global $g, $cfglckkeyconsumers;
118
	if (!$lock)
119
		die("WARNING: You must give a name as parameter to lock() function.");
120
	if (!file_exists("{$g['tmp_path']}/{$lock}.lock"))
121
		@touch("{$g['tmp_path']}/{$lock}.lock");
122
	$cfglckkeyconsumers++;
123
	if ($fp = fopen("{$g['tmp_path']}/{$lock}.lock", "w")) {
124
		if (flock($fp, $op))
125
			return $fp;
126
		else
127
			fclose($fp);
128
	}
129
}
130

    
131
/* unlock configuration file */
132
function unlock($cfglckkey = 0) {
133
	global $g, $cfglckkeyconsumers;
134
	flock($cfglckkey, LOCK_UN);
135
	fclose($cfglckkey);
136
	return;
137
}
138

    
139
function send_event($cmd) {
140
	global $g;
141

    
142
	$try = 0;
143
	while ($try < 3) {
144
		$fd = @fsockopen($g['event_address']);
145
		if ($fd) {
146
			fwrite($fd, $cmd);
147
			$resp = fread($fd, 4096);
148
			if ($resp != "OK\n")
149
				log_error("send_event: sent {$cmd} got {$resp}");
150
			fclose($fd);
151
			$try = 3;
152
		} else
153
			mwexec_bg("/usr/bin/nice -n20 /usr/local/sbin/check_reload_status");
154
		$try++;
155
	}
156
}
157

    
158
function send_multiple_events($cmds) {
159
        global $g;
160

    
161
	if (!is_array($cmds))
162
		return;
163
        $fd = fsockopen($g['event_address']);
164
        if ($fd) {
165
		foreach ($cmds as $cmd) {
166
                	fwrite($fd, $cmd);
167
                	$resp = fread($fd, 4096);
168
                	if ($resp != "OK\n")
169
                        	log_error("send_event: sent {$cmd} got {$resp}");
170
		}
171
                fclose($fd);
172
        }
173
}
174

    
175
function refcount_init($reference) {
176
	$shmid = shmop_open($reference, "c", 0644, 10);
177
	shmop_write($shmid, 0, 0);
178
	shmop_close($shmid);
179
}
180

    
181
function refcount_reference($reference) {
182
	$shmid = @shmop_open($reference, "w", 0644, 10);
183
	if (!$shmid) {
184
		refcount_init($reference);
185
		$shmid = shmop_open($reference, "w", 0644, 10);
186
	}
187
	$shm_data = shmop_read($shmid, 0, 10);
188
	$shm_data = intval($shm_data) + 1;
189
	shmop_write($shmid, $shm_data, 0);
190
	shmop_close($shmid);
191
	
192
	return $shm_data;
193
}
194

    
195
function refcount_unreference($reference) {
196
	/* We assume that the shared memory exists. */
197
	$shmid = shmop_open($reference, "w", 0644, 10);
198
	$shm_data = shmop_read($shmid, 0, 10);
199
	$shm_data = intval($shm_data) - 1;
200
	if ($shm_data < 0) {
201
		//debug_backtrace();
202
		log_error("Reference {$reference} is going negative, not doing unreference.");
203
	} else
204
		shmop_write($shmid, $shm_data, 0);
205
	shmop_close($shmid);
206
	
207
	return $shm_data;
208
}
209

    
210
function is_module_loaded($module_name) {
211
	$running = `/sbin/kldstat | grep {$module_name} | /usr/bin/grep -v grep | /usr/bin/wc -l`;
212
	if (intval($running) >= 1)
213
		return true;
214
	else
215
		return false;
216
}
217

    
218
/* return the subnet address given a host address and a subnet bit count */
219
function gen_subnet($ipaddr, $bits) {
220
	if (!is_ipaddr($ipaddr) || !is_numeric($bits))
221
		return "";
222

    
223
	return long2ip(ip2long($ipaddr) & gen_subnet_mask_long($bits));
224
}
225

    
226
/* return the highest (broadcast) address in the subnet given a host address and a subnet bit count */
227
function gen_subnet_max($ipaddr, $bits) {
228
	if (!is_ipaddr($ipaddr) || !is_numeric($bits))
229
		return "";
230

    
231
	return long2ip32(ip2long($ipaddr) | ~gen_subnet_mask_long($bits));
232
}
233

    
234
/* returns a subnet mask (long given a bit count) */
235
function gen_subnet_mask_long($bits) {
236
	$sm = 0;
237
	for ($i = 0; $i < $bits; $i++) {
238
		$sm >>= 1;
239
		$sm |= 0x80000000;
240
	}
241
	return $sm;
242
}
243

    
244
/* same as above but returns a string */
245
function gen_subnet_mask($bits) {
246
	return long2ip(gen_subnet_mask_long($bits));
247
}
248

    
249
/* Convert long int to IP address, truncating to 32-bits. */
250
function long2ip32($ip) {
251
	return long2ip($ip & 0xFFFFFFFF);
252
}
253

    
254
/* Convert IP address to long int, truncated to 32-bits to avoid sign extension on 64-bit platforms. */
255
function ip2long32($ip) {
256
	return ( ip2long($ip) & 0xFFFFFFFF );
257
}
258

    
259
/* Convert IP address to unsigned long int. */
260
function ip2ulong($ip) {
261
	return sprintf("%u", ip2long32($ip));
262
}
263

    
264
/* Find out how many IPs are contained within a given IP range
265
 *  e.g. 192.168.0.0 to 192.168.0.255 returns 256
266
 */
267
function ip_range_size($startip, $endip) {
268
	if (is_ipaddr($startip) && is_ipaddr($endip)) {
269
		// Operate as unsigned long because otherwise it wouldn't work
270
		//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
271
		return abs(ip2ulong($startip) - ip2ulong($endip)) + 1;
272
	}
273
	return -1;
274
}
275

    
276
/* Find the smallest possible subnet mask which can contain a given number of IPs
277
 *  e.g. 512 IPs can fit in a /23, but 513 IPs need a /22
278
 */
279
function find_smallest_cidr($number) {
280
	$smallest = 1;
281
	for ($b=32; $b > 0; $b--) {
282
		$smallest = ($number <= pow(2,$b)) ? $b : $smallest;
283
	}
284
	return (32-$smallest);
285
}
286

    
287
/* Return the previous IP address before the given address */
288
function ip_before($ip) {
289
	return long2ip32(ip2long($ip)-1);
290
}
291

    
292
/* Return the next IP address after the given address */
293
function ip_after($ip) {
294
	return long2ip32(ip2long($ip)+1);
295
}
296

    
297
/* Return true if the first IP is 'before' the second */
298
function ip_less_than($ip1, $ip2) {
299
	// Compare as unsigned long because otherwise it wouldn't work when
300
	//   crossing over from 127.255.255.255 / 128.0.0.0 barrier
301
	return ip2ulong($ip1) < ip2ulong($ip2);
302
}
303

    
304
/* Return true if the first IP is 'after' the second */
305
function ip_greater_than($ip1, $ip2) {
306
	// Compare as unsigned long because otherwise it wouldn't work
307
	//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
308
	return ip2ulong($ip1) > ip2ulong($ip2);
309
}
310

    
311
/* Convert a range of IPs to an array of subnets which can contain the range. */
312
function ip_range_to_subnet_array($startip, $endip) {
313
	if (!is_ipaddr($startip) || !is_ipaddr($endip)) {
314
		return array();
315
	}
316

    
317
	// Container for subnets within this range.
318
	$rangesubnets = array();
319

    
320
	// Figure out what the smallest subnet is that holds the number of IPs in the given range.
321
	$cidr = find_smallest_cidr(ip_range_size($startip, $endip));
322

    
323
	// Loop here to reduce subnet size and retest as needed. We need to make sure
324
	//   that the target subnet is wholly contained between $startip and $endip.
325
	for ($cidr; $cidr <= 32; $cidr++) {
326
		// Find the network and broadcast addresses for the subnet being tested.
327
		$targetsub_min = gen_subnet($startip, $cidr);
328
		$targetsub_max = gen_subnet_max($startip, $cidr);
329

    
330
		// Check best case where the range is exactly one subnet.
331
		if (($targetsub_min == $startip) && ($targetsub_max == $endip)) {
332
			// Hooray, the range is exactly this subnet!
333
			return array("{$startip}/{$cidr}");
334
		}
335

    
336
		// These remaining scenarios will find a subnet that uses the largest
337
		//  chunk possible of the range being tested, and leave the rest to be
338
		//  tested recursively after the loop.
339

    
340
		// Check if the subnet begins with $startip and ends before $endip
341
		if (($targetsub_min == $startip) && ip_less_than($targetsub_max, $endip)) {
342
			break;
343
		}
344

    
345
		// Check if the subnet ends at $endip and starts after $startip
346
		if (ip_greater_than($targetsub_min, $startip) && ($targetsub_max == $endip)) {
347
			break;
348
		}
349

    
350
		// Check if the subnet is between $startip and $endip
351
		if (ip_greater_than($targetsub_min, $startip) && ip_less_than($targetsub_max, $endip)) {
352
			break;
353
		}
354
	}
355

    
356
	// Some logic that will recursivly search from $startip to the first IP before the start of the subnet we just found.
357
	// NOTE: This may never be hit, the way the above algo turned out, but is left for completeness.
358
	if ($startip != $targetsub_min) {
359
		$rangesubnets = array_merge($rangesubnets, ip_range_to_subnet_array($startip, ip_before($targetsub_min)));
360
	}
361

    
362
	// Add in the subnet we found before, to preserve ordering
363
	$rangesubnets[] = "{$targetsub_min}/{$cidr}";
364

    
365
	// And some more logic that will search after the subnet we found to fill in to the end of the range.
366
	if ($endip != $targetsub_max) {
367
		$rangesubnets = array_merge($rangesubnets, ip_range_to_subnet_array(ip_after($targetsub_max), $endip));
368
	}
369
	return $rangesubnets;
370
}
371

    
372
function is_iprange($range) {
373
	if (substr_count($range, '-') != 1) {
374
		return false;
375
	}
376
	list($ip1, $ip2) = explode ('-', $range);
377
	return (is_ipaddr($ip1) && is_ipaddr($ip2));
378
}
379

    
380
function is_numericint($arg) {
381
	return (preg_match("/[^0-9]/", $arg) ? false : true);
382
}
383

    
384
/* returns true if $ipaddr is a valid dotted IPv4 address */
385
function is_ipaddr($ipaddr) {
386
	if (!is_string($ipaddr))
387
		return false;
388

    
389
	$ip_long = ip2long($ipaddr);
390
	$ip_reverse = long2ip32($ip_long);
391

    
392
	if ($ipaddr == $ip_reverse)
393
		return true;
394
	else
395
		return false;
396
}
397

    
398
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
399
function is_ipaddroralias($ipaddr) {
400
	global $config;
401

    
402
	if (is_alias($ipaddr)) {
403
		if (is_array($config['aliases']['alias'])) {
404
			foreach ($config['aliases']['alias'] as $alias) {
405
                        	if ($alias['name'] == $ipaddr && $alias['type'] != "port")
406
					return true;
407
			}
408
                }
409
		return false;
410
	} else
411
		return is_ipaddr($ipaddr);
412

    
413
}
414

    
415
/* returns true if $subnet is a valid subnet in CIDR format */
416
function is_subnet($subnet) {
417
	if (!is_string($subnet))
418
		return false;
419

    
420
	list($hp,$np) = explode('/', $subnet);
421

    
422
	if (!is_ipaddr($hp))
423
		return false;
424

    
425
	if (!is_numeric($np) || ($np < 1) || ($np > 32))
426
		return false;
427

    
428
	return true;
429
}
430

    
431
/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */
432
function is_subnetoralias($subnet) {
433

    
434
	global $aliastable;
435

    
436
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet]))
437
		return true;
438
	else
439
		return is_subnet($subnet);
440
}
441

    
442
/* returns true if $hostname is a valid hostname */
443
function is_hostname($hostname) {
444
	if (!is_string($hostname))
445
		return false;
446

    
447
	if (preg_match('/^(?:(?:[a-z0-9_]|[a-z0-9_][a-z0-9_\-]*[a-z0-9_])\.)*(?:[a-z0-9_]|[a-z0-9_][a-z0-9_\-]*[a-z0-9_])$/i', $hostname))
448
		return true;
449
	else
450
		return false;
451
}
452

    
453
/* returns true if $domain is a valid domain name */
454
function is_domain($domain) {
455
	if (!is_string($domain))
456
		return false;
457

    
458
	if (preg_match('/^(?:(?:[a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*(?:[a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])$/i', $domain))
459
		return true;
460
	else
461
		return false;
462
}
463

    
464
/* returns true if $macaddr is a valid MAC address */
465
function is_macaddr($macaddr) {
466
	return preg_match('/^[0-9A-F]{2}(?=([:]?))(?:\\1[0-9A-F]{2}){5}$/i', $macaddr) == 1 ? true : false;
467
}
468

    
469
/* returns true if $name is a valid name for an alias */
470
/* returns NULL if a reserved word is used */
471
function is_validaliasname($name) {
472
	/* Array of reserved words */
473
	$reserved = array("port", "pass");
474
	if (in_array($name, $reserved, true))
475
		return; /* return NULL */
476

    
477
	if (!preg_match("/[^a-zA-Z0-9_]/", $name))
478
		return true;
479
	else
480
		return false;
481
}
482

    
483
/* returns true if $port is a valid TCP/UDP port */
484
function is_port($port) {
485
	$tmpports = explode(":", $port);
486
	foreach($tmpports as $tmpport) {
487
		if (getservbyname($tmpport, "tcp") || getservbyname($tmpport, "udp"))
488
                	continue;
489
		if (!ctype_digit($tmpport))
490
			return false;
491
		else if ((intval($tmpport) < 1) || (intval($tmpport) > 65535))
492
			return false;
493
	}
494
	return true;
495
}
496

    
497
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
498
function is_portrange($portrange) {
499
        $ports = explode(":", $portrange);
500

    
501
        if(count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]))
502
                return true;
503
        else
504
                return false;
505
}
506

    
507
/* returns true if $port is a valid port number or an alias thereof */
508
function is_portoralias($port) {
509
	global $config;
510

    
511
        if (is_alias($port)) {
512
                if (is_array($config['aliases']['alias'])) {
513
                        foreach ($config['aliases']['alias'] as $alias) {
514
                                if ($alias['name'] == $port && $alias['type'] == "port")
515
                                        return true;
516
                        }
517
                }
518
                return false;
519
        } else
520
                return is_port($port);
521
}
522

    
523
/* returns true if $val is a valid shaper bandwidth value */
524
function is_valid_shaperbw($val) {
525
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
526
}
527

    
528
/* return the configured carp interface list */
529
function get_configured_carp_interface_list() {
530
	global $config;
531

    
532
	$iflist = array();
533

    
534
	if(is_array($config['virtualip']['vip'])) {
535
                $viparr = &$config['virtualip']['vip'];
536
                foreach ($viparr as $vip) {
537
                        switch ($vip['mode']) {
538
                        case "carp":
539
                        case "carpdev-dhcp":
540
				$vipif = "vip" . $vip['vhid'];
541
                        	$iflist[$vipif] = $vip['subnet'];
542
                                break;
543
                        }
544
                }
545
        }
546

    
547
	return $iflist;
548
}
549

    
550
/* return the configured IP aliases list */
551
function get_configured_ip_aliases_list() {
552
        global $config;
553

    
554
        $alias_list=array();
555

    
556
        if(is_array($config['virtualip']['vip'])) {
557
                $viparr = &$config['virtualip']['vip'];
558
                foreach ($viparr as $vip) {
559
                        if ($vip['mode']=="ipalias") {
560
                                $alias_list[$vip['subnet']] = $vip['interface'];
561
                        }
562
                }
563
        }
564

    
565
        return $alias_list;
566
}
567

    
568

    
569
/* return the configured interfaces list. */
570
function get_configured_interface_list($only_opt = false, $withdisabled = false) {
571
	global $config;
572

    
573
	$iflist = array();
574

    
575
	/* if list */
576
	foreach($config['interfaces'] as $if => $ifdetail) {
577
		if ($only_opt && ($if == "wan" || $if == "lan"))
578
			continue;
579
		if (isset($ifdetail['enable']) || $withdisabled == true)
580
			$iflist[$if] = $if;
581
	}
582

    
583
	return $iflist;
584
}
585

    
586
/* return the configured interfaces list. */
587
function get_configured_interface_list_by_realif($only_opt = false, $withdisabled = false) {
588
	global $config;
589

    
590
	$iflist = array();
591

    
592
	/* if list */
593
	foreach($config['interfaces'] as $if => $ifdetail) {
594
		if ($only_opt && ($if == "wan" || $if == "lan"))
595
			continue;
596
		if (isset($ifdetail['enable']) || $withdisabled == true) {
597
			$tmpif = get_real_interface($if);
598
			if (!empty($tmpif))
599
				$iflist[$tmpif] = $if;
600
		}
601
	}
602

    
603
	return $iflist;
604
}
605

    
606
/* return the configured interfaces list with their description. */
607
function get_configured_interface_with_descr($only_opt = false, $withdisabled = false) {
608
	global $config;
609

    
610
	$iflist = array();
611

    
612
	/* if list */
613
	foreach($config['interfaces'] as $if => $ifdetail) {
614
		if ($only_opt && ($if == "wan" || $if == "lan"))
615
			continue;
616
		if (isset($ifdetail['enable']) || $withdisabled == true) {
617
			if(empty($ifdetail['descr']))
618
				$iflist[$if] = strtoupper($if);
619
			else
620
				$iflist[$if] = strtoupper($ifdetail['descr']);
621
		}
622
	}
623

    
624
	return $iflist;
625
}
626

    
627

    
628
/*
629
 *   get_interface_list() - Return a list of all physical interfaces
630
 *   along with MAC and status.
631
 *
632
 *   $mode = "active" - use ifconfig -lu
633
 *           "media"  - use ifconfig to check physical connection
634
 *			status (much slower)
635
 */
636
function get_interface_list($mode = "active", $keyby = "physical", $vfaces = "") {
637
        global $config;
638
	$upints = array();
639
        /* get a list of virtual interface types */
640
        if(!$vfaces) {
641
		$vfaces = array (
642
				'bridge',
643
				'ppp',
644
				'pppoe',
645
				'pptp',
646
				'l2tp',
647
				'sl',
648
				'gif',
649
				'gre',
650
				'faith',
651
				'lo',
652
				'ng',
653
				'_vlan',
654
				'_wlan',
655
				'pflog',
656
				'plip',
657
				'pfsync',
658
				'enc',
659
				'tun',
660
				'carp',
661
				'lagg',
662
				'vip',
663
				'ipfw'
664
		);
665
	}
666
	switch($mode) {
667
	case "active":
668
                $upints = explode(" ", trim(shell_exec("/sbin/ifconfig -lu")));
669
        	break;
670
	case "media":
671
                $intlist = explode(" ", trim(shell_exec("/sbin/ifconfig -l")));
672
                $ifconfig = "";
673
                exec("/sbin/ifconfig -a", $ifconfig);
674
                $regexp = '/(' . implode('|', $intlist) . '):\s/';
675
                $ifstatus = preg_grep('/status:/', $ifconfig);
676
		foreach($ifstatus as $status) {
677
			$int = array_shift($intlist);
678
                	if(stristr($status, "active")) $upints[] = $int;
679
		}
680
		break;
681
	}
682
        /* build interface list with netstat */
683
        $linkinfo = "";
684
        exec("/usr/bin/netstat -inW -f link | awk '{ print $1, $4 }'", $linkinfo);
685
        array_shift($linkinfo);
686
	/* build ip address list with netstat */
687
	$ipinfo = "";
688
	exec("/usr/bin/netstat -inW -f inet | awk '{ print $1, $4 }'", $ipinfo);
689
	array_shift($ipinfo);
690
	foreach($linkinfo as $link) {
691
		$friendly = "";
692
                $alink = explode(" ", $link);
693
                $ifname = rtrim(trim($alink[0]), '*');
694
                /* trim out all numbers before checking for vfaces */
695
		if (!in_array(array_shift(preg_split('/\d/', $ifname)), $vfaces) &&
696
			!stristr($ifname, "_vlan") && !stristr($ifname, "_wlan")) {
697
			$toput = array(
698
					"mac" => trim($alink[1]),
699
					"up" => in_array($ifname, $upints)
700
				);
701
			foreach($ipinfo as $ip) {
702
				$aip = explode(" ", $ip);
703
				if($aip[0] == $ifname) {
704
					$toput['ipaddr'] = $aip[1];
705
				}
706
			}
707
			foreach($config['interfaces'] as $name => $int) {
708
				if($int['if'] == $ifname) $friendly = $name;
709
			}
710
			switch($keyby) {
711
			case "physical":
712
				if($friendly != "") {
713
					$toput['friendly'] = $friendly;
714
				}
715
				$dmesg_arr = array();
716
				exec("/sbin/dmesg |grep $ifname | head -n1", $dmesg_arr);
717
				preg_match_all("/<(.*?)>/i", $dmesg_arr[0], $dmesg);
718
				$toput['dmesg'] = $dmesg[1][0];
719
				$iflist[$ifname] = $toput;
720
				break;
721
			case "ppp":
722
				
723
			case "friendly":
724
				if($friendly != "") {
725
					$toput['if'] = $ifname;
726
					$iflist[$friendly] = $toput;
727
				}
728
				break;
729
			}
730
                }
731
        }
732
        return $iflist;
733
}
734

    
735
/****f* util/log_error
736
* NAME
737
*   log_error  - Sends a string to syslog.
738
* INPUTS
739
*   $error     - string containing the syslog message.
740
* RESULT
741
*   null
742
******/
743
function log_error($error) {
744
        global $g;
745
        $page = $_SERVER['SCRIPT_NAME'];
746
        syslog(LOG_WARNING, "$page: $error");
747
        if ($g['debug'])
748
                syslog(LOG_WARNING, var_dump(debug_backtrace()));
749
        return;
750
}
751

    
752
/****f* util/exec_command
753
 * NAME
754
 *   exec_command - Execute a command and return a string of the result.
755
 * INPUTS
756
 *   $command   - String of the command to be executed.
757
 * RESULT
758
 *   String containing the command's result.
759
 * NOTES
760
 *   This function returns the command's stdout and stderr.
761
 ******/
762
function exec_command($command) {
763
        $output = array();
764
        exec($command . ' 2>&1 ', $output);
765
        return(implode("\n", $output));
766
}
767

    
768
/* wrapper for exec() */
769
function mwexec($command, $mute = false) {
770

    
771
	global $g;
772
	$oarr = array();
773
	$retval = 0;
774
	if ($g['debug']) {
775
		if (!$_SERVER['REMOTE_ADDR'])
776
			echo "mwexec(): $command\n";
777
		exec("$command 2>&1", $oarr, $retval);
778
	} else {
779
		exec("$command 2>&1", $oarr, $retval);
780
	}
781
	if(isset($config['system']['developerspew']))
782
                $mute = false;
783
	if(($retval <> 0) && ($mute === false)) {
784
		$output = implode(" ", $oarr);
785
		log_error("The command '$command' returned exit code '$retval', the output was '$output' ");
786
	}
787
	return $retval;
788
}
789

    
790
/* wrapper for exec() in background */
791
function mwexec_bg($command) {
792

    
793
	global $g;
794

    
795
	if ($g['debug']) {
796
		if (!$_SERVER['REMOTE_ADDR'])
797
			echo "mwexec(): $command\n";
798
	}
799

    
800
	exec("nohup $command > /dev/null 2>&1 &");
801
}
802

    
803
/* unlink a file, if it exists */
804
function unlink_if_exists($fn) {
805
	$to_do = glob($fn);
806
	if(is_array($to_do)) {
807
		foreach($to_do as $filename)
808
			@unlink($filename);
809
	} else {
810
		@unlink($fn);
811
	}
812
}
813
/* make a global alias table (for faster lookups) */
814
function alias_make_table($config) {
815

    
816
	global $aliastable;
817

    
818
	$aliastable = array();
819

    
820
	if (is_array($config['aliases']['alias'])) {
821
		foreach ($config['aliases']['alias'] as $alias) {
822
			if ($alias['name'])
823
				$aliastable[$alias['name']] = $alias['address'];
824
		}
825
	}
826
}
827
/* check if an alias exists */
828
function is_alias($name) {
829

    
830
	global $aliastable;
831

    
832
	return isset($aliastable[$name]);
833
}
834

    
835
/* expand a host or network alias, if necessary */
836
function alias_expand($name) {
837

    
838
	global $aliastable;
839

    
840
	if (isset($aliastable[$name]))
841
		return "\${$name}";
842
	else if (is_ipaddr($name) || is_subnet($name) || is_port($name))
843
		return "{$name}";
844
	else
845
		return null;
846
}
847

    
848
function alias_expand_urltable($name) {
849
	global $config;
850
	$urltable_prefix = "/var/db/aliastables/";
851
	$urltable_filename = $urltable_prefix . $name . ".txt";
852

    
853
	foreach ($config['aliases']['alias'] as $alias) {
854
		if (($alias['type'] == 'urltable') && ($alias['name'] == $name)) {
855
			if (is_URL($alias["url"]) && file_exists($urltable_filename) && filesize($urltable_filename))
856
				return $urltable_filename;
857
			else if (process_alias_urltable($name, $alias["url"], 0, true))
858
				return $urltable_filename;
859
		}
860
	}
861
	return null;
862
}
863

    
864
/* find out whether two subnets overlap */
865
function check_subnets_overlap($subnet1, $bits1, $subnet2, $bits2) {
866

    
867
	if (!is_numeric($bits1))
868
		$bits1 = 32;
869
	if (!is_numeric($bits2))
870
		$bits2 = 32;
871

    
872
	if ($bits1 < $bits2)
873
		$relbits = $bits1;
874
	else
875
		$relbits = $bits2;
876

    
877
	$sn1 = gen_subnet_mask_long($relbits) & ip2long($subnet1);
878
	$sn2 = gen_subnet_mask_long($relbits) & ip2long($subnet2);
879

    
880
	if ($sn1 == $sn2)
881
		return true;
882
	else
883
		return false;
884
}
885

    
886
/* compare two IP addresses */
887
function ipcmp($a, $b) {
888
	if (ip_less_than($a, $b))
889
		return -1;
890
	else if (ip_greater_than($a, $b))
891
		return 1;
892
	else
893
		return 0;
894
}
895

    
896
/* return true if $addr is in $subnet, false if not */
897
function ip_in_subnet($addr,$subnet) {
898
	list($ip, $mask) = explode('/', $subnet);
899
	$mask = (0xffffffff << (32 - $mask)) & 0xffffffff;
900
	return ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
901
}
902

    
903
/* verify (and remove) the digital signature on a file - returns 0 if OK */
904
function verify_digital_signature($fname) {
905
	global $g;
906

    
907
	if(!file_exists("/usr/local/sbin/gzsig"))
908
		return 4;
909

    
910
	return mwexec("/usr/local/sbin/gzsig verify {$g['etc_path']}/pubkey.pem < " . escapeshellarg($fname));
911
}
912

    
913
/* obtain MAC address given an IP address by looking at the ARP table */
914
function arp_get_mac_by_ip($ip) {
915
	mwexec("/sbin/ping -c 1 -t 1 {$ip}", true);
916
	$arpoutput = "";
917
	exec("/usr/sbin/arp -n {$ip}", $arpoutput);
918

    
919
	if ($arpoutput[0]) {
920
		$arpi = explode(" ", $arpoutput[0]);
921
		$macaddr = $arpi[3];
922
		if (is_macaddr($macaddr))
923
			return $macaddr;
924
		else
925
			return false;
926
	}
927

    
928
	return false;
929
}
930

    
931
/* return a fieldname that is safe for xml usage */
932
function xml_safe_fieldname($fieldname) {
933
	$replace = array('/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
934
			 '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
935
			 ':', ',', '.', '\'', '\\'
936
		);
937
	return strtolower(str_replace($replace, "", $fieldname));
938
}
939

    
940
function mac_format($clientmac) {
941
    $mac =explode(":", $clientmac);
942

    
943
    global $config;
944

    
945
    $mac_format = $config['captiveportal']['radmac_format'] ? $config['captiveportal']['radmac_format'] : false;
946

    
947
    switch($mac_format) {
948

    
949
        case 'singledash':
950
        return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
951

    
952
        case 'ietf':
953
        return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
954

    
955
        case 'cisco':
956
        return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
957

    
958
        case 'unformatted':
959
        return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
960

    
961
        default:
962
        return $clientmac;
963
    }
964
}
965

    
966
function resolve_retry($hostname, $retries = 5) {
967

    
968
       if (is_ipaddr($hostname))
969
               return $hostname;
970

    
971
       for ($i = 0; $i < $retries; $i++) {
972
               $ip = gethostbyname($hostname);
973

    
974
               if ($ip && $ip != $hostname) {
975
                       /* success */
976
                       return $ip;
977
               }
978

    
979
               sleep(1);
980
       }
981

    
982
       return false;
983
}
984

    
985
function format_bytes($bytes) {
986
	if ($bytes >= 1073741824) {
987
		return sprintf("%.2f GB", $bytes/1073741824);
988
	} else if ($bytes >= 1048576) {
989
		return sprintf("%.2f MB", $bytes/1048576);
990
	} else if ($bytes >= 1024) {
991
		return sprintf("%.0f KB", $bytes/1024);
992
	} else {
993
		return sprintf("%d bytes", $bytes);
994
	}
995
}
996

    
997
function update_filter_reload_status($text) {
998
        global $g;
999

    
1000
        file_put_contents("{$g['varrun_path']}/filter_reload_status", $text);
1001
}
1002

    
1003
/****f* util/return_dir_as_array
1004
 * NAME
1005
 *   return_dir_as_array - Return a directory's contents as an array.
1006
 * INPUTS
1007
 *   $dir       - string containing the path to the desired directory.
1008
 * RESULT
1009
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
1010
 ******/
1011
function return_dir_as_array($dir) {
1012
        $dir_array = array();
1013
        if (is_dir($dir)) {
1014
                if ($dh = opendir($dir)) {
1015
                        while (($file = readdir($dh)) !== false) {
1016
                                $canadd = 0;
1017
                                if($file == ".") $canadd = 1;
1018
                                if($file == "..") $canadd = 1;
1019
                                if($canadd == 0)
1020
                                        array_push($dir_array, $file);
1021
                        }
1022
                        closedir($dh);
1023
                }
1024
        }
1025
        return $dir_array;
1026
}
1027

    
1028
function run_plugins($directory) {
1029
        global $config, $g;
1030

    
1031
		/* process packager manager custom rules */
1032
		$files = return_dir_as_array($directory);
1033
		if (is_array($files)) {
1034
			foreach ($files as $file) {
1035
				if (stristr($file, ".sh") == true)
1036
					mwexec($directory . $file . " start");
1037
				else if (!is_dir($directory . "/" . $file) && stristr($file,".inc")) 
1038
					require_once($directory . "/" . $file);
1039
			}
1040
		}
1041
}
1042

    
1043
/*
1044
 *    safe_mkdir($path, $mode = 0755)
1045
 *    create directory if it doesn't already exist and isn't a file!
1046
 */
1047
function safe_mkdir($path, $mode=0755) {
1048
        global $g;
1049

    
1050
        if (!is_file($path) && !is_dir($path)) {
1051
                return @mkdir($path, $mode, true);
1052
        } else {
1053
                return false;
1054
        }
1055
}
1056

    
1057
/*
1058
 * make_dirs($path, $mode = 0755)
1059
 * create directory tree recursively (mkdir -p)
1060
 */
1061
function make_dirs($path, $mode = 0755) {
1062
        $base = '';
1063
        foreach (explode('/', $path) as $dir) {
1064
                $base .= "/$dir";
1065
                if (!is_dir($base)) {
1066
                        if (!@mkdir($base, $mode))
1067
                                return false;
1068
                }
1069
        }
1070
        return true;
1071
}
1072

    
1073
/*
1074
 * get_sysctl($names)
1075
 * Get values of sysctl OID's listed in $names (accepts an array or a single
1076
 * name) and return an array of key/value pairs set for those that exist
1077
 */
1078
function get_sysctl($names) {
1079
	if (empty($names))
1080
		return array();
1081

    
1082
	if (is_array($names)) {
1083
		$name_list = array();
1084
		foreach ($names as $name) {
1085
			$name_list[] = escapeshellarg($name);
1086
		}
1087
	} else
1088
		$name_list = array(escapeshellarg($names));
1089

    
1090
	exec("/sbin/sysctl -i " . implode(" ", $name_list), $output);
1091
	$values = array();
1092
	foreach ($output as $line) {
1093
		$line = explode(": ", $line, 2);
1094
		if (count($line) == 2)
1095
			$values[$line[0]] = $line[1];
1096
	}
1097

    
1098
	return $values;
1099
}
1100

    
1101
/*
1102
 * set_sysctl($value_list)
1103
 * Set sysctl OID's listed as key/value pairs and return
1104
 * an array with keys set for those that succeeded
1105
 */
1106
function set_sysctl($values) {
1107
	if (empty($values))
1108
		return array();
1109

    
1110
	$value_list = array();
1111
	foreach ($values as $key => $value) {
1112
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
1113
	}
1114

    
1115
	exec("/sbin/sysctl -i " . implode(" ", $value_list), $output, $success);
1116

    
1117
	/* Retry individually if failed (one or more read-only) */
1118
	if ($success <> 0 && count($value_list) > 1) {
1119
		foreach ($value_list as $value) {
1120
			exec("/sbin/sysctl -i " . $value, $output);
1121
		}
1122
	}
1123

    
1124
	$ret = array();
1125
	foreach ($output as $line) {
1126
		$line = explode(": ", $line, 2);
1127
		if (count($line) == 2)
1128
			$ret[$line[0]] = true;
1129
	}
1130

    
1131
	return $ret;
1132
}
1133

    
1134
/*
1135
 *     get_memory()
1136
 *     returns an array listing the amount of
1137
 *     memory installed in the hardware
1138
 *     [0]real and [1]available
1139
 */
1140
function get_memory() {
1141
		$matches = "";
1142
        if(file_exists("/var/log/dmesg.boot"))
1143
			$mem = `cat /var/log/dmesg.boot | grep memory`;
1144
		else
1145
			$mem = `dmesg -a | grep memory`;			
1146
		if (preg_match_all("/avail memory.* \((.*)MB\)/", $mem, $matches)) 
1147
			return array($matches[1][0], $matches[1][0]);
1148
		if(!$real && !$avail) {
1149
			$real = trim(`sysctl hw.physmem | cut -d' ' -f2`);
1150
			$avail = trim(`sysctl hw.realmem | cut -d' ' -f2`);
1151
			/* convert from bytes to megabytes */
1152
			return array(($real/1048576),($avail/1048576));
1153
		}
1154
}
1155

    
1156
function mute_kernel_msgs() {
1157
		global $config;
1158
		// Do not mute serial console.  The kernel gets very very cranky
1159
		// and will start dishing you cannot control tty errors.
1160
		if(trim(file_get_contents("/etc/platform")) == "nanobsd") 
1161
			return;
1162
		if($config['system']['enableserial']) 
1163
			return;			
1164
		exec("/sbin/conscontrol mute on");
1165
}
1166

    
1167
function unmute_kernel_msgs() {
1168
		global $config;
1169
		// Do not mute serial console.  The kernel gets very very cranky
1170
		// and will start dishing you cannot control tty errors.
1171
		if(trim(file_get_contents("/etc/platform")) == "nanobsd") 
1172
			return;
1173
		exec("/sbin/conscontrol mute off");
1174
}
1175

    
1176
function start_devd() {
1177
	global $g;
1178

    
1179
        exec("/sbin/devd");
1180
        sleep(1);
1181
}
1182

    
1183
function is_interface_mismatch() {
1184
        global $config, $g;
1185

    
1186
        /* XXX: Should we process only enabled interfaces?! */
1187
        $do_assign = false;
1188
        $i = 0;
1189
        foreach ($config['interfaces'] as $ifname => $ifcfg) {
1190
                if (preg_match("/^enc|^cua|^tun|^l2tp|^pptp|^ppp|^ovpn|^gif|^gre|^lagg|^bridge|vlan|_wlan/i", $ifcfg['if'])) {
1191
                        $i++;
1192
                }
1193
                else if (does_interface_exist($ifcfg['if']) == false) {
1194
                        $do_assign = true;
1195
                } else
1196
                        $i++;
1197
        }
1198

    
1199
        if ($g['minimum_nic_count'] > $i) {
1200
                $do_assign = true;
1201
        } else if (file_exists("{$g['tmp_path']}/assign_complete"))
1202
                $do_assign = false;
1203

    
1204
        return $do_assign;
1205
}
1206

    
1207
/* sync carp entries to other firewalls */
1208
function carp_sync_client() {
1209
	global $g;
1210
	send_event("filter sync");
1211
}
1212

    
1213
/****f* util/isAjax
1214
 * NAME
1215
 *   isAjax - reports if the request is driven from prototype
1216
 * INPUTS
1217
 *   none
1218
 * RESULT
1219
 *   true/false
1220
 ******/
1221
function isAjax() {
1222
        return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
1223
}
1224

    
1225
/****f* util/timeout
1226
 * NAME
1227
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
1228
 * INPUTS
1229
 *   optional, seconds to wait before timeout. Default 9 seconds.
1230
 * RESULT
1231
 *   returns 1 char of user input or null if no input.
1232
 ******/
1233
function timeout($timer = 9) {
1234
	while(!isset($key)) {
1235
		if ($timer >= 9) { echo chr(8) . chr(8) . ($timer==9 ? chr(32) : null)  . "{$timer}";  }
1236
		else { echo chr(8). "{$timer}"; }
1237
		`/bin/stty -icanon min 0 time 25`;
1238
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
1239
		`/bin/stty icanon`;
1240
		if ($key == '')
1241
			unset($key);
1242
		$timer--;
1243
		if ($timer == 0)
1244
			break;
1245
	}
1246
	return $key;	
1247
}
1248

    
1249
/****f* util/msort
1250
 * NAME
1251
 *   msort - sort array
1252
 * INPUTS
1253
 *   $array to be sorted, field to sort by, direction of sort
1254
 * RESULT
1255
 *   returns newly sorted array
1256
 ******/
1257
function msort($array, $id="id", $sort_ascending=true) {
1258
	$temp_array = array();
1259
	while(count($array)>0) {
1260
		$lowest_id = 0;
1261
		$index=0;
1262
		foreach ($array as $item) {
1263
			if (isset($item[$id])) {
1264
				if ($array[$lowest_id][$id]) {
1265
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
1266
						$lowest_id = $index;
1267
					}
1268
				}
1269
			}
1270
			$index++;
1271
		}
1272
		$temp_array[] = $array[$lowest_id];
1273
		$array = array_merge(array_slice($array, 0,$lowest_id), array_slice($array, $lowest_id+1));
1274
	}
1275
	if ($sort_ascending) {
1276
		return $temp_array;
1277
	} else {
1278
    	return array_reverse($temp_array);
1279
	}
1280
}
1281

    
1282
/****f* util/color
1283
 * NAME
1284
 *   color - outputs a color code to the ansi terminal if supported
1285
 * INPUTS
1286
 *   color code or color name
1287
 * RESULT
1288
 *   Outputs the ansi color sequence for the color specified.  Default resets terminal.
1289
 ******/
1290
function color($color = "0m") {
1291
	/*
1292
		Color codes available:
1293
		 0m reset; clears all colors and styles (to white on black)
1294
		 1m bold on (see below)
1295
		 3m italics on
1296
		 4m underline on
1297
		 7m inverse on; reverses foreground & background colors
1298
		 9m strikethrough on
1299
		 22m bold off (see below)
1300
		 23m italics off
1301
		 24m underline off
1302
		 27m inverse off
1303
		 29m strikethrough off
1304
		 30m set foreground color to black
1305
		 31m set foreground color to red
1306
		 32m set foreground color to green
1307
		 33m set foreground color to yellow
1308
		 34m set foreground color to blue
1309
		 35m set foreground color to magenta (purple)
1310
		 36m set foreground color to cyan
1311
		 37m set foreground color to white
1312
		 40m  set background color to black
1313
		 41m set background color to red
1314
		 42m set background color to green
1315
		 43m set background color to yellow
1316
		 44m set background color to blue
1317
		 45m set background color to magenta (purple)
1318
		 46m set background color to cyan
1319
		 47m set background color to white
1320
		 49m set background color to default (black)
1321
	*/	
1322
	// Allow caching of TERM to 
1323
	// speedup subequence requests.
1324
	global $TERM;
1325
	if(!$TERM) 
1326
		$TERM=`/usr/bin/env | grep color`;
1327
	if(!$TERM)
1328
		$TERM=`/usr/bin/env | grep cons25`;
1329
	if($TERM) {
1330
		$ESCAPE=chr(27);
1331
		switch ($color) {
1332
			case "black":
1333
				return "{$ESCAPE}[30m"; 
1334
			case "red":
1335
				return "{$ESCAPE}[31m"; 
1336
			case "green":
1337
				return "{$ESCAPE}[32m"; 
1338
			case "yellow":
1339
				return "{$ESCAPE}[33m"; 
1340
			case "blue":
1341
				return "{$ESCAPE}[34m"; 
1342
			case "magenta":
1343
				return "{$ESCAPE}[35m"; 
1344
			case "cyan":
1345
				return "{$ESCAPE}[36m"; 
1346
			case "white":
1347
				return "{$ESCAPE}[37m"; 
1348
			case "default":
1349
				return "{$ESCAPE}[39m"; 
1350
		}
1351
		return "{$ESCAPE}[{$color}";
1352
	}
1353
}
1354

    
1355
/****f* util/is_URL
1356
 * NAME
1357
 *   is_URL
1358
 * INPUTS
1359
 *   string to check
1360
 * RESULT
1361
 *   Returns true if item is a URL
1362
 ******/
1363
function is_URL($url) {
1364
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
1365
	if($match)
1366
		return true;	
1367
	return false;
1368
}
1369

    
1370
function is_file_included($file = "") {
1371
	$files = get_included_files();
1372
	if (in_array($file, $files))
1373
		return true;
1374
	
1375
	return false;
1376
}
1377

    
1378
/*
1379
	This function was borrowed from a comment on PHP.net at the following URL:
1380
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
1381
 */
1382
function array_merge_recursive_unique($array0, $array1)
1383
{
1384
    $arrays = func_get_args();
1385
    $remains = $arrays;
1386

    
1387
    // We walk through each arrays and put value in the results (without
1388
    // considering previous value).
1389
    $result = array();
1390

    
1391
    // loop available array
1392
    foreach($arrays as $array) {
1393

    
1394
        // The first remaining array is $array. We are processing it. So
1395
        // we remove it from remaing arrays.
1396
        array_shift($remains);
1397

    
1398
        // We don't care non array param, like array_merge since PHP 5.0.
1399
        if(is_array($array)) {
1400
            // Loop values
1401
            foreach($array as $key => $value) {
1402
                if(is_array($value)) {
1403
                    // we gather all remaining arrays that have such key available
1404
                    $args = array();
1405
                    foreach($remains as $remain) {
1406
                        if(array_key_exists($key, $remain)) {
1407
                            array_push($args, $remain[$key]);
1408
                        }
1409
                    }
1410

    
1411
                    if(count($args) > 2) {
1412
                        // put the recursion
1413
                        $result[$key] = call_user_func_array(__FUNCTION__, $args);
1414
                    } else {
1415
                        foreach($value as $vkey => $vval) {
1416
                            $result[$key][$vkey] = $vval;
1417
                        }
1418
                    }
1419
                } else {
1420
                    // simply put the value
1421
                    $result[$key] = $value;
1422
                }
1423
            }
1424
        }
1425
    }
1426
    return $result;
1427
}
1428

    
1429
?>
(42-42/54)