Project

General

Profile

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

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

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

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

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

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

    
33
	This version of captiveportal.inc has been modified by Rob Parker
34
	<rob.parker@keycom.co.uk> to include changes for per-user bandwidth management
35
	via returned RADIUS attributes. This page has been modified to delete any
36
	added rules which may have been created by other per-user code (index.php, etc).
37
	These changes are (c) 2004 Keycom PLC.
38
	
39
	pfSense_BUILDER_BINARIES:	/sbin/ipfw	/sbin/route
40
	pfSense_BUILDER_BINARIES:	/usr/local/sbin/lighttpd	/usr/local/bin/minicron /sbin/pfctl
41
	pfSense_BUILDER_BINARIES:	/bin/hostname	/bin/cp 
42
	pfSense_MODULE: captiveportal
43
*/
44

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

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

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

    
103
EOD;
104

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

    
112
EOD;
113
	}
114

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

    
142
EOD;
143

    
144
	return $htmltext;
145
}
146

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

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

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

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

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

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

    
185
	$captiveportallck = lock("captiveportal{$cpzone}", LOCK_EX);
186
	
187
	if (isset($cpcfg['enable'])) {
188

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

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

    
197
		/* 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
/* reinit will disconnect all users, be careful! */
452
function captiveportal_init_rules($reinit = false) {
453
	global $config, $g, $cpzone, $cpzoneid;
454

    
455
	if (!isset($config['captiveportal'][$cpzone]['enable']))
456
		return;
457

    
458
	captiveportal_load_modules();
459
	mwexec("/sbin/ipfw zone {$cpzoneid} create", true);
460

    
461
	$cpips = array();
462
	$ifaces = get_configured_interface_list();
463
	$cpinterfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
464
	$firsttime = 0;
465
	foreach ($cpinterfaces as $cpifgrp) {
466
		if (!isset($ifaces[$cpifgrp]))
467
			continue;
468
		$tmpif = get_real_interface($cpifgrp);
469
		if (!empty($tmpif)) {
470
			$cpipm = get_interface_ip($cpifgrp);
471
			if (is_ipaddr($cpipm)) {
472
				$carpif = link_ip_to_carp_interface($cpipm);
473
				if (!empty($carpif)) {
474
					$carpsif = explode(" ", $carpif);
475
					foreach ($carpsif as $cpcarp) {
476
						mwexec("/sbin/ipfw zone {$cpzoneid} madd {$cpcarp}", true);
477
						$carpip = find_interface_ip($cpcarp);
478
						if (is_ipaddr($carpip))
479
							$cpips[] = $carpip;
480
					}
481
				}
482
				$cpips[] = $cpipm;
483
			}
484
			mwexec("/sbin/ipfw zone {$cpzoneid} madd {$tmpif}", true);
485
		}
486
	}
487
	if (count($cpips) > 0) {
488
		$cpactive = true;
489
	} else
490
		return false;
491

    
492
	if ($reinit == false)
493
		$captiveportallck = lock("captiveportal{$cpzone}");
494

    
495
	$cprules =	"add 65291 allow pfsync from any to any\n";
496
	$cprules .= "add 65292 allow carp from any to any\n";
497

    
498
	$cprules .= <<<EOD
499
# layer 2: pass ARP
500
add 65301 pass layer2 mac-type arp,rarp
501
# pfsense requires for WPA
502
add 65302 pass layer2 mac-type 0x888e,0x88c7
503
# PPP Over Ethernet Session Stage/Discovery Stage
504
add 65303 pass layer2 mac-type 0x8863,0x8864
505

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

    
509
EOD;
510

    
511
	$rulenum = 65310;
512
	$ipcount = 0;
513
	$ips = "";
514
	foreach ($cpips as $cpip) {
515
		if($ipcount == 0) {
516
			$ips = "{$cpip} ";
517
		} else {
518
			$ips .= "or {$cpip} ";
519
		}
520
		$ipcount++;
521
	}
522
	$ips = "{ 255.255.255.255 or {$ips} }";
523
	$cprules .= "add {$rulenum} pass ip from any to {$ips} in\n";
524
	$rulenum++;
525
	$cprules .= "add {$rulenum} pass ip from {$ips} to any out\n";
526
	$rulenum++;
527
	$cprules .= "add {$rulenum} pass icmp from {$ips} to any out icmptype 0\n";
528
	$rulenum++;
529
	$cprules .= "add {$rulenum} pass icmp from any to {$ips} in icmptype 8 \n";
530
	$rulenum++;
531
	/* Allowed ips */
532
	$cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any in\n";
533
	$rulenum++;
534
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) in\n";
535
	$rulenum++;
536
	$cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any out\n";
537
	$rulenum++;
538
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) out\n";
539
	$rulenum++;
540

    
541
	/* Authenticated users rules. */
542
	$cprules .= "add {$rulenum} pipe tablearg ip from table(1) to any in\n";
543
	$rulenum++;
544
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(2) out\n";
545
	$rulenum++;
546

    
547
	if (!empty($config['captiveportal'][$cpzone]['listenporthttp']))
548
		$listenporthttp = $config['captiveportal'][$cpzone]['listenporthttp'];
549
	else
550
		$listenporthttp = 8000 + $cpzoneid;
551

    
552
	if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
553
		if (!empty($config['captiveportal'][$cpzone]['listenporthttps']))
554
			$listenporthttps = $config['captiveportal'][$cpzone]['listenporthttps'];
555
		else
556
			$listenporthttps = 8001 + $cpzoneid;
557
			if (!isset($config['captiveportal'][$cpzone]['nohttpsforwards'])) {
558
				$cprules .= "add 65531 fwd 127.0.0.1,{$listenporthttps} tcp from any to any dst-port 443 in\n";
559
			}
560
	}
561
	
562
	$cprules .= <<<EOD
563

    
564
# redirect non-authenticated clients to captive portal
565
add 65532 fwd 127.0.0.1,{$listenporthttp} tcp from any to any dst-port 80 in 
566
# let the responses from the captive portal web server back out
567
add 65533 pass tcp from any to any out
568
# block everything else
569
add 65534 deny all from any to any
570

    
571
EOD;
572

    
573
	/* generate passthru mac database */
574
	$cprules .= captiveportal_passthrumac_configure(true);
575
	$cprules .= "\n";
576

    
577
	/* allowed ipfw rules to make allowed ip work */
578
	$cprules .= captiveportal_allowedip_configure();
579

    
580
	/* allowed ipfw rules to make allowed hostnames work */
581
	$cprules .= captiveportal_allowedhostname_configure();
582
	
583
	/* load rules */
584
	$cprules = "flush\n{$cprules}";
585
	file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
586
	mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
587
	//@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
588
	unset($cprules, $tmprules);
589

    
590
	if ($reinit == false)
591
		unlock($captiveportallck);
592
}
593

    
594
/* 
595
 * Remove clients that have been around for longer than the specified amount of time
596
 * db file structure:
597
 * timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time,interim_interval
598
 * (password is in Base64 and only saved when reauthentication is enabled)
599
 */
600
function captiveportal_prune_old() {
601
	global $g, $config, $cpzone, $cpzoneid;
602

    
603
	if (empty($cpzone))
604
		return;
605

    
606
	$cpcfg = $config['captiveportal'][$cpzone];
607
	$vcpcfg = $config['voucher'][$cpzone];
608

    
609
	/* check for expired entries */
610
	$idletimeout = 0;
611
	$timeout = 0;
612
	if (!empty($cpcfg['timeout']) && is_numeric($cpcfg['timeout']))
613
		$timeout = $cpcfg['timeout'] * 60;
614

    
615
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout']))
616
		$idletimeout = $cpcfg['idletimeout'] * 60;
617

    
618
	/* Is there any job to do? */
619
	if (!$timeout && !$idletimeout && !isset($cpcfg['reauthenticate']) &&
620
	    !isset($cpcfg['radiussession_timeout']) && !isset($vcpcfg['enable']))
621
		return;
622

    
623
	$radiussrvs = captiveportal_get_radius_servers();
624

    
625
	/* Read database */
626
	/* NOTE: while this can be simplified in non radius case keep as is for now */
627
	$cpdb = captiveportal_read_db();
628

    
629
	$unsetindexes = array();
630
	$voucher_needs_sync = false;
631
	/* 
632
	 * Snapshot the time here to use for calculation to speed up the process.
633
	 * If something is missed next run will catch it!
634
	 */
635
	$pruning_time = time();
636
	$stop_time = $pruning_time;
637
	foreach ($cpdb as $cpentry) {
638

    
639
		$timedout = false;
640
		$term_cause = 1;
641
		if (empty($cpentry[11]))
642
			$cpentry[11] = 'first';
643
		$radiusservers = $radiussrvs[$cpentry[11]];
644

    
645
		/* hard timeout? */
646
		if ($timeout) {
647
			if (($pruning_time - $cpentry[0]) >= $timeout) {
648
				$timedout = true;
649
				$term_cause = 5; // Session-Timeout
650
			}
651
		}
652

    
653
		/* Session-Terminate-Time */
654
		if (!$timedout && !empty($cpentry[9])) {
655
			if ($pruning_time >= $cpentry[9]) {
656
				$timedout = true;
657
				$term_cause = 5; // Session-Timeout
658
			}
659
		}
660

    
661
		/* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
662
		$uidletimeout = (is_numeric($cpentry[8])) ? $cpentry[8] : $idletimeout;
663
		/* if an idle timeout is specified, get last activity timestamp from ipfw */
664
		if (!$timedout && $uidletimeout > 0) {
665
			$lastact = captiveportal_get_last_activity($cpentry[2], $cpentry[3]);
666
			/*	If the user has logged on but not sent any traffic they will never be logged out.
667
			 *	We "fix" this by setting lastact to the login timestamp. 
668
			 */
669
			$lastact = $lastact ? $lastact : $cpentry[0];
670
			if ($lastact && (($pruning_time - $lastact) >= $uidletimeout)) {
671
				$timedout = true;
672
				$term_cause = 4; // Idle-Timeout
673
				$stop_time = $lastact; // Entry added to comply with WISPr
674
			}
675
		}
676

    
677
		/* if vouchers are configured, activate session timeouts */
678
		if (!$timedout && isset($vcpcfg['enable']) && !empty($cpentry[7])) {
679
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
680
				$timedout = true;
681
				$term_cause = 5; // Session-Timeout
682
				$voucher_needs_sync = true;
683
			}
684
		}
685

    
686
		/* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
687
		if (!$timedout && isset($cpcfg['radiussession_timeout']) && !empty($cpentry[7])) {
688
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
689
				$timedout = true;
690
				$term_cause = 5; // Session-Timeout
691
			}
692
		}
693

    
694
		if ($timedout) {
695
			captiveportal_disconnect($cpentry, $radiusservers,$term_cause,$stop_time);
696
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
697
			$unsetindexes[] = $cpentry[5];
698
		}
699

    
700
		/* do periodic RADIUS reauthentication? */
701
		if (!$timedout && !empty($radiusservers)) {
702
			if (isset($cpcfg['radacct_enable'])) {
703
				if ($cpcfg['reauthenticateacct'] == "stopstart") {
704
					/* stop and restart accounting */
705
					RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
706
						$cpentry[4], // username
707
						$cpentry[5], // sessionid
708
						$cpentry[0], // start time
709
						$radiusservers,
710
						$cpentry[2], // clientip
711
						$cpentry[3], // clientmac
712
						10); // NAS Request
713
					$clientsn = (is_ipaddrv6($cpentry[2])) ? 128 : 32;
714
					$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XZEROENTRY, 1, $cpentry[2], $clientsn, $cpentry[3]);
715
					$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XZEROENTRY, 2, $cpentry[2], $clientsn, $cpentry[3]);
716
					RADIUS_ACCOUNTING_START($cpentry[1], // ruleno
717
						$cpentry[4], // username
718
						$cpentry[5], // sessionid
719
						$radiusservers,
720
						$cpentry[2], // clientip
721
						$cpentry[3]); // clientmac
722
				} else if ($cpcfg['reauthenticateacct'] == "interimupdate") {
723
					$session_time = $pruning_time - $cpentry[0];
724
					if (!empty($cpentry[10]) && $cpentry[10] > 60)
725
						$interval = $cpentry[10];
726
					else
727
						$interval = 0;
728
					$past_interval_min = ($session_time > $interval);
729
					if ($interval != 0)
730
						$within_interval = ($session_time % $interval >= 0 && $session_time % $interval <= 59);
731
					if ($interval === 0 || ($interval > 0 && $past_interval_min && $within_interval)) {
732
						RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
733
							$cpentry[4], // username
734
							$cpentry[5], // sessionid
735
							$cpentry[0], // start time
736
							$radiusservers,
737
							$cpentry[2], // clientip
738
							$cpentry[3], // clientmac
739
							10, // NAS Request
740
							true); // Interim Updates
741
					}
742
				}
743
			}
744

    
745
			/* check this user against RADIUS again */
746
			if (isset($cpcfg['reauthenticate'])) {
747
				$auth_list = RADIUS_AUTHENTICATION($cpentry[4], // username
748
					base64_decode($cpentry[6]), // password
749
					$radiusservers,
750
					$cpentry[2], // clientip
751
					$cpentry[3], // clientmac
752
					$cpentry[1]); // ruleno
753
				if ($auth_list['auth_val'] == 3) {
754
					captiveportal_disconnect($cpentry, $radiusservers, 17);
755
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
756
					$unsetindexes[] = $cpentry[5];
757
				} else if ($auth_list['auth_val'] == 2)
758
					captiveportal_reapply_attributes($cpentry, $auth_list);
759
			}
760
		}
761
	}
762
	unset($cpdb);
763

    
764
	captiveportal_prune_old_automac();
765

    
766
	if ($voucher_needs_sync == true)
767
		/* Triger a sync of the vouchers on config */
768
		send_event("service sync vouchers");
769

    
770
	/* write database */
771
	if (!empty($unsetindexes))
772
		captiveportal_remove_entries($unsetindexes);
773
}
774

    
775
function captiveportal_prune_old_automac() {
776
	global $g, $config, $cpzone, $cpzoneid;
777

    
778
	if (is_array($config['captiveportal'][$cpzone]['passthrumac']) && isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
779
		$tmpvoucherdb = array();
780
		$macrules = "";
781
		$writecfg = false;
782
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $eid => $emac) {
783
			if ($emac['logintype'] == "voucher") {
784
				if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) {
785
					if (isset($tmpvoucherdb[$emac['username']])) {
786
						$temac = $config['captiveportal'][$cpzone]['passthrumac'][$tmpvoucherdb[$emac['username']]];
787
						$ruleno = captiveportal_get_ipfw_passthru_ruleno($temac['mac']);
788
						$pipeno = captiveportal_get_dn_passthru_ruleno($temac['mac']);
789
						if ($ruleno) {
790
							captiveportal_free_ipfw_ruleno($ruleno);
791
							$macrules .= "delete {$ruleno}";
792
							++$ruleno;
793
							$macrules .= "delete {$ruleno}";
794
						}
795
						if ($pipeno) {
796
							captiveportal_free_dn_ruleno($pipeno);
797
							$macrules .= "pipe delete {$pipeno}\n";
798
							++$pipeno;
799
							$macrules .= "pipe delete {$pipeno}\n";
800
						}
801
						$writecfg = true;
802
						captiveportal_logportalauth($temac['username'], $temac['mac'], $temac['ip'], "DUPLICATE {$temac['username']} LOGIN - TERMINATING OLD SESSION");
803
						unset($config['captiveportal'][$cpzone]['passthrumac'][$tmpvoucherdb[$emac['username']]]);
804
					}
805
					$tmpvoucherdb[$emac['username']] = $eid;
806
				}
807
				if (voucher_auth($emac['username']) <= 0) {
808
					$ruleno = captiveportal_get_ipfw_passthru_ruleno($emac['mac']);
809
					$pipeno = captiveportal_get_dn_passthru_ruleno($emac['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($emac['username'], $emac['mac'], $emac['ip'], "EXPIRED {$emac['username']} LOGIN - TERMINATING SESSION");
824
					unset($config['captiveportal'][$cpzone]['passthrumac'][$eid]);
825
				}
826
			}
827
		}
828
		unset($tmpvoucherdb);
829
		if (!empty($macrules)) {
830
			@file_put_contents("{$g['tmp_path']}/macentry.prunerules.tmp", $macrules);
831
			unset($macrules);
832
			mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry.prunerules.tmp");
833
		}
834
		if ($writecfg === true)
835
			write_config("Prune session for auto-added macs");
836
	}
837
}
838

    
839
/* remove a single client according to the DB entry */
840
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
841
	global $g, $config, $cpzone, $cpzoneid;
842

    
843
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
844

    
845
	/* this client needs to be deleted - remove ipfw rules */
846
	if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers)) {
847
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
848
			$dbent[4], // username
849
			$dbent[5], // sessionid
850
			$dbent[0], // start time
851
			$radiusservers,
852
			$dbent[2], // clientip
853
			$dbent[3], // clientmac
854
			$term_cause, // Acct-Terminate-Cause
855
			false,
856
			$stop_time);
857
	}
858
	
859
	if (is_ipaddr($dbent[2])) {
860
		/* Delete client's ip entry from tables 1 and 2. */
861
		$clientsn = (is_ipaddrv6($dbent[2])) ? 128 : 32;
862
		pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XDEL, 1, $dbent[2], $clientsn, $dbent[3]);
863
		pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XDEL, 2, $dbent[2], $clientsn, $dbent[3]);
864
		/* XXX: Redundant?! Ensure all pf(4) states are killed. */
865
		$_gb = @pfSense_kill_states($dbent[2]);
866
		$_gb = @pfSense_kill_srcstates($dbent[2]);
867
	}
868

    
869
	/* 
870
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
871
	* We could get an error if the pipe doesn't exist but everything should still be fine
872
	*/
873
	if (!empty($dbent[1])) {
874
		$_gb = @pfSense_pipe_action("pipe delete {$dbent[1]}");
875
		$_gb = @pfSense_pipe_action("pipe delete " . ($dbent[1]+1));
876

    
877
		/* Release the ruleno so it can be reallocated to new clients. */
878
		captiveportal_free_dn_ruleno($dbent[1]);
879
	}
880

    
881
	// XMLRPC Call over to the master Voucher node
882
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
883
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
884
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
885
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
886
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
887
		$remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time);
888
	}
889

    
890
}
891

    
892
/* remove a single client by sessionid */
893
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
894
	global $g, $config;
895

    
896
	$radiusservers = captiveportal_get_radius_servers();
897

    
898
	/* read database */
899
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
900

    
901
	/* find entry */
902
	if (!empty($result)) {
903
		captiveportal_write_db("DELETE FROM captiveportal WHERE sessionid = '{$sessionid}'");
904

    
905
		foreach ($result as $cpentry) {
906
			if (empty($cpentry[11]))
907
				$cpentry[11] = 'first';
908
			captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], $term_cause);
909
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
910
		}
911
		unset($result);
912
	}
913
}
914

    
915
/* send RADIUS acct stop for all current clients */
916
function captiveportal_radius_stop_all() {
917
	global $config, $cpzone;
918

    
919
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable']))
920
		return;
921

    
922
	$radiusservers = captiveportal_get_radius_servers();
923
	if (!empty($radiusservers)) {
924
		$cpdb = captiveportal_read_db();
925
		foreach ($cpdb as $cpentry) {
926
			if (empty($cpentry[11]))
927
				$cpentry[11] = 'first';
928
			if (!empty($radiusservers[$cpentry[11]])) {
929
				RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
930
					$cpentry[4], // username
931
					$cpentry[5], // sessionid
932
					$cpentry[0], // start time
933
					$radiusservers[$cpentry[11]],
934
					$cpentry[2], // clientip
935
					$cpentry[3], // clientmac
936
					7); // Admin Reboot
937
			}
938
		}
939
	}
940
}
941

    
942
function captiveportal_passthrumac_configure_entry($macent, $pipeinrule = false) {
943
	global $config, $g, $cpzone;
944

    
945
	$bwUp = 0;
946
	if (!empty($macent['bw_up']))
947
		$bwUp = $macent['bw_up'];
948
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultup']))
949
		$bwUp = $config['captiveportal'][$cpzone]['bwdefaultup'];
950
	$bwDown = 0;
951
	if (!empty($macent['bw_down']))
952
		$bwDown = $macent['bw_down'];
953
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultdn']))
954
		$bwDown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
955

    
956
	$ruleno = captiveportal_get_next_ipfw_ruleno();
957

    
958
	if ($macent['action'] == 'pass') {
959
		$rules = "";
960
		$pipeno = captiveportal_get_next_dn_ruleno();
961

    
962
		$pipeup = $pipeno;
963
		if ($pipeinrule == true)
964
			$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16");
965
		else
966
			$rules .= "pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16\n";
967
			
968
		$pipedown = $pipeno + 1;
969
		if ($pipeinrule == true)
970
			$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
971
		else
972
			$rules .= "pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16\n";
973

    
974
		$rules .= "add {$ruleno} pipe {$pipeup} ip from any to any MAC any {$macent['mac']}\n";
975
		$ruleno++;
976
		$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC {$macent['mac']} any\n";
977
	}
978

    
979
	return $rules;
980
}
981

    
982
function captiveportal_passthrumac_delete_entry($macent) {
983
	$rules = "";
984

    
985
	if ($macent['action'] == 'pass') {
986
		$ruleno = captiveportal_get_ipfw_passthru_ruleno($macent['mac']);
987

    
988
		if (!$ruleno)
989
			return $rules;
990

    
991
		captiveportal_free_ipfw_ruleno($ruleno);
992

    
993
		$rules .= "delete {$ruleno}\n";
994
		$rules .= "delete " . ++$ruleno . "\n";
995

    
996
		$pipeno = captiveportal_get_dn_passthru_ruleno($macent['mac']);
997

    
998
		if (!empty($pipeno)) {
999
			captiveportal_free_dn_ruleno($pipeno);
1000
			$rules .= "pipe delete " . $pipeno . "\n";
1001
			$rules .= "pipe delete " . ++$pipeno . "\n";
1002
		}
1003
	}
1004

    
1005
	return $rules;
1006
}
1007

    
1008
function captiveportal_passthrumac_configure($lock = false) {
1009
	global $config, $g, $cpzone;
1010

    
1011
	$rules = "";
1012

    
1013
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1014
		$nentries = count($config['captiveportal'][$cpzone]['passthrumac']);
1015
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1016
			if ($nentries > 100)
1017
				$rules .= captiveportal_passthrumac_configure_entry($macent, true);
1018
			else
1019
				$rules .= captiveportal_passthrumac_configure_entry($macent);
1020
		}
1021
	}
1022

    
1023
	return $rules;
1024
}
1025

    
1026
function captiveportal_passthrumac_findbyname($username) {
1027
	global $config, $cpzone;
1028

    
1029
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1030
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1031
			if ($macent['username'] == $username)
1032
				return $macent;
1033
		}
1034
	}
1035
	return NULL;
1036
}
1037

    
1038
/* 
1039
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1040
 */
1041
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
1042
	global $g;
1043

    
1044
	/*  Instead of copying this entire function for something
1045
	 *  easy such as hostname vs ip address add this check
1046
	 */
1047
	if ($ishostname === true) {
1048
		if (!platform_booting()) {
1049
			$ipaddress = gethostbyname($ipent['hostname']);
1050
			if (!is_ipaddr($ipaddress)) 
1051
				return;
1052
		} else
1053
			$ipaddress = "";
1054
	} else
1055
		$ipaddress = $ipent['ip'];
1056

    
1057
	$rules = "";
1058
	$cp_filterdns_conf = "";
1059
	$enBwup = 0;
1060
	if (!empty($ipent['bw_up']))
1061
		$enBwup = intval($ipent['bw_up']);
1062
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultup']))
1063
		$enBwup = $config['captiveportal'][$cpzone]['bwdefaultup'];
1064
	$enBwdown = 0;
1065
	if (!empty($ipent['bw_down']))
1066
		$enBwdown = intval($ipent['bw_down']);
1067
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultdn']))
1068
		$enBwdown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
1069

    
1070
	$pipeno = captiveportal_get_next_dn_ruleno();
1071
	$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$enBwup}Kbit/s queue 100 buckets 16");
1072
	$pipedown = $pipeno + 1;
1073
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16");
1074
	if ($ishostname === true) {
1075
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
1076
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
1077
		if (!is_ipaddr($ipaddress))
1078
			return array("", $cp_filterdns_conf);
1079
	}
1080
	$subnet = "";
1081
	if (!empty($ipent['sn']))
1082
		$subnet = "/{$ipent['sn']}";
1083
	$rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
1084
	$rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
1085

    
1086
	if ($ishostname === true)
1087
		return array($rules, $cp_filterdns_conf);
1088
	else
1089
		return $rules;
1090
}
1091

    
1092
function captiveportal_allowedhostname_configure() {
1093
	global $config, $g, $cpzone;
1094

    
1095
	$rules = "";
1096
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1097
		$rules = "\n# captiveportal_allowedhostname_configure()\n";
1098
		$cp_filterdns_conf = "";
1099
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) {
1100
			$tmprules = captiveportal_allowedip_configure_entry($hostnameent, true);
1101
			$rules .= $tmprules[0];
1102
			$cp_filterdns_conf .= $tmprules[1];
1103
		}
1104
		$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1105
		@file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1106
		unset($cp_filterdns_conf);
1107
		if (isvalidpid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid"))
1108
			sigkillbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid", "HUP");
1109
		else
1110
			mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzone} -d 1");
1111
	} else {
1112
		killbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1113
		@unlink("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1114
	}
1115

    
1116
	return $rules;
1117
}
1118

    
1119
function captiveportal_allowedip_configure() {
1120
	global $config, $g, $cpzone;
1121

    
1122
	$rules = "";
1123
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1124
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1125
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1126
	}
1127

    
1128
	return $rules;
1129
}
1130

    
1131
/* get last activity timestamp given client IP address */
1132
function captiveportal_get_last_activity($ip, $mac = NULL, $table = 1) {
1133
	global $cpzoneid;
1134

    
1135
	$ipfwoutput = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, $table, $ip, $mac);
1136
	/* Reading only from one of the tables is enough of approximation. */
1137
	if (is_array($ipfwoutput)) {
1138
		return $ipfwoutput['timestamp'];
1139
	}
1140

    
1141
	return 0;
1142
}
1143

    
1144
function captiveportal_init_radius_servers() {
1145
	global $config, $g, $cpzone;
1146

    
1147
	/* generate radius server database */
1148
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1149
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1150
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1151
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1152
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1153
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1154

    
1155
		if ($config['captiveportal'][$cpzone]['radiusport'])
1156
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1157
		else
1158
			$radiusport = 1812;
1159
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1160
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1161
		else
1162
			$radiusacctport = 1813;
1163
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1164
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1165
		else
1166
			$radiusport2 = 1812;
1167
		if ($config['captiveportal'][$cpzone]['radiusport3'])
1168
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1169
		else
1170
			$radiusport3 = 1812;
1171
		if ($config['captiveportal'][$cpzone]['radiusport4'])
1172
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1173
		else
1174
			$radiusport4 = 1812;
1175

    
1176
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1177
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1178
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1179
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1180

    
1181
		$cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX);
1182
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
1183
		if (!$fd) {
1184
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1185
			unlock($cprdsrvlck);
1186
			return 1;
1187
		}
1188
		if (isset($radiusip))
1189
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
1190
		if (isset($radiusip2))
1191
			fwrite($fd,"\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
1192
		if (isset($radiusip3))
1193
			fwrite($fd,"\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
1194
		if (isset($radiusip4))
1195
			fwrite($fd,"\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
1196
		
1197

    
1198
		fclose($fd);
1199
		unlock($cprdsrvlck);
1200
	}
1201
}
1202

    
1203
/* read RADIUS servers into array */
1204
function captiveportal_get_radius_servers() {
1205
	global $g, $cpzone;
1206

    
1207
	$cprdsrvlck = lock("captiveportalradius{$cpzone}");
1208
	if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
1209
		$radiusservers = array();
1210
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", 
1211
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1212
		if ($cpradiusdb) {
1213
			foreach($cpradiusdb as $cpradiusentry) {
1214
				$line = trim($cpradiusentry);
1215
				if ($line) {
1216
					$radsrv = array();
1217
						list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key'], $context) = explode(",",$line);
1218
				}
1219
				if (empty($context)) {
1220
					if (!is_array($radiusservers['first']))
1221
						$radiusservers['first'] = array();
1222
					$radiusservers['first'] = $radsrv;
1223
				} else {
1224
					if (!is_array($radiusservers[$context]))
1225
						$radiusservers[$context] = array();
1226
					$radiusservers[$context][] = $radsrv;
1227
				}
1228
			}
1229
		}
1230
		unlock($cprdsrvlck);
1231
		return $radiusservers;
1232
	}
1233

    
1234
	unlock($cprdsrvlck);
1235
	return false;
1236
}
1237

    
1238
/* log successful captive portal authentication to syslog */
1239
/* part of this code from php.net */
1240
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1241
	// Log it
1242
	if (!$message)
1243
		$message = "{$status}: {$user}, {$mac}, {$ip}";
1244
	else {
1245
		$message = trim($message);
1246
		$message = "{$status}: {$user}, {$mac}, {$ip}, {$message}";
1247
	}
1248
	captiveportal_syslog($message);
1249
}
1250

    
1251
/* log simple messages to syslog */
1252
function captiveportal_syslog($message) {
1253
	global $cpzone;
1254

    
1255
	$message = trim($message);
1256
	$message = "Zone: {$cpzone} - {$message}";
1257
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1258
	// Log it
1259
	syslog(LOG_INFO, $message);
1260
	closelog();
1261
}
1262

    
1263
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1264
	global $g, $config, $cpzoneid;
1265

    
1266
	$pipeno = captiveportal_get_next_dn_ruleno();
1267

    
1268
	/* If the pool is empty, return appropriate message and fail authentication */
1269
	if (empty($pipeno)) {
1270
		$auth_list = array();
1271
		$auth_list['auth_val'] = 1;
1272
		$auth_list['error'] = "System reached maximum login capacity";
1273
		return $auth_list;
1274
	}
1275

    
1276
	$radiusservers = captiveportal_get_radius_servers();
1277

    
1278
	if (is_null($radiusctx))
1279
		$radiusctx = 'first';
1280

    
1281
	$auth_list = RADIUS_AUTHENTICATION($username,
1282
		$password,
1283
		$radiusservers[$radiusctx],
1284
		$clientip,
1285
		$clientmac,
1286
		$pipeno);
1287

    
1288
	if ($auth_list['auth_val'] == 2) {
1289
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1290
		$sessionid = portal_allow($clientip,
1291
			$clientmac,
1292
			$username,
1293
			$password,
1294
			$auth_list,
1295
			$pipeno,
1296
			$radiusctx);
1297
	} else {
1298
	         captiveportal_free_dn_ruleno($pipeno);
1299
	       }
1300

    
1301
	return $auth_list;
1302
}
1303

    
1304
function captiveportal_opendb() {
1305
	global $g, $cpzone;
1306

    
1307
	$DB = new SQLite3("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1308
	if (! $DB->exec("CREATE TABLE IF NOT EXISTS captiveportal (" .
1309
				"allow_time INTEGER, pipeno INTEGER, ip TEXT, mac TEXT, username TEXT, " .
1310
				"sessionid TEXT, bpassword TEXT, session_timeout INTEGER, idle_timeout INTEGER, " .
1311
				"session_terminate_time INTEGER, interim_interval INTEGER, radiusctx TEXT); " .
1312
			"CREATE UNIQUE INDEX IF NOT EXISTS idx_active ON captiveportal (sessionid, username); " .
1313
			"CREATE INDEX IF NOT EXISTS user ON captiveportal (username); " .
1314
			"CREATE INDEX IF NOT EXISTS ip ON captiveportal (ip); " .
1315
			"CREATE INDEX IF NOT EXISTS starttime ON captiveportal (allow_time)"))
1316
		captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$DB->lastErrorMsg()}");
1317

    
1318
	return $DB;
1319
}
1320

    
1321
/* read captive portal DB into array */
1322
function captiveportal_read_db($query = "") {
1323
	$cpdb = array();
1324

    
1325
	$DB = captiveportal_opendb();
1326
	if ($DB) {
1327
		$response = $DB->query("SELECT * FROM captiveportal {$query}");
1328
		if ($response != FALSE) {
1329
			while ($row = $response->fetchArray())
1330
				$cpdb[] = $row;
1331
		}
1332
		$DB->close();
1333
	}
1334

    
1335
	return $cpdb;
1336
}
1337

    
1338
function captiveportal_remove_entries($remove) {
1339

    
1340
	if (!is_array($remove) || empty($remove))
1341
		return;
1342

    
1343
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1344
	foreach($remove as $idx => $unindex) {
1345
		$query .= "'{$unindex}'";
1346
		if ($idx < (count($remove) - 1))
1347
			$query .= ",";
1348
	}
1349
	$query .= ")";
1350
	captiveportal_write_db($query);
1351
}
1352

    
1353
/* write captive portal DB */
1354
function captiveportal_write_db($queries) {
1355
	global $g;
1356

    
1357
	if (is_array($queries))
1358
		$query = implode(";", $queries);
1359
	else
1360
		$query = $queries;
1361

    
1362
	$DB = captiveportal_opendb();
1363
	if ($DB) {
1364
		$DB->exec("BEGIN TRANSACTION");
1365
		$result = $DB->exec($query);
1366
		if (!$result)
1367
			captiveportal_syslog("Trying to modify DB returned error: {$DB->lastErrorMsg()}");
1368
		else
1369
			$DB->exec("END TRANSACTION");
1370
		$DB->close();
1371
		return $result;
1372
	} else
1373
		return true;
1374
}
1375

    
1376
function captiveportal_write_elements() {
1377
	global $g, $config, $cpzone;
1378
	
1379
	$cpcfg = $config['captiveportal'][$cpzone];
1380

    
1381
	if (!is_dir($g['captiveportal_element_path']))
1382
		@mkdir($g['captiveportal_element_path']);
1383

    
1384
	if (is_array($cpcfg['element'])) {
1385
		conf_mount_rw();
1386
		foreach ($cpcfg['element'] as $data) {
1387
			if (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1388
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1389
				return 1;
1390
			}
1391
			if (!file_exists("{$g['captiveportal_path']}/{$data['name']}"))
1392
				@symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1393
		}
1394
		conf_mount_ro();
1395
	}
1396
	
1397
	return 0;
1398
}
1399

    
1400
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1401
	global $cpzone;
1402

    
1403
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1404
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1405
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1406
		$ridx = $rulenos_start;
1407
		while ($ridx < $rulenos_range_max) {
1408
			if ($rules[$ridx] == $cpzone) {
1409
				$rules[$ridx] = false;
1410
				$ridx++;
1411
				$rules[$ridx] = false;
1412
				$ridx++;
1413
			} else
1414
				$ridx += 2;
1415
		}
1416
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1417
		unset($rules);
1418
	}
1419
	unlock($cpruleslck);
1420
}
1421

    
1422
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1423
	global $config, $g, $cpzone;
1424

    
1425
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1426
	$ruleno = 0;
1427
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1428
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1429
		$ridx = $rulenos_start;
1430
		while ($ridx < $rulenos_range_max) {
1431
			if (empty($rules[$ridx])) {
1432
				$ruleno = $ridx;
1433
				$rules[$ridx] = $cpzone;
1434
				$ridx++;
1435
				$rules[$ridx] = $cpzone;
1436
				break;
1437
			} else {
1438
				$ridx += 2;
1439
			}
1440
		}
1441
	} else {
1442
		$rules = array_pad(array(), $rulenos_range_max, false);
1443
		$ruleno = $rulenos_start;
1444
		$rules[$rulenos_start] = $cpzone;
1445
		$rulenos_start++;
1446
		$rules[$rulenos_start] = $cpzone;
1447
	}
1448
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1449
	unlock($cpruleslck);
1450
	unset($rules);
1451

    
1452
	return $ruleno;
1453
}
1454

    
1455
function captiveportal_free_dn_ruleno($ruleno) {
1456
	global $config, $g;
1457

    
1458
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1459
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1460
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1461
		$rules[$ruleno] = false;
1462
		$ruleno++;
1463
		$rules[$ruleno] = false;
1464
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1465
		unset($rules);
1466
	}
1467
	unlock($cpruleslck);
1468
}
1469

    
1470
function captiveportal_get_dn_passthru_ruleno($value) {
1471
	global $config, $g, $cpzone, $cpzoneid;
1472

    
1473
	$cpcfg = $config['captiveportal'][$cpzone];
1474
	if(!isset($cpcfg['enable']))
1475
		return NULL;
1476

    
1477
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1478
	$ruleno = NULL;
1479
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1480
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1481
		unset($output);
1482
		$_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);
1483
		$ruleno = intval($output[0]);
1484
		if (!$rules[$ruleno])
1485
			$ruleno = NULL;
1486
		unset($rules);
1487
	}
1488
	unlock($cpruleslck);
1489

    
1490
	return $ruleno;
1491
}
1492

    
1493
/*
1494
 * This function will calculate the lowest free firewall ruleno
1495
 * within the range specified based on the actual logged on users
1496
 *
1497
 */
1498
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1499
	global $config, $g, $cpzone;
1500

    
1501
	$cpcfg = $config['captiveportal'][$cpzone];
1502
	if(!isset($cpcfg['enable']))
1503
		return NULL;
1504

    
1505
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1506
	$ruleno = 0;
1507
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1508
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1509
		$ridx = $rulenos_start;
1510
		while ($ridx < $rulenos_range_max) {
1511
			if (empty($rules[$ridx])) {
1512
				$ruleno = $ridx;
1513
				$rules[$ridx] = $cpzone;
1514
				$ridx++;
1515
				$rules[$ridx] = $cpzone;
1516
				break;
1517
			} else {
1518
				/* 
1519
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1520
				 * and the out pipe ruleno + 1.
1521
				 */
1522
				$ridx += 2;
1523
			}
1524
		}
1525
	} else {
1526
		$rules = array_pad(array(), $rulenos_range_max, false);
1527
		$ruleno = $rulenos_start;
1528
		$rules[$rulenos_start] = $cpzone;
1529
		$rulenos_start++;
1530
		$rules[$rulenos_start] = $cpzone;
1531
	}
1532
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1533
	unlock($cpruleslck);
1534
	unset($rules);
1535

    
1536
	return $ruleno;
1537
}
1538

    
1539
function captiveportal_free_ipfw_ruleno($ruleno) {
1540
	global $config, $g, $cpzone;
1541

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

    
1546
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1547
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1548
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1549
		$rules[$ruleno] = false;
1550
		$ruleno++;
1551
		$rules[$ruleno] = false;
1552
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1553
		unset($rules);
1554
	}
1555
	unlock($cpruleslck);
1556
}
1557

    
1558
function captiveportal_get_ipfw_passthru_ruleno($value) {
1559
	global $config, $g, $cpzone, $cpzoneid;
1560

    
1561
	$cpcfg = $config['captiveportal'][$cpzone];
1562
	if(!isset($cpcfg['enable']))
1563
		return NULL;
1564

    
1565
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1566
	$ruleno = NULL;
1567
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1568
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1569
		unset($output);
1570
		$_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);
1571
		$ruleno = intval($output[0]);
1572
		if (!$rules[$ruleno])
1573
			$ruleno = NULL;
1574
		unset($rules);
1575
	}
1576
	unlock($cpruleslck);
1577

    
1578
	return $ruleno;
1579
}
1580

    
1581
/**
1582
 * This function will calculate the traffic produced by a client
1583
 * based on its firewall rule
1584
 *
1585
 * Point of view: NAS
1586
 *
1587
 * Input means: from the client
1588
 * Output means: to the client
1589
 *
1590
 */
1591

    
1592
function getVolume($ip, $mac = NULL) {
1593
	global $config, $cpzone, $cpzoneid;
1594

    
1595
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1596
	$volume = array();
1597
	// Initialize vars properly, since we don't want NULL vars
1598
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1599

    
1600
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, 1, $ip, $mac);
1601
	if (is_array($ipfw)) {
1602
		if ($reverse) {
1603
			$volume['output_pkts'] = $ipfw['packets'];
1604
			$volume['output_bytes'] = $ipfw['bytes'];
1605
		}
1606
		else {
1607
			$volume['input_pkts'] = $ipfw['packets'];
1608
			$volume['input_bytes'] = $ipfw['bytes'];
1609
		}
1610
	}
1611

    
1612
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, 2, $ip);
1613
	if (is_array($ipfw)) {
1614
		if ($reverse) {
1615
			$volume['input_pkts'] = $ipfw['packets'];
1616
			$volume['input_bytes'] = $ipfw['bytes'];
1617
		}
1618
		else {
1619
			$volume['output_pkts'] = $ipfw['packets'];
1620
			$volume['output_bytes'] = $ipfw['bytes'];
1621
		}
1622
	}
1623

    
1624
	return $volume;
1625
}
1626

    
1627
/**
1628
 * Get the NAS-IP-Address based on the current wan address
1629
 *
1630
 * Use functions in interfaces.inc to find this out
1631
 *
1632
 */
1633

    
1634
function getNasIP()
1635
{
1636
	global $config, $cpzone;
1637

    
1638
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1639
			$nasIp = get_interface_ip();
1640
	} else {
1641
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1642
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1643
		else
1644
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1645
	}
1646
		
1647
	if(!is_ipaddr($nasIp))
1648
		$nasIp = "0.0.0.0";
1649

    
1650
	return $nasIp;
1651
}
1652

    
1653
function portal_ip_from_client_ip($cliip) {
1654
	global $config, $cpzone;
1655

    
1656
	$isipv6 = is_ipaddrv6($cliip);
1657
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1658
	foreach ($interfaces as $cpif) {
1659
		if ($isipv6) {
1660
			$ip = get_interface_ipv6($cpif);
1661
			$sn = get_interface_subnetv6($cpif);
1662
		} else {
1663
			$ip = get_interface_ip($cpif);
1664
			$sn = get_interface_subnet($cpif);
1665
		}
1666
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1667
			return $ip;
1668
	}
1669

    
1670
	$inet = ($isipv6) ? '-inet6' : '-inet';
1671
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1672
	$iface = trim($iface, "\n");
1673
	if (!empty($iface)) {
1674
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1675
		if (is_ipaddr($ip))
1676
			return $ip;
1677
	}
1678

    
1679
	// doesn't match up to any particular interface
1680
	// so let's set the portal IP to what PHP says 
1681
	// the server IP issuing the request is. 
1682
	// allows same behavior as 1.2.x where IP isn't 
1683
	// in the subnet of any CP interface (static routes, etc.)
1684
	// rather than forcing to DNS hostname resolution
1685
	$ip = $_SERVER['SERVER_ADDR'];
1686
	if (is_ipaddr($ip))
1687
		return $ip;
1688

    
1689
	return false;
1690
}
1691

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

    
1695
	$cpcfg = $config['captiveportal'][$cpzone];
1696

    
1697
	if (isset($cpcfg['httpslogin'])) {
1698
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 8001);
1699
		$ourhostname = $cpcfg['httpsname'];
1700
		
1701
		if ($listenporthttps != 443)
1702
			$ourhostname .= ":" . $listenporthttps;
1703
	} else {
1704
		$listenporthttp  = $cpcfg['listenporthttp']  ? $cpcfg['listenporthttp']  : ($cpcfg['zoneid'] + 8000);
1705
		$ifip = portal_ip_from_client_ip($cliip);
1706
		if (!$ifip)
1707
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1708
		else
1709
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1710
		
1711
		if ($listenporthttp != 80)
1712
			$ourhostname .= ":" . $listenporthttp;
1713
	}
1714
	
1715
	return $ourhostname;
1716
}
1717

    
1718
/* functions move from index.php */
1719

    
1720
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1721
	global $g, $config, $cpzone;
1722

    
1723
	/* Get captive portal layout */
1724
	if ($type == "redir") {
1725
		header("Location: {$redirurl}");
1726
		return;
1727
	} else if ($type == "login")
1728
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1729
	else
1730
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1731

    
1732
	$cpcfg = $config['captiveportal'][$cpzone];
1733

    
1734
	/* substitute the PORTAL_REDIRURL variable */
1735
	if ($cpcfg['preauthurl']) {
1736
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1737
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1738
	}
1739

    
1740
	/* substitute other variables */
1741
	$ourhostname = portal_hostname_from_client_ip($clientip);
1742
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1743
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1744
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1745

    
1746
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1747
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1748
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1749
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1750
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1751

    
1752
	// Special handling case for captive portal master page so that it can be ran 
1753
	// through the PHP interpreter using the include method above.  We convert the
1754
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1755
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1756
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1757
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1758
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1759
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1760
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1761
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1762

    
1763
	echo $htmltext;
1764
}
1765

    
1766
function portal_mac_radius($clientmac,$clientip) {
1767
	global $config, $cpzone;
1768

    
1769
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1770

    
1771
	/* authentication against the radius server */
1772
	$username = mac_format($clientmac);
1773
	$auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1774
	if ($auth_list['auth_val'] == 2)
1775
		return TRUE;
1776

    
1777
	if (!empty($auth_list['url_redirection']))
1778
		portal_reply_page($auth_list['url_redirection'], "redir");
1779

    
1780
	return FALSE;
1781
}
1782

    
1783
function captiveportal_reapply_attributes($cpentry, $attributes) {
1784
	global $config, $cpzone, $g;
1785

    
1786
	$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1787
	$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1788
	$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1789
	$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1790
	$bw_up_pipeno = $cpentry[1];
1791
	$bw_down_pipeno = $cpentry[1]+1;
1792

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

    
1797
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1798
}
1799

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

    
1803
	// Ensure we create an array if we are missing attributes
1804
	if (!is_array($attributes))
1805
		$attributes = array();
1806

    
1807
	unset($sessionid);
1808

    
1809
	/* Do not allow concurrent login execution. */
1810
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1811

    
1812
	if ($attributes['voucher'])
1813
		$remaining_time = $attributes['session_timeout'];
1814

    
1815
	$writecfg = false;
1816
	/* Find an existing session */
1817
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
1818
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1819
			$mac = captiveportal_passthrumac_findbyname($username);
1820
			if (!empty($mac)) {
1821
				if ($_POST['replacemacpassthru']) {
1822
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
1823
						if ($macent['mac'] == $mac['mac']) {
1824
							$macrules = "";
1825
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1826
							$pipeno = captiveportal_get_dn_passthru_ruleno($mac['mac']);
1827
							if ($ruleno) {
1828
								captiveportal_free_ipfw_ruleno($ruleno);
1829
								$macrules .= "delete {$ruleno}\n";
1830
								++$ruleno;
1831
								$macrules .= "delete {$ruleno}\n";
1832
							}
1833
							if ($pipeno) {
1834
								captiveportal_free_dn_ruleno($pipeno);
1835
								$macrules .= "pipe delete {$pipeno}\n";
1836
								++$pipeno;
1837
								$macrules .= "pipe delete {$pipeno}\n";
1838
							}
1839
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1840
							$mac['action'] = 'pass';
1841
							$mac['mac'] = $clientmac;
1842
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1843
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1844
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1845
							mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1846
							$writecfg = true;
1847
							$sessionid = true;
1848
							break;
1849
						}
1850
					}
1851
				} else {
1852
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1853
						$clientmac, $clientip, $username, $password);
1854
					unlock($cpdblck);
1855
					return;
1856
				}
1857
			}
1858
		}
1859
	}
1860

    
1861
	/* read in client database */
1862
	$query = "WHERE ip = '{$clientip}'";
1863
	$tmpusername = strtolower($username);
1864
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins']))
1865
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
1866
	$cpdb = captiveportal_read_db($query);
1867

    
1868
	/* Snapshot the timestamp */
1869
	$allow_time = time();
1870
	$radiusservers = captiveportal_get_radius_servers();
1871
	$unsetindexes = array();
1872
	if (is_null($radiusctx))
1873
		$radiusctx = 'first';
1874

    
1875
	foreach ($cpdb as $cpentry) {
1876
		if (empty($cpentry[11])) {
1877
			$cpentry[11] = 'first';
1878
		}
1879
		/* on the same ip */
1880
		if ($cpentry[2] == $clientip) {
1881
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac)
1882
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1883
			else
1884
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
1885
			$sessionid = $cpentry[5];
1886
			break;
1887
		}
1888
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1889
			// user logged in with an active voucher. Check for how long and calculate 
1890
			// how much time we can give him (voucher credit - used time)
1891
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1892
			if ($remaining_time < 0)    // just in case. 
1893
				$remaining_time = 0;
1894

    
1895
			/* This user was already logged in so we disconnect the old one */
1896
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[11]],13);
1897
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1898
			$unsetindexes[] = $cpentry[5];
1899
			break;
1900
		}
1901
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1902
			/* on the same username */
1903
			if (strcasecmp($cpentry[4], $username) == 0) {
1904
				/* This user was already logged in so we disconnect the old one */
1905
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[11]],13);
1906
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1907
				$unsetindexes[] = $cpentry[5];
1908
				break;
1909
			}
1910
		}
1911
	}
1912
	unset($cpdb);
1913

    
1914
	if (!empty($unsetindexes))
1915
		captiveportal_remove_entries($unsetindexes);
1916

    
1917
	if ($attributes['voucher'] && $remaining_time <= 0)
1918
		return 0;       // voucher already used and no time left
1919

    
1920
	if (!isset($sessionid)) {
1921
		/* generate unique session ID */
1922
		$tod = gettimeofday();
1923
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1924

    
1925
		if ($passthrumac) {
1926
			$mac = array();
1927
			$mac['action'] = 'pass';
1928
			$mac['mac'] = $clientmac;
1929
			$mac['ip'] = $clientip; /* Used only for logging */
1930
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
1931
				$mac['username'] = $username;
1932
				if ($attributes['voucher'])
1933
					$mac['logintype'] = "voucher";
1934
			}
1935
			if ($username = "unauthenticated")
1936
				$mac['descr'] =  "Auto-added";
1937
			else
1938
				$mac['descr'] =  "Auto-added for user {$username}";
1939
			if (!empty($bw_up))
1940
				$mac['bw_up'] = $bw_up;
1941
			if (!empty($bw_down))
1942
				$mac['bw_down'] = $bw_down;
1943
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
1944
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
1945
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1946
			unlock($cpdblck);
1947
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1948
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1949
			mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1950
			$writecfg = true;
1951
		} else {
1952
			/* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
1953
			if (is_null($pipeno))
1954
				$pipeno = captiveportal_get_next_dn_ruleno();
1955

    
1956
			/* if the pool is empty, return appropriate message and exit */
1957
			if (is_null($pipeno)) {
1958
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1959
				log_error("Zone: {$cpzone} - WARNING!  Captive portal has reached maximum login capacity");
1960
				unlock($cpdblck);
1961
				return;
1962
			}
1963

    
1964
			$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1965
			$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1966
			$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1967
			$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1968

    
1969
			$bw_up_pipeno = $pipeno;
1970
			$bw_down_pipeno = $pipeno + 1;
1971
			//$bw_up /= 1000; // Scale to Kbit/s
1972
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1973
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1974

    
1975
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
1976
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1977
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
1978
			else
1979
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
1980

    
1981
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1982
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
1983
			else
1984
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
1985

    
1986
			if ($attributes['voucher'])
1987
				$attributes['session_timeout'] = $remaining_time;
1988
			
1989
			/* handle empty attributes */
1990
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
1991
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
1992
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
1993
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
1994

    
1995
			/* escape username */
1996
			$safe_username = SQLite3::escapeString($username);
1997

    
1998
			/* encode password in Base64 just in case it contains commas */
1999
			$bpassword = base64_encode($password);
2000
			$insertquery  = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, radiusctx) ";
2001
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
2002
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, '{$radiusctx}')";
2003

    
2004
			/* store information to database */
2005
			captiveportal_write_db($insertquery);
2006
			unlock($cpdblck);
2007
			unset($insertquery, $bpassword);
2008

    
2009
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
2010
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
2011
				if ($acct_val == 1)
2012
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
2013
			}
2014
		}
2015
	} else {
2016
		/* NOTE: #3062-11 If the pipeno has been allocated free it to not DoS the CP and maintain proper operation as in radius() case */
2017
		if (!is_null($pipeno))
2018
			captiveportal_free_dn_ruleno($pipeno);
2019

    
2020
		unlock($cpdblck);
2021
	}
2022

    
2023
	if ($writecfg == true)
2024
		write_config();
2025

    
2026
	/* redirect user to desired destination */
2027
	if (!empty($attributes['url_redirection']))
2028
		$my_redirurl = $attributes['url_redirection'];
2029
	else if (!empty($redirurl))
2030
		$my_redirurl = $redirurl;
2031
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
2032
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
2033

    
2034
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
2035
		$ourhostname = portal_hostname_from_client_ip($clientip);
2036
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
2037
		$logouturl = "{$protocol}{$ourhostname}/";
2038

    
2039
		if (isset($attributes['reply_message']))
2040
			$message = $attributes['reply_message'];
2041
		else
2042
			$message = 0;
2043

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

    
2046
	} else {
2047
		portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
2048
	}
2049

    
2050
	return $sessionid;
2051
}
2052

    
2053

    
2054
/*
2055
 * Used for when pass-through credits are enabled.
2056
 * Returns true when there was at least one free login to deduct for the MAC.
2057
 * Expired entries are removed as they are seen.
2058
 * Active entries are updated according to the configuration.
2059
 */
2060
function portal_consume_passthrough_credit($clientmac) {
2061
	global $config, $cpzone;
2062

    
2063
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
2064
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
2065
	else
2066
		return false;
2067

    
2068
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
2069
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2070
	else
2071
		return false;
2072

    
2073
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
2074
		return false;
2075

    
2076
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2077

    
2078
	/*
2079
	 * Read database of used MACs.  Lines are a comma-separated list
2080
	 * of the time, MAC, then the count of pass-through credits remaining.
2081
	 */
2082
	$usedmacs = captiveportal_read_usedmacs_db();
2083

    
2084
	$currenttime = time();
2085
	$found = false;
2086
	foreach ($usedmacs as $key => $usedmac) {
2087
		$usedmac = explode(",", $usedmac);
2088

    
2089
		if ($usedmac[1] == $clientmac) {
2090
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2091
				if ($usedmac[2] < 1) {
2092
					if ($updatetimeouts) {
2093
						$usedmac[0] = $currenttime;
2094
						unset($usedmacs[$key]);
2095
						$usedmacs[] = implode(",", $usedmac);
2096
						captiveportal_write_usedmacs_db($usedmacs);
2097
					}
2098

    
2099
					return false;
2100
				} else {
2101
					$usedmac[2] -= 1;
2102
					$usedmacs[$key] = implode(",", $usedmac);
2103
				}
2104

    
2105
				$found = true;
2106
			} else
2107
				unset($usedmacs[$key]);
2108

    
2109
			break;
2110
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2111
				unset($usedmacs[$key]);
2112
	}
2113

    
2114
	if (!$found) {
2115
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2116
		$usedmacs[] = implode(",", $usedmac);
2117
	}
2118

    
2119
	captiveportal_write_usedmacs_db($usedmacs);
2120
	return true;
2121
}
2122

    
2123
function captiveportal_read_usedmacs_db() {
2124
	global $g, $cpzone;
2125

    
2126
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2127
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2128
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2129
		if (!$usedmacs)
2130
			$usedmacs = array();
2131
	} else
2132
		$usedmacs = array();
2133

    
2134
	unlock($cpumaclck);
2135
	return $usedmacs;
2136
}
2137

    
2138
function captiveportal_write_usedmacs_db($usedmacs) {
2139
	global $g, $cpzone;
2140

    
2141
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2142
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2143
	unlock($cpumaclck);
2144
}
2145

    
2146
function captiveportal_blocked_mac($mac) {
2147
	global $config, $g, $cpzone;
2148

    
2149
	if (empty($mac) || !is_macaddr($mac))
2150
		return false;
2151

    
2152
	if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
2153
		return false;
2154

    
2155
	foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac)
2156
		if (($passthrumac['action'] == 'block') &&
2157
		    ($passthrumac['mac'] == strtolower($mac)))
2158
			return true;
2159

    
2160
	return false;
2161

    
2162
}
2163

    
2164
function captiveportal_send_server_accounting($off = false) {
2165
	global $cpzone, $config;
2166

    
2167
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
2168
		return;
2169
	}
2170
	if ($off) {
2171
		$racct = new Auth_RADIUS_Acct_Off;
2172
	} else {
2173
		$racct = new Auth_RADIUS_Acct_On;
2174
	}
2175
	$radiusservers = captiveportal_get_radius_servers();
2176
	if (empty($radiusservers)) {
2177
		return;
2178
	}
2179
	foreach ($radiusservers['first'] as $radsrv) {
2180
		// Add a new server to our instance
2181
		$racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
2182
	}
2183
	if (PEAR::isError($racct->start())) {
2184
		$retvalue['acct_val'] = 1;
2185
		$retvalue['error'] = $racct->getMessage();
2186

    
2187
		// If we encounter an error immediately stop this function and go back
2188
		$racct->close();
2189
		return $retvalue;
2190
	}
2191
	// Send request
2192
	$result = $racct->send();
2193
	// Evaluation of the response
2194
	// 5 -> Accounting-Response
2195
	// See RFC2866 for this.
2196
	if (PEAR::isError($result)) {
2197
		$retvalue['acct_val'] = 1;
2198
		$retvalue['error'] = $result->getMessage();
2199
	} else if ($result === true) {
2200
		$retvalue['acct_val'] = 5 ;
2201
	} else {
2202
		$retvalue['acct_val'] = 1 ;
2203
	}
2204

    
2205
	$racct->close();
2206
	return $retvalue;
2207
}
2208
?>
(8-8/68)