Project

General

Profile

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

    
40
/* include all configuration functions */
41
require_once("config.inc");
42
require_once("functions.inc");
43
require_once("filter.inc");
44
require_once("radius.inc");
45
require_once("voucher.inc");
46

    
47
function get_default_captive_portal_html() {
48
	global $config, $g, $cpzone;
49

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

    
98
EOD;
99

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

    
107
EOD;
108
	}
109

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

    
137
EOD;
138

    
139
	return $htmltext;
140
}
141

    
142
function captiveportal_load_modules() {
143
	global $config;
144

    
145
	mute_kernel_msgs();
146
	if (!is_module_loaded("ipfw.ko")) {
147
		mwexec("/sbin/kldload ipfw");
148
		/* make sure ipfw is not on pfil hooks */
149
		set_sysctl(array(
150
			"net.inet.ip.pfil.inbound" => "pf", "net.inet6.ip6.pfil.inbound" => "pf",
151
			"net.inet.ip.pfil.outbound" => "pf", "net.inet6.ip6.pfil.outbound" => "pf")
152
		);
153
	}
154
	/* Activate layer2 filtering */
155
	set_sysctl(array("net.link.ether.ipfw" => "1", "net.inet.ip.fw.one_pass" => "1"));
156

    
157
	/* Always load dummynet now that even allowed ip and mac passthrough use it. */
158
	if (!is_module_loaded("dummynet.ko")) {
159
		mwexec("/sbin/kldload dummynet");
160
		set_sysctl(array("net.inet.ip.dummynet.io_fast" => "1", "net.inet.ip.dummynet.hash_size" => "256"));
161
	}
162
	unmute_kernel_msgs();
163
}
164

    
165
function captiveportal_configure() {
166
	global $config, $cpzone, $cpzoneid;
167

    
168
	if (is_array($config['captiveportal'])) {
169
		foreach ($config['captiveportal'] as $cpkey => $cp) {
170
			$cpzone = $cpkey;
171
			$cpzoneid = $cp['zoneid'];
172
			captiveportal_configure_zone($cp);
173
		}
174
	}
175
}
176

    
177
function captiveportal_configure_zone($cpcfg) {
178
	global $config, $g, $cpzone, $cpzoneid;
179

    
180
	$captiveportallck = lock("captiveportal{$cpzone}", LOCK_EX);
181

    
182
	if (isset($cpcfg['enable'])) {
183

    
184
		if (platform_booting()) {
185
			echo "Starting captive portal({$cpcfg['zone']})... ";
186

    
187
			/* remove old information */
188
			unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
189
		} else {
190
			captiveportal_syslog("Reconfiguring captive portal({$cpcfg['zone']}).");
191
		}
192

    
193
		/* init ipfw rules */
194
		captiveportal_init_rules(true);
195

    
196
		/* kill any running minicron */
197
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
198

    
199
		/* initialize minicron interval value */
200
		$croninterval = $cpcfg['croninterval'] ? $cpcfg['croninterval'] : 60;
201

    
202
		/* double check if the $croninterval is numeric and at least 10 seconds. If not we set it to 60 to avoid problems */
203
		if ((!is_numeric($croninterval)) || ($croninterval < 10)) {
204
			$croninterval = 60;
205
		}
206

    
207
		/* write portal page */
208
		if (is_array($cpcfg['page']) && $cpcfg['page']['htmltext']) {
209
			$htmltext = base64_decode($cpcfg['page']['htmltext']);
210
		} else {
211
			/* example/template page */
212
			$htmltext = get_default_captive_portal_html();
213
		}
214

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

    
235
		/* write error page */
236
		if (is_array($cpcfg['page']) && $cpcfg['page']['errtext']) {
237
			$errtext = base64_decode($cpcfg['page']['errtext']);
238
		} else {
239
			/* example page  */
240
			$errtext = get_default_captive_portal_html();
241
		}
242

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

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

    
294
document.location.href="<?=\$my_redirurl;?>";
295
//]]>
296
</script>
297
</body>
298
</html>
299

    
300
EOD;
301
		}
302

    
303
		$fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html", "w");
304
		if ($fd) {
305
			fwrite($fd, $logouttext);
306
			fclose($fd);
307
		}
308
		unset($logouttext);
309

    
310
		/* write elements */
311
		captiveportal_write_elements();
312

    
313
		/* kill any running CP nginx instances */
314
		killbypid("{$g['varrun_path']}/nginx-{$cpzone}-CaptivePortal.pid");
315
		killbypid("{$g['varrun_path']}/nginx-{$cpzone}-CaptivePortal-SSL.pid");
316

    
317
		/* start up the webserving daemon */
318
		captiveportal_init_webgui_zone($cpcfg);
319

    
320
		/* Kill any existing prunecaptiveportal processes */
321
		if (file_exists("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid")) {
322
			killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
323
		}
324

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

    
329
		/* generate radius server database */
330
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
331
		captiveportal_init_radius_servers();
332

    
333
		if (platform_booting()) {
334
			/* send Accounting-On to server */
335
			captiveportal_send_server_accounting();
336
			echo "done\n";
337
		}
338

    
339
	} else {
340
		killbypid("{$g['varrun_path']}/nginx-{$cpzone}-CaptivePortal.pid");
341
		killbypid("{$g['varrun_path']}/nginx-{$cpzone}-CaptivePortal-SSL.pid");
342
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
343
		@unlink("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
344
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
345
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
346

    
347
		captiveportal_radius_stop_all();
348

    
349
		/* send Accounting-Off to server */
350
		if (!platform_booting()) {
351
			captiveportal_send_server_accounting(true);
352
		}
353

    
354
		/* remove old information */
355
		unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
356
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
357
		unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
358
		/* Release allocated pipes for this zone */
359
		captiveportal_free_dnrules();
360

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

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

    
382
	unlock($captiveportallck);
383

    
384
	return 0;
385
}
386

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

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

    
398
function captiveportal_init_webgui_zonename($zone) {
399
	global $config, $cpzone;
400

    
401
	if (isset($config['captiveportal'][$zone])) {
402
		$cpzone = $zone;
403
		captiveportal_init_webgui_zone($config['captiveportal'][$zone]);
404
	}
405
}
406

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

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

    
414
	if (isset($cpcfg['httpslogin'])) {
415
		$cert = lookup_cert($cpcfg['certref']);
416
		$crt = base64_decode($cert['crt']);
417
		$key = base64_decode($cert['prv']);
418
		$ca = ca_chain($cert);
419

    
420
		/* generate nginx configuration */
421
		if (!empty($cpcfg['listenporthttps'])) {
422
			$listenporthttps = $cpcfg['listenporthttps'];
423
		} else {
424
			$listenporthttps = 8001 + $cpcfg['zoneid'];
425
		}
426
		system_generate_nginx_config("{$g['varetc_path']}/nginx-{$cpzone}-CaptivePortal-SSL.conf",
427
			$crt, $key, $ca, "nginx-{$cpzone}-CaptivePortal-SSL.pid", $listenporthttps, "/usr/local/captiveportal",
428
			"cert-{$cpzone}-portal.pem", "ca-{$cpzone}-portal.pem", $cpzone);
429
	}
430

    
431
	/* generate nginx configuration */
432
	if (!empty($cpcfg['listenporthttp'])) {
433
		$listenporthttp = $cpcfg['listenporthttp'];
434
	} else {
435
		$listenporthttp = 8000 + $cpcfg['zoneid'];
436
	}
437
	system_generate_nginx_config("{$g['varetc_path']}/nginx-{$cpzone}-CaptivePortal.conf",
438
		"", "", "", "nginx-{$cpzone}-CaptivePortal.pid", $listenporthttp, "/usr/local/captiveportal",
439
		"", "", $cpzone);
440

    
441
	@unlink("{$g['varrun']}/nginx-{$cpzone}-CaptivePortal.pid");
442
	/* attempt to start nginx */
443
	$res = mwexec("/usr/local/sbin/nginx -c {$g['varetc_path']}/nginx-{$cpzone}-CaptivePortal.conf");
444

    
445
	/* fire up https instance */
446
	if (isset($cpcfg['httpslogin'])) {
447
		@unlink("{$g['varrun']}/nginx-{$cpzone}-CaptivePortal-SSL.pid");
448
		$res = mwexec("/usr/local/sbin/nginx -c {$g['varetc_path']}/nginx-{$cpzone}-CaptivePortal-SSL.conf");
449
	}
450
}
451

    
452
function captiveportal_init_rules_byinterface($interface) {
453
	global $cpzone, $cpzoneid, $config;
454

    
455
	if (!is_array($config['captiveportal'])) {
456
		return;
457
	}
458

    
459
	foreach ($config['captiveportal'] as $cpkey => $cp) {
460
		$cpzone = $cpkey;
461
		$cpzoneid = $cp['zoneid'];
462
		$cpinterfaces = explode(",", $cp['interface']);
463
		if (in_array($interface, $cpinterfaces)) {
464
			captiveportal_init_rules();
465
			break;
466
		}
467
	}
468
}
469

    
470
/* reinit will disconnect all users, be careful! */
471
function captiveportal_init_rules($reinit = false) {
472
	global $config, $g, $cpzone, $cpzoneid;
473

    
474
	if (!isset($config['captiveportal'][$cpzone]['enable'])) {
475
		return;
476
	}
477

    
478
	captiveportal_load_modules();
479
	mwexec("/sbin/ipfw zone {$cpzoneid} create", true);
480

    
481
	/* Cleanup so nothing is leaked */
482
	captiveportal_free_dnrules();
483
	unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
484

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

    
515
	if ($reinit == false) {
516
		$captiveportallck = lock("captiveportal{$cpzone}");
517
	}
518

    
519
	$cprules = <<<EOD
520

    
521
flush
522
add 65291 allow pfsync from any to any
523
add 65292 allow carp from any to any
524

    
525
# layer 2: pass ARP
526
add 65301 pass layer2 mac-type arp,rarp
527
# pfsense requires for WPA
528
add 65302 pass layer2 mac-type 0x888e,0x88c7
529
# PPP Over Ethernet Session Stage/Discovery Stage
530
add 65303 pass layer2 mac-type 0x8863,0x8864
531

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

    
535
EOD;
536

    
537
	$rulenum = 65310;
538
	/* These tables contain host ips */
539
	$cprules .= "add {$rulenum} pass ip from any to table(100) in\n";
540
	$rulenum++;
541
	$cprules .= "add {$rulenum} pass ip from table(100) to any out\n";
542
	$rulenum++;
543
	foreach ($cpips as $cpip) {
544
		$cprules .= "table 100 add {$cpip}\n";
545
	}
546
	$cprules .= "add {$rulenum} pass ip from any to 255.255.255.255 in\n";
547
	$rulenum++;
548
	$cprules .= "add {$rulenum} pass ip from 255.255.255.255 to any out\n";
549
	$rulenum++;
550

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

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

    
567
	if (!empty($config['captiveportal'][$cpzone]['listenporthttp'])) {
568
		$listenporthttp = $config['captiveportal'][$cpzone]['listenporthttp'];
569
	} else {
570
		$listenporthttp = 8000 + $cpzoneid;
571
	}
572

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

    
584
	$cprules .= <<<EOD
585

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

    
593
EOD;
594

    
595
	/* generate passthru mac database */
596
	$cprules .= captiveportal_passthrumac_configure(true);
597
	$cprules .= "\n";
598

    
599
	/* allowed ipfw rules to make allowed ip work */
600
	$cprules .= captiveportal_allowedip_configure();
601

    
602
	/* allowed ipfw rules to make allowed hostnames work */
603
	$cprules .= captiveportal_allowedhostname_configure();
604

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

    
611
	if ($reinit == false) {
612
		unlock($captiveportallck);
613
	}
614
}
615

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

    
625
	if (empty($cpzone)) {
626
		return;
627
	}
628

    
629
	$cpcfg = $config['captiveportal'][$cpzone];
630
	$vcpcfg = $config['voucher'][$cpzone];
631

    
632
	/* check for expired entries */
633
	$idletimeout = 0;
634
	$timeout = 0;
635
	if (!empty($cpcfg['timeout']) && is_numeric($cpcfg['timeout'])) {
636
		$timeout = $cpcfg['timeout'] * 60;
637
	}
638

    
639
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout'])) {
640
		$idletimeout = $cpcfg['idletimeout'] * 60;
641
	}
642

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

    
649
	$radiussrvs = captiveportal_get_radius_servers();
650

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

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

    
665
		$timedout = false;
666
		$term_cause = 1;
667
		if (empty($cpentry[11])) {
668
			$cpentry[11] = 'first';
669
		}
670
		$radiusservers = $radiussrvs[$cpentry[11]];
671

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

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

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

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

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

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

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

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

    
794
	captiveportal_prune_old_automac();
795

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

    
801
	/* write database */
802
	if (!empty($unsetindexes)) {
803
		captiveportal_remove_entries($unsetindexes);
804
	}
805
}
806

    
807
function captiveportal_prune_old_automac() {
808
	global $g, $config, $cpzone, $cpzoneid;
809

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

    
872
/* remove a single client according to the DB entry */
873
function captiveportal_disconnect($dbent, $radiusservers, $term_cause = 1, $stop_time = null) {
874
	global $g, $config, $cpzone, $cpzoneid;
875

    
876
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
877

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

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

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

    
910
		/* Release the ruleno so it can be reallocated to new clients. */
911
		captiveportal_free_dn_ruleno($dbent[1]);
912
	}
913

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

    
923
}
924

    
925
/* remove a single client by sessionid */
926
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
927
	global $g, $config;
928

    
929
	$sessionid = SQLite3::escapeString($sessionid);
930
	$radiusservers = captiveportal_get_radius_servers();
931

    
932
	/* read database */
933
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
934

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

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

    
950
/* send RADIUS acct stop for all current clients */
951
function captiveportal_radius_stop_all() {
952
	global $config, $cpzone;
953

    
954
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
955
		return;
956
	}
957

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

    
979
function captiveportal_passthrumac_configure_entry($macent, $pipeinrule = false) {
980
	global $config, $g, $cpzone;
981

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

    
995
	$ruleno = captiveportal_get_next_ipfw_ruleno();
996

    
997
	if ($macent['action'] == 'pass') {
998
		$rules = "";
999
		$pipeno = captiveportal_get_next_dn_ruleno();
1000

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

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

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

    
1020
	return $rules;
1021
}
1022

    
1023
function captiveportal_passthrumac_delete_entry($macent) {
1024
	$rules = "";
1025

    
1026
	if ($macent['action'] == 'pass') {
1027
		$ruleno = captiveportal_get_ipfw_passthru_ruleno($macent['mac']);
1028

    
1029
		if (!$ruleno) {
1030
			return $rules;
1031
		}
1032

    
1033
		captiveportal_free_ipfw_ruleno($ruleno);
1034

    
1035
		$rules .= "delete {$ruleno}\n";
1036
		$rules .= "delete " . ++$ruleno . "\n";
1037

    
1038
		$pipeno = captiveportal_get_dn_passthru_ruleno($macent['mac']);
1039

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

    
1047
	return $rules;
1048
}
1049

    
1050
function captiveportal_passthrumac_configure($filename = false, $startindex = 0, $stopindex = 0) {
1051
	global $config, $g, $cpzone;
1052

    
1053
	$rules = "";
1054

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

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

    
1086
	return $rules;
1087
}
1088

    
1089
function captiveportal_passthrumac_findbyname($username) {
1090
	global $config, $cpzone;
1091

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

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

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

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

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

    
1157
	if ($ishostname === true) {
1158
		return array($rules, $cp_filterdns_conf);
1159
	} else {
1160
		return $rules;
1161
	}
1162
}
1163

    
1164
function captiveportal_allowedhostname_configure() {
1165
	global $config, $g, $cpzone, $cpzoneid;
1166

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

    
1189
	return $rules;
1190
}
1191

    
1192
function captiveportal_allowedip_configure() {
1193
	global $config, $g, $cpzone;
1194

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

    
1202
	return $rules;
1203
}
1204

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

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

    
1220
	return 0;
1221
}
1222

    
1223
function captiveportal_init_radius_servers() {
1224
	global $config, $g, $cpzone;
1225

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

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

    
1260
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1261
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1262
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1263
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1264

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

    
1285
		fclose($fd);
1286
		unlock($cprdsrvlck);
1287
	}
1288
}
1289

    
1290
/* read RADIUS servers into array */
1291
function captiveportal_get_radius_servers() {
1292
	global $g, $cpzone;
1293

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

    
1323
	unlock($cprdsrvlck);
1324
	return false;
1325
}
1326

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

    
1340
/* log simple messages to syslog */
1341
function captiveportal_syslog($message) {
1342
	global $cpzone;
1343

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

    
1352
function radius($username, $password, $clientip, $clientmac, $type, $radiusctx = null) {
1353
	global $g, $config, $cpzoneid;
1354

    
1355
	$pipeno = captiveportal_get_next_dn_ruleno();
1356

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

    
1365
	$radiusservers = captiveportal_get_radius_servers();
1366

    
1367
	if (is_null($radiusctx)) {
1368
		$radiusctx = 'first';
1369
	}
1370

    
1371
	$auth_list = RADIUS_AUTHENTICATION($username,
1372
		$password,
1373
		$radiusservers[$radiusctx],
1374
		$clientip,
1375
		$clientmac,
1376
		$pipeno);
1377

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

    
1391
	return $auth_list;
1392
}
1393

    
1394
function captiveportal_opendb() {
1395
	global $g, $config, $cpzone, $cpzoneid;
1396

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

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

    
1422
	if (!$DB) {
1423
		captiveportal_syslog("Could not open {$db_path} as an sqlite database for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Trying again.");
1424
		unlink_if_exists($db_path);
1425
		$DB = new SQLite3($db_path);
1426
		$DB->busyTimeout(60000);
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
		$DB->busyTimeout(60000);
1442
		if ($DB->exec($createquery)) {
1443
			captiveportal_syslog("Successfully reinitialized tables for {$cpzone} -- database has been reset.");
1444
			if (!is_numericint($cpzoneid)) {
1445
				if (is_array($config['captiveportal'])) {
1446
					foreach ($config['captiveportal'] as $cpkey => $cp) {
1447
						if ($cpzone == $cp['zone']) {
1448
							$cpzoneid = $cp['zoneid'];
1449
						}
1450
					}
1451
				}
1452
			}
1453
			if (is_numericint($cpzoneid)) {
1454
				mwexec("/sbin/ipfw -x $cpzoneid table all flush");
1455
				captiveportal_syslog("Flushed tables for {$cpzone} after database reset.");
1456
			}
1457
		} else {
1458
			captiveportal_syslog("Still unable to create tables for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Remove the database file manually and try again.");
1459
		}
1460
	}
1461

    
1462
	return $DB;
1463
}
1464

    
1465
/* read captive portal DB into array */
1466
function captiveportal_read_db($query = "") {
1467
	$cpdb = array();
1468

    
1469
	$DB = captiveportal_opendb();
1470
	if ($DB) {
1471
		$response = $DB->query("SELECT * FROM captiveportal {$query}");
1472
		if ($response != FALSE) {
1473
			while ($row = $response->fetchArray()) {
1474
				$cpdb[] = $row;
1475
			}
1476
		}
1477
		$DB->close();
1478
	}
1479

    
1480
	return $cpdb;
1481
}
1482

    
1483
function captiveportal_remove_entries($remove) {
1484

    
1485
	if (!is_array($remove) || empty($remove)) {
1486
		return;
1487
	}
1488

    
1489
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1490
	foreach ($remove as $idx => $unindex) {
1491
		$query .= "'{$unindex}'";
1492
		if ($idx < (count($remove) - 1)) {
1493
			$query .= ",";
1494
		}
1495
	}
1496
	$query .= ")";
1497
	captiveportal_write_db($query);
1498
}
1499

    
1500
/* write captive portal DB */
1501
function captiveportal_write_db($queries) {
1502
	global $g;
1503

    
1504
	if (is_array($queries)) {
1505
		$query = implode(";", $queries);
1506
	} else {
1507
		$query = $queries;
1508
	}
1509

    
1510
	$DB = captiveportal_opendb();
1511
	if ($DB) {
1512
		$DB->exec("BEGIN TRANSACTION");
1513
		$result = $DB->exec($query);
1514
		if (!$result) {
1515
			captiveportal_syslog("Trying to modify DB returned error: {$DB->lastErrorMsg()}");
1516
		} else {
1517
			$DB->exec("END TRANSACTION");
1518
		}
1519
		$DB->close();
1520
		return $result;
1521
	} else {
1522
		return true;
1523
	}
1524
}
1525

    
1526
function captiveportal_write_elements() {
1527
	global $g, $config, $cpzone;
1528

    
1529
	$cpcfg = $config['captiveportal'][$cpzone];
1530

    
1531
	if (!is_dir($g['captiveportal_element_path'])) {
1532
		@mkdir($g['captiveportal_element_path']);
1533
	}
1534

    
1535
	if (is_array($cpcfg['element'])) {
1536
		conf_mount_rw();
1537
		foreach ($cpcfg['element'] as $data) {
1538
			/* Do not attempt to decode or write out empty files. */
1539
			if (empty($data['content']) || empty(base64_decode($data['content']))) {
1540
				unlink_if_exists("{$g['captiveportal_element_path']}/{$data['name']}");
1541
				touch("{$g['captiveportal_element_path']}/{$data['name']}");
1542
			} elseif (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1543
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1544
				return 1;
1545
			}
1546
			if (!file_exists("{$g['captiveportal_path']}/{$data['name']}")) {
1547
				@symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1548
			}
1549
		}
1550
		conf_mount_ro();
1551
	}
1552

    
1553
	return 0;
1554
}
1555

    
1556
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1557
	global $cpzone;
1558

    
1559
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1560
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1561
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1562
		$ridx = $rulenos_start;
1563
		while ($ridx < $rulenos_range_max) {
1564
			if ($rules[$ridx] == $cpzone) {
1565
				$rules[$ridx] = false;
1566
				$ridx++;
1567
				$rules[$ridx] = false;
1568
				$ridx++;
1569
			} else {
1570
				$ridx += 2;
1571
			}
1572
		}
1573
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1574
		unset($rules);
1575
	}
1576
	unlock($cpruleslck);
1577
}
1578

    
1579
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1580
	global $config, $g, $cpzone;
1581

    
1582
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1583
	$ruleno = 0;
1584
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1585
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1586
		$ridx = $rulenos_start;
1587
		while ($ridx < $rulenos_range_max) {
1588
			if (empty($rules[$ridx])) {
1589
				$ruleno = $ridx;
1590
				$rules[$ridx] = $cpzone;
1591
				$ridx++;
1592
				$rules[$ridx] = $cpzone;
1593
				break;
1594
			} else {
1595
				$ridx += 2;
1596
			}
1597
		}
1598
	} else {
1599
		$rules = array_pad(array(), $rulenos_range_max, false);
1600
		$ruleno = $rulenos_start;
1601
		$rules[$rulenos_start] = $cpzone;
1602
		$rulenos_start++;
1603
		$rules[$rulenos_start] = $cpzone;
1604
	}
1605
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1606
	unlock($cpruleslck);
1607
	unset($rules);
1608

    
1609
	return $ruleno;
1610
}
1611

    
1612
function captiveportal_free_dn_ruleno($ruleno) {
1613
	global $config, $g;
1614

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

    
1627
function captiveportal_get_dn_passthru_ruleno($value) {
1628
	global $config, $g, $cpzone, $cpzoneid;
1629

    
1630
	$cpcfg = $config['captiveportal'][$cpzone];
1631
	if (!isset($cpcfg['enable'])) {
1632
		return NULL;
1633
	}
1634

    
1635
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1636
	$ruleno = NULL;
1637
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1638
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1639
		unset($output);
1640
		$_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);
1641
		$ruleno = intval($output[0]);
1642
		if (!$rules[$ruleno]) {
1643
			$ruleno = NULL;
1644
		}
1645
		unset($rules);
1646
	}
1647
	unlock($cpruleslck);
1648

    
1649
	return $ruleno;
1650
}
1651

    
1652
/*
1653
 * This function will calculate the lowest free firewall ruleno
1654
 * within the range specified based on the actual logged on users
1655
 *
1656
 */
1657
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1658
	global $config, $g, $cpzone;
1659

    
1660
	$cpcfg = $config['captiveportal'][$cpzone];
1661
	if (!isset($cpcfg['enable'])) {
1662
		return NULL;
1663
	}
1664

    
1665
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1666
	$ruleno = 0;
1667
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1668
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1669
		$ridx = $rulenos_start;
1670
		while ($ridx < $rulenos_range_max) {
1671
			if (empty($rules[$ridx])) {
1672
				$ruleno = $ridx;
1673
				$rules[$ridx] = $cpzone;
1674
				$ridx++;
1675
				$rules[$ridx] = $cpzone;
1676
				break;
1677
			} else {
1678
				/*
1679
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno
1680
				 * and the out pipe ruleno + 1.
1681
				 */
1682
				$ridx += 2;
1683
			}
1684
		}
1685
	} else {
1686
		$rules = array_pad(array(), $rulenos_range_max, false);
1687
		$ruleno = $rulenos_start;
1688
		$rules[$rulenos_start] = $cpzone;
1689
		$rulenos_start++;
1690
		$rules[$rulenos_start] = $cpzone;
1691
	}
1692
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1693
	unlock($cpruleslck);
1694
	unset($rules);
1695

    
1696
	return $ruleno;
1697
}
1698

    
1699
function captiveportal_free_ipfw_ruleno($ruleno) {
1700
	global $config, $g, $cpzone;
1701

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

    
1707
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1708
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1709
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1710
		$rules[$ruleno] = false;
1711
		$ruleno++;
1712
		$rules[$ruleno] = false;
1713
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1714
		unset($rules);
1715
	}
1716
	unlock($cpruleslck);
1717
}
1718

    
1719
function captiveportal_get_ipfw_passthru_ruleno($value) {
1720
	global $config, $g, $cpzone, $cpzoneid;
1721

    
1722
	$cpcfg = $config['captiveportal'][$cpzone];
1723
	if (!isset($cpcfg['enable'])) {
1724
		return NULL;
1725
	}
1726

    
1727
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1728
	$ruleno = NULL;
1729
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1730
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1731
		unset($output);
1732
		$_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);
1733
		$ruleno = intval($output[0]);
1734
		if (!$rules[$ruleno]) {
1735
			$ruleno = NULL;
1736
		}
1737
		unset($rules);
1738
	}
1739
	unlock($cpruleslck);
1740

    
1741
	return $ruleno;
1742
}
1743

    
1744
/**
1745
 * This function will calculate the traffic produced by a client
1746
 * based on its firewall rule
1747
 *
1748
 * Point of view: NAS
1749
 *
1750
 * Input means: from the client
1751
 * Output means: to the client
1752
 *
1753
 */
1754

    
1755
function getVolume($ip, $mac = NULL) {
1756
	global $config, $cpzone, $cpzoneid;
1757

    
1758
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1759
	$volume = array();
1760
	// Initialize vars properly, since we don't want NULL vars
1761
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1762

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

    
1775
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 2, $ip, $mac);
1776
	if (is_array($ipfw)) {
1777
		if ($reverse) {
1778
			$volume['input_pkts'] = $ipfw['packets'];
1779
			$volume['input_bytes'] = $ipfw['bytes'];
1780
		}
1781
		else {
1782
			$volume['output_pkts'] = $ipfw['packets'];
1783
			$volume['output_bytes'] = $ipfw['bytes'];
1784
		}
1785
	}
1786

    
1787
	return $volume;
1788
}
1789

    
1790
/**
1791
 * Get the NAS-IP-Address based on the current wan address
1792
 *
1793
 * Use functions in interfaces.inc to find this out
1794
 *
1795
 */
1796

    
1797
function getNasIP() {
1798
	global $config, $cpzone;
1799

    
1800
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1801
			$nasIp = get_interface_ip();
1802
	} else {
1803
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1804
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1805
		} else {
1806
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1807
		}
1808
	}
1809

    
1810
	if (!is_ipaddr($nasIp)) {
1811
		$nasIp = "0.0.0.0";
1812
	}
1813

    
1814
	return $nasIp;
1815
}
1816

    
1817
function portal_ip_from_client_ip($cliip) {
1818
	global $config, $cpzone;
1819

    
1820
	$isipv6 = is_ipaddrv6($cliip);
1821
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1822
	foreach ($interfaces as $cpif) {
1823
		if ($isipv6) {
1824
			$ip = get_interface_ipv6($cpif);
1825
			$sn = get_interface_subnetv6($cpif);
1826
		} else {
1827
			$ip = get_interface_ip($cpif);
1828
			$sn = get_interface_subnet($cpif);
1829
		}
1830
		if (ip_in_subnet($cliip, "{$ip}/{$sn}")) {
1831
			return $ip;
1832
		}
1833
	}
1834

    
1835
	$inet = ($isipv6) ? '-inet6' : '-inet';
1836
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1837
	$iface = trim($iface, "\n");
1838
	if (!empty($iface)) {
1839
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1840
		if (is_ipaddr($ip)) {
1841
			return $ip;
1842
		}
1843
	}
1844

    
1845
	// doesn't match up to any particular interface
1846
	// so let's set the portal IP to what PHP says
1847
	// the server IP issuing the request is.
1848
	// allows same behavior as 1.2.x where IP isn't
1849
	// in the subnet of any CP interface (static routes, etc.)
1850
	// rather than forcing to DNS hostname resolution
1851
	$ip = $_SERVER['SERVER_ADDR'];
1852
	if (is_ipaddr($ip)) {
1853
		return $ip;
1854
	}
1855

    
1856
	return false;
1857
}
1858

    
1859
function portal_hostname_from_client_ip($cliip) {
1860
	global $config, $cpzone;
1861

    
1862
	$cpcfg = $config['captiveportal'][$cpzone];
1863

    
1864
	if (isset($cpcfg['httpslogin'])) {
1865
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 8001);
1866
		$ourhostname = $cpcfg['httpsname'];
1867

    
1868
		if ($listenporthttps != 443) {
1869
			$ourhostname .= ":" . $listenporthttps;
1870
		}
1871
	} else {
1872
		$listenporthttp = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : ($cpcfg['zoneid'] + 8000);
1873
		$ifip = portal_ip_from_client_ip($cliip);
1874
		if (!$ifip) {
1875
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1876
		} else {
1877
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1878
		}
1879

    
1880
		if ($listenporthttp != 80) {
1881
			$ourhostname .= ":" . $listenporthttp;
1882
		}
1883
	}
1884

    
1885
	return $ourhostname;
1886
}
1887

    
1888
/* functions move from index.php */
1889

    
1890
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1891
	global $g, $config, $cpzone;
1892

    
1893
	/* Get captive portal layout */
1894
	if ($type == "redir") {
1895
		header("Location: {$redirurl}");
1896
		return;
1897
	} else if ($type == "login") {
1898
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1899
	} else {
1900
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1901
	}
1902

    
1903
	$cpcfg = $config['captiveportal'][$cpzone];
1904

    
1905
	/* substitute the PORTAL_REDIRURL variable */
1906
	if ($cpcfg['preauthurl']) {
1907
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1908
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1909
	}
1910

    
1911
	/* substitute other variables */
1912
	$ourhostname = portal_hostname_from_client_ip($clientip);
1913
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1914
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1915
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1916

    
1917
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1918
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1919
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1920
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1921
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1922

    
1923
	// Special handling case for captive portal master page so that it can be ran
1924
	// through the PHP interpreter using the include method above.  We convert the
1925
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1926
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1927
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1928
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1929
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1930
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1931
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1932
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1933

    
1934
	echo $htmltext;
1935
}
1936

    
1937
function portal_mac_radius($clientmac, $clientip) {
1938
	global $config, $cpzone;
1939

    
1940
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1941

    
1942
	/* authentication against the radius server */
1943
	$username = mac_format($clientmac);
1944
	$auth_list = radius($username, $radmac_secret, $clientip, $clientmac, "MACHINE LOGIN");
1945
	if ($auth_list['auth_val'] == 2) {
1946
		return TRUE;
1947
	}
1948

    
1949
	if (!empty($auth_list['url_redirection'])) {
1950
		portal_reply_page($auth_list['url_redirection'], "redir");
1951
	}
1952

    
1953
	return FALSE;
1954
}
1955

    
1956
function captiveportal_reapply_attributes($cpentry, $attributes) {
1957
	global $config, $cpzone, $g;
1958

    
1959
	if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
1960
		$dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1961
		$dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1962
	} else {
1963
		$dwfaultbw_up = $dwfaultbw_down = 0;
1964
	}
1965
	$bw_up = !empty($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1966
	$bw_down = !empty($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1967
	$bw_up_pipeno = $cpentry[1];
1968
	$bw_down_pipeno = $cpentry[1]+1;
1969

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

    
1974
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1975
}
1976

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

    
1980
	// Ensure we create an array if we are missing attributes
1981
	if (!is_array($attributes)) {
1982
		$attributes = array();
1983
	}
1984

    
1985
	unset($sessionid);
1986

    
1987
	/* Do not allow concurrent login execution. */
1988
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1989

    
1990
	if ($attributes['voucher']) {
1991
		$remaining_time = $attributes['session_timeout'];
1992
	// Set RADIUS-Attribute to Voucher to prevent ReAuth-Reqeuest for Vouchers Bug: #2155
1993
		$radiusctx="voucher";
1994
	}
1995

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

    
2042
	/* read in client database */
2043
	$query = "WHERE ip = '{$clientip}'";
2044
	$tmpusername = strtolower($username);
2045
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) {
2046
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
2047
	}
2048
	$cpdb = captiveportal_read_db($query);
2049

    
2050
	/* Snapshot the timestamp */
2051
	$allow_time = time();
2052
	$radiusservers = captiveportal_get_radius_servers();
2053
	$unsetindexes = array();
2054
	if (is_null($radiusctx)) {
2055
		$radiusctx = 'first';
2056
	}
2057

    
2058
	foreach ($cpdb as $cpentry) {
2059
		if (empty($cpentry[11])) {
2060
			$cpentry[11] = 'first';
2061
		}
2062
		/* on the same ip */
2063
		if ($cpentry[2] == $clientip) {
2064
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac) {
2065
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - REUSING OLD SESSION");
2066
			} else {
2067
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
2068
			}
2069
			$sessionid = $cpentry[5];
2070
			break;
2071
		} elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
2072
			// user logged in with an active voucher. Check for how long and calculate
2073
			// how much time we can give him (voucher credit - used time)
2074
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
2075
			if ($remaining_time < 0) { // just in case.
2076
				$remaining_time = 0;
2077
			}
2078

    
2079
			/* This user was already logged in so we disconnect the old one */
2080
			captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], 13);
2081
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - TERMINATING OLD SESSION");
2082
			$unsetindexes[] = $cpentry[5];
2083
			break;
2084
		} elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
2085
			/* on the same username */
2086
			if (strcasecmp($cpentry[4], $username) == 0) {
2087
				/* This user was already logged in so we disconnect the old one */
2088
				captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], 13);
2089
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - TERMINATING OLD SESSION");
2090
				$unsetindexes[] = $cpentry[5];
2091
				break;
2092
			}
2093
		}
2094
	}
2095
	unset($cpdb);
2096

    
2097
	if (!empty($unsetindexes)) {
2098
		captiveportal_remove_entries($unsetindexes);
2099
	}
2100

    
2101
	if ($attributes['voucher'] && $remaining_time <= 0) {
2102
		return 0;       // voucher already used and no time left
2103
	}
2104

    
2105
	if (!isset($sessionid)) {
2106
		/* generate unique session ID */
2107
		$tod = gettimeofday();
2108
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
2109

    
2110
		if ($passthrumac) {
2111
			$mac = array();
2112
			$mac['action'] = 'pass';
2113
			$mac['mac'] = $clientmac;
2114
			$mac['ip'] = $clientip; /* Used only for logging */
2115
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
2116
				$mac['username'] = $username;
2117
				if ($attributes['voucher']) {
2118
					$mac['logintype'] = "voucher";
2119
				}
2120
			}
2121
			if ($username == "unauthenticated") {
2122
				$mac['descr'] = "Auto-added";
2123
			} else {
2124
				$mac['descr'] = "Auto-added for user {$username}";
2125
			}
2126
			if (!empty($bw_up)) {
2127
				$mac['bw_up'] = $bw_up;
2128
			}
2129
			if (!empty($bw_down)) {
2130
				$mac['bw_down'] = $bw_down;
2131
			}
2132
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
2133
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
2134
			}
2135
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
2136
			unlock($cpdblck);
2137
			$macrules = captiveportal_passthrumac_configure_entry($mac);
2138
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
2139
			mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
2140
			$writecfg = true;
2141
		} else {
2142
			/* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
2143
			if (is_null($pipeno)) {
2144
				$pipeno = captiveportal_get_next_dn_ruleno();
2145
			}
2146

    
2147
			/* if the pool is empty, return appropriate message and exit */
2148
			if (is_null($pipeno)) {
2149
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
2150
				log_error("Zone: {$cpzone} - WARNING!  Captive portal has reached maximum login capacity");
2151
				unlock($cpdblck);
2152
				return;
2153
			}
2154

    
2155
			if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
2156
				$dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
2157
				$dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
2158
			} else {
2159
				$dwfaultbw_up = $dwfaultbw_down = 0;
2160
			}
2161
			$bw_up = !empty($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
2162
			$bw_down = !empty($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
2163

    
2164
			$bw_up_pipeno = $pipeno;
2165
			$bw_down_pipeno = $pipeno + 1;
2166
			//$bw_up /= 1000; // Scale to Kbit/s
2167
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
2168
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
2169

    
2170
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
2171
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
2172
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
2173
			} else {
2174
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
2175
			}
2176

    
2177
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
2178
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
2179
			} else {
2180
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
2181
			}
2182

    
2183
			if ($attributes['voucher']) {
2184
				$attributes['session_timeout'] = $remaining_time;
2185
			}
2186

    
2187
			/* handle empty attributes */
2188
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
2189
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
2190
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
2191
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
2192

    
2193
			/* escape username */
2194
			$safe_username = SQLite3::escapeString($username);
2195

    
2196
			/* encode password in Base64 just in case it contains commas */
2197
			$bpassword = base64_encode($password);
2198
			$insertquery = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, radiusctx) ";
2199
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
2200
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, '{$radiusctx}')";
2201

    
2202
			/* store information to database */
2203
			captiveportal_write_db($insertquery);
2204
			unlock($cpdblck);
2205
			unset($insertquery, $bpassword);
2206

    
2207
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
2208
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
2209
				if ($acct_val == 1) {
2210
					captiveportal_logportalauth($username, $clientmac, $clientip, $type, "RADIUS ACCOUNTING FAILED");
2211
				}
2212
			}
2213
		}
2214
	} else {
2215
		/* NOTE: #3062-11 If the pipeno has been allocated free it to not DoS the CP and maintain proper operation as in radius() case */
2216
		if (!is_null($pipeno)) {
2217
			captiveportal_free_dn_ruleno($pipeno);
2218
		}
2219

    
2220
		unlock($cpdblck);
2221
	}
2222

    
2223
	if ($writecfg == true) {
2224
		write_config();
2225
	}
2226

    
2227
	/* redirect user to desired destination */
2228
	if (!empty($attributes['url_redirection'])) {
2229
		$my_redirurl = $attributes['url_redirection'];
2230
	} else if (!empty($redirurl)) {
2231
		$my_redirurl = $redirurl;
2232
	} else if (!empty($config['captiveportal'][$cpzone]['redirurl'])) {
2233
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
2234
	}
2235

    
2236
	if (isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
2237
		$ourhostname = portal_hostname_from_client_ip($clientip);
2238
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
2239
		$logouturl = "{$protocol}{$ourhostname}/";
2240

    
2241
		if (isset($attributes['reply_message'])) {
2242
			$message = $attributes['reply_message'];
2243
		} else {
2244
			$message = 0;
2245
		}
2246

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

    
2249
	} else {
2250
		portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
2251
	}
2252

    
2253
	return $sessionid;
2254
}
2255

    
2256

    
2257
/*
2258
 * Used for when pass-through credits are enabled.
2259
 * Returns true when there was at least one free login to deduct for the MAC.
2260
 * Expired entries are removed as they are seen.
2261
 * Active entries are updated according to the configuration.
2262
 */
2263
function portal_consume_passthrough_credit($clientmac) {
2264
	global $config, $cpzone;
2265

    
2266
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count'])) {
2267
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
2268
	} else {
2269
		return false;
2270
	}
2271

    
2272
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout'])) {
2273
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2274
	} else {
2275
		return false;
2276
	}
2277

    
2278
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac) {
2279
		return false;
2280
	}
2281

    
2282
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2283

    
2284
	/*
2285
	 * Read database of used MACs.  Lines are a comma-separated list
2286
	 * of the time, MAC, then the count of pass-through credits remaining.
2287
	 */
2288
	$usedmacs = captiveportal_read_usedmacs_db();
2289

    
2290
	$currenttime = time();
2291
	$found = false;
2292
	foreach ($usedmacs as $key => $usedmac) {
2293
		$usedmac = explode(",", $usedmac);
2294

    
2295
		if ($usedmac[1] == $clientmac) {
2296
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2297
				if ($usedmac[2] < 1) {
2298
					if ($updatetimeouts) {
2299
						$usedmac[0] = $currenttime;
2300
						unset($usedmacs[$key]);
2301
						$usedmacs[] = implode(",", $usedmac);
2302
						captiveportal_write_usedmacs_db($usedmacs);
2303
					}
2304

    
2305
					return false;
2306
				} else {
2307
					$usedmac[2] -= 1;
2308
					$usedmacs[$key] = implode(",", $usedmac);
2309
				}
2310

    
2311
				$found = true;
2312
			} else {
2313
				unset($usedmacs[$key]);
2314
			}
2315

    
2316
			break;
2317
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime) {
2318
			unset($usedmacs[$key]);
2319
		}
2320
	}
2321

    
2322
	if (!$found) {
2323
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2324
		$usedmacs[] = implode(",", $usedmac);
2325
	}
2326

    
2327
	captiveportal_write_usedmacs_db($usedmacs);
2328
	return true;
2329
}
2330

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

    
2334
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2335
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2336
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2337
		if (!$usedmacs) {
2338
			$usedmacs = array();
2339
		}
2340
	} else {
2341
		$usedmacs = array();
2342
	}
2343

    
2344
	unlock($cpumaclck);
2345
	return $usedmacs;
2346
}
2347

    
2348
function captiveportal_write_usedmacs_db($usedmacs) {
2349
	global $g, $cpzone;
2350

    
2351
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2352
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2353
	unlock($cpumaclck);
2354
}
2355

    
2356
function captiveportal_blocked_mac($mac) {
2357
	global $config, $g, $cpzone;
2358

    
2359
	if (empty($mac) || !is_macaddr($mac)) {
2360
		return false;
2361
	}
2362

    
2363
	if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
2364
		return false;
2365
	}
2366

    
2367
	foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac) {
2368
		if (($passthrumac['action'] == 'block') &&
2369
		    ($passthrumac['mac'] == strtolower($mac))) {
2370
			return true;
2371
		}
2372
	}
2373

    
2374
	return false;
2375

    
2376
}
2377

    
2378
function captiveportal_send_server_accounting($off = false) {
2379
	global $cpzone, $config;
2380

    
2381
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
2382
		return;
2383
	}
2384
	if ($off) {
2385
		$racct = new Auth_RADIUS_Acct_Off;
2386
	} else {
2387
		$racct = new Auth_RADIUS_Acct_On;
2388
	}
2389
	$radiusservers = captiveportal_get_radius_servers();
2390
	if (empty($radiusservers)) {
2391
		return;
2392
	}
2393
	foreach ($radiusservers['first'] as $radsrv) {
2394
		// Add a new server to our instance
2395
		$racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
2396
	}
2397
	if (PEAR::isError($racct->start())) {
2398
		$retvalue['acct_val'] = 1;
2399
		$retvalue['error'] = $racct->getMessage();
2400

    
2401
		// If we encounter an error immediately stop this function and go back
2402
		$racct->close();
2403
		return $retvalue;
2404
	}
2405
	// Send request
2406
	$result = $racct->send();
2407
	// Evaluation of the response
2408
	// 5 -> Accounting-Response
2409
	// See RFC2866 for this.
2410
	if (PEAR::isError($result)) {
2411
		$retvalue['acct_val'] = 1;
2412
		$retvalue['error'] = $result->getMessage();
2413
	} else if ($result === true) {
2414
		$retvalue['acct_val'] = 5 ;
2415
	} else {
2416
		$retvalue['acct_val'] = 1 ;
2417
	}
2418

    
2419
	$racct->close();
2420
	return $retvalue;
2421
}
2422

    
2423
function captiveportal_isip_logged($clientip) {
2424
	global $g, $cpzone;
2425

    
2426
	/* read in client database */
2427
	$query = "WHERE ip = '{$clientip}'";
2428
	$cpdb = captiveportal_read_db($query);
2429
	foreach ($cpdb as $cpentry) {
2430
		return $cpentry;
2431
	}
2432
}
2433
?>
(7-7/65)