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
	$running = `ps -p $pid | wc -l`;
46
	if(intval($running) > 1)
47
		return true;
48
	else 
49
		return false;
50
}
51

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

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

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

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

    
69
	return 0;
70
}
71

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

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

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

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

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

    
93
	return false;
94
}
95

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
414
}
415

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

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

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

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

    
429
	return true;
430
}
431

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

    
435
	global $aliastable;
436

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

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

    
448
	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))
449
		return true;
450
	else
451
		return false;
452
}
453

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

    
459
	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))
460
		return true;
461
	else
462
		return false;
463
}
464

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

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

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

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

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

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

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

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

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

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

    
533
	$iflist = array();
534

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

    
548
	return $iflist;
549
}
550

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

    
555
        $alias_list=array();
556

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

    
566
        return $alias_list;
567
}
568

    
569

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

    
574
	$iflist = array();
575

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

    
584
	return $iflist;
585
}
586

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

    
591
	$iflist = array();
592

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

    
604
	return $iflist;
605
}
606

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

    
611
	$iflist = array();
612

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

    
625
	return $iflist;
626
}
627

    
628

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

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

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

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

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

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

    
794
	global $g;
795

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

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

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

    
817
	global $aliastable;
818

    
819
	$aliastable = array();
820

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

    
831
	global $aliastable;
832

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

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

    
839
	global $aliastable;
840

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
929
	return false;
930
}
931

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

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

    
944
    global $config;
945

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

    
948
    switch($mac_format) {
949

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

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

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

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

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

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

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

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

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

    
980
               sleep(1);
981
       }
982

    
983
       return false;
984
}
985

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

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

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

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

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

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

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

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

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

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

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

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

    
1099
	return $values;
1100
}
1101

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

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

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

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

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

    
1132
	return $ret;
1133
}
1134

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

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

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

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

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

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

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

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

    
1205
        return $do_assign;
1206
}
1207

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1430
?>
(42-42/54)