Project

General

Profile

Download (38.1 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
	$running = shell_exec("/bin/pgrep -x {$process}");
54

    
55
        return !empty($running);
56
}
57

    
58
function isvalidproc($proc) {
59
	$running = is_process_running($proc);
60
	if (intval($running) >= 1)
61
		return true;
62
	else 
63
		return false;
64
}
65

    
66
/* sigkill a process by pid file */
67
/* return 1 for success and 0 for a failure */
68
function sigkillbypid($pidfile, $sig) {
69
	if (is_file($pidfile)) {
70
		$pid = trim(file_get_contents($pidfile));
71
		if(isvalidpid($pid))
72
			return mwexec("/bin/kill -s $sig {$pid}", true);
73
	}
74
	return 0;
75
}
76

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

    
83
/* kill a process by name */
84
function killbyname($procname) {
85
	if(isvalidproc($procname)) 
86
		mwexec("/usr/bin/killall " . escapeshellarg($procname));
87
}
88

    
89
function is_subsystem_dirty($subsystem = "") {
90
	global $g;
91

    
92
	if ($subsystem == "")
93
		return false;
94

    
95
	if (file_exists("{$g['varrun_path']}/{$subsystem}.dirty"))
96
		return true;
97

    
98
	return false;
99
}
100

    
101
function mark_subsystem_dirty($subsystem = "") {
102
	global $g;
103

    
104
	if (!file_put_contents("{$g['varrun_path']}/{$subsystem}.dirty", "DIRTY"))
105
		log_error("WARNING: Could not mark subsystem: {$subsytem} dirty");
106
}
107

    
108
function clear_subsystem_dirty($subsystem = "") {
109
	global $g;
110

    
111
	@unlink("{$g['varrun_path']}/{$subsystem}.dirty");
112
}
113

    
114
function config_lock() {
115
	return;
116
}
117
function config_unlock() {
118
	return;
119
}
120

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

    
137
/* unlock configuration file */
138
function unlock($cfglckkey = 0) {
139
	global $g, $cfglckkeyconsumers;
140
	flock($cfglckkey, LOCK_UN);
141
	fclose($cfglckkey);
142
	return;
143
}
144

    
145
function refcount_init($reference) {
146
	$shmid = shmop_open($reference, "c", 0644, 10);
147
	shmop_write($shmid, 0, 0);
148
	shmop_close($shmid);
149
}
150

    
151
function refcount_reference($reference) {
152
	$shmid = @shmop_open($reference, "w", 0644, 10);
153
	if (!$shmid) {
154
		refcount_init($reference);
155
		$shmid = shmop_open($reference, "w", 0644, 10);
156
	}
157
	$shm_data = shmop_read($shmid, 0, 10);
158
	$shm_data = intval($shm_data) + 1;
159
	shmop_write($shmid, $shm_data, 0);
160
	shmop_close($shmid);
161
	
162
	return $shm_data;
163
}
164

    
165
function refcount_unreference($reference) {
166
	/* We assume that the shared memory exists. */
167
	$shmid = shmop_open($reference, "w", 0644, 10);
168
	$shm_data = shmop_read($shmid, 0, 10);
169
	$shm_data = intval($shm_data) - 1;
170
	if ($shm_data < 0) {
171
		//debug_backtrace();
172
		log_error("Reference {$reference} is going negative, not doing unreference.");
173
	} else
174
		shmop_write($shmid, $shm_data, 0);
175
	shmop_close($shmid);
176
	
177
	return $shm_data;
178
}
179

    
180
function is_module_loaded($module_name) {
181
	$running = `/sbin/kldstat | grep {$module_name} | /usr/bin/grep -v grep | /usr/bin/wc -l`;
182
	if (intval($running) >= 1)
183
		return true;
184
	else
185
		return false;
186
}
187

    
188
/* return the subnet address given a host address and a subnet bit count */
189
function gen_subnet($ipaddr, $bits) {
190
	if (!is_ipaddr($ipaddr) || !is_numeric($bits))
191
		return "";
192

    
193
	return long2ip(ip2long($ipaddr) & gen_subnet_mask_long($bits));
194
}
195

    
196
/* return the highest (broadcast) address in the subnet given a host address and a subnet bit count */
197
function gen_subnet_max($ipaddr, $bits) {
198
	if (!is_ipaddr($ipaddr) || !is_numeric($bits))
199
		return "";
200

    
201
	return long2ip32(ip2long($ipaddr) | ~gen_subnet_mask_long($bits));
202
}
203

    
204
/* returns a subnet mask (long given a bit count) */
205
function gen_subnet_mask_long($bits) {
206
	$sm = 0;
207
	for ($i = 0; $i < $bits; $i++) {
208
		$sm >>= 1;
209
		$sm |= 0x80000000;
210
	}
211
	return $sm;
212
}
213

    
214
/* same as above but returns a string */
215
function gen_subnet_mask($bits) {
216
	return long2ip(gen_subnet_mask_long($bits));
217
}
218

    
219
/* Convert long int to IP address, truncating to 32-bits. */
220
function long2ip32($ip) {
221
	return long2ip($ip & 0xFFFFFFFF);
222
}
223

    
224
/* Convert IP address to long int, truncated to 32-bits to avoid sign extension on 64-bit platforms. */
225
function ip2long32($ip) {
226
	return ( ip2long($ip) & 0xFFFFFFFF );
227
}
228

    
229
/* Convert IP address to unsigned long int. */
230
function ip2ulong($ip) {
231
	return sprintf("%u", ip2long32($ip));
232
}
233

    
234
/* Find out how many IPs are contained within a given IP range
235
 *  e.g. 192.168.0.0 to 192.168.0.255 returns 256
236
 */
237
function ip_range_size($startip, $endip) {
238
	if (is_ipaddr($startip) && is_ipaddr($endip)) {
239
		// Operate as unsigned long because otherwise it wouldn't work
240
		//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
241
		return abs(ip2ulong($startip) - ip2ulong($endip)) + 1;
242
	}
243
	return -1;
244
}
245

    
246
/* Find the smallest possible subnet mask which can contain a given number of IPs
247
 *  e.g. 512 IPs can fit in a /23, but 513 IPs need a /22
248
 */
249
function find_smallest_cidr($number) {
250
	$smallest = 1;
251
	for ($b=32; $b > 0; $b--) {
252
		$smallest = ($number <= pow(2,$b)) ? $b : $smallest;
253
	}
254
	return (32-$smallest);
255
}
256

    
257
/* Return the previous IP address before the given address */
258
function ip_before($ip) {
259
	return long2ip32(ip2long($ip)-1);
260
}
261

    
262
/* Return the next IP address after the given address */
263
function ip_after($ip) {
264
	return long2ip32(ip2long($ip)+1);
265
}
266

    
267
/* Return true if the first IP is 'before' the second */
268
function ip_less_than($ip1, $ip2) {
269
	// Compare as unsigned long because otherwise it wouldn't work when
270
	//   crossing over from 127.255.255.255 / 128.0.0.0 barrier
271
	return ip2ulong($ip1) < ip2ulong($ip2);
272
}
273

    
274
/* Return true if the first IP is 'after' the second */
275
function ip_greater_than($ip1, $ip2) {
276
	// Compare as unsigned long because otherwise it wouldn't work
277
	//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
278
	return ip2ulong($ip1) > ip2ulong($ip2);
279
}
280

    
281
/* Convert a range of IPs to an array of subnets which can contain the range. */
282
function ip_range_to_subnet_array($startip, $endip) {
283
	if (!is_ipaddr($startip) || !is_ipaddr($endip)) {
284
		return array();
285
	}
286

    
287
	// Container for subnets within this range.
288
	$rangesubnets = array();
289

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

    
293
	// Loop here to reduce subnet size and retest as needed. We need to make sure
294
	//   that the target subnet is wholly contained between $startip and $endip.
295
	for ($cidr; $cidr <= 32; $cidr++) {
296
		// Find the network and broadcast addresses for the subnet being tested.
297
		$targetsub_min = gen_subnet($startip, $cidr);
298
		$targetsub_max = gen_subnet_max($startip, $cidr);
299

    
300
		// Check best case where the range is exactly one subnet.
301
		if (($targetsub_min == $startip) && ($targetsub_max == $endip)) {
302
			// Hooray, the range is exactly this subnet!
303
			return array("{$startip}/{$cidr}");
304
		}
305

    
306
		// These remaining scenarios will find a subnet that uses the largest
307
		//  chunk possible of the range being tested, and leave the rest to be
308
		//  tested recursively after the loop.
309

    
310
		// Check if the subnet begins with $startip and ends before $endip
311
		if (($targetsub_min == $startip) && ip_less_than($targetsub_max, $endip)) {
312
			break;
313
		}
314

    
315
		// Check if the subnet ends at $endip and starts after $startip
316
		if (ip_greater_than($targetsub_min, $startip) && ($targetsub_max == $endip)) {
317
			break;
318
		}
319

    
320
		// Check if the subnet is between $startip and $endip
321
		if (ip_greater_than($targetsub_min, $startip) && ip_less_than($targetsub_max, $endip)) {
322
			break;
323
		}
324
	}
325

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

    
332
	// Add in the subnet we found before, to preserve ordering
333
	$rangesubnets[] = "{$targetsub_min}/{$cidr}";
334

    
335
	// And some more logic that will search after the subnet we found to fill in to the end of the range.
336
	if ($endip != $targetsub_max) {
337
		$rangesubnets = array_merge($rangesubnets, ip_range_to_subnet_array(ip_after($targetsub_max), $endip));
338
	}
339
	return $rangesubnets;
340
}
341

    
342
function is_iprange($range) {
343
	if (substr_count($range, '-') != 1) {
344
		return false;
345
	}
346
	list($ip1, $ip2) = explode ('-', $range);
347
	return (is_ipaddr($ip1) && is_ipaddr($ip2));
348
}
349

    
350
function is_numericint($arg) {
351
	return (preg_match("/[^0-9]/", $arg) ? false : true);
352
}
353

    
354
/* returns true if $ipaddr is a valid dotted IPv4 address */
355
function is_ipaddr($ipaddr) {
356
	if (!is_string($ipaddr))
357
		return false;
358

    
359
	$ip_long = ip2long($ipaddr);
360
	$ip_reverse = long2ip32($ip_long);
361

    
362
	if ($ipaddr == $ip_reverse)
363
		return true;
364
	else
365
		return false;
366
}
367

    
368
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
369
function is_ipaddroralias($ipaddr) {
370
	global $config;
371

    
372
	if (is_alias($ipaddr)) {
373
		if (is_array($config['aliases']['alias'])) {
374
			foreach ($config['aliases']['alias'] as $alias) {
375
                        	if ($alias['name'] == $ipaddr && $alias['type'] != "port")
376
					return true;
377
			}
378
                }
379
		return false;
380
	} else
381
		return is_ipaddr($ipaddr);
382

    
383
}
384

    
385
/* returns true if $subnet is a valid subnet in CIDR format */
386
function is_subnet($subnet) {
387
	if (!is_string($subnet))
388
		return false;
389

    
390
	list($hp,$np) = explode('/', $subnet);
391

    
392
	if (!is_ipaddr($hp))
393
		return false;
394

    
395
	if (!is_numeric($np) || ($np < 1) || ($np > 32))
396
		return false;
397

    
398
	return true;
399
}
400

    
401
/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */
402
function is_subnetoralias($subnet) {
403

    
404
	global $aliastable;
405

    
406
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet]))
407
		return true;
408
	else
409
		return is_subnet($subnet);
410
}
411

    
412
/* returns true if $hostname is a valid hostname */
413
function is_hostname($hostname) {
414
	if (!is_string($hostname))
415
		return false;
416

    
417
	if (preg_match("/^([_a-z0-9\-]+\.?)+$/i", $hostname))
418
		return true;
419
	else
420
		return false;
421
}
422

    
423
/* returns true if $domain is a valid domain name */
424
function is_domain($domain) {
425
	if (!is_string($domain))
426
		return false;
427

    
428
	if (preg_match("/^([a-z0-9\-]+\.?)+$/i", $domain))
429
		return true;
430
	else
431
		return false;
432
}
433

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

    
439
/* returns true if $name is a valid name for an alias */
440
/* returns NULL if a reserved word is used */
441
function is_validaliasname($name) {
442
	/* Array of reserved words */
443
	$reserved = array("port", "pass");
444
	if (in_array($name, $reserved, true))
445
		return; /* return NULL */
446

    
447
	if (!preg_match("/[^a-zA-Z0-9_]/", $name))
448
		return true;
449
	else
450
		return false;
451
}
452

    
453
/* returns true if $port is a valid TCP/UDP port */
454
function is_port($port) {
455
	$tmpports = explode(":", $port);
456
	foreach($tmpports as $tmpport) {
457
		if (getservbyname($tmpport, "tcp") || getservbyname($tmpport, "udp"))
458
                	continue;
459
		if (!ctype_digit($tmpport))
460
			return false;
461
		else if ((intval($tmpport) < 1) || (intval($tmpport) > 65535))
462
			return false;
463
	}
464
	return true;
465
}
466

    
467
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
468
function is_portrange($portrange) {
469
        $ports = explode(":", $portrange);
470

    
471
        if(count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]))
472
                return true;
473
        else
474
                return false;
475
}
476

    
477
/* returns true if $port is a valid port number or an alias thereof */
478
function is_portoralias($port) {
479
	global $config;
480

    
481
        if (is_alias($port)) {
482
                if (is_array($config['aliases']['alias'])) {
483
                        foreach ($config['aliases']['alias'] as $alias) {
484
                                if ($alias['name'] == $port && $alias['type'] == "port")
485
                                        return true;
486
                        }
487
                }
488
                return false;
489
        } else
490
                return is_port($port);
491
}
492

    
493
/* returns true if $val is a valid shaper bandwidth value */
494
function is_valid_shaperbw($val) {
495
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
496
}
497

    
498
/* return the configured carp interface list */
499
function get_configured_carp_interface_list() {
500
	global $config;
501

    
502
	$iflist = array();
503

    
504
	if(is_array($config['virtualip']['vip'])) {
505
                $viparr = &$config['virtualip']['vip'];
506
                foreach ($viparr as $vip) {
507
                        switch ($vip['mode']) {
508
                        case "carp":
509
                        case "carpdev-dhcp":
510
				$vipif = "vip" . $vip['vhid'];
511
                        	$iflist[$vipif] = $vip['subnet'];
512
                                break;
513
                        }
514
                }
515
        }
516

    
517
	return $iflist;
518
}
519

    
520
/* return the configured IP aliases list */
521
function get_configured_ip_aliases_list() {
522
        global $config;
523

    
524
        $alias_list=array();
525

    
526
        if(is_array($config['virtualip']['vip'])) {
527
                $viparr = &$config['virtualip']['vip'];
528
                foreach ($viparr as $vip) {
529
                        if ($vip['mode']=="ipalias") {
530
                                $alias_list[$vip['subnet']] = $vip['interface'];
531
                        }
532
                }
533
        }
534

    
535
        return $alias_list;
536
}
537

    
538

    
539
/* return the configured interfaces list. */
540
function get_configured_interface_list($only_opt = false, $withdisabled = false) {
541
	global $config;
542

    
543
	$iflist = array();
544

    
545
	if (!$only_opt) {
546
		if (isset($config['interfaces']['wan']))
547
			$iflist['wan'] = "wan";
548
		if (isset($config['interfaces']['lan']))
549
			$iflist['lan'] = "lan";
550
	}
551

    
552
	/* if list */
553
        foreach($config['interfaces'] as $if => $ifdetail) {
554
		if ($if == "wan" || $if == "lan")
555
			continue;
556
		if (isset($ifdetail['enable']) || $withdisabled == true)
557
			$iflist[$if] = $if;
558
	}
559

    
560
	return $iflist;
561
}
562

    
563
/* return the configured interfaces list. */
564
function get_configured_interface_list_by_realif($only_opt = false, $withdisabled = false) {
565
        global $config;
566

    
567
        $iflist = array();
568

    
569
        if (!$only_opt) {
570
                if (isset($config['interfaces']['wan'])) {
571
			$tmpif = get_real_interface("wan");
572
			if (!empty($tmpif))
573
				$iflist[$tmpif] = "wan";
574
		}
575
                if (isset($config['interfaces']['lan'])) {
576
			$tmpif = get_real_interface("lan");
577
			if (!empty($tmpif))
578
				$iflist[$tmpif] = "lan";
579
		}
580
        }
581

    
582
        /* if list */
583
        foreach($config['interfaces'] as $if => $ifdetail) {
584
                if ($if == "wan" || $if == "lan")
585
                        continue;
586
                if (isset($ifdetail['enable']) || $withdisabled == true) {
587
			$tmpif = get_real_interface($if);
588
			if (!empty($tmpif))
589
				$iflist[$tmpif] = $if;
590
		}
591
        }
592

    
593
        return $iflist;
594
}
595

    
596
/* return the configured interfaces list with their description. */
597
function get_configured_interface_with_descr($only_opt = false, $withdisabled = false) {
598
	global $config;
599

    
600
	$iflist = array();
601

    
602
	if (!$only_opt) {
603
		if (isset($config['interfaces']['wan'])) {
604
			if (empty($config['interfaces']['wan']['descr']))
605
				$iflist['wan'] = "WAN";
606
			else
607
				$iflist['wan'] = strtoupper($config['interfaces']['wan']['descr']);
608
		}
609
		if (isset($config['interfaces']['lan'])) {
610
			if (empty($config['interfaces']['lan']['descr']))
611
				$iflist['lan'] = "LAN";
612
			else
613
				$iflist['lan'] = strtoupper($config['interfaces']['lan']['descr']);
614
		}
615
	}
616

    
617
	/* if list */
618
	foreach($config['interfaces'] as $if => $ifdetail) {
619
		if (isset($ifdetail['enable']) || $withdisabled == true) {
620
			if($ifdetail['descr'] == "")
621
				$iflist[$if] = strtoupper($if);
622
			else
623
				$iflist[$if] = strtoupper($ifdetail['descr']);
624
		}
625
	}
626

    
627
	return $iflist;
628
}
629

    
630

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

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

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

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

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

    
793
/* wrapper for exec() in background */
794
function mwexec_bg($command) {
795

    
796
	global $g;
797

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

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

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

    
819
	global $aliastable;
820

    
821
	$aliastable = array();
822

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

    
833
	global $aliastable;
834

    
835
	return isset($aliastable[$name]);
836
}
837

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

    
841
	global $aliastable;
842

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

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

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

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

    
870
	if (!is_numeric($bits1))
871
		$bits1 = 32;
872
	if (!is_numeric($bits2))
873
		$bits2 = 32;
874

    
875
	if ($bits1 < $bits2)
876
		$relbits = $bits1;
877
	else
878
		$relbits = $bits2;
879

    
880
	$sn1 = gen_subnet_mask_long($relbits) & ip2long($subnet1);
881
	$sn2 = gen_subnet_mask_long($relbits) & ip2long($subnet2);
882

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

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

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

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

    
910
	if(!file_exists("/usr/local/sbin/gzsig"))
911
		return 4;
912

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

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

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

    
931
	return false;
932
}
933

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

    
943
function mac_format($clientmac) {
944
    $mac =explode(":", $clientmac);
945

    
946
    global $config;
947

    
948
    $mac_format = $config['captiveportal']['radmac_format'] ? $config['captiveportal']['radmac_format'] : false;
949

    
950
    switch($mac_format) {
951

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

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

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

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

    
964
        default:
965
        return $clientmac;
966
    }
967
}
968

    
969
function resolve_retry($hostname, $retries = 5) {
970

    
971
       if (is_ipaddr($hostname))
972
               return $hostname;
973

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

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

    
982
               sleep(1);
983
       }
984

    
985
       return false;
986
}
987

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

    
1000
function update_filter_reload_status($text) {
1001
        global $g;
1002

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

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

    
1031
function run_plugins($directory) {
1032
        global $config, $g;
1033

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

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

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

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

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

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

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

    
1101
	return $values;
1102
}
1103

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

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

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

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

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

    
1134
	return $ret;
1135
}
1136

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

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

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

    
1179
function start_devd() {
1180
	global $g;
1181

    
1182
        exec("/sbin/devd");
1183
        sleep(1);
1184
        if(file_exists("{$g['tmp_path']}/rc.linkup"))
1185
                unlink("{$g['tmp_path']}/rc.linkup");
1186
}
1187

    
1188
function is_interface_mismatch() {
1189
        global $config, $g;
1190

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

    
1204
        if ($g['minimum_nic_count'] > $i) {
1205
                $do_assign = true;
1206
        } else if (file_exists("{$g['tmp_path']}/assign_complete"))
1207
                $do_assign = false;
1208

    
1209
        return $do_assign;
1210
}
1211

    
1212
/* sync carp entries to other firewalls */
1213
function carp_sync_client() {
1214
	global $g;
1215
	touch($g['tmp_path'] . "/filter_sync"); 
1216
}
1217

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

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

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

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

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

    
1375
function is_file_included($file = "") {
1376
	$files = get_included_files();
1377
	if (in_array($file, $files))
1378
		return true;
1379
	
1380
	return false;
1381
}
1382

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

    
1392
    // We walk through each arrays and put value in the results (without
1393
    // considering previous value).
1394
    $result = array();
1395

    
1396
    // loop available array
1397
    foreach($arrays as $array) {
1398

    
1399
        // The first remaining array is $array. We are processing it. So
1400
        // we remove it from remaing arrays.
1401
        array_shift($remains);
1402

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

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

    
1434
?>
(42-42/53)