Project

General

Profile

Download (76 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
		return $ipfwoutput['timestamp'];
1226
	}
1227

    
1228
	return 0;
1229
}
1230

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1363
	$pipeno = captiveportal_get_next_dn_ruleno();
1364

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

    
1373
	$radiusservers = captiveportal_get_radius_servers();
1374

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

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

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

    
1399
	return $auth_list;
1400
}
1401

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

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

    
1417
	return $DB;
1418
}
1419

    
1420
/* read captive portal DB into array */
1421
function captiveportal_read_db($query = "") {
1422
	$cpdb = array();
1423

    
1424
	$DB = captiveportal_opendb();
1425
	if ($DB) {
1426
		$response = $DB->query("SELECT * FROM captiveportal {$query}");
1427
		if ($response != FALSE) {
1428
			while ($row = $response->fetchArray()) {
1429
				$cpdb[] = $row;
1430
			}
1431
		}
1432
		$DB->close();
1433
	}
1434

    
1435
	return $cpdb;
1436
}
1437

    
1438
function captiveportal_remove_entries($remove) {
1439

    
1440
	if (!is_array($remove) || empty($remove)) {
1441
		return;
1442
	}
1443

    
1444
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1445
	foreach ($remove as $idx => $unindex) {
1446
		$query .= "'{$unindex}'";
1447
		if ($idx < (count($remove) - 1)) {
1448
			$query .= ",";
1449
		}
1450
	}
1451
	$query .= ")";
1452
	captiveportal_write_db($query);
1453
}
1454

    
1455
/* write captive portal DB */
1456
function captiveportal_write_db($queries) {
1457
	global $g;
1458

    
1459
	if (is_array($queries)) {
1460
		$query = implode(";", $queries);
1461
	} else {
1462
		$query = $queries;
1463
	}
1464

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

    
1481
function captiveportal_write_elements() {
1482
	global $g, $config, $cpzone;
1483

    
1484
	$cpcfg = $config['captiveportal'][$cpzone];
1485

    
1486
	if (!is_dir($g['captiveportal_element_path'])) {
1487
		@mkdir($g['captiveportal_element_path']);
1488
	}
1489

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

    
1504
	return 0;
1505
}
1506

    
1507
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1508
	global $cpzone;
1509

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

    
1530
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1531
	global $config, $g, $cpzone;
1532

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

    
1560
	return $ruleno;
1561
}
1562

    
1563
function captiveportal_free_dn_ruleno($ruleno) {
1564
	global $config, $g;
1565

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

    
1578
function captiveportal_get_dn_passthru_ruleno($value) {
1579
	global $config, $g, $cpzone, $cpzoneid;
1580

    
1581
	$cpcfg = $config['captiveportal'][$cpzone];
1582
	if (!isset($cpcfg['enable'])) {
1583
		return NULL;
1584
	}
1585

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

    
1600
	return $ruleno;
1601
}
1602

    
1603
/*
1604
 * This function will calculate the lowest free firewall ruleno
1605
 * within the range specified based on the actual logged on users
1606
 *
1607
 */
1608
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1609
	global $config, $g, $cpzone;
1610

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

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

    
1647
	return $ruleno;
1648
}
1649

    
1650
function captiveportal_free_ipfw_ruleno($ruleno) {
1651
	global $config, $g, $cpzone;
1652

    
1653
	$cpcfg = $config['captiveportal'][$cpzone];
1654
	if (!isset($cpcfg['enable'])) {
1655
		return NULL;
1656
	}
1657

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

    
1670
function captiveportal_get_ipfw_passthru_ruleno($value) {
1671
	global $config, $g, $cpzone, $cpzoneid;
1672

    
1673
	$cpcfg = $config['captiveportal'][$cpzone];
1674
	if (!isset($cpcfg['enable'])) {
1675
		return NULL;
1676
	}
1677

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

    
1692
	return $ruleno;
1693
}
1694

    
1695
/**
1696
 * This function will calculate the traffic produced by a client
1697
 * based on its firewall rule
1698
 *
1699
 * Point of view: NAS
1700
 *
1701
 * Input means: from the client
1702
 * Output means: to the client
1703
 *
1704
 */
1705

    
1706
function getVolume($ip, $mac = NULL) {
1707
	global $config, $cpzone, $cpzoneid;
1708

    
1709
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1710
	$volume = array();
1711
	// Initialize vars properly, since we don't want NULL vars
1712
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1713

    
1714
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 1, $ip, $mac);
1715
	if (is_array($ipfw)) {
1716
		if ($reverse) {
1717
			$volume['output_pkts'] = $ipfw['packets'];
1718
			$volume['output_bytes'] = $ipfw['bytes'];
1719
		}
1720
		else {
1721
			$volume['input_pkts'] = $ipfw['packets'];
1722
			$volume['input_bytes'] = $ipfw['bytes'];
1723
		}
1724
	}
1725

    
1726
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 2, $ip, $mac);
1727
	if (is_array($ipfw)) {
1728
		if ($reverse) {
1729
			$volume['input_pkts'] = $ipfw['packets'];
1730
			$volume['input_bytes'] = $ipfw['bytes'];
1731
		}
1732
		else {
1733
			$volume['output_pkts'] = $ipfw['packets'];
1734
			$volume['output_bytes'] = $ipfw['bytes'];
1735
		}
1736
	}
1737

    
1738
	return $volume;
1739
}
1740

    
1741
/**
1742
 * Get the NAS-IP-Address based on the current wan address
1743
 *
1744
 * Use functions in interfaces.inc to find this out
1745
 *
1746
 */
1747

    
1748
function getNasIP()
1749
{
1750
	global $config, $cpzone;
1751

    
1752
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1753
			$nasIp = get_interface_ip();
1754
	} else {
1755
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1756
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1757
		} else {
1758
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1759
		}
1760
	}
1761

    
1762
	if (!is_ipaddr($nasIp)) {
1763
		$nasIp = "0.0.0.0";
1764
	}
1765

    
1766
	return $nasIp;
1767
}
1768

    
1769
function portal_ip_from_client_ip($cliip) {
1770
	global $config, $cpzone;
1771

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

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

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

    
1808
	return false;
1809
}
1810

    
1811
function portal_hostname_from_client_ip($cliip) {
1812
	global $config, $cpzone;
1813

    
1814
	$cpcfg = $config['captiveportal'][$cpzone];
1815

    
1816
	if (isset($cpcfg['httpslogin'])) {
1817
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 8001);
1818
		$ourhostname = $cpcfg['httpsname'];
1819

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

    
1832
		if ($listenporthttp != 80) {
1833
			$ourhostname .= ":" . $listenporthttp;
1834
		}
1835
	}
1836

    
1837
	return $ourhostname;
1838
}
1839

    
1840
/* functions move from index.php */
1841

    
1842
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1843
	global $g, $config, $cpzone;
1844

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

    
1855
	$cpcfg = $config['captiveportal'][$cpzone];
1856

    
1857
	/* substitute the PORTAL_REDIRURL variable */
1858
	if ($cpcfg['preauthurl']) {
1859
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1860
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1861
	}
1862

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

    
1869
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1870
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1871
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1872
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1873
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1874

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

    
1886
	echo $htmltext;
1887
}
1888

    
1889
function portal_mac_radius($clientmac,$clientip) {
1890
	global $config, $cpzone;
1891

    
1892
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1893

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

    
1901
	if (!empty($auth_list['url_redirection'])) {
1902
		portal_reply_page($auth_list['url_redirection'], "redir");
1903
	}
1904

    
1905
	return FALSE;
1906
}
1907

    
1908
function captiveportal_reapply_attributes($cpentry, $attributes) {
1909
	global $config, $cpzone, $g;
1910

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

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

    
1926
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1927
}
1928

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

    
1932
	// Ensure we create an array if we are missing attributes
1933
	if (!is_array($attributes)) {
1934
		$attributes = array();
1935
	}
1936

    
1937
	unset($sessionid);
1938

    
1939
	/* Do not allow concurrent login execution. */
1940
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1941

    
1942
	if ($attributes['voucher']) {
1943
		$remaining_time = $attributes['session_timeout'];
1944
	}
1945

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

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

    
2000
	/* Snapshot the timestamp */
2001
	$allow_time = time();
2002
	$radiusservers = captiveportal_get_radius_servers();
2003
	$unsetindexes = array();
2004
	if (is_null($radiusctx)) {
2005
		$radiusctx = 'first';
2006
	}
2007

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

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

    
2047
	if (!empty($unsetindexes)) {
2048
		captiveportal_remove_entries($unsetindexes);
2049
	}
2050

    
2051
	if ($attributes['voucher'] && $remaining_time <= 0) {
2052
		return 0;       // voucher already used and no time left
2053
	}
2054

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

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

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

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

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

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

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

    
2133
			if ($attributes['voucher']) {
2134
				$attributes['session_timeout'] = $remaining_time;
2135
			}
2136

    
2137
			/* handle empty attributes */
2138
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
2139
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
2140
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
2141
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
2142

    
2143
			/* escape username */
2144
			$safe_username = SQLite3::escapeString($username);
2145

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

    
2152
			/* store information to database */
2153
			captiveportal_write_db($insertquery);
2154
			unlock($cpdblck);
2155
			unset($insertquery, $bpassword);
2156

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

    
2170
		unlock($cpdblck);
2171
	}
2172

    
2173
	if ($writecfg == true) {
2174
		write_config();
2175
	}
2176

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

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

    
2191
		if (isset($attributes['reply_message'])) {
2192
			$message = $attributes['reply_message'];
2193
		} else {
2194
			$message = 0;
2195
		}
2196

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

    
2199
	} else {
2200
		portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
2201
	}
2202

    
2203
	return $sessionid;
2204
}
2205

    
2206

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

    
2216
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count'])) {
2217
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
2218
	} else {
2219
		return false;
2220
	}
2221

    
2222
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout'])) {
2223
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2224
	} else {
2225
		return false;
2226
	}
2227

    
2228
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac) {
2229
		return false;
2230
	}
2231

    
2232
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2233

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

    
2240
	$currenttime = time();
2241
	$found = false;
2242
	foreach ($usedmacs as $key => $usedmac) {
2243
		$usedmac = explode(",", $usedmac);
2244

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

    
2255
					return false;
2256
				} else {
2257
					$usedmac[2] -= 1;
2258
					$usedmacs[$key] = implode(",", $usedmac);
2259
				}
2260

    
2261
				$found = true;
2262
			} else {
2263
				unset($usedmacs[$key]);
2264
			}
2265

    
2266
			break;
2267
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime) {
2268
			unset($usedmacs[$key]);
2269
		}
2270
	}
2271

    
2272
	if (!$found) {
2273
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2274
		$usedmacs[] = implode(",", $usedmac);
2275
	}
2276

    
2277
	captiveportal_write_usedmacs_db($usedmacs);
2278
	return true;
2279
}
2280

    
2281
function captiveportal_read_usedmacs_db() {
2282
	global $g, $cpzone;
2283

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

    
2294
	unlock($cpumaclck);
2295
	return $usedmacs;
2296
}
2297

    
2298
function captiveportal_write_usedmacs_db($usedmacs) {
2299
	global $g, $cpzone;
2300

    
2301
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2302
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2303
	unlock($cpumaclck);
2304
}
2305

    
2306
function captiveportal_blocked_mac($mac) {
2307
	global $config, $g, $cpzone;
2308

    
2309
	if (empty($mac) || !is_macaddr($mac)) {
2310
		return false;
2311
	}
2312

    
2313
	if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
2314
		return false;
2315
	}
2316

    
2317
	foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac) {
2318
		if (($passthrumac['action'] == 'block') &&
2319
		    ($passthrumac['mac'] == strtolower($mac))) {
2320
			return true;
2321
		}
2322
	}
2323

    
2324
	return false;
2325

    
2326
}
2327

    
2328
function captiveportal_send_server_accounting($off = false) {
2329
	global $cpzone, $config;
2330

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

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

    
2369
	$racct->close();
2370
	return $retvalue;
2371
}
2372
?>
(8-8/68)