Project

General

Profile

Download (76.1 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	captiveportal.inc
4
	part of pfSense (https://www.pfsense.org)
5
	Copyright (C) 2004-2011 Scott Ullrich <sullrich@gmail.com>
6
	Copyright (C) 2009-2012 Ermal Luçi <eri@pfsense.org>
7
	Copyright (C) 2003-2006 Manuel Kasper <mk@neon1.net>.
8

    
9
	originally part of m0n0wall (http://m0n0.ch/wall)
10
	All rights reserved.
11

    
12
	Redistribution and use in source and binary forms, with or without
13
	modification, are permitted provided that the following conditions are met:
14

    
15
	1. Redistributions of source code must retain the above copyright notice,
16
	   this list of conditions and the following disclaimer.
17

    
18
	2. Redistributions in binary form must reproduce the above copyright
19
	   notice, this list of conditions and the following disclaimer in the
20
	   documentation and/or other materials provided with the distribution.
21

    
22
	THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
23
	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
24
	AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25
	AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
26
	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27
	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28
	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29
	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31
	POSSIBILITY OF SUCH DAMAGE.
32

    
33
	This version of captiveportal.inc has been modified by Rob Parker
34
	<rob.parker@keycom.co.uk> to include changes for per-user bandwidth management
35
	via returned RADIUS attributes. This page has been modified to delete any
36
	added rules which may have been created by other per-user code (index.php, etc).
37
	These changes are (c) 2004 Keycom PLC.
38

    
39
	pfSense_BUILDER_BINARIES:	/sbin/ipfw	/sbin/route
40
	pfSense_BUILDER_BINARIES:	/usr/local/sbin/lighttpd	/usr/local/bin/minicron /sbin/pfctl
41
	pfSense_BUILDER_BINARIES:	/bin/hostname	/bin/cp
42
	pfSense_MODULE: captiveportal
43
*/
44

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

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

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

    
103
EOD;
104

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

    
112
EOD;
113
	}
114

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

    
142
EOD;
143

    
144
	return $htmltext;
145
}
146

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

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

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

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

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

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

    
185
	$captiveportallck = lock("captiveportal{$cpzone}", LOCK_EX);
186

    
187
	if (isset($cpcfg['enable'])) {
188

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

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

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

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

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

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

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

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

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

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

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

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

    
305
EOD;
306
		}
307

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

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

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

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

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

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

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

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

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

    
352
		captiveportal_radius_stop_all();
353

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

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

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

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

    
387
	unlock($captiveportallck);
388

    
389
	return 0;
390
}
391

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
524
	if ($reinit == false) {
525
		$captiveportallck = lock("captiveportal{$cpzone}");
526
	}
527

    
528
	$cprules = <<<EOD
529

    
530
flush
531
add 65291 allow pfsync from any to any
532
add 65292 allow carp from any to any
533

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

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

    
544
EOD;
545

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

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

    
581
	if (!empty($config['captiveportal'][$cpzone]['listenporthttp'])) {
582
		$listenporthttp = $config['captiveportal'][$cpzone]['listenporthttp'];
583
	} else {
584
		$listenporthttp = 8000 + $cpzoneid;
585
	}
586

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

    
598
	$cprules .= <<<EOD
599

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

    
607
EOD;
608

    
609
	/* generate passthru mac database */
610
	$cprules .= captiveportal_passthrumac_configure(true);
611
	$cprules .= "\n";
612

    
613
	/* allowed ipfw rules to make allowed ip work */
614
	$cprules .= captiveportal_allowedip_configure();
615

    
616
	/* allowed ipfw rules to make allowed hostnames work */
617
	$cprules .= captiveportal_allowedhostname_configure();
618

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

    
625
	if ($reinit == false) {
626
		unlock($captiveportallck);
627
	}
628
}
629

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

    
639
	if (empty($cpzone)) {
640
		return;
641
	}
642

    
643
	$cpcfg = $config['captiveportal'][$cpzone];
644
	$vcpcfg = $config['voucher'][$cpzone];
645

    
646
	/* check for expired entries */
647
	$idletimeout = 0;
648
	$timeout = 0;
649
	if (!empty($cpcfg['timeout']) && is_numeric($cpcfg['timeout'])) {
650
		$timeout = $cpcfg['timeout'] * 60;
651
	}
652

    
653
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout'])) {
654
		$idletimeout = $cpcfg['idletimeout'] * 60;
655
	}
656

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

    
663
	$radiussrvs = captiveportal_get_radius_servers();
664

    
665
	/* Read database */
666
	/* NOTE: while this can be simplified in non radius case keep as is for now */
667
	$cpdb = captiveportal_read_db();
668

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

    
679
		$timedout = false;
680
		$term_cause = 1;
681
		if (empty($cpentry[11])) {
682
			$cpentry[11] = 'first';
683
		}
684
		$radiusservers = $radiussrvs[$cpentry[11]];
685

    
686
		/* hard timeout? */
687
		if ($timeout) {
688
			if (($pruning_time - $cpentry[0]) >= $timeout) {
689
				$timedout = true;
690
				$term_cause = 5; // Session-Timeout
691
			}
692
		}
693

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

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

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

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

    
735
		if ($timedout) {
736
			captiveportal_disconnect($cpentry, $radiusservers, $term_cause, $stop_time);
737
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
738
			$unsetindexes[] = $cpentry[5];
739
		}
740

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

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

    
808
	captiveportal_prune_old_automac();
809

    
810
	if ($voucher_needs_sync == true) {
811
		/* Trigger a sync of the vouchers on config */
812
		send_event("service sync vouchers");
813
	}
814

    
815
	/* write database */
816
	if (!empty($unsetindexes)) {
817
		captiveportal_remove_entries($unsetindexes);
818
	}
819
}
820

    
821
function captiveportal_prune_old_automac() {
822
	global $g, $config, $cpzone, $cpzoneid;
823

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

    
886
/* remove a single client according to the DB entry */
887
function captiveportal_disconnect($dbent, $radiusservers, $term_cause = 1, $stop_time = null) {
888
	global $g, $config, $cpzone, $cpzoneid;
889

    
890
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
891

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

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

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

    
924
		/* Release the ruleno so it can be reallocated to new clients. */
925
		captiveportal_free_dn_ruleno($dbent[1]);
926
	}
927

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

    
937
}
938

    
939
/* remove a single client by sessionid */
940
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
941
	global $g, $config;
942

    
943
	$radiusservers = captiveportal_get_radius_servers();
944

    
945
	/* read database */
946
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
947

    
948
	/* find entry */
949
	if (!empty($result)) {
950
		captiveportal_write_db("DELETE FROM captiveportal WHERE sessionid = '{$sessionid}'");
951

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

    
963
/* send RADIUS acct stop for all current clients */
964
function captiveportal_radius_stop_all() {
965
	global $config, $cpzone;
966

    
967
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
968
		return;
969
	}
970

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

    
992
function captiveportal_passthrumac_configure_entry($macent, $pipeinrule = false) {
993
	global $config, $g, $cpzone;
994

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

    
1008
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1009

    
1010
	if ($macent['action'] == 'pass') {
1011
		$rules = "";
1012
		$pipeno = captiveportal_get_next_dn_ruleno();
1013

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

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

    
1028
		$rules .= "add {$ruleno} pipe {$pipeup} ip from any to any MAC any {$macent['mac']}\n";
1029
		$ruleno++;
1030
		$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC {$macent['mac']} any\n";
1031
	}
1032

    
1033
	return $rules;
1034
}
1035

    
1036
function captiveportal_passthrumac_delete_entry($macent) {
1037
	$rules = "";
1038

    
1039
	if ($macent['action'] == 'pass') {
1040
		$ruleno = captiveportal_get_ipfw_passthru_ruleno($macent['mac']);
1041

    
1042
		if (!$ruleno) {
1043
			return $rules;
1044
		}
1045

    
1046
		captiveportal_free_ipfw_ruleno($ruleno);
1047

    
1048
		$rules .= "delete {$ruleno}\n";
1049
		$rules .= "delete " . ++$ruleno . "\n";
1050

    
1051
		$pipeno = captiveportal_get_dn_passthru_ruleno($macent['mac']);
1052

    
1053
		if (!empty($pipeno)) {
1054
			captiveportal_free_dn_ruleno($pipeno);
1055
			$rules .= "pipe delete " . $pipeno . "\n";
1056
			$rules .= "pipe delete " . ++$pipeno . "\n";
1057
		}
1058
	}
1059

    
1060
	return $rules;
1061
}
1062

    
1063
function captiveportal_passthrumac_configure($filename = false, $startindex = 0, $stopindex = 0) {
1064
	global $config, $g, $cpzone;
1065

    
1066
	$rules = "";
1067

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

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

    
1099
	return $rules;
1100
}
1101

    
1102
function captiveportal_passthrumac_findbyname($username) {
1103
	global $config, $cpzone;
1104

    
1105
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1106
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1107
			if ($macent['username'] == $username) {
1108
				return $macent;
1109
			}
1110
		}
1111
	}
1112
	return NULL;
1113
}
1114

    
1115
/*
1116
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1117
 */
1118
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
1119
	global $g;
1120

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

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

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

    
1170
	if ($ishostname === true) {
1171
		return array($rules, $cp_filterdns_conf);
1172
	} else {
1173
		return $rules;
1174
	}
1175
}
1176

    
1177
function captiveportal_allowedhostname_configure() {
1178
	global $config, $g, $cpzone;
1179

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

    
1202
	return $rules;
1203
}
1204

    
1205
function captiveportal_allowedip_configure() {
1206
	global $config, $g, $cpzone;
1207

    
1208
	$rules = "";
1209
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1210
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) {
1211
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1212
		}
1213
	}
1214

    
1215
	return $rules;
1216
}
1217

    
1218
/* get last activity timestamp given client IP address */
1219
function captiveportal_get_last_activity($ip, $mac = NULL, $table = 1) {
1220
	global $cpzoneid;
1221

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

    
1233
	return 0;
1234
}
1235

    
1236
function captiveportal_init_radius_servers() {
1237
	global $config, $g, $cpzone;
1238

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

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

    
1273
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1274
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1275
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1276
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1277

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

    
1298
		fclose($fd);
1299
		unlock($cprdsrvlck);
1300
	}
1301
}
1302

    
1303
/* read RADIUS servers into array */
1304
function captiveportal_get_radius_servers() {
1305
	global $g, $cpzone;
1306

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

    
1336
	unlock($cprdsrvlck);
1337
	return false;
1338
}
1339

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

    
1353
/* log simple messages to syslog */
1354
function captiveportal_syslog($message) {
1355
	global $cpzone;
1356

    
1357
	$message = trim($message);
1358
	$message = "Zone: {$cpzone} - {$message}";
1359
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1360
	// Log it
1361
	syslog(LOG_INFO, $message);
1362
	closelog();
1363
}
1364

    
1365
function radius($username, $password, $clientip, $clientmac, $type, $radiusctx = null) {
1366
	global $g, $config, $cpzoneid;
1367

    
1368
	$pipeno = captiveportal_get_next_dn_ruleno();
1369

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

    
1378
	$radiusservers = captiveportal_get_radius_servers();
1379

    
1380
	if (is_null($radiusctx)) {
1381
		$radiusctx = 'first';
1382
	}
1383

    
1384
	$auth_list = RADIUS_AUTHENTICATION($username,
1385
		$password,
1386
		$radiusservers[$radiusctx],
1387
		$clientip,
1388
		$clientmac,
1389
		$pipeno);
1390

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

    
1404
	return $auth_list;
1405
}
1406

    
1407
function captiveportal_opendb() {
1408
	global $g, $cpzone;
1409

    
1410
	$DB = new SQLite3("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1411
	if (! $DB->exec("CREATE TABLE IF NOT EXISTS captiveportal (" .
1412
						"allow_time INTEGER, pipeno INTEGER, ip TEXT, mac TEXT, username TEXT, " .
1413
						"sessionid TEXT, bpassword TEXT, session_timeout INTEGER, idle_timeout INTEGER, " .
1414
						"session_terminate_time INTEGER, interim_interval INTEGER, radiusctx TEXT); " .
1415
					"CREATE UNIQUE INDEX IF NOT EXISTS idx_active ON captiveportal (sessionid, username); " .
1416
					"CREATE INDEX IF NOT EXISTS user ON captiveportal (username); " .
1417
					"CREATE INDEX IF NOT EXISTS ip ON captiveportal (ip); " .
1418
					"CREATE INDEX IF NOT EXISTS starttime ON captiveportal (allow_time)")) {
1419
		captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$DB->lastErrorMsg()}");
1420
	}
1421

    
1422
	return $DB;
1423
}
1424

    
1425
/* read captive portal DB into array */
1426
function captiveportal_read_db($query = "") {
1427
	$cpdb = array();
1428

    
1429
	$DB = captiveportal_opendb();
1430
	if ($DB) {
1431
		$response = $DB->query("SELECT * FROM captiveportal {$query}");
1432
		if ($response != FALSE) {
1433
			while ($row = $response->fetchArray()) {
1434
				$cpdb[] = $row;
1435
			}
1436
		}
1437
		$DB->close();
1438
	}
1439

    
1440
	return $cpdb;
1441
}
1442

    
1443
function captiveportal_remove_entries($remove) {
1444

    
1445
	if (!is_array($remove) || empty($remove)) {
1446
		return;
1447
	}
1448

    
1449
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1450
	foreach ($remove as $idx => $unindex) {
1451
		$query .= "'{$unindex}'";
1452
		if ($idx < (count($remove) - 1)) {
1453
			$query .= ",";
1454
		}
1455
	}
1456
	$query .= ")";
1457
	captiveportal_write_db($query);
1458
}
1459

    
1460
/* write captive portal DB */
1461
function captiveportal_write_db($queries) {
1462
	global $g;
1463

    
1464
	if (is_array($queries)) {
1465
		$query = implode(";", $queries);
1466
	} else {
1467
		$query = $queries;
1468
	}
1469

    
1470
	$DB = captiveportal_opendb();
1471
	if ($DB) {
1472
		$DB->exec("BEGIN TRANSACTION");
1473
		$result = $DB->exec($query);
1474
		if (!$result) {
1475
			captiveportal_syslog("Trying to modify DB returned error: {$DB->lastErrorMsg()}");
1476
		} else {
1477
			$DB->exec("END TRANSACTION");
1478
		}
1479
		$DB->close();
1480
		return $result;
1481
	} else {
1482
		return true;
1483
	}
1484
}
1485

    
1486
function captiveportal_write_elements() {
1487
	global $g, $config, $cpzone;
1488

    
1489
	$cpcfg = $config['captiveportal'][$cpzone];
1490

    
1491
	if (!is_dir($g['captiveportal_element_path'])) {
1492
		@mkdir($g['captiveportal_element_path']);
1493
	}
1494

    
1495
	if (is_array($cpcfg['element'])) {
1496
		conf_mount_rw();
1497
		foreach ($cpcfg['element'] as $data) {
1498
			if (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1499
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1500
				return 1;
1501
			}
1502
			if (!file_exists("{$g['captiveportal_path']}/{$data['name']}")) {
1503
				@symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1504
			}
1505
		}
1506
		conf_mount_ro();
1507
	}
1508

    
1509
	return 0;
1510
}
1511

    
1512
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1513
	global $cpzone;
1514

    
1515
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1516
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1517
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1518
		$ridx = $rulenos_start;
1519
		while ($ridx < $rulenos_range_max) {
1520
			if ($rules[$ridx] == $cpzone) {
1521
				$rules[$ridx] = false;
1522
				$ridx++;
1523
				$rules[$ridx] = false;
1524
				$ridx++;
1525
			} else {
1526
				$ridx += 2;
1527
			}
1528
		}
1529
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1530
		unset($rules);
1531
	}
1532
	unlock($cpruleslck);
1533
}
1534

    
1535
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1536
	global $config, $g, $cpzone;
1537

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

    
1565
	return $ruleno;
1566
}
1567

    
1568
function captiveportal_free_dn_ruleno($ruleno) {
1569
	global $config, $g;
1570

    
1571
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1572
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1573
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1574
		$rules[$ruleno] = false;
1575
		$ruleno++;
1576
		$rules[$ruleno] = false;
1577
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1578
		unset($rules);
1579
	}
1580
	unlock($cpruleslck);
1581
}
1582

    
1583
function captiveportal_get_dn_passthru_ruleno($value) {
1584
	global $config, $g, $cpzone, $cpzoneid;
1585

    
1586
	$cpcfg = $config['captiveportal'][$cpzone];
1587
	if (!isset($cpcfg['enable'])) {
1588
		return NULL;
1589
	}
1590

    
1591
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1592
	$ruleno = NULL;
1593
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1594
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1595
		unset($output);
1596
		$_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);
1597
		$ruleno = intval($output[0]);
1598
		if (!$rules[$ruleno]) {
1599
			$ruleno = NULL;
1600
		}
1601
		unset($rules);
1602
	}
1603
	unlock($cpruleslck);
1604

    
1605
	return $ruleno;
1606
}
1607

    
1608
/*
1609
 * This function will calculate the lowest free firewall ruleno
1610
 * within the range specified based on the actual logged on users
1611
 *
1612
 */
1613
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1614
	global $config, $g, $cpzone;
1615

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

    
1621
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1622
	$ruleno = 0;
1623
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1624
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1625
		$ridx = $rulenos_start;
1626
		while ($ridx < $rulenos_range_max) {
1627
			if (empty($rules[$ridx])) {
1628
				$ruleno = $ridx;
1629
				$rules[$ridx] = $cpzone;
1630
				$ridx++;
1631
				$rules[$ridx] = $cpzone;
1632
				break;
1633
			} else {
1634
				/*
1635
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno
1636
				 * and the out pipe ruleno + 1.
1637
				 */
1638
				$ridx += 2;
1639
			}
1640
		}
1641
	} else {
1642
		$rules = array_pad(array(), $rulenos_range_max, false);
1643
		$ruleno = $rulenos_start;
1644
		$rules[$rulenos_start] = $cpzone;
1645
		$rulenos_start++;
1646
		$rules[$rulenos_start] = $cpzone;
1647
	}
1648
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1649
	unlock($cpruleslck);
1650
	unset($rules);
1651

    
1652
	return $ruleno;
1653
}
1654

    
1655
function captiveportal_free_ipfw_ruleno($ruleno) {
1656
	global $config, $g, $cpzone;
1657

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

    
1663
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1664
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1665
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1666
		$rules[$ruleno] = false;
1667
		$ruleno++;
1668
		$rules[$ruleno] = false;
1669
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1670
		unset($rules);
1671
	}
1672
	unlock($cpruleslck);
1673
}
1674

    
1675
function captiveportal_get_ipfw_passthru_ruleno($value) {
1676
	global $config, $g, $cpzone, $cpzoneid;
1677

    
1678
	$cpcfg = $config['captiveportal'][$cpzone];
1679
	if (!isset($cpcfg['enable'])) {
1680
		return NULL;
1681
	}
1682

    
1683
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1684
	$ruleno = NULL;
1685
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1686
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1687
		unset($output);
1688
		$_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);
1689
		$ruleno = intval($output[0]);
1690
		if (!$rules[$ruleno]) {
1691
			$ruleno = NULL;
1692
		}
1693
		unset($rules);
1694
	}
1695
	unlock($cpruleslck);
1696

    
1697
	return $ruleno;
1698
}
1699

    
1700
/**
1701
 * This function will calculate the traffic produced by a client
1702
 * based on its firewall rule
1703
 *
1704
 * Point of view: NAS
1705
 *
1706
 * Input means: from the client
1707
 * Output means: to the client
1708
 *
1709
 */
1710

    
1711
function getVolume($ip, $mac = NULL) {
1712
	global $config, $cpzone, $cpzoneid;
1713

    
1714
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1715
	$volume = array();
1716
	// Initialize vars properly, since we don't want NULL vars
1717
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1718

    
1719
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 1, $ip, $mac);
1720
	if (is_array($ipfw)) {
1721
		if ($reverse) {
1722
			$volume['output_pkts'] = $ipfw['packets'];
1723
			$volume['output_bytes'] = $ipfw['bytes'];
1724
		}
1725
		else {
1726
			$volume['input_pkts'] = $ipfw['packets'];
1727
			$volume['input_bytes'] = $ipfw['bytes'];
1728
		}
1729
	}
1730

    
1731
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 2, $ip, $mac);
1732
	if (is_array($ipfw)) {
1733
		if ($reverse) {
1734
			$volume['input_pkts'] = $ipfw['packets'];
1735
			$volume['input_bytes'] = $ipfw['bytes'];
1736
		}
1737
		else {
1738
			$volume['output_pkts'] = $ipfw['packets'];
1739
			$volume['output_bytes'] = $ipfw['bytes'];
1740
		}
1741
	}
1742

    
1743
	return $volume;
1744
}
1745

    
1746
/**
1747
 * Get the NAS-IP-Address based on the current wan address
1748
 *
1749
 * Use functions in interfaces.inc to find this out
1750
 *
1751
 */
1752

    
1753
function getNasIP() {
1754
	global $config, $cpzone;
1755

    
1756
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1757
			$nasIp = get_interface_ip();
1758
	} else {
1759
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1760
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1761
		} else {
1762
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1763
		}
1764
	}
1765

    
1766
	if (!is_ipaddr($nasIp)) {
1767
		$nasIp = "0.0.0.0";
1768
	}
1769

    
1770
	return $nasIp;
1771
}
1772

    
1773
function portal_ip_from_client_ip($cliip) {
1774
	global $config, $cpzone;
1775

    
1776
	$isipv6 = is_ipaddrv6($cliip);
1777
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1778
	foreach ($interfaces as $cpif) {
1779
		if ($isipv6) {
1780
			$ip = get_interface_ipv6($cpif);
1781
			$sn = get_interface_subnetv6($cpif);
1782
		} else {
1783
			$ip = get_interface_ip($cpif);
1784
			$sn = get_interface_subnet($cpif);
1785
		}
1786
		if (ip_in_subnet($cliip, "{$ip}/{$sn}")) {
1787
			return $ip;
1788
		}
1789
	}
1790

    
1791
	$inet = ($isipv6) ? '-inet6' : '-inet';
1792
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1793
	$iface = trim($iface, "\n");
1794
	if (!empty($iface)) {
1795
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1796
		if (is_ipaddr($ip)) {
1797
			return $ip;
1798
		}
1799
	}
1800

    
1801
	// doesn't match up to any particular interface
1802
	// so let's set the portal IP to what PHP says
1803
	// the server IP issuing the request is.
1804
	// allows same behavior as 1.2.x where IP isn't
1805
	// in the subnet of any CP interface (static routes, etc.)
1806
	// rather than forcing to DNS hostname resolution
1807
	$ip = $_SERVER['SERVER_ADDR'];
1808
	if (is_ipaddr($ip)) {
1809
		return $ip;
1810
	}
1811

    
1812
	return false;
1813
}
1814

    
1815
function portal_hostname_from_client_ip($cliip) {
1816
	global $config, $cpzone;
1817

    
1818
	$cpcfg = $config['captiveportal'][$cpzone];
1819

    
1820
	if (isset($cpcfg['httpslogin'])) {
1821
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 8001);
1822
		$ourhostname = $cpcfg['httpsname'];
1823

    
1824
		if ($listenporthttps != 443) {
1825
			$ourhostname .= ":" . $listenporthttps;
1826
		}
1827
	} else {
1828
		$listenporthttp  = $cpcfg['listenporthttp']  ? $cpcfg['listenporthttp']  : ($cpcfg['zoneid'] + 8000);
1829
		$ifip = portal_ip_from_client_ip($cliip);
1830
		if (!$ifip) {
1831
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1832
		} else {
1833
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1834
		}
1835

    
1836
		if ($listenporthttp != 80) {
1837
			$ourhostname .= ":" . $listenporthttp;
1838
		}
1839
	}
1840

    
1841
	return $ourhostname;
1842
}
1843

    
1844
/* functions move from index.php */
1845

    
1846
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1847
	global $g, $config, $cpzone;
1848

    
1849
	/* Get captive portal layout */
1850
	if ($type == "redir") {
1851
		header("Location: {$redirurl}");
1852
		return;
1853
	} else if ($type == "login") {
1854
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1855
	} else {
1856
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1857
	}
1858

    
1859
	$cpcfg = $config['captiveportal'][$cpzone];
1860

    
1861
	/* substitute the PORTAL_REDIRURL variable */
1862
	if ($cpcfg['preauthurl']) {
1863
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1864
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1865
	}
1866

    
1867
	/* substitute other variables */
1868
	$ourhostname = portal_hostname_from_client_ip($clientip);
1869
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1870
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1871
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1872

    
1873
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1874
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1875
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1876
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1877
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1878

    
1879
	// Special handling case for captive portal master page so that it can be ran
1880
	// through the PHP interpreter using the include method above.  We convert the
1881
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1882
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1883
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1884
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1885
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1886
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1887
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1888
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1889

    
1890
	echo $htmltext;
1891
}
1892

    
1893
function portal_mac_radius($clientmac, $clientip) {
1894
	global $config, $cpzone;
1895

    
1896
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1897

    
1898
	/* authentication against the radius server */
1899
	$username = mac_format($clientmac);
1900
	$auth_list = radius($username, $radmac_secret, $clientip, $clientmac, "MACHINE LOGIN");
1901
	if ($auth_list['auth_val'] == 2) {
1902
		return TRUE;
1903
	}
1904

    
1905
	if (!empty($auth_list['url_redirection'])) {
1906
		portal_reply_page($auth_list['url_redirection'], "redir");
1907
	}
1908

    
1909
	return FALSE;
1910
}
1911

    
1912
function captiveportal_reapply_attributes($cpentry, $attributes) {
1913
	global $config, $cpzone, $g;
1914

    
1915
	if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
1916
		$dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1917
		$dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1918
	} else {
1919
		$dwfaultbw_up = $dwfaultbw_down = 0;
1920
	}
1921
	$bw_up = !empty($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1922
	$bw_down = !empty($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1923
	$bw_up_pipeno = $cpentry[1];
1924
	$bw_down_pipeno = $cpentry[1]+1;
1925

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

    
1930
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1931
}
1932

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

    
1936
	// Ensure we create an array if we are missing attributes
1937
	if (!is_array($attributes)) {
1938
		$attributes = array();
1939
	}
1940

    
1941
	unset($sessionid);
1942

    
1943
	/* Do not allow concurrent login execution. */
1944
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1945

    
1946
	if ($attributes['voucher']) {
1947
		$remaining_time = $attributes['session_timeout'];
1948
	}
1949

    
1950
	$writecfg = false;
1951
	/* Find an existing session */
1952
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
1953
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1954
			$mac = captiveportal_passthrumac_findbyname($username);
1955
			if (!empty($mac)) {
1956
				if ($_POST['replacemacpassthru']) {
1957
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
1958
						if ($macent['mac'] == $mac['mac']) {
1959
							$macrules = "";
1960
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1961
							$pipeno = captiveportal_get_dn_passthru_ruleno($mac['mac']);
1962
							if ($ruleno) {
1963
								captiveportal_free_ipfw_ruleno($ruleno);
1964
								$macrules .= "delete {$ruleno}\n";
1965
								++$ruleno;
1966
								$macrules .= "delete {$ruleno}\n";
1967
							}
1968
							if ($pipeno) {
1969
								captiveportal_free_dn_ruleno($pipeno);
1970
								$macrules .= "pipe delete {$pipeno}\n";
1971
								++$pipeno;
1972
								$macrules .= "pipe delete {$pipeno}\n";
1973
							}
1974
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1975
							$mac['action'] = 'pass';
1976
							$mac['mac'] = $clientmac;
1977
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1978
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1979
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1980
							mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1981
							$writecfg = true;
1982
							$sessionid = true;
1983
							break;
1984
						}
1985
					}
1986
				} else {
1987
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1988
						$clientmac, $clientip, $username, $password);
1989
					unlock($cpdblck);
1990
					return;
1991
				}
1992
			}
1993
		}
1994
	}
1995

    
1996
	/* read in client database */
1997
	$query = "WHERE ip = '{$clientip}'";
1998
	$tmpusername = strtolower($username);
1999
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) {
2000
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
2001
	}
2002
	$cpdb = captiveportal_read_db($query);
2003

    
2004
	/* Snapshot the timestamp */
2005
	$allow_time = time();
2006
	$radiusservers = captiveportal_get_radius_servers();
2007
	$unsetindexes = array();
2008
	if (is_null($radiusctx)) {
2009
		$radiusctx = 'first';
2010
	}
2011

    
2012
	foreach ($cpdb as $cpentry) {
2013
		if (empty($cpentry[11])) {
2014
			$cpentry[11] = 'first';
2015
		}
2016
		/* on the same ip */
2017
		if ($cpentry[2] == $clientip) {
2018
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac) {
2019
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - REUSING OLD SESSION");
2020
			} else {
2021
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
2022
			}
2023
			$sessionid = $cpentry[5];
2024
			break;
2025
		} elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
2026
			// user logged in with an active voucher. Check for how long and calculate
2027
			// how much time we can give him (voucher credit - used time)
2028
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
2029
			if ($remaining_time < 0) { // just in case.
2030
				$remaining_time = 0;
2031
			}
2032

    
2033
			/* This user was already logged in so we disconnect the old one */
2034
			captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], 13);
2035
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - TERMINATING OLD SESSION");
2036
			$unsetindexes[] = $cpentry[5];
2037
			break;
2038
		} elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
2039
			/* on the same username */
2040
			if (strcasecmp($cpentry[4], $username) == 0) {
2041
				/* This user was already logged in so we disconnect the old one */
2042
				captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], 13);
2043
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - TERMINATING OLD SESSION");
2044
				$unsetindexes[] = $cpentry[5];
2045
				break;
2046
			}
2047
		}
2048
	}
2049
	unset($cpdb);
2050

    
2051
	if (!empty($unsetindexes)) {
2052
		captiveportal_remove_entries($unsetindexes);
2053
	}
2054

    
2055
	if ($attributes['voucher'] && $remaining_time <= 0) {
2056
		return 0;       // voucher already used and no time left
2057
	}
2058

    
2059
	if (!isset($sessionid)) {
2060
		/* generate unique session ID */
2061
		$tod = gettimeofday();
2062
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
2063

    
2064
		if ($passthrumac) {
2065
			$mac = array();
2066
			$mac['action'] = 'pass';
2067
			$mac['mac'] = $clientmac;
2068
			$mac['ip'] = $clientip; /* Used only for logging */
2069
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
2070
				$mac['username'] = $username;
2071
				if ($attributes['voucher']) {
2072
					$mac['logintype'] = "voucher";
2073
				}
2074
			}
2075
			if ($username == "unauthenticated") {
2076
				$mac['descr'] =  "Auto-added";
2077
			} else {
2078
				$mac['descr'] =  "Auto-added for user {$username}";
2079
			}
2080
			if (!empty($bw_up)) {
2081
				$mac['bw_up'] = $bw_up;
2082
			}
2083
			if (!empty($bw_down)) {
2084
				$mac['bw_down'] = $bw_down;
2085
			}
2086
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
2087
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
2088
			}
2089
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
2090
			unlock($cpdblck);
2091
			$macrules = captiveportal_passthrumac_configure_entry($mac);
2092
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
2093
			mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
2094
			$writecfg = true;
2095
		} else {
2096
			/* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
2097
			if (is_null($pipeno)) {
2098
				$pipeno = captiveportal_get_next_dn_ruleno();
2099
			}
2100

    
2101
			/* if the pool is empty, return appropriate message and exit */
2102
			if (is_null($pipeno)) {
2103
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
2104
				log_error("Zone: {$cpzone} - WARNING!  Captive portal has reached maximum login capacity");
2105
				unlock($cpdblck);
2106
				return;
2107
			}
2108

    
2109
			if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
2110
				$dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
2111
				$dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
2112
			} else {
2113
				$dwfaultbw_up = $dwfaultbw_down = 0;
2114
			}
2115
			$bw_up = !empty($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
2116
			$bw_down = !empty($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
2117

    
2118
			$bw_up_pipeno = $pipeno;
2119
			$bw_down_pipeno = $pipeno + 1;
2120
			//$bw_up /= 1000; // Scale to Kbit/s
2121
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
2122
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
2123

    
2124
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
2125
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
2126
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
2127
			} else {
2128
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
2129
			}
2130

    
2131
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
2132
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
2133
			} else {
2134
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
2135
			}
2136

    
2137
			if ($attributes['voucher']) {
2138
				$attributes['session_timeout'] = $remaining_time;
2139
			}
2140

    
2141
			/* handle empty attributes */
2142
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
2143
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
2144
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
2145
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
2146

    
2147
			/* escape username */
2148
			$safe_username = SQLite3::escapeString($username);
2149

    
2150
			/* encode password in Base64 just in case it contains commas */
2151
			$bpassword = base64_encode($password);
2152
			$insertquery  = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, radiusctx) ";
2153
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
2154
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, '{$radiusctx}')";
2155

    
2156
			/* store information to database */
2157
			captiveportal_write_db($insertquery);
2158
			unlock($cpdblck);
2159
			unset($insertquery, $bpassword);
2160

    
2161
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
2162
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
2163
				if ($acct_val == 1) {
2164
					captiveportal_logportalauth($username, $clientmac, $clientip, $type, "RADIUS ACCOUNTING FAILED");
2165
				}
2166
			}
2167
		}
2168
	} else {
2169
		/* NOTE: #3062-11 If the pipeno has been allocated free it to not DoS the CP and maintain proper operation as in radius() case */
2170
		if (!is_null($pipeno)) {
2171
			captiveportal_free_dn_ruleno($pipeno);
2172
		}
2173

    
2174
		unlock($cpdblck);
2175
	}
2176

    
2177
	if ($writecfg == true) {
2178
		write_config();
2179
	}
2180

    
2181
	/* redirect user to desired destination */
2182
	if (!empty($attributes['url_redirection'])) {
2183
		$my_redirurl = $attributes['url_redirection'];
2184
	} else if (!empty($redirurl)) {
2185
		$my_redirurl = $redirurl;
2186
	} else if (!empty($config['captiveportal'][$cpzone]['redirurl'])) {
2187
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
2188
	}
2189

    
2190
	if (isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
2191
		$ourhostname = portal_hostname_from_client_ip($clientip);
2192
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
2193
		$logouturl = "{$protocol}{$ourhostname}/";
2194

    
2195
		if (isset($attributes['reply_message'])) {
2196
			$message = $attributes['reply_message'];
2197
		} else {
2198
			$message = 0;
2199
		}
2200

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

    
2203
	} else {
2204
		portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
2205
	}
2206

    
2207
	return $sessionid;
2208
}
2209

    
2210

    
2211
/*
2212
 * Used for when pass-through credits are enabled.
2213
 * Returns true when there was at least one free login to deduct for the MAC.
2214
 * Expired entries are removed as they are seen.
2215
 * Active entries are updated according to the configuration.
2216
 */
2217
function portal_consume_passthrough_credit($clientmac) {
2218
	global $config, $cpzone;
2219

    
2220
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count'])) {
2221
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
2222
	} else {
2223
		return false;
2224
	}
2225

    
2226
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout'])) {
2227
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2228
	} else {
2229
		return false;
2230
	}
2231

    
2232
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac) {
2233
		return false;
2234
	}
2235

    
2236
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2237

    
2238
	/*
2239
	 * Read database of used MACs.  Lines are a comma-separated list
2240
	 * of the time, MAC, then the count of pass-through credits remaining.
2241
	 */
2242
	$usedmacs = captiveportal_read_usedmacs_db();
2243

    
2244
	$currenttime = time();
2245
	$found = false;
2246
	foreach ($usedmacs as $key => $usedmac) {
2247
		$usedmac = explode(",", $usedmac);
2248

    
2249
		if ($usedmac[1] == $clientmac) {
2250
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2251
				if ($usedmac[2] < 1) {
2252
					if ($updatetimeouts) {
2253
						$usedmac[0] = $currenttime;
2254
						unset($usedmacs[$key]);
2255
						$usedmacs[] = implode(",", $usedmac);
2256
						captiveportal_write_usedmacs_db($usedmacs);
2257
					}
2258

    
2259
					return false;
2260
				} else {
2261
					$usedmac[2] -= 1;
2262
					$usedmacs[$key] = implode(",", $usedmac);
2263
				}
2264

    
2265
				$found = true;
2266
			} else {
2267
				unset($usedmacs[$key]);
2268
			}
2269

    
2270
			break;
2271
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime) {
2272
			unset($usedmacs[$key]);
2273
		}
2274
	}
2275

    
2276
	if (!$found) {
2277
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2278
		$usedmacs[] = implode(",", $usedmac);
2279
	}
2280

    
2281
	captiveportal_write_usedmacs_db($usedmacs);
2282
	return true;
2283
}
2284

    
2285
function captiveportal_read_usedmacs_db() {
2286
	global $g, $cpzone;
2287

    
2288
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2289
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2290
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2291
		if (!$usedmacs) {
2292
			$usedmacs = array();
2293
		}
2294
	} else {
2295
		$usedmacs = array();
2296
	}
2297

    
2298
	unlock($cpumaclck);
2299
	return $usedmacs;
2300
}
2301

    
2302
function captiveportal_write_usedmacs_db($usedmacs) {
2303
	global $g, $cpzone;
2304

    
2305
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2306
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2307
	unlock($cpumaclck);
2308
}
2309

    
2310
function captiveportal_blocked_mac($mac) {
2311
	global $config, $g, $cpzone;
2312

    
2313
	if (empty($mac) || !is_macaddr($mac)) {
2314
		return false;
2315
	}
2316

    
2317
	if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
2318
		return false;
2319
	}
2320

    
2321
	foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac) {
2322
		if (($passthrumac['action'] == 'block') &&
2323
		    ($passthrumac['mac'] == strtolower($mac))) {
2324
			return true;
2325
		}
2326
	}
2327

    
2328
	return false;
2329

    
2330
}
2331

    
2332
function captiveportal_send_server_accounting($off = false) {
2333
	global $cpzone, $config;
2334

    
2335
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
2336
		return;
2337
	}
2338
	if ($off) {
2339
		$racct = new Auth_RADIUS_Acct_Off;
2340
	} else {
2341
		$racct = new Auth_RADIUS_Acct_On;
2342
	}
2343
	$radiusservers = captiveportal_get_radius_servers();
2344
	if (empty($radiusservers)) {
2345
		return;
2346
	}
2347
	foreach ($radiusservers['first'] as $radsrv) {
2348
		// Add a new server to our instance
2349
		$racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
2350
	}
2351
	if (PEAR::isError($racct->start())) {
2352
		$retvalue['acct_val'] = 1;
2353
		$retvalue['error'] = $racct->getMessage();
2354

    
2355
		// If we encounter an error immediately stop this function and go back
2356
		$racct->close();
2357
		return $retvalue;
2358
	}
2359
	// Send request
2360
	$result = $racct->send();
2361
	// Evaluation of the response
2362
	// 5 -> Accounting-Response
2363
	// See RFC2866 for this.
2364
	if (PEAR::isError($result)) {
2365
		$retvalue['acct_val'] = 1;
2366
		$retvalue['error'] = $result->getMessage();
2367
	} else if ($result === true) {
2368
		$retvalue['acct_val'] = 5 ;
2369
	} else {
2370
		$retvalue['acct_val'] = 1 ;
2371
	}
2372

    
2373
	$racct->close();
2374
	return $retvalue;
2375
}
2376
?>
(7-7/67)