Project

General

Profile

Download (71.8 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;
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
		unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
366
		/* Release allocated pipes for this zone */
367
		captiveportal_free_dnrules();
368

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
455
	if (!isset($config['captiveportal'][$cpzone]['enable']))
456
		return;
457

    
458
	captiveportal_load_modules();
459
	mwexec("/usr/local/sbin/ipfw_context -a {$cpzone}", true);
460

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

    
492
	if ($reinit == false)
493
		$captiveportallck = lock("captiveportal{$cpzone}");
494

    
495
	$cprules =	"add 65291 allow pfsync from any to any\n";
496
	$cprules .= "add 65292 allow carp from any to any\n";
497

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

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

    
509
EOD;
510

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

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

    
547
	$listenporthttp =
548
		$config['captiveportal'][$cpzone]['listenporthttp'] ?
549
		$config['captiveportal'][$cpzone]['listenporthttp'] :
550
		$config['captiveportal'][$cpzone]['zoneid'];
551

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

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

    
566
EOD;
567

    
568
	/* generate passthru mac database */
569
	$cprules .= captiveportal_passthrumac_configure(true);
570
	$cprules .= "\n";
571

    
572
	/* allowed ipfw rules to make allowed ip work */
573
	$cprules .= captiveportal_allowedip_configure();
574

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

    
585
	if ($reinit == false)
586
		unlock($captiveportallck);
587
}
588

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

    
598
	if (empty($cpzone))
599
		return;
600

    
601
	$cpcfg = $config['captiveportal'][$cpzone];
602
	$vcpcfg = $config['voucher'][$cpzone];
603

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

    
610
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout']))
611
		$idletimeout = $cpcfg['idletimeout'] * 60;
612

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

    
618
	$radiussrvs = captiveportal_get_radius_servers();
619

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

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

    
640
		$timedout = false;
641
		$term_cause = 1;
642
		if (empty($cpentry[10]))
643
			$cpentry[10] = 'first';
644
		$radiusservers = $radiussrvs[$cpentry[10]];
645

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

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

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

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

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

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

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

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

    
763
	captiveportal_prune_old_automac();
764

    
765
	if ($voucher_needs_sync == true)
766
		/* Triger a sync of the vouchers on config */
767
		send_event("service sync vouchers");
768

    
769
	/* write database */
770
	if (!empty($unsetindexes))
771
		captiveportal_remove_entries($unsetindexes);
772
}
773

    
774
function captiveportal_prune_old_automac() {
775
	global $g, $config, $cpzone;
776

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

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

    
839
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
840

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

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

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

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

    
885
}
886

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

    
891
	$radiusservers = captiveportal_get_radius_servers();
892

    
893
	/* read database */
894
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
895

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

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

    
910
/* send RADIUS acct stop for all current clients */
911
function captiveportal_radius_stop_all() {
912
	global $config, $cpzone;
913

    
914
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable']))
915
		return;
916

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

    
937
function captiveportal_passthrumac_configure_entry($macent) {
938
	global $config, $g, $cpzone;
939

    
940
	$bwUp = empty($macent['bw_up']) ? 0 : $macent['bw_up'];
941
	$bwDown = empty($macent['bw_down']) ? 0 : $macent['bw_down'];
942

    
943
	$ruleno = captiveportal_get_next_ipfw_ruleno();
944

    
945
	if ($macent['action'] == 'pass') {
946
		$pipeno = captiveportal_get_next_dn_ruleno();
947

    
948
		$pipeup = $pipeno;
949
		$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16");
950
		$pipedown = $pipeno + 1;
951
		$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
952

    
953
		$rules = "add {$ruleno} pipe {$pipeup} ip from any to any MAC any {$macent['mac']}\n";
954
		$ruleno++;
955
		$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC {$macent['mac']} any\n";
956
	}
957

    
958
	return $rules;
959
}
960

    
961
function captiveportal_passthrumac_delete_entry($macent) {
962
	$rules = "";
963

    
964
	if ($macent['action'] == 'pass') {
965
		$ruleno = captiveportal_get_ipfw_passthru_ruleno($macent['mac']);
966

    
967
		if (!$ruleno)
968
			return $rules;
969

    
970
		captiveportal_free_ipfw_ruleno($ruleno);
971

    
972
		$rules .= "delete {$ruleno}\n";
973
		$rules .= "delete " . ++$ruleno . "\n";
974

    
975
		$pipeno = captiveportal_get_dn_passthru_ruleno($macent['mac']);
976

    
977
		if (!empty($pipeno)) {
978
			captiveportal_free_dn_ruleno($pipeno);
979
			$rules .= "pipe delete " . $pipeno . "\n";
980
			$rules .= "pipe delete " . ++$pipeno . "\n";
981
		}
982
	}
983

    
984
	return $rules;
985
}
986

    
987
function captiveportal_passthrumac_configure($lock = false) {
988
	global $config, $g, $cpzone;
989

    
990
	$rules = "";
991

    
992
	if (is_array($config['captiveportal'][$cpzone]['passthrumac']))
993
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent)
994
			$rules .= captiveportal_passthrumac_configure_entry($macent);
995

    
996
	return $rules;
997
}
998

    
999
function captiveportal_passthrumac_findbyname($username) {
1000
	global $config, $cpzone;
1001

    
1002
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1003
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1004
			if ($macent['username'] == $username)
1005
				return $macent;
1006
		}
1007
	}
1008
	return NULL;
1009
}
1010

    
1011
/* 
1012
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1013
 */
1014
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
1015
	global $g;
1016

    
1017
	/*  Instead of copying this entire function for something
1018
	 *  easy such as hostname vs ip address add this check
1019
	 */
1020
	if ($ishostname === true) {
1021
		if (!$g['booting']) {
1022
			$ipaddress = gethostbyname($ipent['hostname']);
1023
			if (!is_ipaddr($ipaddress)) 
1024
				return;
1025
		} else
1026
			$ipaddress = "";
1027
	} else
1028
		$ipaddress = $ipent['ip'];
1029

    
1030
	$rules = "";
1031
	$cp_filterdns_conf = "";
1032
	$enBwup = empty($ipent['bw_up']) ? 0 : intval($ipent['bw_up']);
1033
	$enBwdown = empty($ipent['bw_down']) ? 0 : intval($ipent['bw_down']);
1034

    
1035
	$pipeno = captiveportal_get_next_dn_ruleno();
1036
	$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$enBwup}Kbit/s queue 100 buckets 16");
1037
	$pipedown = $pipeno + 1;
1038
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16");
1039
	if ($ishostname === true) {
1040
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
1041
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
1042
		if (!is_ipaddr($ipaddress))
1043
			return array("", $cp_filterdns_conf);
1044
	}
1045
	$subnet = "";
1046
	if (!empty($ipent['sn']))
1047
		$subnet = "/{$ipent['sn']}";
1048
	$rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
1049
	$rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
1050

    
1051
	if ($ishostname === true)
1052
		return array($rules, $cp_filterdns_conf);
1053
	else
1054
		return $rules;
1055
}
1056

    
1057
function captiveportal_allowedhostname_configure() {
1058
	global $config, $g, $cpzone;
1059

    
1060
	$rules = "";
1061
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1062
		$rules = "\n# captiveportal_allowedhostname_configure()\n";
1063
		$cp_filterdns_conf = "";
1064
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) {
1065
			$tmprules = captiveportal_allowedip_configure_entry($hostnameent, true);
1066
			$rules .= $tmprules[0];
1067
			$cp_filterdns_conf .= $tmprules[1];
1068
		}
1069
		$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1070
		@file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1071
		unset($cp_filterdns_conf);
1072
		if (isvalidpid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid"))
1073
			sigkillbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid", "HUP");
1074
		else
1075
			mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzone} -d 1");
1076
	} else {
1077
		killbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1078
		@unlink("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1079
	}
1080

    
1081
	return $rules;
1082
}
1083

    
1084
function captiveportal_allowedip_configure() {
1085
	global $config, $g, $cpzone;
1086

    
1087
	$rules = "";
1088
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1089
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1090
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1091
	}
1092

    
1093
	return $rules;
1094
}
1095

    
1096
/* get last activity timestamp given client IP address */
1097
function captiveportal_get_last_activity($ip) {
1098
	global $cpzone;
1099

    
1100
	$ipfwoutput = pfSense_ipfw_getTablestats($cpzone, 1, $ip);
1101
	/* Reading only from one of the tables is enough of approximation. */
1102
	if (is_array($ipfwoutput)) {
1103
		return $ipfwoutput['timestamp'];
1104
	}
1105

    
1106
	return 0;
1107
}
1108

    
1109
function captiveportal_init_radius_servers() {
1110
	global $config, $g, $cpzone;
1111

    
1112
	/* generate radius server database */
1113
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1114
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1115
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1116
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1117
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1118
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1119

    
1120
		if ($config['captiveportal'][$cpzone]['radiusport'])
1121
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1122
		else
1123
			$radiusport = 1812;
1124
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1125
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1126
		else
1127
			$radiusacctport = 1813;
1128
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1129
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1130
		else
1131
			$radiusport2 = 1812;
1132
		if ($config['captiveportal'][$cpzone]['radiusport3'])
1133
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1134
		else
1135
			$radiusport3 = 1812;
1136
		if ($config['captiveportal'][$cpzone]['radiusport4'])
1137
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1138
		else
1139
			$radiusport4 = 1812;
1140

    
1141
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1142
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1143
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1144
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1145

    
1146
		$cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX);
1147
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
1148
		if (!$fd) {
1149
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1150
			unlock($cprdsrvlck);
1151
			return 1;
1152
		}
1153
		if (isset($radiusip))
1154
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
1155
		if (isset($radiusip2))
1156
			fwrite($fd,"\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
1157
		if (isset($radiusip3))
1158
			fwrite($fd,"\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
1159
		if (isset($radiusip4))
1160
			fwrite($fd,"\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
1161
		
1162

    
1163
		fclose($fd);
1164
		unlock($cprdsrvlck);
1165
	}
1166
}
1167

    
1168
/* read RADIUS servers into array */
1169
function captiveportal_get_radius_servers() {
1170
	global $g, $cpzone;
1171

    
1172
	$cprdsrvlck = lock("captiveportalradius{$cpzone}");
1173
	if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
1174
		$radiusservers = array();
1175
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", 
1176
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1177
		if ($cpradiusdb) {
1178
			foreach($cpradiusdb as $cpradiusentry) {
1179
				$line = trim($cpradiusentry);
1180
				if ($line) {
1181
					$radsrv = array();
1182
						list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key'], $context) = explode(",",$line);
1183
				}
1184
				if (empty($context)) {
1185
					if (!is_array($radiusservers['first']))
1186
						$radiusservers['first'] = array();
1187
					$radiusservers['first'] = $radsrv;
1188
				} else {
1189
					if (!is_array($radiusservers[$context]))
1190
						$radiusservers[$context] = array();
1191
					$radiusservers[$context][] = $radsrv;
1192
				}
1193
			}
1194
		}
1195
		unlock($cprdsrvlck);
1196
		return $radiusservers;
1197
	}
1198

    
1199
	unlock($cprdsrvlck);
1200
	return false;
1201
}
1202

    
1203
/* log successful captive portal authentication to syslog */
1204
/* part of this code from php.net */
1205
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1206
	// Log it
1207
	if (!$message)
1208
		$message = "{$status}: {$user}, {$mac}, {$ip}";
1209
	else {
1210
		$message = trim($message);
1211
		$message = "{$status}: {$user}, {$mac}, {$ip}, {$message}";
1212
	}
1213
	captiveportal_syslog($message);
1214
}
1215

    
1216
/* log simple messages to syslog */
1217
function captiveportal_syslog($message) {
1218
	global $cpzone;
1219

    
1220
	$message = trim($message);
1221
	$message .= "Zone: {$cpzone} - {$message}";
1222
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1223
	// Log it
1224
	syslog(LOG_INFO, $message);
1225
	closelog();
1226
}
1227

    
1228
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1229
	global $g, $config;
1230

    
1231
	$pipeno = captiveportal_get_next_dn_ruleno();
1232

    
1233
	/* If the pool is empty, return appropriate message and fail authentication */
1234
	if (empty($pipeno)) {
1235
		$auth_list = array();
1236
		$auth_list['auth_val'] = 1;
1237
		$auth_list['error'] = "System reached maximum login capacity";
1238
		return $auth_list;
1239
	}
1240

    
1241
	$radiusservers = captiveportal_get_radius_servers();
1242

    
1243
	if (is_null($radiusctx))
1244
		$radiusctx = 'first';
1245

    
1246
	$auth_list = RADIUS_AUTHENTICATION($username,
1247
		$password,
1248
		$radiusservers[$radiusctx],
1249
		$clientip,
1250
		$clientmac,
1251
		$pipeno);
1252

    
1253
	if ($auth_list['auth_val'] == 2) {
1254
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1255
		$sessionid = portal_allow($clientip,
1256
			$clientmac,
1257
			$username,
1258
			$password,
1259
			$auth_list,
1260
			$pipeno,
1261
			$radiusctx);
1262
	} else {
1263
	         captiveportal_free_dn_ruleno($pipeno);
1264
	       }
1265

    
1266
	return $auth_list;
1267
}
1268

    
1269
function captiveportal_opendb() {
1270
	global $g, $cpzone;
1271

    
1272
	if (file_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db"))
1273
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1274
	else {
1275
		$errormsg = "";
1276
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1277
		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)) {
1278
			@sqlite_exec($DB, "CREATE UNIQUE INDEX idx_active ON captiveportal (sessionid, username)");
1279
			@sqlite_exec($DB, "CREATE INDEX user ON captiveportal (username)");
1280
			@sqlite_exec($DB, "CREATE INDEX ip ON captiveportal (ip)");
1281
			@sqlite_exec($DB, "CREATE INDEX starttime ON captiveportal (allow_time)");
1282
			@sqlite_exec($DB, "CREATE INDEX serviceid ON captiveportal (serviceid)");
1283
		} else
1284
			captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$errormsg}");
1285
	}
1286

    
1287
	return $DB;
1288
}
1289

    
1290
/* read captive portal DB into array */
1291
function captiveportal_read_db($query = "") {
1292

    
1293
	$DB = captiveportal_opendb();
1294
	if ($DB) {
1295
		sqlite_exec($DB, "BEGIN");
1296
		if (!empty($query))
1297
			$cpdb = @sqlite_array_query($DB, "SELECT * FROM captiveportal {$query}", SQLITE_NUM);
1298
		else {
1299
			$response = @sqlite_unbuffered_query($DB, "SELECT * FROM captiveportal", SQLITE_NUM);
1300
			$cpdb = @sqlite_fetch_all($response, SQLITE_NUM);
1301
		}
1302
		sqlite_exec($DB, "END");
1303
		@sqlite_close($DB);
1304
	}
1305
	if (!$cpdb)
1306
		$cpdb = array();
1307

    
1308
	return $cpdb;
1309
}
1310

    
1311
function captiveportal_remove_entries($remove) {
1312

    
1313
	if (!is_array($remove) || empty($remove))
1314
		return;
1315

    
1316
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1317
	foreach($remove as $idx => $unindex) {
1318
		$query .= "'{$unindex}'";
1319
		if ($idx < (count($remove) - 1))
1320
			$query .= ",";
1321
	}
1322
	$query .= ")";
1323
	captiveportal_write_db($query);
1324
}
1325

    
1326
/* write captive portal DB */
1327
function captiveportal_write_db($queries) {
1328
	global $g;
1329

    
1330
	if (is_array($queries))
1331
		$query = implode(";", $queries);
1332
	else
1333
		$query = $queries;
1334

    
1335
	$DB = captiveportal_opendb();
1336
	if ($DB) {
1337
		$error_msg = "";
1338
		sqlite_exec($DB, "BEGIN TRANSACTION");
1339
		$result = @sqlite_exec($DB, $query, $error_msg);
1340
		if (!$result)
1341
			captiveportal_syslog("Trying to modify DB returned error: {$error_msg}");
1342
		else
1343
			sqlite_exec($DB, "END TRANSACTION");
1344
		@sqlite_close($DB);
1345
		return $result;
1346
	} else
1347
		return true;
1348
}
1349

    
1350
function captiveportal_write_elements() {
1351
	global $g, $config, $cpzone;
1352
	
1353
	$cpcfg = $config['captiveportal'][$cpzone];
1354

    
1355
	if (!is_dir($g['captiveportal_element_path']))
1356
		@mkdir($g['captiveportal_element_path']);
1357

    
1358
	if (is_array($cpcfg['element'])) {
1359
		conf_mount_rw();
1360
		foreach ($cpcfg['element'] as $data) {
1361
			if (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1362
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1363
				return 1;
1364
			}
1365
			if (!file_exists("{$g['captiveportal_path']}/{$data['name']}"))
1366
				@symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1367
		}
1368
		conf_mount_ro();
1369
	}
1370
	
1371
	return 0;
1372
}
1373

    
1374
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1375
	global $cpzone;
1376

    
1377
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1378
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1379
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1380
		for ($ridx = $rulenos_start; $ridx < $rulenos_range_max; $ridx++) {
1381
			if ($rules[$ridx] == $cpzone) {
1382
				$rules[$ridx] = false;
1383
				$ridx++;
1384
				$rules[$ridx] = false;
1385
			}
1386
		}
1387
	}
1388
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1389
	unlock($cpruleslck);
1390
}
1391

    
1392
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1393
	global $config, $g, $cpzone;
1394

    
1395
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1396
	$ruleno = 0;
1397
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1398
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1399
		for ($ridx = $rulenos_start; $ridx < $rulenos_range_max; $ridx++) {
1400
			if ($rules[$ridx]) {
1401
				$ridx++;
1402
				continue;
1403
			}
1404
			$ruleno = $ridx;
1405
			$rules[$ridx] = $cpzone;
1406
			$rules[++$ridx] = $cpzone;
1407
			break;
1408
		}
1409
	} else {
1410
		$rules = array_pad(array(), $rulenos_range_max, false);
1411
		$ruleno = $rulenos_start;
1412
		$rules[$rulenos_start] = $cpzone;
1413
		$rules[++$rulenos_start] = $cpzone;
1414
	}
1415
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1416
	unlock($cpruleslck);
1417

    
1418
	return $ruleno;
1419
}
1420

    
1421
function captiveportal_free_dn_ruleno($ruleno) {
1422
	global $config, $g;
1423

    
1424
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1425
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1426
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1427
		$rules[$ruleno] = false;
1428
		$rules[++$ruleno] = false;
1429
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1430
	}
1431
	unlock($cpruleslck);
1432
}
1433

    
1434
function captiveportal_get_dn_passthru_ruleno($value) {
1435
	global $config, $g, $cpzone;
1436

    
1437
	$cpcfg = $config['captiveportal'][$cpzone];
1438
	if(!isset($cpcfg['enable']))
1439
		return NULL;
1440

    
1441
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1442
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1443
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1444
		$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`);
1445
		if ($rules[$ruleno]) {
1446
			unlock($cpruleslck);
1447
			return $ruleno;
1448
		}
1449
	}
1450

    
1451
	unlock($cpruleslck);
1452
	return NULL;
1453
}
1454

    
1455
/*
1456
 * This function will calculate the lowest free firewall ruleno
1457
 * within the range specified based on the actual logged on users
1458
 *
1459
 */
1460
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1461
	global $config, $g, $cpzone;
1462

    
1463
	$cpcfg = $config['captiveportal'][$cpzone];
1464
	if(!isset($cpcfg['enable']))
1465
		return NULL;
1466

    
1467
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1468
	$ruleno = 0;
1469
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1470
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1471
		for ($ridx = 2; $ridx < ($rulenos_range_max - $rulenos_start); $ridx++) {
1472
			if ($rules[$ridx]) {
1473
				/* 
1474
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1475
				 * and the out pipe ruleno + 1.
1476
				 */
1477
				$ridx++;
1478
				continue;
1479
			}
1480
			$ruleno = $ridx;
1481
			$rules[$ridx] = "used";
1482
			$rules[++$ridx] = "used";
1483
			break;
1484
		}
1485
	} else {
1486
		$rules = array_pad(array(), $rulenos_range_max, false);
1487
		$rules[$rulenos_start] = "used";
1488
		$rules[++$rulenos_start] = "used";
1489
		$ruleno = 2;
1490
	}
1491
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1492
	unlock($cpruleslck);
1493
	return $ruleno;
1494
}
1495

    
1496
function captiveportal_free_ipfw_ruleno($ruleno) {
1497
	global $config, $g, $cpzone;
1498

    
1499
	$cpcfg = $config['captiveportal'][$cpzone];
1500
	if(!isset($cpcfg['enable']))
1501
		return NULL;
1502

    
1503
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1504
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1505
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1506
		$rules[$ruleno] = false;
1507
		$rules[++$ruleno] = false;
1508
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1509
	}
1510
	unlock($cpruleslck);
1511
}
1512

    
1513
function captiveportal_get_ipfw_passthru_ruleno($value) {
1514
	global $config, $g, $cpzone;
1515

    
1516
	$cpcfg = $config['captiveportal'][$cpzone];
1517
	if(!isset($cpcfg['enable']))
1518
		return NULL;
1519

    
1520
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1521
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1522
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1523
		$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`);
1524
		if ($rules[$ruleno]) {
1525
			unlock($cpruleslck);
1526
			return $ruleno;
1527
		}
1528
	}
1529

    
1530
	unlock($cpruleslck);
1531
	return NULL;
1532
}
1533

    
1534
/**
1535
 * This function will calculate the traffic produced by a client
1536
 * based on its firewall rule
1537
 *
1538
 * Point of view: NAS
1539
 *
1540
 * Input means: from the client
1541
 * Output means: to the client
1542
 *
1543
 */
1544

    
1545
function getVolume($ip) {
1546
	global $config, $cpzone;
1547

    
1548
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1549
	$volume = array();
1550
	// Initialize vars properly, since we don't want NULL vars
1551
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1552

    
1553
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 1, $ip);
1554
	if (is_array($ipfw)) {
1555
		if ($reverse) {
1556
			$volume['output_pkts'] = $ipfw['packets'];
1557
			$volume['output_bytes'] = $ipfw['bytes'];
1558
		}
1559
		else {
1560
			$volume['input_pkts'] = $ipfw['packets'];
1561
			$volume['input_bytes'] = $ipfw['bytes'];
1562
		}
1563
	}
1564

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

    
1577
	return $volume;
1578
}
1579

    
1580
/**
1581
 * Get the NAS-IP-Address based on the current wan address
1582
 *
1583
 * Use functions in interfaces.inc to find this out
1584
 *
1585
 */
1586

    
1587
function getNasIP()
1588
{
1589
	global $config, $cpzone;
1590

    
1591
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1592
			$nasIp = get_interface_ip();
1593
	} else {
1594
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1595
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1596
		else
1597
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1598
	}
1599
		
1600
	if(!is_ipaddr($nasIp))
1601
		$nasIp = "0.0.0.0";
1602

    
1603
	return $nasIp;
1604
}
1605

    
1606
function portal_ip_from_client_ip($cliip) {
1607
	global $config, $cpzone;
1608

    
1609
	$isipv6 = is_ipaddrv6($cliip);
1610
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1611
	foreach ($interfaces as $cpif) {
1612
		if ($isipv6) {
1613
			$ip = get_interface_ipv6($cpif);
1614
			$sn = get_interface_subnetv6($cpif);
1615
		} else {
1616
			$ip = get_interface_ip($cpif);
1617
			$sn = get_interface_subnet($cpif);
1618
		}
1619
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1620
			return $ip;
1621
	}
1622

    
1623
	$inet = ($isipv6) ? '-inet6' : '-inet';
1624
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1625
	$iface = trim($iface, "\n");
1626
	if (!empty($iface)) {
1627
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1628
		if (is_ipaddr($ip))
1629
			return $ip;
1630
	}
1631

    
1632
	// doesn't match up to any particular interface
1633
	// so let's set the portal IP to what PHP says 
1634
	// the server IP issuing the request is. 
1635
	// allows same behavior as 1.2.x where IP isn't 
1636
	// in the subnet of any CP interface (static routes, etc.)
1637
	// rather than forcing to DNS hostname resolution
1638
	$ip = $_SERVER['SERVER_ADDR'];
1639
	if (is_ipaddr($ip))
1640
		return $ip;
1641

    
1642
	return false;
1643
}
1644

    
1645
function portal_hostname_from_client_ip($cliip) {
1646
	global $config, $cpzone;
1647

    
1648
	$cpcfg = $config['captiveportal'][$cpzone];
1649

    
1650
	if (isset($cpcfg['httpslogin'])) {
1651
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
1652
		$ourhostname = $cpcfg['httpsname'];
1653
		
1654
		if ($listenporthttps != 443)
1655
			$ourhostname .= ":" . $listenporthttps;
1656
	} else {
1657
		$listenporthttp  = $cpcfg['listenporthttp']  ? $cpcfg['listenporthttp']  : $cpcfg['zoneid'];
1658
		$ifip = portal_ip_from_client_ip($cliip);
1659
		if (!$ifip)
1660
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1661
		else
1662
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1663
		
1664
		if ($listenporthttp != 80)
1665
			$ourhostname .= ":" . $listenporthttp;
1666
	}
1667
	
1668
	return $ourhostname;
1669
}
1670

    
1671
/* functions move from index.php */
1672

    
1673
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1674
	global $g, $config, $cpzone;
1675

    
1676
	/* Get captive portal layout */
1677
	if ($type == "redir") {
1678
		header("Location: {$redirurl}");
1679
		return;
1680
	} else if ($type == "login")
1681
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1682
	else
1683
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1684

    
1685
	$cpcfg = $config['captiveportal'][$cpzone];
1686

    
1687
	/* substitute the PORTAL_REDIRURL variable */
1688
	if ($cpcfg['preauthurl']) {
1689
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1690
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1691
	}
1692

    
1693
	/* substitute other variables */
1694
	$ourhostname = portal_hostname_from_client_ip($clientip);
1695
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1696
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1697
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1698

    
1699
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1700
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1701
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1702
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1703
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1704

    
1705
	// Special handling case for captive portal master page so that it can be ran 
1706
	// through the PHP interpreter using the include method above.  We convert the
1707
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1708
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1709
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1710
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1711
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1712
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1713
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1714
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1715

    
1716
	echo $htmltext;
1717
}
1718

    
1719
function portal_mac_radius($clientmac,$clientip) {
1720
	global $config, $cpzone;
1721

    
1722
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1723

    
1724
	/* authentication against the radius server */
1725
	$username = mac_format($clientmac);
1726
	$auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1727
	if ($auth_list['auth_val'] == 2)
1728
		return TRUE;
1729

    
1730
	if (!empty($auth_list['url_redirection']))
1731
		portal_reply_page($auth_list['url_redirection'], "redir");
1732

    
1733
	return FALSE;
1734
}
1735

    
1736
function captiveportal_reapply_attributes($cpentry, $attributes) {
1737
	global $config, $cpzone, $g;
1738

    
1739
	$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1740
	$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1741
	$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1742
	$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1743
	$bw_up_pipeno = $cpentry[1];
1744
	$bw_down_pipeno = $cpentry[1]+1;
1745

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

    
1750
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1751
}
1752

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

    
1756
	// Ensure we create an array if we are missing attributes
1757
	if (!is_array($attributes))
1758
		$attributes = array();
1759

    
1760
	unset($sessionid);
1761

    
1762
	/* Do not allow concurrent login execution. */
1763
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1764

    
1765
	if ($attributes['voucher'])
1766
		$remaining_time = $attributes['session_timeout'];
1767

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

    
1814
	/* read in client database */
1815
	$query = "WHERE ip = '{$clientip}'";
1816
	$tmpusername = strtolower($username);
1817
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins']))
1818
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
1819
	$cpdb = captiveportal_read_db($query);
1820

    
1821
	/* Snapshot the timestamp */
1822
	$allow_time = time();
1823
	$radiusservers = captiveportal_get_radius_servers();
1824
	$unsetindexes = array();
1825
	if (is_null($radiusctx))
1826
		$radiusctx = 'first';
1827

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

    
1847
			/* This user was already logged in so we disconnect the old one */
1848
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1849
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1850
			$unsetindexes[] = $cpentry[5];
1851
			break;
1852
		}
1853
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1854
			/* on the same username */
1855
			if (strcasecmp($cpentry[4], $username) == 0) {
1856
				/* This user was already logged in so we disconnect the old one */
1857
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1858
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1859
				$unsetindexes[] = $cpentry[5];
1860
				break;
1861
			}
1862
		}
1863
	}
1864
	unset($cpdb);
1865

    
1866
	if (!empty($unsetindexes))
1867
		captiveportal_remove_entries($unsetindexes);
1868

    
1869
	if ($attributes['voucher'] && $remaining_time <= 0)
1870
		return 0;       // voucher already used and no time left
1871

    
1872
	if (!isset($sessionid)) {
1873
		/* generate unique session ID */
1874
		$tod = gettimeofday();
1875
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1876

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

    
1905
			/* if the pool is empty, return appropriate message and exit */
1906
			if (is_null($pipeno)) {
1907
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1908
				log_error("Zone: {$cpzone} - WARNING!  Captive portal has reached maximum login capacity");
1909
				unlock($cpdblck);
1910
				return;
1911
			}
1912

    
1913
			$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1914
			$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1915
			$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1916
			$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1917

    
1918
			$bw_up_pipeno = $pipeno;
1919
			$bw_down_pipeno = $pipeno + 1;
1920
			//$bw_up /= 1000; // Scale to Kbit/s
1921
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1922
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1923

    
1924
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
1925
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1926
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
1927
			else
1928
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
1929

    
1930
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1931
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
1932
			else
1933
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
1934

    
1935
			if ($attributes['voucher'])
1936
				$attributes['session_timeout'] = $remaining_time;
1937
			
1938
			/* handle empty attributes */
1939
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
1940
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
1941
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
1942
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
1943

    
1944
			/* escape username */
1945
			$safe_username = sqlite_escape_string($username);
1946

    
1947
			/* encode password in Base64 just in case it contains commas */
1948
			$bpassword = base64_encode($password);
1949
			$insertquery  = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval) ";
1950
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
1951
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval})";
1952

    
1953
			/* store information to database */
1954
			captiveportal_write_db($insertquery);
1955
			unlock($cpdblck);
1956

    
1957
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
1958
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
1959
				if ($acct_val == 1)
1960
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1961
			}
1962
		}
1963
	} else
1964
		unlock($cpdblck);
1965

    
1966
	if ($writecfg == true)
1967
		write_config();
1968

    
1969
	/* redirect user to desired destination */
1970
	if (!empty($attributes['url_redirection']))
1971
		$my_redirurl = $attributes['url_redirection'];
1972
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
1973
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
1974
	else
1975
		$my_redirurl = $redirurl;
1976

    
1977
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
1978
		$ourhostname = portal_hostname_from_client_ip($clientip);
1979
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
1980
		$logouturl = "{$protocol}{$ourhostname}/";
1981

    
1982
		if (isset($attributes['reply_message']))
1983
			$message = $attributes['reply_message'];
1984
		else
1985
			$message = 0;
1986

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

    
1989
	} else {
1990
		header("Location: " . $my_redirurl);
1991
	}
1992

    
1993
	return $sessionid;
1994
}
1995

    
1996

    
1997
/*
1998
 * Used for when pass-through credits are enabled.
1999
 * Returns true when there was at least one free login to deduct for the MAC.
2000
 * Expired entries are removed as they are seen.
2001
 * Active entries are updated according to the configuration.
2002
 */
2003
function portal_consume_passthrough_credit($clientmac) {
2004
	global $config, $cpzone;
2005

    
2006
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
2007
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
2008
	else
2009
		return false;
2010

    
2011
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
2012
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2013
	else
2014
		return false;
2015

    
2016
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
2017
		return false;
2018

    
2019
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2020

    
2021
	/*
2022
	 * Read database of used MACs.  Lines are a comma-separated list
2023
	 * of the time, MAC, then the count of pass-through credits remaining.
2024
	 */
2025
	$usedmacs = captiveportal_read_usedmacs_db();
2026

    
2027
	$currenttime = time();
2028
	$found = false;
2029
	foreach ($usedmacs as $key => $usedmac) {
2030
		$usedmac = explode(",", $usedmac);
2031

    
2032
		if ($usedmac[1] == $clientmac) {
2033
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2034
				if ($usedmac[2] < 1) {
2035
					if ($updatetimeouts) {
2036
						$usedmac[0] = $currenttime;
2037
						unset($usedmacs[$key]);
2038
						$usedmacs[] = implode(",", $usedmac);
2039
						captiveportal_write_usedmacs_db($usedmacs);
2040
					}
2041

    
2042
					return false;
2043
				} else {
2044
					$usedmac[2] -= 1;
2045
					$usedmacs[$key] = implode(",", $usedmac);
2046
				}
2047

    
2048
				$found = true;
2049
			} else
2050
				unset($usedmacs[$key]);
2051

    
2052
			break;
2053
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2054
				unset($usedmacs[$key]);
2055
	}
2056

    
2057
	if (!$found) {
2058
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2059
		$usedmacs[] = implode(",", $usedmac);
2060
	}
2061

    
2062
	captiveportal_write_usedmacs_db($usedmacs);
2063
	return true;
2064
}
2065

    
2066
function captiveportal_read_usedmacs_db() {
2067
	global $g, $cpzone;
2068

    
2069
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2070
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2071
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2072
		if (!$usedmacs)
2073
			$usedmacs = array();
2074
	} else
2075
		$usedmacs = array();
2076

    
2077
	unlock($cpumaclck);
2078
	return $usedmacs;
2079
}
2080

    
2081
function captiveportal_write_usedmacs_db($usedmacs) {
2082
	global $g, $cpzone;
2083

    
2084
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2085
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2086
	unlock($cpumaclck);
2087
}
2088

    
2089
function captiveportal_blocked_mac($mac) {
2090
	global $config, $g, $cpzone;
2091

    
2092
	if (empty($mac) || !is_macaddr($mac))
2093
		return false;
2094

    
2095
	if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
2096
		return false;
2097

    
2098
	foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac)
2099
		if (($passthrumac['action'] == 'block') &&
2100
		    ($passthrumac['mac'] == strtolower($mac)))
2101
			return true;
2102

    
2103
	return false;
2104

    
2105
}
2106

    
2107
function captiveportal_send_server_accounting($off = false) {
2108
	global $cpzone, $config;
2109

    
2110
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
2111
		return;
2112
	}
2113
	if ($off) {
2114
		$racct = new Auth_RADIUS_Acct_Off;
2115
	} else {
2116
		$racct = new Auth_RADIUS_Acct_On;
2117
	}
2118
	$radiusservers = captiveportal_get_radius_servers();
2119
	if (empty($radiusservers)) {
2120
		return;
2121
	}
2122
	foreach ($radiusservers['first'] as $radsrv) {
2123
		// Add a new server to our instance
2124
		$racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
2125
	}
2126
	if (PEAR::isError($racct->start())) {
2127
		$retvalue['acct_val'] = 1;
2128
		$retvalue['error'] = $racct->getMessage();
2129

    
2130
		// If we encounter an error immediately stop this function and go back
2131
		$racct->close();
2132
		return $retvalue;
2133
	}
2134
	// Send request
2135
	$result = $racct->send();
2136
	// Evaluation of the response
2137
	// 5 -> Accounting-Response
2138
	// See RFC2866 for this.
2139
	if (PEAR::isError($result)) {
2140
		$retvalue['acct_val'] = 1;
2141
		$retvalue['error'] = $result->getMessage();
2142
	} else if ($result === true) {
2143
		$retvalue['acct_val'] = 5 ;
2144
	} else {
2145
		$retvalue['acct_val'] = 1 ;
2146
	}
2147

    
2148
	$racct->close();
2149
	return $retvalue;
2150
}
2151
?>
(8-8/66)