Project

General

Profile

Download (73.4 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
	$unsetindexes = array();
636
	$voucher_needs_sync = false;
637
	/* 
638
	 * Snapshot the time here to use for calculation to speed up the process.
639
	 * If something is missed next run will catch it!
640
	 */
641
	$pruning_time = time();
642
	$stop_time = $pruning_time;
643
	foreach ($cpdb as $cpentry) {
644

    
645
		$timedout = false;
646
		$term_cause = 1;
647
		if (empty($cpentry[11]))
648
			$cpentry[11] = 'first';
649
		$radiusservers = $radiussrvs[$cpentry[11]];
650

    
651
		/* hard timeout? */
652
		if ($timeout) {
653
			if (($pruning_time - $cpentry[0]) >= $timeout) {
654
				$timedout = true;
655
				$term_cause = 5; // Session-Timeout
656
			}
657
		}
658

    
659
		/* Session-Terminate-Time */
660
		if (!$timedout && !empty($cpentry[9])) {
661
			if ($pruning_time >= $cpentry[9]) {
662
				$timedout = true;
663
				$term_cause = 5; // Session-Timeout
664
			}
665
		}
666

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

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

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

    
700
		if ($timedout) {
701
			captiveportal_disconnect($cpentry, $radiusservers,$term_cause,$stop_time);
702
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
703
			$unsetindexes[] = $cpentry[5];
704
		}
705

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

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

    
769
	captiveportal_prune_old_automac();
770

    
771
	if ($voucher_needs_sync == true)
772
		/* Triger a sync of the vouchers on config */
773
		send_event("service sync vouchers");
774

    
775
	/* write database */
776
	if (!empty($unsetindexes))
777
		captiveportal_remove_entries($unsetindexes);
778
}
779

    
780
function captiveportal_prune_old_automac() {
781
	global $g, $config, $cpzone, $cpzoneid;
782

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

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

    
848
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
849

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

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

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

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

    
894
}
895

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

    
900
	$radiusservers = captiveportal_get_radius_servers();
901

    
902
	/* read database */
903
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
904

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

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

    
919
/* send RADIUS acct stop for all current clients */
920
function captiveportal_radius_stop_all() {
921
	global $config, $cpzone;
922

    
923
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable']))
924
		return;
925

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

    
946
function captiveportal_passthrumac_configure_entry($macent) {
947
	global $config, $g, $cpzone;
948

    
949
	$bwUp = 0;
950
	if (!empty($macent['bw_up']))
951
		$bwUp = $macent['bw_up'];
952
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultup']))
953
		$bwUp = $config['captiveportal'][$cpzone]['bwdefaultup'];
954
	$bwDown = 0;
955
	if (!empty($macent['bw_down']))
956
		$bwDown = $macent['bw_down'];
957
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultdn']))
958
		$bwDown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
959

    
960
	$ruleno = captiveportal_get_next_ipfw_ruleno();
961

    
962
	if ($macent['action'] == 'pass') {
963
		$pipeno = captiveportal_get_next_dn_ruleno();
964

    
965
		$pipeup = $pipeno;
966
		$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16");
967
		$pipedown = $pipeno + 1;
968
		$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
969

    
970
		$rules = "add {$ruleno} pipe {$pipeup} ip from any to any MAC any {$macent['mac']}\n";
971
		$ruleno++;
972
		$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC {$macent['mac']} any\n";
973
	}
974

    
975
	return $rules;
976
}
977

    
978
function captiveportal_passthrumac_delete_entry($macent) {
979
	$rules = "";
980

    
981
	if ($macent['action'] == 'pass') {
982
		$ruleno = captiveportal_get_ipfw_passthru_ruleno($macent['mac']);
983

    
984
		if (!$ruleno)
985
			return $rules;
986

    
987
		captiveportal_free_ipfw_ruleno($ruleno);
988

    
989
		$rules .= "delete {$ruleno}\n";
990
		$rules .= "delete " . ++$ruleno . "\n";
991

    
992
		$pipeno = captiveportal_get_dn_passthru_ruleno($macent['mac']);
993

    
994
		if (!empty($pipeno)) {
995
			captiveportal_free_dn_ruleno($pipeno);
996
			$rules .= "pipe delete " . $pipeno . "\n";
997
			$rules .= "pipe delete " . ++$pipeno . "\n";
998
		}
999
	}
1000

    
1001
	return $rules;
1002
}
1003

    
1004
function captiveportal_passthrumac_configure($lock = false) {
1005
	global $config, $g, $cpzone;
1006

    
1007
	$rules = "";
1008

    
1009
	if (is_array($config['captiveportal'][$cpzone]['passthrumac']))
1010
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent)
1011
			$rules .= captiveportal_passthrumac_configure_entry($macent);
1012

    
1013
	return $rules;
1014
}
1015

    
1016
function captiveportal_passthrumac_findbyname($username) {
1017
	global $config, $cpzone;
1018

    
1019
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1020
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1021
			if ($macent['username'] == $username)
1022
				return $macent;
1023
		}
1024
	}
1025
	return NULL;
1026
}
1027

    
1028
/* 
1029
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1030
 */
1031
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
1032
	global $g;
1033

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

    
1047
	$rules = "";
1048
	$cp_filterdns_conf = "";
1049
	$enBwup = 0;
1050
	if (!empty($ipent['bw_up']))
1051
		$enBwup = iintval($ipent['bw_up']);
1052
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultup']))
1053
		$enBwup = $config['captiveportal'][$cpzone]['bwdefaultup'];
1054
	$enBwdown = 0;
1055
	if (!empty($ipent['bw_down']))
1056
		$enBwdown = intval($ipent['bw_down']);
1057
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultdn']))
1058
		$enBwdown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
1059

    
1060
	$pipeno = captiveportal_get_next_dn_ruleno();
1061
	$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$enBwup}Kbit/s queue 100 buckets 16");
1062
	$pipedown = $pipeno + 1;
1063
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16");
1064
	if ($ishostname === true) {
1065
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
1066
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
1067
		if (!is_ipaddr($ipaddress))
1068
			return array("", $cp_filterdns_conf);
1069
	}
1070
	$subnet = "";
1071
	if (!empty($ipent['sn']))
1072
		$subnet = "/{$ipent['sn']}";
1073
	$rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
1074
	$rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
1075

    
1076
	if ($ishostname === true)
1077
		return array($rules, $cp_filterdns_conf);
1078
	else
1079
		return $rules;
1080
}
1081

    
1082
function captiveportal_allowedhostname_configure() {
1083
	global $config, $g, $cpzone;
1084

    
1085
	$rules = "";
1086
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1087
		$rules = "\n# captiveportal_allowedhostname_configure()\n";
1088
		$cp_filterdns_conf = "";
1089
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) {
1090
			$tmprules = captiveportal_allowedip_configure_entry($hostnameent, true);
1091
			$rules .= $tmprules[0];
1092
			$cp_filterdns_conf .= $tmprules[1];
1093
		}
1094
		$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1095
		@file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1096
		unset($cp_filterdns_conf);
1097
		if (isvalidpid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid"))
1098
			sigkillbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid", "HUP");
1099
		else
1100
			mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzone} -d 1");
1101
	} else {
1102
		killbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1103
		@unlink("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1104
	}
1105

    
1106
	return $rules;
1107
}
1108

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

    
1112
	$rules = "";
1113
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1114
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1115
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1116
	}
1117

    
1118
	return $rules;
1119
}
1120

    
1121
/* get last activity timestamp given client IP address */
1122
function captiveportal_get_last_activity($ip, $mac = NULL) {
1123
	global $cpzone;
1124

    
1125
	$ipfwoutput = pfSense_ipfw_getTablestats($cpzone, 1, $ip, $mac);
1126
	/* Reading only from one of the tables is enough of approximation. */
1127
	if (is_array($ipfwoutput)) {
1128
		return $ipfwoutput['timestamp'];
1129
	}
1130

    
1131
	return 0;
1132
}
1133

    
1134
function captiveportal_init_radius_servers() {
1135
	global $config, $g, $cpzone;
1136

    
1137
	/* generate radius server database */
1138
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1139
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1140
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1141
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1142
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1143
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1144

    
1145
		if ($config['captiveportal'][$cpzone]['radiusport'])
1146
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1147
		else
1148
			$radiusport = 1812;
1149
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1150
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1151
		else
1152
			$radiusacctport = 1813;
1153
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1154
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1155
		else
1156
			$radiusport2 = 1812;
1157
		if ($config['captiveportal'][$cpzone]['radiusport3'])
1158
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1159
		else
1160
			$radiusport3 = 1812;
1161
		if ($config['captiveportal'][$cpzone]['radiusport4'])
1162
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1163
		else
1164
			$radiusport4 = 1812;
1165

    
1166
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1167
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1168
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1169
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1170

    
1171
		$cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX);
1172
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
1173
		if (!$fd) {
1174
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1175
			unlock($cprdsrvlck);
1176
			return 1;
1177
		}
1178
		if (isset($radiusip))
1179
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
1180
		if (isset($radiusip2))
1181
			fwrite($fd,"\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
1182
		if (isset($radiusip3))
1183
			fwrite($fd,"\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
1184
		if (isset($radiusip4))
1185
			fwrite($fd,"\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
1186
		
1187

    
1188
		fclose($fd);
1189
		unlock($cprdsrvlck);
1190
	}
1191
}
1192

    
1193
/* read RADIUS servers into array */
1194
function captiveportal_get_radius_servers() {
1195
	global $g, $cpzone;
1196

    
1197
	$cprdsrvlck = lock("captiveportalradius{$cpzone}");
1198
	if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
1199
		$radiusservers = array();
1200
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", 
1201
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1202
		if ($cpradiusdb) {
1203
			foreach($cpradiusdb as $cpradiusentry) {
1204
				$line = trim($cpradiusentry);
1205
				if ($line) {
1206
					$radsrv = array();
1207
						list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key'], $context) = explode(",",$line);
1208
				}
1209
				if (empty($context)) {
1210
					if (!is_array($radiusservers['first']))
1211
						$radiusservers['first'] = array();
1212
					$radiusservers['first'] = $radsrv;
1213
				} else {
1214
					if (!is_array($radiusservers[$context]))
1215
						$radiusservers[$context] = array();
1216
					$radiusservers[$context][] = $radsrv;
1217
				}
1218
			}
1219
		}
1220
		unlock($cprdsrvlck);
1221
		return $radiusservers;
1222
	}
1223

    
1224
	unlock($cprdsrvlck);
1225
	return false;
1226
}
1227

    
1228
/* log successful captive portal authentication to syslog */
1229
/* part of this code from php.net */
1230
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1231
	// Log it
1232
	if (!$message)
1233
		$message = "{$status}: {$user}, {$mac}, {$ip}";
1234
	else {
1235
		$message = trim($message);
1236
		$message = "{$status}: {$user}, {$mac}, {$ip}, {$message}";
1237
	}
1238
	captiveportal_syslog($message);
1239
}
1240

    
1241
/* log simple messages to syslog */
1242
function captiveportal_syslog($message) {
1243
	global $cpzone;
1244

    
1245
	$message = trim($message);
1246
	$message .= "Zone: {$cpzone} - {$message}";
1247
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1248
	// Log it
1249
	syslog(LOG_INFO, $message);
1250
	closelog();
1251
}
1252

    
1253
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1254
	global $g, $config, $cpzoneid;
1255

    
1256
	$pipeno = captiveportal_get_next_dn_ruleno();
1257

    
1258
	/* If the pool is empty, return appropriate message and fail authentication */
1259
	if (empty($pipeno)) {
1260
		$auth_list = array();
1261
		$auth_list['auth_val'] = 1;
1262
		$auth_list['error'] = "System reached maximum login capacity";
1263
		return $auth_list;
1264
	}
1265

    
1266
	$radiusservers = captiveportal_get_radius_servers();
1267

    
1268
	if (is_null($radiusctx))
1269
		$radiusctx = 'first';
1270

    
1271
	$auth_list = RADIUS_AUTHENTICATION($username,
1272
		$password,
1273
		$radiusservers[$radiusctx],
1274
		$clientip,
1275
		$clientmac,
1276
		$pipeno);
1277

    
1278
	if ($auth_list['auth_val'] == 2) {
1279
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1280
		$sessionid = portal_allow($clientip,
1281
			$clientmac,
1282
			$username,
1283
			$password,
1284
			$auth_list,
1285
			$pipeno,
1286
			$radiusctx);
1287
	} else {
1288
	         captiveportal_free_dn_ruleno($pipeno);
1289
	       }
1290

    
1291
	return $auth_list;
1292
}
1293

    
1294
function captiveportal_opendb() {
1295
	global $g, $cpzone;
1296

    
1297
	if (file_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db"))
1298
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1299
	else {
1300
		$errormsg = "";
1301
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1302
		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, radiusctx TEXT) ", $errormsg)) {
1303
			@sqlite_exec($DB, "CREATE UNIQUE INDEX idx_active ON captiveportal (sessionid, username)");
1304
			@sqlite_exec($DB, "CREATE INDEX user ON captiveportal (username)");
1305
			@sqlite_exec($DB, "CREATE INDEX ip ON captiveportal (ip)");
1306
			@sqlite_exec($DB, "CREATE INDEX starttime ON captiveportal (allow_time)");
1307
			@sqlite_exec($DB, "CREATE INDEX serviceid ON captiveportal (serviceid)");
1308
		} else
1309
			captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$errormsg}");
1310
	}
1311

    
1312
	return $DB;
1313
}
1314

    
1315
/* read captive portal DB into array */
1316
function captiveportal_read_db($query = "") {
1317

    
1318
	$DB = captiveportal_opendb();
1319
	if ($DB) {
1320
		sqlite_exec($DB, "BEGIN");
1321
		if (!empty($query))
1322
			$cpdb = @sqlite_array_query($DB, "SELECT * FROM captiveportal {$query}", SQLITE_NUM);
1323
		else {
1324
			$response = @sqlite_unbuffered_query($DB, "SELECT * FROM captiveportal", SQLITE_NUM);
1325
			$cpdb = @sqlite_fetch_all($response, SQLITE_NUM);
1326
		}
1327
		sqlite_exec($DB, "END");
1328
		@sqlite_close($DB);
1329
	}
1330
	if (!$cpdb)
1331
		$cpdb = array();
1332

    
1333
	return $cpdb;
1334
}
1335

    
1336
function captiveportal_remove_entries($remove) {
1337

    
1338
	if (!is_array($remove) || empty($remove))
1339
		return;
1340

    
1341
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1342
	foreach($remove as $idx => $unindex) {
1343
		$query .= "'{$unindex}'";
1344
		if ($idx < (count($remove) - 1))
1345
			$query .= ",";
1346
	}
1347
	$query .= ")";
1348
	captiveportal_write_db($query);
1349
}
1350

    
1351
/* write captive portal DB */
1352
function captiveportal_write_db($queries) {
1353
	global $g;
1354

    
1355
	if (is_array($queries))
1356
		$query = implode(";", $queries);
1357
	else
1358
		$query = $queries;
1359

    
1360
	$DB = captiveportal_opendb();
1361
	if ($DB) {
1362
		$error_msg = "";
1363
		sqlite_exec($DB, "BEGIN TRANSACTION");
1364
		$result = @sqlite_exec($DB, $query, $error_msg);
1365
		if (!$result)
1366
			captiveportal_syslog("Trying to modify DB returned error: {$error_msg}");
1367
		else
1368
			sqlite_exec($DB, "END TRANSACTION");
1369
		@sqlite_close($DB);
1370
		return $result;
1371
	} else
1372
		return true;
1373
}
1374

    
1375
function captiveportal_write_elements() {
1376
	global $g, $config, $cpzone;
1377
	
1378
	$cpcfg = $config['captiveportal'][$cpzone];
1379

    
1380
	if (!is_dir($g['captiveportal_element_path']))
1381
		@mkdir($g['captiveportal_element_path']);
1382

    
1383
	if (is_array($cpcfg['element'])) {
1384
		conf_mount_rw();
1385
		foreach ($cpcfg['element'] as $data) {
1386
			if (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1387
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1388
				return 1;
1389
			}
1390
			if (!file_exists("{$g['captiveportal_path']}/{$data['name']}"))
1391
				@symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1392
		}
1393
		conf_mount_ro();
1394
	}
1395
	
1396
	return 0;
1397
}
1398

    
1399
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1400
	global $cpzone;
1401

    
1402
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1403
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1404
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1405
		$ridx = $rulenos_start;
1406
		while ($ridx < $rulenos_range_max) {
1407
			if ($rules[$ridx] == $cpzone) {
1408
				$rules[$ridx] = false;
1409
				$ridx++;
1410
				$rules[$ridx] = false;
1411
				$ridx++;
1412
			} else
1413
				$ridx += 2;
1414
		}
1415
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1416
		unset($rules);
1417
	}
1418
	unlock($cpruleslck);
1419
}
1420

    
1421
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1422
	global $config, $g, $cpzone;
1423

    
1424
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1425
	$ruleno = 0;
1426
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1427
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1428
		$ridx = $rulenos_start;
1429
		while ($ridx < $rulenos_range_max) {
1430
			if (empty($rules[$ridx])) {
1431
				$ruleno = $ridx;
1432
				$rules[$ridx] = $cpzone;
1433
				$ridx++;
1434
				$rules[$ridx] = $cpzone;
1435
				break;
1436
			} else {
1437
				$ridx += 2;
1438
			}
1439
		}
1440
	} else {
1441
		$rules = array_pad(array(), $rulenos_range_max, false);
1442
		$ruleno = $rulenos_start;
1443
		$rules[$rulenos_start] = $cpzone;
1444
		$rulenos_start++;
1445
		$rules[$rulenos_start] = $cpzone;
1446
	}
1447
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1448
	unlock($cpruleslck);
1449
	unset($rules);
1450

    
1451
	return $ruleno;
1452
}
1453

    
1454
function captiveportal_free_dn_ruleno($ruleno) {
1455
	global $config, $g;
1456

    
1457
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1458
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1459
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1460
		$rules[$ruleno] = false;
1461
		$ruleno++;
1462
		$rules[$ruleno] = false;
1463
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1464
		unset($rules);
1465
	}
1466
	unlock($cpruleslck);
1467
}
1468

    
1469
function captiveportal_get_dn_passthru_ruleno($value) {
1470
	global $config, $g, $cpzone, $cpzoneid;
1471

    
1472
	$cpcfg = $config['captiveportal'][$cpzone];
1473
	if(!isset($cpcfg['enable']))
1474
		return NULL;
1475

    
1476
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1477
	$ruleno = NULL;
1478
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1479
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1480
		unset($output);
1481
		$_gb = exec("/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", $output);
1482
		$ruleno = intval($output[0]);
1483
		if (!$rules[$ruleno])
1484
			$ruleno = NULL;
1485
		unset($rules);
1486
	}
1487
	unlock($cpruleslck);
1488

    
1489
	return $ruleno;
1490
}
1491

    
1492
/*
1493
 * This function will calculate the lowest free firewall ruleno
1494
 * within the range specified based on the actual logged on users
1495
 *
1496
 */
1497
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1498
	global $config, $g, $cpzone;
1499

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

    
1504
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1505
	$ruleno = 0;
1506
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1507
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1508
		$ridx = $rulenos_start;
1509
		while ($ridx < $rulenos_range_max) {
1510
			if (empty($rules[$ridx])) {
1511
				$ruleno = $ridx;
1512
				$rules[$ridx] = $cpzone;
1513
				$ridx++;
1514
				$rules[$ridx] = $cpzone;
1515
				break;
1516
			} else {
1517
				/* 
1518
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1519
				 * and the out pipe ruleno + 1.
1520
				 */
1521
				$ridx += 2;
1522
			}
1523
		}
1524
	} else {
1525
		$rules = array_pad(array(), $rulenos_range_max, false);
1526
		$ruleno = $rulenos_start;
1527
		$rules[$rulenos_start] = $cpzone;
1528
		$rulenos_start++;
1529
		$rules[$rulenos_start] = $cpzone;
1530
	}
1531
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1532
	unlock($cpruleslck);
1533
	unset($rules);
1534

    
1535
	return $ruleno;
1536
}
1537

    
1538
function captiveportal_free_ipfw_ruleno($ruleno) {
1539
	global $config, $g, $cpzone;
1540

    
1541
	$cpcfg = $config['captiveportal'][$cpzone];
1542
	if(!isset($cpcfg['enable']))
1543
		return NULL;
1544

    
1545
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1546
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1547
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1548
		$rules[$ruleno] = false;
1549
		$ruleno++;
1550
		$rules[$ruleno] = false;
1551
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1552
		unset($rules);
1553
	}
1554
	unlock($cpruleslck);
1555
}
1556

    
1557
function captiveportal_get_ipfw_passthru_ruleno($value) {
1558
	global $config, $g, $cpzone, $cpzoneid;
1559

    
1560
	$cpcfg = $config['captiveportal'][$cpzone];
1561
	if(!isset($cpcfg['enable']))
1562
		return NULL;
1563

    
1564
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1565
	$ruleno = NULL;
1566
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1567
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1568
		unset($output);
1569
		$_gb = exec("/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", $output);
1570
		$ruleno = intval($output[0]);
1571
		if (!$rules[$ruleno])
1572
			$ruleno = NULL;
1573
		unset($rules);
1574
	}
1575
	unlock($cpruleslck);
1576

    
1577
	return $ruleno;
1578
}
1579

    
1580
/**
1581
 * This function will calculate the traffic produced by a client
1582
 * based on its firewall rule
1583
 *
1584
 * Point of view: NAS
1585
 *
1586
 * Input means: from the client
1587
 * Output means: to the client
1588
 *
1589
 */
1590

    
1591
function getVolume($ip, $mac = NULL) {
1592
	global $config, $cpzone;
1593

    
1594
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1595
	$volume = array();
1596
	// Initialize vars properly, since we don't want NULL vars
1597
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1598

    
1599
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 1, $ip, $mac);
1600
	if (is_array($ipfw)) {
1601
		if ($reverse) {
1602
			$volume['output_pkts'] = $ipfw['packets'];
1603
			$volume['output_bytes'] = $ipfw['bytes'];
1604
		}
1605
		else {
1606
			$volume['input_pkts'] = $ipfw['packets'];
1607
			$volume['input_bytes'] = $ipfw['bytes'];
1608
		}
1609
	}
1610

    
1611
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 2, $ip);
1612
	if (is_array($ipfw)) {
1613
		if ($reverse) {
1614
			$volume['input_pkts'] = $ipfw['packets'];
1615
			$volume['input_bytes'] = $ipfw['bytes'];
1616
		}
1617
		else {
1618
			$volume['output_pkts'] = $ipfw['packets'];
1619
			$volume['output_bytes'] = $ipfw['bytes'];
1620
		}
1621
	}
1622

    
1623
	return $volume;
1624
}
1625

    
1626
/**
1627
 * Get the NAS-IP-Address based on the current wan address
1628
 *
1629
 * Use functions in interfaces.inc to find this out
1630
 *
1631
 */
1632

    
1633
function getNasIP()
1634
{
1635
	global $config, $cpzone;
1636

    
1637
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1638
			$nasIp = get_interface_ip();
1639
	} else {
1640
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1641
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1642
		else
1643
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1644
	}
1645
		
1646
	if(!is_ipaddr($nasIp))
1647
		$nasIp = "0.0.0.0";
1648

    
1649
	return $nasIp;
1650
}
1651

    
1652
function portal_ip_from_client_ip($cliip) {
1653
	global $config, $cpzone;
1654

    
1655
	$isipv6 = is_ipaddrv6($cliip);
1656
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1657
	foreach ($interfaces as $cpif) {
1658
		if ($isipv6) {
1659
			$ip = get_interface_ipv6($cpif);
1660
			$sn = get_interface_subnetv6($cpif);
1661
		} else {
1662
			$ip = get_interface_ip($cpif);
1663
			$sn = get_interface_subnet($cpif);
1664
		}
1665
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1666
			return $ip;
1667
	}
1668

    
1669
	$inet = ($isipv6) ? '-inet6' : '-inet';
1670
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1671
	$iface = trim($iface, "\n");
1672
	if (!empty($iface)) {
1673
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1674
		if (is_ipaddr($ip))
1675
			return $ip;
1676
	}
1677

    
1678
	// doesn't match up to any particular interface
1679
	// so let's set the portal IP to what PHP says 
1680
	// the server IP issuing the request is. 
1681
	// allows same behavior as 1.2.x where IP isn't 
1682
	// in the subnet of any CP interface (static routes, etc.)
1683
	// rather than forcing to DNS hostname resolution
1684
	$ip = $_SERVER['SERVER_ADDR'];
1685
	if (is_ipaddr($ip))
1686
		return $ip;
1687

    
1688
	return false;
1689
}
1690

    
1691
function portal_hostname_from_client_ip($cliip) {
1692
	global $config, $cpzone;
1693

    
1694
	$cpcfg = $config['captiveportal'][$cpzone];
1695

    
1696
	if (isset($cpcfg['httpslogin'])) {
1697
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
1698
		$ourhostname = $cpcfg['httpsname'];
1699
		
1700
		if ($listenporthttps != 443)
1701
			$ourhostname .= ":" . $listenporthttps;
1702
	} else {
1703
		$listenporthttp  = $cpcfg['listenporthttp']  ? $cpcfg['listenporthttp']  : $cpcfg['zoneid'];
1704
		$ifip = portal_ip_from_client_ip($cliip);
1705
		if (!$ifip)
1706
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1707
		else
1708
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1709
		
1710
		if ($listenporthttp != 80)
1711
			$ourhostname .= ":" . $listenporthttp;
1712
	}
1713
	
1714
	return $ourhostname;
1715
}
1716

    
1717
/* functions move from index.php */
1718

    
1719
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1720
	global $g, $config, $cpzone;
1721

    
1722
	/* Get captive portal layout */
1723
	if ($type == "redir") {
1724
		header("Location: {$redirurl}");
1725
		return;
1726
	} else if ($type == "login")
1727
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1728
	else
1729
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1730

    
1731
	$cpcfg = $config['captiveportal'][$cpzone];
1732

    
1733
	/* substitute the PORTAL_REDIRURL variable */
1734
	if ($cpcfg['preauthurl']) {
1735
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1736
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1737
	}
1738

    
1739
	/* substitute other variables */
1740
	$ourhostname = portal_hostname_from_client_ip($clientip);
1741
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1742
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1743
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1744

    
1745
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1746
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1747
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1748
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1749
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1750

    
1751
	// Special handling case for captive portal master page so that it can be ran 
1752
	// through the PHP interpreter using the include method above.  We convert the
1753
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1754
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1755
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1756
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1757
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1758
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1759
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1760
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1761

    
1762
	echo $htmltext;
1763
}
1764

    
1765
function portal_mac_radius($clientmac,$clientip) {
1766
	global $config, $cpzone;
1767

    
1768
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1769

    
1770
	/* authentication against the radius server */
1771
	$username = mac_format($clientmac);
1772
	$auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1773
	if ($auth_list['auth_val'] == 2)
1774
		return TRUE;
1775

    
1776
	if (!empty($auth_list['url_redirection']))
1777
		portal_reply_page($auth_list['url_redirection'], "redir");
1778

    
1779
	return FALSE;
1780
}
1781

    
1782
function captiveportal_reapply_attributes($cpentry, $attributes) {
1783
	global $config, $cpzone, $g;
1784

    
1785
	$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1786
	$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1787
	$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1788
	$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1789
	$bw_up_pipeno = $cpentry[1];
1790
	$bw_down_pipeno = $cpentry[1]+1;
1791

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

    
1796
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1797
}
1798

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

    
1802
	// Ensure we create an array if we are missing attributes
1803
	if (!is_array($attributes))
1804
		$attributes = array();
1805

    
1806
	unset($sessionid);
1807

    
1808
	/* Do not allow concurrent login execution. */
1809
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1810

    
1811
	if ($attributes['voucher'])
1812
		$remaining_time = $attributes['session_timeout'];
1813

    
1814
	$writecfg = false;
1815
	/* Find an existing session */
1816
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
1817
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1818
			$mac = captiveportal_passthrumac_findbyname($username);
1819
			if (!empty($mac)) {
1820
				if ($_POST['replacemacpassthru']) {
1821
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
1822
						if ($macent['mac'] == $mac['mac']) {
1823
							$macrules = "";
1824
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1825
							$pipeno = captiveportal_get_dn_passthru_ruleno($mac['mac']);
1826
							if ($ruleno) {
1827
								captiveportal_free_ipfw_ruleno($ruleno);
1828
								$macrules .= "delete {$ruleno}\n";
1829
								++$ruleno;
1830
								$macrules .= "delete {$ruleno}\n";
1831
							}
1832
							if ($pipeno) {
1833
								captiveportal_free_dn_ruleno($pipeno);
1834
								$macrules .= "pipe delete {$pipeno}\n";
1835
								++$pipeno;
1836
								$macrules .= "pipe delete {$pipeno}\n";
1837
							}
1838
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1839
							$mac['action'] = 'pass';
1840
							$mac['mac'] = $clientmac;
1841
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1842
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1843
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1844
							mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1845
							$writecfg = true;
1846
							$sessionid = true;
1847
							break;
1848
						}
1849
					}
1850
				} else {
1851
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1852
						$clientmac, $clientip, $username, $password);
1853
					unlock($cpdblck);
1854
					return;
1855
				}
1856
			}
1857
		}
1858
	}
1859

    
1860
	/* read in client database */
1861
	$query = "WHERE ip = '{$clientip}'";
1862
	$tmpusername = strtolower($username);
1863
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins']))
1864
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
1865
	$cpdb = captiveportal_read_db($query);
1866

    
1867
	/* Snapshot the timestamp */
1868
	$allow_time = time();
1869
	$radiusservers = captiveportal_get_radius_servers();
1870
	$unsetindexes = array();
1871
	if (is_null($radiusctx))
1872
		$radiusctx = 'first';
1873

    
1874
	foreach ($cpdb as $cpentry) {
1875
		if (empty($cpentry[11])) {
1876
			$cpentry[11] = 'first';
1877
		}
1878
		/* on the same ip */
1879
		if ($cpentry[2] == $clientip) {
1880
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac)
1881
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1882
			else
1883
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
1884
			$sessionid = $cpentry[5];
1885
			break;
1886
		}
1887
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1888
			// user logged in with an active voucher. Check for how long and calculate 
1889
			// how much time we can give him (voucher credit - used time)
1890
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1891
			if ($remaining_time < 0)    // just in case. 
1892
				$remaining_time = 0;
1893

    
1894
			/* This user was already logged in so we disconnect the old one */
1895
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[11]],13);
1896
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1897
			$unsetindexes[] = $cpentry[5];
1898
			break;
1899
		}
1900
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1901
			/* on the same username */
1902
			if (strcasecmp($cpentry[4], $username) == 0) {
1903
				/* This user was already logged in so we disconnect the old one */
1904
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[11]],13);
1905
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1906
				$unsetindexes[] = $cpentry[5];
1907
				break;
1908
			}
1909
		}
1910
	}
1911
	unset($cpdb);
1912

    
1913
	if (!empty($unsetindexes))
1914
		captiveportal_remove_entries($unsetindexes);
1915

    
1916
	if ($attributes['voucher'] && $remaining_time <= 0)
1917
		return 0;       // voucher already used and no time left
1918

    
1919
	if (!isset($sessionid)) {
1920
		/* generate unique session ID */
1921
		$tod = gettimeofday();
1922
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1923

    
1924
		if ($passthrumac) {
1925
			$mac = array();
1926
			$mac['action'] = 'pass';
1927
			$mac['mac'] = $clientmac;
1928
			$mac['ip'] = $clientip; /* Used only for logging */
1929
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
1930
				$mac['username'] = $username;
1931
				if ($attributes['voucher'])
1932
					$mac['logintype'] = "voucher";
1933
			}
1934
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1935
			if (!empty($bw_up))
1936
				$mac['bw_up'] = $bw_up;
1937
			if (!empty($bw_down))
1938
				$mac['bw_down'] = $bw_down;
1939
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
1940
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
1941
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1942
			unlock($cpdblck);
1943
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1944
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1945
			mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1946
			$writecfg = true;
1947
		} else {
1948
			/* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
1949
			if (is_null($pipeno))
1950
				$pipeno = captiveportal_get_next_dn_ruleno();
1951

    
1952
			/* if the pool is empty, return appropriate message and exit */
1953
			if (is_null($pipeno)) {
1954
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1955
				log_error("Zone: {$cpzone} - WARNING!  Captive portal has reached maximum login capacity");
1956
				unlock($cpdblck);
1957
				return;
1958
			}
1959

    
1960
			$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1961
			$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1962
			$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1963
			$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1964

    
1965
			$bw_up_pipeno = $pipeno;
1966
			$bw_down_pipeno = $pipeno + 1;
1967
			//$bw_up /= 1000; // Scale to Kbit/s
1968
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1969
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1970

    
1971
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
1972
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1973
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
1974
			else
1975
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
1976

    
1977
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1978
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
1979
			else
1980
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
1981

    
1982
			if ($attributes['voucher'])
1983
				$attributes['session_timeout'] = $remaining_time;
1984
			
1985
			/* handle empty attributes */
1986
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
1987
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
1988
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
1989
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
1990

    
1991
			/* escape username */
1992
			$safe_username = sqlite_escape_string($username);
1993

    
1994
			/* encode password in Base64 just in case it contains commas */
1995
			$bpassword = base64_encode($password);
1996
			$insertquery  = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, radiusctx) ";
1997
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
1998
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, '{$radiusctx}')";
1999

    
2000
			/* store information to database */
2001
			captiveportal_write_db($insertquery);
2002
			unlock($cpdblck);
2003
			unset($insertquery, $bpassword);
2004

    
2005
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
2006
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
2007
				if ($acct_val == 1)
2008
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
2009
			}
2010
		}
2011
	} else
2012
		unlock($cpdblck);
2013

    
2014
	if ($writecfg == true)
2015
		write_config();
2016

    
2017
	/* redirect user to desired destination */
2018
	if (!empty($attributes['url_redirection']))
2019
		$my_redirurl = $attributes['url_redirection'];
2020
	else if (!empty($redirurl))
2021
		$my_redirurl = $redirurl;
2022
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
2023
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
2024

    
2025
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
2026
		$ourhostname = portal_hostname_from_client_ip($clientip);
2027
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
2028
		$logouturl = "{$protocol}{$ourhostname}/";
2029

    
2030
		if (isset($attributes['reply_message']))
2031
			$message = $attributes['reply_message'];
2032
		else
2033
			$message = 0;
2034

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

    
2037
	} else {
2038
		portal_reply_page($redirurl, "redir", "Just redirect the user.");
2039
	}
2040

    
2041
	return $sessionid;
2042
}
2043

    
2044

    
2045
/*
2046
 * Used for when pass-through credits are enabled.
2047
 * Returns true when there was at least one free login to deduct for the MAC.
2048
 * Expired entries are removed as they are seen.
2049
 * Active entries are updated according to the configuration.
2050
 */
2051
function portal_consume_passthrough_credit($clientmac) {
2052
	global $config, $cpzone;
2053

    
2054
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
2055
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
2056
	else
2057
		return false;
2058

    
2059
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
2060
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2061
	else
2062
		return false;
2063

    
2064
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
2065
		return false;
2066

    
2067
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2068

    
2069
	/*
2070
	 * Read database of used MACs.  Lines are a comma-separated list
2071
	 * of the time, MAC, then the count of pass-through credits remaining.
2072
	 */
2073
	$usedmacs = captiveportal_read_usedmacs_db();
2074

    
2075
	$currenttime = time();
2076
	$found = false;
2077
	foreach ($usedmacs as $key => $usedmac) {
2078
		$usedmac = explode(",", $usedmac);
2079

    
2080
		if ($usedmac[1] == $clientmac) {
2081
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2082
				if ($usedmac[2] < 1) {
2083
					if ($updatetimeouts) {
2084
						$usedmac[0] = $currenttime;
2085
						unset($usedmacs[$key]);
2086
						$usedmacs[] = implode(",", $usedmac);
2087
						captiveportal_write_usedmacs_db($usedmacs);
2088
					}
2089

    
2090
					return false;
2091
				} else {
2092
					$usedmac[2] -= 1;
2093
					$usedmacs[$key] = implode(",", $usedmac);
2094
				}
2095

    
2096
				$found = true;
2097
			} else
2098
				unset($usedmacs[$key]);
2099

    
2100
			break;
2101
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2102
				unset($usedmacs[$key]);
2103
	}
2104

    
2105
	if (!$found) {
2106
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2107
		$usedmacs[] = implode(",", $usedmac);
2108
	}
2109

    
2110
	captiveportal_write_usedmacs_db($usedmacs);
2111
	return true;
2112
}
2113

    
2114
function captiveportal_read_usedmacs_db() {
2115
	global $g, $cpzone;
2116

    
2117
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2118
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2119
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2120
		if (!$usedmacs)
2121
			$usedmacs = array();
2122
	} else
2123
		$usedmacs = array();
2124

    
2125
	unlock($cpumaclck);
2126
	return $usedmacs;
2127
}
2128

    
2129
function captiveportal_write_usedmacs_db($usedmacs) {
2130
	global $g, $cpzone;
2131

    
2132
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2133
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2134
	unlock($cpumaclck);
2135
}
2136

    
2137
function captiveportal_blocked_mac($mac) {
2138
	global $config, $g, $cpzone;
2139

    
2140
	if (empty($mac) || !is_macaddr($mac))
2141
		return false;
2142

    
2143
	if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
2144
		return false;
2145

    
2146
	foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac)
2147
		if (($passthrumac['action'] == 'block') &&
2148
		    ($passthrumac['mac'] == strtolower($mac)))
2149
			return true;
2150

    
2151
	return false;
2152

    
2153
}
2154

    
2155
function captiveportal_send_server_accounting($off = false) {
2156
	global $cpzone, $config;
2157

    
2158
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
2159
		return;
2160
	}
2161
	if ($off) {
2162
		$racct = new Auth_RADIUS_Acct_Off;
2163
	} else {
2164
		$racct = new Auth_RADIUS_Acct_On;
2165
	}
2166
	$radiusservers = captiveportal_get_radius_servers();
2167
	if (empty($radiusservers)) {
2168
		return;
2169
	}
2170
	foreach ($radiusservers['first'] as $radsrv) {
2171
		// Add a new server to our instance
2172
		$racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
2173
	}
2174
	if (PEAR::isError($racct->start())) {
2175
		$retvalue['acct_val'] = 1;
2176
		$retvalue['error'] = $racct->getMessage();
2177

    
2178
		// If we encounter an error immediately stop this function and go back
2179
		$racct->close();
2180
		return $retvalue;
2181
	}
2182
	// Send request
2183
	$result = $racct->send();
2184
	// Evaluation of the response
2185
	// 5 -> Accounting-Response
2186
	// See RFC2866 for this.
2187
	if (PEAR::isError($result)) {
2188
		$retvalue['acct_val'] = 1;
2189
		$retvalue['error'] = $result->getMessage();
2190
	} else if ($result === true) {
2191
		$retvalue['acct_val'] = 5 ;
2192
	} else {
2193
		$retvalue['acct_val'] = 1 ;
2194
	}
2195

    
2196
	$racct->close();
2197
	return $retvalue;
2198
}
2199
?>
(8-8/67)