Project

General

Profile

Bug #3062 » captiveportal.inc

File FIXED - Alberto Palau, 07/02/2013 03:38 PM

 
1
<?php
2
/*
3
	captiveportal.inc
4
	part of pfSense (http://www.pfSense.org)
5
	Copyright (C) 2004-2011 Scott Ullrich <sullrich@gmail.com>
6
	Copyright (C) 2009-2012 Ermal Lu?i <eri@pfsense.org>
7
	Copyright (C) 2003-2006 Manuel Kasper <mk@neon1.net>.
8

    
9
	originally part of m0n0wall (http://m0n0.ch/wall)
10
	All rights reserved.
11

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

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

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

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

    
33
	This version of captiveportal.inc has been modified by Rob Parker
34
	<rob.parker@keycom.co.uk> to include changes for per-user bandwidth management
35
	via returned RADIUS attributes. This page has been modified to delete any
36
	added rules which may have been created by other per-user code (index.php, etc).
37
	These changes are (c) 2004 Keycom PLC.
38
	
39
	pfSense_BUILDER_BINARIES:	/sbin/ipfw	/sbin/sysctl	/sbin/route
40
	pfSense_BUILDER_BINARIES:	/usr/local/sbin/lighttpd	/usr/local/bin/minicron /sbin/pfctl
41
	pfSense_BUILDER_BINARIES:	/bin/hostname	/bin/cp 
42
	pfSense_MODULE: captiveportal
43
*/
44

    
45
/* include all configuration functions */
46
require_once("config.inc");
47
require_once("functions.inc");
48
require_once("filter.inc");
49
require_once("radius.inc");
50
require_once("voucher.inc");
51

    
52
function get_default_captive_portal_html() {
53
	global $config, $g, $cpzone;
54

    
55
	$htmltext = <<<EOD
56
<html> 
57
<body> 
58
<form method="post" action="\$PORTAL_ACTION\$"> 
59
	<input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$">
60
	<input name="zone" type="hidden" value="\$PORTAL_ZONE\$">
61
	<center>
62
	<table cellpadding="6" cellspacing="0" width="550" height="380" style="border:1px solid #000000">
63
	<tr height="10" bgcolor="#990000">
64
		<td style="border-bottom:1px solid #000000">
65
			<font color='white'>
66
			<b>
67
				{$g['product_name']} captive portal
68
			</b>
69
			</font>
70
		</td>
71
	</tr>
72
	<tr>
73
		<td>
74
			<div id="mainlevel">
75
			<center>
76
			<table width="100%" border="0" cellpadding="5" cellspacing="0">
77
			<tr>
78
				<td>
79
					<center>
80
					<div id="mainarea">
81
					<center>
82
					<table width="100%" border="0" cellpadding="5" cellspacing="5">
83
					<tr>
84
						<td>
85
							<div id="maindivarea">
86
							<center>
87
								<div id='statusbox'>
88
									<font color='red' face='arial' size='+1'>
89
									<b>
90
										\$PORTAL_MESSAGE\$
91
									</b>
92
									</font>
93
								</div>
94
								<br/>
95
								<div id='loginbox'>
96
								<table>
97
									<tr><td colspan="2"><center>Welcome to the {$g['product_name']} Captive Portal!</td></tr>
98
									<tr><td>&nbsp;</td></tr>
99
									<tr><td align="right">Username:</td><td><input name="auth_user" type="text" style="border: 1px dashed;"></td></tr>
100
									<tr><td align="right">Password:</td><td><input name="auth_pass" type="password" style="border: 1px dashed;"></td></tr>
101
									<tr><td>&nbsp;</td></tr>
102

    
103
EOD;
104

    
105
	if(isset($config['voucher'][$cpzone]['enable'])) {
106
	$htmltext .= <<<EOD
107
									<tr>
108
										<td align="right">Enter Voucher Code: </td>
109
										<td><input name="auth_voucher" type="text" style="border:1px dashed;" size="22"></td>
110
									</tr>
111

    
112
EOD;
113
	}
114

    
115
	$htmltext .= <<<EOD
116
									<tr>
117
										<td colspan="2"><center><input name="accept" type="submit" value="Continue"></center></td>
118
									</tr>
119
								</table>
120
								</div>
121
							</center>
122
							</div>
123
						</td>
124
					</tr>
125
					</table>
126
					</center>
127
					</div>
128
					</center>
129
				</td>
130
			</tr>
131
			</table>
132
			</center>
133
			</div>
134
		</td>
135
	</tr>
136
	</table>
137
	</center>
138
</form>
139
</body> 
140
</html>
141

    
142
EOD;
143

    
144
	return $htmltext;
145
}
146

    
147
function captiveportal_load_modules() {
148
	global $config;
149

    
150
	mute_kernel_msgs();
151
	if (!is_module_loaded("ipfw.ko")) {
152
		mwexec("/sbin/kldload ipfw");
153
		/* make sure ipfw is not on pfil hooks */
154
		mwexec("/sbin/sysctl net.inet.ip.pfil.inbound=\"pf\" net.inet6.ip6.pfil.inbound=\"pf\"" .
155
		       " net.inet.ip.pfil.outbound=\"pf\" net.inet6.ip6.pfil.outbound=\"pf\"");
156
		/* Activate layer2 filtering */
157
		mwexec("/sbin/sysctl net.link.ether.ipfw=1 net.inet.ip.fw.one_pass=1");
158
	}
159

    
160
	/* Always load dummynet now that even allowed ip and mac passthrough use it. */
161
	if (!is_module_loaded("dummynet.ko")) {
162
		mwexec("/sbin/kldload dummynet");
163
		mwexec("/sbin/sysctl net.inet.ip.dummynet.io_fast=1 net.inet.ip.dummynet.hash_size=256");
164
	}
165
	unmute_kernel_msgs();
166

    
167
	/* XXX: This are not used in pfSense, if needed can be tuned 
168
	if($config['system']['maximumstates'] <> "" && is_numeric($config['system']['maximumstates'])) {
169
			mwexec("sysctl net.inet.ip.fw.dyn_max={$config['system']['maximumstates']}");
170
	} else {
171
			mwexec("sysctl net.inet.ip.fw.dyn_max=10000");
172
	}
173
	*/
174
}
175

    
176
function captiveportal_configure() {
177
	global $config, $cpzone;
178

    
179
	if (is_array($config['captiveportal'])) {
180
		foreach ($config['captiveportal'] as $cpkey => $cp) {
181
			$cpzone = $cpkey;
182
			captiveportal_configure_zone($cp);
183
		}
184
	} else
185
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
186
}
187

    
188
function captiveportal_configure_zone($cpcfg) {
189
	global $config, $g, $cpzone;
190

    
191
	$captiveportallck = lock("captiveportal{$cpzone}", LOCK_EX);
192
	
193
	if (isset($cpcfg['enable'])) {
194

    
195
		if ($g['booting']) {
196
			echo "Starting captive portal({$cpcfg['zone']})... ";
197

    
198
			/* remove old information */
199
			unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
200
		} else
201
			captiveportal_syslog("Reconfiguring captive portal({$cpcfg['zone']}).");
202

    
203
		/* init ipfw rules */
204
		captiveportal_init_rules(true);
205

    
206
		/* kill any running minicron */
207
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
208

    
209
		/* initialize minicron interval value */
210
		$croninterval = $cpcfg['croninterval'] ? $cpcfg['croninterval'] : 60;
211

    
212
		/* double check if the $croninterval is numeric and at least 10 seconds. If not we set it to 60 to avoid problems */
213
		if ((!is_numeric($croninterval)) || ($croninterval < 10))
214
			$croninterval = 60;
215

    
216
		/* write portal page */
217
		if (is_array($cpcfg['page']) && $cpcfg['page']['htmltext'])
218
			$htmltext = base64_decode($cpcfg['page']['htmltext']);
219
		else {
220
			/* example/template page */
221
			$htmltext = get_default_captive_portal_html();
222
		}
223

    
224
		$fd = @fopen("{$g['varetc_path']}/captiveportal_{$cpzone}.html", "w");
225
		if ($fd) {
226
			// Special case handling.  Convert so that we can pass this page
227
			// through the PHP interpreter later without clobbering the vars.
228
			$htmltext = str_replace("\$PORTAL_ZONE\$", "#PORTAL_ZONE#", $htmltext);
229
			$htmltext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $htmltext);
230
			$htmltext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $htmltext);
231
			$htmltext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $htmltext);
232
			$htmltext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $htmltext);
233
			$htmltext = str_replace("\$ORIGINAL_PORTAL_IP\$", "#ORIGINAL_PORTAL_IP#", $htmltext);
234
			$htmltext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $htmltext);
235
			if($cpcfg['preauthurl']) {
236
				$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
237
				$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
238
			}
239
			fwrite($fd, $htmltext);
240
			fclose($fd);
241
		}
242
		unset($htmltext);
243

    
244
		/* write error page */
245
		if (is_array($cpcfg['page']) && $cpcfg['page']['errtext'])
246
			$errtext = base64_decode($cpcfg['page']['errtext']);
247
		else {
248
			/* example page  */
249
			$errtext = get_default_captive_portal_html();
250
		}
251

    
252
		$fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html", "w");
253
		if ($fd) {
254
			// Special case handling.  Convert so that we can pass this page
255
			// through the PHP interpreter later without clobbering the vars.
256
			$errtext = str_replace("\$PORTAL_ZONE\$", "#PORTAL_ZONE#", $errtext);
257
			$errtext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $errtext);
258
			$errtext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $errtext);
259
			$errtext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $errtext);
260
			$errtext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $errtext);
261
			$errtext = str_replace("\$ORIGINAL_PORTAL_IP\$", "#ORIGINAL_PORTAL_IP#", $errtext);
262
			$errtext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $errtext);
263
			if($cpcfg['preauthurl']) {
264
				$errtext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $errtext);
265
				$errtext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $errtext);
266
			}
267
			fwrite($fd, $errtext);
268
			fclose($fd);
269
		}
270
		unset($errtext);
271

    
272
		/* write logout page */
273
		if (is_array($cpcfg['page']) && $cpcfg['page']['logouttext'])
274
			$logouttext = base64_decode($cpcfg['page']['logouttext']);
275
		else {
276
			/* example page */
277
			$logouttext = <<<EOD
278
<HTML>
279
<HEAD><TITLE>Redirecting...</TITLE></HEAD>
280
<BODY>
281
<SPAN STYLE="font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">
282
<B>Redirecting to <A HREF="<?=\$my_redirurl;?>"><?=\$my_redirurl;?></A>...</B>
283
</SPAN>
284
<SCRIPT LANGUAGE="JavaScript">
285
<!--
286
LogoutWin = window.open('', 'Logout', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=256,height=64');
287
if (LogoutWin) {
288
	LogoutWin.document.write('<HTML>');
289
	LogoutWin.document.write('<HEAD><TITLE>Logout</TITLE></HEAD>') ;
290
	LogoutWin.document.write('<BODY BGCOLOR="#435370">');
291
	LogoutWin.document.write('<DIV ALIGN="center" STYLE="color: #ffffff; font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">') ;
292
	LogoutWin.document.write('<B>Click the button below to disconnect</B><P>');
293
	LogoutWin.document.write('<FORM METHOD="POST" ACTION="<?=\$logouturl;?>">');
294
	LogoutWin.document.write('<INPUT NAME="logout_id" TYPE="hidden" VALUE="<?=\$sessionid;?>">');
295
	LogoutWin.document.write('<INPUT NAME="zone" TYPE="hidden" VALUE="<?=\$cpzone;?>">');
296
	LogoutWin.document.write('<INPUT NAME="logout" TYPE="submit" VALUE="Logout">');
297
	LogoutWin.document.write('</FORM>');
298
	LogoutWin.document.write('</DIV></BODY>');
299
	LogoutWin.document.write('</HTML>');
300
	LogoutWin.document.close();
301
}
302

    
303
document.location.href="<?=\$my_redirurl;?>";
304
-->
305
</SCRIPT>
306
</BODY>
307
</HTML>
308

    
309
EOD;
310
		}
311

    
312
		$fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html", "w");
313
		if ($fd) {
314
			fwrite($fd, $logouttext);
315
			fclose($fd);
316
		}
317
		unset($logouttext);
318

    
319
		/* write elements */
320
		captiveportal_write_elements();
321

    
322
		/* kill any running mini_httpd */
323
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
324
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
325

    
326
		/* start up the webserving daemon */
327
		captiveportal_init_webgui_zone($cpcfg);
328

    
329
		/* Kill any existing prunecaptiveportal processes */
330
		if (file_exists("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid"))
331
			killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
332

    
333
		/* start pruning process (interval defaults to 60 seconds) */
334
		mwexec("/usr/local/bin/minicron $croninterval {$g['varrun_path']}/cp_prunedb_{$cpzone}.pid " .
335
			"/etc/rc.prunecaptiveportal {$cpzone}");
336

    
337
		/* generate radius server database */
338
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
339
		captiveportal_init_radius_servers();
340

    
341
		if ($g['booting']) {
342
			/* send Accounting-On to server */
343
			captiveportal_send_server_accounting();
344
			echo "done\n";
345
		}
346

    
347
	} else {
348
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
349
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
350
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
351
		@unlink("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
352
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
353
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
354

    
355
		captiveportal_radius_stop_all();
356

    
357
		/* send Accounting-Off to server */
358
		if (!$g['booting']) {
359
			captiveportal_send_server_accounting(true);
360
		}
361

    
362
		/* remove old information */
363
		unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
364
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
365

    
366
		mwexec("/usr/local/sbin/ipfw_context -d {$cpzone}", true);
367

    
368
		if (empty($config['captiveportal']))
369
			mwexec("/sbin/sysctl net.link.ether.ipfw=0");
370
		else {
371
			/* Deactivate ipfw(4) if not needed */
372
			$cpactive = false;
373
			if (is_array($config['captiveportal'])) {
374
				foreach ($config['captiveportal'] as $cpkey => $cp) {
375
					if (isset($cp['enable'])) {
376
						$cpactive = true;
377
						break;
378
					}
379
				}
380
			}
381
			if ($cpactive === false)
382
				mwexec("/sbin/sysctl net.link.ether.ipfw=0");
383
				
384
		}
385
	}
386

    
387
	unlock($captiveportallck);
388
	
389
	return 0;
390
}
391

    
392
function captiveportal_init_webgui() {
393
	global $config, $cpzone;
394

    
395
	if (is_array($config['captiveportal'])) {
396
		foreach ($config['captiveportal'] as $cpkey => $cp) {
397
			$cpzone = $cpkey;
398
			captiveportal_init_webgui_zone($cp);
399
		}
400
	}
401
}
402

    
403
function captiveportal_init_webgui_zonename($zone) {
404
	global $config, $cpzone;
405
	
406
	if (isset($config['captiveportal'][$zone])) {
407
		$cpzone = $zone;
408
		captiveportal_init_webgui_zone($config['captiveportal'][$zone]);
409
	}
410
}
411

    
412
function captiveportal_init_webgui_zone($cpcfg) {
413
	global $g, $config, $cpzone;
414

    
415
	if (!isset($cpcfg['enable']))
416
		return;
417

    
418
	if (isset($cpcfg['httpslogin'])) {
419
		$cert = lookup_cert($cpcfg['certref']);
420
		$crt = base64_decode($cert['crt']);
421
		$key = base64_decode($cert['prv']);
422
		$ca = ca_chain($cert);
423

    
424
		/* generate lighttpd configuration */
425
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
426
		system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf",
427
			$crt, $key, $ca, "lighty-{$cpzone}-CaptivePortal-SSL.pid", $listenporthttps, "/usr/local/captiveportal",
428
			"cert-{$cpzone}-portal.pem", "ca-{$cpzone}-portal.pem", $cpzone);
429
	}
430

    
431
	/* generate lighttpd configuration */
432
	$listenporthttp = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : $cpcfg['zoneid'];
433
	system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal.conf",
434
		"", "", "", "lighty-{$cpzone}-CaptivePortal.pid", $listenporthttp, "/usr/local/captiveportal",
435
		"", "", $cpzone);
436

    
437
	@unlink("{$g['varrun']}/lighty-{$cpzone}-CaptivePortal.pid");
438
	/* attempt to start lighttpd */
439
	$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal.conf");
440

    
441
	/* fire up https instance */
442
	if (isset($cpcfg['httpslogin'])) {
443
		@unlink("{$g['varrun']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
444
		$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf");
445
	}
446
}
447

    
448
/* reinit will disconnect all users, be careful! */
449
function captiveportal_init_rules($reinit = false) {
450
	global $config, $g, $cpzone;
451

    
452
	if (!isset($config['captiveportal'][$cpzone]['enable']))
453
		return;
454

    
455
	captiveportal_load_modules();
456
	mwexec("/usr/local/sbin/ipfw_context -a {$cpzone}", true);
457

    
458
	$cpips = array();
459
	$ifaces = get_configured_interface_list();
460
	$cpinterfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
461
	$firsttime = 0;
462
	foreach ($cpinterfaces as $cpifgrp) {
463
		if (!isset($ifaces[$cpifgrp]))
464
			continue;
465
		$tmpif = get_real_interface($cpifgrp);
466
		if (!empty($tmpif)) {
467
			$cpipm = get_interface_ip($cpifgrp);
468
			if (is_ipaddr($cpipm)) {
469
				$carpif = link_ip_to_carp_interface($cpipm);
470
				if (!empty($carpif)) {
471
					$carpsif = explode(" ", $carpif);
472
					foreach ($carpsif as $cpcarp) {
473
						mwexec("/usr/local/sbin/ipfw_context -a {$cpzone} -n {$cpcarp}", true);
474
						$carpip = find_interface_ip($cpcarp);
475
						if (is_ipaddr($carpip))
476
							$cpips[] = $carpip;
477
					}
478
				}
479
				$cpips[] = $cpipm;
480
			}
481
			mwexec("/usr/local/sbin/ipfw_context -a {$cpzone} -n {$tmpif}", true);
482
		}
483
	}
484
	if (count($cpips) > 0) {
485
		$cpactive = true;
486
	} else
487
		return false;
488

    
489
	if ($reinit == false)
490
		$captiveportallck = lock("captiveportal{$cpzone}");
491

    
492
	$cprules =	"add 65291 allow pfsync from any to any\n";
493
	$cprules .= "add 65292 allow carp from any to any\n";
494

    
495
	$cprules .= <<<EOD
496
# layer 2: pass ARP
497
add 65301 pass layer2 mac-type arp,rarp
498
# pfsense requires for WPA
499
add 65302 pass layer2 mac-type 0x888e,0x88c7
500
# PPP Over Ethernet Session Stage/Discovery Stage
501
add 65303 pass layer2 mac-type 0x8863,0x8864
502

    
503
# layer 2: block anything else non-IP(v4/v6)
504
add 65307 deny layer2 not mac-type ip,ipv6
505

    
506
EOD;
507

    
508
	$rulenum = 65310;
509
	$ipcount = 0;
510
	$ips = "";
511
	foreach ($cpips as $cpip) {
512
		if($ipcount == 0) {
513
			$ips = "{$cpip} ";
514
		} else {
515
			$ips .= "or {$cpip} ";
516
		}
517
		$ipcount++;
518
	}
519
	$ips = "{ 255.255.255.255 or {$ips} }";
520
	$cprules .= "add {$rulenum} pass ip from any to {$ips} in\n";
521
	$rulenum++;
522
	$cprules .= "add {$rulenum} pass ip from {$ips} to any out\n";
523
	$rulenum++;
524
	$cprules .= "add {$rulenum} pass icmp from {$ips} to any out icmptype 0\n";
525
	$rulenum++;
526
	$cprules .= "add {$rulenum} pass icmp from any to {$ips} in icmptype 8 \n";
527
	$rulenum++;
528
	/* Allowed ips */
529
	$cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any in\n";
530
	$rulenum++;
531
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) in\n";
532
	$rulenum++;
533
	$cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any out\n";
534
	$rulenum++;
535
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) out\n";
536
	$rulenum++;
537

    
538
	/* Authenticated users rules. */
539
	$cprules .= "add {$rulenum} pipe tablearg ip from table(1) to any in\n";
540
	$rulenum++;
541
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(2) out\n";
542
	$rulenum++;
543

    
544
	$listenporthttp =
545
		$config['captiveportal'][$cpzone]['listenporthttp'] ?
546
		$config['captiveportal'][$cpzone]['listenporthttp'] :
547
		$config['captiveportal'][$cpzone]['zoneid'];
548

    
549
	if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
550
		$listenporthttps = $listenporthttp + 1;
551
		$cprules .= "add 65531 fwd 127.0.0.1,{$listenporthttps} tcp from any to any dst-port 443 in\n";
552
	}
553
	
554
	$cprules .= <<<EOD
555

    
556
# redirect non-authenticated clients to captive portal
557
add 65532 fwd 127.0.0.1,{$listenporthttp} tcp from any to any dst-port 80 in 
558
# let the responses from the captive portal web server back out
559
add 65533 pass tcp from any to any out
560
# block everything else
561
add 65534 deny all from any to any
562

    
563
EOD;
564

    
565
	/* generate passthru mac database */
566
	$cprules .= captiveportal_passthrumac_configure(true);
567
	$cprules .= "\n";
568

    
569
	/* allowed ipfw rules to make allowed ip work */
570
	$cprules .= captiveportal_allowedip_configure();
571

    
572
	/* allowed ipfw rules to make allowed hostnames work */
573
	$cprules .= captiveportal_allowedhostname_configure();
574
	
575
	/* load rules */
576
	$cprules = "flush\n{$cprules}";
577
	file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
578
	mwexec("/sbin/ipfw -x {$cpzone} -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
579
	//@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
580
	unset($cprules, $tmprules);
581

    
582
	if ($reinit == false)
583
		unlock($captiveportallck);
584
}
585

    
586
/* 
587
 * Remove clients that have been around for longer than the specified amount of time
588
 * db file structure:
589
 * timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time,interim_interval
590
 * (password is in Base64 and only saved when reauthentication is enabled)
591
 */
592
function captiveportal_prune_old() {
593
	global $g, $config, $cpzone;
594

    
595
	if (empty($cpzone))
596
		return;
597

    
598
	$cpcfg = $config['captiveportal'][$cpzone];
599
	$vcpcfg = $config['voucher'][$cpzone];
600

    
601
	/* check for expired entries */
602
	$idletimeout = 0;
603
	$timeout = 0;
604
	if (!empty($cpcfg['timeout']) && is_numeric($cpcfg['timeout']))
605
		$timeout = $cpcfg['timeout'] * 60;
606

    
607
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout']))
608
		$idletimeout = $cpcfg['idletimeout'] * 60;
609

    
610
	/* Is there any job to do? */
611
	if (!$timeout && !$idletimeout && !isset($cpcfg['reauthenticate']) &&
612
	    !isset($cpcfg['radiussession_timeout']) && !isset($vcpcfg['enable']))
613
		return;
614

    
615
	$radiussrvs = captiveportal_get_radius_servers();
616

    
617
	/* Read database */
618
	/* NOTE: while this can be simplified in non radius case keep as is for now */
619
	$cpdb = captiveportal_read_db();
620

    
621
	/*
622
	 * To make sure we iterate over ALL accounts on every run the count($cpdb) is moved
623
	 * outside of the loop. Otherwise the loop would evaluate count() on every iteration
624
	 * and since $i would increase and count() would decrement they would meet before we
625
	 * had a chance to iterate over all accounts.
626
	 */
627
	$unsetindexes = array();
628
	$voucher_needs_sync = false;
629
	/* 
630
	 * Snapshot the time here to use for calculation to speed up the process.
631
	 * If something is missed next run will catch it!
632
	 */
633
	$pruning_time = time();
634
	$stop_time = $pruning_time;
635
	foreach ($cpdb as $cpentry) {
636

    
637
		$timedout = false;
638
		$term_cause = 1;
639
		if (empty($cpentry[10]))
640
			$cpentry[10] = 'first';
641
		$radiusservers = $radiussrvs[$cpentry[10]];
642

    
643
		/* hard timeout? */
644
		if ($timeout) {
645
			if (($pruning_time - $cpentry[0]) >= $timeout) {
646
				$timedout = true;
647
				$term_cause = 5; // Session-Timeout
648
			}
649
		}
650

    
651
		/* Session-Terminate-Time */
652
		if (!$timedout && !empty($cpentry[9])) {
653
			if ($pruning_time >= $cpentry[9]) {
654
				$timedout = true;
655
				$term_cause = 5; // Session-Timeout
656
			}
657
		}
658

    
659
		/* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
660
		$uidletimeout = (is_numeric($cpentry[8])) ? $cpentry[8] : $idletimeout;
661
		/* if an idle timeout is specified, get last activity timestamp from ipfw */
662
		if (!$timedout && $uidletimeout > 0) {
663
			$lastact = captiveportal_get_last_activity($cpentry[2]);
664
			/*	If the user has logged on but not sent any traffic they will never be logged out.
665
			 *	We "fix" this by setting lastact to the login timestamp. 
666
			 */
667
			$lastact = $lastact ? $lastact : $cpentry[0];
668
			if ($lastact && (($pruning_time - $lastact) >= $uidletimeout)) {
669
				$timedout = true;
670
				$term_cause = 4; // Idle-Timeout
671
				$stop_time = $lastact; // Entry added to comply with WISPr
672
			}
673
		}
674

    
675
		/* if vouchers are configured, activate session timeouts */
676
		if (!$timedout && isset($vcpcfg['enable']) && !empty($cpentry[7])) {
677
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
678
				$timedout = true;
679
				$term_cause = 5; // Session-Timeout
680
				$voucher_needs_sync = true;
681
			}
682
		}
683

    
684
		/* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
685
		if (!$timedout && isset($cpcfg['radiussession_timeout']) && !empty($cpentry[7])) {
686
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
687
				$timedout = true;
688
				$term_cause = 5; // Session-Timeout
689
			}
690
		}
691

    
692
		if ($timedout) {
693
			captiveportal_disconnect($cpentry, $radiusservers,$term_cause,$stop_time);
694
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
695
			$unsetindexes[] = $cpentry[5];
696
		}
697

    
698
		/* do periodic RADIUS reauthentication? */
699
		if (!$timedout && !empty($radiusservers)) {
700
			if (isset($cpcfg['radacct_enable'])) {
701
				if ($cpcfg['reauthenticateacct'] == "stopstart") {
702
					/* stop and restart accounting */
703
					RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
704
						$cpentry[4], // username
705
						$cpentry[5], // sessionid
706
						$cpentry[0], // start time
707
						$radiusservers,
708
						$cpentry[2], // clientip
709
						$cpentry[3], // clientmac
710
						10); // NAS Request
711
					$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ZERO_ENTRY_STATS, 1, $cpentry[2]);
712
					$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ZERO_ENTRY_STATS, 2, $cpentry[2]);
713
					RADIUS_ACCOUNTING_START($cpentry[1], // ruleno
714
						$cpentry[4], // username
715
						$cpentry[5], // sessionid
716
						$radiusservers,
717
						$cpentry[2], // clientip
718
						$cpentry[3]); // clientmac
719
				} else if ($cpcfg['reauthenticateacct'] == "interimupdate") {
720
					$session_time = $pruning_time - $cpentry[0];
721
					if (!empty($cpentry[10]) && $cpentry[10] > 60)
722
						$interval = $cpentry[10];
723
					else
724
						$interval = 0;
725
					$past_interval_min = ($session_time > $interval);
726
					$within_interval = ($session_time % $interval >= 0 && $session_time % $interval <= 59);
727
					if (($interval > 0 && $past_interval_min && $within_interval) || $interval === 0) {
728
						RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
729
							$cpentry[4], // username
730
							$cpentry[5], // sessionid
731
							$cpentry[0], // start time
732
							$radiusservers,
733
							$cpentry[2], // clientip
734
							$cpentry[3], // clientmac
735
							10, // NAS Request
736
							true); // Interim Updates
737
					}
738
				}
739
			}
740

    
741
			/* check this user against RADIUS again */
742
			if (isset($cpcfg['reauthenticate'])) {
743
				$auth_list = RADIUS_AUTHENTICATION($cpentry[4], // username
744
					base64_decode($cpentry[6]), // password
745
					$radiusservers,
746
					$cpentry[2], // clientip
747
					$cpentry[3], // clientmac
748
					$cpentry[1]); // ruleno
749
				if ($auth_list['auth_val'] == 3) {
750
					captiveportal_disconnect($cpentry, $radiusservers, 17);
751
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
752
					$unsetindexes[] = $cpentry[5];
753
				} else if ($auth_list['auth_val'] == 2)
754
					captiveportal_reapply_attributes($cpentry, $auth_list);
755
			}
756
		}
757
	}
758
	unset($cpdb);
759

    
760
	captiveportal_prune_old_automac();
761

    
762
	if ($voucher_needs_sync == true)
763
		/* Triger a sync of the vouchers on config */
764
		send_event("service sync vouchers");
765

    
766
	/* write database */
767
	if (!empty($unsetindexes))
768
		captiveportal_remove_entries($unsetindexes);
769
}
770

    
771
function captiveportal_prune_old_automac() {
772
	global $g, $config, $cpzone;
773

    
774
	if (is_array($config['captiveportal'][$cpzone]['passthrumac']) && isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
775
		$tmpvoucherdb = array();
776
		$macrules = "";
777
		$writecfg = false;
778
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $eid => $emac) {
779
			if ($emac['logintype'] == "voucher") {
780
				if (isset($tmpvoucherdb[$emac['username']])) {
781
					$temac = $config['captiveportal'][$cpzone]['passthrumac'][$tmpvoucherdb[$emac['username']]];
782
					$ruleno = captiveportal_get_ipfw_passthru_ruleno($temac['mac']);
783
					$pipeno = captiveportal_get_dn_passthru_ruleno($temac['mac']);
784
					if ($ruleno) {
785
						captiveportal_free_ipfw_ruleno($ruleno);
786
						$macrules .= "delete {$ruleno}";
787
						++$ruleno;
788
						$macrules .= "delete {$ruleno}";
789
					}
790
					if ($pipeno) {
791
						captiveportal_free_dn_ruleno($pipeno);
792
						$macrules .= "pipe delete {$pipeno}\n";
793
						++$pipeno;
794
						$macrules .= "pipe delete {$pipeno}\n";
795
					}
796
					$writecfg = true;
797
					captiveportal_logportalauth($temac['username'], $temac['mac'], $temac['ip'], "DUPLICATE {$temac['username']} LOGIN - TERMINATING OLD SESSION");
798
					unset($config['captiveportal'][$cpzone]['passthrumac'][$tmpvoucherdb[$emac['username']]]);
799
				}
800
				$tmpvoucherdb[$emac['username']] = $eid;
801
				if (voucher_auth($emac['username']) <= 0) {
802
					$ruleno = captiveportal_get_ipfw_passthru_ruleno($emac['mac']);
803
					$pipeno = captiveportal_get_dn_passthru_ruleno($emac['mac']);
804
					if ($ruleno) {
805
						captiveportal_free_ipfw_ruleno($ruleno);
806
						$macrules .= "delete {$ruleno}";
807
						++$ruleno;
808
						$macrules .= "delete {$ruleno}";
809
					}
810
					if ($pipeno) {
811
						captiveportal_free_dn_ruleno($pipeno);
812
						$macrules .= "pipe delete {$pipeno}\n";
813
						++$pipeno;
814
						$macrules .= "pipe delete {$pipeno}\n";
815
					}
816
					$writecfg = true;
817
					captiveportal_logportalauth($emac['username'], $emac['mac'], $emac['ip'], "EXPIRED {$emac['username']} LOGIN - TERMINATING SESSION");
818
					unset($config['captiveportal'][$cpzone]['passthrumac'][$eid]);
819
				}
820
			}
821
		}
822
		if (!empty($macrules)) {
823
			@file_put_contents("{$g['tmp_path']}/macentry.prunerules.tmp", $macrules);
824
			unset($macrules);
825
			mwexec("/sbin/ipfw -x {$cpzone} -q {$g['tmp_path']}/macentry.prunerules.tmp");
826
		}
827
		if ($writecfg === true)
828
			write_config("Prune session for auto-added macs");
829
	}
830
}
831

    
832
/* remove a single client according to the DB entry */
833
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
834
	global $g, $config, $cpzone;
835

    
836
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
837

    
838
	/* this client needs to be deleted - remove ipfw rules */
839
	if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers)) {
840
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
841
			$dbent[4], // username
842
			$dbent[5], // sessionid
843
			$dbent[0], // start time
844
			$radiusservers,
845
			$dbent[2], // clientip
846
			$dbent[3], // clientmac
847
			$term_cause, // Acct-Terminate-Cause
848
			false,
849
			$stop_time);
850
	}
851
	
852
	if (is_ipaddr($dbent[2])) {
853
		/* Delete client's ip entry from tables 1 and 2. */
854
		$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_DEL, 1, $dbent[2]);
855
		$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_DEL, 2, $dbent[2]);
856
		/* XXX: Redundant?! Ensure all pf(4) states are killed. */
857
		$_gb = @pfSense_kill_states($dbent[2]);
858
		$_gb = @pfSense_kill_srcstates($dbent[2]);
859
	}
860

    
861
	/* 
862
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
863
	* We could get an error if the pipe doesn't exist but everything should still be fine
864
	*/
865
	if (!empty($dbent[1])) {
866
		$_gb = @pfSense_pipe_action("pipe delete {$dbent[1]}");
867
		$_gb = @pfSense_pipe_action("pipe delete " . ($dbent[1]+1));
868

    
869
		/* Release the ruleno so it can be reallocated to new clients. */
870
		captiveportal_free_dn_ruleno($dbent[1]);
871
	}
872

    
873
	// XMLRPC Call over to the master Voucher node
874
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
875
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
876
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
877
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
878
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
879
		$remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time);
880
	}
881

    
882
}
883

    
884
/* remove a single client by sessionid */
885
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
886
	global $g, $config;
887

    
888
	$radiusservers = captiveportal_get_radius_servers();
889

    
890
	/* read database */
891
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
892

    
893
	/* find entry */
894
	if (!empty($result)) {
895
		captiveportal_write_db("DELETE FROM captiveportal WHERE sessionid = '{$sessionid}'");
896

    
897
		foreach ($result as $cpentry) {
898
			if (empty($cpentry[10]))
899
				$cpentry[10] = 'first';
900
			captiveportal_disconnect($cpentry, $radiusservers[$cpentry[10]], $term_cause);
901
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
902
		}
903
		unset($result);
904
	}
905
}
906

    
907
/* send RADIUS acct stop for all current clients */
908
function captiveportal_radius_stop_all() {
909
	global $config, $cpzone;
910

    
911
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable']))
912
		return;
913

    
914
	$radiusservers = captiveportal_get_radius_servers();
915
	if (!empty($radiusservers)) {
916
		$cpdb = captiveportal_read_db();
917
		foreach ($cpdb as $cpentry) {
918
			if (empty($cpentry[10]))
919
				$cpentry[10] = 'first';
920
			if (!empty($radiusservers[$cpentry[10]])) {
921
				RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
922
					$cpentry[4], // username
923
					$cpentry[5], // sessionid
924
					$cpentry[0], // start time
925
					$radiusservers[$cpentry[10]],
926
					$cpentry[2], // clientip
927
					$cpentry[3], // clientmac
928
					7); // Admin Reboot
929
			}
930
		}
931
	}
932
}
933

    
934
function captiveportal_passthrumac_configure_entry($macent) {
935

    
936
	$bwUp = empty($macent['bw_up']) ? 0 : $macent['bw_up'];
937
	$bwDown = empty($macent['bw_down']) ? 0 : $macent['bw_down'];
938

    
939
	$ruleno = captiveportal_get_next_ipfw_ruleno();
940
	$pipeno = captiveportal_get_next_dn_ruleno();
941

    
942
	$rules = "";
943
	$pipeup = $pipeno;
944
	$_gb = @pfSense_pipe_action("pipe {$pipeup} config bw {$bwUp}Kbit/s queue 100 buckets 16");
945
	$pipedown = $pipeno + 1;
946
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
947
	$rules .= "add {$ruleno} pipe {$pipeup} ip from any to any MAC {$macent['mac']} any\n";
948
	$ruleno++;
949
	$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC any {$macent['mac']}\n";
950

    
951
	return $rules;
952
}
953

    
954
function captiveportal_passthrumac_configure($lock = false) {
955
	global $config, $g, $cpzone;
956

    
957
	$rules = "";
958

    
959
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
960
		$macdb = array();
961
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
962
			$rules .= captiveportal_passthrumac_configure_entry($macent);
963
			$macdb[$macent['mac']][$cpzone]['active']  = true;
964

    
965
		}
966
	}
967

    
968
	return $rules;
969
}
970

    
971
function captiveportal_passthrumac_findbyname($username) {
972
	global $config, $cpzone;
973

    
974
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
975
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
976
			if ($macent['username'] == $username)
977
				return $macent;
978
		}
979
	}
980
	return NULL;
981
}
982

    
983
/* 
984
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
985
 */
986
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
987
	global $g;
988

    
989
	/*  Instead of copying this entire function for something
990
	 *  easy such as hostname vs ip address add this check
991
	 */
992
	if ($ishostname === true) {
993
		if (!$g['booting']) {
994
			$ipaddress = gethostbyname($ipent['hostname']);
995
			if (!is_ipaddr($ipaddress)) 
996
				return;
997
		} else
998
			$ipaddress = "";
999
	} else
1000
		$ipaddress = $ipent['ip'];
1001

    
1002
	$rules = "";
1003
	$cp_filterdns_conf = "";
1004
	$enBwup = empty($ipent['bw_up']) ? 0 : intval($ipent['bw_up']);
1005
	$enBwdown = empty($ipent['bw_down']) ? 0 : intval($ipent['bw_down']);
1006

    
1007
	$pipeno = captiveportal_get_next_dn_ruleno();
1008
	$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$enBwup}Kbit/s queue 100 buckets 16");
1009
	$pipedown = $pipeno + 1;
1010
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16");
1011
	if ($ishostname === true) {
1012
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
1013
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
1014
		if (!is_ipaddr($ipaddress))
1015
			return array("", $cp_filterdns_conf);
1016
	}
1017
	$subnet = "";
1018
	if (!empty($ipent['sn']))
1019
		$subnet = "/{$ipent['sn']}";
1020
	$rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
1021
	$rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
1022

    
1023
	if ($ishostname === true)
1024
		return array($rules, $cp_filterdns_conf);
1025
	else
1026
		return $rules;
1027
}
1028

    
1029
function captiveportal_allowedhostname_configure() {
1030
	global $config, $g, $cpzone;
1031

    
1032
	$rules = "";
1033
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1034
		$rules = "\n# captiveportal_allowedhostname_configure()\n";
1035
		$cp_filterdns_conf = "";
1036
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) {
1037
			$tmprules = captiveportal_allowedip_configure_entry($hostnameent, true);
1038
			$rules .= $tmprules[0];
1039
			$cp_filterdns_conf .= $tmprules[1];
1040
		}
1041
		$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1042
		@file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1043
		unset($cp_filterdns_conf);
1044
		if (isvalidpid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid"))
1045
			sigkillbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid", "HUP");
1046
		else
1047
			mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzone} -d 1");
1048
	} else {
1049
		killbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1050
		@unlink("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1051
	}
1052

    
1053
	return $rules;
1054
}
1055

    
1056
function captiveportal_allowedip_configure() {
1057
	global $config, $g, $cpzone;
1058

    
1059
	$rules = "";
1060
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1061
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1062
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1063
	}
1064

    
1065
	return $rules;
1066
}
1067

    
1068
/* get last activity timestamp given client IP address */
1069
function captiveportal_get_last_activity($ip) {
1070
	global $cpzone;
1071

    
1072
	$ipfwoutput = pfSense_ipfw_getTablestats($cpzone, 1, $ip);
1073
	/* Reading only from one of the tables is enough of approximation. */
1074
	if (is_array($ipfwoutput)) {
1075
		return $ipfwoutput['timestamp'];
1076
	}
1077

    
1078
	return 0;
1079
}
1080

    
1081
function captiveportal_init_radius_servers() {
1082
	global $config, $g, $cpzone;
1083

    
1084
	/* generate radius server database */
1085
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1086
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1087
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1088
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1089
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1090
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1091

    
1092
		if ($config['captiveportal'][$cpzone]['radiusport'])
1093
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1094
		else
1095
			$radiusport = 1812;
1096
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1097
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1098
		else
1099
			$radiusacctport = 1813;
1100
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1101
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1102
		else
1103
			$radiusport2 = 1812;
1104
		if ($config['captiveportal'][$cpzone]['radiusport3'])
1105
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1106
		else
1107
			$radiusport3 = 1812;
1108
		if ($config['captiveportal'][$cpzone]['radiusport4'])
1109
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1110
		else
1111
			$radiusport4 = 1812;
1112

    
1113
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1114
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1115
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1116
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1117

    
1118
		$cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX);
1119
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
1120
		if (!$fd) {
1121
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1122
			unlock($cprdsrvlck);
1123
			return 1;
1124
		}
1125
		if (isset($radiusip))
1126
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
1127
		if (isset($radiusip2))
1128
			fwrite($fd,"\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
1129
		if (isset($radiusip3))
1130
			fwrite($fd,"\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
1131
		if (isset($radiusip4))
1132
			fwrite($fd,"\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
1133
		
1134

    
1135
		fclose($fd);
1136
		unlock($cprdsrvlck);
1137
	}
1138
}
1139

    
1140
/* read RADIUS servers into array */
1141
function captiveportal_get_radius_servers() {
1142
	global $g, $cpzone;
1143

    
1144
	$cprdsrvlck = lock("captiveportalradius{$cpzone}");
1145
	if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
1146
		$radiusservers = array();
1147
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", 
1148
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1149
		if ($cpradiusdb) {
1150
			foreach($cpradiusdb as $cpradiusentry) {
1151
				$line = trim($cpradiusentry);
1152
				if ($line) {
1153
					$radsrv = array();
1154
						list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key'], $context) = explode(",",$line);
1155
				}
1156
				if (empty($context)) {
1157
					if (!is_array($radiusservers['first']))
1158
						$radiusservers['first'] = array();
1159
					$radiusservers['first'] = $radsrv;
1160
				} else {
1161
					if (!is_array($radiusservers[$context]))
1162
						$radiusservers[$context] = array();
1163
					$radiusservers[$context][] = $radsrv;
1164
				}
1165
			}
1166
		}
1167
		unlock($cprdsrvlck);
1168
		return $radiusservers;
1169
	}
1170

    
1171
	unlock($cprdsrvlck);
1172
	return false;
1173
}
1174

    
1175
/* log successful captive portal authentication to syslog */
1176
/* part of this code from php.net */
1177
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1178
	// Log it
1179
	if (!$message)
1180
		$message = "$status: $user, $mac, $ip";
1181
	else {
1182
		$message = trim($message);
1183
		$message = "$status: $user, $mac, $ip, $message";
1184
	}
1185
	captiveportal_syslog($message);
1186
}
1187

    
1188
/* log simple messages to syslog */
1189
function captiveportal_syslog($message) {
1190
	$message = trim($message);
1191
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1192
	// Log it
1193
	syslog(LOG_INFO, $message);
1194
	closelog();
1195
}
1196

    
1197
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1198
	global $g, $config;
1199

    
1200
	
1201
	$radiusservers = captiveportal_get_radius_servers();
1202
    $pipeno = captiveportal_get_next_dn_ruleno();
1203

    
1204
	/* If the pool is empty, return appropriate message and fail authentication */
1205
	if (is_null($pipeno)) {
1206
		$auth_list = array();
1207
		$auth_list['auth_val'] = 1;
1208
		$auth_list['error'] = "System reached maximum login capacity";
1209
		return $auth_list;
1210
	}
1211

    
1212
	if (is_null($radiusctx))
1213
		$radiusctx = 'first';
1214
	    $auth_list = RADIUS_AUTHENTICATION($username,
1215
		$password,
1216
		$radiusservers[$radiusctx],
1217
		$clientip,
1218
		$clientmac,
1219
		$pipeno);
1220

    
1221
	if ($auth_list['auth_val'] == 2) {
1222
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1223
		$sessionid = portal_allow($clientip,
1224
			$clientmac,
1225
			$username,
1226
			$password,
1227
			$auth_list,
1228
			$pipeno,
1229
			$radiusctx);} 
1230
	else {
1231
	      captiveportal_free_dn_ruleno($pipeno);
1232
	     }
1233

    
1234
	return $auth_list;
1235
}
1236

    
1237
function captiveportal_opendb() {
1238
	global $g, $cpzone;
1239

    
1240
	if (file_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db"))
1241
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1242
	else {
1243
		$errormsg = "";
1244
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1245
		if (@sqlite_exec($DB, "CREATE TABLE captiveportal (allow_time INTEGER, pipeno INTEGER, ip TEXT, mac TEXT, username TEXT, sessionid TEXT, bpassword TEXT, session_timeout INTEGER, idle_timeout INTEGER, session_terminate_time INTEGER, interim_interval INTEGER) ", $errormsg)) {
1246
			@sqlite_exec($DB, "CREATE UNIQUE INDEX idx_active ON captiveportal (sessionid, username)");
1247
			@sqlite_exec($DB, "CREATE INDEX user ON captiveportal (username)");
1248
			@sqlite_exec($DB, "CREATE INDEX ip ON captiveportal (ip)");
1249
			@sqlite_exec($DB, "CREATE INDEX starttime ON captiveportal (allow_time)");
1250
			@sqlite_exec($DB, "CREATE INDEX serviceid ON captiveportal (serviceid)");
1251
		} else
1252
			captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$errormsg}");
1253
	}
1254

    
1255
	return $DB;
1256
}
1257

    
1258
/* read captive portal DB into array */
1259
function captiveportal_read_db($query = "") {
1260

    
1261
	$DB = captiveportal_opendb();
1262
	if ($DB) {
1263
		sqlite_exec($DB, "BEGIN");
1264
		if (!empty($query))
1265
			$cpdb = @sqlite_array_query($DB, "SELECT * FROM captiveportal {$query}", SQLITE_NUM);
1266
		else {
1267
			$response = @sqlite_unbuffered_query($DB, "SELECT * FROM captiveportal", SQLITE_NUM);
1268
			$cpdb = @sqlite_fetch_all($response, SQLITE_NUM);
1269
		}
1270
		sqlite_exec($DB, "END");
1271
		@sqlite_close($DB);
1272
	}
1273
	if (!$cpdb)
1274
		$cpdb = array();
1275

    
1276
	return $cpdb;
1277
}
1278

    
1279
function captiveportal_remove_entries($remove) {
1280

    
1281
	if (!is_array($remove) || empty($remove))
1282
		return;
1283

    
1284
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1285
	foreach($remove as $idx => $unindex) {
1286
		$query .= "'{$unindex}'";
1287
		if ($idx < (count($remove) - 1))
1288
			$query .= ",";
1289
	}
1290
	$query .= ")";
1291
	captiveportal_write_db($query);
1292
}
1293

    
1294
/* write captive portal DB */
1295
function captiveportal_write_db($queries) {
1296
	global $g;
1297

    
1298
	if (is_array($queries))
1299
		$query = implode(";", $queries);
1300
	else
1301
		$query = $queries;
1302

    
1303
	$DB = captiveportal_opendb();
1304
	if ($DB) {
1305
		$error_msg = "";
1306
		sqlite_exec($DB, "BEGIN TRANSACTION");
1307
		$result = @sqlite_exec($DB, $query, $error_msg);
1308
		if (!$result)
1309
			captiveportal_syslog("Trying to modify DB returned error: {$error_msg}");
1310
		else
1311
			sqlite_exec($DB, "END TRANSACTION");
1312
		@sqlite_close($DB);
1313
		return $result;
1314
	} else
1315
		return true;
1316
}
1317

    
1318
function captiveportal_write_elements() {
1319
	global $g, $config, $cpzone;
1320
	
1321
	$cpcfg = $config['captiveportal'][$cpzone];
1322

    
1323
	if (!is_dir($g['captiveportal_element_path']))
1324
		@mkdir($g['captiveportal_element_path']);
1325

    
1326
	if (is_array($cpcfg['element'])) {
1327
		conf_mount_rw();
1328
		foreach ($cpcfg['element'] as $data) {
1329
			if (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1330
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1331
				return 1;
1332
			}
1333
			if (!file_exists("{$g['captiveportal_path']}/{$data['name']}"))
1334
				@symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1335
		}
1336
		conf_mount_ro();
1337
	}
1338
	
1339
	return 0;
1340
}
1341

    
1342
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1343
	global $config, $g;
1344

    
1345
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1346
	$ruleno = 0;
1347
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1348
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1349
		for ($ridx = $rulenos_start; $ridx < $rulenos_range_max; $ridx++) {
1350
			if ($rules[$ridx]) {
1351
				$ridx++;
1352
				continue;
1353
			}
1354
			$ruleno = $ridx;
1355
			$rules[$ridx] = "used";
1356
			$rules[++$ridx] = "used";
1357
			break;
1358
		}
1359
	} else {
1360
		$rules = array_pad(array(), $rulenos_range_max, false);
1361
		$ruleno = $rulenos_start;
1362
		$rules[$rulenos_start] = "used";
1363
		$rules[++$rulenos_start] = "used";
1364
	}
1365
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1366
	unlock($cpruleslck);
1367

    
1368
	return $ruleno;
1369
}
1370

    
1371
function captiveportal_free_dn_ruleno($ruleno) {
1372
	global $config, $g;
1373

    
1374
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1375
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1376
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1377
		$rules[$ruleno] = false;
1378
		$rules[++$ruleno] = false;
1379
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1380
	}
1381
	unlock($cpruleslck);
1382
}
1383

    
1384
function captiveportal_get_dn_passthru_ruleno($value) {
1385
	global $config, $g, $cpzone;
1386

    
1387
	$cpcfg = $config['captiveportal'][$cpzone];
1388
	if(!isset($cpcfg['enable']))
1389
		return NULL;
1390

    
1391
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1392
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1393
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1394
		$ruleno = intval(`/sbin/ipfw -x {$cpzone} show | /usr/bin/grep {$value} |  /usr/bin/grep -v grep | /usr/bin/cut -d " " -f 5 | /usr/bin/head -n 1`);
1395
		if ($rules[$ruleno]) {
1396
			unlock($cpruleslck);
1397
			return $ruleno;
1398
		}
1399
	}
1400

    
1401
	unlock($cpruleslck);
1402
	return NULL;
1403
}
1404

    
1405
/*
1406
 * This function will calculate the lowest free firewall ruleno
1407
 * within the range specified based on the actual logged on users
1408
 *
1409
 */
1410
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1411
	global $config, $g, $cpzone;
1412

    
1413
	$cpcfg = $config['captiveportal'][$cpzone];
1414
	if(!isset($cpcfg['enable']))
1415
		return NULL;
1416

    
1417
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1418
	$ruleno = 0;
1419
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1420
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1421
		for ($ridx = 2; $ridx < ($rulenos_range_max - $rulenos_start); $ridx++) {
1422
			if ($rules[$ridx]) {
1423
				/* 
1424
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1425
				 * and the out pipe ruleno + 1.
1426
				 */
1427
				$ridx++;
1428
				continue;
1429
			}
1430
			$ruleno = $ridx;
1431
			$rules[$ridx] = "used";
1432
			$rules[++$ridx] = "used";
1433
			break;
1434
		}
1435
	} else {
1436
		$rules = array_pad(array(), $rulenos_range_max, false);
1437
		$rules[$rulenos_start] = "used";
1438
		$rules[++$rulenos_start] = "used";
1439
		$ruleno = 2;
1440
	}
1441
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1442
	unlock($cpruleslck);
1443
	return $ruleno;
1444
}
1445

    
1446
function captiveportal_free_ipfw_ruleno($ruleno) {
1447
	global $config, $g, $cpzone;
1448

    
1449
	$cpcfg = $config['captiveportal'][$cpzone];
1450
	if(!isset($cpcfg['enable']))
1451
		return NULL;
1452

    
1453
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1454
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1455
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1456
		$rules[$ruleno] = false;
1457
		$rules[++$ruleno] = false;
1458
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1459
	}
1460
	unlock($cpruleslck);
1461
}
1462

    
1463
function captiveportal_get_ipfw_passthru_ruleno($value) {
1464
	global $config, $g, $cpzone;
1465

    
1466
	$cpcfg = $config['captiveportal'][$cpzone];
1467
	if(!isset($cpcfg['enable']))
1468
		return NULL;
1469

    
1470
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1471
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1472
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1473
		$ruleno = intval(`/sbin/ipfw -x {$cpzone} show | /usr/bin/grep {$value} |  /usr/bin/grep -v grep | /usr/bin/cut -d " " -f 1 | /usr/bin/head -n 1`);
1474
		if ($rules[$ruleno]) {
1475
			unlock($cpruleslck);
1476
			return $ruleno;
1477
		}
1478
	}
1479

    
1480
	unlock($cpruleslck);
1481
	return NULL;
1482
}
1483

    
1484
/**
1485
 * This function will calculate the traffic produced by a client
1486
 * based on its firewall rule
1487
 *
1488
 * Point of view: NAS
1489
 *
1490
 * Input means: from the client
1491
 * Output means: to the client
1492
 *
1493
 */
1494

    
1495
function getVolume($ip) {
1496
	global $config, $cpzone;
1497

    
1498
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1499
	$volume = array();
1500
	// Initialize vars properly, since we don't want NULL vars
1501
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1502

    
1503
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 1, $ip);
1504
	if (is_array($ipfw)) {
1505
		if ($reverse) {
1506
			$volume['output_pkts'] = $ipfw['packets'];
1507
			$volume['output_bytes'] = $ipfw['bytes'];
1508
		}
1509
		else {
1510
			$volume['input_pkts'] = $ipfw['packets'];
1511
			$volume['input_bytes'] = $ipfw['bytes'];
1512
		}
1513
	}
1514

    
1515
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 2, $ip);
1516
	if (is_array($ipfw)) {
1517
		if ($reverse) {
1518
			$volume['input_pkts'] = $ipfw['packets'];
1519
			$volume['input_bytes'] = $ipfw['bytes'];
1520
		}
1521
		else {
1522
			$volume['output_pkts'] = $ipfw['packets'];
1523
			$volume['output_bytes'] = $ipfw['bytes'];
1524
		}
1525
	}
1526

    
1527
	return $volume;
1528
}
1529

    
1530
/**
1531
 * Get the NAS-IP-Address based on the current wan address
1532
 *
1533
 * Use functions in interfaces.inc to find this out
1534
 *
1535
 */
1536

    
1537
function getNasIP()
1538
{
1539
	global $config, $cpzone;
1540

    
1541
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1542
			$nasIp = get_interface_ip();
1543
	} else {
1544
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1545
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1546
		else
1547
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1548
	}
1549
		
1550
	if(!is_ipaddr($nasIp))
1551
		$nasIp = "0.0.0.0";
1552

    
1553
	return $nasIp;
1554
}
1555

    
1556
function portal_ip_from_client_ip($cliip) {
1557
	global $config, $cpzone;
1558

    
1559
	$isipv6 = is_ipaddrv6($cliip);
1560
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1561
	foreach ($interfaces as $cpif) {
1562
		if ($isipv6) {
1563
			$ip = get_interface_ipv6($cpif);
1564
			$sn = get_interface_subnetv6($cpif);
1565
		} else {
1566
			$ip = get_interface_ip($cpif);
1567
			$sn = get_interface_subnet($cpif);
1568
		}
1569
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1570
			return $ip;
1571
	}
1572

    
1573
	$inet = ($isipv6) ? '-inet6' : '-inet';
1574
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1575
	$iface = trim($iface, "\n");
1576
	if (!empty($iface)) {
1577
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1578
		if (is_ipaddr($ip))
1579
			return $ip;
1580
	}
1581

    
1582
	// doesn't match up to any particular interface
1583
	// so let's set the portal IP to what PHP says 
1584
	// the server IP issuing the request is. 
1585
	// allows same behavior as 1.2.x where IP isn't 
1586
	// in the subnet of any CP interface (static routes, etc.)
1587
	// rather than forcing to DNS hostname resolution
1588
	$ip = $_SERVER['SERVER_ADDR'];
1589
	if (is_ipaddr($ip))
1590
		return $ip;
1591

    
1592
	return false;
1593
}
1594

    
1595
function portal_hostname_from_client_ip($cliip) {
1596
	global $config, $cpzone;
1597

    
1598
	$cpcfg = $config['captiveportal'][$cpzone];
1599

    
1600
	if (isset($cpcfg['httpslogin'])) {
1601
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
1602
		$ourhostname = $cpcfg['httpsname'];
1603
		
1604
		if ($listenporthttps != 443)
1605
			$ourhostname .= ":" . $listenporthttps;
1606
	} else {
1607
		$listenporthttp  = $cpcfg['listenporthttp']  ? $cpcfg['listenporthttp']  : $cpcfg['zoneid'];
1608
		$ifip = portal_ip_from_client_ip($cliip);
1609
		if (!$ifip)
1610
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1611
		else
1612
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1613
		
1614
		if ($listenporthttp != 80)
1615
			$ourhostname .= ":" . $listenporthttp;
1616
	}
1617
	
1618
	return $ourhostname;
1619
}
1620

    
1621
/* functions move from index.php */
1622

    
1623
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1624
	global $g, $config, $cpzone;
1625

    
1626
	/* Get captive portal layout */
1627
	if ($type == "redir") {
1628
		header("Location: {$redirurl}");
1629
		return;
1630
	} else if ($type == "login")
1631
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1632
	else
1633
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1634

    
1635
	$cpcfg = $config['captiveportal'][$cpzone];
1636

    
1637
	/* substitute the PORTAL_REDIRURL variable */
1638
	if ($cpcfg['preauthurl']) {
1639
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1640
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1641
	}
1642

    
1643
	/* substitute other variables */
1644
	$ourhostname = portal_hostname_from_client_ip($clientip);
1645
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1646
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1647
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1648

    
1649
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1650
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1651
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1652
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1653
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1654

    
1655
	// Special handling case for captive portal master page so that it can be ran 
1656
	// through the PHP interpreter using the include method above.  We convert the
1657
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1658
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1659
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1660
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1661
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1662
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1663
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1664
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1665

    
1666
	echo $htmltext;
1667
}
1668

    
1669
function portal_mac_radius($clientmac,$clientip) {
1670
	global $config, $cpzone;
1671

    
1672
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1673

    
1674
	/* authentication against the radius server */
1675
	$username = mac_format($clientmac);
1676
	$auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1677
	if ($auth_list['auth_val'] == 2)
1678
		return TRUE;
1679

    
1680
	if (!empty($auth_list['url_redirection']))
1681
		portal_reply_page($auth_list['url_redirection'], "redir");
1682

    
1683
	return FALSE;
1684
}
1685

    
1686
function captiveportal_reapply_attributes($cpentry, $attributes) {
1687
	global $config, $cpzone, $g;
1688

    
1689
	$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1690
	$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1691
	$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1692
	$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1693
	$bw_up_pipeno = $cpentry[1];
1694
	$bw_down_pipeno = $cpentry[1]+1;
1695

    
1696
	$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1697
	$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1698
	//captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_BANDWIDTH_REAPPLY", "{$bw_up}/{$bw_down}");
1699

    
1700
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1701
}
1702

    
1703
function portal_allow($clientip,$clientmac,$username,$password = null, $attributes = null, $pipeno = null, $radiusctx = null)  {
1704
	global $redirurl, $g, $config, $type, $passthrumac, $_POST, $cpzone;
1705

    
1706
	// Ensure we create an array if we are missing attributes
1707
	if (!is_array($attributes))
1708
		$attributes = array();
1709

    
1710
	unset($sessionid);
1711

    
1712
	/* Do not allow concurrent login execution. */
1713
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1714

    
1715
	if ($attributes['voucher'])
1716
		$remaining_time = $attributes['session_timeout'];
1717

    
1718
	$writecfg = false;
1719
	/* Find an existing session */
1720
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
1721
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1722
			$mac = captiveportal_passthrumac_findbyname($username);
1723
			if (!empty($mac)) {
1724
				if ($_POST['replacemacpassthru']) {
1725
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
1726
						if ($macent['mac'] == $mac['mac']) {
1727
							$macrules = "";
1728
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1729
							$pipeno = captiveportal_get_dn_passthru_ruleno($mac['mac']);
1730
							if ($ruleno) {
1731
								captiveportal_free_ipfw_ruleno($ruleno);
1732
								$macrules .= "delete {$ruleno}\n";
1733
								++$ruleno;
1734
								$macrules .= "delete {$ruleno}\n";
1735
							}
1736
							if ($pipeno) {
1737
								captiveportal_free_dn_ruleno($pipeno);
1738
								$macrules .= "pipe delete {$pipeno}\n";
1739
								++$pipeno;
1740
								$macrules .= "pipe delete {$pipeno}\n";
1741
							}
1742
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1743
							$mac['mac'] = $clientmac;
1744
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1745
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1746
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1747
							mwexec("/sbin/ipfw -x {$cpzone} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1748
							$writecfg = true;
1749
							$sessionid = true;
1750
							break;
1751
						}
1752
					}
1753
				} else {
1754
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1755
						$clientmac, $clientip, $username, $password);
1756
					unlock($cpdblck);
1757
					return;
1758
				}
1759
			}
1760
		}
1761
	}
1762

    
1763
	/* read in client database */
1764
	$query = "WHERE ip = '{$clientip}'";
1765
	$tmpusername = strtolower($username);
1766
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins']))
1767
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
1768
	$cpdb = captiveportal_read_db($query);
1769

    
1770
	/* Snapshot the timestamp */
1771
	$allow_time = time();
1772
	$radiusservers = captiveportal_get_radius_servers();
1773
	$unsetindexes = array();
1774
	if (is_null($radiusctx))
1775
		$radiusctx = 'first';
1776

    
1777
	foreach ($cpdb as $cpentry) {
1778
		if (empty($cpentry[10]))
1779
			$cpentry[10] = 'first';
1780
		/* on the same ip */
1781
		if ($cpentry[2] == $clientip) {
1782
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac)
1783
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1784
			else
1785
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
1786
			$sessionid = $cpentry[5];
1787
			break;
1788
		}
1789
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1790
			// user logged in with an active voucher. Check for how long and calculate 
1791
			// how much time we can give him (voucher credit - used time)
1792
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1793
			if ($remaining_time < 0)    // just in case. 
1794
				$remaining_time = 0;
1795

    
1796
			/* This user was already logged in so we disconnect the old one */
1797
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1798
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1799
			$unsetindexes[] = $cpentry[5];
1800
			break;
1801
		}
1802
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1803
			/* on the same username */
1804
			if (strcasecmp($cpentry[4], $username) == 0) {
1805
				/* This user was already logged in so we disconnect the old one */
1806
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1807
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1808
				$unsetindexes[] = $cpentry[5];
1809
				break;
1810
			}
1811
		}
1812
	}
1813
	unset($cpdb);
1814

    
1815
	if (!empty($unsetindexes))
1816
		captiveportal_remove_entries($unsetindexes);
1817

    
1818
	if ($attributes['voucher'] && $remaining_time <= 0)
1819
		return 0;       // voucher already used and no time left
1820

    
1821
	if (!isset($sessionid)) {
1822
		/* generate unique session ID */
1823
		$tod = gettimeofday();
1824
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1825

    
1826
		if ($passthrumac) {
1827
			$mac = array();
1828
			$mac['mac'] = $clientmac;
1829
			$mac['ip'] = $clientip; /* Used only for logging */
1830
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
1831
				$mac['username'] = $username;
1832
				if ($attributes['voucher'])
1833
					$mac['logintype'] = "voucher";
1834
			}
1835
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1836
			if (!empty($bw_up))
1837
				$mac['bw_up'] = $bw_up;
1838
			if (!empty($bw_down))
1839
				$mac['bw_down'] = $bw_down;
1840
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
1841
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
1842
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1843
			unlock($cpdblck);
1844
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1845
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1846
			mwexec("/sbin/ipfw -x {$cpzone}-q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1847
			$writecfg = true;
1848
		} else {
1849
			/* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
1850
			if (is_null($pipeno))
1851
				$pipeno = captiveportal_get_next_dn_ruleno();
1852

    
1853
			/* if the pool is empty, return appropriate message and exit */
1854
			if (is_null($pipeno)) {
1855
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1856
				log_error("WARNING!  Captive portal has reached maximum login capacity");
1857
				unlock($cpdblck);
1858
				return;
1859
			}
1860

    
1861
			$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1862
			$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1863
			$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1864
			$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1865

    
1866
			$bw_up_pipeno = $pipeno;
1867
			$bw_down_pipeno = $pipeno + 1;
1868
			//$bw_up /= 1000; // Scale to Kbit/s
1869
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1870
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1871

    
1872
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
1873
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1874
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
1875
			else
1876
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
1877

    
1878
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1879
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
1880
			else
1881
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
1882

    
1883
			if ($attributes['voucher'])
1884
				$attributes['session_timeout'] = $remaining_time;
1885
			
1886
			/* handle empty attributes */
1887
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
1888
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
1889
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
1890
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
1891

    
1892
			/* escape username */
1893
			$safe_username = sqlite_escape_string($username);
1894

    
1895
			/* encode password in Base64 just in case it contains commas */
1896
			$bpassword = base64_encode($password);
1897
			$insertquery  = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval) ";
1898
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
1899
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval})";
1900

    
1901
			/* store information to database */
1902
			captiveportal_write_db($insertquery);
1903
			unlock($cpdblck);
1904

    
1905
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
1906
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
1907
				if ($acct_val == 1)
1908
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1909
			}
1910
		}
1911
	} else
1912
		unlock($cpdblck);
1913

    
1914
	if ($writecfg == true)
1915
		write_config();
1916

    
1917
	/* redirect user to desired destination */
1918
	if (!empty($attributes['url_redirection']))
1919
		$my_redirurl = $attributes['url_redirection'];
1920
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
1921
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
1922
	else
1923
		$my_redirurl = $redirurl;
1924

    
1925
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
1926
		$ourhostname = portal_hostname_from_client_ip($clientip);
1927
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
1928
		$logouturl = "{$protocol}{$ourhostname}/";
1929

    
1930
		if (isset($attributes['reply_message']))
1931
			$message = $attributes['reply_message'];
1932
		else
1933
			$message = 0;
1934

    
1935
		include("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
1936

    
1937
	} else {
1938
		header("Location: " . $my_redirurl);
1939
	}
1940

    
1941
	return $sessionid;
1942
}
1943

    
1944

    
1945
/*
1946
 * Used for when pass-through credits are enabled.
1947
 * Returns true when there was at least one free login to deduct for the MAC.
1948
 * Expired entries are removed as they are seen.
1949
 * Active entries are updated according to the configuration.
1950
 */
1951
function portal_consume_passthrough_credit($clientmac) {
1952
	global $config, $cpzone;
1953

    
1954
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
1955
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
1956
	else
1957
		return false;
1958

    
1959
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
1960
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
1961
	else
1962
		return false;
1963

    
1964
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1965
		return false;
1966

    
1967
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
1968

    
1969
	/*
1970
	 * Read database of used MACs.  Lines are a comma-separated list
1971
	 * of the time, MAC, then the count of pass-through credits remaining.
1972
	 */
1973
	$usedmacs = captiveportal_read_usedmacs_db();
1974

    
1975
	$currenttime = time();
1976
	$found = false;
1977
	foreach ($usedmacs as $key => $usedmac) {
1978
		$usedmac = explode(",", $usedmac);
1979

    
1980
		if ($usedmac[1] == $clientmac) {
1981
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
1982
				if ($usedmac[2] < 1) {
1983
					if ($updatetimeouts) {
1984
						$usedmac[0] = $currenttime;
1985
						unset($usedmacs[$key]);
1986
						$usedmacs[] = implode(",", $usedmac);
1987
						captiveportal_write_usedmacs_db($usedmacs);
1988
					}
1989

    
1990
					return false;
1991
				} else {
1992
					$usedmac[2] -= 1;
1993
					$usedmacs[$key] = implode(",", $usedmac);
1994
				}
1995

    
1996
				$found = true;
1997
			} else
1998
				unset($usedmacs[$key]);
1999

    
2000
			break;
2001
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2002
				unset($usedmacs[$key]);
2003
	}
2004

    
2005
	if (!$found) {
2006
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2007
		$usedmacs[] = implode(",", $usedmac);
2008
	}
2009

    
2010
	captiveportal_write_usedmacs_db($usedmacs);
2011
	return true;
2012
}
2013

    
2014
function captiveportal_read_usedmacs_db() {
2015
	global $g, $cpzone;
2016

    
2017
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2018
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2019
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2020
		if (!$usedmacs)
2021
			$usedmacs = array();
2022
	} else
2023
		$usedmacs = array();
2024

    
2025
	unlock($cpumaclck);
2026
	return $usedmacs;
2027
}
2028

    
2029
function captiveportal_write_usedmacs_db($usedmacs) {
2030
	global $g, $cpzone;
2031

    
2032
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2033
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2034
	unlock($cpumaclck);
2035
}
2036

    
2037
function captiveportal_send_server_accounting($off = false) {
2038
	global $cpzone, $config;
2039

    
2040
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
2041
		return;
2042
	}
2043
	if ($off) {
2044
		$racct = new Auth_RADIUS_Acct_Off;
2045
	} else {
2046
		$racct = new Auth_RADIUS_Acct_On;
2047
	}
2048
	$radiusservers = captiveportal_get_radius_servers();
2049
	if (empty($radiusservers)) {
2050
		return;
2051
	}
2052
	foreach ($radiusservers['first'] as $radsrv) {
2053
		// Add a new server to our instance
2054
		$racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
2055
	}
2056
	if (PEAR::isError($racct->start())) {
2057
		$retvalue['acct_val'] = 1;
2058
		$retvalue['error'] = $racct->getMessage();
2059

    
2060
		// If we encounter an error immediately stop this function and go back
2061
		$racct->close();
2062
		return $retvalue;
2063
	}
2064
	// Send request
2065
	$result = $racct->send();
2066
	// Evaluation of the response
2067
	// 5 -> Accounting-Response
2068
	// See RFC2866 for this.
2069
	if (PEAR::isError($result)) {
2070
		$retvalue['acct_val'] = 1;
2071
		$retvalue['error'] = $result->getMessage();
2072
	} else if ($result === true) {
2073
		$retvalue['acct_val'] = 5 ;
2074
	} else {
2075
		$retvalue['acct_val'] = 1 ;
2076
	}
2077

    
2078
	$racct->close();
2079
	return $retvalue;
2080
}
2081
?>
(2-2/2)