Project

General

Profile

Download (76 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	captiveportal.inc
4
	part of pfSense (https://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/route
40
	pfSense_BUILDER_BINARIES:	/usr/local/sbin/lighttpd	/usr/local/bin/minicron /sbin/pfctl
41
	pfSense_BUILDER_BINARIES:	/bin/hostname	/bin/cp 
42
	pfSense_MODULE: captiveportal
43
*/
44

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

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

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

    
103
EOD;
104

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

    
112
EOD;
113
	}
114

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

    
142
EOD;
143

    
144
	return $htmltext;
145
}
146

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

    
150
	mute_kernel_msgs();
151
	if (!is_module_loaded("ipfw.ko")) {
152
		mwexec("/sbin/kldload ipfw");
153
		/* make sure ipfw is not on pfil hooks */
154
		set_sysctl(array(
155
			"net.inet.ip.pfil.inbound" => "pf", "net.inet6.ip6.pfil.inbound" => "pf",
156
			"net.inet.ip.pfil.outbound" => "pf", "net.inet6.ip6.pfil.outbound" => "pf")
157
		);
158
	}
159
	/* Activate layer2 filtering */
160
	set_sysctl(array("net.link.ether.ipfw" => "1", "net.inet.ip.fw.one_pass" => "1"));
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
		set_sysctl(array("net.inet.ip.dummynet.io_fast" => "1", "net.inet.ip.dummynet.hash_size" => "256"));
166
	}
167
	unmute_kernel_msgs();
168
}
169

    
170
function captiveportal_configure() {
171
	global $config, $cpzone, $cpzoneid;
172

    
173
	if (is_array($config['captiveportal'])) {
174
		foreach ($config['captiveportal'] as $cpkey => $cp) {
175
			$cpzone = $cpkey;
176
			$cpzoneid = $cp['zoneid'];
177
			captiveportal_configure_zone($cp);
178
		}
179
	}
180
}
181

    
182
function captiveportal_configure_zone($cpcfg) {
183
	global $config, $g, $cpzone, $cpzoneid;
184

    
185
	$captiveportallck = lock("captiveportal{$cpzone}", LOCK_EX);
186
	
187
	if (isset($cpcfg['enable'])) {
188

    
189
		if (platform_booting()) {
190
			echo "Starting captive portal({$cpcfg['zone']})... ";
191

    
192
			/* remove old information */
193
			unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
194
		} else
195
			captiveportal_syslog("Reconfiguring captive portal({$cpcfg['zone']}).");
196

    
197
		if (is_array($cpcfg['passthrumac'])) {
198
			$nentries = count($cpcfg['passthrumac']);
199
			if ($nentries > 2000) {
200
				if (!set_time_limit(0))
201
					log_error("Execution time limit may be reached while reconfiguring zone = {$cpzone} due to many passthrough entries!");
202
			}
203
		}
204
					
205
		/* init ipfw rules */
206
		captiveportal_init_rules(true);
207

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

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

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

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

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

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

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

    
274
		/* write logout page */
275
		if (is_array($cpcfg['page']) && $cpcfg['page']['logouttext'])
276
			$logouttext = base64_decode($cpcfg['page']['logouttext']);
277
		else {
278
			/* example page */
279
			$logouttext = <<<EOD
280
<html>
281
<head><title>Redirecting...</title></head>
282
<body>
283
<span style="font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">
284
<b>Redirecting to <a href="<?=\$my_redirurl;?>"><?=\$my_redirurl;?></a>...</b>
285
</span>
286
<script type="text/javascript">
287
//<![CDATA[
288
LogoutWin = window.open('', 'Logout', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=256,height=64');
289
if (LogoutWin) {
290
	LogoutWin.document.write('<html>');
291
	LogoutWin.document.write('<head><title>Logout</title></head>') ;
292
	LogoutWin.document.write('<body bgcolor="#435370">');
293
	LogoutWin.document.write('<div align="center" style="color: #ffffff; font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">') ;
294
	LogoutWin.document.write('<b>Click the button below to disconnect</b><p />');
295
	LogoutWin.document.write('<form method="POST" action="<?=\$logouturl;?>">');
296
	LogoutWin.document.write('<input name="logout_id" type="hidden" value="<?=\$sessionid;?>" />');
297
	LogoutWin.document.write('<input name="zone" type="hidden" value="<?=\$cpzone;?>" />');
298
	LogoutWin.document.write('<input name="logout" type="submit" value="Logout" />');
299
	LogoutWin.document.write('</form>');
300
	LogoutWin.document.write('</div></body>');
301
	LogoutWin.document.write('</html>');
302
	LogoutWin.document.close();
303
}
304

    
305
document.location.href="<?=\$my_redirurl;?>";
306
//]]>
307
</script>
308
</body>
309
</html>
310

    
311
EOD;
312
		}
313

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

    
321
		/* write elements */
322
		captiveportal_write_elements();
323

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

    
328
		/* start up the webserving daemon */
329
		captiveportal_init_webgui_zone($cpcfg);
330

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

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

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

    
343
		if (platform_booting()) {
344
			/* send Accounting-On to server */
345
			captiveportal_send_server_accounting();
346
			echo "done\n";
347
		}
348

    
349
	} else {
350
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
351
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
352
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
353
		@unlink("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
354
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
355
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
356

    
357
		captiveportal_radius_stop_all();
358

    
359
		/* send Accounting-Off to server */
360
		if (!platform_booting()) {
361
			captiveportal_send_server_accounting(true);
362
		}
363

    
364
		/* remove old information */
365
		unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
366
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
367
		unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
368
		/* Release allocated pipes for this zone */
369
		captiveportal_free_dnrules();
370

    
371
		mwexec("/sbin/ipfw zone {$cpzoneid} destroy", true);
372

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

    
389
		}
390
	}
391

    
392
	/* XXX: Same as in rc.php_init_setup */
393
	set_time_limit(900);
394
	unlock($captiveportallck);
395
	
396
	return 0;
397
}
398

    
399
function captiveportal_init_webgui() {
400
	global $config, $cpzone;
401

    
402
	if (is_array($config['captiveportal'])) {
403
		foreach ($config['captiveportal'] as $cpkey => $cp) {
404
			$cpzone = $cpkey;
405
			captiveportal_init_webgui_zone($cp);
406
		}
407
	}
408
}
409

    
410
function captiveportal_init_webgui_zonename($zone) {
411
	global $config, $cpzone;
412
	
413
	if (isset($config['captiveportal'][$zone])) {
414
		$cpzone = $zone;
415
		captiveportal_init_webgui_zone($config['captiveportal'][$zone]);
416
	}
417
}
418

    
419
function captiveportal_init_webgui_zone($cpcfg) {
420
	global $g, $config, $cpzone;
421

    
422
	if (!isset($cpcfg['enable']))
423
		return;
424

    
425
	if (isset($cpcfg['httpslogin'])) {
426
		$cert = lookup_cert($cpcfg['certref']);
427
		$crt = base64_decode($cert['crt']);
428
		$key = base64_decode($cert['prv']);
429
		$ca = ca_chain($cert);
430

    
431
		/* generate lighttpd configuration */
432
		if (!empty($cpcfg['listenporthttps']))
433
			$listenporthttps = $cpcfg['listenporthttps'];
434
		else
435
			$listenporthttps = 8001 + $cpcfg['zoneid'];
436
		system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf",
437
			$crt, $key, $ca, "lighty-{$cpzone}-CaptivePortal-SSL.pid", $listenporthttps, "/usr/local/captiveportal",
438
			"cert-{$cpzone}-portal.pem", "ca-{$cpzone}-portal.pem", $cpzone);
439
	}
440

    
441
	/* generate lighttpd configuration */
442
	if (!empty($cpcfg['listenporthttp']))
443
		$listenporthttp = $cpcfg['listenporthttp'];
444
	else
445
		$listenporthttp = 8000 + $cpcfg['zoneid'];
446
	system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal.conf",
447
		"", "", "", "lighty-{$cpzone}-CaptivePortal.pid", $listenporthttp, "/usr/local/captiveportal",
448
		"", "", $cpzone);
449

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

    
454
	/* fire up https instance */
455
	if (isset($cpcfg['httpslogin'])) {
456
		@unlink("{$g['varrun']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
457
		$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf");
458
	}
459
}
460

    
461
function captiveportal_init_rules_byinterface($interface) {
462
	global $cpzone, $cpzoneid, $config;
463

    
464
	if (!is_array($config['captiveportal']))
465
		return;
466

    
467
	foreach ($config['captiveportal'] as $cpkey => $cp) {
468
		$cpzone = $cpkey;
469
		$cpzoneid = $cp['zoneid'];
470
		$cpinterfaces = explode(",", $cp['interface']);
471
		if (in_array($interface, $cpinterfaces)) {
472
			captiveportal_init_rules();
473
			break;
474
		}
475
	}
476
}
477

    
478
/* reinit will disconnect all users, be careful! */
479
function captiveportal_init_rules($reinit = false) {
480
	global $config, $g, $cpzone, $cpzoneid;
481

    
482
	if (!isset($config['captiveportal'][$cpzone]['enable']))
483
		return;
484

    
485
	captiveportal_load_modules();
486
	mwexec("/sbin/ipfw zone {$cpzoneid} create", true);
487

    
488
	/* Cleanup so nothing is leaked */
489
	captiveportal_free_dnrules();
490
	unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
491

    
492
	$cpips = array();
493
	$ifaces = get_configured_interface_list();
494
	$cpinterfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
495
	$firsttime = 0;
496
	foreach ($cpinterfaces as $cpifgrp) {
497
		if (!isset($ifaces[$cpifgrp]))
498
			continue;
499
		$tmpif = get_real_interface($cpifgrp);
500
		if (!empty($tmpif)) {
501
			$cpipm = get_interface_ip($cpifgrp);
502
			if (is_ipaddr($cpipm)) {
503
				$cpips[] = $cpipm;
504
				if (!is_array($config['virtualip']) || !is_array($config['virtualip']['vip'])) {
505
					continue;
506
				}
507
				foreach ($config['virtualip']['vip'] as $vip) {
508
					if (($vip['interface'] == $cpifgrp) && (($vip['mode'] == "carp") || ($vip['mode'] == "ipalias"))) {
509
						$cpips[] = $vip['subnet'];
510
					}
511
				}
512
			}
513
			mwexec("/sbin/ipfw zone {$cpzoneid} madd {$tmpif}", true);
514
		}
515
	}
516
	if (count($cpips) > 0) {
517
		$cpactive = true;
518
	} else
519
		return false;
520

    
521
	if ($reinit == false)
522
		$captiveportallck = lock("captiveportal{$cpzone}");
523

    
524
	$cprules =	"add 65291 allow pfsync from any to any\n";
525
	$cprules .= "add 65292 allow carp from any to any\n";
526

    
527
	$cprules .= <<<EOD
528
# layer 2: pass ARP
529
add 65301 pass layer2 mac-type arp,rarp
530
# pfsense requires for WPA
531
add 65302 pass layer2 mac-type 0x888e,0x88c7
532
# PPP Over Ethernet Session Stage/Discovery Stage
533
add 65303 pass layer2 mac-type 0x8863,0x8864
534

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

    
538
EOD;
539

    
540
	$rulenum = 65310;
541
	$ipcount = 0;
542
	$ips = "";
543
	foreach ($cpips as $cpip) {
544
		if($ipcount == 0) {
545
			$ips = "{$cpip} ";
546
		} else {
547
			$ips .= "or {$cpip} ";
548
		}
549
		$ipcount++;
550
	}
551
	$ips = "{ 255.255.255.255 or {$ips} }";
552
	$cprules .= "add {$rulenum} pass ip from any to {$ips} in\n";
553
	$rulenum++;
554
	$cprules .= "add {$rulenum} pass ip from {$ips} to any out\n";
555
	$rulenum++;
556
	$cprules .= "add {$rulenum} pass icmp from {$ips} to any out icmptype 0\n";
557
	$rulenum++;
558
	$cprules .= "add {$rulenum} pass icmp from any to {$ips} in icmptype 8 \n";
559
	$rulenum++;
560
	/* Allowed ips */
561
	$cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any in\n";
562
	$rulenum++;
563
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) in\n";
564
	$rulenum++;
565
	$cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any out\n";
566
	$rulenum++;
567
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) out\n";
568
	$rulenum++;
569

    
570
	/* Authenticated users rules. */
571
	$cprules .= "add {$rulenum} pipe tablearg ip from table(1) to any in\n";
572
	$rulenum++;
573
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(2) out\n";
574
	$rulenum++;
575

    
576
	if (!empty($config['captiveportal'][$cpzone]['listenporthttp']))
577
		$listenporthttp = $config['captiveportal'][$cpzone]['listenporthttp'];
578
	else
579
		$listenporthttp = 8000 + $cpzoneid;
580

    
581
	if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
582
		if (!empty($config['captiveportal'][$cpzone]['listenporthttps']))
583
			$listenporthttps = $config['captiveportal'][$cpzone]['listenporthttps'];
584
		else
585
			$listenporthttps = 8001 + $cpzoneid;
586
			if (!isset($config['captiveportal'][$cpzone]['nohttpsforwards'])) {
587
				$cprules .= "add 65531 fwd 127.0.0.1,{$listenporthttps} tcp from any to any dst-port 443 in\n";
588
			}
589
	}
590
	
591
	$cprules .= <<<EOD
592

    
593
# redirect non-authenticated clients to captive portal
594
add 65532 fwd 127.0.0.1,{$listenporthttp} tcp from any to any dst-port 80 in 
595
# let the responses from the captive portal web server back out
596
add 65533 pass tcp from any to any out
597
# block everything else
598
add 65534 deny all from any to any
599

    
600
EOD;
601

    
602
	/* generate passthru mac database */
603
	$cprules .= captiveportal_passthrumac_configure(true);
604
	$cprules .= "\n";
605

    
606
	/* allowed ipfw rules to make allowed ip work */
607
	$cprules .= captiveportal_allowedip_configure();
608

    
609
	/* allowed ipfw rules to make allowed hostnames work */
610
	$cprules .= captiveportal_allowedhostname_configure();
611
	
612
	/* load rules */
613
	$cprules = "flush\n{$cprules}";
614
	file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
615
	mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
616
	//@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
617
	unset($cprules, $tmprules);
618

    
619
	if ($reinit == false)
620
		unlock($captiveportallck);
621
}
622

    
623
/* 
624
 * Remove clients that have been around for longer than the specified amount of time
625
 * db file structure:
626
 * timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time,interim_interval
627
 * (password is in Base64 and only saved when reauthentication is enabled)
628
 */
629
function captiveportal_prune_old() {
630
	global $g, $config, $cpzone, $cpzoneid;
631

    
632
	if (empty($cpzone))
633
		return;
634

    
635
	$cpcfg = $config['captiveportal'][$cpzone];
636
	$vcpcfg = $config['voucher'][$cpzone];
637

    
638
	/* check for expired entries */
639
	$idletimeout = 0;
640
	$timeout = 0;
641
	if (!empty($cpcfg['timeout']) && is_numeric($cpcfg['timeout']))
642
		$timeout = $cpcfg['timeout'] * 60;
643

    
644
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout']))
645
		$idletimeout = $cpcfg['idletimeout'] * 60;
646

    
647
	/* Is there any job to do? */
648
	if (!$timeout && !$idletimeout && !isset($cpcfg['reauthenticate']) &&
649
	    !isset($cpcfg['radiussession_timeout']) && !isset($vcpcfg['enable']))
650
		return;
651

    
652
	$radiussrvs = captiveportal_get_radius_servers();
653

    
654
	/* Read database */
655
	/* NOTE: while this can be simplified in non radius case keep as is for now */
656
	$cpdb = captiveportal_read_db();
657

    
658
	$unsetindexes = array();
659
	$voucher_needs_sync = false;
660
	/* 
661
	 * Snapshot the time here to use for calculation to speed up the process.
662
	 * If something is missed next run will catch it!
663
	 */
664
	$pruning_time = time();
665
	$stop_time = $pruning_time;
666
	foreach ($cpdb as $cpentry) {
667

    
668
		$timedout = false;
669
		$term_cause = 1;
670
		if (empty($cpentry[11]))
671
			$cpentry[11] = 'first';
672
		$radiusservers = $radiussrvs[$cpentry[11]];
673

    
674
		/* hard timeout? */
675
		if ($timeout) {
676
			if (($pruning_time - $cpentry[0]) >= $timeout) {
677
				$timedout = true;
678
				$term_cause = 5; // Session-Timeout
679
			}
680
		}
681

    
682
		/* Session-Terminate-Time */
683
		if (!$timedout && !empty($cpentry[9])) {
684
			if ($pruning_time >= $cpentry[9]) {
685
				$timedout = true;
686
				$term_cause = 5; // Session-Timeout
687
			}
688
		}
689

    
690
		/* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
691
		$uidletimeout = (is_numeric($cpentry[8])) ? $cpentry[8] : $idletimeout;
692
		/* if an idle timeout is specified, get last activity timestamp from ipfw */
693
		if (!$timedout && $uidletimeout > 0) {
694
			$lastact = captiveportal_get_last_activity($cpentry[2], $cpentry[3]);
695
			/*	If the user has logged on but not sent any traffic they will never be logged out.
696
			 *	We "fix" this by setting lastact to the login timestamp. 
697
			 */
698
			$lastact = $lastact ? $lastact : $cpentry[0];
699
			if ($lastact && (($pruning_time - $lastact) >= $uidletimeout)) {
700
				$timedout = true;
701
				$term_cause = 4; // Idle-Timeout
702
				$stop_time = $lastact; // Entry added to comply with WISPr
703
			}
704
		}
705

    
706
		/* if vouchers are configured, activate session timeouts */
707
		if (!$timedout && isset($vcpcfg['enable']) && !empty($cpentry[7])) {
708
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
709
				$timedout = true;
710
				$term_cause = 5; // Session-Timeout
711
				$voucher_needs_sync = true;
712
			}
713
		}
714

    
715
		/* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
716
		if (!$timedout && isset($cpcfg['radiussession_timeout']) && !empty($cpentry[7])) {
717
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
718
				$timedout = true;
719
				$term_cause = 5; // Session-Timeout
720
			}
721
		}
722

    
723
		if ($timedout) {
724
			captiveportal_disconnect($cpentry, $radiusservers,$term_cause,$stop_time);
725
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
726
			$unsetindexes[] = $cpentry[5];
727
		}
728

    
729
		/* do periodic RADIUS reauthentication? */
730
		if (!$timedout && !empty($radiusservers)) {
731
			if (isset($cpcfg['radacct_enable'])) {
732
				if ($cpcfg['reauthenticateacct'] == "stopstart") {
733
					/* stop and restart accounting */
734
					RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
735
						$cpentry[4], // username
736
						$cpentry[5], // sessionid
737
						$cpentry[0], // start time
738
						$radiusservers,
739
						$cpentry[2], // clientip
740
						$cpentry[3], // clientmac
741
						10); // NAS Request
742
					$clientsn = (is_ipaddrv6($cpentry[2])) ? 128 : 32;
743
					$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XZEROENTRY, 1, $cpentry[2], $clientsn, $cpentry[3]);
744
					$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XZEROENTRY, 2, $cpentry[2], $clientsn, $cpentry[3]);
745
					RADIUS_ACCOUNTING_START($cpentry[1], // ruleno
746
						$cpentry[4], // username
747
						$cpentry[5], // sessionid
748
						$radiusservers,
749
						$cpentry[2], // clientip
750
						$cpentry[3]); // clientmac
751
				} else if ($cpcfg['reauthenticateacct'] == "interimupdate") {
752
					$session_time = $pruning_time - $cpentry[0];
753
					if (!empty($cpentry[10]) && $cpentry[10] > 60)
754
						$interval = $cpentry[10];
755
					else
756
						$interval = 0;
757
					$past_interval_min = ($session_time > $interval);
758
					if ($interval != 0)
759
						$within_interval = ($session_time % $interval >= 0 && $session_time % $interval <= 59);
760
					if ($interval === 0 || ($interval > 0 && $past_interval_min && $within_interval)) {
761
						RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
762
							$cpentry[4], // username
763
							$cpentry[5], // sessionid
764
							$cpentry[0], // start time
765
							$radiusservers,
766
							$cpentry[2], // clientip
767
							$cpentry[3], // clientmac
768
							10, // NAS Request
769
							true); // Interim Updates
770
					}
771
				}
772
			}
773

    
774
			/* check this user against RADIUS again */
775
			if (isset($cpcfg['reauthenticate'])) {
776
				$auth_list = RADIUS_AUTHENTICATION($cpentry[4], // username
777
					base64_decode($cpentry[6]), // password
778
					$radiusservers,
779
					$cpentry[2], // clientip
780
					$cpentry[3], // clientmac
781
					$cpentry[1]); // ruleno
782
				if ($auth_list['auth_val'] == 3) {
783
					captiveportal_disconnect($cpentry, $radiusservers, 17);
784
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
785
					$unsetindexes[] = $cpentry[5];
786
				} else if ($auth_list['auth_val'] == 2)
787
					captiveportal_reapply_attributes($cpentry, $auth_list);
788
			}
789
		}
790
	}
791
	unset($cpdb);
792

    
793
	captiveportal_prune_old_automac();
794

    
795
	if ($voucher_needs_sync == true)
796
		/* Trigger a sync of the vouchers on config */
797
		send_event("service sync vouchers");
798

    
799
	/* write database */
800
	if (!empty($unsetindexes))
801
		captiveportal_remove_entries($unsetindexes);
802
}
803

    
804
function captiveportal_prune_old_automac() {
805
	global $g, $config, $cpzone, $cpzoneid;
806

    
807
	if (is_array($config['captiveportal'][$cpzone]['passthrumac']) && isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
808
		$tmpvoucherdb = array();
809
		$macrules = "";
810
		$writecfg = false;
811
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $eid => $emac) {
812
			if ($emac['logintype'] == "voucher") {
813
				if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) {
814
					if (isset($tmpvoucherdb[$emac['username']])) {
815
						$temac = $config['captiveportal'][$cpzone]['passthrumac'][$tmpvoucherdb[$emac['username']]];
816
						$ruleno = captiveportal_get_ipfw_passthru_ruleno($temac['mac']);
817
						$pipeno = captiveportal_get_dn_passthru_ruleno($temac['mac']);
818
						if ($ruleno) {
819
							captiveportal_free_ipfw_ruleno($ruleno);
820
							$macrules .= "delete {$ruleno}";
821
							++$ruleno;
822
							$macrules .= "delete {$ruleno}";
823
						}
824
						if ($pipeno) {
825
							captiveportal_free_dn_ruleno($pipeno);
826
							$macrules .= "pipe delete {$pipeno}\n";
827
							++$pipeno;
828
							$macrules .= "pipe delete {$pipeno}\n";
829
						}
830
						$writecfg = true;
831
						captiveportal_logportalauth($temac['username'], $temac['mac'], $temac['ip'], "DUPLICATE {$temac['username']} LOGIN - TERMINATING OLD SESSION");
832
						unset($config['captiveportal'][$cpzone]['passthrumac'][$tmpvoucherdb[$emac['username']]]);
833
					}
834
					$tmpvoucherdb[$emac['username']] = $eid;
835
				}
836
				if (voucher_auth($emac['username']) <= 0) {
837
					$ruleno = captiveportal_get_ipfw_passthru_ruleno($emac['mac']);
838
					$pipeno = captiveportal_get_dn_passthru_ruleno($emac['mac']);
839
					if ($ruleno) {
840
						captiveportal_free_ipfw_ruleno($ruleno);
841
						$macrules .= "delete {$ruleno}";
842
						++$ruleno;
843
						$macrules .= "delete {$ruleno}";
844
					}
845
					if ($pipeno) {
846
						captiveportal_free_dn_ruleno($pipeno);
847
						$macrules .= "pipe delete {$pipeno}\n";
848
						++$pipeno;
849
						$macrules .= "pipe delete {$pipeno}\n";
850
					}
851
					$writecfg = true;
852
					captiveportal_logportalauth($emac['username'], $emac['mac'], $emac['ip'], "EXPIRED {$emac['username']} LOGIN - TERMINATING SESSION");
853
					unset($config['captiveportal'][$cpzone]['passthrumac'][$eid]);
854
				}
855
			}
856
		}
857
		unset($tmpvoucherdb);
858
		if (!empty($macrules)) {
859
			@file_put_contents("{$g['tmp_path']}/macentry.prunerules.tmp", $macrules);
860
			unset($macrules);
861
			mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry.prunerules.tmp");
862
		}
863
		if ($writecfg === true)
864
			write_config("Prune session for auto-added macs");
865
	}
866
}
867

    
868
/* remove a single client according to the DB entry */
869
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
870
	global $g, $config, $cpzone, $cpzoneid;
871

    
872
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
873

    
874
	/* this client needs to be deleted - remove ipfw rules */
875
	if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers)) {
876
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
877
			$dbent[4], // username
878
			$dbent[5], // sessionid
879
			$dbent[0], // start time
880
			$radiusservers,
881
			$dbent[2], // clientip
882
			$dbent[3], // clientmac
883
			$term_cause, // Acct-Terminate-Cause
884
			false,
885
			$stop_time);
886
	}
887
	
888
	if (is_ipaddr($dbent[2])) {
889
		/* Delete client's ip entry from tables 1 and 2. */
890
		$clientsn = (is_ipaddrv6($dbent[2])) ? 128 : 32;
891
		pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XDEL, 1, $dbent[2], $clientsn, $dbent[3]);
892
		pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XDEL, 2, $dbent[2], $clientsn, $dbent[3]);
893
		/* XXX: Redundant?! Ensure all pf(4) states are killed. */
894
		$_gb = @pfSense_kill_states($dbent[2]);
895
		$_gb = @pfSense_kill_srcstates($dbent[2]);
896
	}
897

    
898
	/* 
899
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
900
	* We could get an error if the pipe doesn't exist but everything should still be fine
901
	*/
902
	if (!empty($dbent[1])) {
903
		$_gb = @pfSense_pipe_action("pipe delete {$dbent[1]}");
904
		$_gb = @pfSense_pipe_action("pipe delete " . ($dbent[1]+1));
905

    
906
		/* Release the ruleno so it can be reallocated to new clients. */
907
		captiveportal_free_dn_ruleno($dbent[1]);
908
	}
909

    
910
	// XMLRPC Call over to the master Voucher node
911
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
912
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
913
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
914
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
915
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
916
		$remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time);
917
	}
918

    
919
}
920

    
921
/* remove a single client by sessionid */
922
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
923
	global $g, $config;
924

    
925
	$radiusservers = captiveportal_get_radius_servers();
926

    
927
	/* read database */
928
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
929

    
930
	/* find entry */
931
	if (!empty($result)) {
932
		captiveportal_write_db("DELETE FROM captiveportal WHERE sessionid = '{$sessionid}'");
933

    
934
		foreach ($result as $cpentry) {
935
			if (empty($cpentry[11]))
936
				$cpentry[11] = 'first';
937
			captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], $term_cause);
938
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
939
		}
940
		unset($result);
941
	}
942
}
943

    
944
/* send RADIUS acct stop for all current clients */
945
function captiveportal_radius_stop_all() {
946
	global $config, $cpzone;
947

    
948
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable']))
949
		return;
950

    
951
	$radiusservers = captiveportal_get_radius_servers();
952
	if (!empty($radiusservers)) {
953
		$cpdb = captiveportal_read_db();
954
		foreach ($cpdb as $cpentry) {
955
			if (empty($cpentry[11]))
956
				$cpentry[11] = 'first';
957
			if (!empty($radiusservers[$cpentry[11]])) {
958
				RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
959
					$cpentry[4], // username
960
					$cpentry[5], // sessionid
961
					$cpentry[0], // start time
962
					$radiusservers[$cpentry[11]],
963
					$cpentry[2], // clientip
964
					$cpentry[3], // clientmac
965
					7); // Admin Reboot
966
			}
967
		}
968
	}
969
}
970

    
971
function captiveportal_passthrumac_configure_entry($macent, $pipeinrule = false) {
972
	global $config, $g, $cpzone;
973

    
974
	$bwUp = 0;
975
	if (!empty($macent['bw_up']))
976
		$bwUp = $macent['bw_up'];
977
	else if (!empty($config['captiveportal'][$cpzone]['bwdefaultup']))
978
		$bwUp = $config['captiveportal'][$cpzone]['bwdefaultup'];
979
	$bwDown = 0;
980
	if (!empty($macent['bw_down']))
981
		$bwDown = $macent['bw_down'];
982
	else if (!empty($config['captiveportal'][$cpzone]['bwdefaultdn']))
983
		$bwDown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
984

    
985
	$ruleno = captiveportal_get_next_ipfw_ruleno();
986

    
987
	if ($macent['action'] == 'pass') {
988
		$rules = "";
989
		$pipeno = captiveportal_get_next_dn_ruleno();
990

    
991
		$pipeup = $pipeno;
992
		if ($pipeinrule == true)
993
			$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16");
994
		else
995
			$rules .= "pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16\n";
996
			
997
		$pipedown = $pipeno + 1;
998
		if ($pipeinrule == true)
999
			$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
1000
		else
1001
			$rules .= "pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16\n";
1002

    
1003
		$rules .= "add {$ruleno} pipe {$pipeup} ip from any to any MAC any {$macent['mac']}\n";
1004
		$ruleno++;
1005
		$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC {$macent['mac']} any\n";
1006
	}
1007

    
1008
	return $rules;
1009
}
1010

    
1011
function captiveportal_passthrumac_delete_entry($macent) {
1012
	$rules = "";
1013

    
1014
	if ($macent['action'] == 'pass') {
1015
		$ruleno = captiveportal_get_ipfw_passthru_ruleno($macent['mac']);
1016

    
1017
		if (!$ruleno)
1018
			return $rules;
1019

    
1020
		captiveportal_free_ipfw_ruleno($ruleno);
1021

    
1022
		$rules .= "delete {$ruleno}\n";
1023
		$rules .= "delete " . ++$ruleno . "\n";
1024

    
1025
		$pipeno = captiveportal_get_dn_passthru_ruleno($macent['mac']);
1026

    
1027
		if (!empty($pipeno)) {
1028
			captiveportal_free_dn_ruleno($pipeno);
1029
			$rules .= "pipe delete " . $pipeno . "\n";
1030
			$rules .= "pipe delete " . ++$pipeno . "\n";
1031
		}
1032
	}
1033

    
1034
	return $rules;
1035
}
1036

    
1037
function captiveportal_passthrumac_configure($lock = false) {
1038
	global $config, $g, $cpzone;
1039

    
1040
	$rules = "";
1041

    
1042
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1043
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1044
			$rules .= captiveportal_passthrumac_configure_entry($macent, true);
1045
		}
1046
	}
1047

    
1048
	return $rules;
1049
}
1050

    
1051
function captiveportal_passthrumac_findbyname($username) {
1052
	global $config, $cpzone;
1053

    
1054
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1055
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1056
			if ($macent['username'] == $username)
1057
				return $macent;
1058
		}
1059
	}
1060
	return NULL;
1061
}
1062

    
1063
/* 
1064
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1065
 */
1066
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
1067
	global $g;
1068

    
1069
	/*  Instead of copying this entire function for something
1070
	 *  easy such as hostname vs ip address add this check
1071
	 */
1072
	if ($ishostname === true) {
1073
		if (!platform_booting()) {
1074
			$ipaddress = gethostbyname($ipent['hostname']);
1075
			if (!is_ipaddr($ipaddress)) 
1076
				return;
1077
		} else
1078
			$ipaddress = "";
1079
	} else
1080
		$ipaddress = $ipent['ip'];
1081

    
1082
	$rules = "";
1083
	$cp_filterdns_conf = "";
1084
	$enBwup = 0;
1085
	if (!empty($ipent['bw_up']))
1086
		$enBwup = intval($ipent['bw_up']);
1087
	else if (!empty($config['captiveportal'][$cpzone]['bwdefaultup']))
1088
		$enBwup = $config['captiveportal'][$cpzone]['bwdefaultup'];
1089
	$enBwdown = 0;
1090
	if (!empty($ipent['bw_down']))
1091
		$enBwdown = intval($ipent['bw_down']);
1092
	else if (!empty($config['captiveportal'][$cpzone]['bwdefaultdn']))
1093
		$enBwdown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
1094

    
1095
	$pipeno = captiveportal_get_next_dn_ruleno();
1096
	$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$enBwup}Kbit/s queue 100 buckets 16");
1097
	$pipedown = $pipeno + 1;
1098
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16");
1099
	if ($ishostname === true) {
1100
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
1101
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
1102
		if (!is_ipaddr($ipaddress))
1103
			return array("", $cp_filterdns_conf);
1104
	}
1105
	$subnet = "";
1106
	if (!empty($ipent['sn']))
1107
		$subnet = "/{$ipent['sn']}";
1108
	$rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
1109
	$rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
1110

    
1111
	if ($ishostname === true)
1112
		return array($rules, $cp_filterdns_conf);
1113
	else
1114
		return $rules;
1115
}
1116

    
1117
function captiveportal_allowedhostname_configure() {
1118
	global $config, $g, $cpzone, $cpzoneid;
1119

    
1120
	$rules = "";
1121
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1122
		$rules = "\n# captiveportal_allowedhostname_configure()\n";
1123
		$cp_filterdns_conf = "";
1124
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) {
1125
			$tmprules = captiveportal_allowedip_configure_entry($hostnameent, true);
1126
			$rules .= $tmprules[0];
1127
			$cp_filterdns_conf .= $tmprules[1];
1128
		}
1129
		$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1130
		@file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1131
		unset($cp_filterdns_conf);
1132
		if (isvalidpid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid"))
1133
			sigkillbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid", "HUP");
1134
		else
1135
			mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzoneid} -d 1");
1136
	} else {
1137
		killbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1138
		@unlink("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1139
	}
1140

    
1141
	return $rules;
1142
}
1143

    
1144
function captiveportal_allowedip_configure() {
1145
	global $config, $g, $cpzone;
1146

    
1147
	$rules = "";
1148
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1149
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1150
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1151
	}
1152

    
1153
	return $rules;
1154
}
1155

    
1156
/* get last activity timestamp given client IP address */
1157
function captiveportal_get_last_activity($ip, $mac = NULL, $table = 1) {
1158
	global $cpzoneid;
1159

    
1160
	$ipfwoutput = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, $table, $ip, $mac);
1161
	/* Reading only from one of the tables is enough of approximation. */
1162
	if (is_array($ipfwoutput)) {
1163
		/* Workaround for #46652 */
1164
		if ($ipfwoutput['packets'] > 0) { 
1165
			return $ipfwoutput['timestamp'];
1166
		} else {
1167
			return 0;
1168
		}
1169
	}
1170

    
1171
	return 0;
1172
}
1173

    
1174
function captiveportal_init_radius_servers() {
1175
	global $config, $g, $cpzone;
1176

    
1177
	/* generate radius server database */
1178
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1179
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1180
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1181
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1182
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1183
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1184

    
1185
		if ($config['captiveportal'][$cpzone]['radiusport'])
1186
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1187
		else
1188
			$radiusport = 1812;
1189
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1190
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1191
		else
1192
			$radiusacctport = 1813;
1193
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1194
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1195
		else
1196
			$radiusport2 = 1812;
1197
		if ($config['captiveportal'][$cpzone]['radiusport3'])
1198
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1199
		else
1200
			$radiusport3 = 1812;
1201
		if ($config['captiveportal'][$cpzone]['radiusport4'])
1202
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1203
		else
1204
			$radiusport4 = 1812;
1205

    
1206
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1207
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1208
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1209
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1210

    
1211
		$cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX);
1212
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
1213
		if (!$fd) {
1214
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1215
			unlock($cprdsrvlck);
1216
			return 1;
1217
		}
1218
		if (isset($radiusip))
1219
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
1220
		if (isset($radiusip2))
1221
			fwrite($fd,"\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
1222
		if (isset($radiusip3))
1223
			fwrite($fd,"\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
1224
		if (isset($radiusip4))
1225
			fwrite($fd,"\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
1226
		
1227

    
1228
		fclose($fd);
1229
		unlock($cprdsrvlck);
1230
	}
1231
}
1232

    
1233
/* read RADIUS servers into array */
1234
function captiveportal_get_radius_servers() {
1235
	global $g, $cpzone;
1236

    
1237
	$cprdsrvlck = lock("captiveportalradius{$cpzone}");
1238
	if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
1239
		$radiusservers = array();
1240
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", 
1241
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1242
		if ($cpradiusdb) {
1243
			foreach($cpradiusdb as $cpradiusentry) {
1244
				$line = trim($cpradiusentry);
1245
				if ($line) {
1246
					$radsrv = array();
1247
						list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key'], $context) = explode(",",$line);
1248
				}
1249
				if (empty($context)) {
1250
					if (!is_array($radiusservers['first']))
1251
						$radiusservers['first'] = array();
1252
					$radiusservers['first'] = $radsrv;
1253
				} else {
1254
					if (!is_array($radiusservers[$context]))
1255
						$radiusservers[$context] = array();
1256
					$radiusservers[$context][] = $radsrv;
1257
				}
1258
			}
1259
		}
1260
		unlock($cprdsrvlck);
1261
		return $radiusservers;
1262
	}
1263

    
1264
	unlock($cprdsrvlck);
1265
	return false;
1266
}
1267

    
1268
/* log successful captive portal authentication to syslog */
1269
/* part of this code from php.net */
1270
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1271
	// Log it
1272
	if (!$message)
1273
		$message = "{$status}: {$user}, {$mac}, {$ip}";
1274
	else {
1275
		$message = trim($message);
1276
		$message = "{$status}: {$user}, {$mac}, {$ip}, {$message}";
1277
	}
1278
	captiveportal_syslog($message);
1279
}
1280

    
1281
/* log simple messages to syslog */
1282
function captiveportal_syslog($message) {
1283
	global $cpzone;
1284

    
1285
	$message = trim($message);
1286
	$message = "Zone: {$cpzone} - {$message}";
1287
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1288
	// Log it
1289
	syslog(LOG_INFO, $message);
1290
	closelog();
1291
}
1292

    
1293
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1294
	global $g, $config, $cpzoneid;
1295

    
1296
	$pipeno = captiveportal_get_next_dn_ruleno();
1297

    
1298
	/* If the pool is empty, return appropriate message and fail authentication */
1299
	if (empty($pipeno)) {
1300
		$auth_list = array();
1301
		$auth_list['auth_val'] = 1;
1302
		$auth_list['error'] = "System reached maximum login capacity";
1303
		return $auth_list;
1304
	}
1305

    
1306
	$radiusservers = captiveportal_get_radius_servers();
1307

    
1308
	if (is_null($radiusctx))
1309
		$radiusctx = 'first';
1310

    
1311
	$auth_list = RADIUS_AUTHENTICATION($username,
1312
		$password,
1313
		$radiusservers[$radiusctx],
1314
		$clientip,
1315
		$clientmac,
1316
		$pipeno);
1317

    
1318
	if ($auth_list['auth_val'] == 2) {
1319
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1320
		$sessionid = portal_allow($clientip,
1321
			$clientmac,
1322
			$username,
1323
			$password,
1324
			$auth_list,
1325
			$pipeno,
1326
			$radiusctx);
1327
	} else {
1328
	         captiveportal_free_dn_ruleno($pipeno);
1329
	       }
1330

    
1331
	return $auth_list;
1332
}
1333

    
1334
function captiveportal_opendb() {
1335
	global $g, $cpzone;
1336

    
1337
	$db_path = "{$g['vardb_path']}/captiveportal{$cpzone}.db";
1338
	$createquery = "CREATE TABLE IF NOT EXISTS captiveportal (" .
1339
				"allow_time INTEGER, pipeno INTEGER, ip TEXT, mac TEXT, username TEXT, " .
1340
				"sessionid TEXT, bpassword TEXT, session_timeout INTEGER, idle_timeout INTEGER, " .
1341
				"session_terminate_time INTEGER, interim_interval INTEGER, radiusctx TEXT); " .
1342
			"CREATE UNIQUE INDEX IF NOT EXISTS idx_active ON captiveportal (sessionid, username); " .
1343
			"CREATE INDEX IF NOT EXISTS user ON captiveportal (username); " .
1344
			"CREATE INDEX IF NOT EXISTS ip ON captiveportal (ip); " .
1345
			"CREATE INDEX IF NOT EXISTS starttime ON captiveportal (allow_time)";
1346

    
1347
	try {
1348
		$DB = new SQLite3($db_path);
1349
	} catch (Exception $e) {
1350
		captiveportal_syslog("Could not open {$db_path} as an sqlite database for {$cpzone}. Error message: " . $e->getMessage() . " -- Trying again.");
1351
		unlink_if_exists($db_path);
1352
		try {
1353
			$DB = new SQLite3($db_path);
1354
		} catch (Exception $e) {
1355
			captiveportal_syslog("Still could not open {$db_path} as an sqlite database for {$cpzone}. Error message: " . $e->getMessage() . " -- Remove the database file manually and ensure there is enough free space.");
1356
			return;
1357
		}
1358
	}
1359

    
1360
	if (!$DB) {
1361
		captiveportal_syslog("Could not open {$db_path} as an sqlite database for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Trying again.");
1362
		unlink_if_exists($db_path);
1363
		$DB = new SQLite3($db_path);
1364
		if (!$DB) {
1365
			captiveportal_syslog("Still could not open {$db_path} as an sqlite database for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Remove the database file manually and ensure there is enough free space.");
1366
			return;
1367
		}
1368
	}
1369

    
1370
	if (! $DB->exec($createquery)) {
1371
		captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$DB->lastErrorMsg()}. Resetting and trying again.");
1372

    
1373
		/* If unable to initialize the database, reset and try again. */
1374
		$DB->close();
1375
		unset($DB);
1376
		unlink_if_exists($db_path);
1377
		$DB = new SQLite3($db_path);
1378
		if ($DB->exec($createquery)) {
1379
			captiveportal_syslog("Successfully reinitialized tables for {$cpzone} -- database has been reset.");
1380
		} else {
1381
			captiveportal_syslog("Still unable to create tables for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Remove the database file manually and try again.");
1382
		}
1383
	}
1384

    
1385
	return $DB;
1386
}
1387

    
1388
/* read captive portal DB into array */
1389
function captiveportal_read_db($query = "") {
1390
	$cpdb = array();
1391

    
1392
	$DB = captiveportal_opendb();
1393
	if ($DB) {
1394
		$response = $DB->query("SELECT * FROM captiveportal {$query}");
1395
		if ($response != FALSE) {
1396
			while ($row = $response->fetchArray())
1397
				$cpdb[] = $row;
1398
		}
1399
		$DB->close();
1400
	}
1401

    
1402
	return $cpdb;
1403
}
1404

    
1405
function captiveportal_remove_entries($remove) {
1406

    
1407
	if (!is_array($remove) || empty($remove))
1408
		return;
1409

    
1410
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1411
	foreach($remove as $idx => $unindex) {
1412
		$query .= "'{$unindex}'";
1413
		if ($idx < (count($remove) - 1))
1414
			$query .= ",";
1415
	}
1416
	$query .= ")";
1417
	captiveportal_write_db($query);
1418
}
1419

    
1420
/* write captive portal DB */
1421
function captiveportal_write_db($queries) {
1422
	global $g;
1423

    
1424
	if (is_array($queries))
1425
		$query = implode(";", $queries);
1426
	else
1427
		$query = $queries;
1428

    
1429
	$DB = captiveportal_opendb();
1430
	if ($DB) {
1431
		$DB->exec("BEGIN TRANSACTION");
1432
		$result = $DB->exec($query);
1433
		if (!$result)
1434
			captiveportal_syslog("Trying to modify DB returned error: {$DB->lastErrorMsg()}");
1435
		else
1436
			$DB->exec("END TRANSACTION");
1437
		$DB->close();
1438
		return $result;
1439
	} else
1440
		return true;
1441
}
1442

    
1443
function captiveportal_write_elements() {
1444
	global $g, $config, $cpzone;
1445
	
1446
	$cpcfg = $config['captiveportal'][$cpzone];
1447

    
1448
	if (!is_dir($g['captiveportal_element_path']))
1449
		@mkdir($g['captiveportal_element_path']);
1450

    
1451
	if (is_array($cpcfg['element'])) {
1452
		conf_mount_rw();
1453
		foreach ($cpcfg['element'] as $data) {
1454
			if (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1455
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1456
				return 1;
1457
			}
1458
			if (!file_exists("{$g['captiveportal_path']}/{$data['name']}"))
1459
				@symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1460
		}
1461
		conf_mount_ro();
1462
	}
1463
	
1464
	return 0;
1465
}
1466

    
1467
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1468
	global $cpzone;
1469

    
1470
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1471
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1472
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1473
		$ridx = $rulenos_start;
1474
		while ($ridx < $rulenos_range_max) {
1475
			if ($rules[$ridx] == $cpzone) {
1476
				$rules[$ridx] = false;
1477
				$ridx++;
1478
				$rules[$ridx] = false;
1479
				$ridx++;
1480
			} else
1481
				$ridx += 2;
1482
		}
1483
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1484
		unset($rules);
1485
	}
1486
	unlock($cpruleslck);
1487
}
1488

    
1489
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1490
	global $config, $g, $cpzone;
1491

    
1492
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1493
	$ruleno = 0;
1494
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1495
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1496
		$ridx = $rulenos_start;
1497
		while ($ridx < $rulenos_range_max) {
1498
			if (empty($rules[$ridx])) {
1499
				$ruleno = $ridx;
1500
				$rules[$ridx] = $cpzone;
1501
				$ridx++;
1502
				$rules[$ridx] = $cpzone;
1503
				break;
1504
			} else {
1505
				$ridx += 2;
1506
			}
1507
		}
1508
	} else {
1509
		$rules = array_pad(array(), $rulenos_range_max, false);
1510
		$ruleno = $rulenos_start;
1511
		$rules[$rulenos_start] = $cpzone;
1512
		$rulenos_start++;
1513
		$rules[$rulenos_start] = $cpzone;
1514
	}
1515
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1516
	unlock($cpruleslck);
1517
	unset($rules);
1518

    
1519
	return $ruleno;
1520
}
1521

    
1522
function captiveportal_free_dn_ruleno($ruleno) {
1523
	global $config, $g;
1524

    
1525
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1526
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1527
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1528
		$rules[$ruleno] = false;
1529
		$ruleno++;
1530
		$rules[$ruleno] = false;
1531
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1532
		unset($rules);
1533
	}
1534
	unlock($cpruleslck);
1535
}
1536

    
1537
function captiveportal_get_dn_passthru_ruleno($value) {
1538
	global $config, $g, $cpzone, $cpzoneid;
1539

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

    
1544
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1545
	$ruleno = NULL;
1546
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1547
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1548
		unset($output);
1549
		$_gb = exec("/sbin/ipfw -x {$cpzoneid} show | /usr/bin/grep " . escapeshellarg($value) . " | /usr/bin/grep -v grep | /usr/bin/awk '{print $5}' | /usr/bin/head -n 1", $output);
1550
		$ruleno = intval($output[0]);
1551
		if (!$rules[$ruleno])
1552
			$ruleno = NULL;
1553
		unset($rules);
1554
	}
1555
	unlock($cpruleslck);
1556

    
1557
	return $ruleno;
1558
}
1559

    
1560
/*
1561
 * This function will calculate the lowest free firewall ruleno
1562
 * within the range specified based on the actual logged on users
1563
 *
1564
 */
1565
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1566
	global $config, $g, $cpzone;
1567

    
1568
	$cpcfg = $config['captiveportal'][$cpzone];
1569
	if(!isset($cpcfg['enable']))
1570
		return NULL;
1571

    
1572
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1573
	$ruleno = 0;
1574
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1575
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1576
		$ridx = $rulenos_start;
1577
		while ($ridx < $rulenos_range_max) {
1578
			if (empty($rules[$ridx])) {
1579
				$ruleno = $ridx;
1580
				$rules[$ridx] = $cpzone;
1581
				$ridx++;
1582
				$rules[$ridx] = $cpzone;
1583
				break;
1584
			} else {
1585
				/* 
1586
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1587
				 * and the out pipe ruleno + 1.
1588
				 */
1589
				$ridx += 2;
1590
			}
1591
		}
1592
	} else {
1593
		$rules = array_pad(array(), $rulenos_range_max, false);
1594
		$ruleno = $rulenos_start;
1595
		$rules[$rulenos_start] = $cpzone;
1596
		$rulenos_start++;
1597
		$rules[$rulenos_start] = $cpzone;
1598
	}
1599
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1600
	unlock($cpruleslck);
1601
	unset($rules);
1602

    
1603
	return $ruleno;
1604
}
1605

    
1606
function captiveportal_free_ipfw_ruleno($ruleno) {
1607
	global $config, $g, $cpzone;
1608

    
1609
	$cpcfg = $config['captiveportal'][$cpzone];
1610
	if(!isset($cpcfg['enable']))
1611
		return NULL;
1612

    
1613
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1614
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1615
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1616
		$rules[$ruleno] = false;
1617
		$ruleno++;
1618
		$rules[$ruleno] = false;
1619
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1620
		unset($rules);
1621
	}
1622
	unlock($cpruleslck);
1623
}
1624

    
1625
function captiveportal_get_ipfw_passthru_ruleno($value) {
1626
	global $config, $g, $cpzone, $cpzoneid;
1627

    
1628
	$cpcfg = $config['captiveportal'][$cpzone];
1629
	if(!isset($cpcfg['enable']))
1630
		return NULL;
1631

    
1632
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1633
	$ruleno = NULL;
1634
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1635
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1636
		unset($output);
1637
		$_gb = exec("/sbin/ipfw -x {$cpzoneid} show | /usr/bin/grep " . escapeshellarg($value) . " | /usr/bin/grep -v grep | /usr/bin/awk '{print $1}' | /usr/bin/head -n 1", $output);
1638
		$ruleno = intval($output[0]);
1639
		if (!$rules[$ruleno])
1640
			$ruleno = NULL;
1641
		unset($rules);
1642
	}
1643
	unlock($cpruleslck);
1644

    
1645
	return $ruleno;
1646
}
1647

    
1648
/**
1649
 * This function will calculate the traffic produced by a client
1650
 * based on its firewall rule
1651
 *
1652
 * Point of view: NAS
1653
 *
1654
 * Input means: from the client
1655
 * Output means: to the client
1656
 *
1657
 */
1658

    
1659
function getVolume($ip, $mac = NULL) {
1660
	global $config, $cpzone, $cpzoneid;
1661

    
1662
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1663
	$volume = array();
1664
	// Initialize vars properly, since we don't want NULL vars
1665
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1666

    
1667
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 1, $ip, $mac);
1668
	if (is_array($ipfw)) {
1669
		if ($reverse) {
1670
			$volume['output_pkts'] = $ipfw['packets'];
1671
			$volume['output_bytes'] = $ipfw['bytes'];
1672
		}
1673
		else {
1674
			$volume['input_pkts'] = $ipfw['packets'];
1675
			$volume['input_bytes'] = $ipfw['bytes'];
1676
		}
1677
	}
1678

    
1679
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 2, $ip, $mac);
1680
	if (is_array($ipfw)) {
1681
		if ($reverse) {
1682
			$volume['input_pkts'] = $ipfw['packets'];
1683
			$volume['input_bytes'] = $ipfw['bytes'];
1684
		}
1685
		else {
1686
			$volume['output_pkts'] = $ipfw['packets'];
1687
			$volume['output_bytes'] = $ipfw['bytes'];
1688
		}
1689
	}
1690

    
1691
	return $volume;
1692
}
1693

    
1694
/**
1695
 * Get the NAS-IP-Address based on the current wan address
1696
 *
1697
 * Use functions in interfaces.inc to find this out
1698
 *
1699
 */
1700

    
1701
function getNasIP()
1702
{
1703
	global $config, $cpzone;
1704

    
1705
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1706
			$nasIp = get_interface_ip();
1707
	} else {
1708
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1709
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1710
		else
1711
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1712
	}
1713
		
1714
	if(!is_ipaddr($nasIp))
1715
		$nasIp = "0.0.0.0";
1716

    
1717
	return $nasIp;
1718
}
1719

    
1720
function portal_ip_from_client_ip($cliip) {
1721
	global $config, $cpzone;
1722

    
1723
	$isipv6 = is_ipaddrv6($cliip);
1724
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1725
	foreach ($interfaces as $cpif) {
1726
		if ($isipv6) {
1727
			$ip = get_interface_ipv6($cpif);
1728
			$sn = get_interface_subnetv6($cpif);
1729
		} else {
1730
			$ip = get_interface_ip($cpif);
1731
			$sn = get_interface_subnet($cpif);
1732
		}
1733
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1734
			return $ip;
1735
	}
1736

    
1737
	$inet = ($isipv6) ? '-inet6' : '-inet';
1738
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1739
	$iface = trim($iface, "\n");
1740
	if (!empty($iface)) {
1741
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1742
		if (is_ipaddr($ip))
1743
			return $ip;
1744
	}
1745

    
1746
	// doesn't match up to any particular interface
1747
	// so let's set the portal IP to what PHP says 
1748
	// the server IP issuing the request is. 
1749
	// allows same behavior as 1.2.x where IP isn't 
1750
	// in the subnet of any CP interface (static routes, etc.)
1751
	// rather than forcing to DNS hostname resolution
1752
	$ip = $_SERVER['SERVER_ADDR'];
1753
	if (is_ipaddr($ip))
1754
		return $ip;
1755

    
1756
	return false;
1757
}
1758

    
1759
function portal_hostname_from_client_ip($cliip) {
1760
	global $config, $cpzone;
1761

    
1762
	$cpcfg = $config['captiveportal'][$cpzone];
1763

    
1764
	if (isset($cpcfg['httpslogin'])) {
1765
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 8001);
1766
		$ourhostname = $cpcfg['httpsname'];
1767
		
1768
		if ($listenporthttps != 443)
1769
			$ourhostname .= ":" . $listenporthttps;
1770
	} else {
1771
		$listenporthttp  = $cpcfg['listenporthttp']  ? $cpcfg['listenporthttp']  : ($cpcfg['zoneid'] + 8000);
1772
		$ifip = portal_ip_from_client_ip($cliip);
1773
		if (!$ifip)
1774
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1775
		else
1776
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1777
		
1778
		if ($listenporthttp != 80)
1779
			$ourhostname .= ":" . $listenporthttp;
1780
	}
1781
	
1782
	return $ourhostname;
1783
}
1784

    
1785
/* functions move from index.php */
1786

    
1787
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1788
	global $g, $config, $cpzone;
1789

    
1790
	/* Get captive portal layout */
1791
	if ($type == "redir") {
1792
		header("Location: {$redirurl}");
1793
		return;
1794
	} else if ($type == "login")
1795
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1796
	else
1797
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1798

    
1799
	$cpcfg = $config['captiveportal'][$cpzone];
1800

    
1801
	/* substitute the PORTAL_REDIRURL variable */
1802
	if ($cpcfg['preauthurl']) {
1803
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1804
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1805
	}
1806

    
1807
	/* substitute other variables */
1808
	$ourhostname = portal_hostname_from_client_ip($clientip);
1809
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1810
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1811
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1812

    
1813
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1814
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1815
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1816
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1817
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1818

    
1819
	// Special handling case for captive portal master page so that it can be ran 
1820
	// through the PHP interpreter using the include method above.  We convert the
1821
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1822
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1823
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1824
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1825
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1826
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1827
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1828
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1829

    
1830
	echo $htmltext;
1831
}
1832

    
1833
function portal_mac_radius($clientmac,$clientip) {
1834
	global $config, $cpzone;
1835

    
1836
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1837

    
1838
	/* authentication against the radius server */
1839
	$username = mac_format($clientmac);
1840
	$auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1841
	if ($auth_list['auth_val'] == 2)
1842
		return TRUE;
1843

    
1844
	if (!empty($auth_list['url_redirection']))
1845
		portal_reply_page($auth_list['url_redirection'], "redir");
1846

    
1847
	return FALSE;
1848
}
1849

    
1850
function captiveportal_reapply_attributes($cpentry, $attributes) {
1851
	global $config, $cpzone, $g;
1852

    
1853
	if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
1854
		$dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1855
		$dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1856
	} else
1857
		$dwfaultbw_up = $dwfaultbw_down = 0;
1858
	$bw_up = !empty($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1859
	$bw_down = !empty($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1860
	$bw_up_pipeno = $cpentry[1];
1861
	$bw_down_pipeno = $cpentry[1]+1;
1862

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

    
1867
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1868
}
1869

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

    
1873
	// Ensure we create an array if we are missing attributes
1874
	if (!is_array($attributes))
1875
		$attributes = array();
1876

    
1877
	unset($sessionid);
1878

    
1879
	/* Do not allow concurrent login execution. */
1880
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1881

    
1882
	if ($attributes['voucher'])
1883
		$remaining_time = $attributes['session_timeout'];
1884

    
1885
	$writecfg = false;
1886
	/* Find an existing session */
1887
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
1888
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1889
			$mac = captiveportal_passthrumac_findbyname($username);
1890
			if (!empty($mac)) {
1891
				if ($_POST['replacemacpassthru']) {
1892
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
1893
						if ($macent['mac'] == $mac['mac']) {
1894
							$macrules = "";
1895
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1896
							$pipeno = captiveportal_get_dn_passthru_ruleno($mac['mac']);
1897
							if ($ruleno) {
1898
								captiveportal_free_ipfw_ruleno($ruleno);
1899
								$macrules .= "delete {$ruleno}\n";
1900
								++$ruleno;
1901
								$macrules .= "delete {$ruleno}\n";
1902
							}
1903
							if ($pipeno) {
1904
								captiveportal_free_dn_ruleno($pipeno);
1905
								$macrules .= "pipe delete {$pipeno}\n";
1906
								++$pipeno;
1907
								$macrules .= "pipe delete {$pipeno}\n";
1908
							}
1909
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1910
							$mac['action'] = 'pass';
1911
							$mac['mac'] = $clientmac;
1912
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1913
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1914
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1915
							mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1916
							$writecfg = true;
1917
							$sessionid = true;
1918
							break;
1919
						}
1920
					}
1921
				} else {
1922
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1923
						$clientmac, $clientip, $username, $password);
1924
					unlock($cpdblck);
1925
					return;
1926
				}
1927
			}
1928
		}
1929
	}
1930

    
1931
	/* read in client database */
1932
	$query = "WHERE ip = '{$clientip}'";
1933
	$tmpusername = strtolower($username);
1934
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins']))
1935
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
1936
	$cpdb = captiveportal_read_db($query);
1937

    
1938
	/* Snapshot the timestamp */
1939
	$allow_time = time();
1940
	$radiusservers = captiveportal_get_radius_servers();
1941
	$unsetindexes = array();
1942
	if (is_null($radiusctx))
1943
		$radiusctx = 'first';
1944

    
1945
	foreach ($cpdb as $cpentry) {
1946
		if (empty($cpentry[11])) {
1947
			$cpentry[11] = 'first';
1948
		}
1949
		/* on the same ip */
1950
		if ($cpentry[2] == $clientip) {
1951
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac)
1952
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1953
			else
1954
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
1955
			$sessionid = $cpentry[5];
1956
			break;
1957
		}
1958
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1959
			// user logged in with an active voucher. Check for how long and calculate 
1960
			// how much time we can give him (voucher credit - used time)
1961
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1962
			if ($remaining_time < 0)    // just in case. 
1963
				$remaining_time = 0;
1964

    
1965
			/* This user was already logged in so we disconnect the old one */
1966
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[11]],13);
1967
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1968
			$unsetindexes[] = $cpentry[5];
1969
			break;
1970
		}
1971
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1972
			/* on the same username */
1973
			if (strcasecmp($cpentry[4], $username) == 0) {
1974
				/* This user was already logged in so we disconnect the old one */
1975
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[11]],13);
1976
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1977
				$unsetindexes[] = $cpentry[5];
1978
				break;
1979
			}
1980
		}
1981
	}
1982
	unset($cpdb);
1983

    
1984
	if (!empty($unsetindexes))
1985
		captiveportal_remove_entries($unsetindexes);
1986

    
1987
	if ($attributes['voucher'] && $remaining_time <= 0)
1988
		return 0;       // voucher already used and no time left
1989

    
1990
	if (!isset($sessionid)) {
1991
		/* generate unique session ID */
1992
		$tod = gettimeofday();
1993
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1994

    
1995
		if ($passthrumac) {
1996
			$mac = array();
1997
			$mac['action'] = 'pass';
1998
			$mac['mac'] = $clientmac;
1999
			$mac['ip'] = $clientip; /* Used only for logging */
2000
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
2001
				$mac['username'] = $username;
2002
				if ($attributes['voucher'])
2003
					$mac['logintype'] = "voucher";
2004
			}
2005
			if ($username == "unauthenticated")
2006
				$mac['descr'] =  "Auto-added";
2007
			else
2008
				$mac['descr'] =  "Auto-added for user {$username}";
2009
			if (!empty($bw_up))
2010
				$mac['bw_up'] = $bw_up;
2011
			if (!empty($bw_down))
2012
				$mac['bw_down'] = $bw_down;
2013
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
2014
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
2015
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
2016
			unlock($cpdblck);
2017
			$macrules = captiveportal_passthrumac_configure_entry($mac);
2018
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
2019
			mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
2020
			$writecfg = true;
2021
		} else {
2022
			/* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
2023
			if (is_null($pipeno))
2024
				$pipeno = captiveportal_get_next_dn_ruleno();
2025

    
2026
			/* if the pool is empty, return appropriate message and exit */
2027
			if (is_null($pipeno)) {
2028
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
2029
				log_error("Zone: {$cpzone} - WARNING!  Captive portal has reached maximum login capacity");
2030
				unlock($cpdblck);
2031
				return;
2032
			}
2033

    
2034
			if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
2035
				$dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
2036
				$dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
2037
			} else
2038
				$dwfaultbw_up = $dwfaultbw_down = 0;
2039
			$bw_up = !empty($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
2040
			$bw_down = !empty($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
2041

    
2042
			$bw_up_pipeno = $pipeno;
2043
			$bw_down_pipeno = $pipeno + 1;
2044
			//$bw_up /= 1000; // Scale to Kbit/s
2045
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
2046
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
2047

    
2048
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
2049
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
2050
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
2051
			else
2052
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
2053

    
2054
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
2055
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
2056
			else
2057
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
2058

    
2059
			if ($attributes['voucher'])
2060
				$attributes['session_timeout'] = $remaining_time;
2061
			
2062
			/* handle empty attributes */
2063
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
2064
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
2065
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
2066
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
2067

    
2068
			/* escape username */
2069
			$safe_username = SQLite3::escapeString($username);
2070

    
2071
			/* encode password in Base64 just in case it contains commas */
2072
			$bpassword = base64_encode($password);
2073
			$insertquery  = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, radiusctx) ";
2074
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
2075
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, '{$radiusctx}')";
2076

    
2077
			/* store information to database */
2078
			captiveportal_write_db($insertquery);
2079
			unlock($cpdblck);
2080
			unset($insertquery, $bpassword);
2081

    
2082
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
2083
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
2084
				if ($acct_val == 1)
2085
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
2086
			}
2087
		}
2088
	} else {
2089
		/* NOTE: #3062-11 If the pipeno has been allocated free it to not DoS the CP and maintain proper operation as in radius() case */
2090
		if (!is_null($pipeno))
2091
			captiveportal_free_dn_ruleno($pipeno);
2092

    
2093
		unlock($cpdblck);
2094
	}
2095

    
2096
	if ($writecfg == true)
2097
		write_config();
2098

    
2099
	/* redirect user to desired destination */
2100
	if (!empty($attributes['url_redirection']))
2101
		$my_redirurl = $attributes['url_redirection'];
2102
	else if (!empty($redirurl))
2103
		$my_redirurl = $redirurl;
2104
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
2105
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
2106

    
2107
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
2108
		$ourhostname = portal_hostname_from_client_ip($clientip);
2109
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
2110
		$logouturl = "{$protocol}{$ourhostname}/";
2111

    
2112
		if (isset($attributes['reply_message']))
2113
			$message = $attributes['reply_message'];
2114
		else
2115
			$message = 0;
2116

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

    
2119
	} else {
2120
		portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
2121
	}
2122

    
2123
	return $sessionid;
2124
}
2125

    
2126

    
2127
/*
2128
 * Used for when pass-through credits are enabled.
2129
 * Returns true when there was at least one free login to deduct for the MAC.
2130
 * Expired entries are removed as they are seen.
2131
 * Active entries are updated according to the configuration.
2132
 */
2133
function portal_consume_passthrough_credit($clientmac) {
2134
	global $config, $cpzone;
2135

    
2136
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
2137
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
2138
	else
2139
		return false;
2140

    
2141
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
2142
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2143
	else
2144
		return false;
2145

    
2146
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
2147
		return false;
2148

    
2149
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2150

    
2151
	/*
2152
	 * Read database of used MACs.  Lines are a comma-separated list
2153
	 * of the time, MAC, then the count of pass-through credits remaining.
2154
	 */
2155
	$usedmacs = captiveportal_read_usedmacs_db();
2156

    
2157
	$currenttime = time();
2158
	$found = false;
2159
	foreach ($usedmacs as $key => $usedmac) {
2160
		$usedmac = explode(",", $usedmac);
2161

    
2162
		if ($usedmac[1] == $clientmac) {
2163
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2164
				if ($usedmac[2] < 1) {
2165
					if ($updatetimeouts) {
2166
						$usedmac[0] = $currenttime;
2167
						unset($usedmacs[$key]);
2168
						$usedmacs[] = implode(",", $usedmac);
2169
						captiveportal_write_usedmacs_db($usedmacs);
2170
					}
2171

    
2172
					return false;
2173
				} else {
2174
					$usedmac[2] -= 1;
2175
					$usedmacs[$key] = implode(",", $usedmac);
2176
				}
2177

    
2178
				$found = true;
2179
			} else
2180
				unset($usedmacs[$key]);
2181

    
2182
			break;
2183
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2184
				unset($usedmacs[$key]);
2185
	}
2186

    
2187
	if (!$found) {
2188
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2189
		$usedmacs[] = implode(",", $usedmac);
2190
	}
2191

    
2192
	captiveportal_write_usedmacs_db($usedmacs);
2193
	return true;
2194
}
2195

    
2196
function captiveportal_read_usedmacs_db() {
2197
	global $g, $cpzone;
2198

    
2199
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2200
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2201
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2202
		if (!$usedmacs)
2203
			$usedmacs = array();
2204
	} else
2205
		$usedmacs = array();
2206

    
2207
	unlock($cpumaclck);
2208
	return $usedmacs;
2209
}
2210

    
2211
function captiveportal_write_usedmacs_db($usedmacs) {
2212
	global $g, $cpzone;
2213

    
2214
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2215
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2216
	unlock($cpumaclck);
2217
}
2218

    
2219
function captiveportal_blocked_mac($mac) {
2220
	global $config, $g, $cpzone;
2221

    
2222
	if (empty($mac) || !is_macaddr($mac))
2223
		return false;
2224

    
2225
	if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
2226
		return false;
2227

    
2228
	foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac)
2229
		if (($passthrumac['action'] == 'block') &&
2230
		    ($passthrumac['mac'] == strtolower($mac)))
2231
			return true;
2232

    
2233
	return false;
2234

    
2235
}
2236

    
2237
function captiveportal_send_server_accounting($off = false) {
2238
	global $cpzone, $config;
2239

    
2240
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
2241
		return;
2242
	}
2243
	if ($off) {
2244
		$racct = new Auth_RADIUS_Acct_Off;
2245
	} else {
2246
		$racct = new Auth_RADIUS_Acct_On;
2247
	}
2248
	$radiusservers = captiveportal_get_radius_servers();
2249
	if (empty($radiusservers)) {
2250
		return;
2251
	}
2252
	foreach ($radiusservers['first'] as $radsrv) {
2253
		// Add a new server to our instance
2254
		$racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
2255
	}
2256
	if (PEAR::isError($racct->start())) {
2257
		$retvalue['acct_val'] = 1;
2258
		$retvalue['error'] = $racct->getMessage();
2259

    
2260
		// If we encounter an error immediately stop this function and go back
2261
		$racct->close();
2262
		return $retvalue;
2263
	}
2264
	// Send request
2265
	$result = $racct->send();
2266
	// Evaluation of the response
2267
	// 5 -> Accounting-Response
2268
	// See RFC2866 for this.
2269
	if (PEAR::isError($result)) {
2270
		$retvalue['acct_val'] = 1;
2271
		$retvalue['error'] = $result->getMessage();
2272
	} else if ($result === true) {
2273
		$retvalue['acct_val'] = 5 ;
2274
	} else {
2275
		$retvalue['acct_val'] = 1 ;
2276
	}
2277

    
2278
	$racct->close();
2279
	return $retvalue;
2280
}
2281
?>
(7-7/68)