Projet

Général

Profil

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

univnautes / etc / inc / captiveportal.inc @ 391cad9f

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
	if (!empty($cpcfg['listenporthttp']))
431
		$listenporthttp = $cpcfg['listenporthttp'];
432
	else
433
		$listenporthttp = 8000 + $cpcfg['zoneid'];
434
	system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal.conf",
435
		"", "", "", "lighty-{$cpzone}-CaptivePortal.pid", $listenporthttp, "/usr/local/captiveportal",
436
		"", "", $cpzone);
437

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

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

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

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

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

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

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

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

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

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

    
507
EOD;
508

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

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

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

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

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

    
569
EOD;
570

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

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

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

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

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

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

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

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

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

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

    
621
	$radiussrvs = captiveportal_get_radius_servers();
622

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

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

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

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

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

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

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

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

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

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

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

    
761
	captiveportal_prune_old_automac();
762

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

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

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

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

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

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

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

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

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

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

    
886
}
887

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

    
892
	$radiusservers = captiveportal_get_radius_servers();
893

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

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

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

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

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

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

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

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

    
952
	$ruleno = captiveportal_get_next_ipfw_ruleno();
953

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

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

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

    
967
	return $rules;
968
}
969

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

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

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

    
979
		captiveportal_free_ipfw_ruleno($ruleno);
980

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

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

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

    
993
	return $rules;
994
}
995

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

    
999
	$rules = "";
1000

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

    
1005
	return $rules;
1006
}
1007

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

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

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

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

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

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

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

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

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

    
1098
	return $rules;
1099
}
1100

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

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

    
1110
	return $rules;
1111
}
1112

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

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

    
1123
	return 0;
1124
}
1125

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1248
	$pipeno = captiveportal_get_next_dn_ruleno();
1249

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

    
1258
	$radiusservers = captiveportal_get_radius_servers();
1259

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

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

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

    
1283
	return $auth_list;
1284
}
1285

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

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

    
1300
	return $DB;
1301
}
1302

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

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

    
1315
	return $cpdb;
1316
}
1317

    
1318
function captiveportal_remove_entries($remove) {
1319

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

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

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

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

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

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

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

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

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

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

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

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

    
1432
	return $ruleno;
1433
}
1434

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

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

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

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

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

    
1470
	return $ruleno;
1471
}
1472

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

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

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

    
1516
	return $ruleno;
1517
}
1518

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

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

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

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

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

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

    
1558
	return $ruleno;
1559
}
1560

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

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

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

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

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

    
1604
	return $volume;
1605
}
1606

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

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

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

    
1630
	return $nasIp;
1631
}
1632

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

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

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

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

    
1669
	return false;
1670
}
1671

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

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

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

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

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

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

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

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

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

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

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

    
1743
	echo $htmltext;
1744
}
1745

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

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

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

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

    
1760
	return FALSE;
1761
}
1762

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

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

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

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

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

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

    
1787
	unset($sessionid);
1788

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1997
		unlock($cpdblck);
1998
	}
1999

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

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

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

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

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

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

    
2027
	return $sessionid;
2028
}
2029

    
2030

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2137
	return false;
2138

    
2139
}
2140

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

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

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

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