Projet

Général

Profil

Télécharger (72,8 ko) Statistiques
| Branche: | Tag: | Révision:

univnautes / etc / inc / captiveportal.inc @ 5cf91315

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/sysctl	/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
		mwexec("/sbin/sysctl net.inet.ip.pfil.inbound=\"pf\" net.inet6.ip6.pfil.inbound=\"pf\"" .
155
		       " net.inet.ip.pfil.outbound=\"pf\" net.inet6.ip6.pfil.outbound=\"pf\"");
156
	}
157
	/* Activate layer2 filtering */
158
	mwexec("/sbin/sysctl net.link.ether.ipfw=1 net.inet.ip.fw.one_pass=1");
159

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

    
168
function captiveportal_configure() {
169
	global $config, $cpzone, $cpzoneid;
170

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

    
180
function captiveportal_configure_zone($cpcfg) {
181
	global $config, $g, $cpzone, $cpzoneid;
182

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

    
187
		if ($g['booting']) {
188
			echo "Starting captive portal({$cpcfg['zone']})... ";
189

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

    
195
		/* init ipfw rules */
196
		captiveportal_init_rules(true);
197

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

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

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

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

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

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

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

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

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

    
301
EOD;
302
		}
303

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

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

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

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

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

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

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

    
333
		if ($g['booting']) {
334
			/* send Accounting-On to server */
335
			captiveportal_send_server_accounting();
336
			echo "done\n";
337
		}
338

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

    
347
		captiveportal_radius_stop_all();
348

    
349
		/* send Accounting-Off to server */
350
		if (!$g['booting']) {
351
			captiveportal_send_server_accounting(true);
352
		}
353

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

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

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

    
382
	unlock($captiveportallck);
383
	
384
	return 0;
385
}
386

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

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

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

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

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

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

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

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

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

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

    
450
/* reinit will disconnect all users, be careful! */
451
function captiveportal_init_rules($reinit = false) {
452
	global $config, $g, $cpzone, $cpzoneid;
453

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

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

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

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

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

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

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

    
508
EOD;
509

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

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

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

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

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

    
570
EOD;
571

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

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

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

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

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

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

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

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

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

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

    
622
	$radiussrvs = captiveportal_get_radius_servers();
623

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

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

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

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

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

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

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

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

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

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

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

    
762
	captiveportal_prune_old_automac();
763

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

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

    
773
function captiveportal_prune_old_automac() {
774
	global $g, $config, $cpzone, $cpzoneid;
775

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

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

    
841
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
842

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

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

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

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

    
887
}
888

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

    
893
	$radiusservers = captiveportal_get_radius_servers();
894

    
895
	/* read database */
896
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
897

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

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

    
912
/* send RADIUS acct stop for all current clients */
913
function captiveportal_radius_stop_all() {
914
	global $config, $cpzone;
915

    
916
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable']))
917
		return;
918

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

    
939
function captiveportal_passthrumac_configure_entry($macent) {
940
	global $config, $g, $cpzone;
941

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

    
953
	$ruleno = captiveportal_get_next_ipfw_ruleno();
954

    
955
	if ($macent['action'] == 'pass') {
956
		$pipeno = captiveportal_get_next_dn_ruleno();
957

    
958
		$pipeup = $pipeno;
959
		$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16");
960
		$pipedown = $pipeno + 1;
961
		$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
962

    
963
		$rules = "add {$ruleno} pipe {$pipeup} ip from any to any MAC any {$macent['mac']}\n";
964
		$ruleno++;
965
		$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC {$macent['mac']} any\n";
966
	}
967

    
968
	return $rules;
969
}
970

    
971
function captiveportal_passthrumac_delete_entry($macent) {
972
	$rules = "";
973

    
974
	if ($macent['action'] == 'pass') {
975
		$ruleno = captiveportal_get_ipfw_passthru_ruleno($macent['mac']);
976

    
977
		if (!$ruleno)
978
			return $rules;
979

    
980
		captiveportal_free_ipfw_ruleno($ruleno);
981

    
982
		$rules .= "delete {$ruleno}\n";
983
		$rules .= "delete " . ++$ruleno . "\n";
984

    
985
		$pipeno = captiveportal_get_dn_passthru_ruleno($macent['mac']);
986

    
987
		if (!empty($pipeno)) {
988
			captiveportal_free_dn_ruleno($pipeno);
989
			$rules .= "pipe delete " . $pipeno . "\n";
990
			$rules .= "pipe delete " . ++$pipeno . "\n";
991
		}
992
	}
993

    
994
	return $rules;
995
}
996

    
997
function captiveportal_passthrumac_configure($lock = false) {
998
	global $config, $g, $cpzone;
999

    
1000
	$rules = "";
1001

    
1002
	if (is_array($config['captiveportal'][$cpzone]['passthrumac']))
1003
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent)
1004
			$rules .= captiveportal_passthrumac_configure_entry($macent);
1005

    
1006
	return $rules;
1007
}
1008

    
1009
function captiveportal_passthrumac_findbyname($username) {
1010
	global $config, $cpzone;
1011

    
1012
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1013
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1014
			if ($macent['username'] == $username)
1015
				return $macent;
1016
		}
1017
	}
1018
	return NULL;
1019
}
1020

    
1021
/* 
1022
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1023
 */
1024
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
1025
	global $g;
1026

    
1027
	/*  Instead of copying this entire function for something
1028
	 *  easy such as hostname vs ip address add this check
1029
	 */
1030
	if ($ishostname === true) {
1031
		if (!$g['booting']) {
1032
			$ipaddress = gethostbyname($ipent['hostname']);
1033
			if (!is_ipaddr($ipaddress)) 
1034
				return;
1035
		} else
1036
			$ipaddress = "";
1037
	} else
1038
		$ipaddress = $ipent['ip'];
1039

    
1040
	$rules = "";
1041
	$cp_filterdns_conf = "";
1042
	$enBwup = 0;
1043
	if (!empty($ipent['bw_up']))
1044
		$enBwup = intval($ipent['bw_up']);
1045
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultup']))
1046
		$enBwup = $config['captiveportal'][$cpzone]['bwdefaultup'];
1047
	$enBwdown = 0;
1048
	if (!empty($ipent['bw_down']))
1049
		$enBwdown = intval($ipent['bw_down']);
1050
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultdn']))
1051
		$enBwdown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
1052

    
1053
	$pipeno = captiveportal_get_next_dn_ruleno();
1054
	$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$enBwup}Kbit/s queue 100 buckets 16");
1055
	$pipedown = $pipeno + 1;
1056
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16");
1057
	if ($ishostname === true) {
1058
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
1059
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
1060
		if (!is_ipaddr($ipaddress))
1061
			return array("", $cp_filterdns_conf);
1062
	}
1063
	$subnet = "";
1064
	if (!empty($ipent['sn']))
1065
		$subnet = "/{$ipent['sn']}";
1066
	$rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
1067
	$rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
1068

    
1069
	if ($ishostname === true)
1070
		return array($rules, $cp_filterdns_conf);
1071
	else
1072
		return $rules;
1073
}
1074

    
1075
function captiveportal_allowedhostname_configure() {
1076
	global $config, $g, $cpzone;
1077

    
1078
	$rules = "";
1079
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1080
		$rules = "\n# captiveportal_allowedhostname_configure()\n";
1081
		$cp_filterdns_conf = "";
1082
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) {
1083
			$tmprules = captiveportal_allowedip_configure_entry($hostnameent, true);
1084
			$rules .= $tmprules[0];
1085
			$cp_filterdns_conf .= $tmprules[1];
1086
		}
1087
		$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1088
		@file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1089
		unset($cp_filterdns_conf);
1090
		if (isvalidpid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid"))
1091
			sigkillbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid", "HUP");
1092
		else
1093
			mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzone} -d 1");
1094
	} else {
1095
		killbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1096
		@unlink("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1097
	}
1098

    
1099
	return $rules;
1100
}
1101

    
1102
function captiveportal_allowedip_configure() {
1103
	global $config, $g, $cpzone;
1104

    
1105
	$rules = "";
1106
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1107
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1108
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1109
	}
1110

    
1111
	return $rules;
1112
}
1113

    
1114
/* get last activity timestamp given client IP address */
1115
function captiveportal_get_last_activity($ip, $mac = NULL) {
1116
	global $cpzone;
1117

    
1118
	$ipfwoutput = pfSense_ipfw_getTablestats($cpzone, 1, $ip, $mac);
1119
	/* Reading only from one of the tables is enough of approximation. */
1120
	if (is_array($ipfwoutput)) {
1121
		return $ipfwoutput['timestamp'];
1122
	}
1123

    
1124
	return 0;
1125
}
1126

    
1127
function captiveportal_init_radius_servers() {
1128
	global $config, $g, $cpzone;
1129

    
1130
	/* generate radius server database */
1131
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1132
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1133
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1134
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1135
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1136
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1137

    
1138
		if ($config['captiveportal'][$cpzone]['radiusport'])
1139
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1140
		else
1141
			$radiusport = 1812;
1142
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1143
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1144
		else
1145
			$radiusacctport = 1813;
1146
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1147
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1148
		else
1149
			$radiusport2 = 1812;
1150
		if ($config['captiveportal'][$cpzone]['radiusport3'])
1151
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1152
		else
1153
			$radiusport3 = 1812;
1154
		if ($config['captiveportal'][$cpzone]['radiusport4'])
1155
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1156
		else
1157
			$radiusport4 = 1812;
1158

    
1159
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1160
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1161
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1162
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1163

    
1164
		$cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX);
1165
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
1166
		if (!$fd) {
1167
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1168
			unlock($cprdsrvlck);
1169
			return 1;
1170
		}
1171
		if (isset($radiusip))
1172
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
1173
		if (isset($radiusip2))
1174
			fwrite($fd,"\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
1175
		if (isset($radiusip3))
1176
			fwrite($fd,"\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
1177
		if (isset($radiusip4))
1178
			fwrite($fd,"\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
1179
		
1180

    
1181
		fclose($fd);
1182
		unlock($cprdsrvlck);
1183
	}
1184
}
1185

    
1186
/* read RADIUS servers into array */
1187
function captiveportal_get_radius_servers() {
1188
	global $g, $cpzone;
1189

    
1190
	$cprdsrvlck = lock("captiveportalradius{$cpzone}");
1191
	if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
1192
		$radiusservers = array();
1193
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", 
1194
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1195
		if ($cpradiusdb) {
1196
			foreach($cpradiusdb as $cpradiusentry) {
1197
				$line = trim($cpradiusentry);
1198
				if ($line) {
1199
					$radsrv = array();
1200
						list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key'], $context) = explode(",",$line);
1201
				}
1202
				if (empty($context)) {
1203
					if (!is_array($radiusservers['first']))
1204
						$radiusservers['first'] = array();
1205
					$radiusservers['first'] = $radsrv;
1206
				} else {
1207
					if (!is_array($radiusservers[$context]))
1208
						$radiusservers[$context] = array();
1209
					$radiusservers[$context][] = $radsrv;
1210
				}
1211
			}
1212
		}
1213
		unlock($cprdsrvlck);
1214
		return $radiusservers;
1215
	}
1216

    
1217
	unlock($cprdsrvlck);
1218
	return false;
1219
}
1220

    
1221
/* log successful captive portal authentication to syslog */
1222
/* part of this code from php.net */
1223
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1224
	// Log it
1225
	if (!$message)
1226
		$message = "{$status}: {$user}, {$mac}, {$ip}";
1227
	else {
1228
		$message = trim($message);
1229
		$message = "{$status}: {$user}, {$mac}, {$ip}, {$message}";
1230
	}
1231
	captiveportal_syslog($message);
1232
}
1233

    
1234
/* log simple messages to syslog */
1235
function captiveportal_syslog($message) {
1236
	global $cpzone;
1237

    
1238
	$message = trim($message);
1239
	$message .= "Zone: {$cpzone} - {$message}";
1240
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1241
	// Log it
1242
	syslog(LOG_INFO, $message);
1243
	closelog();
1244
}
1245

    
1246
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1247
	global $g, $config, $cpzoneid;
1248

    
1249
	$pipeno = captiveportal_get_next_dn_ruleno();
1250

    
1251
	/* If the pool is empty, return appropriate message and fail authentication */
1252
	if (empty($pipeno)) {
1253
		$auth_list = array();
1254
		$auth_list['auth_val'] = 1;
1255
		$auth_list['error'] = "System reached maximum login capacity";
1256
		return $auth_list;
1257
	}
1258

    
1259
	$radiusservers = captiveportal_get_radius_servers();
1260

    
1261
	if (is_null($radiusctx))
1262
		$radiusctx = 'first';
1263

    
1264
	$auth_list = RADIUS_AUTHENTICATION($username,
1265
		$password,
1266
		$radiusservers[$radiusctx],
1267
		$clientip,
1268
		$clientmac,
1269
		$pipeno);
1270

    
1271
	if ($auth_list['auth_val'] == 2) {
1272
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1273
		$sessionid = portal_allow($clientip,
1274
			$clientmac,
1275
			$username,
1276
			$password,
1277
			$auth_list,
1278
			$pipeno,
1279
			$radiusctx);
1280
	} else {
1281
	         captiveportal_free_dn_ruleno($pipeno);
1282
	       }
1283

    
1284
	return $auth_list;
1285
}
1286

    
1287
function captiveportal_opendb() {
1288
	global $g, $cpzone;
1289

    
1290
	$DB = new SQLite3("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1291
	if (! $DB->exec("CREATE TABLE IF NOT EXISTS captiveportal (" .
1292
				"allow_time INTEGER, pipeno INTEGER, ip TEXT, mac TEXT, username TEXT, " .
1293
				"sessionid TEXT, bpassword TEXT, session_timeout INTEGER, idle_timeout INTEGER, " .
1294
				"session_terminate_time INTEGER, interim_interval INTEGER, radiusctx TEXT); " .
1295
			"CREATE UNIQUE INDEX IF NOT EXISTS idx_active ON captiveportal (sessionid, username); " .
1296
			"CREATE INDEX IF NOT EXISTS user ON captiveportal (username); " .
1297
			"CREATE INDEX IF NOT EXISTS ip ON captiveportal (ip); " .
1298
			"CREATE INDEX IF NOT EXISTS starttime ON captiveportal (allow_time)"))
1299
		captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$DB->lastErrorMsg()}");
1300

    
1301
	return $DB;
1302
}
1303

    
1304
/* read captive portal DB into array */
1305
function captiveportal_read_db($query = "") {
1306
	$cpdb = array();
1307

    
1308
	$DB = captiveportal_opendb();
1309
	if ($DB) {
1310
		$response = $DB->query("SELECT * FROM captiveportal {$query}");
1311
		while ($row = $response->fetchArray())
1312
			$cpdb[] = $row;
1313
		$DB->close();
1314
	}
1315

    
1316
	return $cpdb;
1317
}
1318

    
1319
function captiveportal_remove_entries($remove) {
1320

    
1321
	if (!is_array($remove) || empty($remove))
1322
		return;
1323

    
1324
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1325
	foreach($remove as $idx => $unindex) {
1326
		$query .= "'{$unindex}'";
1327
		if ($idx < (count($remove) - 1))
1328
			$query .= ",";
1329
	}
1330
	$query .= ")";
1331
	captiveportal_write_db($query);
1332
}
1333

    
1334
/* write captive portal DB */
1335
function captiveportal_write_db($queries) {
1336
	global $g;
1337

    
1338
	if (is_array($queries))
1339
		$query = implode(";", $queries);
1340
	else
1341
		$query = $queries;
1342

    
1343
	$DB = captiveportal_opendb();
1344
	if ($DB) {
1345
		$DB->exec("BEGIN TRANSACTION");
1346
		$result = $DB->exec($query);
1347
		if (!$result)
1348
			captiveportal_syslog("Trying to modify DB returned error: {$DB->lastErrorMsg()}");
1349
		else
1350
			$DB->exec("END TRANSACTION");
1351
		$DB->close();
1352
		return $result;
1353
	} else
1354
		return true;
1355
}
1356

    
1357
function captiveportal_write_elements() {
1358
	global $g, $config, $cpzone;
1359
	
1360
	$cpcfg = $config['captiveportal'][$cpzone];
1361

    
1362
	if (!is_dir($g['captiveportal_element_path']))
1363
		@mkdir($g['captiveportal_element_path']);
1364

    
1365
	if (is_array($cpcfg['element'])) {
1366
		conf_mount_rw();
1367
		foreach ($cpcfg['element'] as $data) {
1368
			if (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1369
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1370
				return 1;
1371
			}
1372
			if (!file_exists("{$g['captiveportal_path']}/{$data['name']}"))
1373
				@symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1374
		}
1375
		conf_mount_ro();
1376
	}
1377
	
1378
	return 0;
1379
}
1380

    
1381
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1382
	global $cpzone;
1383

    
1384
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1385
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1386
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1387
		$ridx = $rulenos_start;
1388
		while ($ridx < $rulenos_range_max) {
1389
			if ($rules[$ridx] == $cpzone) {
1390
				$rules[$ridx] = false;
1391
				$ridx++;
1392
				$rules[$ridx] = false;
1393
				$ridx++;
1394
			} else
1395
				$ridx += 2;
1396
		}
1397
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1398
		unset($rules);
1399
	}
1400
	unlock($cpruleslck);
1401
}
1402

    
1403
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1404
	global $config, $g, $cpzone;
1405

    
1406
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1407
	$ruleno = 0;
1408
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1409
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1410
		$ridx = $rulenos_start;
1411
		while ($ridx < $rulenos_range_max) {
1412
			if (empty($rules[$ridx])) {
1413
				$ruleno = $ridx;
1414
				$rules[$ridx] = $cpzone;
1415
				$ridx++;
1416
				$rules[$ridx] = $cpzone;
1417
				break;
1418
			} else {
1419
				$ridx += 2;
1420
			}
1421
		}
1422
	} else {
1423
		$rules = array_pad(array(), $rulenos_range_max, false);
1424
		$ruleno = $rulenos_start;
1425
		$rules[$rulenos_start] = $cpzone;
1426
		$rulenos_start++;
1427
		$rules[$rulenos_start] = $cpzone;
1428
	}
1429
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1430
	unlock($cpruleslck);
1431
	unset($rules);
1432

    
1433
	return $ruleno;
1434
}
1435

    
1436
function captiveportal_free_dn_ruleno($ruleno) {
1437
	global $config, $g;
1438

    
1439
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1440
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1441
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1442
		$rules[$ruleno] = false;
1443
		$ruleno++;
1444
		$rules[$ruleno] = false;
1445
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1446
		unset($rules);
1447
	}
1448
	unlock($cpruleslck);
1449
}
1450

    
1451
function captiveportal_get_dn_passthru_ruleno($value) {
1452
	global $config, $g, $cpzone, $cpzoneid;
1453

    
1454
	$cpcfg = $config['captiveportal'][$cpzone];
1455
	if(!isset($cpcfg['enable']))
1456
		return NULL;
1457

    
1458
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1459
	$ruleno = NULL;
1460
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1461
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1462
		unset($output);
1463
		$_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);
1464
		$ruleno = intval($output[0]);
1465
		if (!$rules[$ruleno])
1466
			$ruleno = NULL;
1467
		unset($rules);
1468
	}
1469
	unlock($cpruleslck);
1470

    
1471
	return $ruleno;
1472
}
1473

    
1474
/*
1475
 * This function will calculate the lowest free firewall ruleno
1476
 * within the range specified based on the actual logged on users
1477
 *
1478
 */
1479
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1480
	global $config, $g, $cpzone;
1481

    
1482
	$cpcfg = $config['captiveportal'][$cpzone];
1483
	if(!isset($cpcfg['enable']))
1484
		return NULL;
1485

    
1486
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1487
	$ruleno = 0;
1488
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1489
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1490
		$ridx = $rulenos_start;
1491
		while ($ridx < $rulenos_range_max) {
1492
			if (empty($rules[$ridx])) {
1493
				$ruleno = $ridx;
1494
				$rules[$ridx] = $cpzone;
1495
				$ridx++;
1496
				$rules[$ridx] = $cpzone;
1497
				break;
1498
			} else {
1499
				/* 
1500
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1501
				 * and the out pipe ruleno + 1.
1502
				 */
1503
				$ridx += 2;
1504
			}
1505
		}
1506
	} else {
1507
		$rules = array_pad(array(), $rulenos_range_max, false);
1508
		$ruleno = $rulenos_start;
1509
		$rules[$rulenos_start] = $cpzone;
1510
		$rulenos_start++;
1511
		$rules[$rulenos_start] = $cpzone;
1512
	}
1513
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1514
	unlock($cpruleslck);
1515
	unset($rules);
1516

    
1517
	return $ruleno;
1518
}
1519

    
1520
function captiveportal_free_ipfw_ruleno($ruleno) {
1521
	global $config, $g, $cpzone;
1522

    
1523
	$cpcfg = $config['captiveportal'][$cpzone];
1524
	if(!isset($cpcfg['enable']))
1525
		return NULL;
1526

    
1527
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1528
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1529
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1530
		$rules[$ruleno] = false;
1531
		$ruleno++;
1532
		$rules[$ruleno] = false;
1533
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1534
		unset($rules);
1535
	}
1536
	unlock($cpruleslck);
1537
}
1538

    
1539
function captiveportal_get_ipfw_passthru_ruleno($value) {
1540
	global $config, $g, $cpzone, $cpzoneid;
1541

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

    
1546
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1547
	$ruleno = NULL;
1548
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1549
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1550
		unset($output);
1551
		$_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);
1552
		$ruleno = intval($output[0]);
1553
		if (!$rules[$ruleno])
1554
			$ruleno = NULL;
1555
		unset($rules);
1556
	}
1557
	unlock($cpruleslck);
1558

    
1559
	return $ruleno;
1560
}
1561

    
1562
/**
1563
 * This function will calculate the traffic produced by a client
1564
 * based on its firewall rule
1565
 *
1566
 * Point of view: NAS
1567
 *
1568
 * Input means: from the client
1569
 * Output means: to the client
1570
 *
1571
 */
1572

    
1573
function getVolume($ip, $mac = NULL) {
1574
	global $config, $cpzone;
1575

    
1576
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1577
	$volume = array();
1578
	// Initialize vars properly, since we don't want NULL vars
1579
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1580

    
1581
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 1, $ip, $mac);
1582
	if (is_array($ipfw)) {
1583
		if ($reverse) {
1584
			$volume['output_pkts'] = $ipfw['packets'];
1585
			$volume['output_bytes'] = $ipfw['bytes'];
1586
		}
1587
		else {
1588
			$volume['input_pkts'] = $ipfw['packets'];
1589
			$volume['input_bytes'] = $ipfw['bytes'];
1590
		}
1591
	}
1592

    
1593
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 2, $ip);
1594
	if (is_array($ipfw)) {
1595
		if ($reverse) {
1596
			$volume['input_pkts'] = $ipfw['packets'];
1597
			$volume['input_bytes'] = $ipfw['bytes'];
1598
		}
1599
		else {
1600
			$volume['output_pkts'] = $ipfw['packets'];
1601
			$volume['output_bytes'] = $ipfw['bytes'];
1602
		}
1603
	}
1604

    
1605
	return $volume;
1606
}
1607

    
1608
/**
1609
 * Get the NAS-IP-Address based on the current wan address
1610
 *
1611
 * Use functions in interfaces.inc to find this out
1612
 *
1613
 */
1614

    
1615
function getNasIP()
1616
{
1617
	global $config, $cpzone;
1618

    
1619
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1620
			$nasIp = get_interface_ip();
1621
	} else {
1622
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1623
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1624
		else
1625
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1626
	}
1627
		
1628
	if(!is_ipaddr($nasIp))
1629
		$nasIp = "0.0.0.0";
1630

    
1631
	return $nasIp;
1632
}
1633

    
1634
function portal_ip_from_client_ip($cliip) {
1635
	global $config, $cpzone;
1636

    
1637
	$isipv6 = is_ipaddrv6($cliip);
1638
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1639
	foreach ($interfaces as $cpif) {
1640
		if ($isipv6) {
1641
			$ip = get_interface_ipv6($cpif);
1642
			$sn = get_interface_subnetv6($cpif);
1643
		} else {
1644
			$ip = get_interface_ip($cpif);
1645
			$sn = get_interface_subnet($cpif);
1646
		}
1647
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1648
			return $ip;
1649
	}
1650

    
1651
	$inet = ($isipv6) ? '-inet6' : '-inet';
1652
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1653
	$iface = trim($iface, "\n");
1654
	if (!empty($iface)) {
1655
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1656
		if (is_ipaddr($ip))
1657
			return $ip;
1658
	}
1659

    
1660
	// doesn't match up to any particular interface
1661
	// so let's set the portal IP to what PHP says 
1662
	// the server IP issuing the request is. 
1663
	// allows same behavior as 1.2.x where IP isn't 
1664
	// in the subnet of any CP interface (static routes, etc.)
1665
	// rather than forcing to DNS hostname resolution
1666
	$ip = $_SERVER['SERVER_ADDR'];
1667
	if (is_ipaddr($ip))
1668
		return $ip;
1669

    
1670
	return false;
1671
}
1672

    
1673
function portal_hostname_from_client_ip($cliip) {
1674
	global $config, $cpzone;
1675

    
1676
	$cpcfg = $config['captiveportal'][$cpzone];
1677

    
1678
	if (isset($cpcfg['httpslogin'])) {
1679
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
1680
		$ourhostname = $cpcfg['httpsname'];
1681
		
1682
		if ($listenporthttps != 443)
1683
			$ourhostname .= ":" . $listenporthttps;
1684
	} else {
1685
		$listenporthttp  = $cpcfg['listenporthttp']  ? $cpcfg['listenporthttp']  : $cpcfg['zoneid'];
1686
		$ifip = portal_ip_from_client_ip($cliip);
1687
		if (!$ifip)
1688
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1689
		else
1690
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1691
		
1692
		if ($listenporthttp != 80)
1693
			$ourhostname .= ":" . $listenporthttp;
1694
	}
1695
	
1696
	return $ourhostname;
1697
}
1698

    
1699
/* functions move from index.php */
1700

    
1701
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1702
	global $g, $config, $cpzone;
1703

    
1704
	/* Get captive portal layout */
1705
	if ($type == "redir") {
1706
		header("Location: {$redirurl}");
1707
		return;
1708
	} else if ($type == "login")
1709
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1710
	else
1711
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1712

    
1713
	$cpcfg = $config['captiveportal'][$cpzone];
1714

    
1715
	/* substitute the PORTAL_REDIRURL variable */
1716
	if ($cpcfg['preauthurl']) {
1717
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1718
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1719
	}
1720

    
1721
	/* substitute other variables */
1722
	$ourhostname = portal_hostname_from_client_ip($clientip);
1723
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1724
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1725
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1726

    
1727
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1728
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1729
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1730
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1731
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1732

    
1733
	// Special handling case for captive portal master page so that it can be ran 
1734
	// through the PHP interpreter using the include method above.  We convert the
1735
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1736
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1737
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1738
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1739
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1740
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1741
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1742
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1743

    
1744
	echo $htmltext;
1745
}
1746

    
1747
function portal_mac_radius($clientmac,$clientip) {
1748
	global $config, $cpzone;
1749

    
1750
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1751

    
1752
	/* authentication against the radius server */
1753
	$username = mac_format($clientmac);
1754
	$auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1755
	if ($auth_list['auth_val'] == 2)
1756
		return TRUE;
1757

    
1758
	if (!empty($auth_list['url_redirection']))
1759
		portal_reply_page($auth_list['url_redirection'], "redir");
1760

    
1761
	return FALSE;
1762
}
1763

    
1764
function captiveportal_reapply_attributes($cpentry, $attributes) {
1765
	global $config, $cpzone, $g;
1766

    
1767
	$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1768
	$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1769
	$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1770
	$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1771
	$bw_up_pipeno = $cpentry[1];
1772
	$bw_down_pipeno = $cpentry[1]+1;
1773

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

    
1778
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1779
}
1780

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

    
1784
	// Ensure we create an array if we are missing attributes
1785
	if (!is_array($attributes))
1786
		$attributes = array();
1787

    
1788
	unset($sessionid);
1789

    
1790
	/* Do not allow concurrent login execution. */
1791
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1792

    
1793
	if ($attributes['voucher'])
1794
		$remaining_time = $attributes['session_timeout'];
1795

    
1796
	$writecfg = false;
1797
	/* Find an existing session */
1798
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
1799
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1800
			$mac = captiveportal_passthrumac_findbyname($username);
1801
			if (!empty($mac)) {
1802
				if ($_POST['replacemacpassthru']) {
1803
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
1804
						if ($macent['mac'] == $mac['mac']) {
1805
							$macrules = "";
1806
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1807
							$pipeno = captiveportal_get_dn_passthru_ruleno($mac['mac']);
1808
							if ($ruleno) {
1809
								captiveportal_free_ipfw_ruleno($ruleno);
1810
								$macrules .= "delete {$ruleno}\n";
1811
								++$ruleno;
1812
								$macrules .= "delete {$ruleno}\n";
1813
							}
1814
							if ($pipeno) {
1815
								captiveportal_free_dn_ruleno($pipeno);
1816
								$macrules .= "pipe delete {$pipeno}\n";
1817
								++$pipeno;
1818
								$macrules .= "pipe delete {$pipeno}\n";
1819
							}
1820
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1821
							$mac['action'] = 'pass';
1822
							$mac['mac'] = $clientmac;
1823
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1824
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1825
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1826
							mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1827
							$writecfg = true;
1828
							$sessionid = true;
1829
							break;
1830
						}
1831
					}
1832
				} else {
1833
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1834
						$clientmac, $clientip, $username, $password);
1835
					unlock($cpdblck);
1836
					return;
1837
				}
1838
			}
1839
		}
1840
	}
1841

    
1842
	/* read in client database */
1843
	$query = "WHERE ip = '{$clientip}'";
1844
	$tmpusername = strtolower($username);
1845
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins']))
1846
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
1847
	$cpdb = captiveportal_read_db($query);
1848

    
1849
	/* Snapshot the timestamp */
1850
	$allow_time = time();
1851
	$radiusservers = captiveportal_get_radius_servers();
1852
	$unsetindexes = array();
1853
	if (is_null($radiusctx))
1854
		$radiusctx = 'first';
1855

    
1856
	foreach ($cpdb as $cpentry) {
1857
		if (empty($cpentry[11])) {
1858
			$cpentry[11] = 'first';
1859
		}
1860
		/* on the same ip */
1861
		if ($cpentry[2] == $clientip) {
1862
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac)
1863
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1864
			else
1865
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
1866
			$sessionid = $cpentry[5];
1867
			break;
1868
		}
1869
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1870
			// user logged in with an active voucher. Check for how long and calculate 
1871
			// how much time we can give him (voucher credit - used time)
1872
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1873
			if ($remaining_time < 0)    // just in case. 
1874
				$remaining_time = 0;
1875

    
1876
			/* This user was already logged in so we disconnect the old one */
1877
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[11]],13);
1878
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1879
			$unsetindexes[] = $cpentry[5];
1880
			break;
1881
		}
1882
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1883
			/* on the same username */
1884
			if (strcasecmp($cpentry[4], $username) == 0) {
1885
				/* This user was already logged in so we disconnect the old one */
1886
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[11]],13);
1887
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1888
				$unsetindexes[] = $cpentry[5];
1889
				break;
1890
			}
1891
		}
1892
	}
1893
	unset($cpdb);
1894

    
1895
	if (!empty($unsetindexes))
1896
		captiveportal_remove_entries($unsetindexes);
1897

    
1898
	if ($attributes['voucher'] && $remaining_time <= 0)
1899
		return 0;       // voucher already used and no time left
1900

    
1901
	if (!isset($sessionid)) {
1902
		/* generate unique session ID */
1903
		$tod = gettimeofday();
1904
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1905

    
1906
		if ($passthrumac) {
1907
			$mac = array();
1908
			$mac['action'] = 'pass';
1909
			$mac['mac'] = $clientmac;
1910
			$mac['ip'] = $clientip; /* Used only for logging */
1911
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
1912
				$mac['username'] = $username;
1913
				if ($attributes['voucher'])
1914
					$mac['logintype'] = "voucher";
1915
			}
1916
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1917
			if (!empty($bw_up))
1918
				$mac['bw_up'] = $bw_up;
1919
			if (!empty($bw_down))
1920
				$mac['bw_down'] = $bw_down;
1921
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
1922
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
1923
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1924
			unlock($cpdblck);
1925
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1926
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1927
			mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1928
			$writecfg = true;
1929
		} else {
1930
			/* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
1931
			if (is_null($pipeno))
1932
				$pipeno = captiveportal_get_next_dn_ruleno();
1933

    
1934
			/* if the pool is empty, return appropriate message and exit */
1935
			if (is_null($pipeno)) {
1936
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1937
				log_error("Zone: {$cpzone} - WARNING!  Captive portal has reached maximum login capacity");
1938
				unlock($cpdblck);
1939
				return;
1940
			}
1941

    
1942
			$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1943
			$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1944
			$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1945
			$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1946

    
1947
			$bw_up_pipeno = $pipeno;
1948
			$bw_down_pipeno = $pipeno + 1;
1949
			//$bw_up /= 1000; // Scale to Kbit/s
1950
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1951
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1952

    
1953
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
1954
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1955
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
1956
			else
1957
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
1958

    
1959
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1960
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
1961
			else
1962
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
1963

    
1964
			if ($attributes['voucher'])
1965
				$attributes['session_timeout'] = $remaining_time;
1966
			
1967
			/* handle empty attributes */
1968
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
1969
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
1970
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
1971
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
1972

    
1973
			/* escape username */
1974
			$safe_username = SQLite3::escapeString($username);
1975

    
1976
			/* encode password in Base64 just in case it contains commas */
1977
			$bpassword = base64_encode($password);
1978
			$insertquery  = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, radiusctx) ";
1979
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
1980
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, '{$radiusctx}')";
1981

    
1982
			/* store information to database */
1983
			captiveportal_write_db($insertquery);
1984
			unlock($cpdblck);
1985
			unset($insertquery, $bpassword);
1986

    
1987
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
1988
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
1989
				if ($acct_val == 1)
1990
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1991
			}
1992
		}
1993
	} else {
1994
		/* NOTE: #3062-11 If the pipeno has been allocated free it to not DoS the CP and maintain proper operation as in radius() case */
1995
		if (!is_null($pipeno))
1996
			captiveportal_free_dn_ruleno($pipeno);
1997

    
1998
		unlock($cpdblck);
1999
	}
2000

    
2001
	if ($writecfg == true)
2002
		write_config();
2003

    
2004
	/* redirect user to desired destination */
2005
	if (!empty($attributes['url_redirection']))
2006
		$my_redirurl = $attributes['url_redirection'];
2007
	else if (!empty($redirurl))
2008
		$my_redirurl = $redirurl;
2009
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
2010
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
2011

    
2012
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
2013
		$ourhostname = portal_hostname_from_client_ip($clientip);
2014
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
2015
		$logouturl = "{$protocol}{$ourhostname}/";
2016

    
2017
		if (isset($attributes['reply_message']))
2018
			$message = $attributes['reply_message'];
2019
		else
2020
			$message = 0;
2021

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

    
2024
	} else {
2025
		portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
2026
	}
2027

    
2028
	return $sessionid;
2029
}
2030

    
2031

    
2032
/*
2033
 * Used for when pass-through credits are enabled.
2034
 * Returns true when there was at least one free login to deduct for the MAC.
2035
 * Expired entries are removed as they are seen.
2036
 * Active entries are updated according to the configuration.
2037
 */
2038
function portal_consume_passthrough_credit($clientmac) {
2039
	global $config, $cpzone;
2040

    
2041
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
2042
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
2043
	else
2044
		return false;
2045

    
2046
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
2047
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2048
	else
2049
		return false;
2050

    
2051
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
2052
		return false;
2053

    
2054
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2055

    
2056
	/*
2057
	 * Read database of used MACs.  Lines are a comma-separated list
2058
	 * of the time, MAC, then the count of pass-through credits remaining.
2059
	 */
2060
	$usedmacs = captiveportal_read_usedmacs_db();
2061

    
2062
	$currenttime = time();
2063
	$found = false;
2064
	foreach ($usedmacs as $key => $usedmac) {
2065
		$usedmac = explode(",", $usedmac);
2066

    
2067
		if ($usedmac[1] == $clientmac) {
2068
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2069
				if ($usedmac[2] < 1) {
2070
					if ($updatetimeouts) {
2071
						$usedmac[0] = $currenttime;
2072
						unset($usedmacs[$key]);
2073
						$usedmacs[] = implode(",", $usedmac);
2074
						captiveportal_write_usedmacs_db($usedmacs);
2075
					}
2076

    
2077
					return false;
2078
				} else {
2079
					$usedmac[2] -= 1;
2080
					$usedmacs[$key] = implode(",", $usedmac);
2081
				}
2082

    
2083
				$found = true;
2084
			} else
2085
				unset($usedmacs[$key]);
2086

    
2087
			break;
2088
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2089
				unset($usedmacs[$key]);
2090
	}
2091

    
2092
	if (!$found) {
2093
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2094
		$usedmacs[] = implode(",", $usedmac);
2095
	}
2096

    
2097
	captiveportal_write_usedmacs_db($usedmacs);
2098
	return true;
2099
}
2100

    
2101
function captiveportal_read_usedmacs_db() {
2102
	global $g, $cpzone;
2103

    
2104
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2105
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2106
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2107
		if (!$usedmacs)
2108
			$usedmacs = array();
2109
	} else
2110
		$usedmacs = array();
2111

    
2112
	unlock($cpumaclck);
2113
	return $usedmacs;
2114
}
2115

    
2116
function captiveportal_write_usedmacs_db($usedmacs) {
2117
	global $g, $cpzone;
2118

    
2119
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2120
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2121
	unlock($cpumaclck);
2122
}
2123

    
2124
function captiveportal_blocked_mac($mac) {
2125
	global $config, $g, $cpzone;
2126

    
2127
	if (empty($mac) || !is_macaddr($mac))
2128
		return false;
2129

    
2130
	if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
2131
		return false;
2132

    
2133
	foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac)
2134
		if (($passthrumac['action'] == 'block') &&
2135
		    ($passthrumac['mac'] == strtolower($mac)))
2136
			return true;
2137

    
2138
	return false;
2139

    
2140
}
2141

    
2142
function captiveportal_send_server_accounting($off = false) {
2143
	global $cpzone, $config;
2144

    
2145
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
2146
		return;
2147
	}
2148
	if ($off) {
2149
		$racct = new Auth_RADIUS_Acct_Off;
2150
	} else {
2151
		$racct = new Auth_RADIUS_Acct_On;
2152
	}
2153
	$radiusservers = captiveportal_get_radius_servers();
2154
	if (empty($radiusservers)) {
2155
		return;
2156
	}
2157
	foreach ($radiusservers['first'] as $radsrv) {
2158
		// Add a new server to our instance
2159
		$racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
2160
	}
2161
	if (PEAR::isError($racct->start())) {
2162
		$retvalue['acct_val'] = 1;
2163
		$retvalue['error'] = $racct->getMessage();
2164

    
2165
		// If we encounter an error immediately stop this function and go back
2166
		$racct->close();
2167
		return $retvalue;
2168
	}
2169
	// Send request
2170
	$result = $racct->send();
2171
	// Evaluation of the response
2172
	// 5 -> Accounting-Response
2173
	// See RFC2866 for this.
2174
	if (PEAR::isError($result)) {
2175
		$retvalue['acct_val'] = 1;
2176
		$retvalue['error'] = $result->getMessage();
2177
	} else if ($result === true) {
2178
		$retvalue['acct_val'] = 5 ;
2179
	} else {
2180
		$retvalue['acct_val'] = 1 ;
2181
	}
2182

    
2183
	$racct->close();
2184
	return $retvalue;
2185
}
2186
?>
(8-8/67)