Project

General

Profile

Download (77.8 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

    
198
		/* init ipfw rules */
199
		captiveportal_init_rules(true);
200

    
201
		/* kill any running minicron */
202
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
203

    
204
		/* initialize minicron interval value */
205
		$croninterval = $cpcfg['croninterval'] ? $cpcfg['croninterval'] : 60;
206

    
207
		/* double check if the $croninterval is numeric and at least 10 seconds. If not we set it to 60 to avoid problems */
208
		if ((!is_numeric($croninterval)) || ($croninterval < 10)) {
209
			$croninterval = 60;
210
		}
211

    
212
		/* write portal page */
213
		if (is_array($cpcfg['page']) && $cpcfg['page']['htmltext']) {
214
			$htmltext = base64_decode($cpcfg['page']['htmltext']);
215
		} else {
216
			/* example/template page */
217
			$htmltext = get_default_captive_portal_html();
218
		}
219

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

    
240
		/* write error page */
241
		if (is_array($cpcfg['page']) && $cpcfg['page']['errtext']) {
242
			$errtext = base64_decode($cpcfg['page']['errtext']);
243
		} else {
244
			/* example page  */
245
			$errtext = get_default_captive_portal_html();
246
		}
247

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

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

    
299
document.location.href="<?=\$my_redirurl;?>";
300
//]]>
301
</script>
302
</body>
303
</html>
304

    
305
EOD;
306
		}
307

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

    
315
		/* write elements */
316
		captiveportal_write_elements();
317

    
318
		/* kill any running mini_httpd */
319
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
320
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
321

    
322
		/* start up the webserving daemon */
323
		captiveportal_init_webgui_zone($cpcfg);
324

    
325
		/* Kill any existing prunecaptiveportal processes */
326
		if (file_exists("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid")) {
327
			killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
328
		}
329

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

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

    
338
		if (platform_booting()) {
339
			/* send Accounting-On to server */
340
			captiveportal_send_server_accounting();
341
			echo "done\n";
342
		}
343

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

    
352
		captiveportal_radius_stop_all();
353

    
354
		/* send Accounting-Off to server */
355
		if (!platform_booting()) {
356
			captiveportal_send_server_accounting(true);
357
		}
358

    
359
		/* remove old information */
360
		unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
361
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
362
		unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
363
		/* Release allocated pipes for this zone */
364
		captiveportal_free_dnrules();
365

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

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

    
387
	unlock($captiveportallck);
388

    
389
	return 0;
390
}
391

    
392
function captiveportal_init_webgui() {
393
	global $config, $cpzone;
394

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

    
403
function captiveportal_init_webgui_zonename($zone) {
404
	global $config, $cpzone;
405

    
406
	if (isset($config['captiveportal'][$zone])) {
407
		$cpzone = $zone;
408
		captiveportal_init_webgui_zone($config['captiveportal'][$zone]);
409
	}
410
}
411

    
412
function captiveportal_init_webgui_zone($cpcfg) {
413
	global $g, $config, $cpzone;
414

    
415
	if (!isset($cpcfg['enable'])) {
416
		return;
417
	}
418

    
419
	if (isset($cpcfg['httpslogin'])) {
420
		$cert = lookup_cert($cpcfg['certref']);
421
		$crt = base64_decode($cert['crt']);
422
		$key = base64_decode($cert['prv']);
423
		$ca = ca_chain($cert);
424

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

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

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

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

    
457
function captiveportal_init_rules_byinterface($interface) {
458
	global $cpzone, $cpzoneid, $config;
459

    
460
	if (!is_array($config['captiveportal'])) {
461
		return;
462
	}
463

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

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

    
479
	if (!isset($config['captiveportal'][$cpzone]['enable'])) {
480
		return;
481
	}
482

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

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

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

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

    
524
	$cprules = <<<EOD
525

    
526
flush
527
add 65291 allow pfsync from any to any
528
add 65292 allow carp from any to any
529

    
530
# layer 2: pass ARP
531
add 65301 pass layer2 mac-type arp,rarp
532
# pfsense requires for WPA
533
add 65302 pass layer2 mac-type 0x888e,0x88c7
534
# PPP Over Ethernet Session Stage/Discovery Stage
535
add 65303 pass layer2 mac-type 0x8863,0x8864
536

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

    
540
EOD;
541

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

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

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

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

    
594
	$cprules .= <<<EOD
595

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

    
603
EOD;
604

    
605
	/* generate passthru mac database */
606
	$cprules .= captiveportal_passthrumac_configure(true);
607
	$cprules .= "\n";
608

    
609
	/* allowed ipfw rules to make allowed ip work */
610
	$cprules .= captiveportal_allowedip_configure();
611

    
612
	/* allowed ipfw rules to make allowed hostnames work */
613
	$cprules .= captiveportal_allowedhostname_configure();
614

    
615
	/* load rules */
616
	file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
617
	mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
618
	//@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
619
	unset($cprules);
620

    
621
	if ($reinit == false) {
622
		unlock($captiveportallck);
623
	}
624
}
625

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

    
635
	if (empty($cpzone)) {
636
		return;
637
	}
638

    
639
	$cpcfg = $config['captiveportal'][$cpzone];
640
	$vcpcfg = $config['voucher'][$cpzone];
641

    
642
	/* check for expired entries */
643
	$idletimeout = 0;
644
	$timeout = 0;
645
	if (!empty($cpcfg['timeout']) && is_numeric($cpcfg['timeout'])) {
646
		$timeout = $cpcfg['timeout'] * 60;
647
	}
648

    
649
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout'])) {
650
		$idletimeout = $cpcfg['idletimeout'] * 60;
651
	}
652

    
653
	/* Is there any job to do? */
654
	if (!$timeout && !$idletimeout && !isset($cpcfg['reauthenticate']) &&
655
	    !isset($cpcfg['radiussession_timeout']) && !isset($vcpcfg['enable'])) {
656
		return;
657
	}
658

    
659
	$radiussrvs = captiveportal_get_radius_servers();
660

    
661
	/* Read database */
662
	/* NOTE: while this can be simplified in non radius case keep as is for now */
663
	$cpdb = captiveportal_read_db();
664

    
665
	$unsetindexes = array();
666
	$voucher_needs_sync = false;
667
	/*
668
	 * Snapshot the time here to use for calculation to speed up the process.
669
	 * If something is missed next run will catch it!
670
	 */
671
	$pruning_time = time();
672
	$stop_time = $pruning_time;
673
	foreach ($cpdb as $cpentry) {
674

    
675
		$timedout = false;
676
		$term_cause = 1;
677
		if (empty($cpentry[11])) {
678
			$cpentry[11] = 'first';
679
		}
680
		$radiusservers = $radiussrvs[$cpentry[11]];
681

    
682
		/* hard timeout? */
683
		if ($timeout) {
684
			if (($pruning_time - $cpentry[0]) >= $timeout) {
685
				$timedout = true;
686
				$term_cause = 5; // Session-Timeout
687
			}
688
		}
689

    
690
		/* Session-Terminate-Time */
691
		if (!$timedout && !empty($cpentry[9])) {
692
			if ($pruning_time >= $cpentry[9]) {
693
				$timedout = true;
694
				$term_cause = 5; // Session-Timeout
695
			}
696
		}
697

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

    
714
		/* if vouchers are configured, activate session timeouts */
715
		if (!$timedout && isset($vcpcfg['enable']) && !empty($cpentry[7])) {
716
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
717
				$timedout = true;
718
				$term_cause = 5; // Session-Timeout
719
				$voucher_needs_sync = true;
720
			}
721
		}
722

    
723
		/* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
724
		if (!$timedout && isset($cpcfg['radiussession_timeout']) && !empty($cpentry[7])) {
725
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
726
				$timedout = true;
727
				$term_cause = 5; // Session-Timeout
728
			}
729
		}
730

    
731
		if ($timedout) {
732
			captiveportal_disconnect($cpentry, $radiusservers, $term_cause, $stop_time);
733
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
734
			$unsetindexes[] = $cpentry[5];
735
		}
736

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

    
784
			/* check this user against RADIUS again */
785
			if (isset($cpcfg['reauthenticate'])) {
786
				$auth_list = RADIUS_AUTHENTICATION($cpentry[4], // username
787
					base64_decode($cpentry[6]), // password
788
					$radiusservers,
789
					$cpentry[2], // clientip
790
					$cpentry[3], // clientmac
791
					$cpentry[1]); // ruleno
792
				if ($auth_list['auth_val'] == 3) {
793
					captiveportal_disconnect($cpentry, $radiusservers, 17);
794
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
795
					$unsetindexes[] = $cpentry[5];
796
				} else if ($auth_list['auth_val'] == 2) {
797
					captiveportal_reapply_attributes($cpentry, $auth_list);
798
				}
799
			}
800
		}
801
	}
802
	unset($cpdb);
803

    
804
	captiveportal_prune_old_automac();
805

    
806
	if ($voucher_needs_sync == true) {
807
		/* Trigger a sync of the vouchers on config */
808
		send_event("service sync vouchers");
809
	}
810

    
811
	/* write database */
812
	if (!empty($unsetindexes)) {
813
		captiveportal_remove_entries($unsetindexes);
814
	}
815
}
816

    
817
function captiveportal_prune_old_automac() {
818
	global $g, $config, $cpzone, $cpzoneid;
819

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

    
882
/* remove a single client according to the DB entry */
883
function captiveportal_disconnect($dbent, $radiusservers, $term_cause = 1, $stop_time = null) {
884
	global $g, $config, $cpzone, $cpzoneid;
885

    
886
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
887

    
888
	/* this client needs to be deleted - remove ipfw rules */
889
	if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers)) {
890
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
891
			$dbent[4], // username
892
			$dbent[5], // sessionid
893
			$dbent[0], // start time
894
			$radiusservers,
895
			$dbent[2], // clientip
896
			$dbent[3], // clientmac
897
			$term_cause, // Acct-Terminate-Cause
898
			false,
899
			$stop_time);
900
	}
901

    
902
	if (is_ipaddr($dbent[2])) {
903
		/* Delete client's ip entry from tables 1 and 2. */
904
		$clientsn = (is_ipaddrv6($dbent[2])) ? 128 : 32;
905
		pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XDEL, 1, $dbent[2], $clientsn, $dbent[3]);
906
		pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XDEL, 2, $dbent[2], $clientsn, $dbent[3]);
907
		/* XXX: Redundant?! Ensure all pf(4) states are killed. */
908
		$_gb = @pfSense_kill_states($dbent[2]);
909
		$_gb = @pfSense_kill_srcstates($dbent[2]);
910
	}
911

    
912
	/*
913
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
914
	* We could get an error if the pipe doesn't exist but everything should still be fine
915
	*/
916
	if (!empty($dbent[1])) {
917
		$_gb = @pfSense_pipe_action("pipe delete {$dbent[1]}");
918
		$_gb = @pfSense_pipe_action("pipe delete " . ($dbent[1]+1));
919

    
920
		/* Release the ruleno so it can be reallocated to new clients. */
921
		captiveportal_free_dn_ruleno($dbent[1]);
922
	}
923

    
924
	// XMLRPC Call over to the master Voucher node
925
	if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
926
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
927
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
928
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
929
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
930
		$remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time);
931
	}
932

    
933
}
934

    
935
/* remove a single client by sessionid */
936
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
937
	global $g, $config;
938

    
939
	$radiusservers = captiveportal_get_radius_servers();
940

    
941
	/* read database */
942
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
943

    
944
	/* find entry */
945
	if (!empty($result)) {
946
		captiveportal_write_db("DELETE FROM captiveportal WHERE sessionid = '{$sessionid}'");
947

    
948
		foreach ($result as $cpentry) {
949
			if (empty($cpentry[11])) {
950
				$cpentry[11] = 'first';
951
			}
952
			captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], $term_cause);
953
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
954
		}
955
		unset($result);
956
	}
957
}
958

    
959
/* send RADIUS acct stop for all current clients */
960
function captiveportal_radius_stop_all() {
961
	global $config, $cpzone;
962

    
963
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
964
		return;
965
	}
966

    
967
	$radiusservers = captiveportal_get_radius_servers();
968
	if (!empty($radiusservers)) {
969
		$cpdb = captiveportal_read_db();
970
		foreach ($cpdb as $cpentry) {
971
			if (empty($cpentry[11])) {
972
				$cpentry[11] = 'first';
973
			}
974
			if (!empty($radiusservers[$cpentry[11]])) {
975
				RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
976
					$cpentry[4], // username
977
					$cpentry[5], // sessionid
978
					$cpentry[0], // start time
979
					$radiusservers[$cpentry[11]],
980
					$cpentry[2], // clientip
981
					$cpentry[3], // clientmac
982
					7); // Admin Reboot
983
			}
984
		}
985
	}
986
}
987

    
988
function captiveportal_passthrumac_configure_entry($macent, $pipeinrule = false) {
989
	global $config, $g, $cpzone;
990

    
991
	$bwUp = 0;
992
	if (!empty($macent['bw_up'])) {
993
		$bwUp = $macent['bw_up'];
994
	} else if (!empty($config['captiveportal'][$cpzone]['bwdefaultup'])) {
995
		$bwUp = $config['captiveportal'][$cpzone]['bwdefaultup'];
996
	}
997
	$bwDown = 0;
998
	if (!empty($macent['bw_down'])) {
999
		$bwDown = $macent['bw_down'];
1000
	} else if (!empty($config['captiveportal'][$cpzone]['bwdefaultdn'])) {
1001
		$bwDown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
1002
	}
1003

    
1004
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1005

    
1006
	if ($macent['action'] == 'pass') {
1007
		$rules = "";
1008
		$pipeno = captiveportal_get_next_dn_ruleno();
1009

    
1010
		$pipeup = $pipeno;
1011
		if ($pipeinrule == true) {
1012
			$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16");
1013
		} else {
1014
			$rules .= "pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16\n";
1015
		}
1016

    
1017
		$pipedown = $pipeno + 1;
1018
		if ($pipeinrule == true) {
1019
			$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
1020
		} else {
1021
			$rules .= "pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16\n";
1022
		}
1023

    
1024
		$rules .= "add {$ruleno} pipe {$pipeup} ip from any to any MAC any {$macent['mac']}\n";
1025
		$ruleno++;
1026
		$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC {$macent['mac']} any\n";
1027
	}
1028

    
1029
	return $rules;
1030
}
1031

    
1032
function captiveportal_passthrumac_delete_entry($macent) {
1033
	$rules = "";
1034

    
1035
	if ($macent['action'] == 'pass') {
1036
		$ruleno = captiveportal_get_ipfw_passthru_ruleno($macent['mac']);
1037

    
1038
		if (!$ruleno) {
1039
			return $rules;
1040
		}
1041

    
1042
		captiveportal_free_ipfw_ruleno($ruleno);
1043

    
1044
		$rules .= "delete {$ruleno}\n";
1045
		$rules .= "delete " . ++$ruleno . "\n";
1046

    
1047
		$pipeno = captiveportal_get_dn_passthru_ruleno($macent['mac']);
1048

    
1049
		if (!empty($pipeno)) {
1050
			captiveportal_free_dn_ruleno($pipeno);
1051
			$rules .= "pipe delete " . $pipeno . "\n";
1052
			$rules .= "pipe delete " . ++$pipeno . "\n";
1053
		}
1054
	}
1055

    
1056
	return $rules;
1057
}
1058

    
1059
function captiveportal_passthrumac_configure($filename = false, $startindex = 0, $stopindex = 0) {
1060
	global $config, $g, $cpzone;
1061

    
1062
	$rules = "";
1063

    
1064
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1065
		if ($stopindex > 0) {
1066
			$fd = fopen($filename, "w");
1067
			for ($idx = $startindex; $idx <= $stopindex; $idx++) {
1068
				if (isset($config['captiveportal'][$cpzone]['passthrumac'][$idx])) {
1069
					$rules = captiveportal_passthrumac_configure_entry($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1070
					fwrite($fd, $rules);
1071
				}
1072
			}
1073
			fclose($fd);
1074

    
1075
			return;
1076
		} else {
1077
			$nentries = count($config['captiveportal'][$cpzone]['passthrumac']);
1078
			if ($nentries > 2000) {
1079
				$nloops = $nentries / 1000;
1080
				$remainder= $nentries % 1000;
1081
				for ($i = 0; $i < $nloops; $i++) {
1082
					mwexec_bg("/usr/local/sbin/fcgicli -f /etc/rc.captiveportal_configure_mac -d \"cpzone={$cpzone}&startidx=" . ($i * 1000) . "&stopidx=" . ((($i+1) * 1000) - 1) . "\"");
1083
				}
1084
				if ($remainder > 0) {
1085
					mwexec_bg("/usr/local/sbin/fcgicli -f /etc/rc.captiveportal_configure_mac -d \"cpzone={$cpzone}&startidx=" . ($i * 1000) . "&stopidx=" . (($i* 1000) + $remainder) ."\"");
1086
				}
1087
			} else {
1088
				foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1089
					$rules .= captiveportal_passthrumac_configure_entry($macent, true);
1090
				}
1091
			}
1092
		}
1093
	}
1094

    
1095
	return $rules;
1096
}
1097

    
1098
function captiveportal_passthrumac_findbyname($username) {
1099
	global $config, $cpzone;
1100

    
1101
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1102
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1103
			if ($macent['username'] == $username) {
1104
				return $macent;
1105
			}
1106
		}
1107
	}
1108
	return NULL;
1109
}
1110

    
1111
/*
1112
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1113
 */
1114
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
1115
	global $g;
1116

    
1117
	/*  Instead of copying this entire function for something
1118
	 *  easy such as hostname vs ip address add this check
1119
	 */
1120
	if ($ishostname === true) {
1121
		if (!platform_booting()) {
1122
			$ipaddress = gethostbyname($ipent['hostname']);
1123
			if (!is_ipaddr($ipaddress)) {
1124
				return;
1125
			}
1126
		} else {
1127
			$ipaddress = "";
1128
		}
1129
	} else {
1130
		$ipaddress = $ipent['ip'];
1131
	}
1132

    
1133
	$rules = "";
1134
	$cp_filterdns_conf = "";
1135
	$enBwup = 0;
1136
	if (!empty($ipent['bw_up'])) {
1137
		$enBwup = intval($ipent['bw_up']);
1138
	} else if (!empty($config['captiveportal'][$cpzone]['bwdefaultup'])) {
1139
		$enBwup = $config['captiveportal'][$cpzone]['bwdefaultup'];
1140
	}
1141
	$enBwdown = 0;
1142
	if (!empty($ipent['bw_down'])) {
1143
		$enBwdown = intval($ipent['bw_down']);
1144
	} else if (!empty($config['captiveportal'][$cpzone]['bwdefaultdn'])) {
1145
		$enBwdown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
1146
	}
1147

    
1148
	$pipeno = captiveportal_get_next_dn_ruleno();
1149
	$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$enBwup}Kbit/s queue 100 buckets 16");
1150
	$pipedown = $pipeno + 1;
1151
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16");
1152
	if ($ishostname === true) {
1153
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
1154
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
1155
		if (!is_ipaddr($ipaddress)) {
1156
			return array("", $cp_filterdns_conf);
1157
		}
1158
	}
1159
	$subnet = "";
1160
	if (!empty($ipent['sn'])) {
1161
		$subnet = "/{$ipent['sn']}";
1162
	}
1163
	$rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
1164
	$rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
1165

    
1166
	if ($ishostname === true) {
1167
		return array($rules, $cp_filterdns_conf);
1168
	} else {
1169
		return $rules;
1170
	}
1171
}
1172

    
1173
function captiveportal_allowedhostname_configure() {
1174
	global $config, $g, $cpzone, $cpzoneid;
1175

    
1176
	$rules = "";
1177
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1178
		$rules = "\n# captiveportal_allowedhostname_configure()\n";
1179
		$cp_filterdns_conf = "";
1180
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) {
1181
			$tmprules = captiveportal_allowedip_configure_entry($hostnameent, true);
1182
			$rules .= $tmprules[0];
1183
			$cp_filterdns_conf .= $tmprules[1];
1184
		}
1185
		$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1186
		@file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1187
		unset($cp_filterdns_conf);
1188
		if (isvalidpid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid")) {
1189
			sigkillbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid", "HUP");
1190
		} else {
1191
			mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzoneid} -d 1");
1192
		}
1193
	} else {
1194
		killbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1195
		@unlink("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1196
	}
1197

    
1198
	return $rules;
1199
}
1200

    
1201
function captiveportal_allowedip_configure() {
1202
	global $config, $g, $cpzone;
1203

    
1204
	$rules = "";
1205
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1206
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) {
1207
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1208
		}
1209
	}
1210

    
1211
	return $rules;
1212
}
1213

    
1214
/* get last activity timestamp given client IP address */
1215
function captiveportal_get_last_activity($ip, $mac = NULL, $table = 1) {
1216
	global $cpzoneid;
1217

    
1218
	$ipfwoutput = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, $table, $ip, $mac);
1219
	/* Reading only from one of the tables is enough of approximation. */
1220
	if (is_array($ipfwoutput)) {
1221
		/* Workaround for #46652 */
1222
		if ($ipfwoutput['packets'] > 0) {
1223
			return $ipfwoutput['timestamp'];
1224
		} else {
1225
			return 0;
1226
		}
1227
	}
1228

    
1229
	return 0;
1230
}
1231

    
1232
function captiveportal_init_radius_servers() {
1233
	global $config, $g, $cpzone;
1234

    
1235
	/* generate radius server database */
1236
	if ($config['captiveportal'][$cpzone]['radiusip'] &&
1237
	    (!isset($config['captiveportal'][$cpzone]['auth_method']) || $config['captiveportal'][$cpzone]['auth_method'] == "radius")) {
1238
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1239
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1240
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1241
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1242

    
1243
		if ($config['captiveportal'][$cpzone]['radiusport']) {
1244
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1245
		} else {
1246
			$radiusport = 1812;
1247
		}
1248
		if ($config['captiveportal'][$cpzone]['radiusacctport']) {
1249
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1250
		} else {
1251
			$radiusacctport = 1813;
1252
		}
1253
		if ($config['captiveportal'][$cpzone]['radiusport2']) {
1254
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1255
		} else {
1256
			$radiusport2 = 1812;
1257
		}
1258
		if ($config['captiveportal'][$cpzone]['radiusport3']) {
1259
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1260
		} else {
1261
			$radiusport3 = 1812;
1262
		}
1263
		if ($config['captiveportal'][$cpzone]['radiusport4']) {
1264
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1265
		} else {
1266
			$radiusport4 = 1812;
1267
		}
1268

    
1269
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1270
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1271
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1272
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1273

    
1274
		$cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX);
1275
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
1276
		if (!$fd) {
1277
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1278
			unlock($cprdsrvlck);
1279
			return 1;
1280
		}
1281
		if (isset($radiusip)) {
1282
			fwrite($fd, $radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
1283
		}
1284
		if (isset($radiusip2)) {
1285
			fwrite($fd, "\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
1286
		}
1287
		if (isset($radiusip3)) {
1288
			fwrite($fd, "\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
1289
		}
1290
		if (isset($radiusip4)) {
1291
			fwrite($fd, "\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
1292
		}
1293

    
1294
		fclose($fd);
1295
		unlock($cprdsrvlck);
1296
	}
1297
}
1298

    
1299
/* read RADIUS servers into array */
1300
function captiveportal_get_radius_servers() {
1301
	global $g, $cpzone;
1302

    
1303
	$cprdsrvlck = lock("captiveportalradius{$cpzone}");
1304
	if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
1305
		$radiusservers = array();
1306
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db",
1307
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1308
		if ($cpradiusdb) {
1309
			foreach ($cpradiusdb as $cpradiusentry) {
1310
				$line = trim($cpradiusentry);
1311
				if ($line) {
1312
					$radsrv = array();
1313
					list($radsrv['ipaddr'], $radsrv['port'], $radsrv['acctport'], $radsrv['key'], $context) = explode(",", $line);
1314
				}
1315
				if (empty($context)) {
1316
					if (!is_array($radiusservers['first'])) {
1317
						$radiusservers['first'] = array();
1318
					}
1319
					$radiusservers['first'] = $radsrv;
1320
				} else {
1321
					if (!is_array($radiusservers[$context])) {
1322
						$radiusservers[$context] = array();
1323
					}
1324
					$radiusservers[$context][] = $radsrv;
1325
				}
1326
			}
1327
		}
1328
		unlock($cprdsrvlck);
1329
		return $radiusservers;
1330
	}
1331

    
1332
	unlock($cprdsrvlck);
1333
	return false;
1334
}
1335

    
1336
/* log successful captive portal authentication to syslog */
1337
/* part of this code from php.net */
1338
function captiveportal_logportalauth($user, $mac, $ip, $status, $message = null) {
1339
	// Log it
1340
	if (!$message) {
1341
		$message = "{$status}: {$user}, {$mac}, {$ip}";
1342
	} else {
1343
		$message = trim($message);
1344
		$message = "{$status}: {$user}, {$mac}, {$ip}, {$message}";
1345
	}
1346
	captiveportal_syslog($message);
1347
}
1348

    
1349
/* log simple messages to syslog */
1350
function captiveportal_syslog($message) {
1351
	global $cpzone;
1352

    
1353
	$message = trim($message);
1354
	$message = "Zone: {$cpzone} - {$message}";
1355
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1356
	// Log it
1357
	syslog(LOG_INFO, $message);
1358
	closelog();
1359
}
1360

    
1361
function radius($username, $password, $clientip, $clientmac, $type, $radiusctx = null) {
1362
	global $g, $config, $cpzoneid;
1363

    
1364
	$pipeno = captiveportal_get_next_dn_ruleno();
1365

    
1366
	/* If the pool is empty, return appropriate message and fail authentication */
1367
	if (empty($pipeno)) {
1368
		$auth_list = array();
1369
		$auth_list['auth_val'] = 1;
1370
		$auth_list['error'] = "System reached maximum login capacity";
1371
		return $auth_list;
1372
	}
1373

    
1374
	$radiusservers = captiveportal_get_radius_servers();
1375

    
1376
	if (is_null($radiusctx)) {
1377
		$radiusctx = 'first';
1378
	}
1379

    
1380
	$auth_list = RADIUS_AUTHENTICATION($username,
1381
		$password,
1382
		$radiusservers[$radiusctx],
1383
		$clientip,
1384
		$clientmac,
1385
		$pipeno);
1386

    
1387
	if ($auth_list['auth_val'] == 2) {
1388
		captiveportal_logportalauth($username, $clientmac, $clientip, $type);
1389
		$sessionid = portal_allow($clientip,
1390
			$clientmac,
1391
			$username,
1392
			$password,
1393
			$auth_list,
1394
			$pipeno,
1395
			$radiusctx);
1396
	} else {
1397
		captiveportal_free_dn_ruleno($pipeno);
1398
	}
1399

    
1400
	return $auth_list;
1401
}
1402

    
1403
function captiveportal_opendb() {
1404
	global $g, $cpzone;
1405

    
1406
	$db_path = "{$g['vardb_path']}/captiveportal{$cpzone}.db";
1407
	$createquery = "CREATE TABLE IF NOT EXISTS captiveportal (" .
1408
				"allow_time INTEGER, pipeno INTEGER, ip TEXT, mac TEXT, username TEXT, " .
1409
				"sessionid TEXT, bpassword TEXT, session_timeout INTEGER, idle_timeout INTEGER, " .
1410
				"session_terminate_time INTEGER, interim_interval INTEGER, radiusctx TEXT); " .
1411
			"CREATE UNIQUE INDEX IF NOT EXISTS idx_active ON captiveportal (sessionid, username); " .
1412
			"CREATE INDEX IF NOT EXISTS user ON captiveportal (username); " .
1413
			"CREATE INDEX IF NOT EXISTS ip ON captiveportal (ip); " .
1414
			"CREATE INDEX IF NOT EXISTS starttime ON captiveportal (allow_time)";
1415

    
1416
	try {
1417
		$DB = new SQLite3($db_path);
1418
	} catch (Exception $e) {
1419
		captiveportal_syslog("Could not open {$db_path} as an sqlite database for {$cpzone}. Error message: " . $e->getMessage() . " -- Trying again.");
1420
		unlink_if_exists($db_path);
1421
		try {
1422
			$DB = new SQLite3($db_path);
1423
		} catch (Exception $e) {
1424
			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.");
1425
			return;
1426
		}
1427
	}
1428

    
1429
	if (!$DB) {
1430
		captiveportal_syslog("Could not open {$db_path} as an sqlite database for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Trying again.");
1431
		unlink_if_exists($db_path);
1432
		$DB = new SQLite3($db_path);
1433
		if (!$DB) {
1434
			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.");
1435
			return;
1436
		}
1437
	}
1438

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

    
1442
		/* If unable to initialize the database, reset and try again. */
1443
		$DB->close();
1444
		unset($DB);
1445
		unlink_if_exists($db_path);
1446
		$DB = new SQLite3($db_path);
1447
		if ($DB->exec($createquery)) {
1448
			captiveportal_syslog("Successfully reinitialized tables for {$cpzone} -- database has been reset.");
1449
		} else {
1450
			captiveportal_syslog("Still unable to create tables for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Remove the database file manually and try again.");
1451
		}
1452
	}
1453

    
1454
	return $DB;
1455
}
1456

    
1457
/* read captive portal DB into array */
1458
function captiveportal_read_db($query = "") {
1459
	$cpdb = array();
1460

    
1461
	$DB = captiveportal_opendb();
1462
	if ($DB) {
1463
		$response = $DB->query("SELECT * FROM captiveportal {$query}");
1464
		if ($response != FALSE) {
1465
			while ($row = $response->fetchArray()) {
1466
				$cpdb[] = $row;
1467
			}
1468
		}
1469
		$DB->close();
1470
	}
1471

    
1472
	return $cpdb;
1473
}
1474

    
1475
function captiveportal_remove_entries($remove) {
1476

    
1477
	if (!is_array($remove) || empty($remove)) {
1478
		return;
1479
	}
1480

    
1481
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1482
	foreach ($remove as $idx => $unindex) {
1483
		$query .= "'{$unindex}'";
1484
		if ($idx < (count($remove) - 1)) {
1485
			$query .= ",";
1486
		}
1487
	}
1488
	$query .= ")";
1489
	captiveportal_write_db($query);
1490
}
1491

    
1492
/* write captive portal DB */
1493
function captiveportal_write_db($queries) {
1494
	global $g;
1495

    
1496
	if (is_array($queries)) {
1497
		$query = implode(";", $queries);
1498
	} else {
1499
		$query = $queries;
1500
	}
1501

    
1502
	$DB = captiveportal_opendb();
1503
	if ($DB) {
1504
		$DB->exec("BEGIN TRANSACTION");
1505
		$result = $DB->exec($query);
1506
		if (!$result) {
1507
			captiveportal_syslog("Trying to modify DB returned error: {$DB->lastErrorMsg()}");
1508
		} else {
1509
			$DB->exec("END TRANSACTION");
1510
		}
1511
		$DB->close();
1512
		return $result;
1513
	} else {
1514
		return true;
1515
	}
1516
}
1517

    
1518
function captiveportal_write_elements() {
1519
	global $g, $config, $cpzone;
1520

    
1521
	$cpcfg = $config['captiveportal'][$cpzone];
1522

    
1523
	if (!is_dir($g['captiveportal_element_path'])) {
1524
		@mkdir($g['captiveportal_element_path']);
1525
	}
1526

    
1527
	if (is_array($cpcfg['element'])) {
1528
		conf_mount_rw();
1529
		foreach ($cpcfg['element'] as $data) {
1530
			if (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1531
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1532
				return 1;
1533
			}
1534
			if (!file_exists("{$g['captiveportal_path']}/{$data['name']}")) {
1535
				@symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1536
			}
1537
		}
1538
		conf_mount_ro();
1539
	}
1540

    
1541
	return 0;
1542
}
1543

    
1544
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1545
	global $cpzone;
1546

    
1547
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1548
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1549
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1550
		$ridx = $rulenos_start;
1551
		while ($ridx < $rulenos_range_max) {
1552
			if ($rules[$ridx] == $cpzone) {
1553
				$rules[$ridx] = false;
1554
				$ridx++;
1555
				$rules[$ridx] = false;
1556
				$ridx++;
1557
			} else {
1558
				$ridx += 2;
1559
			}
1560
		}
1561
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1562
		unset($rules);
1563
	}
1564
	unlock($cpruleslck);
1565
}
1566

    
1567
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1568
	global $config, $g, $cpzone;
1569

    
1570
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1571
	$ruleno = 0;
1572
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1573
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1574
		$ridx = $rulenos_start;
1575
		while ($ridx < $rulenos_range_max) {
1576
			if (empty($rules[$ridx])) {
1577
				$ruleno = $ridx;
1578
				$rules[$ridx] = $cpzone;
1579
				$ridx++;
1580
				$rules[$ridx] = $cpzone;
1581
				break;
1582
			} else {
1583
				$ridx += 2;
1584
			}
1585
		}
1586
	} else {
1587
		$rules = array_pad(array(), $rulenos_range_max, false);
1588
		$ruleno = $rulenos_start;
1589
		$rules[$rulenos_start] = $cpzone;
1590
		$rulenos_start++;
1591
		$rules[$rulenos_start] = $cpzone;
1592
	}
1593
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1594
	unlock($cpruleslck);
1595
	unset($rules);
1596

    
1597
	return $ruleno;
1598
}
1599

    
1600
function captiveportal_free_dn_ruleno($ruleno) {
1601
	global $config, $g;
1602

    
1603
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1604
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1605
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1606
		$rules[$ruleno] = false;
1607
		$ruleno++;
1608
		$rules[$ruleno] = false;
1609
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1610
		unset($rules);
1611
	}
1612
	unlock($cpruleslck);
1613
}
1614

    
1615
function captiveportal_get_dn_passthru_ruleno($value) {
1616
	global $config, $g, $cpzone, $cpzoneid;
1617

    
1618
	$cpcfg = $config['captiveportal'][$cpzone];
1619
	if (!isset($cpcfg['enable'])) {
1620
		return NULL;
1621
	}
1622

    
1623
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1624
	$ruleno = NULL;
1625
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1626
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1627
		unset($output);
1628
		$_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);
1629
		$ruleno = intval($output[0]);
1630
		if (!$rules[$ruleno]) {
1631
			$ruleno = NULL;
1632
		}
1633
		unset($rules);
1634
	}
1635
	unlock($cpruleslck);
1636

    
1637
	return $ruleno;
1638
}
1639

    
1640
/*
1641
 * This function will calculate the lowest free firewall ruleno
1642
 * within the range specified based on the actual logged on users
1643
 *
1644
 */
1645
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1646
	global $config, $g, $cpzone;
1647

    
1648
	$cpcfg = $config['captiveportal'][$cpzone];
1649
	if (!isset($cpcfg['enable'])) {
1650
		return NULL;
1651
	}
1652

    
1653
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1654
	$ruleno = 0;
1655
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1656
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1657
		$ridx = $rulenos_start;
1658
		while ($ridx < $rulenos_range_max) {
1659
			if (empty($rules[$ridx])) {
1660
				$ruleno = $ridx;
1661
				$rules[$ridx] = $cpzone;
1662
				$ridx++;
1663
				$rules[$ridx] = $cpzone;
1664
				break;
1665
			} else {
1666
				/*
1667
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno
1668
				 * and the out pipe ruleno + 1.
1669
				 */
1670
				$ridx += 2;
1671
			}
1672
		}
1673
	} else {
1674
		$rules = array_pad(array(), $rulenos_range_max, false);
1675
		$ruleno = $rulenos_start;
1676
		$rules[$rulenos_start] = $cpzone;
1677
		$rulenos_start++;
1678
		$rules[$rulenos_start] = $cpzone;
1679
	}
1680
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1681
	unlock($cpruleslck);
1682
	unset($rules);
1683

    
1684
	return $ruleno;
1685
}
1686

    
1687
function captiveportal_free_ipfw_ruleno($ruleno) {
1688
	global $config, $g, $cpzone;
1689

    
1690
	$cpcfg = $config['captiveportal'][$cpzone];
1691
	if (!isset($cpcfg['enable'])) {
1692
		return NULL;
1693
	}
1694

    
1695
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1696
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1697
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1698
		$rules[$ruleno] = false;
1699
		$ruleno++;
1700
		$rules[$ruleno] = false;
1701
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1702
		unset($rules);
1703
	}
1704
	unlock($cpruleslck);
1705
}
1706

    
1707
function captiveportal_get_ipfw_passthru_ruleno($value) {
1708
	global $config, $g, $cpzone, $cpzoneid;
1709

    
1710
	$cpcfg = $config['captiveportal'][$cpzone];
1711
	if (!isset($cpcfg['enable'])) {
1712
		return NULL;
1713
	}
1714

    
1715
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1716
	$ruleno = NULL;
1717
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1718
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1719
		unset($output);
1720
		$_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);
1721
		$ruleno = intval($output[0]);
1722
		if (!$rules[$ruleno]) {
1723
			$ruleno = NULL;
1724
		}
1725
		unset($rules);
1726
	}
1727
	unlock($cpruleslck);
1728

    
1729
	return $ruleno;
1730
}
1731

    
1732
/**
1733
 * This function will calculate the traffic produced by a client
1734
 * based on its firewall rule
1735
 *
1736
 * Point of view: NAS
1737
 *
1738
 * Input means: from the client
1739
 * Output means: to the client
1740
 *
1741
 */
1742

    
1743
function getVolume($ip, $mac = NULL) {
1744
	global $config, $cpzone, $cpzoneid;
1745

    
1746
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1747
	$volume = array();
1748
	// Initialize vars properly, since we don't want NULL vars
1749
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1750

    
1751
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 1, $ip, $mac);
1752
	if (is_array($ipfw)) {
1753
		if ($reverse) {
1754
			$volume['output_pkts'] = $ipfw['packets'];
1755
			$volume['output_bytes'] = $ipfw['bytes'];
1756
		}
1757
		else {
1758
			$volume['input_pkts'] = $ipfw['packets'];
1759
			$volume['input_bytes'] = $ipfw['bytes'];
1760
		}
1761
	}
1762

    
1763
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 2, $ip, $mac);
1764
	if (is_array($ipfw)) {
1765
		if ($reverse) {
1766
			$volume['input_pkts'] = $ipfw['packets'];
1767
			$volume['input_bytes'] = $ipfw['bytes'];
1768
		}
1769
		else {
1770
			$volume['output_pkts'] = $ipfw['packets'];
1771
			$volume['output_bytes'] = $ipfw['bytes'];
1772
		}
1773
	}
1774

    
1775
	return $volume;
1776
}
1777

    
1778
/**
1779
 * Get the NAS-IP-Address based on the current wan address
1780
 *
1781
 * Use functions in interfaces.inc to find this out
1782
 *
1783
 */
1784

    
1785
function getNasIP() {
1786
	global $config, $cpzone;
1787

    
1788
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1789
			$nasIp = get_interface_ip();
1790
	} else {
1791
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1792
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1793
		} else {
1794
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1795
		}
1796
	}
1797

    
1798
	if (!is_ipaddr($nasIp)) {
1799
		$nasIp = "0.0.0.0";
1800
	}
1801

    
1802
	return $nasIp;
1803
}
1804

    
1805
function portal_ip_from_client_ip($cliip) {
1806
	global $config, $cpzone;
1807

    
1808
	$isipv6 = is_ipaddrv6($cliip);
1809
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1810
	foreach ($interfaces as $cpif) {
1811
		if ($isipv6) {
1812
			$ip = get_interface_ipv6($cpif);
1813
			$sn = get_interface_subnetv6($cpif);
1814
		} else {
1815
			$ip = get_interface_ip($cpif);
1816
			$sn = get_interface_subnet($cpif);
1817
		}
1818
		if (ip_in_subnet($cliip, "{$ip}/{$sn}")) {
1819
			return $ip;
1820
		}
1821
	}
1822

    
1823
	$inet = ($isipv6) ? '-inet6' : '-inet';
1824
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1825
	$iface = trim($iface, "\n");
1826
	if (!empty($iface)) {
1827
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1828
		if (is_ipaddr($ip)) {
1829
			return $ip;
1830
		}
1831
	}
1832

    
1833
	// doesn't match up to any particular interface
1834
	// so let's set the portal IP to what PHP says
1835
	// the server IP issuing the request is.
1836
	// allows same behavior as 1.2.x where IP isn't
1837
	// in the subnet of any CP interface (static routes, etc.)
1838
	// rather than forcing to DNS hostname resolution
1839
	$ip = $_SERVER['SERVER_ADDR'];
1840
	if (is_ipaddr($ip)) {
1841
		return $ip;
1842
	}
1843

    
1844
	return false;
1845
}
1846

    
1847
function portal_hostname_from_client_ip($cliip) {
1848
	global $config, $cpzone;
1849

    
1850
	$cpcfg = $config['captiveportal'][$cpzone];
1851

    
1852
	if (isset($cpcfg['httpslogin'])) {
1853
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 8001);
1854
		$ourhostname = $cpcfg['httpsname'];
1855

    
1856
		if ($listenporthttps != 443) {
1857
			$ourhostname .= ":" . $listenporthttps;
1858
		}
1859
	} else {
1860
		$listenporthttp = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : ($cpcfg['zoneid'] + 8000);
1861
		$ifip = portal_ip_from_client_ip($cliip);
1862
		if (!$ifip) {
1863
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1864
		} else {
1865
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1866
		}
1867

    
1868
		if ($listenporthttp != 80) {
1869
			$ourhostname .= ":" . $listenporthttp;
1870
		}
1871
	}
1872

    
1873
	return $ourhostname;
1874
}
1875

    
1876
/* functions move from index.php */
1877

    
1878
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1879
	global $g, $config, $cpzone;
1880

    
1881
	/* Get captive portal layout */
1882
	if ($type == "redir") {
1883
		header("Location: {$redirurl}");
1884
		return;
1885
	} else if ($type == "login") {
1886
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1887
	} else {
1888
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1889
	}
1890

    
1891
	$cpcfg = $config['captiveportal'][$cpzone];
1892

    
1893
	/* substitute the PORTAL_REDIRURL variable */
1894
	if ($cpcfg['preauthurl']) {
1895
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1896
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1897
	}
1898

    
1899
	/* substitute other variables */
1900
	$ourhostname = portal_hostname_from_client_ip($clientip);
1901
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1902
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1903
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1904

    
1905
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1906
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1907
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1908
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1909
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1910

    
1911
	// Special handling case for captive portal master page so that it can be ran
1912
	// through the PHP interpreter using the include method above.  We convert the
1913
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1914
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1915
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1916
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1917
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1918
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1919
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1920
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1921

    
1922
	echo $htmltext;
1923
}
1924

    
1925
function portal_mac_radius($clientmac, $clientip) {
1926
	global $config, $cpzone;
1927

    
1928
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1929

    
1930
	/* authentication against the radius server */
1931
	$username = mac_format($clientmac);
1932
	$auth_list = radius($username, $radmac_secret, $clientip, $clientmac, "MACHINE LOGIN");
1933
	if ($auth_list['auth_val'] == 2) {
1934
		return TRUE;
1935
	}
1936

    
1937
	if (!empty($auth_list['url_redirection'])) {
1938
		portal_reply_page($auth_list['url_redirection'], "redir");
1939
	}
1940

    
1941
	return FALSE;
1942
}
1943

    
1944
function captiveportal_reapply_attributes($cpentry, $attributes) {
1945
	global $config, $cpzone, $g;
1946

    
1947
	if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
1948
		$dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1949
		$dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1950
	} else {
1951
		$dwfaultbw_up = $dwfaultbw_down = 0;
1952
	}
1953
	$bw_up = !empty($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1954
	$bw_down = !empty($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1955
	$bw_up_pipeno = $cpentry[1];
1956
	$bw_down_pipeno = $cpentry[1]+1;
1957

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

    
1962
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1963
}
1964

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

    
1968
	// Ensure we create an array if we are missing attributes
1969
	if (!is_array($attributes)) {
1970
		$attributes = array();
1971
	}
1972

    
1973
	unset($sessionid);
1974

    
1975
	/* Do not allow concurrent login execution. */
1976
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1977

    
1978
	if ($attributes['voucher']) {
1979
		$remaining_time = $attributes['session_timeout'];
1980
	}
1981

    
1982
	$writecfg = false;
1983
	/* Find an existing session */
1984
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
1985
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1986
			$mac = captiveportal_passthrumac_findbyname($username);
1987
			if (!empty($mac)) {
1988
				if ($_POST['replacemacpassthru']) {
1989
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
1990
						if ($macent['mac'] == $mac['mac']) {
1991
							$macrules = "";
1992
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1993
							$pipeno = captiveportal_get_dn_passthru_ruleno($mac['mac']);
1994
							if ($ruleno) {
1995
								captiveportal_free_ipfw_ruleno($ruleno);
1996
								$macrules .= "delete {$ruleno}\n";
1997
								++$ruleno;
1998
								$macrules .= "delete {$ruleno}\n";
1999
							}
2000
							if ($pipeno) {
2001
								captiveportal_free_dn_ruleno($pipeno);
2002
								$macrules .= "pipe delete {$pipeno}\n";
2003
								++$pipeno;
2004
								$macrules .= "pipe delete {$pipeno}\n";
2005
							}
2006
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
2007
							$mac['action'] = 'pass';
2008
							$mac['mac'] = $clientmac;
2009
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
2010
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
2011
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
2012
							mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
2013
							$writecfg = true;
2014
							$sessionid = true;
2015
							break;
2016
						}
2017
					}
2018
				} else {
2019
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
2020
						$clientmac, $clientip, $username, $password);
2021
					unlock($cpdblck);
2022
					return;
2023
				}
2024
			}
2025
		}
2026
	}
2027

    
2028
	/* read in client database */
2029
	$query = "WHERE ip = '{$clientip}'";
2030
	$tmpusername = strtolower($username);
2031
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) {
2032
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
2033
	}
2034
	$cpdb = captiveportal_read_db($query);
2035

    
2036
	/* Snapshot the timestamp */
2037
	$allow_time = time();
2038
	$radiusservers = captiveportal_get_radius_servers();
2039
	$unsetindexes = array();
2040
	if (is_null($radiusctx)) {
2041
		$radiusctx = 'first';
2042
	}
2043

    
2044
	foreach ($cpdb as $cpentry) {
2045
		if (empty($cpentry[11])) {
2046
			$cpentry[11] = 'first';
2047
		}
2048
		/* on the same ip */
2049
		if ($cpentry[2] == $clientip) {
2050
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac) {
2051
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - REUSING OLD SESSION");
2052
			} else {
2053
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
2054
			}
2055
			$sessionid = $cpentry[5];
2056
			break;
2057
		} elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
2058
			// user logged in with an active voucher. Check for how long and calculate
2059
			// how much time we can give him (voucher credit - used time)
2060
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
2061
			if ($remaining_time < 0) { // just in case.
2062
				$remaining_time = 0;
2063
			}
2064

    
2065
			/* This user was already logged in so we disconnect the old one */
2066
			captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], 13);
2067
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - TERMINATING OLD SESSION");
2068
			$unsetindexes[] = $cpentry[5];
2069
			break;
2070
		} elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
2071
			/* on the same username */
2072
			if (strcasecmp($cpentry[4], $username) == 0) {
2073
				/* This user was already logged in so we disconnect the old one */
2074
				captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], 13);
2075
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - TERMINATING OLD SESSION");
2076
				$unsetindexes[] = $cpentry[5];
2077
				break;
2078
			}
2079
		}
2080
	}
2081
	unset($cpdb);
2082

    
2083
	if (!empty($unsetindexes)) {
2084
		captiveportal_remove_entries($unsetindexes);
2085
	}
2086

    
2087
	if ($attributes['voucher'] && $remaining_time <= 0) {
2088
		return 0;       // voucher already used and no time left
2089
	}
2090

    
2091
	if (!isset($sessionid)) {
2092
		/* generate unique session ID */
2093
		$tod = gettimeofday();
2094
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
2095

    
2096
		if ($passthrumac) {
2097
			$mac = array();
2098
			$mac['action'] = 'pass';
2099
			$mac['mac'] = $clientmac;
2100
			$mac['ip'] = $clientip; /* Used only for logging */
2101
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
2102
				$mac['username'] = $username;
2103
				if ($attributes['voucher']) {
2104
					$mac['logintype'] = "voucher";
2105
				}
2106
			}
2107
			if ($username == "unauthenticated") {
2108
				$mac['descr'] = "Auto-added";
2109
			} else {
2110
				$mac['descr'] = "Auto-added for user {$username}";
2111
			}
2112
			if (!empty($bw_up)) {
2113
				$mac['bw_up'] = $bw_up;
2114
			}
2115
			if (!empty($bw_down)) {
2116
				$mac['bw_down'] = $bw_down;
2117
			}
2118
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
2119
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
2120
			}
2121
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
2122
			unlock($cpdblck);
2123
			$macrules = captiveportal_passthrumac_configure_entry($mac);
2124
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
2125
			mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
2126
			$writecfg = true;
2127
		} else {
2128
			/* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
2129
			if (is_null($pipeno)) {
2130
				$pipeno = captiveportal_get_next_dn_ruleno();
2131
			}
2132

    
2133
			/* if the pool is empty, return appropriate message and exit */
2134
			if (is_null($pipeno)) {
2135
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
2136
				log_error("Zone: {$cpzone} - WARNING!  Captive portal has reached maximum login capacity");
2137
				unlock($cpdblck);
2138
				return;
2139
			}
2140

    
2141
			if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
2142
				$dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
2143
				$dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
2144
			} else {
2145
				$dwfaultbw_up = $dwfaultbw_down = 0;
2146
			}
2147
			$bw_up = !empty($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
2148
			$bw_down = !empty($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
2149

    
2150
			$bw_up_pipeno = $pipeno;
2151
			$bw_down_pipeno = $pipeno + 1;
2152
			//$bw_up /= 1000; // Scale to Kbit/s
2153
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
2154
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
2155

    
2156
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
2157
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
2158
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
2159
			} else {
2160
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
2161
			}
2162

    
2163
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
2164
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
2165
			} else {
2166
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
2167
			}
2168

    
2169
			if ($attributes['voucher']) {
2170
				$attributes['session_timeout'] = $remaining_time;
2171
			}
2172

    
2173
			/* handle empty attributes */
2174
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
2175
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
2176
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
2177
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
2178

    
2179
			/* escape username */
2180
			$safe_username = SQLite3::escapeString($username);
2181

    
2182
			/* encode password in Base64 just in case it contains commas */
2183
			$bpassword = base64_encode($password);
2184
			$insertquery = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, radiusctx) ";
2185
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
2186
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, '{$radiusctx}')";
2187

    
2188
			/* store information to database */
2189
			captiveportal_write_db($insertquery);
2190
			unlock($cpdblck);
2191
			unset($insertquery, $bpassword);
2192

    
2193
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
2194
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
2195
				if ($acct_val == 1) {
2196
					captiveportal_logportalauth($username, $clientmac, $clientip, $type, "RADIUS ACCOUNTING FAILED");
2197
				}
2198
			}
2199
		}
2200
	} else {
2201
		/* NOTE: #3062-11 If the pipeno has been allocated free it to not DoS the CP and maintain proper operation as in radius() case */
2202
		if (!is_null($pipeno)) {
2203
			captiveportal_free_dn_ruleno($pipeno);
2204
		}
2205

    
2206
		unlock($cpdblck);
2207
	}
2208

    
2209
	if ($writecfg == true) {
2210
		write_config();
2211
	}
2212

    
2213
	/* redirect user to desired destination */
2214
	if (!empty($attributes['url_redirection'])) {
2215
		$my_redirurl = $attributes['url_redirection'];
2216
	} else if (!empty($redirurl)) {
2217
		$my_redirurl = $redirurl;
2218
	} else if (!empty($config['captiveportal'][$cpzone]['redirurl'])) {
2219
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
2220
	}
2221

    
2222
	if (isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
2223
		$ourhostname = portal_hostname_from_client_ip($clientip);
2224
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
2225
		$logouturl = "{$protocol}{$ourhostname}/";
2226

    
2227
		if (isset($attributes['reply_message'])) {
2228
			$message = $attributes['reply_message'];
2229
		} else {
2230
			$message = 0;
2231
		}
2232

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

    
2235
	} else {
2236
		portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
2237
	}
2238

    
2239
	return $sessionid;
2240
}
2241

    
2242

    
2243
/*
2244
 * Used for when pass-through credits are enabled.
2245
 * Returns true when there was at least one free login to deduct for the MAC.
2246
 * Expired entries are removed as they are seen.
2247
 * Active entries are updated according to the configuration.
2248
 */
2249
function portal_consume_passthrough_credit($clientmac) {
2250
	global $config, $cpzone;
2251

    
2252
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count'])) {
2253
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
2254
	} else {
2255
		return false;
2256
	}
2257

    
2258
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout'])) {
2259
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2260
	} else {
2261
		return false;
2262
	}
2263

    
2264
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac) {
2265
		return false;
2266
	}
2267

    
2268
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2269

    
2270
	/*
2271
	 * Read database of used MACs.  Lines are a comma-separated list
2272
	 * of the time, MAC, then the count of pass-through credits remaining.
2273
	 */
2274
	$usedmacs = captiveportal_read_usedmacs_db();
2275

    
2276
	$currenttime = time();
2277
	$found = false;
2278
	foreach ($usedmacs as $key => $usedmac) {
2279
		$usedmac = explode(",", $usedmac);
2280

    
2281
		if ($usedmac[1] == $clientmac) {
2282
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2283
				if ($usedmac[2] < 1) {
2284
					if ($updatetimeouts) {
2285
						$usedmac[0] = $currenttime;
2286
						unset($usedmacs[$key]);
2287
						$usedmacs[] = implode(",", $usedmac);
2288
						captiveportal_write_usedmacs_db($usedmacs);
2289
					}
2290

    
2291
					return false;
2292
				} else {
2293
					$usedmac[2] -= 1;
2294
					$usedmacs[$key] = implode(",", $usedmac);
2295
				}
2296

    
2297
				$found = true;
2298
			} else {
2299
				unset($usedmacs[$key]);
2300
			}
2301

    
2302
			break;
2303
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime) {
2304
			unset($usedmacs[$key]);
2305
		}
2306
	}
2307

    
2308
	if (!$found) {
2309
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2310
		$usedmacs[] = implode(",", $usedmac);
2311
	}
2312

    
2313
	captiveportal_write_usedmacs_db($usedmacs);
2314
	return true;
2315
}
2316

    
2317
function captiveportal_read_usedmacs_db() {
2318
	global $g, $cpzone;
2319

    
2320
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2321
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2322
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2323
		if (!$usedmacs) {
2324
			$usedmacs = array();
2325
		}
2326
	} else {
2327
		$usedmacs = array();
2328
	}
2329

    
2330
	unlock($cpumaclck);
2331
	return $usedmacs;
2332
}
2333

    
2334
function captiveportal_write_usedmacs_db($usedmacs) {
2335
	global $g, $cpzone;
2336

    
2337
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2338
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2339
	unlock($cpumaclck);
2340
}
2341

    
2342
function captiveportal_blocked_mac($mac) {
2343
	global $config, $g, $cpzone;
2344

    
2345
	if (empty($mac) || !is_macaddr($mac)) {
2346
		return false;
2347
	}
2348

    
2349
	if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
2350
		return false;
2351
	}
2352

    
2353
	foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac) {
2354
		if (($passthrumac['action'] == 'block') &&
2355
		    ($passthrumac['mac'] == strtolower($mac))) {
2356
			return true;
2357
		}
2358
	}
2359

    
2360
	return false;
2361

    
2362
}
2363

    
2364
function captiveportal_send_server_accounting($off = false) {
2365
	global $cpzone, $config;
2366

    
2367
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
2368
		return;
2369
	}
2370
	if ($off) {
2371
		$racct = new Auth_RADIUS_Acct_Off;
2372
	} else {
2373
		$racct = new Auth_RADIUS_Acct_On;
2374
	}
2375
	$radiusservers = captiveportal_get_radius_servers();
2376
	if (empty($radiusservers)) {
2377
		return;
2378
	}
2379
	foreach ($radiusservers['first'] as $radsrv) {
2380
		// Add a new server to our instance
2381
		$racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
2382
	}
2383
	if (PEAR::isError($racct->start())) {
2384
		$retvalue['acct_val'] = 1;
2385
		$retvalue['error'] = $racct->getMessage();
2386

    
2387
		// If we encounter an error immediately stop this function and go back
2388
		$racct->close();
2389
		return $retvalue;
2390
	}
2391
	// Send request
2392
	$result = $racct->send();
2393
	// Evaluation of the response
2394
	// 5 -> Accounting-Response
2395
	// See RFC2866 for this.
2396
	if (PEAR::isError($result)) {
2397
		$retvalue['acct_val'] = 1;
2398
		$retvalue['error'] = $result->getMessage();
2399
	} else if ($result === true) {
2400
		$retvalue['acct_val'] = 5 ;
2401
	} else {
2402
		$retvalue['acct_val'] = 1 ;
2403
	}
2404

    
2405
	$racct->close();
2406
	return $retvalue;
2407
}
2408

    
2409
function captiveportal_isip_logged($clientip) {
2410
	global $g, $cpzone;
2411

    
2412
	/* read in client database */
2413
	$query = "WHERE ip = '{$clientip}'";
2414
	$cpdb = captiveportal_read_db($query);
2415
	foreach ($cpdb as $cpentry) {
2416
		return $cpentry;
2417
	}
2418
}
2419
?>
(7-7/68)