Projet

Général

Profil

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

univnautes / etc / inc / captiveportal.inc @ 4ec6b54d

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
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
	/* XXX: This are not used in pfSense, if needed can be tuned 
168
	if($config['system']['maximumstates'] <> "" && is_numeric($config['system']['maximumstates'])) {
169
			mwexec("sysctl net.inet.ip.fw.dyn_max={$config['system']['maximumstates']}");
170
	} else {
171
			mwexec("sysctl net.inet.ip.fw.dyn_max=10000");
172
	}
173
	*/
174
}
175

    
176
function captiveportal_configure() {
177
	global $config, $cpzone;
178

    
179
	if (is_array($config['captiveportal'])) {
180
		foreach ($config['captiveportal'] as $cpkey => $cp) {
181
			$cpzone = $cpkey;
182
			captiveportal_configure_zone($cp);
183
		}
184
	} else
185
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
186
}
187

    
188
function captiveportal_configure_zone($cpcfg) {
189
	global $config, $g, $cpzone;
190

    
191
	$captiveportallck = lock("captiveportal{$cpzone}", LOCK_EX);
192
	
193
	if (isset($cpcfg['enable'])) {
194

    
195
		if ($g['booting']) {
196
			echo "Starting captive portal({$cpcfg['zone']})... ";
197

    
198
			/* remove old information */
199
			unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
200
		} else
201
			captiveportal_syslog("Reconfiguring captive portal({$cpcfg['zone']}).");
202

    
203
		/* init ipfw rules */
204
		captiveportal_init_rules(true);
205

    
206
		/* kill any running minicron */
207
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
208

    
209
		/* initialize minicron interval value */
210
		$croninterval = $cpcfg['croninterval'] ? $cpcfg['croninterval'] : 60;
211

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

    
216
		/* write portal page */
217
		if (is_array($cpcfg['page']) && $cpcfg['page']['htmltext'])
218
			$htmltext = base64_decode($cpcfg['page']['htmltext']);
219
		else {
220
			/* example/template page */
221
			$htmltext = get_default_captive_portal_html();
222
		}
223

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

    
244
		/* write error page */
245
		if (is_array($cpcfg['page']) && $cpcfg['page']['errtext'])
246
			$errtext = base64_decode($cpcfg['page']['errtext']);
247
		else {
248
			/* example page  */
249
			$errtext = get_default_captive_portal_html();
250
		}
251

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

    
272
		/* write logout page */
273
		if (is_array($cpcfg['page']) && $cpcfg['page']['logouttext'])
274
			$logouttext = base64_decode($cpcfg['page']['logouttext']);
275
		else {
276
			/* example page */
277
			$logouttext = <<<EOD
278
<HTML>
279
<HEAD><TITLE>Redirecting...</TITLE></HEAD>
280
<BODY>
281
<SPAN STYLE="font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">
282
<B>Redirecting to <A HREF="<?=\$my_redirurl;?>"><?=\$my_redirurl;?></A>...</B>
283
</SPAN>
284
<SCRIPT LANGUAGE="JavaScript">
285
<!--
286
LogoutWin = window.open('', 'Logout', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=256,height=64');
287
if (LogoutWin) {
288
	LogoutWin.document.write('<HTML>');
289
	LogoutWin.document.write('<HEAD><TITLE>Logout</TITLE></HEAD>') ;
290
	LogoutWin.document.write('<BODY BGCOLOR="#435370">');
291
	LogoutWin.document.write('<DIV ALIGN="center" STYLE="color: #ffffff; font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">') ;
292
	LogoutWin.document.write('<B>Click the button below to disconnect</B><P>');
293
	LogoutWin.document.write('<FORM METHOD="POST" ACTION="<?=\$logouturl;?>">');
294
	LogoutWin.document.write('<INPUT NAME="logout_id" TYPE="hidden" VALUE="<?=\$sessionid;?>">');
295
	LogoutWin.document.write('<INPUT NAME="zone" TYPE="hidden" VALUE="<?=\$cpzone;?>">');
296
	LogoutWin.document.write('<INPUT NAME="logout" TYPE="submit" VALUE="Logout">');
297
	LogoutWin.document.write('</FORM>');
298
	LogoutWin.document.write('</DIV></BODY>');
299
	LogoutWin.document.write('</HTML>');
300
	LogoutWin.document.close();
301
}
302

    
303
document.location.href="<?=\$my_redirurl;?>";
304
-->
305
</SCRIPT>
306
</BODY>
307
</HTML>
308

    
309
EOD;
310
		}
311

    
312
		$fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html", "w");
313
		if ($fd) {
314
			fwrite($fd, $logouttext);
315
			fclose($fd);
316
		}
317
		unset($logouttext);
318

    
319
		/* write elements */
320
		captiveportal_write_elements();
321

    
322
		/* kill any running mini_httpd */
323
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
324
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
325

    
326
		/* start up the webserving daemon */
327
		captiveportal_init_webgui_zone($cpcfg);
328

    
329
		/* Kill any existing prunecaptiveportal processes */
330
		if (file_exists("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid"))
331
			killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
332

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

    
337
		/* generate radius server database */
338
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
339
		captiveportal_init_radius_servers();
340

    
341
		if ($g['booting']) {
342
			/* send Accounting-On to server */
343
			captiveportal_send_server_accounting();
344
			echo "done\n";
345
		}
346

    
347
	} else {
348
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
349
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
350
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
351
		@unlink("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
352
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
353
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
354

    
355
		captiveportal_radius_stop_all();
356

    
357
		/* send Accounting-Off to server */
358
		if (!$g['booting']) {
359
			captiveportal_send_server_accounting(true);
360
		}
361

    
362
		/* remove old information */
363
		unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
364
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
365
		unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
366
		/* Release allocated pipes for this zone */
367
		captiveportal_free_dnrules();
368

    
369
		mwexec("/usr/local/sbin/ipfw_context -d {$cpzone}", true);
370

    
371
		if (empty($config['captiveportal']))
372
			mwexec("/sbin/sysctl net.link.ether.ipfw=0");
373
		else {
374
			/* Deactivate ipfw(4) if not needed */
375
			$cpactive = false;
376
			if (is_array($config['captiveportal'])) {
377
				foreach ($config['captiveportal'] as $cpkey => $cp) {
378
					if (isset($cp['enable'])) {
379
						$cpactive = true;
380
						break;
381
					}
382
				}
383
			}
384
			if ($cpactive === false)
385
				mwexec("/sbin/sysctl net.link.ether.ipfw=0");
386
				
387
		}
388
	}
389

    
390
	unlock($captiveportallck);
391
	
392
	return 0;
393
}
394

    
395
function captiveportal_init_webgui() {
396
	global $config, $cpzone;
397

    
398
	if (is_array($config['captiveportal'])) {
399
		foreach ($config['captiveportal'] as $cpkey => $cp) {
400
			$cpzone = $cpkey;
401
			captiveportal_init_webgui_zone($cp);
402
		}
403
	}
404
}
405

    
406
function captiveportal_init_webgui_zonename($zone) {
407
	global $config, $cpzone;
408
	
409
	if (isset($config['captiveportal'][$zone])) {
410
		$cpzone = $zone;
411
		captiveportal_init_webgui_zone($config['captiveportal'][$zone]);
412
	}
413
}
414

    
415
function captiveportal_init_webgui_zone($cpcfg) {
416
	global $g, $config, $cpzone;
417

    
418
	if (!isset($cpcfg['enable']))
419
		return;
420

    
421
	if (isset($cpcfg['httpslogin'])) {
422
		$cert = lookup_cert($cpcfg['certref']);
423
		$crt = base64_decode($cert['crt']);
424
		$key = base64_decode($cert['prv']);
425
		$ca = ca_chain($cert);
426

    
427
		/* generate lighttpd configuration */
428
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
429
		system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf",
430
			$crt, $key, $ca, "lighty-{$cpzone}-CaptivePortal-SSL.pid", $listenporthttps, "/usr/local/captiveportal",
431
			"cert-{$cpzone}-portal.pem", "ca-{$cpzone}-portal.pem", $cpzone);
432
	}
433

    
434
	/* generate lighttpd configuration */
435
	$listenporthttp = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : $cpcfg['zoneid'];
436
	system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal.conf",
437
		"", "", "", "lighty-{$cpzone}-CaptivePortal.pid", $listenporthttp, "/usr/local/captiveportal",
438
		"", "", $cpzone);
439

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

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

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

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

    
458
	captiveportal_load_modules();
459
	mwexec("/usr/local/sbin/ipfw_context -a {$cpzone}", true);
460

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

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

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

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

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

    
509
EOD;
510

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

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

    
547
	$listenporthttp =
548
		$config['captiveportal'][$cpzone]['listenporthttp'] ?
549
		$config['captiveportal'][$cpzone]['listenporthttp'] :
550
		$config['captiveportal'][$cpzone]['zoneid'];
551

    
552
	if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
553
		$listenporthttps = $listenporthttp + 1;
554
		$cprules .= "add 65531 fwd 127.0.0.1,{$listenporthttps} tcp from any to any dst-port 443 in\n";
555
	}
556
	
557
	$cprules .= <<<EOD
558

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

    
566
EOD;
567

    
568
	/* generate passthru mac database */
569
	$cprules .= captiveportal_passthrumac_configure(true);
570
	$cprules .= "\n";
571

    
572
	/* allowed ipfw rules to make allowed ip work */
573
	$cprules .= captiveportal_allowedip_configure();
574

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

    
585
	if ($reinit == false)
586
		unlock($captiveportallck);
587
}
588

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

    
598
	if (empty($cpzone))
599
		return;
600

    
601
	$cpcfg = $config['captiveportal'][$cpzone];
602
	$vcpcfg = $config['voucher'][$cpzone];
603

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

    
610
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout']))
611
		$idletimeout = $cpcfg['idletimeout'] * 60;
612

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

    
618
	$radiussrvs = captiveportal_get_radius_servers();
619

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

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

    
634
		$timedout = false;
635
		$term_cause = 1;
636
		if (empty($cpentry[11]))
637
			$cpentry[11] = 'first';
638
		$radiusservers = $radiussrvs[$cpentry[11]];
639

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

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

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

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

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

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

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

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

    
758
	captiveportal_prune_old_automac();
759

    
760
	if ($voucher_needs_sync == true)
761
		/* Triger a sync of the vouchers on config */
762
		send_event("service sync vouchers");
763

    
764
	/* write database */
765
	if (!empty($unsetindexes))
766
		captiveportal_remove_entries($unsetindexes);
767
}
768

    
769
function captiveportal_prune_old_automac() {
770
	global $g, $config, $cpzone;
771

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

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

    
837
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
838

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

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

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

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

    
883
}
884

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

    
889
	$radiusservers = captiveportal_get_radius_servers();
890

    
891
	/* read database */
892
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
893

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

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

    
908
/* send RADIUS acct stop for all current clients */
909
function captiveportal_radius_stop_all() {
910
	global $config, $cpzone;
911

    
912
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable']))
913
		return;
914

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

    
935
function captiveportal_passthrumac_configure_entry($macent) {
936
	global $cpzone, $config;
937

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

    
949
	$ruleno = captiveportal_get_next_ipfw_ruleno();
950
	$pipeno = captiveportal_get_next_dn_ruleno();
951

    
952
	$rules = "";
953
	$pipeup = $pipeno;
954
	$_gb = @pfSense_pipe_action("pipe {$pipeup} config bw {$bwUp}Kbit/s queue 100 buckets 16");
955
	$pipedown = $pipeno + 1;
956
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
957
	$rules .= "add {$ruleno} pipe {$pipeup} ip from any to any MAC any {$macent['mac']}\n";
958
	$ruleno++;
959
	$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC {$macent['mac']} any\n";
960

    
961
	return $rules;
962
}
963

    
964
function captiveportal_passthrumac_configure($lock = false) {
965
	global $config, $g, $cpzone;
966

    
967
	$rules = "";
968

    
969
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
970
		$macdb = array();
971
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
972
			$rules .= captiveportal_passthrumac_configure_entry($macent);
973
			$macdb[$macent['mac']][$cpzone]['active']  = true;
974

    
975
		}
976
	}
977

    
978
	return $rules;
979
}
980

    
981
function captiveportal_passthrumac_findbyname($username) {
982
	global $config, $cpzone;
983

    
984
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
985
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
986
			if ($macent['username'] == $username)
987
				return $macent;
988
		}
989
	}
990
	return NULL;
991
}
992

    
993
/* 
994
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
995
 */
996
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
997
	global $g;
998

    
999
	/*  Instead of copying this entire function for something
1000
	 *  easy such as hostname vs ip address add this check
1001
	 */
1002
	if ($ishostname === true) {
1003
		if (!$g['booting']) {
1004
			$ipaddress = gethostbyname($ipent['hostname']);
1005
			if (!is_ipaddr($ipaddress)) 
1006
				return;
1007
		} else
1008
			$ipaddress = "";
1009
	} else
1010
		$ipaddress = $ipent['ip'];
1011

    
1012
	$rules = "";
1013
	$cp_filterdns_conf = "";
1014
	$enBwup = 0;
1015
	if (!empty($ipent['bw_up']))
1016
		$enBwup = intval($ipent['bw_up']);
1017
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultup']))
1018
		$enBwup = $config['captiveportal'][$cpzone]['bwdefaultup'];
1019
	$enBwdown = 0;
1020
	if (!empty($ipent['bw_down']))
1021
		$enBwdown = intval($ipent['bw_down']);
1022
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultdn']))
1023
		$enBwdown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
1024

    
1025
	$pipeno = captiveportal_get_next_dn_ruleno();
1026
	$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$enBwup}Kbit/s queue 100 buckets 16");
1027
	$pipedown = $pipeno + 1;
1028
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16");
1029
	if ($ishostname === true) {
1030
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
1031
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
1032
		if (!is_ipaddr($ipaddress))
1033
			return array("", $cp_filterdns_conf);
1034
	}
1035
	$subnet = "";
1036
	if (!empty($ipent['sn']))
1037
		$subnet = "/{$ipent['sn']}";
1038
	$rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
1039
	$rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
1040

    
1041
	if ($ishostname === true)
1042
		return array($rules, $cp_filterdns_conf);
1043
	else
1044
		return $rules;
1045
}
1046

    
1047
function captiveportal_allowedhostname_configure() {
1048
	global $config, $g, $cpzone;
1049

    
1050
	$rules = "";
1051
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1052
		$rules = "\n# captiveportal_allowedhostname_configure()\n";
1053
		$cp_filterdns_conf = "";
1054
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) {
1055
			$tmprules = captiveportal_allowedip_configure_entry($hostnameent, true);
1056
			$rules .= $tmprules[0];
1057
			$cp_filterdns_conf .= $tmprules[1];
1058
		}
1059
		$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1060
		@file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1061
		unset($cp_filterdns_conf);
1062
		if (isvalidpid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid"))
1063
			sigkillbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid", "HUP");
1064
		else
1065
			mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzone} -d 1");
1066
	} else {
1067
		killbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1068
		@unlink("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1069
	}
1070

    
1071
	return $rules;
1072
}
1073

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

    
1077
	$rules = "";
1078
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1079
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1080
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1081
	}
1082

    
1083
	return $rules;
1084
}
1085

    
1086
/* get last activity timestamp given client IP address */
1087
function captiveportal_get_last_activity($ip) {
1088
	global $cpzone;
1089

    
1090
	$ipfwoutput = pfSense_ipfw_getTablestats($cpzone, 1, $ip);
1091
	/* Reading only from one of the tables is enough of approximation. */
1092
	if (is_array($ipfwoutput)) {
1093
		return $ipfwoutput['timestamp'];
1094
	}
1095

    
1096
	return 0;
1097
}
1098

    
1099
function captiveportal_init_radius_servers() {
1100
	global $config, $g, $cpzone;
1101

    
1102
	/* generate radius server database */
1103
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1104
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1105
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1106
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1107
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1108
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1109

    
1110
		if ($config['captiveportal'][$cpzone]['radiusport'])
1111
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1112
		else
1113
			$radiusport = 1812;
1114
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1115
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1116
		else
1117
			$radiusacctport = 1813;
1118
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1119
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1120
		else
1121
			$radiusport2 = 1812;
1122
		if ($config['captiveportal'][$cpzone]['radiusport3'])
1123
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1124
		else
1125
			$radiusport3 = 1812;
1126
		if ($config['captiveportal'][$cpzone]['radiusport4'])
1127
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1128
		else
1129
			$radiusport4 = 1812;
1130

    
1131
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1132
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1133
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1134
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1135

    
1136
		$cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX);
1137
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
1138
		if (!$fd) {
1139
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1140
			unlock($cprdsrvlck);
1141
			return 1;
1142
		}
1143
		if (isset($radiusip))
1144
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
1145
		if (isset($radiusip2))
1146
			fwrite($fd,"\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
1147
		if (isset($radiusip3))
1148
			fwrite($fd,"\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
1149
		if (isset($radiusip4))
1150
			fwrite($fd,"\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
1151
		
1152

    
1153
		fclose($fd);
1154
		unlock($cprdsrvlck);
1155
	}
1156
}
1157

    
1158
/* read RADIUS servers into array */
1159
function captiveportal_get_radius_servers() {
1160
	global $g, $cpzone;
1161

    
1162
	$cprdsrvlck = lock("captiveportalradius{$cpzone}");
1163
	if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
1164
		$radiusservers = array();
1165
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", 
1166
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1167
		if ($cpradiusdb) {
1168
			foreach($cpradiusdb as $cpradiusentry) {
1169
				$line = trim($cpradiusentry);
1170
				if ($line) {
1171
					$radsrv = array();
1172
						list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key'], $context) = explode(",",$line);
1173
				}
1174
				if (empty($context)) {
1175
					if (!is_array($radiusservers['first']))
1176
						$radiusservers['first'] = array();
1177
					$radiusservers['first'] = $radsrv;
1178
				} else {
1179
					if (!is_array($radiusservers[$context]))
1180
						$radiusservers[$context] = array();
1181
					$radiusservers[$context][] = $radsrv;
1182
				}
1183
			}
1184
		}
1185
		unlock($cprdsrvlck);
1186
		return $radiusservers;
1187
	}
1188

    
1189
	unlock($cprdsrvlck);
1190
	return false;
1191
}
1192

    
1193
/* log successful captive portal authentication to syslog */
1194
/* part of this code from php.net */
1195
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1196
	// Log it
1197
	if (!$message)
1198
		$message = "$status: $user, $mac, $ip";
1199
	else {
1200
		$message = trim($message);
1201
		$message = "$status: $user, $mac, $ip, $message";
1202
	}
1203
	captiveportal_syslog($message);
1204
}
1205

    
1206
/* log simple messages to syslog */
1207
function captiveportal_syslog($message) {
1208
	$message = trim($message);
1209
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1210
	// Log it
1211
	syslog(LOG_INFO, $message);
1212
	closelog();
1213
}
1214

    
1215
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1216
	global $g, $config;
1217

    
1218
	$pipeno = captiveportal_get_next_dn_ruleno();
1219

    
1220
	/* If the pool is empty, return appropriate message and fail authentication */
1221
	if (empty($pipeno)) {
1222
		$auth_list = array();
1223
		$auth_list['auth_val'] = 1;
1224
		$auth_list['error'] = "System reached maximum login capacity";
1225
		return $auth_list;
1226
	}
1227

    
1228
	$radiusservers = captiveportal_get_radius_servers();
1229

    
1230
	if (is_null($radiusctx))
1231
		$radiusctx = 'first';
1232

    
1233
	$auth_list = RADIUS_AUTHENTICATION($username,
1234
		$password,
1235
		$radiusservers[$radiusctx],
1236
		$clientip,
1237
		$clientmac,
1238
		$pipeno);
1239

    
1240
	if ($auth_list['auth_val'] == 2) {
1241
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1242
		$sessionid = portal_allow($clientip,
1243
			$clientmac,
1244
			$username,
1245
			$password,
1246
			$auth_list,
1247
			$pipeno,
1248
			$radiusctx);
1249
	} else {
1250
	         captiveportal_free_dn_ruleno($pipeno);
1251
	       }
1252

    
1253
	return $auth_list;
1254
}
1255

    
1256
function captiveportal_opendb() {
1257
	global $g, $cpzone;
1258

    
1259
	if (file_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db"))
1260
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1261
	else {
1262
		$errormsg = "";
1263
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1264
		if (@sqlite_exec($DB, "CREATE TABLE captiveportal (allow_time INTEGER, pipeno INTEGER, ip TEXT, mac TEXT, username TEXT, sessionid TEXT, bpassword TEXT, session_timeout INTEGER, idle_timeout INTEGER, session_terminate_time INTEGER, interim_interval INTEGER, radiusctx TEXT) ", $errormsg)) {
1265
			@sqlite_exec($DB, "CREATE UNIQUE INDEX idx_active ON captiveportal (sessionid, username)");
1266
			@sqlite_exec($DB, "CREATE INDEX user ON captiveportal (username)");
1267
			@sqlite_exec($DB, "CREATE INDEX ip ON captiveportal (ip)");
1268
			@sqlite_exec($DB, "CREATE INDEX starttime ON captiveportal (allow_time)");
1269
			@sqlite_exec($DB, "CREATE INDEX serviceid ON captiveportal (serviceid)");
1270
		} else
1271
			captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$errormsg}");
1272
	}
1273

    
1274
	return $DB;
1275
}
1276

    
1277
/* read captive portal DB into array */
1278
function captiveportal_read_db($query = "") {
1279

    
1280
	$DB = captiveportal_opendb();
1281
	if ($DB) {
1282
		sqlite_exec($DB, "BEGIN");
1283
		if (!empty($query))
1284
			$cpdb = @sqlite_array_query($DB, "SELECT * FROM captiveportal {$query}", SQLITE_NUM);
1285
		else {
1286
			$response = @sqlite_unbuffered_query($DB, "SELECT * FROM captiveportal", SQLITE_NUM);
1287
			$cpdb = @sqlite_fetch_all($response, SQLITE_NUM);
1288
		}
1289
		sqlite_exec($DB, "END");
1290
		@sqlite_close($DB);
1291
	}
1292
	if (!$cpdb)
1293
		$cpdb = array();
1294

    
1295
	return $cpdb;
1296
}
1297

    
1298
function captiveportal_remove_entries($remove) {
1299

    
1300
	if (!is_array($remove) || empty($remove))
1301
		return;
1302

    
1303
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1304
	foreach($remove as $idx => $unindex) {
1305
		$query .= "'{$unindex}'";
1306
		if ($idx < (count($remove) - 1))
1307
			$query .= ",";
1308
	}
1309
	$query .= ")";
1310
	captiveportal_write_db($query);
1311
}
1312

    
1313
/* write captive portal DB */
1314
function captiveportal_write_db($queries) {
1315
	global $g;
1316

    
1317
	if (is_array($queries))
1318
		$query = implode(";", $queries);
1319
	else
1320
		$query = $queries;
1321

    
1322
	$DB = captiveportal_opendb();
1323
	if ($DB) {
1324
		$error_msg = "";
1325
		sqlite_exec($DB, "BEGIN TRANSACTION");
1326
		$result = @sqlite_exec($DB, $query, $error_msg);
1327
		if (!$result)
1328
			captiveportal_syslog("Trying to modify DB returned error: {$error_msg}");
1329
		else
1330
			sqlite_exec($DB, "END TRANSACTION");
1331
		@sqlite_close($DB);
1332
		return $result;
1333
	} else
1334
		return true;
1335
}
1336

    
1337
function captiveportal_write_elements() {
1338
	global $g, $config, $cpzone;
1339
	
1340
	$cpcfg = $config['captiveportal'][$cpzone];
1341

    
1342
	if (!is_dir($g['captiveportal_element_path']))
1343
		@mkdir($g['captiveportal_element_path']);
1344

    
1345
	if (is_array($cpcfg['element'])) {
1346
		conf_mount_rw();
1347
		foreach ($cpcfg['element'] as $data) {
1348
			if (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1349
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1350
				return 1;
1351
			}
1352
			if (!file_exists("{$g['captiveportal_path']}/{$data['name']}"))
1353
				@symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1354
		}
1355
		conf_mount_ro();
1356
	}
1357
	
1358
	return 0;
1359
}
1360

    
1361
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1362
	global $cpzone;
1363

    
1364
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1365
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1366
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1367
		$ridx = $rulenos_start;
1368
		while ($ridx < $rulenos_range_max) {
1369
			if ($rules[$ridx] == $cpzone) {
1370
				$rules[$ridx] = false;
1371
				$ridx++;
1372
				$rules[$ridx] = false;
1373
				$ridx++;
1374
			} else
1375
				$ridx += 2;
1376
		}
1377
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1378
		unset($rules);
1379
	}
1380
	unlock($cpruleslck);
1381
}
1382

    
1383
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1384
	global $config, $g, $cpzone;
1385

    
1386
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1387
	$ruleno = 0;
1388
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1389
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1390
		$ridx = $rulenos_start;
1391
		while ($ridx < $rulenos_range_max) {
1392
			if (empty($rules[$ridx])) {
1393
				$ruleno = $ridx;
1394
				$rules[$ridx] = $cpzone;
1395
				$ridx++;
1396
				$rules[$ridx] = $cpzone;
1397
				break;
1398
			} else {
1399
				$ridx += 2;
1400
			}
1401
		}
1402
	} else {
1403
		$rules = array_pad(array(), $rulenos_range_max, false);
1404
		$ruleno = $rulenos_start;
1405
		$rules[$rulenos_start] = $cpzone;
1406
		$rulenos_start++;
1407
		$rules[$rulenos_start] = $cpzone;
1408
	}
1409
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1410
	unlock($cpruleslck);
1411
	unset($rules);
1412

    
1413
	return $ruleno;
1414
}
1415

    
1416
function captiveportal_free_dn_ruleno($ruleno) {
1417
	global $config, $g;
1418

    
1419
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1420
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1421
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1422
		$rules[$ruleno] = false;
1423
		$ruleno++;
1424
		$rules[$ruleno] = false;
1425
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1426
		unset($rules);
1427
	}
1428
	unlock($cpruleslck);
1429
}
1430

    
1431
function captiveportal_get_dn_passthru_ruleno($value) {
1432
	global $config, $g, $cpzone;
1433

    
1434
	$cpcfg = $config['captiveportal'][$cpzone];
1435
	if(!isset($cpcfg['enable']))
1436
		return NULL;
1437

    
1438
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1439
	$ruleno = NULL;
1440
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1441
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1442
		unset($output);
1443
		$_gb = exec("/sbin/ipfw -x {$cpzone} show | /usr/bin/grep {$value} | /usr/bin/grep -v grep | /usr/bin/awk '{print $5}' | /usr/bin/head -n 1", $output);
1444
		$ruleno = intval($output[0]);
1445
		if (!$rules[$ruleno])
1446
			$ruleno = NULL;
1447
		unset($rules);
1448
	}
1449
	unlock($cpruleslck);
1450

    
1451
	return $ruleno;
1452
}
1453

    
1454
/*
1455
 * This function will calculate the lowest free firewall ruleno
1456
 * within the range specified based on the actual logged on users
1457
 *
1458
 */
1459
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1460
	global $config, $g, $cpzone;
1461

    
1462
	$cpcfg = $config['captiveportal'][$cpzone];
1463
	if(!isset($cpcfg['enable']))
1464
		return NULL;
1465

    
1466
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1467
	$ruleno = 0;
1468
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1469
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1470
		$ridx = $rulenos_start;
1471
		while ($ridx < $rulenos_range_max) {
1472
			if (empty($rules[$ridx])) {
1473
				$ruleno = $ridx;
1474
				$rules[$ridx] = $cpzone;
1475
				$ridx++;
1476
				$rules[$ridx] = $cpzone;
1477
				break;
1478
			} else {
1479
				/* 
1480
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1481
				 * and the out pipe ruleno + 1.
1482
				 */
1483
				$ridx += 2;
1484
			}
1485
		}
1486
	} else {
1487
		$rules = array_pad(array(), $rulenos_range_max, false);
1488
		$ruleno = $rulenos_start;
1489
		$rules[$rulenos_start] = $cpzone;
1490
		$rulenos_start++;
1491
		$rules[$rulenos_start] = $cpzone;
1492
	}
1493
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1494
	unlock($cpruleslck);
1495
	unset($rules);
1496

    
1497
	return $ruleno;
1498
}
1499

    
1500
function captiveportal_free_ipfw_ruleno($ruleno) {
1501
	global $config, $g, $cpzone;
1502

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

    
1507
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1508
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1509
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1510
		$rules[$ruleno] = false;
1511
		$ruleno++;
1512
		$rules[$ruleno] = false;
1513
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1514
	}
1515
	unlock($cpruleslck);
1516
	unset($rules);
1517
}
1518

    
1519
function captiveportal_get_ipfw_passthru_ruleno($value) {
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
	$ruleno = NULL;
1528
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1529
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1530
		unset($output);
1531
		$_gb = exec("/sbin/ipfw -x {$cpzone} show | /usr/bin/grep {$value} | /usr/bin/grep -v grep | /usr/bin/awk '{print $1}' | /usr/bin/head -n 1", $output);
1532
		$ruleno = intval($output[0]);
1533
		if (!$rules[$ruleno])
1534
			$ruleno = NULL;
1535
	}
1536
	unlock($cpruleslck);
1537
	unset($rules);
1538

    
1539
	return $ruleno;
1540
}
1541

    
1542
/**
1543
 * This function will calculate the traffic produced by a client
1544
 * based on its firewall rule
1545
 *
1546
 * Point of view: NAS
1547
 *
1548
 * Input means: from the client
1549
 * Output means: to the client
1550
 *
1551
 */
1552

    
1553
function getVolume($ip) {
1554
	global $config, $cpzone;
1555

    
1556
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1557
	$volume = array();
1558
	// Initialize vars properly, since we don't want NULL vars
1559
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1560

    
1561
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 1, $ip);
1562
	if (is_array($ipfw)) {
1563
		if ($reverse) {
1564
			$volume['output_pkts'] = $ipfw['packets'];
1565
			$volume['output_bytes'] = $ipfw['bytes'];
1566
		}
1567
		else {
1568
			$volume['input_pkts'] = $ipfw['packets'];
1569
			$volume['input_bytes'] = $ipfw['bytes'];
1570
		}
1571
	}
1572

    
1573
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 2, $ip);
1574
	if (is_array($ipfw)) {
1575
		if ($reverse) {
1576
			$volume['input_pkts'] = $ipfw['packets'];
1577
			$volume['input_bytes'] = $ipfw['bytes'];
1578
		}
1579
		else {
1580
			$volume['output_pkts'] = $ipfw['packets'];
1581
			$volume['output_bytes'] = $ipfw['bytes'];
1582
		}
1583
	}
1584

    
1585
	return $volume;
1586
}
1587

    
1588
/**
1589
 * Get the NAS-IP-Address based on the current wan address
1590
 *
1591
 * Use functions in interfaces.inc to find this out
1592
 *
1593
 */
1594

    
1595
function getNasIP()
1596
{
1597
	global $config, $cpzone;
1598

    
1599
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1600
			$nasIp = get_interface_ip();
1601
	} else {
1602
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1603
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1604
		else
1605
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1606
	}
1607
		
1608
	if(!is_ipaddr($nasIp))
1609
		$nasIp = "0.0.0.0";
1610

    
1611
	return $nasIp;
1612
}
1613

    
1614
function portal_ip_from_client_ip($cliip) {
1615
	global $config, $cpzone;
1616

    
1617
	$isipv6 = is_ipaddrv6($cliip);
1618
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1619
	foreach ($interfaces as $cpif) {
1620
		if ($isipv6) {
1621
			$ip = get_interface_ipv6($cpif);
1622
			$sn = get_interface_subnetv6($cpif);
1623
		} else {
1624
			$ip = get_interface_ip($cpif);
1625
			$sn = get_interface_subnet($cpif);
1626
		}
1627
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1628
			return $ip;
1629
	}
1630

    
1631
	$inet = ($isipv6) ? '-inet6' : '-inet';
1632
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1633
	$iface = trim($iface, "\n");
1634
	if (!empty($iface)) {
1635
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1636
		if (is_ipaddr($ip))
1637
			return $ip;
1638
	}
1639

    
1640
	// doesn't match up to any particular interface
1641
	// so let's set the portal IP to what PHP says 
1642
	// the server IP issuing the request is. 
1643
	// allows same behavior as 1.2.x where IP isn't 
1644
	// in the subnet of any CP interface (static routes, etc.)
1645
	// rather than forcing to DNS hostname resolution
1646
	$ip = $_SERVER['SERVER_ADDR'];
1647
	if (is_ipaddr($ip))
1648
		return $ip;
1649

    
1650
	return false;
1651
}
1652

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

    
1656
	$cpcfg = $config['captiveportal'][$cpzone];
1657

    
1658
	if (isset($cpcfg['httpslogin'])) {
1659
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
1660
		$ourhostname = $cpcfg['httpsname'];
1661
		
1662
		if ($listenporthttps != 443)
1663
			$ourhostname .= ":" . $listenporthttps;
1664
	} else {
1665
		$listenporthttp  = $cpcfg['listenporthttp']  ? $cpcfg['listenporthttp']  : $cpcfg['zoneid'];
1666
		$ifip = portal_ip_from_client_ip($cliip);
1667
		if (!$ifip)
1668
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1669
		else
1670
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1671
		
1672
		if ($listenporthttp != 80)
1673
			$ourhostname .= ":" . $listenporthttp;
1674
	}
1675
	
1676
	return $ourhostname;
1677
}
1678

    
1679
/* functions move from index.php */
1680

    
1681
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1682
	global $g, $config, $cpzone;
1683

    
1684
	/* Get captive portal layout */
1685
	if ($type == "redir") {
1686
		header("Location: {$redirurl}");
1687
		return;
1688
	} else if ($type == "login")
1689
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1690
	else
1691
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1692

    
1693
	$cpcfg = $config['captiveportal'][$cpzone];
1694

    
1695
	/* substitute the PORTAL_REDIRURL variable */
1696
	if ($cpcfg['preauthurl']) {
1697
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1698
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1699
	}
1700

    
1701
	/* substitute other variables */
1702
	$ourhostname = portal_hostname_from_client_ip($clientip);
1703
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1704
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1705
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1706

    
1707
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1708
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1709
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1710
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1711
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1712

    
1713
	// Special handling case for captive portal master page so that it can be ran 
1714
	// through the PHP interpreter using the include method above.  We convert the
1715
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1716
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1717
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1718
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1719
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1720
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1721
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1722
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1723

    
1724
	echo $htmltext;
1725
}
1726

    
1727
function portal_mac_radius($clientmac,$clientip) {
1728
	global $config, $cpzone;
1729

    
1730
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1731

    
1732
	/* authentication against the radius server */
1733
	$username = mac_format($clientmac);
1734
	$auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1735
	if ($auth_list['auth_val'] == 2)
1736
		return TRUE;
1737

    
1738
	if (!empty($auth_list['url_redirection']))
1739
		portal_reply_page($auth_list['url_redirection'], "redir");
1740

    
1741
	return FALSE;
1742
}
1743

    
1744
function captiveportal_reapply_attributes($cpentry, $attributes) {
1745
	global $config, $cpzone, $g;
1746

    
1747
	$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1748
	$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1749
	$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1750
	$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1751
	$bw_up_pipeno = $cpentry[1];
1752
	$bw_down_pipeno = $cpentry[1]+1;
1753

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

    
1758
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1759
}
1760

    
1761
function portal_allow($clientip,$clientmac,$username,$password = null, $attributes = null, $pipeno = null, $radiusctx = null)  {
1762
	global $redirurl, $g, $config, $type, $passthrumac, $_POST, $cpzone;
1763

    
1764
	// Ensure we create an array if we are missing attributes
1765
	if (!is_array($attributes))
1766
		$attributes = array();
1767

    
1768
	unset($sessionid);
1769

    
1770
	/* Do not allow concurrent login execution. */
1771
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1772

    
1773
	if ($attributes['voucher'])
1774
		$remaining_time = $attributes['session_timeout'];
1775

    
1776
	$writecfg = false;
1777
	/* Find an existing session */
1778
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
1779
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1780
			$mac = captiveportal_passthrumac_findbyname($username);
1781
			if (!empty($mac)) {
1782
				if ($_POST['replacemacpassthru']) {
1783
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
1784
						if ($macent['mac'] == $mac['mac']) {
1785
							$macrules = "";
1786
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1787
							$pipeno = captiveportal_get_dn_passthru_ruleno($mac['mac']);
1788
							if ($ruleno) {
1789
								captiveportal_free_ipfw_ruleno($ruleno);
1790
								$macrules .= "delete {$ruleno}\n";
1791
								++$ruleno;
1792
								$macrules .= "delete {$ruleno}\n";
1793
							}
1794
							if ($pipeno) {
1795
								captiveportal_free_dn_ruleno($pipeno);
1796
								$macrules .= "pipe delete {$pipeno}\n";
1797
								++$pipeno;
1798
								$macrules .= "pipe delete {$pipeno}\n";
1799
							}
1800
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1801
							$mac['mac'] = $clientmac;
1802
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1803
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1804
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1805
							mwexec("/sbin/ipfw -x {$cpzone} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1806
							$writecfg = true;
1807
							$sessionid = true;
1808
							break;
1809
						}
1810
					}
1811
				} else {
1812
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1813
						$clientmac, $clientip, $username, $password);
1814
					unlock($cpdblck);
1815
					return;
1816
				}
1817
			}
1818
		}
1819
	}
1820

    
1821
	/* read in client database */
1822
	$query = "WHERE ip = '{$clientip}'";
1823
	$tmpusername = strtolower($username);
1824
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins']))
1825
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
1826
	$cpdb = captiveportal_read_db($query);
1827

    
1828
	/* Snapshot the timestamp */
1829
	$allow_time = time();
1830
	$radiusservers = captiveportal_get_radius_servers();
1831
	$unsetindexes = array();
1832
	if (is_null($radiusctx))
1833
		$radiusctx = 'first';
1834

    
1835
	foreach ($cpdb as $cpentry) {
1836
		if (empty($cpentry[11])) {
1837
			$cpentry[11] = 'first';
1838
		}
1839
		/* on the same ip */
1840
		if ($cpentry[2] == $clientip) {
1841
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac)
1842
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1843
			else
1844
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
1845
			$sessionid = $cpentry[5];
1846
			break;
1847
		}
1848
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1849
			// user logged in with an active voucher. Check for how long and calculate 
1850
			// how much time we can give him (voucher credit - used time)
1851
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1852
			if ($remaining_time < 0)    // just in case. 
1853
				$remaining_time = 0;
1854

    
1855
			/* This user was already logged in so we disconnect the old one */
1856
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[11]],13);
1857
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1858
			$unsetindexes[] = $cpentry[5];
1859
			break;
1860
		}
1861
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1862
			/* on the same username */
1863
			if (strcasecmp($cpentry[4], $username) == 0) {
1864
				/* This user was already logged in so we disconnect the old one */
1865
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[11]],13);
1866
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1867
				$unsetindexes[] = $cpentry[5];
1868
				break;
1869
			}
1870
		}
1871
	}
1872
	unset($cpdb);
1873

    
1874
	if (!empty($unsetindexes))
1875
		captiveportal_remove_entries($unsetindexes);
1876

    
1877
	if ($attributes['voucher'] && $remaining_time <= 0)
1878
		return 0;       // voucher already used and no time left
1879

    
1880
	if (!isset($sessionid)) {
1881
		/* generate unique session ID */
1882
		$tod = gettimeofday();
1883
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1884

    
1885
		if ($passthrumac) {
1886
			$mac = array();
1887
			$mac['mac'] = $clientmac;
1888
			$mac['ip'] = $clientip; /* Used only for logging */
1889
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
1890
				$mac['username'] = $username;
1891
				if ($attributes['voucher'])
1892
					$mac['logintype'] = "voucher";
1893
			}
1894
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1895
			if (!empty($bw_up))
1896
				$mac['bw_up'] = $bw_up;
1897
			if (!empty($bw_down))
1898
				$mac['bw_down'] = $bw_down;
1899
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
1900
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
1901
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1902
			unlock($cpdblck);
1903
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1904
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1905
			mwexec("/sbin/ipfw -x {$cpzone} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1906
			$writecfg = true;
1907
		} else {
1908
			/* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
1909
			if (is_null($pipeno))
1910
				$pipeno = captiveportal_get_next_dn_ruleno();
1911

    
1912
			/* if the pool is empty, return appropriate message and exit */
1913
			if (is_null($pipeno)) {
1914
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1915
				log_error("WARNING!  Captive portal has reached maximum login capacity");
1916
				unlock($cpdblck);
1917
				return;
1918
			}
1919

    
1920
			$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1921
			$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1922
			$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1923
			$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1924

    
1925
			$bw_up_pipeno = $pipeno;
1926
			$bw_down_pipeno = $pipeno + 1;
1927
			//$bw_up /= 1000; // Scale to Kbit/s
1928
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1929
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1930

    
1931
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
1932
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1933
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
1934
			else
1935
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
1936

    
1937
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1938
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
1939
			else
1940
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
1941

    
1942
			if ($attributes['voucher'])
1943
				$attributes['session_timeout'] = $remaining_time;
1944
			
1945
			/* handle empty attributes */
1946
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
1947
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
1948
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
1949
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
1950

    
1951
			/* escape username */
1952
			$safe_username = sqlite_escape_string($username);
1953

    
1954
			/* encode password in Base64 just in case it contains commas */
1955
			$bpassword = base64_encode($password);
1956
			$insertquery  = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, radiusctx) ";
1957
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
1958
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, '{$radiusctx}')";
1959

    
1960
			/* store information to database */
1961
			captiveportal_write_db($insertquery);
1962
			unlock($cpdblck);
1963
			unset($insertquery, $bpassword);
1964

    
1965
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
1966
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
1967
				if ($acct_val == 1)
1968
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1969
			}
1970
		}
1971
	} else {
1972
		/* NOTE: #3062-11 If the pipeno has been allocated free it to not DoS the CP and maintain proper operation as in radius() case */
1973
		if (!is_null($pipeno))
1974
			captiveportal_free_dn_ruleno($pipeno);
1975

    
1976
		unlock($cpdblck);
1977
	}
1978

    
1979
	if ($writecfg == true)
1980
		write_config();
1981

    
1982
	/* redirect user to desired destination */
1983
	if (!empty($attributes['url_redirection']))
1984
		$my_redirurl = $attributes['url_redirection'];
1985
	else if (!empty($redirurl))
1986
		$my_redirurl = $redirurl;
1987
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
1988
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
1989

    
1990
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
1991
		$ourhostname = portal_hostname_from_client_ip($clientip);
1992
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
1993
		$logouturl = "{$protocol}{$ourhostname}/";
1994

    
1995
		if (isset($attributes['reply_message']))
1996
			$message = $attributes['reply_message'];
1997
		else
1998
			$message = 0;
1999

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

    
2002
	} else {
2003
		portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
2004
	}
2005

    
2006
	return $sessionid;
2007
}
2008

    
2009

    
2010
/*
2011
 * Used for when pass-through credits are enabled.
2012
 * Returns true when there was at least one free login to deduct for the MAC.
2013
 * Expired entries are removed as they are seen.
2014
 * Active entries are updated according to the configuration.
2015
 */
2016
function portal_consume_passthrough_credit($clientmac) {
2017
	global $config, $cpzone;
2018

    
2019
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
2020
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
2021
	else
2022
		return false;
2023

    
2024
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
2025
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2026
	else
2027
		return false;
2028

    
2029
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
2030
		return false;
2031

    
2032
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2033

    
2034
	/*
2035
	 * Read database of used MACs.  Lines are a comma-separated list
2036
	 * of the time, MAC, then the count of pass-through credits remaining.
2037
	 */
2038
	$usedmacs = captiveportal_read_usedmacs_db();
2039

    
2040
	$currenttime = time();
2041
	$found = false;
2042
	foreach ($usedmacs as $key => $usedmac) {
2043
		$usedmac = explode(",", $usedmac);
2044

    
2045
		if ($usedmac[1] == $clientmac) {
2046
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2047
				if ($usedmac[2] < 1) {
2048
					if ($updatetimeouts) {
2049
						$usedmac[0] = $currenttime;
2050
						unset($usedmacs[$key]);
2051
						$usedmacs[] = implode(",", $usedmac);
2052
						captiveportal_write_usedmacs_db($usedmacs);
2053
					}
2054

    
2055
					return false;
2056
				} else {
2057
					$usedmac[2] -= 1;
2058
					$usedmacs[$key] = implode(",", $usedmac);
2059
				}
2060

    
2061
				$found = true;
2062
			} else
2063
				unset($usedmacs[$key]);
2064

    
2065
			break;
2066
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2067
				unset($usedmacs[$key]);
2068
	}
2069

    
2070
	if (!$found) {
2071
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2072
		$usedmacs[] = implode(",", $usedmac);
2073
	}
2074

    
2075
	captiveportal_write_usedmacs_db($usedmacs);
2076
	return true;
2077
}
2078

    
2079
function captiveportal_read_usedmacs_db() {
2080
	global $g, $cpzone;
2081

    
2082
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2083
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2084
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2085
		if (!$usedmacs)
2086
			$usedmacs = array();
2087
	} else
2088
		$usedmacs = array();
2089

    
2090
	unlock($cpumaclck);
2091
	return $usedmacs;
2092
}
2093

    
2094
function captiveportal_write_usedmacs_db($usedmacs) {
2095
	global $g, $cpzone;
2096

    
2097
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2098
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2099
	unlock($cpumaclck);
2100
}
2101

    
2102
function captiveportal_send_server_accounting($off = false) {
2103
	global $cpzone, $config;
2104

    
2105
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
2106
		return;
2107
	}
2108
	if ($off) {
2109
		$racct = new Auth_RADIUS_Acct_Off;
2110
	} else {
2111
		$racct = new Auth_RADIUS_Acct_On;
2112
	}
2113
	$radiusservers = captiveportal_get_radius_servers();
2114
	if (empty($radiusservers)) {
2115
		return;
2116
	}
2117
	foreach ($radiusservers['first'] as $radsrv) {
2118
		// Add a new server to our instance
2119
		$racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
2120
	}
2121
	if (PEAR::isError($racct->start())) {
2122
		$retvalue['acct_val'] = 1;
2123
		$retvalue['error'] = $racct->getMessage();
2124

    
2125
		// If we encounter an error immediately stop this function and go back
2126
		$racct->close();
2127
		return $retvalue;
2128
	}
2129
	// Send request
2130
	$result = $racct->send();
2131
	// Evaluation of the response
2132
	// 5 -> Accounting-Response
2133
	// See RFC2866 for this.
2134
	if (PEAR::isError($result)) {
2135
		$retvalue['acct_val'] = 1;
2136
		$retvalue['error'] = $result->getMessage();
2137
	} else if ($result === true) {
2138
		$retvalue['acct_val'] = 5 ;
2139
	} else {
2140
		$retvalue['acct_val'] = 1 ;
2141
	}
2142

    
2143
	$racct->close();
2144
	return $retvalue;
2145
}
2146
?>
(8-8/66)