Project

General

Profile

Download (77.5 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
	$radiusservers = captiveportal_get_radius_servers();
934

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

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

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

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

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

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

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

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

    
998
	$ruleno = captiveportal_get_next_ipfw_ruleno();
999

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

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

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

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

    
1023
	return $rules;
1024
}
1025

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

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

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

    
1036
		captiveportal_free_ipfw_ruleno($ruleno);
1037

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

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

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

    
1050
	return $rules;
1051
}
1052

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

    
1056
	$rules = "";
1057

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

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

    
1089
	return $rules;
1090
}
1091

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

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

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

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

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

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

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

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

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

    
1192
	return $rules;
1193
}
1194

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

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

    
1205
	return $rules;
1206
}
1207

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

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

    
1223
	return 0;
1224
}
1225

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1358
	$pipeno = captiveportal_get_next_dn_ruleno();
1359

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

    
1368
	$radiusservers = captiveportal_get_radius_servers();
1369

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

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

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

    
1394
	return $auth_list;
1395
}
1396

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

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

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

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

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

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

    
1448
	return $DB;
1449
}
1450

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

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

    
1466
	return $cpdb;
1467
}
1468

    
1469
function captiveportal_remove_entries($remove) {
1470

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

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

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

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

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

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

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

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

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

    
1535
	return 0;
1536
}
1537

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

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

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

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

    
1591
	return $ruleno;
1592
}
1593

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

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

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

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

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

    
1631
	return $ruleno;
1632
}
1633

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

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

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

    
1678
	return $ruleno;
1679
}
1680

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

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

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

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

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

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

    
1723
	return $ruleno;
1724
}
1725

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

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

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

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

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

    
1769
	return $volume;
1770
}
1771

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

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

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

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

    
1796
	return $nasIp;
1797
}
1798

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

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

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

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

    
1838
	return false;
1839
}
1840

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

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

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

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

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

    
1867
	return $ourhostname;
1868
}
1869

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

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

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

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

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

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

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

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

    
1916
	echo $htmltext;
1917
}
1918

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

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

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

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

    
1935
	return FALSE;
1936
}
1937

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

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

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

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

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

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

    
1967
	unset($sessionid);
1968

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

    
1972
	if ($attributes['voucher']) {
1973
		$remaining_time = $attributes['session_timeout'];
1974
	}
1975

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

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

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

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

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

    
2077
	if (!empty($unsetindexes)) {
2078
		captiveportal_remove_entries($unsetindexes);
2079
	}
2080

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

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

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

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

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

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

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

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

    
2163
			if ($attributes['voucher']) {
2164
				$attributes['session_timeout'] = $remaining_time;
2165
			}
2166

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

    
2173
			/* escape username */
2174
			$safe_username = SQLite3::escapeString($username);
2175

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

    
2182
			/* store information to database */
2183
			captiveportal_write_db($insertquery);
2184
			unlock($cpdblck);
2185
			unset($insertquery, $bpassword);
2186

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

    
2200
		unlock($cpdblck);
2201
	}
2202

    
2203
	if ($writecfg == true) {
2204
		write_config();
2205
	}
2206

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

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

    
2221
		if (isset($attributes['reply_message'])) {
2222
			$message = $attributes['reply_message'];
2223
		} else {
2224
			$message = 0;
2225
		}
2226

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

    
2229
	} else {
2230
		portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
2231
	}
2232

    
2233
	return $sessionid;
2234
}
2235

    
2236

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

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

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

    
2258
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac) {
2259
		return false;
2260
	}
2261

    
2262
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2263

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

    
2270
	$currenttime = time();
2271
	$found = false;
2272
	foreach ($usedmacs as $key => $usedmac) {
2273
		$usedmac = explode(",", $usedmac);
2274

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

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

    
2291
				$found = true;
2292
			} else {
2293
				unset($usedmacs[$key]);
2294
			}
2295

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

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

    
2307
	captiveportal_write_usedmacs_db($usedmacs);
2308
	return true;
2309
}
2310

    
2311
function captiveportal_read_usedmacs_db() {
2312
	global $g, $cpzone;
2313

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

    
2324
	unlock($cpumaclck);
2325
	return $usedmacs;
2326
}
2327

    
2328
function captiveportal_write_usedmacs_db($usedmacs) {
2329
	global $g, $cpzone;
2330

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

    
2336
function captiveportal_blocked_mac($mac) {
2337
	global $config, $g, $cpzone;
2338

    
2339
	if (empty($mac) || !is_macaddr($mac)) {
2340
		return false;
2341
	}
2342

    
2343
	if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
2344
		return false;
2345
	}
2346

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

    
2354
	return false;
2355

    
2356
}
2357

    
2358
function captiveportal_send_server_accounting($off = false) {
2359
	global $cpzone, $config;
2360

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

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

    
2399
	$racct->close();
2400
	return $retvalue;
2401
}
2402

    
2403
function captiveportal_isip_logged($clientip) {
2404
	global $g, $cpzone;
2405

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