Project

General

Profile

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

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

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

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

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

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

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

    
113
EOD;
114
	}
115

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

    
145
EOD;
146

    
147
	return $htmltext;
148
}
149

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

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

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

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

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

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

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

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

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

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

    
205
			/* remove old information */
206
			unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
207
		} else
208
			captiveportal_syslog("Reconfiguring captive portal({$cpcfg['zone']}).");
209

    
210
		/* init ipfw rules */
211
		captiveportal_init_rules(true);
212

    
213
		/* kill any running minicron */
214
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
215

    
216
		/* initialize minicron interval value */
217
		$croninterval = $cpcfg['croninterval'] ? $cpcfg['croninterval'] : 60;
218

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

    
223
		/* write portal page */
224
		if (is_array($cpcfg['page']) && $cpcfg['page']['htmltext'])
225
			$htmltext = base64_decode($cpcfg['page']['htmltext']);
226
		else {
227
			/* example/template page */
228
			$htmltext = get_default_captive_portal_html();
229
		}
230

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

    
251
		/* write error page */
252
		if (is_array($cpcfg['page']) && $cpcfg['page']['errtext'])
253
			$errtext = base64_decode($cpcfg['page']['errtext']);
254
		else {
255
			/* example page  */
256
			$errtext = get_default_captive_portal_html();
257
		}
258

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

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

    
310
document.location.href="<?=\$my_redirurl;?>";
311
-->
312
</SCRIPT>
313
</BODY>
314
</HTML>
315

    
316
EOD;
317
		}
318

    
319
		$fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html", "w");
320
		if ($fd) {
321
			fwrite($fd, $logouttext);
322
			fclose($fd);
323
		}
324
		unset($logouttext);
325

    
326
		/* write elements */
327
		captiveportal_write_elements();
328

    
329
		/* kill any running mini_httpd */
330
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
331
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
332

    
333
		/* start up the webserving daemon */
334
		captiveportal_init_webgui_zone($cpcfg);
335

    
336
		/* Kill any existing prunecaptiveportal processes */
337
		if (file_exists("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid"))
338
			killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
339

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

    
344
		/* generate radius server database */
345
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
346
		captiveportal_init_radius_servers();
347

    
348
		if ($g['booting'])
349
			echo "done\n";
350

    
351
	} else {
352
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
353
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
354
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
355
		@unlink("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
356
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
357
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
358
		/* remove old information */
359
		unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
360
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
361

    
362
		captiveportal_radius_stop_all();
363

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

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

    
383
	unlock($captiveportallck);
384
	
385
	return 0;
386
}
387

    
388
function captiveportal_init_webgui() {
389
	global $config, $cpzone;
390

    
391
	if (is_array($config['captiveportal'])) {
392
		foreach ($config['captiveportal'] as $cpkey => $cp) {
393
			$cpzone = $cpkey;
394
			captiveportal_init_webgui_zone($cp);
395
		}
396
	}
397
}
398

    
399
function captiveportal_init_webgui_zonename($zone) {
400
	global $config, $cpzone;
401
	
402
	if (isset($config['captiveportal'][$zone])) {
403
		$cpzone = $zone;
404
		captiveportal_init_webgui_zone($config['captiveportal'][$zone]);
405
	}
406
}
407

    
408
function captiveportal_init_webgui_zone($cpcfg) {
409
	global $g, $config, $cpzone;
410

    
411
	if (!isset($cpcfg['enable']))
412
		return;
413

    
414
	$use_fastcgi = true;
415

    
416
	if (isset($cpcfg['httpslogin'])) {
417
		$cert = lookup_cert($cpcfg['certref']);
418
		$crt = base64_decode($cert['crt']);
419
		$key = base64_decode($cert['prv']);
420
		$ca = ca_chain($cert);
421
    
422
		/* generate lighttpd configuration */
423
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
424
		system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf",
425
			$crt, $key, $ca, "lighty-{$cpzone}-CaptivePortal-SSL.pid", $listenporthttps, "/usr/local/captiveportal",
426
			"cert-{$cpzone}-portal.pem", "ca-{$cpzone}-portal.pem", "1", $use_fastcgi, $cpzone);
427
	}
428

    
429
	/* generate lighttpd configuration */
430
	$listenporthttp = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : $cpcfg['zoneid'];
431
	system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal.conf",
432
		"", "", "", "lighty-{$cpzone}-CaptivePortal.pid", $listenporthttp, "/usr/local/captiveportal",
433
		"", "", "1", $use_fastcgi, $cpzone);
434

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

    
438
	/* fire up https instance */
439
	if (isset($cpcfg['httpslogin']))
440
		$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf");
441
}
442

    
443
/* reinit will disconnect all users, be careful! */
444
function captiveportal_init_rules($reinit = false) {
445
	global $config, $g, $cpzone;
446

    
447
	if (!isset($config['captiveportal'][$cpzone]['enable']))
448
		return;
449

    
450
	captiveportal_load_modules();
451
	mwexec("/usr/local/sbin/ipfw_context -a {$cpzone}", true);
452
	captiveportal_ipfw_set_context($cpzone);
453

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

    
485
	if ($reinit == false)
486
		$captiveportallck = lock("captiveportal{$cpzone}");
487

    
488
	$cprules =	"add 65291 allow pfsync from any to any\n";
489
	$cprules .= "add 65292 allow carp from any to any\n";
490

    
491
	$cprules .= <<<EOD
492
# layer 2: pass ARP
493
add 65301 pass layer2 mac-type arp,rarp
494
# pfsense requires for WPA
495
add 65302 pass layer2 mac-type 0x888e,0x88c7
496
# PPP Over Ethernet Session Stage/Discovery Stage
497
add 65303 pass layer2 mac-type 0x8863,0x8864
498

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

    
502
EOD;
503

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

    
530
	/* Authenticated users rules. */
531
	$cprules .= "add {$rulenum} pipe tablearg ip from table(1) to any in\n";
532
	$rulenum++;
533
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(2) out\n";
534
	$rulenum++;
535
	
536
	$listenporthttp =
537
		$config['captiveportal'][$cpzone]['listenporthttp'] ?
538
		$config['captiveportal'][$cpzone]['listenporthttp'] :
539
		$config['captiveportal'][$cpzone]['zoneid'];
540

    
541
	if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
542
		$listenporthttps = $listenporthttp + 1;
543
		$cprules .= "add 65531 fwd 127.0.0.1,{$listenporthttps} tcp from any to any dst-port 443 in\n";
544
	}
545
	
546
	$cprules .= <<<EOD
547

    
548
# redirect non-authenticated clients to captive portal
549
add 65532 fwd 127.0.0.1,{$listenporthttp} tcp from any to any dst-port 80 in 
550
# let the responses from the captive portal web server back out
551
add 65533 pass tcp from any to any out
552
# block everything else
553
add 65534 deny all from any to any
554

    
555
EOD;
556

    
557
	/* generate passthru mac database */
558
	$cprules .= captiveportal_passthrumac_configure(true);
559
	$cprules .= "\n";
560

    
561
	/* allowed ipfw rules to make allowed ip work */
562
	$cprules .= captiveportal_allowedip_configure();
563

    
564
	/* allowed ipfw rules to make allowed hostnames work */
565
	$cprules .= captiveportal_allowedhostname_configure();
566
	
567
	/* load rules */
568
	$cprules = "flush\n{$cprules}";
569
	file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
570
	captiveportal_ipfw_set_context($cpzone);
571
	mwexec("/sbin/ipfw -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
572
	//@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
573
	unset($cprules, $tmprules);
574

    
575
	if ($reinit == false)
576
		unlock($captiveportallck);
577
}
578

    
579
/* 
580
 * Remove clients that have been around for longer than the specified amount of time
581
 * db file structure:
582
 * timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time
583
 * (password is in Base64 and only saved when reauthentication is enabled)
584
 */
585
function captiveportal_prune_old() {
586
	global $g, $config, $cpzone;
587

    
588
	if (empty($cpzone))
589
		return;
590

    
591
	$cpcfg = $config['captiveportal'][$cpzone];
592
	$vcpcfg = $config['voucher'][$cpzone];
593

    
594
	/* check for expired entries */
595
	$idletimeout = 0;
596
	$timeout = 0;
597
	if (!empty($cpcfg['timeout']) && is_numeric($cpcfg['timeout']))
598
		$timeout = $cpcfg['timeout'] * 60;
599

    
600
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout']))
601
		$idletimeout = $cpcfg['idletimeout'] * 60;
602

    
603
	/* Is there any job to do? */
604
	if (!$timeout && !$idletimeout && !isset($cpcfg['reauthenticate']) && 
605
	    !isset($cpcfg['radiussession_timeout']) && !isset($vcpcfg['enable']))
606
		return;
607

    
608
	$radiussrvs = captiveportal_get_radius_servers();
609

    
610
	/* Read database */
611
	/* NOTE: while this can be simplified in non radius case keep as is for now */
612
	$cpdb = captiveportal_read_db();
613

    
614
	/*
615
	 * To make sure we iterate over ALL accounts on every run the count($cpdb) is moved
616
	 * outside of the loop. Otherwise the loop would evaluate count() on every iteration
617
	 * and since $i would increase and count() would decrement they would meet before we
618
	 * had a chance to iterate over all accounts.
619
	 */
620
	$unsetindexes = array();
621
	$voucher_needs_sync = false;
622
	/* 
623
	 * Snapshot the time here to use for calculation to speed up the process.
624
	 * If something is missed next run will catch it!
625
	 */
626
	$pruning_time = time();
627
	$stop_time = $pruning_time;
628
	foreach ($cpdb as $cpentry) {
629

    
630
		$timedout = false;
631
		$term_cause = 1;
632
		if (empty($cpentry[10]))
633
			$cpentry[10] = 'first';
634
		$radiusservers = $radiussrvs[$cpentry[10]];
635

    
636
		/* hard timeout? */
637
		if ($timeout) {
638
			if (($pruning_time - $cpentry[0]) >= $timeout) {
639
				$timedout = true;
640
				$term_cause = 5; // Session-Timeout
641
			}
642
		}
643

    
644
		/* Session-Terminate-Time */
645
		if (!$timedout && !empty($cpentry[9])) {
646
			if ($pruning_time >= $cpentry[9]) {
647
				$timedout = true;
648
				$term_cause = 5; // Session-Timeout
649
			}
650
		}
651

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

    
668
		/* if vouchers are configured, activate session timeouts */
669
		if (!$timedout && isset($vcpcfg['enable']) && !empty($cpentry[7])) {
670
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
671
				$timedout = true;
672
				$term_cause = 5; // Session-Timeout
673
				$voucher_needs_sync = true;
674
			}
675
		}
676

    
677
		/* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
678
		if (!$timedout && isset($cpcfg['radiussession_timeout']) && !empty($cpentry[7])) {
679
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
680
				$timedout = true;
681
				$term_cause = 5; // Session-Timeout
682
			}
683
		}
684

    
685
		if ($timedout) {
686
			captiveportal_disconnect($cpentry, $radiusservers,$term_cause,$stop_time);
687
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
688
			$unsetindexes[] = $cpentry[5];
689
		}
690

    
691
		/* do periodic RADIUS reauthentication? */
692
		if (!$timedout && !empty($radiusservers)) {
693
			if (isset($cpcfg['radacct_enable'])) {
694
				if ($cpcfg['reauthenticateacct'] == "stopstart") {
695
					/* stop and restart accounting */
696
					RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
697
						$cpentry[4], // username
698
						$cpentry[5], // sessionid
699
						$cpentry[0], // start time
700
						$radiusservers,
701
						$cpentry[2], // clientip
702
						$cpentry[3], // clientmac
703
						10); // NAS Request
704
					captiveportal_ipfw_set_context($cpzone);
705
					pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ZERO_ENTRY_STATS, 1, $cpentry[2]);
706
					pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ZERO_ENTRY_STATS, 2, $cpentry[2]);
707
					RADIUS_ACCOUNTING_START($cpentry[1], // ruleno
708
						$cpentry[4], // username
709
						$cpentry[5], // sessionid
710
						$radiusservers,
711
						$cpentry[2], // clientip
712
						$cpentry[3]); // clientmac
713
				} else if ($cpcfg['reauthenticateacct'] == "interimupdate") {
714
					RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
715
						$cpentry[4], // username
716
						$cpentry[5], // sessionid
717
						$cpentry[0], // start time
718
						$radiusservers,
719
						$cpentry[2], // clientip
720
						$cpentry[3], // clientmac
721
						10, // NAS Request
722
						true); // Interim Updates
723
				}
724
			}
725

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

    
744
	captiveportal_prune_old_automac();
745

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

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

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

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

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

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

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

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

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

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

    
867
}
868

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

    
873
	$radiusservers = captiveportal_get_radius_servers();
874

    
875
	/* read database */
876
	$cpentry = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
877

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

    
882
		if (empty($cpentry[10]))
883
			$cpentry[10] = 'first';
884
		captiveportal_disconnect($cpentry, $radiusservers[$cpentry[10]], $term_cause);
885
		captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
886
		unset($cpentry);
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
		killbypid("{$g['tmp_path']}/filterdns-{$cpzone}-cpah.pid");
1025
		mwexec("/usr/local/sbin/filterdns -p {$g['tmp_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzone} -d 1");
1026
	}
1027

    
1028
	return $rules;
1029
}
1030

    
1031
function captiveportal_allowedip_configure() {
1032
	global $config, $g, $cpzone;
1033

    
1034
	$rules = "";
1035
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1036
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1037
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1038
	}
1039

    
1040
	return $rules;
1041
}
1042

    
1043
/* get last activity timestamp given client IP address */
1044
function captiveportal_get_last_activity($ip) {
1045
	global $cpzone;
1046

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

    
1054
	return 0;
1055
}
1056

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

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

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

    
1089
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1090
		$radiuskey2 = ($config['captiveportal'][$cpzone]['radiuskey2']) ? $config['captiveportal'][$cpzone]['radiuskey2'] : null;
1091
		$radiuskey3 = ($config['captiveportal'][$cpzone]['radiuskey3']) ? $config['captiveportal'][$cpzone]['radiuskey3'] : null;
1092
		$radiuskey4 = ($config['captiveportal'][$cpzone]['radiuskey4']) ? $config['captiveportal'][$cpzone]['radiuskey4'] : null;
1093

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

    
1111
		fclose($fd);
1112
		unlock($cprdsrvlck);
1113
	}
1114
}
1115

    
1116
/* read RADIUS servers into array */
1117
function captiveportal_get_radius_servers() {
1118
	global $g, $cpzone;
1119

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

    
1147
	unlock($cprdsrvlck);
1148
	return false;
1149
}
1150

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

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

    
1173
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1174
	global $g, $config;
1175

    
1176
	$pipeno = captiveportal_get_next_dn_ruleno();
1177

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

    
1186
	$radiusservers = captiveportal_get_radius_servers();
1187

    
1188
	if (is_null($radiusctx))
1189
		$radiusctx = 'first';
1190

    
1191
	$auth_list = RADIUS_AUTHENTICATION($username,
1192
		$password,
1193
		$radiusservers[$radiusctx],
1194
		$clientip,
1195
		$clientmac,
1196
		$pipeno);
1197

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

    
1209
	return $auth_list;
1210
}
1211

    
1212
function captiveportal_opendb() {
1213
	global $g, $cpzone;
1214

    
1215
	if (file_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db"))
1216
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1217
	else {
1218
		$errormsg = "";
1219
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1220
		if (@sqlite_exec($DB, "CREATE TABLE captiveportal (allow_time INTEGER, ruleno INTEGER, ip TEXT, mac TEXT, username TEXT, sessionid TEXT, bpassword TEXT, session_timeout INTEGER, idle_timeout INTEGER, session_terminate_time INTEGER) ", $errormsg)) {
1221
			@sqlite_exec($DB, "CREATE UNIQUE INDEX idx_active ON captiveportal (sessionid, username)");
1222
			@sqlite_exec($DB, "CREATE INDEX user ON captiveportal (username)");
1223
			@sqlite_exec($DB, "CREATE INDEX ip ON captiveportal (ip)");
1224
			@sqlite_exec($DB, "CREATE INDEX starttime ON captiveportal (allow_time)");
1225
			@sqlite_exec($DB, "CREATE INDEX serviceid ON captiveportal (serviceid)");
1226
		} else
1227
			captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$errormsg}");
1228
	}
1229

    
1230
	return $DB;
1231
}
1232

    
1233
/* read captive portal DB into array */
1234
function captiveportal_read_db($query = "") {
1235

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

    
1251
	return $cpdb;
1252
}
1253

    
1254
function captiveportal_remove_entries($remove) {
1255

    
1256
	if (!is_array($remove) || empty($remove))
1257
		return;
1258

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

    
1269
/* write captive portal DB */
1270
function captiveportal_write_db($queries) {
1271
	global $g;
1272

    
1273
	if (is_array($queries))
1274
		$query = implode(";", $queries);
1275
	else
1276
		$query = $queries;
1277

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

    
1293
function captiveportal_write_elements() {
1294
	global $g, $config, $cpzone;
1295
	
1296
	$cpcfg = $config['captiveportal'][$cpzone];
1297

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

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

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

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

    
1357
	return $ruleno;
1358
}
1359

    
1360
function captiveportal_free_dn_ruleno($ruleno) {
1361
        global $config, $g;
1362

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

    
1373
function captiveportal_get_dn_passthru_ruleno($value) {
1374
	global $config, $g, $cpzone;
1375

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

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

    
1391
	unlock($cpruleslck);
1392
	return NULL;
1393
}
1394

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

    
1403
	$cpcfg = $config['captiveportal'][$cpzone];
1404
	if(!isset($cpcfg['enable']))
1405
		return NULL;
1406

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

    
1436
function captiveportal_free_ipfw_ruleno($ruleno) {
1437
	global $config, $g, $cpzone;
1438

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

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

    
1453
function captiveportal_get_ipfw_passthru_ruleno($value) {
1454
	global $config, $g, $cpzone;
1455

    
1456
	$cpcfg = $config['captiveportal'][$cpzone];
1457
	if(!isset($cpcfg['enable']))
1458
		return NULL;
1459

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

    
1471
	unlock($cpruleslck);
1472
	return NULL;
1473
}
1474

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

    
1486
function getVolume($ip) {
1487
	global $cpzone;
1488

    
1489
	$volume = array();
1490
	// Initialize vars properly, since we don't want NULL vars
1491
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1492

    
1493
	captiveportal_ipfw_set_context($cpzone);
1494
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 1, $ip);
1495
	if (is_array($ipfw)) {
1496
		$volume['input_pkts'] = $ipfw['packets'];
1497
		$volume['input_bytes'] = $ipfw['bytes'];
1498
	}
1499

    
1500
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 2, $ip);
1501
	if (is_array($ipfw)) {
1502
		$volume['output_pkts'] = $ipfw['packets'];
1503
		$volume['output_bytes'] = $ipfw['bytes'];
1504
	}
1505

    
1506
	return $volume;
1507
}
1508

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

    
1523
/**
1524
 * Get the NAS-IP-Address based on the current wan address
1525
 *
1526
 * Use functions in interfaces.inc to find this out
1527
 *
1528
 */
1529

    
1530
function getNasIP()
1531
{
1532
	global $config, $cpzone;
1533

    
1534
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1535
			$nasIp = get_interface_ip();
1536
	} else {
1537
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1538
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1539
		else
1540
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1541
	}
1542
		
1543
	if(!is_ipaddr($nasIp))
1544
		$nasIp = "0.0.0.0";
1545

    
1546
	return $nasIp;
1547
}
1548

    
1549
function portal_ip_from_client_ip($cliip) {
1550
	global $config, $cpzone;
1551

    
1552
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1553
	foreach ($interfaces as $cpif) {
1554
		$ip = get_interface_ip($cpif);
1555
		$sn = get_interface_subnet($cpif);
1556
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1557
			return $ip;
1558
	}
1559

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

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

    
1578
	return false;
1579
}
1580

    
1581
/* functions move from index.php */
1582

    
1583
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1584
	global $g, $config, $cpzone;
1585

    
1586
	/* Get captive portal layout */
1587
	if ($type == "redir") {
1588
		header("Location: {$redirurl}");
1589
		return;
1590
	} else if ($type == "login")
1591
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1592
	else
1593
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1594

    
1595
	$cpcfg = $config['captiveportal'][$cpzone];
1596

    
1597
	/* substitute the PORTAL_REDIRURL variable */
1598
	if ($config['captiveportal'][$cpzone]['preauthurl']) {
1599
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$config['captiveportal'][$cpzone]['preauthurl']}", $htmltext);
1600
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$config['captiveportal'][$cpzone]['preauthurl']}", $htmltext);
1601
	}
1602

    
1603
	/* substitute other variables */
1604
	if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
1605
		$httpsport = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
1606
		$htmltext = str_replace("\$PORTAL_ACTION\$", "https://{$config['captiveportal'][$cpzone]['httpsname']}:{$httpsport}/", $htmltext);
1607
		$htmltext = str_replace("#PORTAL_ACTION#", "https://{$config['captiveportal'][$cpzone]['httpsname']}:{$httpsport}/", $htmltext);
1608
	} else {
1609
		$httpport = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : $cpcfg['zoneid'];
1610
		$ifip = portal_ip_from_client_ip($clientip);
1611
		if (!$ifip) {
1612
			$ourhostname = $config['system']['hostname'] . ":{$httpport}";
1613
		} else {
1614
			if (is_ipaddrv6($ifip))
1615
				$ourhostname = "[{$ifip}]:{$httpport}";
1616
			else
1617
				$ourhostname = "{$ifip}:{$httpport}";
1618
		}
1619
		$htmltext = str_replace("\$PORTAL_ACTION\$", "http://{$ourhostname}/", $htmltext);
1620
		$htmltext = str_replace("#PORTAL_ACTION#", "http://{$ourhostname}/", $htmltext);
1621
	}
1622

    
1623
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1624
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1625
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1626
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1627
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1628

    
1629
	// Special handling case for captive portal master page so that it can be ran 
1630
	// through the PHP interpreter using the include method above.  We convert the
1631
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1632
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1633
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1634
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1635
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1636
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1637
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1638
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1639

    
1640
    echo $htmltext;
1641
}
1642

    
1643
function portal_mac_radius($clientmac,$clientip) {
1644
    global $config, $cpzone;
1645

    
1646
    $radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1647

    
1648
    /* authentication against the radius server */
1649
    $username = mac_format($clientmac);
1650
    $auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1651
    if ($auth_list['auth_val'] == 2)
1652
        return TRUE;
1653
    if (!empty($auth_list['url_redirection']))
1654
	portal_reply_page($auth_list['url_redirection'], "redir");
1655

    
1656
    return FALSE;
1657
}
1658

    
1659
function captiveportal_reapply_attributes($cpentry, $attributes) {
1660
	global $config, $cpzone, $g;
1661
                         
1662
	$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1663
	$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1664
        $bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1665
        $bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1666
        $bw_up_pipeno = $cpentry[1];
1667
        $bw_down_pipeno = $cpentry[1]+1;
1668

    
1669
	pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1670
	pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1671
	//captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_BANDWIDTH_REAPPLY", "{$bw_up}/{$bw_down}");
1672

    
1673
        unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1674
}
1675

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

    
1679
	// Ensure we create an array if we are missing attributes
1680
	if (!is_array($attributes))
1681
		$attributes = array();
1682

    
1683
	unset($sessionid);
1684

    
1685
	/* Do not allow concurrent login execution. */
1686
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1687

    
1688
	if ($attributes['voucher'])
1689
		$remaining_time = $attributes['session_timeout'];
1690

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

    
1737
	/* read in client database */
1738
	$query = "WHERE ip = '{$clientip}'";
1739
	$tmpusername = strtolower($username);
1740
	if (isset($config['captiveportal']['noconcurrentlogins']))
1741
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
1742
	$cpdb = captiveportal_read_db($query);
1743

    
1744
	/* Snapshot the timestamp */
1745
	$allow_time = time();
1746
	$radiusservers = captiveportal_get_radius_servers();
1747
	$unsetindexes = array();
1748
	if (is_null($radiusctx))
1749
		$radiusctx = 'first';
1750

    
1751
	foreach ($cpdb as $cpentry) {
1752
		if (empty($cpentry[10]))
1753
			$cpentry[10] = 'first';
1754
		/* on the same ip */
1755
		if ($cpentry[2] == $clientip) {
1756
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac)   
1757
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1758
			else
1759
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
1760
			$sessionid = $cpentry[5];
1761
			break;
1762
		}
1763
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1764
			// user logged in with an active voucher. Check for how long and calculate 
1765
			// how much time we can give him (voucher credit - used time)
1766
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1767
			if ($remaining_time < 0)    // just in case. 
1768
				$remaining_time = 0;
1769

    
1770
			/* This user was already logged in so we disconnect the old one */
1771
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1772
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1773
			$unsetindexes[] = $cpentry[5];
1774
			break;
1775
		}
1776
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1777
			/* on the same username */
1778
			if (strcasecmp($cpentry[4], $username) == 0) {
1779
				/* This user was already logged in so we disconnect the old one */
1780
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1781
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1782
				$unsetindexes[] = $cpentry[5];
1783
				break;
1784
			}
1785
		}
1786
	}
1787

    
1788
	if (!empty($unsetindexes))
1789
		captiveportal_remove_entries($unsetindexes);
1790

    
1791
	if ($attributes['voucher'] && $remaining_time <= 0)
1792
		return 0;       // voucher already used and no time left
1793

    
1794
	if (!isset($sessionid)) {
1795
		/* generate unique session ID */
1796
		$tod = gettimeofday();
1797
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1798

    
1799
		if ($passthrumac) {
1800
			$mac = array();
1801
			$mac['mac'] = $clientmac;
1802
			$mac['ip'] = $clientip; /* Used only for logging */
1803
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
1804
				$mac['username'] = $username;
1805
				if ($attributes['voucher'])
1806
					$mac['logintype'] = "voucher";
1807
			}
1808
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1809
			if (!empty($bw_up))
1810
				$mac['bw_up'] = $bw_up;
1811
			if (!empty($bw_down))
1812
				$mac['bw_down'] = $bw_down;
1813
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
1814
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
1815
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1816
			unlock($cpdblck);
1817
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1818
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1819
			captiveportal_ipfw_set_context($cpzone);
1820
			mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1821
			$writecfg = true;
1822
		} else {
1823
			/* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
1824
			if (is_null($pipeno))
1825
				$pipeno = captiveportal_get_next_dn_ruleno();
1826

    
1827
			/* if the pool is empty, return appropriate message and exit */
1828
			if (is_null($pipeno)) {
1829
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1830
				log_error("WARNING!  Captive portal has reached maximum login capacity");
1831
				unlock($cpdblck);
1832
				return;
1833
			}
1834

    
1835
			$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1836
			$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1837
			$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1838
			$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1839

    
1840
			$bw_up_pipeno = $pipeno;
1841
			$bw_down_pipeno = $pipeno + 1;
1842
			//$bw_up /= 1000; // Scale to Kbit/s
1843
			pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1844
			pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1845

    
1846
			captiveportal_ipfw_set_context($cpzone);
1847
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1848
				pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, 32, $clientmac, $bw_up_pipeno);
1849
			else
1850
				pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, 32, NULL, $bw_up_pipeno);
1851

    
1852
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1853
				pfSense_ipfw_Tableaction("", IP_FW_TABLE_ADD, 2, $clientip, 32, $clientmac, $bw_down_pipeno);
1854
			else
1855
				pfSense_ipfw_Tableaction("", IP_FW_TABLE_ADD, 2, $clientip, 32, NULL, $bw_down_pipeno);
1856

    
1857
			if ($attributes['voucher'])
1858
				$attributes['session_timeout'] = $remaining_time;
1859

    
1860
			/* encode password in Base64 just in case it contains commas */
1861
			$bpassword = base64_encode($password);
1862
			$cpdb[] = array($allow_time, $pipeno, $clientip, $clientmac, $username, $sessionid, $bpassword,
1863
				$attributes['session_timeout'], $attributes['idle_timeout'], $attributes['session_terminate_time'], $radiusctx);
1864
			$insertquery = "INSERT INTO captiveportal (allow_time, ruleno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time) ";
1865
			$insertquery .= " VALUES ({$allow_time}, {$ruleno}, '{$clientip}', '{$clientmac}', '{$username}', '{$sessionid}', '{$bpassword}',";
1866
			$insertquery .= "{$attributes['session_timeout']}, {$attributes['idle_timeout']}, {$attributes['session_terminate_time']})";
1867

    
1868
			/* store information to database */
1869
			captiveportal_write_db($insertquery);
1870
			unlock($cpdblck);
1871

    
1872
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
1873
				$acct_val = RADIUS_ACCOUNTING_START($pipeno,
1874
                                		$username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
1875
				if ($acct_val == 1)
1876
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1877
			}
1878
		}
1879
	} else
1880
		unlock($cpdblck);
1881

    
1882
	if ($writecfg == true)
1883
		write_config();
1884

    
1885
	/* redirect user to desired destination */
1886
	if (!empty($attributes['url_redirection']))
1887
		$my_redirurl = $attributes['url_redirection'];
1888
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
1889
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
1890
	else
1891
		$my_redirurl = $redirurl;
1892

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

    
1895
		if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
1896
			$httpsport =
1897
				$config['captiveportal'][$cpzone]['listenporthttps'] ?
1898
				$config['captiveportal'][$cpzone]['listenporthttps'] :
1899
				($config['captiveportal'][$cpzone]['zoneid'] + 1);
1900
			$logouturl = "https://{$config['captiveportal'][$cpzone]['httpsname']}:{$httpsport}/";
1901
		} else {
1902
			$ifip = portal_ip_from_client_ip($clientip);
1903
			$httpport =
1904
				$config['captiveportal'][$cpzone]['listenporthttp'] ?
1905
				$config['captiveportal'][$cpzone]['listenporthttp'] :
1906
				$config['captiveportal'][$cpzone]['zoneid'];
1907
			if (!$ifip)
1908
				$ourhostname = $config['system']['hostname'] . ":{$httpport}";
1909
			else
1910
				$ourhostname = "{$ifip}:{$httpport}";
1911
			$logouturl = "http://{$ourhostname}/";
1912
		}
1913

    
1914
		if (isset($attributes['reply_message']))
1915
			$message = $attributes['reply_message'];
1916
		else
1917
			$message = 0;
1918

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

    
1921
	} else {
1922
		header("Location: " . $my_redirurl);
1923
	}
1924

    
1925
	return $sessionid;
1926
}
1927

    
1928

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

    
1938
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
1939
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
1940
	else
1941
		return false;
1942

    
1943
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
1944
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
1945
	else
1946
		return false;
1947

    
1948
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1949
		return false;
1950

    
1951
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
1952

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

    
1959
	$currenttime = time();
1960
	$found = false;
1961
	foreach ($usedmacs as $key => $usedmac) {
1962
		$usedmac = explode(",", $usedmac);
1963

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

    
1974
					return false;
1975
				} else {
1976
					$usedmac[2] -= 1;
1977
					$usedmacs[$key] = implode(",", $usedmac);
1978
				}
1979

    
1980
				$found = true;
1981
			} else
1982
				unset($usedmacs[$key]);
1983

    
1984
			break;
1985
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
1986
				unset($usedmacs[$key]);
1987
	}
1988

    
1989
	if (!$found) {
1990
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
1991
		$usedmacs[] = implode(",", $usedmac);
1992
	}
1993

    
1994
	captiveportal_write_usedmacs_db($usedmacs);
1995
	return true;
1996
}
1997

    
1998
function captiveportal_read_usedmacs_db() {
1999
	global $g, $cpzone;
2000

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

    
2009
	unlock($cpumaclck);
2010
	return $usedmacs;
2011
}
2012

    
2013
function captiveportal_write_usedmacs_db($usedmacs) {
2014
	global $g, $cpzone;
2015

    
2016
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2017
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2018
	unlock($cpumaclck);
2019
}
2020

    
2021
?>
(8-8/68)