Project

General

Profile

Download (75.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
		/* init ipfw rules */
198
		captiveportal_init_rules(true);
199

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

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

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

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

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

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

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

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

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

    
303
EOD;
304
		}
305

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

    
313
		/* write elements */
314
		captiveportal_write_elements();
315

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

    
320
		/* start up the webserving daemon */
321
		captiveportal_init_webgui_zone($cpcfg);
322

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

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

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

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

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

    
349
		captiveportal_radius_stop_all();
350

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

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

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

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

    
381
		}
382
	}
383

    
384
	unlock($captiveportallck);
385
	
386
	return 0;
387
}
388

    
389
function captiveportal_init_webgui() {
390
	global $config, $cpzone;
391

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

    
400
function captiveportal_init_webgui_zonename($zone) {
401
	global $config, $cpzone;
402
	
403
	if (isset($config['captiveportal'][$zone])) {
404
		$cpzone = $zone;
405
		captiveportal_init_webgui_zone($config['captiveportal'][$zone]);
406
	}
407
}
408

    
409
function captiveportal_init_webgui_zone($cpcfg) {
410
	global $g, $config, $cpzone;
411

    
412
	if (!isset($cpcfg['enable']))
413
		return;
414

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

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

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

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

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

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

    
454
	if (!is_array($config['captiveportal']))
455
		return;
456

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

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

    
472
	if (!isset($config['captiveportal'][$cpzone]['enable']))
473
		return;
474

    
475
	captiveportal_load_modules();
476
	mwexec("/sbin/ipfw zone {$cpzoneid} create", true);
477

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

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

    
513
	if ($reinit == false)
514
		$captiveportallck = lock("captiveportal{$cpzone}");
515

    
516
	$cprules = <<<EOD
517

    
518
flush
519
add 65291 allow pfsync from any to any
520
add 65292 allow carp from any to any
521

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

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

    
532
EOD;
533

    
534
	$rulenum = 65310;
535
	/* These tables contain host ips */
536
	$cprules .= "add {$rulenum} pass ip from any to table(100) in\n";
537
	$rulenum++;
538
	$cprules .= "add {$rulenum} pass ip from table(100) to any out\n";
539
	$rulenum++;
540
	$ips = "";
541
	foreach ($cpips as $cpip) {
542
		$cprules .= "table 100 add {$cpip}\n";
543
	}
544
	$cprules .= "table 100 add 255.255.255.255\n";
545
	$cprules .= "add {$rulenum} pass ip from any to {$ips} in\n";
546
	$rulenum++;
547
	$cprules .= "add {$rulenum} pass ip from {$ips} to any out\n";
548
	$rulenum++;
549
	$cprules .= "add {$rulenum} pass icmp from {$ips} to any out icmptype 0\n";
550
	$rulenum++;
551
	$cprules .= "add {$rulenum} pass icmp from any to {$ips} in icmptype 8 \n";
552
	$rulenum++;
553
	/* Allowed ips */
554
	$cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any in\n";
555
	$rulenum++;
556
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) in\n";
557
	$rulenum++;
558
	$cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any out\n";
559
	$rulenum++;
560
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) out\n";
561
	$rulenum++;
562

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

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

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

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

    
593
EOD;
594

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

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

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

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

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

    
624
	if (empty($cpzone))
625
		return;
626

    
627
	$cpcfg = $config['captiveportal'][$cpzone];
628
	$vcpcfg = $config['voucher'][$cpzone];
629

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

    
636
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout']))
637
		$idletimeout = $cpcfg['idletimeout'] * 60;
638

    
639
	/* Is there any job to do? */
640
	if (!$timeout && !$idletimeout && !isset($cpcfg['reauthenticate']) &&
641
	    !isset($cpcfg['radiussession_timeout']) && !isset($vcpcfg['enable']))
642
		return;
643

    
644
	$radiussrvs = captiveportal_get_radius_servers();
645

    
646
	/* Read database */
647
	/* NOTE: while this can be simplified in non radius case keep as is for now */
648
	$cpdb = captiveportal_read_db();
649

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

    
660
		$timedout = false;
661
		$term_cause = 1;
662
		if (empty($cpentry[11]))
663
			$cpentry[11] = 'first';
664
		$radiusservers = $radiussrvs[$cpentry[11]];
665

    
666
		/* hard timeout? */
667
		if ($timeout) {
668
			if (($pruning_time - $cpentry[0]) >= $timeout) {
669
				$timedout = true;
670
				$term_cause = 5; // Session-Timeout
671
			}
672
		}
673

    
674
		/* Session-Terminate-Time */
675
		if (!$timedout && !empty($cpentry[9])) {
676
			if ($pruning_time >= $cpentry[9]) {
677
				$timedout = true;
678
				$term_cause = 5; // Session-Timeout
679
			}
680
		}
681

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

    
698
		/* if vouchers are configured, activate session timeouts */
699
		if (!$timedout && isset($vcpcfg['enable']) && !empty($cpentry[7])) {
700
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
701
				$timedout = true;
702
				$term_cause = 5; // Session-Timeout
703
				$voucher_needs_sync = true;
704
			}
705
		}
706

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

    
715
		if ($timedout) {
716
			captiveportal_disconnect($cpentry, $radiusservers,$term_cause,$stop_time);
717
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
718
			$unsetindexes[] = $cpentry[5];
719
		}
720

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

    
766
			/* check this user against RADIUS again */
767
			if (isset($cpcfg['reauthenticate'])) {
768
				$auth_list = RADIUS_AUTHENTICATION($cpentry[4], // username
769
					base64_decode($cpentry[6]), // password
770
					$radiusservers,
771
					$cpentry[2], // clientip
772
					$cpentry[3], // clientmac
773
					$cpentry[1]); // ruleno
774
				if ($auth_list['auth_val'] == 3) {
775
					captiveportal_disconnect($cpentry, $radiusservers, 17);
776
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
777
					$unsetindexes[] = $cpentry[5];
778
				} else if ($auth_list['auth_val'] == 2)
779
					captiveportal_reapply_attributes($cpentry, $auth_list);
780
			}
781
		}
782
	}
783
	unset($cpdb);
784

    
785
	captiveportal_prune_old_automac();
786

    
787
	if ($voucher_needs_sync == true)
788
		/* Triger a sync of the vouchers on config */
789
		send_event("service sync vouchers");
790

    
791
	/* write database */
792
	if (!empty($unsetindexes))
793
		captiveportal_remove_entries($unsetindexes);
794
}
795

    
796
function captiveportal_prune_old_automac() {
797
	global $g, $config, $cpzone, $cpzoneid;
798

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

    
860
/* remove a single client according to the DB entry */
861
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
862
	global $g, $config, $cpzone, $cpzoneid;
863

    
864
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
865

    
866
	/* this client needs to be deleted - remove ipfw rules */
867
	if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers)) {
868
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
869
			$dbent[4], // username
870
			$dbent[5], // sessionid
871
			$dbent[0], // start time
872
			$radiusservers,
873
			$dbent[2], // clientip
874
			$dbent[3], // clientmac
875
			$term_cause, // Acct-Terminate-Cause
876
			false,
877
			$stop_time);
878
	}
879
	
880
	if (is_ipaddr($dbent[2])) {
881
		/* Delete client's ip entry from tables 1 and 2. */
882
		$clientsn = (is_ipaddrv6($dbent[2])) ? 128 : 32;
883
		pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XDEL, 1, $dbent[2], $clientsn, $dbent[3]);
884
		pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XDEL, 2, $dbent[2], $clientsn, $dbent[3]);
885
		/* XXX: Redundant?! Ensure all pf(4) states are killed. */
886
		$_gb = @pfSense_kill_states($dbent[2]);
887
		$_gb = @pfSense_kill_srcstates($dbent[2]);
888
	}
889

    
890
	/* 
891
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
892
	* We could get an error if the pipe doesn't exist but everything should still be fine
893
	*/
894
	if (!empty($dbent[1])) {
895
		$_gb = @pfSense_pipe_action("pipe delete {$dbent[1]}");
896
		$_gb = @pfSense_pipe_action("pipe delete " . ($dbent[1]+1));
897

    
898
		/* Release the ruleno so it can be reallocated to new clients. */
899
		captiveportal_free_dn_ruleno($dbent[1]);
900
	}
901

    
902
	// XMLRPC Call over to the master Voucher node
903
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
904
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
905
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
906
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
907
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
908
		$remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time);
909
	}
910

    
911
}
912

    
913
/* remove a single client by sessionid */
914
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
915
	global $g, $config;
916

    
917
	$radiusservers = captiveportal_get_radius_servers();
918

    
919
	/* read database */
920
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
921

    
922
	/* find entry */
923
	if (!empty($result)) {
924
		captiveportal_write_db("DELETE FROM captiveportal WHERE sessionid = '{$sessionid}'");
925

    
926
		foreach ($result as $cpentry) {
927
			if (empty($cpentry[11]))
928
				$cpentry[11] = 'first';
929
			captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], $term_cause);
930
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
931
		}
932
		unset($result);
933
	}
934
}
935

    
936
/* send RADIUS acct stop for all current clients */
937
function captiveportal_radius_stop_all() {
938
	global $config, $cpzone;
939

    
940
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable']))
941
		return;
942

    
943
	$radiusservers = captiveportal_get_radius_servers();
944
	if (!empty($radiusservers)) {
945
		$cpdb = captiveportal_read_db();
946
		foreach ($cpdb as $cpentry) {
947
			if (empty($cpentry[11]))
948
				$cpentry[11] = 'first';
949
			if (!empty($radiusservers[$cpentry[11]])) {
950
				RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
951
					$cpentry[4], // username
952
					$cpentry[5], // sessionid
953
					$cpentry[0], // start time
954
					$radiusservers[$cpentry[11]],
955
					$cpentry[2], // clientip
956
					$cpentry[3], // clientmac
957
					7); // Admin Reboot
958
			}
959
		}
960
	}
961
}
962

    
963
function captiveportal_passthrumac_configure_entry($macent, $pipeinrule = false) {
964
	global $config, $g, $cpzone;
965

    
966
	$bwUp = 0;
967
	if (!empty($macent['bw_up']))
968
		$bwUp = $macent['bw_up'];
969
	else if (!empty($config['captiveportal'][$cpzone]['bwdefaultup']))
970
		$bwUp = $config['captiveportal'][$cpzone]['bwdefaultup'];
971
	$bwDown = 0;
972
	if (!empty($macent['bw_down']))
973
		$bwDown = $macent['bw_down'];
974
	else if (!empty($config['captiveportal'][$cpzone]['bwdefaultdn']))
975
		$bwDown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
976

    
977
	$ruleno = captiveportal_get_next_ipfw_ruleno();
978

    
979
	if ($macent['action'] == 'pass') {
980
		$rules = "";
981
		$pipeno = captiveportal_get_next_dn_ruleno();
982

    
983
		$pipeup = $pipeno;
984
		if ($pipeinrule == true)
985
			$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16");
986
		else
987
			$rules .= "pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16\n";
988
			
989
		$pipedown = $pipeno + 1;
990
		if ($pipeinrule == true)
991
			$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
992
		else
993
			$rules .= "pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16\n";
994

    
995
		$rules .= "add {$ruleno} pipe {$pipeup} ip from any to any MAC any {$macent['mac']}\n";
996
		$ruleno++;
997
		$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC {$macent['mac']} any\n";
998
	}
999

    
1000
	return $rules;
1001
}
1002

    
1003
function captiveportal_passthrumac_delete_entry($macent) {
1004
	$rules = "";
1005

    
1006
	if ($macent['action'] == 'pass') {
1007
		$ruleno = captiveportal_get_ipfw_passthru_ruleno($macent['mac']);
1008

    
1009
		if (!$ruleno)
1010
			return $rules;
1011

    
1012
		captiveportal_free_ipfw_ruleno($ruleno);
1013

    
1014
		$rules .= "delete {$ruleno}\n";
1015
		$rules .= "delete " . ++$ruleno . "\n";
1016

    
1017
		$pipeno = captiveportal_get_dn_passthru_ruleno($macent['mac']);
1018

    
1019
		if (!empty($pipeno)) {
1020
			captiveportal_free_dn_ruleno($pipeno);
1021
			$rules .= "pipe delete " . $pipeno . "\n";
1022
			$rules .= "pipe delete " . ++$pipeno . "\n";
1023
		}
1024
	}
1025

    
1026
	return $rules;
1027
}
1028

    
1029
function captiveportal_passthrumac_configure($filename = false, $startindex = 0, $stopindex = 0) {
1030
	global $config, $g, $cpzone;
1031

    
1032
	$rules = "";
1033

    
1034
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1035
		if ($stopindex > 0) {
1036
			$fd = fopen($filename, "w");
1037
			for ($idx = $startindex; $idx <= $stopindex; $idx++) {
1038
				if (isset($config['captiveportal'][$cpzone]['passthrumac'][$idx])) {
1039
					$rules = captiveportal_passthrumac_configure_entry($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1040
					fwrite($fd, $rules);
1041
				}
1042
			}
1043
			fclose($fd);
1044

    
1045
			return;
1046
		} else {
1047
			$nentries = count($config['captiveportal'][$cpzone]['passthrumac']);
1048
			if ($nentries > 2000) {
1049
				$nloops = $nentries / 1000;
1050
				$remainder= $nentries % 1000;
1051
				for ($i = 0; $i < $nloops; $i++)
1052
					mwexec_bg("/usr/local/sbin/fcgicli -f /etc/rc.captiveportal_configure_mac -d \"cpzone={$cpzone}&startidx=" . ($i * 1000) . "&stopidx=" . ((($i+1) * 1000) - 1) . "\"");
1053
				if ($remainder > 0)
1054
					mwexec_bg("/usr/local/sbin/fcgicli -f /etc/rc.captiveportal_configure_mac -d \"cpzone={$cpzone}&startidx=" . ($i * 1000) . "&stopidx=" . (($i* 1000) + $remainder) ."\"");
1055
			} else {
1056
				foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent)
1057
					$rules .= captiveportal_passthrumac_configure_entry($macent, true);
1058
			}
1059
		}
1060
	}
1061

    
1062
	return $rules;
1063
}
1064

    
1065
function captiveportal_passthrumac_findbyname($username) {
1066
	global $config, $cpzone;
1067

    
1068
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1069
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1070
			if ($macent['username'] == $username)
1071
				return $macent;
1072
		}
1073
	}
1074
	return NULL;
1075
}
1076

    
1077
/* 
1078
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1079
 */
1080
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
1081
	global $g;
1082

    
1083
	/*  Instead of copying this entire function for something
1084
	 *  easy such as hostname vs ip address add this check
1085
	 */
1086
	if ($ishostname === true) {
1087
		if (!platform_booting()) {
1088
			$ipaddress = gethostbyname($ipent['hostname']);
1089
			if (!is_ipaddr($ipaddress)) 
1090
				return;
1091
		} else
1092
			$ipaddress = "";
1093
	} else
1094
		$ipaddress = $ipent['ip'];
1095

    
1096
	$rules = "";
1097
	$cp_filterdns_conf = "";
1098
	$enBwup = 0;
1099
	if (!empty($ipent['bw_up']))
1100
		$enBwup = intval($ipent['bw_up']);
1101
	else if (!empty($config['captiveportal'][$cpzone]['bwdefaultup']))
1102
		$enBwup = $config['captiveportal'][$cpzone]['bwdefaultup'];
1103
	$enBwdown = 0;
1104
	if (!empty($ipent['bw_down']))
1105
		$enBwdown = intval($ipent['bw_down']);
1106
	else if (!empty($config['captiveportal'][$cpzone]['bwdefaultdn']))
1107
		$enBwdown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
1108

    
1109
	$pipeno = captiveportal_get_next_dn_ruleno();
1110
	$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$enBwup}Kbit/s queue 100 buckets 16");
1111
	$pipedown = $pipeno + 1;
1112
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16");
1113
	if ($ishostname === true) {
1114
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
1115
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
1116
		if (!is_ipaddr($ipaddress))
1117
			return array("", $cp_filterdns_conf);
1118
	}
1119
	$subnet = "";
1120
	if (!empty($ipent['sn']))
1121
		$subnet = "/{$ipent['sn']}";
1122
	$rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
1123
	$rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
1124

    
1125
	if ($ishostname === true)
1126
		return array($rules, $cp_filterdns_conf);
1127
	else
1128
		return $rules;
1129
}
1130

    
1131
function captiveportal_allowedhostname_configure() {
1132
	global $config, $g, $cpzone;
1133

    
1134
	$rules = "";
1135
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1136
		$rules = "\n# captiveportal_allowedhostname_configure()\n";
1137
		$cp_filterdns_conf = "";
1138
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) {
1139
			$tmprules = captiveportal_allowedip_configure_entry($hostnameent, true);
1140
			$rules .= $tmprules[0];
1141
			$cp_filterdns_conf .= $tmprules[1];
1142
		}
1143
		$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1144
		@file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1145
		unset($cp_filterdns_conf);
1146
		if (isvalidpid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid"))
1147
			sigkillbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid", "HUP");
1148
		else
1149
			mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzone} -d 1");
1150
	} else {
1151
		killbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1152
		@unlink("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1153
	}
1154

    
1155
	return $rules;
1156
}
1157

    
1158
function captiveportal_allowedip_configure() {
1159
	global $config, $g, $cpzone;
1160

    
1161
	$rules = "";
1162
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1163
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1164
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1165
	}
1166

    
1167
	return $rules;
1168
}
1169

    
1170
/* get last activity timestamp given client IP address */
1171
function captiveportal_get_last_activity($ip, $mac = NULL, $table = 1) {
1172
	global $cpzoneid;
1173

    
1174
	$ipfwoutput = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, $table, $ip, $mac);
1175
	/* Reading only from one of the tables is enough of approximation. */
1176
	if (is_array($ipfwoutput)) {
1177
		return $ipfwoutput['timestamp'];
1178
	}
1179

    
1180
	return 0;
1181
}
1182

    
1183
function captiveportal_init_radius_servers() {
1184
	global $config, $g, $cpzone;
1185

    
1186
	/* generate radius server database */
1187
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1188
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1189
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1190
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1191
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1192
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1193

    
1194
		if ($config['captiveportal'][$cpzone]['radiusport'])
1195
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1196
		else
1197
			$radiusport = 1812;
1198
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1199
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1200
		else
1201
			$radiusacctport = 1813;
1202
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1203
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1204
		else
1205
			$radiusport2 = 1812;
1206
		if ($config['captiveportal'][$cpzone]['radiusport3'])
1207
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1208
		else
1209
			$radiusport3 = 1812;
1210
		if ($config['captiveportal'][$cpzone]['radiusport4'])
1211
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1212
		else
1213
			$radiusport4 = 1812;
1214

    
1215
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1216
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1217
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1218
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1219

    
1220
		$cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX);
1221
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
1222
		if (!$fd) {
1223
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1224
			unlock($cprdsrvlck);
1225
			return 1;
1226
		}
1227
		if (isset($radiusip))
1228
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
1229
		if (isset($radiusip2))
1230
			fwrite($fd,"\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
1231
		if (isset($radiusip3))
1232
			fwrite($fd,"\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
1233
		if (isset($radiusip4))
1234
			fwrite($fd,"\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
1235
		
1236

    
1237
		fclose($fd);
1238
		unlock($cprdsrvlck);
1239
	}
1240
}
1241

    
1242
/* read RADIUS servers into array */
1243
function captiveportal_get_radius_servers() {
1244
	global $g, $cpzone;
1245

    
1246
	$cprdsrvlck = lock("captiveportalradius{$cpzone}");
1247
	if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
1248
		$radiusservers = array();
1249
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", 
1250
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1251
		if ($cpradiusdb) {
1252
			foreach($cpradiusdb as $cpradiusentry) {
1253
				$line = trim($cpradiusentry);
1254
				if ($line) {
1255
					$radsrv = array();
1256
						list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key'], $context) = explode(",",$line);
1257
				}
1258
				if (empty($context)) {
1259
					if (!is_array($radiusservers['first']))
1260
						$radiusservers['first'] = array();
1261
					$radiusservers['first'] = $radsrv;
1262
				} else {
1263
					if (!is_array($radiusservers[$context]))
1264
						$radiusservers[$context] = array();
1265
					$radiusservers[$context][] = $radsrv;
1266
				}
1267
			}
1268
		}
1269
		unlock($cprdsrvlck);
1270
		return $radiusservers;
1271
	}
1272

    
1273
	unlock($cprdsrvlck);
1274
	return false;
1275
}
1276

    
1277
/* log successful captive portal authentication to syslog */
1278
/* part of this code from php.net */
1279
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1280
	// Log it
1281
	if (!$message)
1282
		$message = "{$status}: {$user}, {$mac}, {$ip}";
1283
	else {
1284
		$message = trim($message);
1285
		$message = "{$status}: {$user}, {$mac}, {$ip}, {$message}";
1286
	}
1287
	captiveportal_syslog($message);
1288
}
1289

    
1290
/* log simple messages to syslog */
1291
function captiveportal_syslog($message) {
1292
	global $cpzone;
1293

    
1294
	$message = trim($message);
1295
	$message = "Zone: {$cpzone} - {$message}";
1296
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1297
	// Log it
1298
	syslog(LOG_INFO, $message);
1299
	closelog();
1300
}
1301

    
1302
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1303
	global $g, $config, $cpzoneid;
1304

    
1305
	$pipeno = captiveportal_get_next_dn_ruleno();
1306

    
1307
	/* If the pool is empty, return appropriate message and fail authentication */
1308
	if (empty($pipeno)) {
1309
		$auth_list = array();
1310
		$auth_list['auth_val'] = 1;
1311
		$auth_list['error'] = "System reached maximum login capacity";
1312
		return $auth_list;
1313
	}
1314

    
1315
	$radiusservers = captiveportal_get_radius_servers();
1316

    
1317
	if (is_null($radiusctx))
1318
		$radiusctx = 'first';
1319

    
1320
	$auth_list = RADIUS_AUTHENTICATION($username,
1321
		$password,
1322
		$radiusservers[$radiusctx],
1323
		$clientip,
1324
		$clientmac,
1325
		$pipeno);
1326

    
1327
	if ($auth_list['auth_val'] == 2) {
1328
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1329
		$sessionid = portal_allow($clientip,
1330
			$clientmac,
1331
			$username,
1332
			$password,
1333
			$auth_list,
1334
			$pipeno,
1335
			$radiusctx);
1336
	} else {
1337
	         captiveportal_free_dn_ruleno($pipeno);
1338
	       }
1339

    
1340
	return $auth_list;
1341
}
1342

    
1343
function captiveportal_opendb() {
1344
	global $g, $cpzone;
1345

    
1346
	$DB = new SQLite3("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1347
	if (! $DB->exec("CREATE TABLE IF NOT EXISTS captiveportal (" .
1348
				"allow_time INTEGER, pipeno INTEGER, ip TEXT, mac TEXT, username TEXT, " .
1349
				"sessionid TEXT, bpassword TEXT, session_timeout INTEGER, idle_timeout INTEGER, " .
1350
				"session_terminate_time INTEGER, interim_interval INTEGER, radiusctx TEXT); " .
1351
			"CREATE UNIQUE INDEX IF NOT EXISTS idx_active ON captiveportal (sessionid, username); " .
1352
			"CREATE INDEX IF NOT EXISTS user ON captiveportal (username); " .
1353
			"CREATE INDEX IF NOT EXISTS ip ON captiveportal (ip); " .
1354
			"CREATE INDEX IF NOT EXISTS starttime ON captiveportal (allow_time)"))
1355
		captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$DB->lastErrorMsg()}");
1356

    
1357
	return $DB;
1358
}
1359

    
1360
/* read captive portal DB into array */
1361
function captiveportal_read_db($query = "") {
1362
	$cpdb = array();
1363

    
1364
	$DB = captiveportal_opendb();
1365
	if ($DB) {
1366
		$response = $DB->query("SELECT * FROM captiveportal {$query}");
1367
		if ($response != FALSE) {
1368
			while ($row = $response->fetchArray())
1369
				$cpdb[] = $row;
1370
		}
1371
		$DB->close();
1372
	}
1373

    
1374
	return $cpdb;
1375
}
1376

    
1377
function captiveportal_remove_entries($remove) {
1378

    
1379
	if (!is_array($remove) || empty($remove))
1380
		return;
1381

    
1382
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1383
	foreach($remove as $idx => $unindex) {
1384
		$query .= "'{$unindex}'";
1385
		if ($idx < (count($remove) - 1))
1386
			$query .= ",";
1387
	}
1388
	$query .= ")";
1389
	captiveportal_write_db($query);
1390
}
1391

    
1392
/* write captive portal DB */
1393
function captiveportal_write_db($queries) {
1394
	global $g;
1395

    
1396
	if (is_array($queries))
1397
		$query = implode(";", $queries);
1398
	else
1399
		$query = $queries;
1400

    
1401
	$DB = captiveportal_opendb();
1402
	if ($DB) {
1403
		$DB->exec("BEGIN TRANSACTION");
1404
		$result = $DB->exec($query);
1405
		if (!$result)
1406
			captiveportal_syslog("Trying to modify DB returned error: {$DB->lastErrorMsg()}");
1407
		else
1408
			$DB->exec("END TRANSACTION");
1409
		$DB->close();
1410
		return $result;
1411
	} else
1412
		return true;
1413
}
1414

    
1415
function captiveportal_write_elements() {
1416
	global $g, $config, $cpzone;
1417
	
1418
	$cpcfg = $config['captiveportal'][$cpzone];
1419

    
1420
	if (!is_dir($g['captiveportal_element_path']))
1421
		@mkdir($g['captiveportal_element_path']);
1422

    
1423
	if (is_array($cpcfg['element'])) {
1424
		conf_mount_rw();
1425
		foreach ($cpcfg['element'] as $data) {
1426
			if (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1427
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1428
				return 1;
1429
			}
1430
			if (!file_exists("{$g['captiveportal_path']}/{$data['name']}"))
1431
				@symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1432
		}
1433
		conf_mount_ro();
1434
	}
1435
	
1436
	return 0;
1437
}
1438

    
1439
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1440
	global $cpzone;
1441

    
1442
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1443
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1444
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1445
		$ridx = $rulenos_start;
1446
		while ($ridx < $rulenos_range_max) {
1447
			if ($rules[$ridx] == $cpzone) {
1448
				$rules[$ridx] = false;
1449
				$ridx++;
1450
				$rules[$ridx] = false;
1451
				$ridx++;
1452
			} else
1453
				$ridx += 2;
1454
		}
1455
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1456
		unset($rules);
1457
	}
1458
	unlock($cpruleslck);
1459
}
1460

    
1461
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1462
	global $config, $g, $cpzone;
1463

    
1464
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1465
	$ruleno = 0;
1466
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1467
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1468
		$ridx = $rulenos_start;
1469
		while ($ridx < $rulenos_range_max) {
1470
			if (empty($rules[$ridx])) {
1471
				$ruleno = $ridx;
1472
				$rules[$ridx] = $cpzone;
1473
				$ridx++;
1474
				$rules[$ridx] = $cpzone;
1475
				break;
1476
			} else {
1477
				$ridx += 2;
1478
			}
1479
		}
1480
	} else {
1481
		$rules = array_pad(array(), $rulenos_range_max, false);
1482
		$ruleno = $rulenos_start;
1483
		$rules[$rulenos_start] = $cpzone;
1484
		$rulenos_start++;
1485
		$rules[$rulenos_start] = $cpzone;
1486
	}
1487
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1488
	unlock($cpruleslck);
1489
	unset($rules);
1490

    
1491
	return $ruleno;
1492
}
1493

    
1494
function captiveportal_free_dn_ruleno($ruleno) {
1495
	global $config, $g;
1496

    
1497
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1498
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1499
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1500
		$rules[$ruleno] = false;
1501
		$ruleno++;
1502
		$rules[$ruleno] = false;
1503
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1504
		unset($rules);
1505
	}
1506
	unlock($cpruleslck);
1507
}
1508

    
1509
function captiveportal_get_dn_passthru_ruleno($value) {
1510
	global $config, $g, $cpzone, $cpzoneid;
1511

    
1512
	$cpcfg = $config['captiveportal'][$cpzone];
1513
	if(!isset($cpcfg['enable']))
1514
		return NULL;
1515

    
1516
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1517
	$ruleno = NULL;
1518
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1519
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1520
		unset($output);
1521
		$_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);
1522
		$ruleno = intval($output[0]);
1523
		if (!$rules[$ruleno])
1524
			$ruleno = NULL;
1525
		unset($rules);
1526
	}
1527
	unlock($cpruleslck);
1528

    
1529
	return $ruleno;
1530
}
1531

    
1532
/*
1533
 * This function will calculate the lowest free firewall ruleno
1534
 * within the range specified based on the actual logged on users
1535
 *
1536
 */
1537
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1538
	global $config, $g, $cpzone;
1539

    
1540
	$cpcfg = $config['captiveportal'][$cpzone];
1541
	if(!isset($cpcfg['enable']))
1542
		return NULL;
1543

    
1544
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1545
	$ruleno = 0;
1546
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1547
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1548
		$ridx = $rulenos_start;
1549
		while ($ridx < $rulenos_range_max) {
1550
			if (empty($rules[$ridx])) {
1551
				$ruleno = $ridx;
1552
				$rules[$ridx] = $cpzone;
1553
				$ridx++;
1554
				$rules[$ridx] = $cpzone;
1555
				break;
1556
			} else {
1557
				/* 
1558
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1559
				 * and the out pipe ruleno + 1.
1560
				 */
1561
				$ridx += 2;
1562
			}
1563
		}
1564
	} else {
1565
		$rules = array_pad(array(), $rulenos_range_max, false);
1566
		$ruleno = $rulenos_start;
1567
		$rules[$rulenos_start] = $cpzone;
1568
		$rulenos_start++;
1569
		$rules[$rulenos_start] = $cpzone;
1570
	}
1571
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1572
	unlock($cpruleslck);
1573
	unset($rules);
1574

    
1575
	return $ruleno;
1576
}
1577

    
1578
function captiveportal_free_ipfw_ruleno($ruleno) {
1579
	global $config, $g, $cpzone;
1580

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

    
1585
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1586
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1587
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1588
		$rules[$ruleno] = false;
1589
		$ruleno++;
1590
		$rules[$ruleno] = false;
1591
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1592
		unset($rules);
1593
	}
1594
	unlock($cpruleslck);
1595
}
1596

    
1597
function captiveportal_get_ipfw_passthru_ruleno($value) {
1598
	global $config, $g, $cpzone, $cpzoneid;
1599

    
1600
	$cpcfg = $config['captiveportal'][$cpzone];
1601
	if(!isset($cpcfg['enable']))
1602
		return NULL;
1603

    
1604
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1605
	$ruleno = NULL;
1606
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1607
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1608
		unset($output);
1609
		$_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);
1610
		$ruleno = intval($output[0]);
1611
		if (!$rules[$ruleno])
1612
			$ruleno = NULL;
1613
		unset($rules);
1614
	}
1615
	unlock($cpruleslck);
1616

    
1617
	return $ruleno;
1618
}
1619

    
1620
/**
1621
 * This function will calculate the traffic produced by a client
1622
 * based on its firewall rule
1623
 *
1624
 * Point of view: NAS
1625
 *
1626
 * Input means: from the client
1627
 * Output means: to the client
1628
 *
1629
 */
1630

    
1631
function getVolume($ip, $mac = NULL) {
1632
	global $config, $cpzone, $cpzoneid;
1633

    
1634
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1635
	$volume = array();
1636
	// Initialize vars properly, since we don't want NULL vars
1637
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1638

    
1639
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 1, $ip, $mac);
1640
	if (is_array($ipfw)) {
1641
		if ($reverse) {
1642
			$volume['output_pkts'] = $ipfw['packets'];
1643
			$volume['output_bytes'] = $ipfw['bytes'];
1644
		}
1645
		else {
1646
			$volume['input_pkts'] = $ipfw['packets'];
1647
			$volume['input_bytes'] = $ipfw['bytes'];
1648
		}
1649
	}
1650

    
1651
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 2, $ip, $mac);
1652
	if (is_array($ipfw)) {
1653
		if ($reverse) {
1654
			$volume['input_pkts'] = $ipfw['packets'];
1655
			$volume['input_bytes'] = $ipfw['bytes'];
1656
		}
1657
		else {
1658
			$volume['output_pkts'] = $ipfw['packets'];
1659
			$volume['output_bytes'] = $ipfw['bytes'];
1660
		}
1661
	}
1662

    
1663
	return $volume;
1664
}
1665

    
1666
/**
1667
 * Get the NAS-IP-Address based on the current wan address
1668
 *
1669
 * Use functions in interfaces.inc to find this out
1670
 *
1671
 */
1672

    
1673
function getNasIP()
1674
{
1675
	global $config, $cpzone;
1676

    
1677
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1678
			$nasIp = get_interface_ip();
1679
	} else {
1680
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1681
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1682
		else
1683
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1684
	}
1685
		
1686
	if(!is_ipaddr($nasIp))
1687
		$nasIp = "0.0.0.0";
1688

    
1689
	return $nasIp;
1690
}
1691

    
1692
function portal_ip_from_client_ip($cliip) {
1693
	global $config, $cpzone;
1694

    
1695
	$isipv6 = is_ipaddrv6($cliip);
1696
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1697
	foreach ($interfaces as $cpif) {
1698
		if ($isipv6) {
1699
			$ip = get_interface_ipv6($cpif);
1700
			$sn = get_interface_subnetv6($cpif);
1701
		} else {
1702
			$ip = get_interface_ip($cpif);
1703
			$sn = get_interface_subnet($cpif);
1704
		}
1705
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1706
			return $ip;
1707
	}
1708

    
1709
	$inet = ($isipv6) ? '-inet6' : '-inet';
1710
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1711
	$iface = trim($iface, "\n");
1712
	if (!empty($iface)) {
1713
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1714
		if (is_ipaddr($ip))
1715
			return $ip;
1716
	}
1717

    
1718
	// doesn't match up to any particular interface
1719
	// so let's set the portal IP to what PHP says 
1720
	// the server IP issuing the request is. 
1721
	// allows same behavior as 1.2.x where IP isn't 
1722
	// in the subnet of any CP interface (static routes, etc.)
1723
	// rather than forcing to DNS hostname resolution
1724
	$ip = $_SERVER['SERVER_ADDR'];
1725
	if (is_ipaddr($ip))
1726
		return $ip;
1727

    
1728
	return false;
1729
}
1730

    
1731
function portal_hostname_from_client_ip($cliip) {
1732
	global $config, $cpzone;
1733

    
1734
	$cpcfg = $config['captiveportal'][$cpzone];
1735

    
1736
	if (isset($cpcfg['httpslogin'])) {
1737
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 8001);
1738
		$ourhostname = $cpcfg['httpsname'];
1739
		
1740
		if ($listenporthttps != 443)
1741
			$ourhostname .= ":" . $listenporthttps;
1742
	} else {
1743
		$listenporthttp  = $cpcfg['listenporthttp']  ? $cpcfg['listenporthttp']  : ($cpcfg['zoneid'] + 8000);
1744
		$ifip = portal_ip_from_client_ip($cliip);
1745
		if (!$ifip)
1746
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1747
		else
1748
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1749
		
1750
		if ($listenporthttp != 80)
1751
			$ourhostname .= ":" . $listenporthttp;
1752
	}
1753
	
1754
	return $ourhostname;
1755
}
1756

    
1757
/* functions move from index.php */
1758

    
1759
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1760
	global $g, $config, $cpzone;
1761

    
1762
	/* Get captive portal layout */
1763
	if ($type == "redir") {
1764
		header("Location: {$redirurl}");
1765
		return;
1766
	} else if ($type == "login")
1767
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1768
	else
1769
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1770

    
1771
	$cpcfg = $config['captiveportal'][$cpzone];
1772

    
1773
	/* substitute the PORTAL_REDIRURL variable */
1774
	if ($cpcfg['preauthurl']) {
1775
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1776
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1777
	}
1778

    
1779
	/* substitute other variables */
1780
	$ourhostname = portal_hostname_from_client_ip($clientip);
1781
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1782
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1783
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1784

    
1785
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1786
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1787
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1788
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1789
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1790

    
1791
	// Special handling case for captive portal master page so that it can be ran 
1792
	// through the PHP interpreter using the include method above.  We convert the
1793
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1794
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1795
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1796
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1797
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1798
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1799
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1800
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1801

    
1802
	echo $htmltext;
1803
}
1804

    
1805
function portal_mac_radius($clientmac,$clientip) {
1806
	global $config, $cpzone;
1807

    
1808
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1809

    
1810
	/* authentication against the radius server */
1811
	$username = mac_format($clientmac);
1812
	$auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1813
	if ($auth_list['auth_val'] == 2)
1814
		return TRUE;
1815

    
1816
	if (!empty($auth_list['url_redirection']))
1817
		portal_reply_page($auth_list['url_redirection'], "redir");
1818

    
1819
	return FALSE;
1820
}
1821

    
1822
function captiveportal_reapply_attributes($cpentry, $attributes) {
1823
	global $config, $cpzone, $g;
1824

    
1825
	if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
1826
		$dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1827
		$dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1828
	} else
1829
		$dwfaultbw_up = $dwfaultbw_down = 0;
1830
	$bw_up = !empty($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1831
	$bw_down = !empty($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1832
	$bw_up_pipeno = $cpentry[1];
1833
	$bw_down_pipeno = $cpentry[1]+1;
1834

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

    
1839
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1840
}
1841

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

    
1845
	// Ensure we create an array if we are missing attributes
1846
	if (!is_array($attributes))
1847
		$attributes = array();
1848

    
1849
	unset($sessionid);
1850

    
1851
	/* Do not allow concurrent login execution. */
1852
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1853

    
1854
	if ($attributes['voucher'])
1855
		$remaining_time = $attributes['session_timeout'];
1856

    
1857
	$writecfg = false;
1858
	/* Find an existing session */
1859
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
1860
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1861
			$mac = captiveportal_passthrumac_findbyname($username);
1862
			if (!empty($mac)) {
1863
				if ($_POST['replacemacpassthru']) {
1864
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
1865
						if ($macent['mac'] == $mac['mac']) {
1866
							$macrules = "";
1867
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1868
							$pipeno = captiveportal_get_dn_passthru_ruleno($mac['mac']);
1869
							if ($ruleno) {
1870
								captiveportal_free_ipfw_ruleno($ruleno);
1871
								$macrules .= "delete {$ruleno}\n";
1872
								++$ruleno;
1873
								$macrules .= "delete {$ruleno}\n";
1874
							}
1875
							if ($pipeno) {
1876
								captiveportal_free_dn_ruleno($pipeno);
1877
								$macrules .= "pipe delete {$pipeno}\n";
1878
								++$pipeno;
1879
								$macrules .= "pipe delete {$pipeno}\n";
1880
							}
1881
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1882
							$mac['action'] = 'pass';
1883
							$mac['mac'] = $clientmac;
1884
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1885
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1886
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1887
							mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1888
							$writecfg = true;
1889
							$sessionid = true;
1890
							break;
1891
						}
1892
					}
1893
				} else {
1894
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1895
						$clientmac, $clientip, $username, $password);
1896
					unlock($cpdblck);
1897
					return;
1898
				}
1899
			}
1900
		}
1901
	}
1902

    
1903
	/* read in client database */
1904
	$query = "WHERE ip = '{$clientip}'";
1905
	$tmpusername = strtolower($username);
1906
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins']))
1907
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
1908
	$cpdb = captiveportal_read_db($query);
1909

    
1910
	/* Snapshot the timestamp */
1911
	$allow_time = time();
1912
	$radiusservers = captiveportal_get_radius_servers();
1913
	$unsetindexes = array();
1914
	if (is_null($radiusctx))
1915
		$radiusctx = 'first';
1916

    
1917
	foreach ($cpdb as $cpentry) {
1918
		if (empty($cpentry[11])) {
1919
			$cpentry[11] = 'first';
1920
		}
1921
		/* on the same ip */
1922
		if ($cpentry[2] == $clientip) {
1923
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac)
1924
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1925
			else
1926
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
1927
			$sessionid = $cpentry[5];
1928
			break;
1929
		}
1930
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1931
			// user logged in with an active voucher. Check for how long and calculate 
1932
			// how much time we can give him (voucher credit - used time)
1933
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1934
			if ($remaining_time < 0)    // just in case. 
1935
				$remaining_time = 0;
1936

    
1937
			/* This user was already logged in so we disconnect the old one */
1938
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[11]],13);
1939
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1940
			$unsetindexes[] = $cpentry[5];
1941
			break;
1942
		}
1943
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1944
			/* on the same username */
1945
			if (strcasecmp($cpentry[4], $username) == 0) {
1946
				/* This user was already logged in so we disconnect the old one */
1947
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[11]],13);
1948
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1949
				$unsetindexes[] = $cpentry[5];
1950
				break;
1951
			}
1952
		}
1953
	}
1954
	unset($cpdb);
1955

    
1956
	if (!empty($unsetindexes))
1957
		captiveportal_remove_entries($unsetindexes);
1958

    
1959
	if ($attributes['voucher'] && $remaining_time <= 0)
1960
		return 0;       // voucher already used and no time left
1961

    
1962
	if (!isset($sessionid)) {
1963
		/* generate unique session ID */
1964
		$tod = gettimeofday();
1965
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1966

    
1967
		if ($passthrumac) {
1968
			$mac = array();
1969
			$mac['action'] = 'pass';
1970
			$mac['mac'] = $clientmac;
1971
			$mac['ip'] = $clientip; /* Used only for logging */
1972
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
1973
				$mac['username'] = $username;
1974
				if ($attributes['voucher'])
1975
					$mac['logintype'] = "voucher";
1976
			}
1977
			if ($username = "unauthenticated")
1978
				$mac['descr'] =  "Auto-added";
1979
			else
1980
				$mac['descr'] =  "Auto-added for user {$username}";
1981
			if (!empty($bw_up))
1982
				$mac['bw_up'] = $bw_up;
1983
			if (!empty($bw_down))
1984
				$mac['bw_down'] = $bw_down;
1985
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
1986
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
1987
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1988
			unlock($cpdblck);
1989
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1990
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1991
			mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1992
			$writecfg = true;
1993
		} else {
1994
			/* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
1995
			if (is_null($pipeno))
1996
				$pipeno = captiveportal_get_next_dn_ruleno();
1997

    
1998
			/* if the pool is empty, return appropriate message and exit */
1999
			if (is_null($pipeno)) {
2000
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
2001
				log_error("Zone: {$cpzone} - WARNING!  Captive portal has reached maximum login capacity");
2002
				unlock($cpdblck);
2003
				return;
2004
			}
2005

    
2006
			if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
2007
				$dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
2008
				$dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
2009
			} else
2010
				$dwfaultbw_up = $dwfaultbw_down = 0;
2011
			$bw_up = !empty($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
2012
			$bw_down = !empty($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
2013

    
2014
			$bw_up_pipeno = $pipeno;
2015
			$bw_down_pipeno = $pipeno + 1;
2016
			//$bw_up /= 1000; // Scale to Kbit/s
2017
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
2018
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
2019

    
2020
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
2021
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
2022
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
2023
			else
2024
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
2025

    
2026
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
2027
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
2028
			else
2029
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
2030

    
2031
			if ($attributes['voucher'])
2032
				$attributes['session_timeout'] = $remaining_time;
2033
			
2034
			/* handle empty attributes */
2035
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
2036
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
2037
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
2038
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
2039

    
2040
			/* escape username */
2041
			$safe_username = SQLite3::escapeString($username);
2042

    
2043
			/* encode password in Base64 just in case it contains commas */
2044
			$bpassword = base64_encode($password);
2045
			$insertquery  = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, radiusctx) ";
2046
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
2047
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, '{$radiusctx}')";
2048

    
2049
			/* store information to database */
2050
			captiveportal_write_db($insertquery);
2051
			unlock($cpdblck);
2052
			unset($insertquery, $bpassword);
2053

    
2054
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
2055
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
2056
				if ($acct_val == 1)
2057
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
2058
			}
2059
		}
2060
	} else {
2061
		/* NOTE: #3062-11 If the pipeno has been allocated free it to not DoS the CP and maintain proper operation as in radius() case */
2062
		if (!is_null($pipeno))
2063
			captiveportal_free_dn_ruleno($pipeno);
2064

    
2065
		unlock($cpdblck);
2066
	}
2067

    
2068
	if ($writecfg == true)
2069
		write_config();
2070

    
2071
	/* redirect user to desired destination */
2072
	if (!empty($attributes['url_redirection']))
2073
		$my_redirurl = $attributes['url_redirection'];
2074
	else if (!empty($redirurl))
2075
		$my_redirurl = $redirurl;
2076
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
2077
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
2078

    
2079
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
2080
		$ourhostname = portal_hostname_from_client_ip($clientip);
2081
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
2082
		$logouturl = "{$protocol}{$ourhostname}/";
2083

    
2084
		if (isset($attributes['reply_message']))
2085
			$message = $attributes['reply_message'];
2086
		else
2087
			$message = 0;
2088

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

    
2091
	} else {
2092
		portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
2093
	}
2094

    
2095
	return $sessionid;
2096
}
2097

    
2098

    
2099
/*
2100
 * Used for when pass-through credits are enabled.
2101
 * Returns true when there was at least one free login to deduct for the MAC.
2102
 * Expired entries are removed as they are seen.
2103
 * Active entries are updated according to the configuration.
2104
 */
2105
function portal_consume_passthrough_credit($clientmac) {
2106
	global $config, $cpzone;
2107

    
2108
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
2109
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
2110
	else
2111
		return false;
2112

    
2113
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
2114
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2115
	else
2116
		return false;
2117

    
2118
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
2119
		return false;
2120

    
2121
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2122

    
2123
	/*
2124
	 * Read database of used MACs.  Lines are a comma-separated list
2125
	 * of the time, MAC, then the count of pass-through credits remaining.
2126
	 */
2127
	$usedmacs = captiveportal_read_usedmacs_db();
2128

    
2129
	$currenttime = time();
2130
	$found = false;
2131
	foreach ($usedmacs as $key => $usedmac) {
2132
		$usedmac = explode(",", $usedmac);
2133

    
2134
		if ($usedmac[1] == $clientmac) {
2135
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2136
				if ($usedmac[2] < 1) {
2137
					if ($updatetimeouts) {
2138
						$usedmac[0] = $currenttime;
2139
						unset($usedmacs[$key]);
2140
						$usedmacs[] = implode(",", $usedmac);
2141
						captiveportal_write_usedmacs_db($usedmacs);
2142
					}
2143

    
2144
					return false;
2145
				} else {
2146
					$usedmac[2] -= 1;
2147
					$usedmacs[$key] = implode(",", $usedmac);
2148
				}
2149

    
2150
				$found = true;
2151
			} else
2152
				unset($usedmacs[$key]);
2153

    
2154
			break;
2155
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2156
				unset($usedmacs[$key]);
2157
	}
2158

    
2159
	if (!$found) {
2160
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2161
		$usedmacs[] = implode(",", $usedmac);
2162
	}
2163

    
2164
	captiveportal_write_usedmacs_db($usedmacs);
2165
	return true;
2166
}
2167

    
2168
function captiveportal_read_usedmacs_db() {
2169
	global $g, $cpzone;
2170

    
2171
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2172
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2173
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2174
		if (!$usedmacs)
2175
			$usedmacs = array();
2176
	} else
2177
		$usedmacs = array();
2178

    
2179
	unlock($cpumaclck);
2180
	return $usedmacs;
2181
}
2182

    
2183
function captiveportal_write_usedmacs_db($usedmacs) {
2184
	global $g, $cpzone;
2185

    
2186
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2187
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2188
	unlock($cpumaclck);
2189
}
2190

    
2191
function captiveportal_blocked_mac($mac) {
2192
	global $config, $g, $cpzone;
2193

    
2194
	if (empty($mac) || !is_macaddr($mac))
2195
		return false;
2196

    
2197
	if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
2198
		return false;
2199

    
2200
	foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac)
2201
		if (($passthrumac['action'] == 'block') &&
2202
		    ($passthrumac['mac'] == strtolower($mac)))
2203
			return true;
2204

    
2205
	return false;
2206

    
2207
}
2208

    
2209
function captiveportal_send_server_accounting($off = false) {
2210
	global $cpzone, $config;
2211

    
2212
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
2213
		return;
2214
	}
2215
	if ($off) {
2216
		$racct = new Auth_RADIUS_Acct_Off;
2217
	} else {
2218
		$racct = new Auth_RADIUS_Acct_On;
2219
	}
2220
	$radiusservers = captiveportal_get_radius_servers();
2221
	if (empty($radiusservers)) {
2222
		return;
2223
	}
2224
	foreach ($radiusservers['first'] as $radsrv) {
2225
		// Add a new server to our instance
2226
		$racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
2227
	}
2228
	if (PEAR::isError($racct->start())) {
2229
		$retvalue['acct_val'] = 1;
2230
		$retvalue['error'] = $racct->getMessage();
2231

    
2232
		// If we encounter an error immediately stop this function and go back
2233
		$racct->close();
2234
		return $retvalue;
2235
	}
2236
	// Send request
2237
	$result = $racct->send();
2238
	// Evaluation of the response
2239
	// 5 -> Accounting-Response
2240
	// See RFC2866 for this.
2241
	if (PEAR::isError($result)) {
2242
		$retvalue['acct_val'] = 1;
2243
		$retvalue['error'] = $result->getMessage();
2244
	} else if ($result === true) {
2245
		$retvalue['acct_val'] = 5 ;
2246
	} else {
2247
		$retvalue['acct_val'] = 1 ;
2248
	}
2249

    
2250
	$racct->close();
2251
	return $retvalue;
2252
}
2253
?>
(8-8/68)