Project

General

Profile

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

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

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

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

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

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

    
33
	This version of captiveportal.inc has been modified by Rob Parker
34
	<rob.parker@keycom.co.uk> to include changes for per-user bandwidth management
35
	via returned RADIUS attributes. This page has been modified to delete any
36
	added rules which may have been created by other per-user code (index.php, etc).
37
	These changes are (c) 2004 Keycom PLC.
38
	
39
	pfSense_BUILDER_BINARIES:	/sbin/ipfw	/sbin/sysctl
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><td>
108
									Enter Voucher Code: 
109
									<input name="auth_voucher" type="text" style="border:1px dashed;" size="22"> 
110
								   </td></tr>
111

    
112
EOD;
113
	}
114

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

    
144
EOD;
145

    
146
	return $htmltext;
147
}
148

    
149
function captiveportal_load_modules() {
150
        global $config;
151

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

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

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

    
178
function captiveportal_configure() {
179
	global $config, $cpzone;
180

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

    
190
function captiveportal_ipfw_set_context($cpzone) {
191
	mwexec("/usr/local/sbin/ipfw_context -s {$cpzone}", true);
192
}
193

    
194
function captiveportal_configure_zone($cpcfg) {
195
	global $config, $g, $cpzone;
196

    
197
	$captiveportallck = lock("captiveportal{$cpzone}", LOCK_EX);
198
	
199
	if (isset($cpcfg['enable'])) {
200

    
201
		if ($g['booting'])
202
			echo "Starting captive portal({$cpcfg['zone']})... ";
203
		else
204
			captiveportal_syslog("Restarting captive portal({$cpcfg['zone']}).");
205

    
206
		/* kill any running mini_httpd */
207
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
208
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
209

    
210
		/* remove old information */
211
		unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.db");
212
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac_{$cpzone}.db");
213
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip_{$cpzone}.db");
214
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
215

    
216
		/* setup new database in case someone tries to access the status -> captive portal page */
217
		touch("{$g['vardb_path']}/captiveportal_{$cpzone}.db");
218

    
219
		/* kill any running minicron */
220
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
221

    
222
		/* init ipfw rules */
223
		captiveportal_init_rules(true);
224

    
225
		/* initialize minicron interval value */
226
		$croninterval = $cpcfg['croninterval'] ? $cpcfg['croninterval'] : 60;
227

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

    
232
		/* write portal page */
233
		if (is_array($cpcfg['page']) && $cpcfg['page']['htmltext'])
234
			$htmltext = base64_decode($cpcfg['page']['htmltext']);
235
		else {
236
			/* example/template page */
237
			$htmltext = get_default_captive_portal_html();
238
		}
239

    
240
		$fd = @fopen("{$g['varetc_path']}/captiveportal_{$cpzone}.html", "w");
241
		if ($fd) {
242
			// Special case handling.  Convert so that we can pass this page
243
			// through the PHP interpreter later without clobbering the vars.
244
			$htmltext = str_replace("\$PORTAL_ZONE\$", "#PORTAL_ZONE#", $htmltext);
245
			$htmltext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $htmltext);
246
			$htmltext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $htmltext);
247
			$htmltext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $htmltext);
248
			$htmltext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $htmltext);
249
			$htmltext = str_replace("\$ORIGINAL_PORTAL_IP\$", "#ORIGINAL_PORTAL_IP#", $htmltext);
250
			$htmltext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $htmltext);
251
			if($cpcfg['preauthurl']) {
252
				$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
253
				$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
254
			}
255
			fwrite($fd, $htmltext);
256
			fclose($fd);
257
		}
258
		unset($htmltext);
259

    
260
		/* write error page */
261
		if (is_array($cpcfg['page']) && $cpcfg['page']['errtext'])
262
			$errtext = base64_decode($cpcfg['page']['errtext']);
263
		else {
264
			/* example page  */
265
			$errtext = get_default_captive_portal_html();
266
		}
267

    
268
		$fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html", "w");
269
		if ($fd) {
270
			// Special case handling.  Convert so that we can pass this page
271
			// through the PHP interpreter later without clobbering the vars.
272
			$errtext = str_replace("\$PORTAL_ZONE\$", "#PORTAL_ZONE#", $errtext);
273
			$errtext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $errtext);
274
			$errtext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $errtext);
275
			$errtext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $errtext);
276
			$errtext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $errtext);
277
			$errtext = str_replace("\$ORIGINAL_PORTAL_IP\$", "#ORIGINAL_PORTAL_IP#", $errtext);
278
			$errtext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $errtext);
279
			if($cpcfg['preauthurl']) {
280
				$errtext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $errtext);
281
				$errtext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $errtext);
282
			}
283
			fwrite($fd, $errtext);
284
			fclose($fd);
285
		}
286
		unset($errtext);
287

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

    
319
document.location.href="<?=\$my_redirurl;?>";
320
-->
321
</SCRIPT>
322
</BODY>
323
</HTML>
324

    
325
EOD;
326
		}
327

    
328
		$fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html", "w");
329
		if ($fd) {
330
			fwrite($fd, $logouttext);
331
			fclose($fd);
332
		}
333
		unset($logouttext);
334

    
335
		/* write elements */
336
		captiveportal_write_elements();
337

    
338
		/* start up the webserving daemon */
339
		captiveportal_init_webgui_zone($cpcfg);
340

    
341
		/* Kill any existing prunecaptiveportal processes */
342
		if (file_exists("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid"))
343
			killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
344

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

    
349
		/* generate radius server database */
350
		captiveportal_init_radius_servers();
351

    
352
		if ($g['booting'])
353
			echo "done\n";
354

    
355
	} else {
356
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
357
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
358
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
359
		@unlink("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
360
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
361
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
362
		/* remove old information */
363
		unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.db");
364
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac_{$cpzone}.db");
365
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip_{$cpzone}.db");
366
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
367

    
368
		captiveportal_radius_stop_all();
369

    
370
		mwexec("/usr/local/sbin/ipfw_context -d {$cpzone}", 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
			foreach ($config['captiveportal'] as $cpkey => $cp) {
378
				if (isset($cp['enable'])) {
379
					$cpactive = true;
380
					break;
381
				}
382
			}
383
			if ($cpactive === false)
384
				mwexec("/sbin/sysctl net.link.ether.ipfw=0");
385
				
386
		}
387
	}
388

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

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

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

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

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

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

    
420
	$use_fastcgi = true;
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
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
430
		system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf",
431
			$crt, $key, $ca, "lighty-{$cpzone}-CaptivePortal-SSL.pid", $listenporthttps, "/usr/local/captiveportal",
432
			"cert-{$cpzone}-portal.pem", "ca-{$cpzone}-portal.pem", "1", $use_fastcgi, $cpzone);
433
	}
434

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

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

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

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

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

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

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

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

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

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

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

    
508
EOD;
509

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

    
536
	/* Authenticated users rules. */
537
	$cprules .= "add {$rulenum} pipe tablearg ip from table(1) to any in\n";
538
	$rulenum++;
539
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(2) out\n";
540
	$rulenum++;
541
	
542
	$listenporthttp =
543
		$config['captiveportal'][$cpzone]['listenporthttp'] ?
544
		$config['captiveportal'][$cpzone]['listenporthttp'] :
545
		$config['captiveportal'][$cpzone]['zoneid'];
546

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

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

    
561
EOD;
562

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

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

    
570
	/* allowed ipfw rules to make allowed hostnames work */
571
	$cprules .= captiveportal_allowedhostname_configure();
572
	
573
	/* load rules */
574
	if ($reinit == true)
575
		$cprules = "table all flush\nflush\n{$cprules}";
576
	else {
577
		$tmprules = "table 3 flush\n";
578
		$tmprules .= "table 4 flush\n";
579
		$tmprules .= "flush\n";
580
		$cprules = "{$tmprules}\n{$cprules}";
581
	}
582

    
583
	file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
584
	captiveportal_ipfw_set_context($cpzone);
585
	mwexec("/sbin/ipfw -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
586
	//@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
587
	unset($cprules, $tmprules);
588

    
589
	if ($reinit == false)
590
		unlock($captiveportallck);
591
}
592

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

    
602
	if (empty($cpzone))
603
		return;
604

    
605
	$cpcfg = $config['captiveportal'][$cpzone];
606
	$vcpcfg = $config['voucher'][$cpzone];
607

    
608
	/* check for expired entries */
609
	$idletimeout = 0;
610
	$timeout = 0;
611
	if (!empty($cpcfg['timeout']) && is_numeric($cpcfg['timeout']))
612
		$timeout = $cpcfg['timeout'] * 60;
613

    
614
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout']))
615
		$idletimeout = $cpcfg['idletimeout'] * 60;
616

    
617
	/* Is there any job to do? */
618
	if (!$timeout && !$idletimeout && !isset($cpcfg['reauthenticate']) && 
619
	    !isset($cpcfg['radiussession_timeout']) && !isset($vcpcfg['enable']))
620
		return;
621

    
622
	$radiussrvs = captiveportal_get_radius_servers();
623

    
624
	/* read database */
625
	$cpdb = captiveportal_read_db();
626

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

    
643
		$timedout = false;
644
		$term_cause = 1;
645
		if (empty($cpentry[10]))
646
			$cpentry[10] = 'first';
647
		$radiusservers = $radiussrvs[$cpentry[10]];
648

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

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

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

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

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

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

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

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

    
757
	captiveportal_prune_old_automac();
758

    
759
	if ($voucher_needs_sync == true)
760
		/* Triger a sync of the vouchers on config */
761
		send_event("service sync vouchers");
762

    
763
	/* write database */
764
	if (!empty($unsetindexes))
765
		captiveportal_write_db($cpdb, false, $unsetindexes);
766
}
767

    
768
function captiveportal_prune_old_automac() {
769
	global $g, $config, $cpzone;
770

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

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

    
833
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
834

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

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

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

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

    
880
}
881

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

    
886
	$radiusservers = captiveportal_get_radius_servers();
887
	$unsetindex = array();
888

    
889
	/* read database */
890
	$cpdb = captiveportal_read_db();
891

    
892
	/* find entry */
893
	if (isset($cpdb[$sessionid])) {
894
		$cpentry = $cpdb[$sessionid];
895
		/* write database */
896
		$unsetindex[] = $sessionid;
897
		captiveportal_write_db($cpdb, false, $unsetindex);
898
		if (empty($cpentry[10]))
899
			$cpentry[10] = 'first';
900
		captiveportal_disconnect($cpentry, $radiusservers[$cpentry[10]], $term_cause);
901
		captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
902
	}		
903
}
904

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

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

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

    
932
function captiveportal_passthrumac_configure_entry($macent) {
933

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

    
937
	$ruleno = captiveportal_get_next_ipfw_ruleno();
938
	$pipeno = captiveportal_get_next_dn_ruleno();
939

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

    
949
	return $rules;
950
}
951

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

    
955
	$rules = "";
956

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

    
963
		}
964
	}
965

    
966
	return $rules;
967
}
968

    
969
function captiveportal_passthrumac_findbyname($username) {
970
	global $config, $cpzone;
971

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

    
981
/* 
982
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
983
 */
984
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
985

    
986
	/* This function can deal with hostname or ipaddress */
987
	if($ipent['ip']) 	
988
		$ipaddress = $ipent['ip'];
989

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

    
999
	$rules = "";
1000
	$cp_filterdns_conf = "";
1001
	$enBwup = empty($ipent['bw_up']) ? 0 : intval($ipent['bw_up']);
1002
	$enBwdown = empty($ipent['bw_down']) ? 0 : intval($ipent['bw_down']);
1003

    
1004
	$pipeno = captiveportal_get_next_dn_ruleno();
1005
	$rules .= "pipe {$pipeno} config bw {$ipent['bw_up']}Kbit/s queue 100 buckets 16\n";
1006
	$pipedown = $pipeno + 1;
1007
	$rules .= "pipe {$pipedown} config bw {$ipent['bw_down']}Kbit/s queue 100 buckets 16\n";
1008
	if ($ishostname === true) {
1009
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
1010
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
1011
	}
1012
	$subnet = "";
1013
	if (!empty($ipent['sn']))
1014
		$subnet = "/{$ipent['sn']}";
1015
	$rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
1016
	$rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
1017

    
1018
	if ($ishostname === true)
1019
		return array($rules, $cp_filterdns_conf);
1020
	else
1021
		return $rules;
1022
}
1023

    
1024
function captiveportal_allowedhostname_configure() {
1025
	global $config, $g, $cpzone;
1026

    
1027
	$rules = "";
1028
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1029
		$rules = "\n# captiveportal_allowedhostname_configure()\n";
1030
		$cp_filterdns_conf = "";
1031
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) {
1032
			$tmprules = captiveportal_allowedip_configure_entry($hostnameent, true);
1033
			$rules .= $tmprules[0];
1034
			$cp_filterdns_conf .= $tmprules[1];
1035
		}
1036
		$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1037
		@file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1038
		unset($cp_filterdns_conf);
1039
		killbypid("{$g['tmp_path']}/filterdns-{$cpzone}-cpah.pid");
1040
		mwexec("/usr/local/sbin/filterdns -p {$g['tmp_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzone} -d 1");
1041
	}
1042

    
1043
	return $rules;
1044
}
1045

    
1046
function captiveportal_allowedip_configure() {
1047
	global $config, $g, $cpzone;
1048

    
1049
	$rules = "";
1050
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1051
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1052
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1053
	}
1054

    
1055
	return $rules;
1056
}
1057

    
1058
/* get last activity timestamp given client IP address */
1059
function captiveportal_get_last_activity($ip) {
1060
	global $cpzone;
1061

    
1062
	captiveportal_ipfw_set_context($cpzone);
1063
	$ipfwoutput = pfSense_ipfw_getTablestats($cpzone, 1, $ip);
1064
	/* Reading only from one of the tables is enough of approximation. */
1065
	if (is_array($ipfwoutput)) {
1066
		return $ipfwoutput['timestamp'];
1067
	}
1068

    
1069
	return 0;
1070
}
1071

    
1072
function captiveportal_init_radius_servers() {
1073
	global $config, $g, $cpzone;
1074

    
1075
	/* generate radius server database */
1076
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1077
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1078
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1079
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1080
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1081
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1082

    
1083
		if ($config['captiveportal'][$cpzone]['radiusport'])
1084
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1085
		else
1086
			$radiusport = 1812;
1087
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1088
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1089
		else
1090
			$radiusacctport = 1813;
1091
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1092
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1093
		else
1094
			$radiusport2 = 1812;
1095
		if ($config['captiveportal'][$cpzone]['radiusport3'])
1096
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1097
		else
1098
			$radiusport3 = 1812;
1099
		if ($config['captiveportal'][$cpzone]['radiusport4'])
1100
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1101
		else
1102
			$radiusport4 = 1812;
1103

    
1104
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1105
		$radiuskey2 = ($config['captiveportal'][$cpzone]['radiuskey2']) ? $config['captiveportal'][$cpzone]['radiuskey2'] : null;
1106
		$radiuskey3 = ($config['captiveportal'][$cpzone]['radiuskey3']) ? $config['captiveportal'][$cpzone]['radiuskey3'] : null;
1107
		$radiuskey4 = ($config['captiveportal'][$cpzone]['radiuskey4']) ? $config['captiveportal'][$cpzone]['radiuskey4'] : null;
1108

    
1109
		$cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX);
1110
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
1111
		if (!$fd) {
1112
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1113
			unlock($cprdsrvlck);
1114
			return 1;
1115
		}
1116
		if (isset($radiusip, $radiuskey))
1117
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
1118
		if (isset($radiusip2, $radiuskey2))
1119
			fwrite($fd,"\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
1120
		if (isset($radiusip3, $radiuskey3))
1121
			fwrite($fd,"\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
1122
		if (isset($radiusip4, $radiuskey4))
1123
			fwrite($fd,"\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
1124
		
1125

    
1126
		fclose($fd);
1127
		unlock($cprdsrvlck);
1128
	}
1129
}
1130

    
1131
/* read RADIUS servers into array */
1132
function captiveportal_get_radius_servers() {
1133
	global $g, $cpzone;
1134

    
1135
	$cprdsrvlck = lock("captiveportalradius{$cpzone}");
1136
	if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
1137
		$radiusservers = array();
1138
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", 
1139
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1140
		if ($cpradiusdb) {
1141
			foreach($cpradiusdb as $cpradiusentry) {
1142
				$line = trim($cpradiusentry);
1143
				if ($line) {
1144
					$radsrv = array();
1145
						list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key'], $context) = explode(",",$line);
1146
				}
1147
				if (empty($context)) {
1148
					if (!is_array($radiusservers['first']))
1149
						$radiusservers['first'] = array();
1150
					$radiusservers['first'] = $radsrv;
1151
				} else {
1152
					if (!is_array($radiusservers[$context]))
1153
						$radiusservers[$context] = array();
1154
					$radiusservers[$context][] = $radsrv;
1155
				}
1156
			}
1157
		}
1158
		unlock($cprdsrvlck);
1159
		return $radiusservers;
1160
	}
1161

    
1162
	unlock($cprdsrvlck);
1163
	return false;
1164
}
1165

    
1166
/* log successful captive portal authentication to syslog */
1167
/* part of this code from php.net */
1168
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1169
	// Log it
1170
	if (!$message)
1171
		$message = "$status: $user, $mac, $ip";
1172
	else {
1173
		$message = trim($message);
1174
		$message = "$status: $user, $mac, $ip, $message";
1175
	}
1176
	captiveportal_syslog($message);
1177
}
1178

    
1179
/* log simple messages to syslog */
1180
function captiveportal_syslog($message) {
1181
	$message = trim($message);
1182
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1183
	// Log it
1184
	syslog(LOG_INFO, $message);
1185
	closelog();
1186
}
1187

    
1188
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1189
	global $g, $config;
1190

    
1191
	$pipeno = captiveportal_get_next_dn_ruleno();
1192

    
1193
	/* If the pool is empty, return appropriate message and fail authentication */
1194
	if (is_null($pipeno)) {
1195
		$auth_list = array();
1196
		$auth_list['auth_val'] = 1;
1197
		$auth_list['error'] = "System reached maximum login capacity";
1198
		return $auth_list;
1199
	}
1200

    
1201
	$radiusservers = captiveportal_get_radius_servers();
1202

    
1203
	if (is_null($radiusctx))
1204
		$radiusctx = 'first';
1205

    
1206
	$auth_list = RADIUS_AUTHENTICATION($username,
1207
		$password,
1208
		$radiusservers[$radiusctx],
1209
		$clientip,
1210
		$clientmac,
1211
		$pipeno);
1212

    
1213
	if ($auth_list['auth_val'] == 2) {
1214
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1215
		$sessionid = portal_allow($clientip,
1216
			$clientmac,
1217
			$username,
1218
			$password,
1219
			$auth_list,
1220
			$pipeno,
1221
			$radiusctx);
1222
	}
1223

    
1224
	return $auth_list;
1225
}
1226

    
1227
/* read captive portal DB into array */
1228
function captiveportal_read_db($locked = false, $index = 5 /* sessionid by default */) {
1229
	global $g, $cpzone;
1230

    
1231
	$cpdb = array();
1232

    
1233
	if ($locked == false)
1234
		$cpdblck = lock("captiveportaldb{$cpzone}");
1235
	$fd = @fopen("{$g['vardb_path']}/captiveportal_{$cpzone}.db", "r");
1236
	if ($fd) {
1237
		while (!feof($fd)) {
1238
			$line = trim(fgets($fd));
1239
			if ($line) {
1240
				$cpe = explode(",", $line);
1241
				/* Hash by session id */
1242
				$cpdb[$cpe[$index]] = $cpe;
1243
			}
1244
		}
1245
		fclose($fd);
1246
	}
1247
	if ($locked == false)
1248
		unlock($cpdblck);
1249
	return $cpdb;
1250
}
1251

    
1252
/* write captive portal DB */
1253
function captiveportal_write_db($cpdb, $locked = false, $remove = false) {
1254
	global $g, $cpzone;
1255

    
1256
	if ($locked == false)
1257
		$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1258

    
1259
	if (is_array($remove)) {
1260
		if (!empty($remove)) {
1261
			$cpdb = captiveportal_read_db(true);
1262
			foreach ($remove as $key) {
1263
				if (is_array($key))
1264
					log_error("Captive portal Array passed as unset index: " . print_r($key, true));
1265
				else
1266
					unset($cpdb[$key]);
1267
			}
1268
		} else {
1269
			if ($locked == false)
1270
				unlock($cpdblck);
1271
			return; //This makes sure no record removal calls
1272
		}
1273
	}
1274
	$fd = @fopen("{$g['vardb_path']}/captiveportal_{$cpzone}.db", "w");
1275
	if ($fd) {
1276
		foreach ($cpdb as $cpent) {
1277
			fwrite($fd, join(",", $cpent) . "\n");
1278
		}
1279
		fclose($fd);
1280
	}
1281
	if ($locked == false)
1282
		unlock($cpdblck);
1283
}
1284

    
1285
function captiveportal_write_elements() {
1286
	global $g, $config, $cpzone;
1287
	
1288
	$cpcfg = $config['captiveportal'][$cpzone];
1289

    
1290
	/* delete any existing elements */
1291
	if (is_dir($g['captiveportal_element_path'])) {
1292
		$dh = opendir($g['captiveportal_element_path']);
1293
		while (($file = readdir($dh)) !== false) {
1294
			if ($file != "." && $file != "..")
1295
				unlink($g['captiveportal_element_path'] . "/" . $file);
1296
		}
1297
		closedir($dh);
1298
	} else {
1299
		@mkdir($g['captiveportal_element_path']);
1300
	}
1301

    
1302
	if (is_array($cpcfg['element'])) {
1303
		conf_mount_rw();
1304
		foreach ($cpcfg['element'] as $data) {
1305
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
1306
			if (!$fd) {
1307
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1308
				return 1;
1309
			}
1310
			$decoded = base64_decode($data['content']);
1311
			fwrite($fd,$decoded);
1312
			fclose($fd);
1313
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1314
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1315
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
1316
		}
1317
		conf_mount_ro();
1318
	}
1319
	
1320
	return 0;
1321
}
1322

    
1323
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1324
	global $config, $g;
1325

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

    
1349
	return $ruleno;
1350
}
1351

    
1352
function captiveportal_free_dn_ruleno($ruleno) {
1353
        global $config, $g;
1354

    
1355
        $cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1356
        if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1357
                $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1358
                $rules[$ruleno] = false;
1359
                $rules[++$ruleno] = false;
1360
                file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1361
        }
1362
        unlock($cpruleslck);
1363
}
1364

    
1365
function captiveportal_get_dn_passthru_ruleno($value) {
1366
	global $config, $g, $cpzone;
1367

    
1368
	$cpcfg = $config['captiveportal'][$cpzone];
1369
	if(!isset($cpcfg['enable']))
1370
		return NULL;
1371

    
1372
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1373
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1374
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1375
		captiveportal_ipfw_set_context($cpzone);
1376
		$ruleno = intval(`/sbin/ipfw show | /usr/bin/grep {$value} |  /usr/bin/grep -v grep | /usr/bin/cut -d " " -f 5 | /usr/bin/head -n 1`);
1377
		if ($rules[$ruleno]) {
1378
			unlock($cpruleslck);
1379
			return $ruleno;
1380
		}
1381
	}
1382

    
1383
	unlock($cpruleslck);
1384
	return NULL;
1385
}
1386

    
1387
/*
1388
 * This function will calculate the lowest free firewall ruleno
1389
 * within the range specified based on the actual logged on users
1390
 *
1391
 */
1392
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1393
	global $config, $g, $cpzone;
1394

    
1395
	$cpcfg = $config['captiveportal'][$cpzone];
1396
	if(!isset($cpcfg['enable']))
1397
		return NULL;
1398

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

    
1428
function captiveportal_free_ipfw_ruleno($ruleno) {
1429
	global $config, $g, $cpzone;
1430

    
1431
	$cpcfg = $config['captiveportal'][$cpzone];
1432
	if(!isset($cpcfg['enable']))
1433
		return NULL;
1434

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

    
1445
function captiveportal_get_ipfw_passthru_ruleno($value) {
1446
	global $config, $g, $cpzone;
1447

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

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

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

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

    
1478
function getVolume($ip) {
1479
	global $cpzone;
1480

    
1481
	$volume = array();
1482
	// Initialize vars properly, since we don't want NULL vars
1483
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1484

    
1485
	captiveportal_ipfw_set_context($cpzone);
1486
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 1, $ip);
1487
	if (is_array($ipfw)) {
1488
		$volume['input_pkts'] = $ipfw['packets'];
1489
		$volume['input_bytes'] = $ipfw['bytes'];
1490
	}
1491

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

    
1498
	return $volume;
1499
}
1500

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

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

    
1522
function getNasIP()
1523
{
1524
	global $config, $cpzone;
1525

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

    
1538
	return $nasIp;
1539
}
1540

    
1541
function portal_ip_from_client_ip($cliip) {
1542
	global $config, $cpzone;
1543

    
1544
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1545
	foreach ($interfaces as $cpif) {
1546
		$ip = get_interface_ip($cpif);
1547
		$sn = get_interface_subnet($cpif);
1548
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1549
			return $ip;
1550
	}
1551

    
1552
	$iface = exec_command("/sbin/route -n get {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1553
	$iface = trim($iface, "\n");
1554
	if (!empty($iface)) {
1555
		$ip = find_interface_ip($iface);
1556
		if (is_ipaddr($ip))
1557
			return $ip;
1558
	}
1559

    
1560
	// doesn't match up to any particular interface
1561
	// so let's set the portal IP to what PHP says 
1562
	// the server IP issuing the request is. 
1563
	// allows same behavior as 1.2.x where IP isn't 
1564
	// in the subnet of any CP interface (static routes, etc.)
1565
	// rather than forcing to DNS hostname resolution
1566
	$ip = $_SERVER['SERVER_ADDR'];
1567
	if (is_ipaddr($ip))
1568
		return $ip;
1569

    
1570
	return false;
1571
}
1572

    
1573
/* functions move from index.php */
1574

    
1575
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1576
	global $g, $config, $cpzone;
1577

    
1578
	/* Get captive portal layout */
1579
	if ($type == "redir") {
1580
		header("Location: {$redirurl}");
1581
		return;
1582
	} else if ($type == "login")
1583
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1584
	else
1585
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1586

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

    
1589
	/* substitute the PORTAL_REDIRURL variable */
1590
	if ($config['captiveportal'][$cpzone]['preauthurl']) {
1591
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$config['captiveportal'][$cpzone]['preauthurl']}", $htmltext);
1592
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$config['captiveportal'][$cpzone]['preauthurl']}", $htmltext);
1593
	}
1594

    
1595
	/* substitute other variables */
1596
	if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
1597
		$httpsport = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
1598
		$htmltext = str_replace("\$PORTAL_ACTION\$", "https://{$config['captiveportal'][$cpzone]['httpsname']}:{$httpsport}/", $htmltext);
1599
		$htmltext = str_replace("#PORTAL_ACTION#", "https://{$config['captiveportal'][$cpzone]['httpsname']}:{$httpsport}/", $htmltext);
1600
	} else {
1601
		$httpport = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : $cpcfg['zoneid'];
1602
		$ifip = portal_ip_from_client_ip($clientip);
1603
		if (!$ifip)
1604
			$ourhostname = $config['system']['hostname'] . ":{$httpport}";
1605
		else
1606
			$ourhostname = "{$ifip}:{$httpport}";
1607
		$htmltext = str_replace("\$PORTAL_ACTION\$", "http://{$ourhostname}/", $htmltext);
1608
		$htmltext = str_replace("#PORTAL_ACTION#", "http://{$ourhostname}/", $htmltext);
1609
	}
1610

    
1611
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1612
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1613
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1614
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1615
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1616

    
1617
	// Special handling case for captive portal master page so that it can be ran 
1618
	// through the PHP interpreter using the include method above.  We convert the
1619
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1620
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1621
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1622
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1623
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1624
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1625
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1626
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1627

    
1628
    echo $htmltext;
1629
}
1630

    
1631
function portal_mac_radius($clientmac,$clientip) {
1632
    global $config, $cpzone;
1633

    
1634
    $radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1635

    
1636
    /* authentication against the radius server */
1637
    $username = mac_format($clientmac);
1638
    $auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1639
    if ($auth_list['auth_val'] == 2)
1640
        return TRUE;
1641
    if (!empty($auth_list['url_redirection']))
1642
	portal_reply_page($auth_list['url_redirection'], "redir");
1643

    
1644
    return FALSE;
1645
}
1646

    
1647
function captiveportal_reapply_attributes($cpentry, $attributes) {
1648
	global $config, $cpzone, $g;
1649
                         
1650
	$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1651
	$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1652
        $bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1653
        $bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1654
        $bw_up_pipeno = $cpentry[1];
1655
        $bw_down_pipeno = $cpentry[1]+1;
1656

    
1657
	pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1658
	pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1659
	//captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_BANDWIDTH_REAPPLY", "{$bw_up}/{$bw_down}");
1660

    
1661
        unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1662
}
1663

    
1664
function portal_allow($clientip,$clientmac,$username,$password = null, $attributes = null, $pipeno = null, $radiusctx = null)  {
1665

    
1666
	global $redirurl, $g, $config, $type, $passthrumac, $_POST, $cpzone;
1667

    
1668
	// Ensure we create an array if we are missing attributes
1669
	if (!is_array($attributes))
1670
		$attributes = array();
1671

    
1672
	$radiusservers = captiveportal_get_radius_servers();
1673

    
1674
	/* Do not allow concurrent login execution. */
1675
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1676

    
1677
	unset($sessionid);
1678

    
1679
	/* read in client database */
1680
	$cpdb = captiveportal_read_db(true);
1681

    
1682
	if ($attributes['voucher'])
1683
		$remaining_time = $attributes['session_timeout'];
1684

    
1685
	$writecfg = false;
1686
	/* Find an existing session */
1687
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
1688
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1689
			$mac = captiveportal_passthrumac_findbyname($username);
1690
			if (!empty($mac)) {
1691
				if ($_POST['replacemacpassthru']) {
1692
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
1693
						if ($macent['mac'] == $mac['mac']) {
1694
							$macrules = "";
1695
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1696
							$pipeno = captiveportal_get_dn_passthru_ruleno($mac['mac']);
1697
                                			if ($ruleno) {
1698
								captiveportal_free_ipfw_ruleno($ruleno);
1699
                                        			$macrules .= "delete {$ruleno}\n";
1700
								++$ruleno;
1701
                                        			$macrules .= "delete {$ruleno}\n";
1702
                                			}
1703
							if ($pipeno) {
1704
								captiveportal_free_dn_ruleno($pipeno);
1705
                                        			$macrules .= "pipe delete {$pipeno}\n";
1706
								++$pipeno;
1707
                                        			$macrules .= "pipe delete {$pipeno}\n";
1708
							}
1709
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1710
							$mac['mac'] = $clientmac;
1711
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1712
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1713
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1714
							captiveportal_ipfw_set_context($cpzone);
1715
							mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1716
							$writecfg = true;
1717
							$sessionid = true;
1718
							break;
1719
						}
1720
					}
1721
                                } else {
1722
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1723
						$clientmac, $clientip, $username, $password);
1724
					unlock($cpdblck);
1725
					return;
1726
				}
1727
			}
1728
		}
1729
	}
1730

    
1731
	/* Snapshot the timestamp */
1732
	$allow_time = time();
1733
	if (is_null($radiusctx))
1734
		$radiusctx = 'first';
1735
	foreach ($cpdb as $sid => $cpentry) {
1736
		if (empty($cpentry[10]))
1737
			$cpentry[10] = 'first';
1738
		/* on the same ip */
1739
		if ($cpentry[2] == $clientip) {
1740
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac)   
1741
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1742
			else
1743
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
1744
			$sessionid = $sid;
1745
			break;
1746
		}
1747
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1748
			// user logged in with an active voucher. Check for how long and calculate 
1749
			// how much time we can give him (voucher credit - used time)
1750
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1751
			if ($remaining_time < 0)    // just in case. 
1752
				$remaining_time = 0;
1753

    
1754
			/* This user was already logged in so we disconnect the old one */
1755
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1756
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1757
			unset($cpdb[$sid]);
1758
			break;
1759
		}
1760
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1761
			/* on the same username */
1762
			if (strcasecmp($cpentry[4], $username) == 0) {
1763
				/* This user was already logged in so we disconnect the old one */
1764
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1765
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1766
				unset($cpdb[$sid]);
1767
				break;
1768
			}
1769
		}
1770
	}
1771

    
1772
	if ($attributes['voucher'] && $remaining_time <= 0)
1773
		return 0;       // voucher already used and no time left
1774

    
1775
	if (!isset($sessionid)) {
1776
		/* generate unique session ID */
1777
		$tod = gettimeofday();
1778
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1779

    
1780
		$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1781
		$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1782
		$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1783
		$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1784

    
1785
		if ($passthrumac) {
1786
			$mac = array();
1787
			$mac['mac'] = $clientmac;
1788
			$mac['ip'] = $clientip; /* Used only for logging */
1789
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
1790
				$mac['username'] = $username;
1791
				if ($attributes['voucher'])
1792
					$mac['logintype'] = "voucher";
1793
			}
1794
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1795
			if (!empty($bw_up))
1796
				$mac['bw_up'] = $bw_up;
1797
			if (!empty($bw_down))
1798
				$mac['bw_down'] = $bw_down;
1799
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
1800
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
1801
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1802
			unlock($cpdblck);
1803
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1804
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1805
			captiveportal_ipfw_set_context($cpzone);
1806
			mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1807
			$writecfg = true;
1808
		} else {
1809
			/* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
1810
			if (is_null($pipeno))
1811
				$pipeno = captiveportal_get_next_dn_ruleno();
1812

    
1813
			/* if the pool is empty, return appropriate message and exit */
1814
			if (is_null($pipeno)) {
1815
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1816
				log_error("WARNING!  Captive portal has reached maximum login capacity");
1817
				unlock($cpdblck);
1818
				return;
1819
			}
1820

    
1821
			$bw_up_pipeno = $pipeno;
1822
			$bw_down_pipeno = $pipeno + 1;
1823
			//$bw_up /= 1000; // Scale to Kbit/s
1824
			pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1825
			pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1826

    
1827
			captiveportal_ipfw_set_context($cpzone);
1828
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1829
				pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, 32, $clientmac, $bw_up_pipeno);
1830
			else
1831
				pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, 32, NULL, $bw_up_pipeno);
1832

    
1833
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1834
				pfSense_ipfw_Tableaction("", IP_FW_TABLE_ADD, 2, $clientip, 32, $clientmac, $bw_down_pipeno);
1835
			else
1836
				pfSense_ipfw_Tableaction("", IP_FW_TABLE_ADD, 2, $clientip, 32, NULL, $bw_down_pipeno);
1837

    
1838
			if ($attributes['voucher'])
1839
				$attributes['session_timeout'] = $remaining_time;
1840

    
1841
			/* encode password in Base64 just in case it contains commas */
1842
			$bpassword = base64_encode($password);
1843
			$cpdb[] = array($allow_time, $pipeno, $clientip, $clientmac, $username, $sessionid, $bpassword,
1844
				$attributes['session_timeout'], $attributes['idle_timeout'], $attributes['session_terminate_time'], $radiusctx);
1845

    
1846
			/* rewrite information to database */
1847
			captiveportal_write_db($cpdb, true);
1848
			unlock($cpdblck);
1849

    
1850
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
1851
				$acct_val = RADIUS_ACCOUNTING_START($pipeno,
1852
                                		$username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
1853
				if ($acct_val == 1)
1854
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1855
			}
1856
		}
1857
	} else
1858
		unlock($cpdblck);
1859

    
1860
	if ($writecfg == true)
1861
		write_config();
1862

    
1863
	/* redirect user to desired destination */
1864
	if (!empty($attributes['url_redirection']))
1865
		$my_redirurl = $attributes['url_redirection'];
1866
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
1867
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
1868
	else
1869
		$my_redirurl = $redirurl;
1870

    
1871
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
1872

    
1873
		if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
1874
			$httpsport =
1875
				$config['captiveportal'][$cpzone]['listenporthttps'] ?
1876
				$config['captiveportal'][$cpzone]['listenporthttps'] :
1877
				($config['captiveportal'][$cpzone]['zoneid'] + 1);
1878
			$logouturl = "https://{$config['captiveportal'][$cpzone]['httpsname']}:{$httpsport}/";
1879
		} else {
1880
			$ifip = portal_ip_from_client_ip($clientip);
1881
			$httpport =
1882
				$config['captiveportal'][$cpzone]['listenporthttp'] ?
1883
				$config['captiveportal'][$cpzone]['listenporthttp'] :
1884
				$config['captiveportal'][$cpzone]['zoneid'];
1885
			if (!$ifip)
1886
				$ourhostname = $config['system']['hostname'] . ":{$httpport}";
1887
			else
1888
				$ourhostname = "{$ifip}:{$httpport}";
1889
			$logouturl = "http://{$ourhostname}/";
1890
		}
1891

    
1892
		if (isset($attributes['reply_message']))
1893
			$message = $attributes['reply_message'];
1894
		else
1895
			$message = 0;
1896

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

    
1899
	} else {
1900
		header("Location: " . $my_redirurl);
1901
	}
1902

    
1903
	return $sessionid;
1904
}
1905

    
1906

    
1907
/*
1908
 * Used for when pass-through credits are enabled.
1909
 * Returns true when there was at least one free login to deduct for the MAC.
1910
 * Expired entries are removed as they are seen.
1911
 * Active entries are updated according to the configuration.
1912
 */
1913
function portal_consume_passthrough_credit($clientmac) {
1914
	global $config, $cpzone;
1915

    
1916
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
1917
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
1918
	else
1919
		return false;
1920

    
1921
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
1922
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
1923
	else
1924
		return false;
1925

    
1926
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1927
		return false;
1928

    
1929
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
1930

    
1931
	/*
1932
	 * Read database of used MACs.  Lines are a comma-separated list
1933
	 * of the time, MAC, then the count of pass-through credits remaining.
1934
	 */
1935
	$usedmacs = captiveportal_read_usedmacs_db();
1936

    
1937
	$currenttime = time();
1938
	$found = false;
1939
	foreach ($usedmacs as $key => $usedmac) {
1940
		$usedmac = explode(",", $usedmac);
1941

    
1942
		if ($usedmac[1] == $clientmac) {
1943
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
1944
				if ($usedmac[2] < 1) {
1945
					if ($updatetimeouts) {
1946
						$usedmac[0] = $currenttime;
1947
						unset($usedmacs[$key]);
1948
						$usedmacs[] = implode(",", $usedmac);
1949
						captiveportal_write_usedmacs_db($usedmacs);
1950
					}
1951

    
1952
					return false;
1953
				} else {
1954
					$usedmac[2] -= 1;
1955
					$usedmacs[$key] = implode(",", $usedmac);
1956
				}
1957

    
1958
				$found = true;
1959
			} else
1960
				unset($usedmacs[$key]);
1961

    
1962
			break;
1963
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
1964
				unset($usedmacs[$key]);
1965
	}
1966

    
1967
	if (!$found) {
1968
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
1969
		$usedmacs[] = implode(",", $usedmac);
1970
	}
1971

    
1972
	captiveportal_write_usedmacs_db($usedmacs);
1973
	return true;
1974
}
1975

    
1976
function captiveportal_read_usedmacs_db() {
1977
	global $g, $cpzone;
1978

    
1979
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
1980
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
1981
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1982
		if (!$usedmacs)
1983
			$usedmacs = array();
1984
	} else
1985
		$usedmacs = array();
1986

    
1987
	unlock($cpumaclck);
1988
	return $usedmacs;
1989
}
1990

    
1991
function captiveportal_write_usedmacs_db($usedmacs) {
1992
	global $g, $cpzone;
1993

    
1994
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
1995
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
1996
	unlock($cpumaclck);
1997
}
1998

    
1999
?>
(8-8/68)