Project

General

Profile

Download (68.3 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
40
	pfSense_BUILDER_BINARIES:	/usr/local/sbin/lighttpd	/usr/local/bin/minicron /sbin/pfctl
41
	pfSense_BUILDER_BINARIES:	/bin/hostname	/bin/cp 
42
	pfSense_MODULE: captiveportal
43
*/
44

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

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

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

    
103
EOD;
104

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

    
112
EOD;
113
	}
114

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

    
142
EOD;
143

    
144
	return $htmltext;
145
}
146

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
309
EOD;
310
		}
311

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

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

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

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

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

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

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

    
341
		if ($g['booting'])
342
			echo "done\n";
343

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

    
352
		captiveportal_radius_stop_all();
353

    
354
		/* remove old information */
355
		unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
356
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
357

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

    
360
		if (empty($config['captiveportal']))
361
			mwexec("/sbin/sysctl net.link.ether.ipfw=0");
362
		else {
363
			/* Deactivate ipfw(4) if not needed */
364
			$cpactive = false;
365
			foreach ($config['captiveportal'] as $cpkey => $cp) {
366
				if (isset($cp['enable'])) {
367
					$cpactive = true;
368
					break;
369
				}
370
			}
371
			if ($cpactive === false)
372
				mwexec("/sbin/sysctl net.link.ether.ipfw=0");
373
				
374
		}
375
	}
376

    
377
	unlock($captiveportallck);
378
	
379
	return 0;
380
}
381

    
382
function captiveportal_init_webgui() {
383
	global $config, $cpzone;
384

    
385
	if (is_array($config['captiveportal'])) {
386
		foreach ($config['captiveportal'] as $cpkey => $cp) {
387
			$cpzone = $cpkey;
388
			captiveportal_init_webgui_zone($cp);
389
		}
390
	}
391
}
392

    
393
function captiveportal_init_webgui_zonename($zone) {
394
	global $config, $cpzone;
395
	
396
	if (isset($config['captiveportal'][$zone])) {
397
		$cpzone = $zone;
398
		captiveportal_init_webgui_zone($config['captiveportal'][$zone]);
399
	}
400
}
401

    
402
function captiveportal_init_webgui_zone($cpcfg) {
403
	global $g, $config, $cpzone;
404

    
405
	if (!isset($cpcfg['enable']))
406
		return;
407

    
408
	if (isset($cpcfg['httpslogin'])) {
409
		$cert = lookup_cert($cpcfg['certref']);
410
		$crt = base64_decode($cert['crt']);
411
		$key = base64_decode($cert['prv']);
412
		$ca = ca_chain($cert);
413

    
414
		/* generate lighttpd configuration */
415
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
416
		system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf",
417
			$crt, $key, $ca, "lighty-{$cpzone}-CaptivePortal-SSL.pid", $listenporthttps, "/usr/local/captiveportal",
418
			"cert-{$cpzone}-portal.pem", "ca-{$cpzone}-portal.pem", $cpzone);
419
	}
420

    
421
	/* generate lighttpd configuration */
422
	$listenporthttp = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : $cpcfg['zoneid'];
423
	system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal.conf",
424
		"", "", "", "lighty-{$cpzone}-CaptivePortal.pid", $listenporthttp, "/usr/local/captiveportal",
425
		"", "", $cpzone);
426

    
427
	/* attempt to start lighttpd */
428
	$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal.conf");
429

    
430
	/* fire up https instance */
431
	if (isset($cpcfg['httpslogin']))
432
		$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf");
433
}
434

    
435
/* reinit will disconnect all users, be careful! */
436
function captiveportal_init_rules($reinit = false) {
437
	global $config, $g, $cpzone;
438

    
439
	if (!isset($config['captiveportal'][$cpzone]['enable']))
440
		return;
441

    
442
	captiveportal_load_modules();
443
	mwexec("/usr/local/sbin/ipfw_context -a {$cpzone}", true);
444

    
445
	$cpips = array();
446
	$ifaces = get_configured_interface_list();
447
	$cpinterfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
448
	$firsttime = 0;
449
	foreach ($cpinterfaces as $cpifgrp) {
450
		if (!isset($ifaces[$cpifgrp]))
451
			continue;
452
		$tmpif = get_real_interface($cpifgrp);
453
		if (!empty($tmpif)) {
454
			$cpipm = get_interface_ip($cpifgrp);
455
			if (is_ipaddr($cpipm)) {
456
				$carpif = link_ip_to_carp_interface($cpipm);
457
				if (!empty($carpif)) {
458
					$carpsif = explode(" ", $carpif);
459
					foreach ($carpsif as $cpcarp) {
460
						mwexec("/usr/local/sbin/ipfw_context -a {$cpzone} -n {$cpcarp}", true);
461
						$carpip = find_interface_ip($cpcarp);
462
						if (is_ipaddr($carpip))
463
							$cpips[] = $carpip;
464
					}
465
				}
466
				$cpips[] = $cpipm;
467
			}
468
			mwexec("/usr/local/sbin/ipfw_context -a {$cpzone} -n {$tmpif}", true);
469
		}
470
	}
471
	if (count($cpips) > 0) {
472
		$cpactive = true;
473
	} else
474
		return false;
475

    
476
	if ($reinit == false)
477
		$captiveportallck = lock("captiveportal{$cpzone}");
478

    
479
	$cprules =	"add 65291 allow pfsync from any to any\n";
480
	$cprules .= "add 65292 allow carp from any to any\n";
481

    
482
	$cprules .= <<<EOD
483
# layer 2: pass ARP
484
add 65301 pass layer2 mac-type arp,rarp
485
# pfsense requires for WPA
486
add 65302 pass layer2 mac-type 0x888e,0x88c7
487
# PPP Over Ethernet Session Stage/Discovery Stage
488
add 65303 pass layer2 mac-type 0x8863,0x8864
489

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

    
493
EOD;
494

    
495
	$rulenum = 65310;
496
	$ipcount = 0;
497
	$ips = "";
498
	foreach ($cpips as $cpip) {
499
		if($ipcount == 0) {
500
			$ips = "{$cpip} ";
501
		} else {
502
			$ips .= "or {$cpip} ";
503
		}
504
		$ipcount++;
505
	}
506
	$ips = "{ 255.255.255.255 or {$ips} }";
507
	$cprules .= "add {$rulenum} pass ip from any to {$ips} in\n";
508
	$rulenum++;
509
	$cprules .= "add {$rulenum} pass ip from {$ips} to any out\n";
510
	$rulenum++;
511
	$cprules .= "add {$rulenum} pass icmp from {$ips} to any out icmptype 0\n";
512
	$rulenum++;
513
	$cprules .= "add {$rulenum} pass icmp from any to {$ips} in icmptype 8 \n";
514
	$rulenum++;
515
	/* Allowed ips */
516
	$cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any in\n";
517
	$rulenum++;
518
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) out\n";
519
	$rulenum++;
520

    
521
	/* Authenticated users rules. */
522
	$cprules .= "add {$rulenum} pipe tablearg ip from table(1) to any in\n";
523
	$rulenum++;
524
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(2) out\n";
525
	$rulenum++;
526
	
527
	$listenporthttp =
528
		$config['captiveportal'][$cpzone]['listenporthttp'] ?
529
		$config['captiveportal'][$cpzone]['listenporthttp'] :
530
		$config['captiveportal'][$cpzone]['zoneid'];
531

    
532
	if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
533
		$listenporthttps = $listenporthttp + 1;
534
		$cprules .= "add 65531 fwd 127.0.0.1,{$listenporthttps} tcp from any to any dst-port 443 in\n";
535
	}
536
	
537
	$cprules .= <<<EOD
538

    
539
# redirect non-authenticated clients to captive portal
540
add 65532 fwd 127.0.0.1,{$listenporthttp} tcp from any to any dst-port 80 in 
541
# let the responses from the captive portal web server back out
542
add 65533 pass tcp from any to any out
543
# block everything else
544
add 65534 deny all from any to any
545

    
546
EOD;
547

    
548
	/* generate passthru mac database */
549
	$cprules .= captiveportal_passthrumac_configure(true);
550
	$cprules .= "\n";
551

    
552
	/* allowed ipfw rules to make allowed ip work */
553
	$cprules .= captiveportal_allowedip_configure();
554

    
555
	/* allowed ipfw rules to make allowed hostnames work */
556
	$cprules .= captiveportal_allowedhostname_configure();
557
	
558
	/* load rules */
559
	$cprules = "flush\n{$cprules}";
560
	file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
561
	mwexec("/sbin/ipfw -x {$cpzone} -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
562
	//@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
563
	unset($cprules, $tmprules);
564

    
565
	if ($reinit == false)
566
		unlock($captiveportallck);
567
}
568

    
569
/* 
570
 * Remove clients that have been around for longer than the specified amount of time
571
 * db file structure:
572
 * timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time,interim_interval
573
 * (password is in Base64 and only saved when reauthentication is enabled)
574
 */
575
function captiveportal_prune_old() {
576
	global $g, $config, $cpzone;
577

    
578
	if (empty($cpzone))
579
		return;
580

    
581
	$cpcfg = $config['captiveportal'][$cpzone];
582
	$vcpcfg = $config['voucher'][$cpzone];
583

    
584
	/* check for expired entries */
585
	$idletimeout = 0;
586
	$timeout = 0;
587
	if (!empty($cpcfg['timeout']) && is_numeric($cpcfg['timeout']))
588
		$timeout = $cpcfg['timeout'] * 60;
589

    
590
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout']))
591
		$idletimeout = $cpcfg['idletimeout'] * 60;
592

    
593
	/* Is there any job to do? */
594
	if (!$timeout && !$idletimeout && !isset($cpcfg['reauthenticate']) &&
595
	    !isset($cpcfg['radiussession_timeout']) && !isset($vcpcfg['enable']))
596
		return;
597

    
598
	$radiussrvs = captiveportal_get_radius_servers();
599

    
600
	/* Read database */
601
	/* NOTE: while this can be simplified in non radius case keep as is for now */
602
	$cpdb = captiveportal_read_db();
603

    
604
	/*
605
	 * To make sure we iterate over ALL accounts on every run the count($cpdb) is moved
606
	 * outside of the loop. Otherwise the loop would evaluate count() on every iteration
607
	 * and since $i would increase and count() would decrement they would meet before we
608
	 * had a chance to iterate over all accounts.
609
	 */
610
	$unsetindexes = array();
611
	$voucher_needs_sync = false;
612
	/* 
613
	 * Snapshot the time here to use for calculation to speed up the process.
614
	 * If something is missed next run will catch it!
615
	 */
616
	$pruning_time = time();
617
	$stop_time = $pruning_time;
618
	foreach ($cpdb as $cpentry) {
619

    
620
		$timedout = false;
621
		$term_cause = 1;
622
		if (empty($cpentry[10]))
623
			$cpentry[10] = 'first';
624
		$radiusservers = $radiussrvs[$cpentry[10]];
625

    
626
		/* hard timeout? */
627
		if ($timeout) {
628
			if (($pruning_time - $cpentry[0]) >= $timeout) {
629
				$timedout = true;
630
				$term_cause = 5; // Session-Timeout
631
			}
632
		}
633

    
634
		/* Session-Terminate-Time */
635
		if (!$timedout && !empty($cpentry[9])) {
636
			if ($pruning_time >= $cpentry[9]) {
637
				$timedout = true;
638
				$term_cause = 5; // Session-Timeout
639
			}
640
		}
641

    
642
		/* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
643
		$uidletimeout = (is_numeric($cpentry[8])) ? $cpentry[8] : $idletimeout;
644
		/* if an idle timeout is specified, get last activity timestamp from ipfw */
645
		if (!$timedout && $uidletimeout) {
646
			$lastact = captiveportal_get_last_activity($cpentry[2]);
647
			/*	If the user has logged on but not sent any traffic they will never be logged out.
648
			 *	We "fix" this by setting lastact to the login timestamp. 
649
			 */
650
			$lastact = $lastact ? $lastact : $cpentry[0];
651
			if ($lastact && (($pruning_time - $lastact) >= $uidletimeout)) {
652
				$timedout = true;
653
				$term_cause = 4; // Idle-Timeout
654
				$stop_time = $lastact; // Entry added to comply with WISPr
655
			}
656
		}
657

    
658
		/* if vouchers are configured, activate session timeouts */
659
		if (!$timedout && isset($vcpcfg['enable']) && !empty($cpentry[7])) {
660
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
661
				$timedout = true;
662
				$term_cause = 5; // Session-Timeout
663
				$voucher_needs_sync = true;
664
			}
665
		}
666

    
667
		/* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
668
		if (!$timedout && isset($cpcfg['radiussession_timeout']) && !empty($cpentry[7])) {
669
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
670
				$timedout = true;
671
				$term_cause = 5; // Session-Timeout
672
			}
673
		}
674

    
675
		if ($timedout) {
676
			captiveportal_disconnect($cpentry, $radiusservers,$term_cause,$stop_time);
677
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
678
			$unsetindexes[] = $cpentry[5];
679
		}
680

    
681
		/* do periodic RADIUS reauthentication? */
682
		if (!$timedout && !empty($radiusservers)) {
683
			if (isset($cpcfg['radacct_enable'])) {
684
				if ($cpcfg['reauthenticateacct'] == "stopstart") {
685
					/* stop and restart accounting */
686
					RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
687
						$cpentry[4], // username
688
						$cpentry[5], // sessionid
689
						$cpentry[0], // start time
690
						$radiusservers,
691
						$cpentry[2], // clientip
692
						$cpentry[3], // clientmac
693
						10); // NAS Request
694
					pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ZERO_ENTRY_STATS, 1, $cpentry[2]);
695
					pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ZERO_ENTRY_STATS, 2, $cpentry[2]);
696
					RADIUS_ACCOUNTING_START($cpentry[1], // ruleno
697
						$cpentry[4], // username
698
						$cpentry[5], // sessionid
699
						$radiusservers,
700
						$cpentry[2], // clientip
701
						$cpentry[3]); // clientmac
702
				} else if ($cpcfg['reauthenticateacct'] == "interimupdate") {
703
					$session_time = $pruning_time - $cpentry[0];
704
					if (!empty($cpentry[10]) && $cpentry[10] > 60)
705
						$interval = $cpentry[10];
706
					else
707
						$interval = 0;
708
					$past_interval_min = ($session_time > $interval);
709
					$within_interval = ($session_time % $interval >= 0 && $session_time % $interval <= 59);
710
					if (($interval > 0 && $past_interval_min && $within_interval) || $interval === 0) {
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
							true); // Interim Updates
720
					}
721
				}
722
			}
723

    
724
			/* check this user against RADIUS again */
725
			if (isset($cpcfg['reauthenticate'])) {
726
				$auth_list = RADIUS_AUTHENTICATION($cpentry[4], // username
727
					base64_decode($cpentry[6]), // password
728
					$radiusservers,
729
					$cpentry[2], // clientip
730
					$cpentry[3], // clientmac
731
					$cpentry[1]); // ruleno
732
				if ($auth_list['auth_val'] == 3) {
733
					captiveportal_disconnect($cpentry, $radiusservers, 17);
734
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
735
					$unsetindexes[] = $cpentry[5];
736
				} else if ($auth_list['auth_val'] == 2)
737
					captiveportal_reapply_attributes($cpentry, $auth_list);
738
			}
739
		}
740
	}
741
	unset($cpdb);
742

    
743
	captiveportal_prune_old_automac();
744

    
745
	if ($voucher_needs_sync == true)
746
		/* Triger a sync of the vouchers on config */
747
		send_event("service sync vouchers");
748

    
749
	/* write database */
750
	if (!empty($unsetindexes))
751
		captiveportal_remove_entries($unsetindexes);
752
}
753

    
754
function captiveportal_prune_old_automac() {
755
	global $g, $config, $cpzone;
756

    
757
	if (is_array($config['captiveportal'][$cpzone]['passthrumac']) && isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
758
		$tmpvoucherdb = array();
759
		$macrules = "";
760
		$writecfg = false;
761
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $eid => $emac) {
762
			if ($emac['logintype'] == "voucher") {
763
				if (isset($tmpvoucherdb[$emac['username']])) {
764
					$temac = $config['captiveportal'][$cpzone]['passthrumac'][$tmpvoucherdb[$emac['username']]];
765
					$ruleno = captiveportal_get_ipfw_passthru_ruleno($temac['mac']);
766
					$pipeno = captiveportal_get_dn_passthru_ruleno($temac['mac']);
767
					if ($ruleno) {
768
						captiveportal_free_ipfw_ruleno($ruleno);
769
						$macrules .= "delete {$ruleno}";
770
						++$ruleno;
771
						$macrules .= "delete {$ruleno}";
772
					}
773
					if ($pipeno) {
774
						captiveportal_free_dn_ruleno($pipeno);
775
						$macrules .= "pipe delete {$pipeno}\n";
776
						++$pipeno;
777
						$macrules .= "pipe delete {$pipeno}\n";
778
					}
779
					$writecfg = true;
780
					captiveportal_logportalauth($temac['username'], $temac['mac'], $temac['ip'], "DUPLICATE {$temac['username']} LOGIN - TERMINATING OLD SESSION");
781
					unset($config['captiveportal'][$cpzone]['passthrumac'][$tmpvoucherdb[$emac['username']]]);
782
				}
783
				$tmpvoucherdb[$emac['username']] = $eid;
784
				if (voucher_auth($emac['username']) <= 0) {
785
					$ruleno = captiveportal_get_ipfw_passthru_ruleno($emac['mac']);
786
					$pipeno = captiveportal_get_dn_passthru_ruleno($emac['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($emac['username'], $emac['mac'], $emac['ip'], "EXPIRED {$emac['username']} LOGIN - TERMINATING SESSION");
801
					unset($config['captiveportal'][$cpzone]['passthrumac'][$eid]);
802
				}
803
			}
804
		}
805
		if (!empty($macrules)) {
806
			@file_put_contents("{$g['tmp_path']}/macentry.prunerules.tmp", $macrules);
807
			unset($macrules);
808
			mwexec("/sbin/ipfw -x {$cpzone} -q {$g['tmp_path']}/macentry.prunerules.tmp");
809
		}
810
		if ($writecfg === true)
811
			write_config("Prune session for auto-added macs");
812
	}
813
}
814

    
815
/* remove a single client according to the DB entry */
816
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
817
	global $g, $config, $cpzone;
818

    
819
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
820

    
821
	/* this client needs to be deleted - remove ipfw rules */
822
	if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers)) {
823
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
824
			$dbent[4], // username
825
			$dbent[5], // sessionid
826
			$dbent[0], // start time
827
			$radiusservers,
828
			$dbent[2], // clientip
829
			$dbent[3], // clientmac
830
			$term_cause, // Acct-Terminate-Cause
831
			false,
832
			$stop_time);
833
	}
834
	
835
	if (is_ipaddr($dbent[2])) {
836
		/* Delete client's ip entry from tables 1 and 2. */
837
		pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_DEL, 1, $dbent[2]);
838
		pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_DEL, 2, $dbent[2]);
839
		/* XXX: Redundant?! Ensure all pf(4) states are killed. */
840
		pfSense_kill_states($dbent[2]);
841
		pfSense_kill_srcstates($dbent[2]);
842
	}
843

    
844
	/* 
845
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
846
	* We could get an error if the pipe doesn't exist but everything should still be fine
847
	*/
848
	if (!empty($dbent[1])) {
849
		pfSense_pipe_action("pipe delete {$dbent[1]}");
850
		pfSense_pipe_action("pipe delete " . ($dbent[1]+1));
851

    
852
		/* Release the ruleno so it can be reallocated to new clients. */
853
		captiveportal_free_dn_ruleno($dbent[1]);
854
	}
855

    
856
	// XMLRPC Call over to the master Voucher node
857
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
858
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
859
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
860
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
861
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
862
		$remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time);
863
	}
864

    
865
}
866

    
867
/* remove a single client by sessionid */
868
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
869
	global $g, $config;
870

    
871
	$radiusservers = captiveportal_get_radius_servers();
872

    
873
	/* read database */
874
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
875

    
876
	/* find entry */
877
	if (!empty($result)) {
878
		captiveportal_write_db("DELETE FROM captiveportal WHERE sessionid = '{$sessionid}'");
879

    
880
		foreach ($result as $cpentry) {
881
			if (empty($cpentry[10]))
882
				$cpentry[10] = 'first';
883
			captiveportal_disconnect($cpentry, $radiusservers[$cpentry[10]], $term_cause);
884
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
885
		}
886
		unset($result);
887
	}
888
}
889

    
890
/* send RADIUS acct stop for all current clients */
891
function captiveportal_radius_stop_all() {
892
	global $config, $cpzone;
893

    
894
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable']))
895
		return;
896

    
897
	$radiusservers = captiveportal_get_radius_servers();
898
	if (!empty($radiusservers)) {
899
		$cpdb = captiveportal_read_db();
900
		foreach ($cpdb as $cpentry) {
901
			if (empty($cpentry[10]))
902
				$cpentry[10] = 'first';
903
			if (!empty($radiusservers[$cpentry[10]])) {
904
				RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
905
					$cpentry[4], // username
906
					$cpentry[5], // sessionid
907
					$cpentry[0], // start time
908
					$radiusservers[$cpentry[10]],
909
					$cpentry[2], // clientip
910
					$cpentry[3], // clientmac
911
					7); // Admin Reboot
912
			}
913
		}
914
	}
915
}
916

    
917
function captiveportal_passthrumac_configure_entry($macent) {
918

    
919
	$bwUp = empty($macent['bw_up']) ? 0 : $macent['bw_up'];
920
	$bwDown = empty($macent['bw_down']) ? 0 : $macent['bw_down'];
921

    
922
	$ruleno = captiveportal_get_next_ipfw_ruleno();
923
	$pipeno = captiveportal_get_next_dn_ruleno();
924

    
925
	$rules = "";
926
	$pipeup = $pipeno;
927
	$rules .= "pipe {$pipeup} config bw {$bwUp}Kbit/s queue 100 buckets 16\n";
928
	$pipedown = $pipeno + 1;
929
	$rules .= "pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16\n";
930
	$rules .= "add {$ruleno} pipe {$pipeup} ip from any to any MAC {$macent['mac']} any\n";
931
	$ruleno++;
932
	$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC any {$macent['mac']}\n";
933

    
934
	return $rules;
935
}
936

    
937
function captiveportal_passthrumac_configure($lock = false) {
938
	global $config, $g, $cpzone;
939

    
940
	$rules = "";
941

    
942
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
943
		$macdb = array();
944
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
945
			$rules .= captiveportal_passthrumac_configure_entry($macent);
946
			$macdb[$macent['mac']][$cpzone]['active']  = true;
947

    
948
		}
949
	}
950

    
951
	return $rules;
952
}
953

    
954
function captiveportal_passthrumac_findbyname($username) {
955
	global $config, $cpzone;
956

    
957
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
958
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
959
			if ($macent['username'] == $username)
960
				return $macent;
961
		}
962
	}
963
	return NULL;
964
}
965

    
966
/* 
967
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
968
 */
969
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
970

    
971
	/* This function can deal with hostname or ipaddress */
972
	if($ipent['ip']) 	
973
		$ipaddress = $ipent['ip'];
974

    
975
	/*  Instead of copying this entire function for something
976
	 *  easy such as hostname vs ip address add this check
977
	 */
978
	if(!empty($ipent['hostname'])) {
979
		$ipaddress = gethostbyname($ipent['hostname']);
980
		if(!is_ipaddr($ipaddress)) 
981
			return;
982
	}
983

    
984
	$rules = "";
985
	$cp_filterdns_conf = "";
986
	$enBwup = empty($ipent['bw_up']) ? 0 : intval($ipent['bw_up']);
987
	$enBwdown = empty($ipent['bw_down']) ? 0 : intval($ipent['bw_down']);
988

    
989
	$pipeno = captiveportal_get_next_dn_ruleno();
990
	$rules .= "pipe {$pipeno} config bw {$ipent['bw_up']}Kbit/s queue 100 buckets 16\n";
991
	$pipedown = $pipeno + 1;
992
	$rules .= "pipe {$pipedown} config bw {$ipent['bw_down']}Kbit/s queue 100 buckets 16\n";
993
	if ($ishostname === true) {
994
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
995
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
996
	}
997
	$subnet = "";
998
	if (!empty($ipent['sn']))
999
		$subnet = "/{$ipent['sn']}";
1000
	$rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
1001
	$rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
1002

    
1003
	if ($ishostname === true)
1004
		return array($rules, $cp_filterdns_conf);
1005
	else
1006
		return $rules;
1007
}
1008

    
1009
function captiveportal_allowedhostname_configure() {
1010
	global $config, $g, $cpzone;
1011

    
1012
	$rules = "";
1013
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1014
		$rules = "\n# captiveportal_allowedhostname_configure()\n";
1015
		$cp_filterdns_conf = "";
1016
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) {
1017
			$tmprules = captiveportal_allowedip_configure_entry($hostnameent, true);
1018
			$rules .= $tmprules[0];
1019
			$cp_filterdns_conf .= $tmprules[1];
1020
		}
1021
		$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1022
		@file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1023
		unset($cp_filterdns_conf);
1024
		if (isvalidpid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid"))
1025
			sigkillbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid", "HUP");
1026
		else {
1027
			killbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1028
			mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzone} -d 1");
1029
		}
1030
	}
1031

    
1032
	return $rules;
1033
}
1034

    
1035
function captiveportal_allowedip_configure() {
1036
	global $config, $g, $cpzone;
1037

    
1038
	$rules = "";
1039
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1040
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1041
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1042
	}
1043

    
1044
	return $rules;
1045
}
1046

    
1047
/* get last activity timestamp given client IP address */
1048
function captiveportal_get_last_activity($ip) {
1049
	global $cpzone;
1050

    
1051
	$ipfwoutput = pfSense_ipfw_getTablestats($cpzone, 1, $ip);
1052
	/* Reading only from one of the tables is enough of approximation. */
1053
	if (is_array($ipfwoutput)) {
1054
		return $ipfwoutput['timestamp'];
1055
	}
1056

    
1057
	return 0;
1058
}
1059

    
1060
function captiveportal_init_radius_servers() {
1061
	global $config, $g, $cpzone;
1062

    
1063
	/* generate radius server database */
1064
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1065
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1066
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1067
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1068
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1069
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1070

    
1071
		if ($config['captiveportal'][$cpzone]['radiusport'])
1072
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1073
		else
1074
			$radiusport = 1812;
1075
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1076
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1077
		else
1078
			$radiusacctport = 1813;
1079
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1080
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1081
		else
1082
			$radiusport2 = 1812;
1083
		if ($config['captiveportal'][$cpzone]['radiusport3'])
1084
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1085
		else
1086
			$radiusport3 = 1812;
1087
		if ($config['captiveportal'][$cpzone]['radiusport4'])
1088
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1089
		else
1090
			$radiusport4 = 1812;
1091

    
1092
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1093
		$radiuskey2 = ($config['captiveportal'][$cpzone]['radiuskey2']) ? $config['captiveportal'][$cpzone]['radiuskey2'] : null;
1094
		$radiuskey3 = ($config['captiveportal'][$cpzone]['radiuskey3']) ? $config['captiveportal'][$cpzone]['radiuskey3'] : null;
1095
		$radiuskey4 = ($config['captiveportal'][$cpzone]['radiuskey4']) ? $config['captiveportal'][$cpzone]['radiuskey4'] : null;
1096

    
1097
		$cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX);
1098
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
1099
		if (!$fd) {
1100
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1101
			unlock($cprdsrvlck);
1102
			return 1;
1103
		}
1104
		if (isset($radiusip, $radiuskey))
1105
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
1106
		if (isset($radiusip2, $radiuskey2))
1107
			fwrite($fd,"\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
1108
		if (isset($radiusip3, $radiuskey3))
1109
			fwrite($fd,"\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
1110
		if (isset($radiusip4, $radiuskey4))
1111
			fwrite($fd,"\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
1112
		
1113

    
1114
		fclose($fd);
1115
		unlock($cprdsrvlck);
1116
	}
1117
}
1118

    
1119
/* read RADIUS servers into array */
1120
function captiveportal_get_radius_servers() {
1121
	global $g, $cpzone;
1122

    
1123
	$cprdsrvlck = lock("captiveportalradius{$cpzone}");
1124
	if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
1125
		$radiusservers = array();
1126
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", 
1127
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1128
		if ($cpradiusdb) {
1129
			foreach($cpradiusdb as $cpradiusentry) {
1130
				$line = trim($cpradiusentry);
1131
				if ($line) {
1132
					$radsrv = array();
1133
						list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key'], $context) = explode(",",$line);
1134
				}
1135
				if (empty($context)) {
1136
					if (!is_array($radiusservers['first']))
1137
						$radiusservers['first'] = array();
1138
					$radiusservers['first'] = $radsrv;
1139
				} else {
1140
					if (!is_array($radiusservers[$context]))
1141
						$radiusservers[$context] = array();
1142
					$radiusservers[$context][] = $radsrv;
1143
				}
1144
			}
1145
		}
1146
		unlock($cprdsrvlck);
1147
		return $radiusservers;
1148
	}
1149

    
1150
	unlock($cprdsrvlck);
1151
	return false;
1152
}
1153

    
1154
/* log successful captive portal authentication to syslog */
1155
/* part of this code from php.net */
1156
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1157
	// Log it
1158
	if (!$message)
1159
		$message = "$status: $user, $mac, $ip";
1160
	else {
1161
		$message = trim($message);
1162
		$message = "$status: $user, $mac, $ip, $message";
1163
	}
1164
	captiveportal_syslog($message);
1165
}
1166

    
1167
/* log simple messages to syslog */
1168
function captiveportal_syslog($message) {
1169
	$message = trim($message);
1170
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1171
	// Log it
1172
	syslog(LOG_INFO, $message);
1173
	closelog();
1174
}
1175

    
1176
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1177
	global $g, $config;
1178

    
1179
	$pipeno = captiveportal_get_next_dn_ruleno();
1180

    
1181
	/* If the pool is empty, return appropriate message and fail authentication */
1182
	if (is_null($pipeno)) {
1183
		$auth_list = array();
1184
		$auth_list['auth_val'] = 1;
1185
		$auth_list['error'] = "System reached maximum login capacity";
1186
		return $auth_list;
1187
	}
1188

    
1189
	$radiusservers = captiveportal_get_radius_servers();
1190

    
1191
	if (is_null($radiusctx))
1192
		$radiusctx = 'first';
1193

    
1194
	$auth_list = RADIUS_AUTHENTICATION($username,
1195
		$password,
1196
		$radiusservers[$radiusctx],
1197
		$clientip,
1198
		$clientmac,
1199
		$pipeno);
1200

    
1201
	if ($auth_list['auth_val'] == 2) {
1202
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1203
		$sessionid = portal_allow($clientip,
1204
			$clientmac,
1205
			$username,
1206
			$password,
1207
			$auth_list,
1208
			$pipeno,
1209
			$radiusctx);
1210
	}
1211

    
1212
	return $auth_list;
1213
}
1214

    
1215
function captiveportal_opendb() {
1216
	global $g, $cpzone;
1217

    
1218
	if (file_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db"))
1219
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1220
	else {
1221
		$errormsg = "";
1222
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1223
		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)) {
1224
			@sqlite_exec($DB, "CREATE UNIQUE INDEX idx_active ON captiveportal (sessionid, username)");
1225
			@sqlite_exec($DB, "CREATE INDEX user ON captiveportal (username)");
1226
			@sqlite_exec($DB, "CREATE INDEX ip ON captiveportal (ip)");
1227
			@sqlite_exec($DB, "CREATE INDEX starttime ON captiveportal (allow_time)");
1228
			@sqlite_exec($DB, "CREATE INDEX serviceid ON captiveportal (serviceid)");
1229
		} else
1230
			captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$errormsg}");
1231
	}
1232

    
1233
	return $DB;
1234
}
1235

    
1236
/* read captive portal DB into array */
1237
function captiveportal_read_db($query = "") {
1238

    
1239
	$DB = captiveportal_opendb();
1240
	if ($DB) {
1241
		sqlite_exec($DB, "BEGIN");
1242
		if (!empty($query))
1243
			$cpdb = @sqlite_array_query($DB, "SELECT * FROM captiveportal {$query}", SQLITE_NUM);
1244
		else {
1245
			$response = @sqlite_unbuffered_query($DB, "SELECT * FROM captiveportal", SQLITE_NUM);
1246
			$cpdb = @sqlite_fetch_all($response, SQLITE_NUM);
1247
		}
1248
		sqlite_exec($DB, "END");
1249
		@sqlite_close($DB);
1250
	}
1251
	if (!$cpdb)
1252
		$cpdb = array();
1253

    
1254
	return $cpdb;
1255
}
1256

    
1257
function captiveportal_remove_entries($remove) {
1258

    
1259
	if (!is_array($remove) || empty($remove))
1260
		return;
1261

    
1262
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1263
	foreach($remove as $idx => $unindex) {
1264
		$query .= "'{$unindex}'";
1265
		if ($idx < (count($remove) - 1))
1266
			$query .= ",";
1267
	}
1268
	$query .= ")";
1269
	captiveportal_write_db($query);
1270
}
1271

    
1272
/* write captive portal DB */
1273
function captiveportal_write_db($queries) {
1274
	global $g;
1275

    
1276
	if (is_array($queries))
1277
		$query = implode(";", $queries);
1278
	else
1279
		$query = $queries;
1280

    
1281
	$DB = captiveportal_opendb();
1282
	if ($DB) {
1283
		$error_msg = "";
1284
		sqlite_exec($DB, "BEGIN TRANSACTION");
1285
		$result = @sqlite_exec($DB, $query, $error_msg);
1286
		if (!$result)
1287
			captiveportal_syslog("Trying to modify DB returned error: {$error_msg}");
1288
		else
1289
			sqlite_exec($DB, "END TRANSACTION");
1290
		@sqlite_close($DB);
1291
		return $result;
1292
	} else
1293
		return true;
1294
}
1295

    
1296
function captiveportal_write_elements() {
1297
	global $g, $config, $cpzone;
1298
	
1299
	$cpcfg = $config['captiveportal'][$cpzone];
1300

    
1301
	/* delete any existing elements */
1302
	if (is_dir($g['captiveportal_element_path'])) {
1303
		$dh = opendir($g['captiveportal_element_path']);
1304
		while (($file = readdir($dh)) !== false) {
1305
			if ($file != "." && $file != "..")
1306
				unlink($g['captiveportal_element_path'] . "/" . $file);
1307
		}
1308
		closedir($dh);
1309
	} else {
1310
		@mkdir($g['captiveportal_element_path']);
1311
	}
1312

    
1313
	if (is_array($cpcfg['element'])) {
1314
		conf_mount_rw();
1315
		foreach ($cpcfg['element'] as $data) {
1316
			if (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1317
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1318
				return 1;
1319
			}
1320
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1321
			@symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1322
		}
1323
		conf_mount_ro();
1324
	}
1325
	
1326
	return 0;
1327
}
1328

    
1329
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1330
	global $config, $g;
1331

    
1332
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1333
	$ruleno = 0;
1334
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1335
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1336
		for ($ridx = $rulenos_start; $ridx < $rulenos_range_max; $ridx++) {
1337
			if ($rules[$ridx]) {
1338
				$ridx++;
1339
				continue;
1340
			}
1341
			$ruleno = $ridx;
1342
			$rules[$ridx] = "used";
1343
			$rules[++$ridx] = "used";
1344
			break;
1345
		}
1346
	} else {
1347
		$rules = array_pad(array(), $rulenos_range_max, false);
1348
		$rules[$rulenos_start] = "used";
1349
		$rules[++$rulenos_start] = "used";
1350
		$ruleno = $rulenos_start;
1351
	}
1352
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1353
	unlock($cpruleslck);
1354

    
1355
	return $ruleno;
1356
}
1357

    
1358
function captiveportal_free_dn_ruleno($ruleno) {
1359
	global $config, $g;
1360

    
1361
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1362
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1363
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1364
		$rules[$ruleno] = false;
1365
		$rules[++$ruleno] = false;
1366
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1367
	}
1368
	unlock($cpruleslck);
1369
}
1370

    
1371
function captiveportal_get_dn_passthru_ruleno($value) {
1372
	global $config, $g, $cpzone;
1373

    
1374
	$cpcfg = $config['captiveportal'][$cpzone];
1375
	if(!isset($cpcfg['enable']))
1376
		return NULL;
1377

    
1378
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1379
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1380
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1381
		$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`);
1382
		if ($rules[$ruleno]) {
1383
			unlock($cpruleslck);
1384
			return $ruleno;
1385
		}
1386
	}
1387

    
1388
	unlock($cpruleslck);
1389
	return NULL;
1390
}
1391

    
1392
/*
1393
 * This function will calculate the lowest free firewall ruleno
1394
 * within the range specified based on the actual logged on users
1395
 *
1396
 */
1397
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1398
	global $config, $g, $cpzone;
1399

    
1400
	$cpcfg = $config['captiveportal'][$cpzone];
1401
	if(!isset($cpcfg['enable']))
1402
		return NULL;
1403

    
1404
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1405
	$ruleno = 0;
1406
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1407
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1408
		for ($ridx = 2; $ridx < ($rulenos_range_max - $rulenos_start); $ridx++) {
1409
			if ($rules[$ridx]) {
1410
				/* 
1411
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1412
				 * and the out pipe ruleno + 1.
1413
				 */
1414
				$ridx++;
1415
				continue;
1416
			}
1417
			$ruleno = $ridx;
1418
			$rules[$ridx] = "used";
1419
			$rules[++$ridx] = "used";
1420
			break;
1421
		}
1422
	} else {
1423
		$rules = array_pad(array(), $rulenos_range_max, false);
1424
		$rules[$rulenos_start] = "used";
1425
		$rules[++$rulenos_start] = "used";
1426
		$ruleno = 2;
1427
	}
1428
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1429
	unlock($cpruleslck);
1430
	return $ruleno;
1431
}
1432

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

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

    
1440
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1441
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1442
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1443
		$rules[$ruleno] = false;
1444
		$rules[++$ruleno] = false;
1445
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1446
	}
1447
	unlock($cpruleslck);
1448
}
1449

    
1450
function captiveportal_get_ipfw_passthru_ruleno($value) {
1451
	global $config, $g, $cpzone;
1452

    
1453
	$cpcfg = $config['captiveportal'][$cpzone];
1454
	if(!isset($cpcfg['enable']))
1455
		return NULL;
1456

    
1457
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1458
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1459
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1460
		$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`);
1461
		if ($rules[$ruleno]) {
1462
			unlock($cpruleslck);
1463
			return $ruleno;
1464
		}
1465
	}
1466

    
1467
	unlock($cpruleslck);
1468
	return NULL;
1469
}
1470

    
1471
/**
1472
 * This function will calculate the traffic produced by a client
1473
 * based on its firewall rule
1474
 *
1475
 * Point of view: NAS
1476
 *
1477
 * Input means: from the client
1478
 * Output means: to the client
1479
 *
1480
 */
1481

    
1482
function getVolume($ip) {
1483
	global $cpzone;
1484

    
1485
	$volume = array();
1486
	// Initialize vars properly, since we don't want NULL vars
1487
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1488

    
1489
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 1, $ip);
1490
	if (is_array($ipfw)) {
1491
		$volume['input_pkts'] = $ipfw['packets'];
1492
		$volume['input_bytes'] = $ipfw['bytes'];
1493
	}
1494

    
1495
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 2, $ip);
1496
	if (is_array($ipfw)) {
1497
		$volume['output_pkts'] = $ipfw['packets'];
1498
		$volume['output_bytes'] = $ipfw['bytes'];
1499
	}
1500

    
1501
	return $volume;
1502
}
1503

    
1504
/**
1505
 * Get the NAS-Identifier
1506
 *
1507
 * We will use our local hostname to make up the nas_id
1508
 */
1509
function getNasID()
1510
{
1511
	$nasId = "";
1512
	exec("/bin/hostname", $nasId);
1513
	if(!$nasId[0])
1514
		$nasId[0] = "{$g['product_name']}";
1515
	return $nasId[0];
1516
}
1517

    
1518
/**
1519
 * Get the NAS-IP-Address based on the current wan address
1520
 *
1521
 * Use functions in interfaces.inc to find this out
1522
 *
1523
 */
1524

    
1525
function getNasIP()
1526
{
1527
	global $config, $cpzone;
1528

    
1529
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1530
			$nasIp = get_interface_ip();
1531
	} else {
1532
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1533
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1534
		else
1535
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1536
	}
1537
		
1538
	if(!is_ipaddr($nasIp))
1539
		$nasIp = "0.0.0.0";
1540

    
1541
	return $nasIp;
1542
}
1543

    
1544
function portal_ip_from_client_ip($cliip) {
1545
	global $config, $cpzone;
1546

    
1547
	$isipv6 = is_ipaddrv6($cliip);
1548
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1549
	foreach ($interfaces as $cpif) {
1550
		if ($isipv6) {
1551
			$ip = get_interface_ipv6($cpif);
1552
			$sn = get_interface_subnetv6($cpif);
1553
		} else {
1554
			$ip = get_interface_ip($cpif);
1555
			$sn = get_interface_subnet($cpif);
1556
		}
1557
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1558
			return $ip;
1559
	}
1560

    
1561
	$inet = ($isipv6) ? '-inet6' : '-inet';
1562
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1563
	$iface = trim($iface, "\n");
1564
	if (!empty($iface)) {
1565
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1566
		if (is_ipaddr($ip))
1567
			return $ip;
1568
	}
1569

    
1570
	// doesn't match up to any particular interface
1571
	// so let's set the portal IP to what PHP says 
1572
	// the server IP issuing the request is. 
1573
	// allows same behavior as 1.2.x where IP isn't 
1574
	// in the subnet of any CP interface (static routes, etc.)
1575
	// rather than forcing to DNS hostname resolution
1576
	$ip = $_SERVER['SERVER_ADDR'];
1577
	if (is_ipaddr($ip))
1578
		return $ip;
1579

    
1580
	return false;
1581
}
1582

    
1583
function portal_hostname_from_client_ip($cliip) {
1584
	global $config, $cpzone;
1585

    
1586
	$cpcfg = $config['captiveportal'][$cpzone];
1587

    
1588
	if (isset($cpcfg['httpslogin'])) {
1589
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
1590
		$ourhostname = $cpcfg['httpsname'];
1591
		
1592
		if ($listenporthttps != 443)
1593
			$ourhostname .= ":" . $listenporthttps;
1594
	} else {
1595
		$listenporthttp  = $cpcfg['listenporthttp']  ? $cpcfg['listenporthttp']  : $cpcfg['zoneid'];
1596
		$ifip = portal_ip_from_client_ip($cliip);
1597
		if (!$ifip)
1598
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1599
		else
1600
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1601
		
1602
		if ($listenporthttp != 80)
1603
			$ourhostname .= ":" . $listenporthttp;
1604
	}
1605
	
1606
	return $ourhostname;
1607
}
1608

    
1609
/* functions move from index.php */
1610

    
1611
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1612
	global $g, $config, $cpzone;
1613

    
1614
	/* Get captive portal layout */
1615
	if ($type == "redir") {
1616
		header("Location: {$redirurl}");
1617
		return;
1618
	} else if ($type == "login")
1619
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1620
	else
1621
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1622

    
1623
	$cpcfg = $config['captiveportal'][$cpzone];
1624

    
1625
	/* substitute the PORTAL_REDIRURL variable */
1626
	if ($cpcfg['preauthurl']) {
1627
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1628
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1629
	}
1630

    
1631
	/* substitute other variables */
1632
	$ourhostname = portal_hostname_from_client_ip($clientip);
1633
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1634
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1635
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1636

    
1637
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1638
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1639
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1640
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1641
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1642

    
1643
	// Special handling case for captive portal master page so that it can be ran 
1644
	// through the PHP interpreter using the include method above.  We convert the
1645
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1646
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1647
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1648
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1649
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1650
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1651
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1652
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1653

    
1654
	echo $htmltext;
1655
}
1656

    
1657
function portal_mac_radius($clientmac,$clientip) {
1658
	global $config, $cpzone;
1659

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

    
1662
	/* authentication against the radius server */
1663
	$username = mac_format($clientmac);
1664
	$auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1665
	if ($auth_list['auth_val'] == 2)
1666
		return TRUE;
1667

    
1668
	if (!empty($auth_list['url_redirection']))
1669
		portal_reply_page($auth_list['url_redirection'], "redir");
1670

    
1671
	return FALSE;
1672
}
1673

    
1674
function captiveportal_reapply_attributes($cpentry, $attributes) {
1675
	global $config, $cpzone, $g;
1676

    
1677
	$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1678
	$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1679
	$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1680
	$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1681
	$bw_up_pipeno = $cpentry[1];
1682
	$bw_down_pipeno = $cpentry[1]+1;
1683

    
1684
	pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1685
	pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1686
	//captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_BANDWIDTH_REAPPLY", "{$bw_up}/{$bw_down}");
1687

    
1688
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1689
}
1690

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

    
1694
	// Ensure we create an array if we are missing attributes
1695
	if (!is_array($attributes))
1696
		$attributes = array();
1697

    
1698
	unset($sessionid);
1699

    
1700
	/* Do not allow concurrent login execution. */
1701
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1702

    
1703
	if ($attributes['voucher'])
1704
		$remaining_time = $attributes['session_timeout'];
1705

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

    
1751
	/* read in client database */
1752
	$query = "WHERE ip = '{$clientip}'";
1753
	$tmpusername = strtolower($username);
1754
	if (isset($config['captiveportal']['noconcurrentlogins']))
1755
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
1756
	$cpdb = captiveportal_read_db($query);
1757

    
1758
	/* Snapshot the timestamp */
1759
	$allow_time = time();
1760
	$radiusservers = captiveportal_get_radius_servers();
1761
	$unsetindexes = array();
1762
	if (is_null($radiusctx))
1763
		$radiusctx = 'first';
1764

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

    
1784
			/* This user was already logged in so we disconnect the old one */
1785
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1786
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1787
			$unsetindexes[] = $cpentry[5];
1788
			break;
1789
		}
1790
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1791
			/* on the same username */
1792
			if (strcasecmp($cpentry[4], $username) == 0) {
1793
				/* This user was already logged in so we disconnect the old one */
1794
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1795
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1796
				$unsetindexes[] = $cpentry[5];
1797
				break;
1798
			}
1799
		}
1800
	}
1801
	unset($cpdb);
1802

    
1803
	if (!empty($unsetindexes))
1804
		captiveportal_remove_entries($unsetindexes);
1805

    
1806
	if ($attributes['voucher'] && $remaining_time <= 0)
1807
		return 0;       // voucher already used and no time left
1808

    
1809
	if (!isset($sessionid)) {
1810
		/* generate unique session ID */
1811
		$tod = gettimeofday();
1812
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1813

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

    
1841
			/* if the pool is empty, return appropriate message and exit */
1842
			if (is_null($pipeno)) {
1843
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1844
				log_error("WARNING!  Captive portal has reached maximum login capacity");
1845
				unlock($cpdblck);
1846
				return;
1847
			}
1848

    
1849
			$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1850
			$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1851
			$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1852
			$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1853

    
1854
			$bw_up_pipeno = $pipeno;
1855
			$bw_down_pipeno = $pipeno + 1;
1856
			//$bw_up /= 1000; // Scale to Kbit/s
1857
			pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1858
			pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1859

    
1860
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
1861
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1862
				pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
1863
			else
1864
				pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
1865

    
1866
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1867
				pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
1868
			else
1869
				pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
1870

    
1871
			if ($attributes['voucher'])
1872
				$attributes['session_timeout'] = $remaining_time;
1873
			
1874
			/* handle empty attributes */
1875
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
1876
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
1877
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
1878
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
1879

    
1880
			/* escape username */
1881
			$safe_username = sqlite_escape_string($username);
1882

    
1883
			/* encode password in Base64 just in case it contains commas */
1884
			$bpassword = base64_encode($password);
1885
			$insertquery  = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval) ";
1886
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
1887
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval})";
1888

    
1889
			/* store information to database */
1890
			captiveportal_write_db($insertquery);
1891
			unlock($cpdblck);
1892

    
1893
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
1894
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
1895
				if ($acct_val == 1)
1896
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1897
			}
1898
		}
1899
	} else
1900
		unlock($cpdblck);
1901

    
1902
	if ($writecfg == true)
1903
		write_config();
1904

    
1905
	/* redirect user to desired destination */
1906
	if (!empty($attributes['url_redirection']))
1907
		$my_redirurl = $attributes['url_redirection'];
1908
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
1909
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
1910
	else
1911
		$my_redirurl = $redirurl;
1912

    
1913
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
1914
		$ourhostname = portal_hostname_from_client_ip($clientip);
1915
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
1916
		$logouturl = "{$protocol}{$ourhostname}/";
1917

    
1918
		if (isset($attributes['reply_message']))
1919
			$message = $attributes['reply_message'];
1920
		else
1921
			$message = 0;
1922

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

    
1925
	} else {
1926
		header("Location: " . $my_redirurl);
1927
	}
1928

    
1929
	return $sessionid;
1930
}
1931

    
1932

    
1933
/*
1934
 * Used for when pass-through credits are enabled.
1935
 * Returns true when there was at least one free login to deduct for the MAC.
1936
 * Expired entries are removed as they are seen.
1937
 * Active entries are updated according to the configuration.
1938
 */
1939
function portal_consume_passthrough_credit($clientmac) {
1940
	global $config, $cpzone;
1941

    
1942
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
1943
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
1944
	else
1945
		return false;
1946

    
1947
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
1948
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
1949
	else
1950
		return false;
1951

    
1952
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1953
		return false;
1954

    
1955
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
1956

    
1957
	/*
1958
	 * Read database of used MACs.  Lines are a comma-separated list
1959
	 * of the time, MAC, then the count of pass-through credits remaining.
1960
	 */
1961
	$usedmacs = captiveportal_read_usedmacs_db();
1962

    
1963
	$currenttime = time();
1964
	$found = false;
1965
	foreach ($usedmacs as $key => $usedmac) {
1966
		$usedmac = explode(",", $usedmac);
1967

    
1968
		if ($usedmac[1] == $clientmac) {
1969
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
1970
				if ($usedmac[2] < 1) {
1971
					if ($updatetimeouts) {
1972
						$usedmac[0] = $currenttime;
1973
						unset($usedmacs[$key]);
1974
						$usedmacs[] = implode(",", $usedmac);
1975
						captiveportal_write_usedmacs_db($usedmacs);
1976
					}
1977

    
1978
					return false;
1979
				} else {
1980
					$usedmac[2] -= 1;
1981
					$usedmacs[$key] = implode(",", $usedmac);
1982
				}
1983

    
1984
				$found = true;
1985
			} else
1986
				unset($usedmacs[$key]);
1987

    
1988
			break;
1989
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
1990
				unset($usedmacs[$key]);
1991
	}
1992

    
1993
	if (!$found) {
1994
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
1995
		$usedmacs[] = implode(",", $usedmac);
1996
	}
1997

    
1998
	captiveportal_write_usedmacs_db($usedmacs);
1999
	return true;
2000
}
2001

    
2002
function captiveportal_read_usedmacs_db() {
2003
	global $g, $cpzone;
2004

    
2005
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2006
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2007
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2008
		if (!$usedmacs)
2009
			$usedmacs = array();
2010
	} else
2011
		$usedmacs = array();
2012

    
2013
	unlock($cpumaclck);
2014
	return $usedmacs;
2015
}
2016

    
2017
function captiveportal_write_usedmacs_db($usedmacs) {
2018
	global $g, $cpzone;
2019

    
2020
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2021
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2022
	unlock($cpumaclck);
2023
}
2024

    
2025
?>
(8-8/67)