Project

General

Profile

Download (77.7 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']['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
	foreach ($cpips as $cpip)
549
		$cprules .= "table 100 add {$cpip}\n";
550
	$cprules .= "add {$rulenum} pass ip from any to 255.255.255.255 in\n";
551
	$rulenum++;
552
	$cprules .= "add {$rulenum} pass ip from 255.255.255.255 to any out\n";
553
	$rulenum++;
554

    
555
	/* Allowed ips */
556
	$cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any in\n";
557
	$rulenum++;
558
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) in\n";
559
	$rulenum++;
560
	$cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any out\n";
561
	$rulenum++;
562
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) out\n";
563
	$rulenum++;
564

    
565
	/* Authenticated users rules. */
566
	$cprules .= "add {$rulenum} pipe tablearg ip from table(1) to any in\n";
567
	$rulenum++;
568
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(2) out\n";
569
	$rulenum++;
570

    
571
	if (!empty($config['captiveportal'][$cpzone]['listenporthttp'])) {
572
		$listenporthttp = $config['captiveportal'][$cpzone]['listenporthttp'];
573
	} else {
574
		$listenporthttp = 8000 + $cpzoneid;
575
	}
576

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

    
588
	$cprules .= <<<EOD
589

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

    
597
EOD;
598

    
599
	/* generate passthru mac database */
600
	$cprules .= captiveportal_passthrumac_configure(true);
601
	$cprules .= "\n";
602

    
603
	/* allowed ipfw rules to make allowed ip work */
604
	$cprules .= captiveportal_allowedip_configure();
605

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

    
609
	/* load rules */
610
	file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
611
	mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
612
	//@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
613
	unset($cprules);
614

    
615
	if ($reinit == false) {
616
		unlock($captiveportallck);
617
	}
618
}
619

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

    
629
	if (empty($cpzone)) {
630
		return;
631
	}
632

    
633
	$cpcfg = $config['captiveportal'][$cpzone];
634
	$vcpcfg = $config['voucher'][$cpzone];
635

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

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

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

    
653
	$radiussrvs = captiveportal_get_radius_servers();
654

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

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

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

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

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

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

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

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

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

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

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

    
798
	captiveportal_prune_old_automac();
799

    
800
	if ($voucher_needs_sync == true) {
801
		/* Trigger a sync of the vouchers on config */
802
		send_event("service sync vouchers");
803
	}
804

    
805
	/* write database */
806
	if (!empty($unsetindexes)) {
807
		captiveportal_remove_entries($unsetindexes);
808
	}
809
}
810

    
811
function captiveportal_prune_old_automac() {
812
	global $g, $config, $cpzone, $cpzoneid;
813

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

    
876
/* remove a single client according to the DB entry */
877
function captiveportal_disconnect($dbent, $radiusservers, $term_cause = 1, $stop_time = null) {
878
	global $g, $config, $cpzone, $cpzoneid;
879

    
880
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
881

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

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

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

    
914
		/* Release the ruleno so it can be reallocated to new clients. */
915
		captiveportal_free_dn_ruleno($dbent[1]);
916
	}
917

    
918
	// XMLRPC Call over to the master Voucher node
919
	if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
920
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
921
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
922
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
923
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
924
		$remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time);
925
	}
926

    
927
}
928

    
929
/* remove a single client by sessionid */
930
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
931
	global $g, $config;
932

    
933
	$sessionid = SQLite3::escapeString($sessionid);
934
	$radiusservers = captiveportal_get_radius_servers();
935

    
936
	/* read database */
937
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
938

    
939
	/* find entry */
940
	if (!empty($result)) {
941
		captiveportal_write_db("DELETE FROM captiveportal WHERE sessionid = '{$sessionid}'");
942

    
943
		foreach ($result as $cpentry) {
944
			if (empty($cpentry[11])) {
945
				$cpentry[11] = 'first';
946
			}
947
			captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], $term_cause);
948
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
949
		}
950
		unset($result);
951
	}
952
}
953

    
954
/* send RADIUS acct stop for all current clients */
955
function captiveportal_radius_stop_all() {
956
	global $config, $cpzone;
957

    
958
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
959
		return;
960
	}
961

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

    
983
function captiveportal_passthrumac_configure_entry($macent, $pipeinrule = false) {
984
	global $config, $g, $cpzone;
985

    
986
	$bwUp = 0;
987
	if (!empty($macent['bw_up'])) {
988
		$bwUp = $macent['bw_up'];
989
	} else if (!empty($config['captiveportal'][$cpzone]['bwdefaultup'])) {
990
		$bwUp = $config['captiveportal'][$cpzone]['bwdefaultup'];
991
	}
992
	$bwDown = 0;
993
	if (!empty($macent['bw_down'])) {
994
		$bwDown = $macent['bw_down'];
995
	} else if (!empty($config['captiveportal'][$cpzone]['bwdefaultdn'])) {
996
		$bwDown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
997
	}
998

    
999
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1000

    
1001
	if ($macent['action'] == 'pass') {
1002
		$rules = "";
1003
		$pipeno = captiveportal_get_next_dn_ruleno();
1004

    
1005
		$pipeup = $pipeno;
1006
		if ($pipeinrule == true) {
1007
			$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16");
1008
		} else {
1009
			$rules .= "pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16\n";
1010
		}
1011

    
1012
		$pipedown = $pipeno + 1;
1013
		if ($pipeinrule == true) {
1014
			$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
1015
		} else {
1016
			$rules .= "pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16\n";
1017
		}
1018

    
1019
		$rules .= "add {$ruleno} pipe {$pipeup} ip from any to any MAC any {$macent['mac']}\n";
1020
		$ruleno++;
1021
		$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC {$macent['mac']} any\n";
1022
	}
1023

    
1024
	return $rules;
1025
}
1026

    
1027
function captiveportal_passthrumac_delete_entry($macent) {
1028
	$rules = "";
1029

    
1030
	if ($macent['action'] == 'pass') {
1031
		$ruleno = captiveportal_get_ipfw_passthru_ruleno($macent['mac']);
1032

    
1033
		if (!$ruleno) {
1034
			return $rules;
1035
		}
1036

    
1037
		captiveportal_free_ipfw_ruleno($ruleno);
1038

    
1039
		$rules .= "delete {$ruleno}\n";
1040
		$rules .= "delete " . ++$ruleno . "\n";
1041

    
1042
		$pipeno = captiveportal_get_dn_passthru_ruleno($macent['mac']);
1043

    
1044
		if (!empty($pipeno)) {
1045
			captiveportal_free_dn_ruleno($pipeno);
1046
			$rules .= "pipe delete " . $pipeno . "\n";
1047
			$rules .= "pipe delete " . ++$pipeno . "\n";
1048
		}
1049
	}
1050

    
1051
	return $rules;
1052
}
1053

    
1054
function captiveportal_passthrumac_configure($filename = false, $startindex = 0, $stopindex = 0) {
1055
	global $config, $g, $cpzone;
1056

    
1057
	$rules = "";
1058

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

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

    
1090
	return $rules;
1091
}
1092

    
1093
function captiveportal_passthrumac_findbyname($username) {
1094
	global $config, $cpzone;
1095

    
1096
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1097
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1098
			if ($macent['username'] == $username) {
1099
				return $macent;
1100
			}
1101
		}
1102
	}
1103
	return NULL;
1104
}
1105

    
1106
/*
1107
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1108
 */
1109
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
1110
	global $g;
1111

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

    
1128
	$rules = "";
1129
	$cp_filterdns_conf = "";
1130
	$enBwup = 0;
1131
	if (!empty($ipent['bw_up'])) {
1132
		$enBwup = intval($ipent['bw_up']);
1133
	} else if (!empty($config['captiveportal'][$cpzone]['bwdefaultup'])) {
1134
		$enBwup = $config['captiveportal'][$cpzone]['bwdefaultup'];
1135
	}
1136
	$enBwdown = 0;
1137
	if (!empty($ipent['bw_down'])) {
1138
		$enBwdown = intval($ipent['bw_down']);
1139
	} else if (!empty($config['captiveportal'][$cpzone]['bwdefaultdn'])) {
1140
		$enBwdown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
1141
	}
1142

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

    
1161
	if ($ishostname === true) {
1162
		return array($rules, $cp_filterdns_conf);
1163
	} else {
1164
		return $rules;
1165
	}
1166
}
1167

    
1168
function captiveportal_allowedhostname_configure() {
1169
	global $config, $g, $cpzone, $cpzoneid;
1170

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

    
1193
	return $rules;
1194
}
1195

    
1196
function captiveportal_allowedip_configure() {
1197
	global $config, $g, $cpzone;
1198

    
1199
	$rules = "";
1200
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1201
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) {
1202
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1203
		}
1204
	}
1205

    
1206
	return $rules;
1207
}
1208

    
1209
/* get last activity timestamp given client IP address */
1210
function captiveportal_get_last_activity($ip, $mac = NULL, $table = 1) {
1211
	global $cpzoneid;
1212

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

    
1224
	return 0;
1225
}
1226

    
1227
function captiveportal_init_radius_servers() {
1228
	global $config, $g, $cpzone;
1229

    
1230
	/* generate radius server database */
1231
	if ($config['captiveportal'][$cpzone]['radiusip'] &&
1232
	    (!isset($config['captiveportal'][$cpzone]['auth_method']) || $config['captiveportal'][$cpzone]['auth_method'] == "radius")) {
1233
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1234
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1235
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1236
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1237

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

    
1264
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1265
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1266
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1267
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1268

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

    
1289
		fclose($fd);
1290
		unlock($cprdsrvlck);
1291
	}
1292
}
1293

    
1294
/* read RADIUS servers into array */
1295
function captiveportal_get_radius_servers() {
1296
	global $g, $cpzone;
1297

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

    
1327
	unlock($cprdsrvlck);
1328
	return false;
1329
}
1330

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

    
1344
/* log simple messages to syslog */
1345
function captiveportal_syslog($message) {
1346
	global $cpzone;
1347

    
1348
	$message = trim($message);
1349
	$message = "Zone: {$cpzone} - {$message}";
1350
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1351
	// Log it
1352
	syslog(LOG_INFO, $message);
1353
	closelog();
1354
}
1355

    
1356
function radius($username, $password, $clientip, $clientmac, $type, $radiusctx = null) {
1357
	global $g, $config, $cpzoneid;
1358

    
1359
	$pipeno = captiveportal_get_next_dn_ruleno();
1360

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

    
1369
	$radiusservers = captiveportal_get_radius_servers();
1370

    
1371
	if (is_null($radiusctx)) {
1372
		$radiusctx = 'first';
1373
	}
1374

    
1375
	$auth_list = RADIUS_AUTHENTICATION($username,
1376
		$password,
1377
		$radiusservers[$radiusctx],
1378
		$clientip,
1379
		$clientmac,
1380
		$pipeno);
1381

    
1382
	if ($auth_list['auth_val'] == 2) {
1383
		captiveportal_logportalauth($username, $clientmac, $clientip, $type);
1384
		$sessionid = portal_allow($clientip,
1385
			$clientmac,
1386
			$username,
1387
			$password,
1388
			$auth_list,
1389
			$pipeno,
1390
			$radiusctx);
1391
	} else {
1392
		captiveportal_free_dn_ruleno($pipeno);
1393
	}
1394

    
1395
	return $auth_list;
1396
}
1397

    
1398
function captiveportal_opendb() {
1399
	global $g, $cpzone;
1400

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

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

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

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

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

    
1449
	return $DB;
1450
}
1451

    
1452
/* read captive portal DB into array */
1453
function captiveportal_read_db($query = "") {
1454
	$cpdb = array();
1455

    
1456
	$DB = captiveportal_opendb();
1457
	if ($DB) {
1458
		$response = $DB->query("SELECT * FROM captiveportal {$query}");
1459
		if ($response != FALSE) {
1460
			while ($row = $response->fetchArray()) {
1461
				$cpdb[] = $row;
1462
			}
1463
		}
1464
		$DB->close();
1465
	}
1466

    
1467
	return $cpdb;
1468
}
1469

    
1470
function captiveportal_remove_entries($remove) {
1471

    
1472
	if (!is_array($remove) || empty($remove)) {
1473
		return;
1474
	}
1475

    
1476
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1477
	foreach ($remove as $idx => $unindex) {
1478
		$query .= "'{$unindex}'";
1479
		if ($idx < (count($remove) - 1)) {
1480
			$query .= ",";
1481
		}
1482
	}
1483
	$query .= ")";
1484
	captiveportal_write_db($query);
1485
}
1486

    
1487
/* write captive portal DB */
1488
function captiveportal_write_db($queries) {
1489
	global $g;
1490

    
1491
	if (is_array($queries)) {
1492
		$query = implode(";", $queries);
1493
	} else {
1494
		$query = $queries;
1495
	}
1496

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

    
1513
function captiveportal_write_elements() {
1514
	global $g, $config, $cpzone;
1515

    
1516
	$cpcfg = $config['captiveportal'][$cpzone];
1517

    
1518
	if (!is_dir($g['captiveportal_element_path'])) {
1519
		@mkdir($g['captiveportal_element_path']);
1520
	}
1521

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

    
1536
	return 0;
1537
}
1538

    
1539
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1540
	global $cpzone;
1541

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

    
1562
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1563
	global $config, $g, $cpzone;
1564

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

    
1592
	return $ruleno;
1593
}
1594

    
1595
function captiveportal_free_dn_ruleno($ruleno) {
1596
	global $config, $g;
1597

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

    
1610
function captiveportal_get_dn_passthru_ruleno($value) {
1611
	global $config, $g, $cpzone, $cpzoneid;
1612

    
1613
	$cpcfg = $config['captiveportal'][$cpzone];
1614
	if (!isset($cpcfg['enable'])) {
1615
		return NULL;
1616
	}
1617

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

    
1632
	return $ruleno;
1633
}
1634

    
1635
/*
1636
 * This function will calculate the lowest free firewall ruleno
1637
 * within the range specified based on the actual logged on users
1638
 *
1639
 */
1640
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1641
	global $config, $g, $cpzone;
1642

    
1643
	$cpcfg = $config['captiveportal'][$cpzone];
1644
	if (!isset($cpcfg['enable'])) {
1645
		return NULL;
1646
	}
1647

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

    
1679
	return $ruleno;
1680
}
1681

    
1682
function captiveportal_free_ipfw_ruleno($ruleno) {
1683
	global $config, $g, $cpzone;
1684

    
1685
	$cpcfg = $config['captiveportal'][$cpzone];
1686
	if (!isset($cpcfg['enable'])) {
1687
		return NULL;
1688
	}
1689

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

    
1702
function captiveportal_get_ipfw_passthru_ruleno($value) {
1703
	global $config, $g, $cpzone, $cpzoneid;
1704

    
1705
	$cpcfg = $config['captiveportal'][$cpzone];
1706
	if (!isset($cpcfg['enable'])) {
1707
		return NULL;
1708
	}
1709

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

    
1724
	return $ruleno;
1725
}
1726

    
1727
/**
1728
 * This function will calculate the traffic produced by a client
1729
 * based on its firewall rule
1730
 *
1731
 * Point of view: NAS
1732
 *
1733
 * Input means: from the client
1734
 * Output means: to the client
1735
 *
1736
 */
1737

    
1738
function getVolume($ip, $mac = NULL) {
1739
	global $config, $cpzone, $cpzoneid;
1740

    
1741
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1742
	$volume = array();
1743
	// Initialize vars properly, since we don't want NULL vars
1744
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1745

    
1746
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 1, $ip, $mac);
1747
	if (is_array($ipfw)) {
1748
		if ($reverse) {
1749
			$volume['output_pkts'] = $ipfw['packets'];
1750
			$volume['output_bytes'] = $ipfw['bytes'];
1751
		}
1752
		else {
1753
			$volume['input_pkts'] = $ipfw['packets'];
1754
			$volume['input_bytes'] = $ipfw['bytes'];
1755
		}
1756
	}
1757

    
1758
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 2, $ip, $mac);
1759
	if (is_array($ipfw)) {
1760
		if ($reverse) {
1761
			$volume['input_pkts'] = $ipfw['packets'];
1762
			$volume['input_bytes'] = $ipfw['bytes'];
1763
		}
1764
		else {
1765
			$volume['output_pkts'] = $ipfw['packets'];
1766
			$volume['output_bytes'] = $ipfw['bytes'];
1767
		}
1768
	}
1769

    
1770
	return $volume;
1771
}
1772

    
1773
/**
1774
 * Get the NAS-IP-Address based on the current wan address
1775
 *
1776
 * Use functions in interfaces.inc to find this out
1777
 *
1778
 */
1779

    
1780
function getNasIP() {
1781
	global $config, $cpzone;
1782

    
1783
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1784
			$nasIp = get_interface_ip();
1785
	} else {
1786
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1787
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1788
		} else {
1789
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1790
		}
1791
	}
1792

    
1793
	if (!is_ipaddr($nasIp)) {
1794
		$nasIp = "0.0.0.0";
1795
	}
1796

    
1797
	return $nasIp;
1798
}
1799

    
1800
function portal_ip_from_client_ip($cliip) {
1801
	global $config, $cpzone;
1802

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

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

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

    
1839
	return false;
1840
}
1841

    
1842
function portal_hostname_from_client_ip($cliip) {
1843
	global $config, $cpzone;
1844

    
1845
	$cpcfg = $config['captiveportal'][$cpzone];
1846

    
1847
	if (isset($cpcfg['httpslogin'])) {
1848
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 8001);
1849
		$ourhostname = $cpcfg['httpsname'];
1850

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

    
1863
		if ($listenporthttp != 80) {
1864
			$ourhostname .= ":" . $listenporthttp;
1865
		}
1866
	}
1867

    
1868
	return $ourhostname;
1869
}
1870

    
1871
/* functions move from index.php */
1872

    
1873
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1874
	global $g, $config, $cpzone;
1875

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

    
1886
	$cpcfg = $config['captiveportal'][$cpzone];
1887

    
1888
	/* substitute the PORTAL_REDIRURL variable */
1889
	if ($cpcfg['preauthurl']) {
1890
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1891
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1892
	}
1893

    
1894
	/* substitute other variables */
1895
	$ourhostname = portal_hostname_from_client_ip($clientip);
1896
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1897
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1898
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1899

    
1900
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1901
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1902
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1903
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1904
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1905

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

    
1917
	echo $htmltext;
1918
}
1919

    
1920
function portal_mac_radius($clientmac, $clientip) {
1921
	global $config, $cpzone;
1922

    
1923
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1924

    
1925
	/* authentication against the radius server */
1926
	$username = mac_format($clientmac);
1927
	$auth_list = radius($username, $radmac_secret, $clientip, $clientmac, "MACHINE LOGIN");
1928
	if ($auth_list['auth_val'] == 2) {
1929
		return TRUE;
1930
	}
1931

    
1932
	if (!empty($auth_list['url_redirection'])) {
1933
		portal_reply_page($auth_list['url_redirection'], "redir");
1934
	}
1935

    
1936
	return FALSE;
1937
}
1938

    
1939
function captiveportal_reapply_attributes($cpentry, $attributes) {
1940
	global $config, $cpzone, $g;
1941

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

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

    
1957
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1958
}
1959

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

    
1963
	// Ensure we create an array if we are missing attributes
1964
	if (!is_array($attributes)) {
1965
		$attributes = array();
1966
	}
1967

    
1968
	unset($sessionid);
1969

    
1970
	/* Do not allow concurrent login execution. */
1971
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1972

    
1973
	if ($attributes['voucher']) {
1974
		$remaining_time = $attributes['session_timeout'];
1975
	// Set RADIUS-Attribute to Voucher to prevent ReAuth-Reqeuest for Vouchers Bug: #2155
1976
		$radiusctx="voucher";
1977
	}
1978

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

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

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

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

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

    
2080
	if (!empty($unsetindexes)) {
2081
		captiveportal_remove_entries($unsetindexes);
2082
	}
2083

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

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

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

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

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

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

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

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

    
2166
			if ($attributes['voucher']) {
2167
				$attributes['session_timeout'] = $remaining_time;
2168
			}
2169

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

    
2176
			/* escape username */
2177
			$safe_username = SQLite3::escapeString($username);
2178

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

    
2185
			/* store information to database */
2186
			captiveportal_write_db($insertquery);
2187
			unlock($cpdblck);
2188
			unset($insertquery, $bpassword);
2189

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

    
2203
		unlock($cpdblck);
2204
	}
2205

    
2206
	if ($writecfg == true) {
2207
		write_config();
2208
	}
2209

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

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

    
2224
		if (isset($attributes['reply_message'])) {
2225
			$message = $attributes['reply_message'];
2226
		} else {
2227
			$message = 0;
2228
		}
2229

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

    
2232
	} else {
2233
		portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
2234
	}
2235

    
2236
	return $sessionid;
2237
}
2238

    
2239

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

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

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

    
2261
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac) {
2262
		return false;
2263
	}
2264

    
2265
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2266

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

    
2273
	$currenttime = time();
2274
	$found = false;
2275
	foreach ($usedmacs as $key => $usedmac) {
2276
		$usedmac = explode(",", $usedmac);
2277

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

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

    
2294
				$found = true;
2295
			} else {
2296
				unset($usedmacs[$key]);
2297
			}
2298

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

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

    
2310
	captiveportal_write_usedmacs_db($usedmacs);
2311
	return true;
2312
}
2313

    
2314
function captiveportal_read_usedmacs_db() {
2315
	global $g, $cpzone;
2316

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

    
2327
	unlock($cpumaclck);
2328
	return $usedmacs;
2329
}
2330

    
2331
function captiveportal_write_usedmacs_db($usedmacs) {
2332
	global $g, $cpzone;
2333

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

    
2339
function captiveportal_blocked_mac($mac) {
2340
	global $config, $g, $cpzone;
2341

    
2342
	if (empty($mac) || !is_macaddr($mac)) {
2343
		return false;
2344
	}
2345

    
2346
	if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
2347
		return false;
2348
	}
2349

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

    
2357
	return false;
2358

    
2359
}
2360

    
2361
function captiveportal_send_server_accounting($off = false) {
2362
	global $cpzone, $config;
2363

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

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

    
2402
	$racct->close();
2403
	return $retvalue;
2404
}
2405

    
2406
function captiveportal_isip_logged($clientip) {
2407
	global $g, $cpzone;
2408

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