Project

General

Profile

Download (72.5 KB) Statistics
| Branch: | Tag: | Revision:
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
	}
157
	/* Activate layer2 filtering */
158
	mwexec("/sbin/sysctl net.link.ether.ipfw=1 net.inet.ip.fw.one_pass=1");
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, $cpzoneid;
178

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
310
EOD;
311
		}
312

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

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

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

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

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

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

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

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

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

    
356
		captiveportal_radius_stop_all();
357

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

    
363
		/* remove old information */
364
		unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
365
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
366
		unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
367
		/* Release allocated pipes for this zone */
368
		captiveportal_free_dnrules();
369

    
370
		mwexec("/usr/local/sbin/ipfw zone {$cpzoneid} destory", true);
371

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

    
391
	unlock($captiveportallck);
392
	
393
	return 0;
394
}
395

    
396
function captiveportal_init_webgui() {
397
	global $config, $cpzone;
398

    
399
	if (is_array($config['captiveportal'])) {
400
		foreach ($config['captiveportal'] as $cpkey => $cp) {
401
			$cpzone = $cpkey;
402
			captiveportal_init_webgui_zone($cp);
403
		}
404
	}
405
}
406

    
407
function captiveportal_init_webgui_zonename($zone) {
408
	global $config, $cpzone;
409
	
410
	if (isset($config['captiveportal'][$zone])) {
411
		$cpzone = $zone;
412
		captiveportal_init_webgui_zone($config['captiveportal'][$zone]);
413
	}
414
}
415

    
416
function captiveportal_init_webgui_zone($cpcfg) {
417
	global $g, $config, $cpzone;
418

    
419
	if (!isset($cpcfg['enable']))
420
		return;
421

    
422
	if (isset($cpcfg['httpslogin'])) {
423
		$cert = lookup_cert($cpcfg['certref']);
424
		$crt = base64_decode($cert['crt']);
425
		$key = base64_decode($cert['prv']);
426
		$ca = ca_chain($cert);
427

    
428
		/* generate lighttpd configuration */
429
		if (!empty($cpcfg['listenporthttps']))
430
			$listenporthttps = $cpcfg['listenporthttps'];
431
		else
432
			$listenporthttps = 8001 + $cpcfg['zoneid'];
433
		system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf",
434
			$crt, $key, $ca, "lighty-{$cpzone}-CaptivePortal-SSL.pid", $listenporthttps, "/usr/local/captiveportal",
435
			"cert-{$cpzone}-portal.pem", "ca-{$cpzone}-portal.pem", $cpzone);
436
	}
437

    
438
	/* generate lighttpd configuration */
439
	$listenporthttp = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : $cpcfg['zoneid'];
440
	if (!empty($cpcfg['listenporthttp']))
441
		$listenporthttp = $cpcfg['listenporthttp'];
442
	else
443
		$listenporthttp = 8000 + $cpcfg['zoneid'];
444
	system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal.conf",
445
		"", "", "", "lighty-{$cpzone}-CaptivePortal.pid", $listenporthttp, "/usr/local/captiveportal",
446
		"", "", $cpzone);
447

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

    
452
	/* fire up https instance */
453
	if (isset($cpcfg['httpslogin'])) {
454
		@unlink("{$g['varrun']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
455
		$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf");
456
	}
457
}
458

    
459
/* reinit will disconnect all users, be careful! */
460
function captiveportal_init_rules($reinit = false) {
461
	global $config, $g, $cpzone, $cpzoneid;
462

    
463
	if (!isset($config['captiveportal'][$cpzone]['enable']))
464
		return;
465

    
466
	captiveportal_load_modules();
467
	mwexec("/usr/local/sbin/ipfw zone {$cpzoneid} create", true);
468

    
469
	$cpips = array();
470
	$ifaces = get_configured_interface_list();
471
	$cpinterfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
472
	$firsttime = 0;
473
	foreach ($cpinterfaces as $cpifgrp) {
474
		if (!isset($ifaces[$cpifgrp]))
475
			continue;
476
		$tmpif = get_real_interface($cpifgrp);
477
		if (!empty($tmpif)) {
478
			$cpipm = get_interface_ip($cpifgrp);
479
			if (is_ipaddr($cpipm)) {
480
				$carpif = link_ip_to_carp_interface($cpipm);
481
				if (!empty($carpif)) {
482
					$carpsif = explode(" ", $carpif);
483
					foreach ($carpsif as $cpcarp) {
484
						mwexec("/usr/local/sbin/ipfw zone {$cpzoneid} madd {$cpcarp}", true);
485
						$carpip = find_interface_ip($cpcarp);
486
						if (is_ipaddr($carpip))
487
							$cpips[] = $carpip;
488
					}
489
				}
490
				$cpips[] = $cpipm;
491
			}
492
			mwexec("/usr/local/sbin/ipfw zone {$cpzoneid} madd {$tmpif}", true);
493
		}
494
	}
495
	if (count($cpips) > 0) {
496
		$cpactive = true;
497
	} else
498
		return false;
499

    
500
	if ($reinit == false)
501
		$captiveportallck = lock("captiveportal{$cpzone}");
502

    
503
	$cprules =	"add 65291 allow pfsync from any to any\n";
504
	$cprules .= "add 65292 allow carp from any to any\n";
505

    
506
	$cprules .= <<<EOD
507
# layer 2: pass ARP
508
add 65301 pass layer2 mac-type arp,rarp
509
# pfsense requires for WPA
510
add 65302 pass layer2 mac-type 0x888e,0x88c7
511
# PPP Over Ethernet Session Stage/Discovery Stage
512
add 65303 pass layer2 mac-type 0x8863,0x8864
513

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

    
517
EOD;
518

    
519
	$rulenum = 65310;
520
	$ipcount = 0;
521
	$ips = "";
522
	foreach ($cpips as $cpip) {
523
		if($ipcount == 0) {
524
			$ips = "{$cpip} ";
525
		} else {
526
			$ips .= "or {$cpip} ";
527
		}
528
		$ipcount++;
529
	}
530
	$ips = "{ 255.255.255.255 or {$ips} }";
531
	$cprules .= "add {$rulenum} pass ip from any to {$ips} in\n";
532
	$rulenum++;
533
	$cprules .= "add {$rulenum} pass ip from {$ips} to any out\n";
534
	$rulenum++;
535
	$cprules .= "add {$rulenum} pass icmp from {$ips} to any out icmptype 0\n";
536
	$rulenum++;
537
	$cprules .= "add {$rulenum} pass icmp from any to {$ips} in icmptype 8 \n";
538
	$rulenum++;
539
	/* Allowed ips */
540
	$cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any in\n";
541
	$rulenum++;
542
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) in\n";
543
	$rulenum++;
544
	$cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any out\n";
545
	$rulenum++;
546
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) out\n";
547
	$rulenum++;
548

    
549
	/* Authenticated users rules. */
550
	$cprules .= "add {$rulenum} pipe tablearg ip from table(1) to any in\n";
551
	$rulenum++;
552
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(2) out\n";
553
	$rulenum++;
554

    
555
	if (!empty($$config['captiveportal'][$cpzone]['listenporthttp']))
556
		$listenporthttp = $$config['captiveportal'][$cpzone]['listenporthttp'];
557
	else
558
		$listenporthttp = 8000 + $$config['captiveportal'][$cpzone]['zoneid'];
559

    
560
	if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
561
		if (!empty($config['captiveportal'][$cpzone]['listenporthttps']))
562
			$listenporthttps = $config['captiveportal'][$cpzone]['listenporthttps'];
563
		else
564
			$listenporthttps = 8001 + $$config['captiveportal'][$cpzone]['zoneid'];
565
		$cprules .= "add 65531 fwd 127.0.0.1,{$listenporthttps} tcp from any to any dst-port 443 in\n";
566
	}
567
	
568
	$cprules .= <<<EOD
569

    
570
# redirect non-authenticated clients to captive portal
571
add 65532 fwd 127.0.0.1,{$listenporthttp} tcp from any to any dst-port 80 in 
572
# let the responses from the captive portal web server back out
573
add 65533 pass tcp from any to any out
574
# block everything else
575
add 65534 deny all from any to any
576

    
577
EOD;
578

    
579
	/* generate passthru mac database */
580
	$cprules .= captiveportal_passthrumac_configure(true);
581
	$cprules .= "\n";
582

    
583
	/* allowed ipfw rules to make allowed ip work */
584
	$cprules .= captiveportal_allowedip_configure();
585

    
586
	/* allowed ipfw rules to make allowed hostnames work */
587
	$cprules .= captiveportal_allowedhostname_configure();
588
	
589
	/* load rules */
590
	$cprules = "flush\n{$cprules}";
591
	file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
592
	mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
593
	//@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
594
	unset($cprules, $tmprules);
595

    
596
	if ($reinit == false)
597
		unlock($captiveportallck);
598
}
599

    
600
/* 
601
 * Remove clients that have been around for longer than the specified amount of time
602
 * db file structure:
603
 * timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time,interim_interval
604
 * (password is in Base64 and only saved when reauthentication is enabled)
605
 */
606
function captiveportal_prune_old() {
607
	global $g, $config, $cpzone, $cpzoneid;
608

    
609
	if (empty($cpzone))
610
		return;
611

    
612
	$cpcfg = $config['captiveportal'][$cpzone];
613
	$vcpcfg = $config['voucher'][$cpzone];
614

    
615
	/* check for expired entries */
616
	$idletimeout = 0;
617
	$timeout = 0;
618
	if (!empty($cpcfg['timeout']) && is_numeric($cpcfg['timeout']))
619
		$timeout = $cpcfg['timeout'] * 60;
620

    
621
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout']))
622
		$idletimeout = $cpcfg['idletimeout'] * 60;
623

    
624
	/* Is there any job to do? */
625
	if (!$timeout && !$idletimeout && !isset($cpcfg['reauthenticate']) &&
626
	    !isset($cpcfg['radiussession_timeout']) && !isset($vcpcfg['enable']))
627
		return;
628

    
629
	$radiussrvs = captiveportal_get_radius_servers();
630

    
631
	/* Read database */
632
	/* NOTE: while this can be simplified in non radius case keep as is for now */
633
	$cpdb = captiveportal_read_db();
634

    
635
	/*
636
	 * To make sure we iterate over ALL accounts on every run the count($cpdb) is moved
637
	 * outside of the loop. Otherwise the loop would evaluate count() on every iteration
638
	 * and since $i would increase and count() would decrement they would meet before we
639
	 * had a chance to iterate over all accounts.
640
	 */
641
	$unsetindexes = array();
642
	$voucher_needs_sync = false;
643
	/* 
644
	 * Snapshot the time here to use for calculation to speed up the process.
645
	 * If something is missed next run will catch it!
646
	 */
647
	$pruning_time = time();
648
	$stop_time = $pruning_time;
649
	foreach ($cpdb as $cpentry) {
650

    
651
		$timedout = false;
652
		$term_cause = 1;
653
		if (empty($cpentry[10]))
654
			$cpentry[10] = 'first';
655
		$radiusservers = $radiussrvs[$cpentry[10]];
656

    
657
		/* hard timeout? */
658
		if ($timeout) {
659
			if (($pruning_time - $cpentry[0]) >= $timeout) {
660
				$timedout = true;
661
				$term_cause = 5; // Session-Timeout
662
			}
663
		}
664

    
665
		/* Session-Terminate-Time */
666
		if (!$timedout && !empty($cpentry[9])) {
667
			if ($pruning_time >= $cpentry[9]) {
668
				$timedout = true;
669
				$term_cause = 5; // Session-Timeout
670
			}
671
		}
672

    
673
		/* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
674
		$uidletimeout = (is_numeric($cpentry[8])) ? $cpentry[8] : $idletimeout;
675
		/* if an idle timeout is specified, get last activity timestamp from ipfw */
676
		if (!$timedout && $uidletimeout > 0) {
677
			$lastact = captiveportal_get_last_activity($cpentry[2], $cpentry[3]);
678
			/*	If the user has logged on but not sent any traffic they will never be logged out.
679
			 *	We "fix" this by setting lastact to the login timestamp. 
680
			 */
681
			$lastact = $lastact ? $lastact : $cpentry[0];
682
			if ($lastact && (($pruning_time - $lastact) >= $uidletimeout)) {
683
				$timedout = true;
684
				$term_cause = 4; // Idle-Timeout
685
				$stop_time = $lastact; // Entry added to comply with WISPr
686
			}
687
		}
688

    
689
		/* if vouchers are configured, activate session timeouts */
690
		if (!$timedout && isset($vcpcfg['enable']) && !empty($cpentry[7])) {
691
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
692
				$timedout = true;
693
				$term_cause = 5; // Session-Timeout
694
				$voucher_needs_sync = true;
695
			}
696
		}
697

    
698
		/* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
699
		if (!$timedout && isset($cpcfg['radiussession_timeout']) && !empty($cpentry[7])) {
700
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
701
				$timedout = true;
702
				$term_cause = 5; // Session-Timeout
703
			}
704
		}
705

    
706
		if ($timedout) {
707
			captiveportal_disconnect($cpentry, $radiusservers,$term_cause,$stop_time);
708
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
709
			$unsetindexes[] = $cpentry[5];
710
		}
711

    
712
		/* do periodic RADIUS reauthentication? */
713
		if (!$timedout && !empty($radiusservers)) {
714
			if (isset($cpcfg['radacct_enable'])) {
715
				if ($cpcfg['reauthenticateacct'] == "stopstart") {
716
					/* stop and restart accounting */
717
					RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
718
						$cpentry[4], // username
719
						$cpentry[5], // sessionid
720
						$cpentry[0], // start time
721
						$radiusservers,
722
						$cpentry[2], // clientip
723
						$cpentry[3], // clientmac
724
						10); // NAS Request
725
					$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ZERO_ENTRY_STATS, 1, $cpentry[2], $cpentry[3]);
726
					$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ZERO_ENTRY_STATS, 2, $cpentry[2], $cpentry[3]);
727
					RADIUS_ACCOUNTING_START($cpentry[1], // ruleno
728
						$cpentry[4], // username
729
						$cpentry[5], // sessionid
730
						$radiusservers,
731
						$cpentry[2], // clientip
732
						$cpentry[3]); // clientmac
733
				} else if ($cpcfg['reauthenticateacct'] == "interimupdate") {
734
					$session_time = $pruning_time - $cpentry[0];
735
					if (!empty($cpentry[10]) && $cpentry[10] > 60)
736
						$interval = $cpentry[10];
737
					else
738
						$interval = 0;
739
					$past_interval_min = ($session_time > $interval);
740
					if ($interval != 0)
741
						$within_interval = ($session_time % $interval >= 0 && $session_time % $interval <= 59);
742
					if ($interval === 0 || ($interval > 0 && $past_interval_min && $within_interval)) {
743
						RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
744
							$cpentry[4], // username
745
							$cpentry[5], // sessionid
746
							$cpentry[0], // start time
747
							$radiusservers,
748
							$cpentry[2], // clientip
749
							$cpentry[3], // clientmac
750
							10, // NAS Request
751
							true); // Interim Updates
752
					}
753
				}
754
			}
755

    
756
			/* check this user against RADIUS again */
757
			if (isset($cpcfg['reauthenticate'])) {
758
				$auth_list = RADIUS_AUTHENTICATION($cpentry[4], // username
759
					base64_decode($cpentry[6]), // password
760
					$radiusservers,
761
					$cpentry[2], // clientip
762
					$cpentry[3], // clientmac
763
					$cpentry[1]); // ruleno
764
				if ($auth_list['auth_val'] == 3) {
765
					captiveportal_disconnect($cpentry, $radiusservers, 17);
766
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
767
					$unsetindexes[] = $cpentry[5];
768
				} else if ($auth_list['auth_val'] == 2)
769
					captiveportal_reapply_attributes($cpentry, $auth_list);
770
			}
771
		}
772
	}
773
	unset($cpdb);
774

    
775
	captiveportal_prune_old_automac();
776

    
777
	if ($voucher_needs_sync == true)
778
		/* Triger a sync of the vouchers on config */
779
		send_event("service sync vouchers");
780

    
781
	/* write database */
782
	if (!empty($unsetindexes))
783
		captiveportal_remove_entries($unsetindexes);
784
}
785

    
786
function captiveportal_prune_old_automac() {
787
	global $g, $config, $cpzone, $cpzoneid;
788

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

    
847
/* remove a single client according to the DB entry */
848
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
849
	global $g, $config, $cpzone;
850

    
851
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
852

    
853
	/* this client needs to be deleted - remove ipfw rules */
854
	if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers)) {
855
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
856
			$dbent[4], // username
857
			$dbent[5], // sessionid
858
			$dbent[0], // start time
859
			$radiusservers,
860
			$dbent[2], // clientip
861
			$dbent[3], // clientmac
862
			$term_cause, // Acct-Terminate-Cause
863
			false,
864
			$stop_time);
865
	}
866
	
867
	if (is_ipaddr($dbent[2])) {
868
		/* Delete client's ip entry from tables 1 and 2. */
869
		$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_DEL, 1, $dbent[2], $dbent[3]);
870
		$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_DEL, 2, $dbent[2], $dbent[3]);
871
		/* XXX: Redundant?! Ensure all pf(4) states are killed. */
872
		$_gb = @pfSense_kill_states($dbent[2]);
873
		$_gb = @pfSense_kill_srcstates($dbent[2]);
874
	}
875

    
876
	/* 
877
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
878
	* We could get an error if the pipe doesn't exist but everything should still be fine
879
	*/
880
	if (!empty($dbent[1])) {
881
		$_gb = @pfSense_pipe_action("pipe delete {$dbent[1]}");
882
		$_gb = @pfSense_pipe_action("pipe delete " . ($dbent[1]+1));
883

    
884
		/* Release the ruleno so it can be reallocated to new clients. */
885
		captiveportal_free_dn_ruleno($dbent[1]);
886
	}
887

    
888
	// XMLRPC Call over to the master Voucher node
889
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
890
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
891
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
892
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
893
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
894
		$remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time);
895
	}
896

    
897
}
898

    
899
/* remove a single client by sessionid */
900
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
901
	global $g, $config;
902

    
903
	$radiusservers = captiveportal_get_radius_servers();
904

    
905
	/* read database */
906
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
907

    
908
	/* find entry */
909
	if (!empty($result)) {
910
		captiveportal_write_db("DELETE FROM captiveportal WHERE sessionid = '{$sessionid}'");
911

    
912
		foreach ($result as $cpentry) {
913
			if (empty($cpentry[10]))
914
				$cpentry[10] = 'first';
915
			captiveportal_disconnect($cpentry, $radiusservers[$cpentry[10]], $term_cause);
916
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
917
		}
918
		unset($result);
919
	}
920
}
921

    
922
/* send RADIUS acct stop for all current clients */
923
function captiveportal_radius_stop_all() {
924
	global $config, $cpzone;
925

    
926
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable']))
927
		return;
928

    
929
	$radiusservers = captiveportal_get_radius_servers();
930
	if (!empty($radiusservers)) {
931
		$cpdb = captiveportal_read_db();
932
		foreach ($cpdb as $cpentry) {
933
			if (empty($cpentry[10]))
934
				$cpentry[10] = 'first';
935
			if (!empty($radiusservers[$cpentry[10]])) {
936
				RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
937
					$cpentry[4], // username
938
					$cpentry[5], // sessionid
939
					$cpentry[0], // start time
940
					$radiusservers[$cpentry[10]],
941
					$cpentry[2], // clientip
942
					$cpentry[3], // clientmac
943
					7); // Admin Reboot
944
			}
945
		}
946
	}
947
}
948

    
949
function captiveportal_passthrumac_configure_entry($macent) {
950
	global $config, $g, $cpzone;
951

    
952
	$bwUp = empty($macent['bw_up']) ? 0 : $macent['bw_up'];
953
	$bwDown = empty($macent['bw_down']) ? 0 : $macent['bw_down'];
954

    
955
	$ruleno = captiveportal_get_next_ipfw_ruleno();
956

    
957
	if ($macent['action'] == 'pass') {
958
		$pipeno = captiveportal_get_next_dn_ruleno();
959

    
960
		$pipeup = $pipeno;
961
		$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16");
962
		$pipedown = $pipeno + 1;
963
		$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
964

    
965
		$rules = "add {$ruleno} pipe {$pipeup} ip from any to any MAC any {$macent['mac']}\n";
966
		$ruleno++;
967
		$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC {$macent['mac']} any\n";
968
	}
969

    
970
	return $rules;
971
}
972

    
973
function captiveportal_passthrumac_delete_entry($macent) {
974
	$rules = "";
975

    
976
	if ($macent['action'] == 'pass') {
977
		$ruleno = captiveportal_get_ipfw_passthru_ruleno($macent['mac']);
978

    
979
		if (!$ruleno)
980
			return $rules;
981

    
982
		captiveportal_free_ipfw_ruleno($ruleno);
983

    
984
		$rules .= "delete {$ruleno}\n";
985
		$rules .= "delete " . ++$ruleno . "\n";
986

    
987
		$pipeno = captiveportal_get_dn_passthru_ruleno($macent['mac']);
988

    
989
		if (!empty($pipeno)) {
990
			captiveportal_free_dn_ruleno($pipeno);
991
			$rules .= "pipe delete " . $pipeno . "\n";
992
			$rules .= "pipe delete " . ++$pipeno . "\n";
993
		}
994
	}
995

    
996
	return $rules;
997
}
998

    
999
function captiveportal_passthrumac_configure($lock = false) {
1000
	global $config, $g, $cpzone;
1001

    
1002
	$rules = "";
1003

    
1004
	if (is_array($config['captiveportal'][$cpzone]['passthrumac']))
1005
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent)
1006
			$rules .= captiveportal_passthrumac_configure_entry($macent);
1007

    
1008
	return $rules;
1009
}
1010

    
1011
function captiveportal_passthrumac_findbyname($username) {
1012
	global $config, $cpzone;
1013

    
1014
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1015
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1016
			if ($macent['username'] == $username)
1017
				return $macent;
1018
		}
1019
	}
1020
	return NULL;
1021
}
1022

    
1023
/* 
1024
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1025
 */
1026
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
1027
	global $g;
1028

    
1029
	/*  Instead of copying this entire function for something
1030
	 *  easy such as hostname vs ip address add this check
1031
	 */
1032
	if ($ishostname === true) {
1033
		if (!$g['booting']) {
1034
			$ipaddress = gethostbyname($ipent['hostname']);
1035
			if (!is_ipaddr($ipaddress)) 
1036
				return;
1037
		} else
1038
			$ipaddress = "";
1039
	} else
1040
		$ipaddress = $ipent['ip'];
1041

    
1042
	$rules = "";
1043
	$cp_filterdns_conf = "";
1044
	$enBwup = empty($ipent['bw_up']) ? 0 : intval($ipent['bw_up']);
1045
	$enBwdown = empty($ipent['bw_down']) ? 0 : intval($ipent['bw_down']);
1046

    
1047
	$pipeno = captiveportal_get_next_dn_ruleno();
1048
	$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$enBwup}Kbit/s queue 100 buckets 16");
1049
	$pipedown = $pipeno + 1;
1050
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16");
1051
	if ($ishostname === true) {
1052
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
1053
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
1054
		if (!is_ipaddr($ipaddress))
1055
			return array("", $cp_filterdns_conf);
1056
	}
1057
	$subnet = "";
1058
	if (!empty($ipent['sn']))
1059
		$subnet = "/{$ipent['sn']}";
1060
	$rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
1061
	$rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
1062

    
1063
	if ($ishostname === true)
1064
		return array($rules, $cp_filterdns_conf);
1065
	else
1066
		return $rules;
1067
}
1068

    
1069
function captiveportal_allowedhostname_configure() {
1070
	global $config, $g, $cpzone;
1071

    
1072
	$rules = "";
1073
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1074
		$rules = "\n# captiveportal_allowedhostname_configure()\n";
1075
		$cp_filterdns_conf = "";
1076
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) {
1077
			$tmprules = captiveportal_allowedip_configure_entry($hostnameent, true);
1078
			$rules .= $tmprules[0];
1079
			$cp_filterdns_conf .= $tmprules[1];
1080
		}
1081
		$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1082
		@file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1083
		unset($cp_filterdns_conf);
1084
		if (isvalidpid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid"))
1085
			sigkillbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid", "HUP");
1086
		else
1087
			mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzone} -d 1");
1088
	} else {
1089
		killbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1090
		@unlink("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1091
	}
1092

    
1093
	return $rules;
1094
}
1095

    
1096
function captiveportal_allowedip_configure() {
1097
	global $config, $g, $cpzone;
1098

    
1099
	$rules = "";
1100
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1101
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1102
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1103
	}
1104

    
1105
	return $rules;
1106
}
1107

    
1108
/* get last activity timestamp given client IP address */
1109
function captiveportal_get_last_activity($ip, $mac = NULL) {
1110
	global $cpzone;
1111

    
1112
	$ipfwoutput = pfSense_ipfw_getTablestats($cpzone, 1, $ip, $mac);
1113
	/* Reading only from one of the tables is enough of approximation. */
1114
	if (is_array($ipfwoutput)) {
1115
		return $ipfwoutput['timestamp'];
1116
	}
1117

    
1118
	return 0;
1119
}
1120

    
1121
function captiveportal_init_radius_servers() {
1122
	global $config, $g, $cpzone;
1123

    
1124
	/* generate radius server database */
1125
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1126
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1127
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1128
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1129
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1130
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1131

    
1132
		if ($config['captiveportal'][$cpzone]['radiusport'])
1133
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1134
		else
1135
			$radiusport = 1812;
1136
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1137
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1138
		else
1139
			$radiusacctport = 1813;
1140
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1141
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1142
		else
1143
			$radiusport2 = 1812;
1144
		if ($config['captiveportal'][$cpzone]['radiusport3'])
1145
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1146
		else
1147
			$radiusport3 = 1812;
1148
		if ($config['captiveportal'][$cpzone]['radiusport4'])
1149
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1150
		else
1151
			$radiusport4 = 1812;
1152

    
1153
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1154
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1155
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1156
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1157

    
1158
		$cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX);
1159
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
1160
		if (!$fd) {
1161
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1162
			unlock($cprdsrvlck);
1163
			return 1;
1164
		}
1165
		if (isset($radiusip))
1166
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
1167
		if (isset($radiusip2))
1168
			fwrite($fd,"\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
1169
		if (isset($radiusip3))
1170
			fwrite($fd,"\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
1171
		if (isset($radiusip4))
1172
			fwrite($fd,"\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
1173
		
1174

    
1175
		fclose($fd);
1176
		unlock($cprdsrvlck);
1177
	}
1178
}
1179

    
1180
/* read RADIUS servers into array */
1181
function captiveportal_get_radius_servers() {
1182
	global $g, $cpzone;
1183

    
1184
	$cprdsrvlck = lock("captiveportalradius{$cpzone}");
1185
	if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
1186
		$radiusservers = array();
1187
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", 
1188
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1189
		if ($cpradiusdb) {
1190
			foreach($cpradiusdb as $cpradiusentry) {
1191
				$line = trim($cpradiusentry);
1192
				if ($line) {
1193
					$radsrv = array();
1194
						list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key'], $context) = explode(",",$line);
1195
				}
1196
				if (empty($context)) {
1197
					if (!is_array($radiusservers['first']))
1198
						$radiusservers['first'] = array();
1199
					$radiusservers['first'] = $radsrv;
1200
				} else {
1201
					if (!is_array($radiusservers[$context]))
1202
						$radiusservers[$context] = array();
1203
					$radiusservers[$context][] = $radsrv;
1204
				}
1205
			}
1206
		}
1207
		unlock($cprdsrvlck);
1208
		return $radiusservers;
1209
	}
1210

    
1211
	unlock($cprdsrvlck);
1212
	return false;
1213
}
1214

    
1215
/* log successful captive portal authentication to syslog */
1216
/* part of this code from php.net */
1217
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1218
	// Log it
1219
	if (!$message)
1220
		$message = "{$status}: {$user}, {$mac}, {$ip}";
1221
	else {
1222
		$message = trim($message);
1223
		$message = "{$status}: {$user}, {$mac}, {$ip}, {$message}";
1224
	}
1225
	captiveportal_syslog($message);
1226
}
1227

    
1228
/* log simple messages to syslog */
1229
function captiveportal_syslog($message) {
1230
	global $cpzone;
1231

    
1232
	$message = trim($message);
1233
	$message .= "Zone: {$cpzone} - {$message}";
1234
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1235
	// Log it
1236
	syslog(LOG_INFO, $message);
1237
	closelog();
1238
}
1239

    
1240
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1241
	global $g, $config, $cpzoneid;
1242

    
1243
	$pipeno = captiveportal_get_next_dn_ruleno();
1244

    
1245
	/* If the pool is empty, return appropriate message and fail authentication */
1246
	if (empty($pipeno)) {
1247
		$auth_list = array();
1248
		$auth_list['auth_val'] = 1;
1249
		$auth_list['error'] = "System reached maximum login capacity";
1250
		return $auth_list;
1251
	}
1252

    
1253
	$radiusservers = captiveportal_get_radius_servers();
1254

    
1255
	if (is_null($radiusctx))
1256
		$radiusctx = 'first';
1257

    
1258
	$auth_list = RADIUS_AUTHENTICATION($username,
1259
		$password,
1260
		$radiusservers[$radiusctx],
1261
		$clientip,
1262
		$clientmac,
1263
		$pipeno);
1264

    
1265
	if ($auth_list['auth_val'] == 2) {
1266
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1267
		$sessionid = portal_allow($clientip,
1268
			$clientmac,
1269
			$username,
1270
			$password,
1271
			$auth_list,
1272
			$pipeno,
1273
			$radiusctx);
1274
	} else {
1275
	         captiveportal_free_dn_ruleno($pipeno);
1276
	       }
1277

    
1278
	return $auth_list;
1279
}
1280

    
1281
function captiveportal_opendb() {
1282
	global $g, $cpzone;
1283

    
1284
	if (file_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db"))
1285
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1286
	else {
1287
		$errormsg = "";
1288
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1289
		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)) {
1290
			@sqlite_exec($DB, "CREATE UNIQUE INDEX idx_active ON captiveportal (sessionid, username)");
1291
			@sqlite_exec($DB, "CREATE INDEX user ON captiveportal (username)");
1292
			@sqlite_exec($DB, "CREATE INDEX ip ON captiveportal (ip)");
1293
			@sqlite_exec($DB, "CREATE INDEX starttime ON captiveportal (allow_time)");
1294
			@sqlite_exec($DB, "CREATE INDEX serviceid ON captiveportal (serviceid)");
1295
		} else
1296
			captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$errormsg}");
1297
	}
1298

    
1299
	return $DB;
1300
}
1301

    
1302
/* read captive portal DB into array */
1303
function captiveportal_read_db($query = "") {
1304

    
1305
	$DB = captiveportal_opendb();
1306
	if ($DB) {
1307
		sqlite_exec($DB, "BEGIN");
1308
		if (!empty($query))
1309
			$cpdb = @sqlite_array_query($DB, "SELECT * FROM captiveportal {$query}", SQLITE_NUM);
1310
		else {
1311
			$response = @sqlite_unbuffered_query($DB, "SELECT * FROM captiveportal", SQLITE_NUM);
1312
			$cpdb = @sqlite_fetch_all($response, SQLITE_NUM);
1313
		}
1314
		sqlite_exec($DB, "END");
1315
		@sqlite_close($DB);
1316
	}
1317
	if (!$cpdb)
1318
		$cpdb = array();
1319

    
1320
	return $cpdb;
1321
}
1322

    
1323
function captiveportal_remove_entries($remove) {
1324

    
1325
	if (!is_array($remove) || empty($remove))
1326
		return;
1327

    
1328
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1329
	foreach($remove as $idx => $unindex) {
1330
		$query .= "'{$unindex}'";
1331
		if ($idx < (count($remove) - 1))
1332
			$query .= ",";
1333
	}
1334
	$query .= ")";
1335
	captiveportal_write_db($query);
1336
}
1337

    
1338
/* write captive portal DB */
1339
function captiveportal_write_db($queries) {
1340
	global $g;
1341

    
1342
	if (is_array($queries))
1343
		$query = implode(";", $queries);
1344
	else
1345
		$query = $queries;
1346

    
1347
	$DB = captiveportal_opendb();
1348
	if ($DB) {
1349
		$error_msg = "";
1350
		sqlite_exec($DB, "BEGIN TRANSACTION");
1351
		$result = @sqlite_exec($DB, $query, $error_msg);
1352
		if (!$result)
1353
			captiveportal_syslog("Trying to modify DB returned error: {$error_msg}");
1354
		else
1355
			sqlite_exec($DB, "END TRANSACTION");
1356
		@sqlite_close($DB);
1357
		return $result;
1358
	} else
1359
		return true;
1360
}
1361

    
1362
function captiveportal_write_elements() {
1363
	global $g, $config, $cpzone;
1364
	
1365
	$cpcfg = $config['captiveportal'][$cpzone];
1366

    
1367
	if (!is_dir($g['captiveportal_element_path']))
1368
		@mkdir($g['captiveportal_element_path']);
1369

    
1370
	if (is_array($cpcfg['element'])) {
1371
		conf_mount_rw();
1372
		foreach ($cpcfg['element'] as $data) {
1373
			if (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1374
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1375
				return 1;
1376
			}
1377
			if (!file_exists("{$g['captiveportal_path']}/{$data['name']}"))
1378
				@symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1379
		}
1380
		conf_mount_ro();
1381
	}
1382
	
1383
	return 0;
1384
}
1385

    
1386
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1387
	global $cpzone;
1388

    
1389
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1390
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1391
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1392
		for ($ridx = $rulenos_start; $ridx < $rulenos_range_max; $ridx++) {
1393
			if ($rules[$ridx] == $cpzone) {
1394
				$rules[$ridx] = false;
1395
				$ridx++;
1396
				$rules[$ridx] = false;
1397
			}
1398
		}
1399
	}
1400
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1401
	unlock($cpruleslck);
1402
}
1403

    
1404
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1405
	global $config, $g, $cpzone;
1406

    
1407
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1408
	$ruleno = 0;
1409
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1410
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1411
		for ($ridx = $rulenos_start; $ridx < $rulenos_range_max; $ridx++) {
1412
			if ($rules[$ridx]) {
1413
				$ridx++;
1414
				continue;
1415
			}
1416
			$ruleno = $ridx;
1417
			$rules[$ridx] = $cpzone;
1418
			$rules[++$ridx] = $cpzone;
1419
			break;
1420
		}
1421
	} else {
1422
		$rules = array_pad(array(), $rulenos_range_max, false);
1423
		$ruleno = $rulenos_start;
1424
		$rules[$rulenos_start] = $cpzone;
1425
		$rules[++$rulenos_start] = $cpzone;
1426
	}
1427
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1428
	unlock($cpruleslck);
1429

    
1430
	return $ruleno;
1431
}
1432

    
1433
function captiveportal_free_dn_ruleno($ruleno) {
1434
	global $config, $g;
1435

    
1436
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1437
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1438
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1439
		$rules[$ruleno] = false;
1440
		$rules[++$ruleno] = false;
1441
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1442
	}
1443
	unlock($cpruleslck);
1444
}
1445

    
1446
function captiveportal_get_dn_passthru_ruleno($value) {
1447
	global $config, $g, $cpzone, $cpzoneid;
1448

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

    
1453
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1454
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1455
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1456
		$ruleno = intval(`/sbin/ipfw -x {$cpzoneid} show | /usr/bin/grep {$value} |  /usr/bin/grep -v grep | /usr/bin/cut -d " " -f 5 | /usr/bin/head -n 1`);
1457
		if ($rules[$ruleno]) {
1458
			unlock($cpruleslck);
1459
			return $ruleno;
1460
		}
1461
	}
1462

    
1463
	unlock($cpruleslck);
1464
	return NULL;
1465
}
1466

    
1467
/*
1468
 * This function will calculate the lowest free firewall ruleno
1469
 * within the range specified based on the actual logged on users
1470
 *
1471
 */
1472
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1473
	global $config, $g, $cpzone;
1474

    
1475
	$cpcfg = $config['captiveportal'][$cpzone];
1476
	if(!isset($cpcfg['enable']))
1477
		return NULL;
1478

    
1479
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1480
	$ruleno = 0;
1481
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1482
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1483
		for ($ridx = 2; $ridx < ($rulenos_range_max - $rulenos_start); $ridx++) {
1484
			if ($rules[$ridx]) {
1485
				/* 
1486
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1487
				 * and the out pipe ruleno + 1.
1488
				 */
1489
				$ridx++;
1490
				continue;
1491
			}
1492
			$ruleno = $ridx;
1493
			$rules[$ridx] = "used";
1494
			$rules[++$ridx] = "used";
1495
			break;
1496
		}
1497
	} else {
1498
		$rules = array_pad(array(), $rulenos_range_max, false);
1499
		$rules[$rulenos_start] = "used";
1500
		$rules[++$rulenos_start] = "used";
1501
		$ruleno = 2;
1502
	}
1503
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1504
	unlock($cpruleslck);
1505
	return $ruleno;
1506
}
1507

    
1508
function captiveportal_free_ipfw_ruleno($ruleno) {
1509
	global $config, $g, $cpzone;
1510

    
1511
	$cpcfg = $config['captiveportal'][$cpzone];
1512
	if(!isset($cpcfg['enable']))
1513
		return NULL;
1514

    
1515
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1516
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1517
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1518
		$rules[$ruleno] = false;
1519
		$rules[++$ruleno] = false;
1520
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1521
	}
1522
	unlock($cpruleslck);
1523
}
1524

    
1525
function captiveportal_get_ipfw_passthru_ruleno($value) {
1526
	global $config, $g, $cpzone, $cpzoneid;
1527

    
1528
	$cpcfg = $config['captiveportal'][$cpzone];
1529
	if(!isset($cpcfg['enable']))
1530
		return NULL;
1531

    
1532
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1533
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1534
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1535
		$ruleno = intval(`/sbin/ipfw -x {$cpzoneid} show | /usr/bin/grep {$value} |  /usr/bin/grep -v grep | /usr/bin/cut -d " " -f 1 | /usr/bin/head -n 1`);
1536
		if ($rules[$ruleno]) {
1537
			unlock($cpruleslck);
1538
			return $ruleno;
1539
		}
1540
	}
1541

    
1542
	unlock($cpruleslck);
1543
	return NULL;
1544
}
1545

    
1546
/**
1547
 * This function will calculate the traffic produced by a client
1548
 * based on its firewall rule
1549
 *
1550
 * Point of view: NAS
1551
 *
1552
 * Input means: from the client
1553
 * Output means: to the client
1554
 *
1555
 */
1556

    
1557
function getVolume($ip, $mac = NULL) {
1558
	global $config, $cpzone;
1559

    
1560
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1561
	$volume = array();
1562
	// Initialize vars properly, since we don't want NULL vars
1563
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1564

    
1565
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 1, $ip, $mac);
1566
	if (is_array($ipfw)) {
1567
		if ($reverse) {
1568
			$volume['output_pkts'] = $ipfw['packets'];
1569
			$volume['output_bytes'] = $ipfw['bytes'];
1570
		}
1571
		else {
1572
			$volume['input_pkts'] = $ipfw['packets'];
1573
			$volume['input_bytes'] = $ipfw['bytes'];
1574
		}
1575
	}
1576

    
1577
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 2, $ip);
1578
	if (is_array($ipfw)) {
1579
		if ($reverse) {
1580
			$volume['input_pkts'] = $ipfw['packets'];
1581
			$volume['input_bytes'] = $ipfw['bytes'];
1582
		}
1583
		else {
1584
			$volume['output_pkts'] = $ipfw['packets'];
1585
			$volume['output_bytes'] = $ipfw['bytes'];
1586
		}
1587
	}
1588

    
1589
	return $volume;
1590
}
1591

    
1592
/**
1593
 * Get the NAS-IP-Address based on the current wan address
1594
 *
1595
 * Use functions in interfaces.inc to find this out
1596
 *
1597
 */
1598

    
1599
function getNasIP()
1600
{
1601
	global $config, $cpzone;
1602

    
1603
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1604
			$nasIp = get_interface_ip();
1605
	} else {
1606
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1607
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1608
		else
1609
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1610
	}
1611
		
1612
	if(!is_ipaddr($nasIp))
1613
		$nasIp = "0.0.0.0";
1614

    
1615
	return $nasIp;
1616
}
1617

    
1618
function portal_ip_from_client_ip($cliip) {
1619
	global $config, $cpzone;
1620

    
1621
	$isipv6 = is_ipaddrv6($cliip);
1622
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1623
	foreach ($interfaces as $cpif) {
1624
		if ($isipv6) {
1625
			$ip = get_interface_ipv6($cpif);
1626
			$sn = get_interface_subnetv6($cpif);
1627
		} else {
1628
			$ip = get_interface_ip($cpif);
1629
			$sn = get_interface_subnet($cpif);
1630
		}
1631
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1632
			return $ip;
1633
	}
1634

    
1635
	$inet = ($isipv6) ? '-inet6' : '-inet';
1636
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1637
	$iface = trim($iface, "\n");
1638
	if (!empty($iface)) {
1639
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1640
		if (is_ipaddr($ip))
1641
			return $ip;
1642
	}
1643

    
1644
	// doesn't match up to any particular interface
1645
	// so let's set the portal IP to what PHP says 
1646
	// the server IP issuing the request is. 
1647
	// allows same behavior as 1.2.x where IP isn't 
1648
	// in the subnet of any CP interface (static routes, etc.)
1649
	// rather than forcing to DNS hostname resolution
1650
	$ip = $_SERVER['SERVER_ADDR'];
1651
	if (is_ipaddr($ip))
1652
		return $ip;
1653

    
1654
	return false;
1655
}
1656

    
1657
function portal_hostname_from_client_ip($cliip) {
1658
	global $config, $cpzone;
1659

    
1660
	$cpcfg = $config['captiveportal'][$cpzone];
1661

    
1662
	if (isset($cpcfg['httpslogin'])) {
1663
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
1664
		$ourhostname = $cpcfg['httpsname'];
1665
		
1666
		if ($listenporthttps != 443)
1667
			$ourhostname .= ":" . $listenporthttps;
1668
	} else {
1669
		$listenporthttp  = $cpcfg['listenporthttp']  ? $cpcfg['listenporthttp']  : $cpcfg['zoneid'];
1670
		$ifip = portal_ip_from_client_ip($cliip);
1671
		if (!$ifip)
1672
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1673
		else
1674
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1675
		
1676
		if ($listenporthttp != 80)
1677
			$ourhostname .= ":" . $listenporthttp;
1678
	}
1679
	
1680
	return $ourhostname;
1681
}
1682

    
1683
/* functions move from index.php */
1684

    
1685
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1686
	global $g, $config, $cpzone;
1687

    
1688
	/* Get captive portal layout */
1689
	if ($type == "redir") {
1690
		header("Location: {$redirurl}");
1691
		return;
1692
	} else if ($type == "login")
1693
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1694
	else
1695
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1696

    
1697
	$cpcfg = $config['captiveportal'][$cpzone];
1698

    
1699
	/* substitute the PORTAL_REDIRURL variable */
1700
	if ($cpcfg['preauthurl']) {
1701
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1702
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1703
	}
1704

    
1705
	/* substitute other variables */
1706
	$ourhostname = portal_hostname_from_client_ip($clientip);
1707
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1708
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1709
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1710

    
1711
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1712
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1713
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1714
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1715
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1716

    
1717
	// Special handling case for captive portal master page so that it can be ran 
1718
	// through the PHP interpreter using the include method above.  We convert the
1719
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1720
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1721
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1722
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1723
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1724
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1725
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1726
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1727

    
1728
	echo $htmltext;
1729
}
1730

    
1731
function portal_mac_radius($clientmac,$clientip) {
1732
	global $config, $cpzone;
1733

    
1734
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1735

    
1736
	/* authentication against the radius server */
1737
	$username = mac_format($clientmac);
1738
	$auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1739
	if ($auth_list['auth_val'] == 2)
1740
		return TRUE;
1741

    
1742
	if (!empty($auth_list['url_redirection']))
1743
		portal_reply_page($auth_list['url_redirection'], "redir");
1744

    
1745
	return FALSE;
1746
}
1747

    
1748
function captiveportal_reapply_attributes($cpentry, $attributes) {
1749
	global $config, $cpzone, $g;
1750

    
1751
	$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1752
	$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1753
	$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1754
	$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1755
	$bw_up_pipeno = $cpentry[1];
1756
	$bw_down_pipeno = $cpentry[1]+1;
1757

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

    
1762
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1763
}
1764

    
1765
function portal_allow($clientip,$clientmac,$username,$password = null, $attributes = null, $pipeno = null, $radiusctx = null)  {
1766
	global $redirurl, $g, $config, $type, $passthrumac, $_POST, $cpzone, $cpzoneid;
1767

    
1768
	// Ensure we create an array if we are missing attributes
1769
	if (!is_array($attributes))
1770
		$attributes = array();
1771

    
1772
	unset($sessionid);
1773

    
1774
	/* Do not allow concurrent login execution. */
1775
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1776

    
1777
	if ($attributes['voucher'])
1778
		$remaining_time = $attributes['session_timeout'];
1779

    
1780
	$writecfg = false;
1781
	/* Find an existing session */
1782
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
1783
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1784
			$mac = captiveportal_passthrumac_findbyname($username);
1785
			if (!empty($mac)) {
1786
				if ($_POST['replacemacpassthru']) {
1787
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
1788
						if ($macent['mac'] == $mac['mac']) {
1789
							$macrules = "";
1790
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1791
							$pipeno = captiveportal_get_dn_passthru_ruleno($mac['mac']);
1792
							if ($ruleno) {
1793
								captiveportal_free_ipfw_ruleno($ruleno);
1794
								$macrules .= "delete {$ruleno}\n";
1795
								++$ruleno;
1796
								$macrules .= "delete {$ruleno}\n";
1797
							}
1798
							if ($pipeno) {
1799
								captiveportal_free_dn_ruleno($pipeno);
1800
								$macrules .= "pipe delete {$pipeno}\n";
1801
								++$pipeno;
1802
								$macrules .= "pipe delete {$pipeno}\n";
1803
							}
1804
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1805
							$mac['action'] = 'pass';
1806
							$mac['mac'] = $clientmac;
1807
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1808
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1809
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1810
							mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1811
							$writecfg = true;
1812
							$sessionid = true;
1813
							break;
1814
						}
1815
					}
1816
				} else {
1817
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1818
						$clientmac, $clientip, $username, $password);
1819
					unlock($cpdblck);
1820
					return;
1821
				}
1822
			}
1823
		}
1824
	}
1825

    
1826
	/* read in client database */
1827
	$query = "WHERE ip = '{$clientip}'";
1828
	$tmpusername = strtolower($username);
1829
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins']))
1830
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
1831
	$cpdb = captiveportal_read_db($query);
1832

    
1833
	/* Snapshot the timestamp */
1834
	$allow_time = time();
1835
	$radiusservers = captiveportal_get_radius_servers();
1836
	$unsetindexes = array();
1837
	if (is_null($radiusctx))
1838
		$radiusctx = 'first';
1839

    
1840
	foreach ($cpdb as $cpentry) {
1841
		if (empty($cpentry[10]))
1842
			$cpentry[10] = 'first';
1843
		/* on the same ip */
1844
		if ($cpentry[2] == $clientip) {
1845
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac)
1846
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1847
			else
1848
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
1849
			$sessionid = $cpentry[5];
1850
			break;
1851
		}
1852
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1853
			// user logged in with an active voucher. Check for how long and calculate 
1854
			// how much time we can give him (voucher credit - used time)
1855
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1856
			if ($remaining_time < 0)    // just in case. 
1857
				$remaining_time = 0;
1858

    
1859
			/* This user was already logged in so we disconnect the old one */
1860
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1861
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1862
			$unsetindexes[] = $cpentry[5];
1863
			break;
1864
		}
1865
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1866
			/* on the same username */
1867
			if (strcasecmp($cpentry[4], $username) == 0) {
1868
				/* This user was already logged in so we disconnect the old one */
1869
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1870
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1871
				$unsetindexes[] = $cpentry[5];
1872
				break;
1873
			}
1874
		}
1875
	}
1876
	unset($cpdb);
1877

    
1878
	if (!empty($unsetindexes))
1879
		captiveportal_remove_entries($unsetindexes);
1880

    
1881
	if ($attributes['voucher'] && $remaining_time <= 0)
1882
		return 0;       // voucher already used and no time left
1883

    
1884
	if (!isset($sessionid)) {
1885
		/* generate unique session ID */
1886
		$tod = gettimeofday();
1887
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1888

    
1889
		if ($passthrumac) {
1890
			$mac = array();
1891
			$mac['action'] = 'pass';
1892
			$mac['mac'] = $clientmac;
1893
			$mac['ip'] = $clientip; /* Used only for logging */
1894
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
1895
				$mac['username'] = $username;
1896
				if ($attributes['voucher'])
1897
					$mac['logintype'] = "voucher";
1898
			}
1899
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1900
			if (!empty($bw_up))
1901
				$mac['bw_up'] = $bw_up;
1902
			if (!empty($bw_down))
1903
				$mac['bw_down'] = $bw_down;
1904
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
1905
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
1906
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1907
			unlock($cpdblck);
1908
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1909
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1910
			mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1911
			$writecfg = true;
1912
		} else {
1913
			/* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
1914
			if (is_null($pipeno))
1915
				$pipeno = captiveportal_get_next_dn_ruleno();
1916

    
1917
			/* if the pool is empty, return appropriate message and exit */
1918
			if (is_null($pipeno)) {
1919
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1920
				log_error("Zone: {$cpzone} - WARNING!  Captive portal has reached maximum login capacity");
1921
				unlock($cpdblck);
1922
				return;
1923
			}
1924

    
1925
			$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1926
			$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1927
			$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1928
			$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1929

    
1930
			$bw_up_pipeno = $pipeno;
1931
			$bw_down_pipeno = $pipeno + 1;
1932
			//$bw_up /= 1000; // Scale to Kbit/s
1933
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1934
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1935

    
1936
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
1937
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1938
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
1939
			else
1940
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
1941

    
1942
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1943
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
1944
			else
1945
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
1946

    
1947
			if ($attributes['voucher'])
1948
				$attributes['session_timeout'] = $remaining_time;
1949
			
1950
			/* handle empty attributes */
1951
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
1952
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
1953
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
1954
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
1955

    
1956
			/* escape username */
1957
			$safe_username = sqlite_escape_string($username);
1958

    
1959
			/* encode password in Base64 just in case it contains commas */
1960
			$bpassword = base64_encode($password);
1961
			$insertquery  = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval) ";
1962
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
1963
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval})";
1964

    
1965
			/* store information to database */
1966
			captiveportal_write_db($insertquery);
1967
			unlock($cpdblck);
1968

    
1969
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
1970
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
1971
				if ($acct_val == 1)
1972
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1973
			}
1974
		}
1975
	} else
1976
		unlock($cpdblck);
1977

    
1978
	if ($writecfg == true)
1979
		write_config();
1980

    
1981
	/* redirect user to desired destination */
1982
	if (!empty($attributes['url_redirection']))
1983
		$my_redirurl = $attributes['url_redirection'];
1984
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
1985
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
1986
	else
1987
		$my_redirurl = $redirurl;
1988

    
1989
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
1990
		$ourhostname = portal_hostname_from_client_ip($clientip);
1991
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
1992
		$logouturl = "{$protocol}{$ourhostname}/";
1993

    
1994
		if (isset($attributes['reply_message']))
1995
			$message = $attributes['reply_message'];
1996
		else
1997
			$message = 0;
1998

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

    
2001
	} else {
2002
		header("Location: " . $my_redirurl);
2003
	}
2004

    
2005
	return $sessionid;
2006
}
2007

    
2008

    
2009
/*
2010
 * Used for when pass-through credits are enabled.
2011
 * Returns true when there was at least one free login to deduct for the MAC.
2012
 * Expired entries are removed as they are seen.
2013
 * Active entries are updated according to the configuration.
2014
 */
2015
function portal_consume_passthrough_credit($clientmac) {
2016
	global $config, $cpzone;
2017

    
2018
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
2019
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
2020
	else
2021
		return false;
2022

    
2023
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
2024
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2025
	else
2026
		return false;
2027

    
2028
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
2029
		return false;
2030

    
2031
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2032

    
2033
	/*
2034
	 * Read database of used MACs.  Lines are a comma-separated list
2035
	 * of the time, MAC, then the count of pass-through credits remaining.
2036
	 */
2037
	$usedmacs = captiveportal_read_usedmacs_db();
2038

    
2039
	$currenttime = time();
2040
	$found = false;
2041
	foreach ($usedmacs as $key => $usedmac) {
2042
		$usedmac = explode(",", $usedmac);
2043

    
2044
		if ($usedmac[1] == $clientmac) {
2045
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2046
				if ($usedmac[2] < 1) {
2047
					if ($updatetimeouts) {
2048
						$usedmac[0] = $currenttime;
2049
						unset($usedmacs[$key]);
2050
						$usedmacs[] = implode(",", $usedmac);
2051
						captiveportal_write_usedmacs_db($usedmacs);
2052
					}
2053

    
2054
					return false;
2055
				} else {
2056
					$usedmac[2] -= 1;
2057
					$usedmacs[$key] = implode(",", $usedmac);
2058
				}
2059

    
2060
				$found = true;
2061
			} else
2062
				unset($usedmacs[$key]);
2063

    
2064
			break;
2065
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2066
				unset($usedmacs[$key]);
2067
	}
2068

    
2069
	if (!$found) {
2070
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2071
		$usedmacs[] = implode(",", $usedmac);
2072
	}
2073

    
2074
	captiveportal_write_usedmacs_db($usedmacs);
2075
	return true;
2076
}
2077

    
2078
function captiveportal_read_usedmacs_db() {
2079
	global $g, $cpzone;
2080

    
2081
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2082
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2083
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2084
		if (!$usedmacs)
2085
			$usedmacs = array();
2086
	} else
2087
		$usedmacs = array();
2088

    
2089
	unlock($cpumaclck);
2090
	return $usedmacs;
2091
}
2092

    
2093
function captiveportal_write_usedmacs_db($usedmacs) {
2094
	global $g, $cpzone;
2095

    
2096
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2097
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2098
	unlock($cpumaclck);
2099
}
2100

    
2101
function captiveportal_blocked_mac($mac) {
2102
	global $config, $g, $cpzone;
2103

    
2104
	if (empty($mac) || !is_macaddr($mac))
2105
		return false;
2106

    
2107
	if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
2108
		return false;
2109

    
2110
	foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac)
2111
		if (($passthrumac['action'] == 'block') &&
2112
		    ($passthrumac['mac'] == strtolower($mac)))
2113
			return true;
2114

    
2115
	return false;
2116

    
2117
}
2118

    
2119
function captiveportal_send_server_accounting($off = false) {
2120
	global $cpzone, $config;
2121

    
2122
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
2123
		return;
2124
	}
2125
	if ($off) {
2126
		$racct = new Auth_RADIUS_Acct_Off;
2127
	} else {
2128
		$racct = new Auth_RADIUS_Acct_On;
2129
	}
2130
	$radiusservers = captiveportal_get_radius_servers();
2131
	if (empty($radiusservers)) {
2132
		return;
2133
	}
2134
	foreach ($radiusservers['first'] as $radsrv) {
2135
		// Add a new server to our instance
2136
		$racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
2137
	}
2138
	if (PEAR::isError($racct->start())) {
2139
		$retvalue['acct_val'] = 1;
2140
		$retvalue['error'] = $racct->getMessage();
2141

    
2142
		// If we encounter an error immediately stop this function and go back
2143
		$racct->close();
2144
		return $retvalue;
2145
	}
2146
	// Send request
2147
	$result = $racct->send();
2148
	// Evaluation of the response
2149
	// 5 -> Accounting-Response
2150
	// See RFC2866 for this.
2151
	if (PEAR::isError($result)) {
2152
		$retvalue['acct_val'] = 1;
2153
		$retvalue['error'] = $result->getMessage();
2154
	} else if ($result === true) {
2155
		$retvalue['acct_val'] = 5 ;
2156
	} else {
2157
		$retvalue['acct_val'] = 1 ;
2158
	}
2159

    
2160
	$racct->close();
2161
	return $retvalue;
2162
}
2163
?>
(8-8/66)