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
	$output = "";
46
	exec("/bin/pgrep -F {$pid}", $output, $retval);
47

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

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

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

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

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

    
68
	return 0;
69
}
70

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

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

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

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

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

    
92
	return false;
93
}
94

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
413
}
414

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

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

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

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

    
428
	return true;
429
}
430

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

    
434
	global $aliastable;
435

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
532
	$iflist = array();
533

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

    
547
	return $iflist;
548
}
549

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

    
554
        $alias_list=array();
555

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

    
565
        return $alias_list;
566
}
567

    
568

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

    
573
	$iflist = array();
574

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

    
583
	return $iflist;
584
}
585

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

    
590
	$iflist = array();
591

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

    
603
	return $iflist;
604
}
605

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

    
610
	$iflist = array();
611

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

    
624
	return $iflist;
625
}
626

    
627

    
628
/*
629
 *   get_interface_list() - Return a list of all physical interfaces
630
 *   along with MAC and status.
631
 *
632
 *   $mode = "active" - use ifconfig -lu
633
 *           "media"  - use ifconfig to check physical connection
634
 *			status (much slower)
635
 */
636
function get_interface_list($mode = "active", $keyby = "physical", $vfaces = "") {
637
        global $config;
638
	$upints = array();
639
        /* get a list of virtual interface types */
640
        if(!$vfaces) {
641
		$vfaces = array (
642
				'bridge',
643
				'ppp',
644
				'pppoe',
645
				'pptp',
646
				'l2tp',
647
				'sl',
648
				'gif',
649
				'gre',
650
				'faith',
651
				'lo',
652
				'ng',
653
				'_vlan',
654
				'_wlan',
655
				'pflog',
656
				'plip',
657
				'pfsync',
658
				'enc',
659
				'tun',
660
				'carp',
661
				'lagg',
662
				'vip',
663
				'ipfw'
664
		);
665
	}
666
	switch($mode) {
667
	case "active":
668
                $upints = explode(" ", trim(shell_exec("/sbin/ifconfig -lu")));
669
        	break;
670
	case "media":
671
                $intlist = explode(" ", trim(shell_exec("/sbin/ifconfig -l")));
672
                $ifconfig = "";
673
                exec("/sbin/ifconfig -a", $ifconfig);
674
                $regexp = '/(' . implode('|', $intlist) . '):\s/';
675
                $ifstatus = preg_grep('/status:/', $ifconfig);
676
		foreach($ifstatus as $status) {
677
			$int = array_shift($intlist);
678
                	if(stristr($status, "active")) $upints[] = $int;
679
		}
680
		break;
681
	default:
682
		$upints = explode(" ", trim(shell_exec("/sbin/ifconfig -l")));
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
			if (is_array($config['interfaces'])) {
711
				foreach($config['interfaces'] as $name => $int)
712
					if($int['if'] == $ifname) $friendly = $name;
713
			}
714
			switch($keyby) {
715
			case "physical":
716
				if($friendly != "") {
717
					$toput['friendly'] = $friendly;
718
				}
719
				$dmesg_arr = array();
720
				exec("/sbin/dmesg |grep $ifname | head -n1", $dmesg_arr);
721
				preg_match_all("/<(.*?)>/i", $dmesg_arr[0], $dmesg);
722
				$toput['dmesg'] = $dmesg[1][0];
723
				$iflist[$ifname] = $toput;
724
				break;
725
			case "ppp":
726
				
727
			case "friendly":
728
				if($friendly != "") {
729
					$toput['if'] = $ifname;
730
					$iflist[$friendly] = $toput;
731
				}
732
				break;
733
			}
734
                }
735
        }
736
        return $iflist;
737
}
738

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

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

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

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

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

    
797
	global $g;
798

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

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

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

    
820
	global $aliastable;
821

    
822
	$aliastable = array();
823

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

    
834
	global $aliastable;
835

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

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

    
842
	global $aliastable;
843

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
932
	return false;
933
}
934

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

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

    
947
    global $config;
948

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

    
951
    switch($mac_format) {
952

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

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

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

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

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

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

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

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

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

    
983
               sleep(1);
984
       }
985

    
986
       return false;
987
}
988

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

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

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

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

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

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

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

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

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

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

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

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

    
1102
	return $values;
1103
}
1104

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

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

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

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

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

    
1135
	return $ret;
1136
}
1137

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

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

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

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

    
1183
        exec("/sbin/devd");
1184
        sleep(1);
1185
}
1186

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

    
1190
        /* XXX: Should we process only enabled interfaces?! */
1191
        $do_assign = false;
1192
        $i = 0;
1193
	if (is_array($config['interfaces'])) {
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

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

    
1210
        return $do_assign;
1211
}
1212

    
1213
/* sync carp entries to other firewalls */
1214
function carp_sync_client() {
1215
	global $g;
1216
	send_event("filter sync");
1217
}
1218

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

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

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

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

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

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

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

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

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

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

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

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

    
1435
?>
(42-42/54)