Project

General

Profile

Download (69.8 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	captiveportal.inc
4
	part of pfSense (http://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
		/* Activate layer2 filtering */
157
		mwexec("/sbin/sysctl net.link.ether.ipfw=1 net.inet.ip.fw.one_pass=1");
158
	}
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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
446
/* reinit will disconnect all users, be careful! */
447
function captiveportal_init_rules($reinit = false) {
448
	global $config, $g, $cpzone;
449

    
450
	if (!isset($config['captiveportal'][$cpzone]['enable']))
451
		return;
452

    
453
	captiveportal_load_modules();
454
	mwexec("/usr/local/sbin/ipfw_context -a {$cpzone}", true);
455

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

    
487
	if ($reinit == false)
488
		$captiveportallck = lock("captiveportal{$cpzone}");
489

    
490
	$cprules =	"add 65291 allow pfsync from any to any\n";
491
	$cprules .= "add 65292 allow carp from any to any\n";
492

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

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

    
504
EOD;
505

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

    
532
	/* Authenticated users rules. */
533
	$cprules .= "add {$rulenum} pipe tablearg ip from table(1) to any in\n";
534
	$rulenum++;
535
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(2) out\n";
536
	$rulenum++;
537

    
538
	$listenporthttp =
539
		$config['captiveportal'][$cpzone]['listenporthttp'] ?
540
		$config['captiveportal'][$cpzone]['listenporthttp'] :
541
		$config['captiveportal'][$cpzone]['zoneid'];
542

    
543
	if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
544
		$listenporthttps = $listenporthttp + 1;
545
		$cprules .= "add 65531 fwd 127.0.0.1,{$listenporthttps} tcp from any to any dst-port 443 in\n";
546
	}
547
	
548
	$cprules .= <<<EOD
549

    
550
# redirect non-authenticated clients to captive portal
551
add 65532 fwd 127.0.0.1,{$listenporthttp} tcp from any to any dst-port 80 in 
552
# let the responses from the captive portal web server back out
553
add 65533 pass tcp from any to any out
554
# block everything else
555
add 65534 deny all from any to any
556

    
557
EOD;
558

    
559
	/* generate passthru mac database */
560
	$cprules .= captiveportal_passthrumac_configure(true);
561
	$cprules .= "\n";
562

    
563
	/* allowed ipfw rules to make allowed ip work */
564
	$cprules .= captiveportal_allowedip_configure();
565

    
566
	/* allowed ipfw rules to make allowed hostnames work */
567
	$cprules .= captiveportal_allowedhostname_configure();
568
	
569
	/* load rules */
570
	$cprules = "flush\n{$cprules}";
571
	file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
572
	mwexec("/sbin/ipfw -x {$cpzone} -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
573
	//@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
574
	unset($cprules, $tmprules);
575

    
576
	if ($reinit == false)
577
		unlock($captiveportallck);
578
}
579

    
580
/* 
581
 * Remove clients that have been around for longer than the specified amount of time
582
 * db file structure:
583
 * timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time,interim_interval
584
 * (password is in Base64 and only saved when reauthentication is enabled)
585
 */
586
function captiveportal_prune_old() {
587
	global $g, $config, $cpzone;
588

    
589
	if (empty($cpzone))
590
		return;
591

    
592
	$cpcfg = $config['captiveportal'][$cpzone];
593
	$vcpcfg = $config['voucher'][$cpzone];
594

    
595
	/* check for expired entries */
596
	$idletimeout = 0;
597
	$timeout = 0;
598
	if (!empty($cpcfg['timeout']) && is_numeric($cpcfg['timeout']))
599
		$timeout = $cpcfg['timeout'] * 60;
600

    
601
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout']))
602
		$idletimeout = $cpcfg['idletimeout'] * 60;
603

    
604
	/* Is there any job to do? */
605
	if (!$timeout && !$idletimeout && !isset($cpcfg['reauthenticate']) &&
606
	    !isset($cpcfg['radiussession_timeout']) && !isset($vcpcfg['enable']))
607
		return;
608

    
609
	$radiussrvs = captiveportal_get_radius_servers();
610

    
611
	/* Read database */
612
	/* NOTE: while this can be simplified in non radius case keep as is for now */
613
	$cpdb = captiveportal_read_db();
614

    
615
	/*
616
	 * To make sure we iterate over ALL accounts on every run the count($cpdb) is moved
617
	 * outside of the loop. Otherwise the loop would evaluate count() on every iteration
618
	 * and since $i would increase and count() would decrement they would meet before we
619
	 * had a chance to iterate over all accounts.
620
	 */
621
	$unsetindexes = array();
622
	$voucher_needs_sync = false;
623
	/* 
624
	 * Snapshot the time here to use for calculation to speed up the process.
625
	 * If something is missed next run will catch it!
626
	 */
627
	$pruning_time = time();
628
	$stop_time = $pruning_time;
629
	foreach ($cpdb as $cpentry) {
630

    
631
		$timedout = false;
632
		$term_cause = 1;
633
		if (empty($cpentry[10]))
634
			$cpentry[10] = 'first';
635
		$radiusservers = $radiussrvs[$cpentry[10]];
636

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

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

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

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

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

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

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

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

    
754
	captiveportal_prune_old_automac();
755

    
756
	if ($voucher_needs_sync == true)
757
		/* Triger a sync of the vouchers on config */
758
		send_event("service sync vouchers");
759

    
760
	/* write database */
761
	if (!empty($unsetindexes))
762
		captiveportal_remove_entries($unsetindexes);
763
}
764

    
765
function captiveportal_prune_old_automac() {
766
	global $g, $config, $cpzone;
767

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

    
826
/* remove a single client according to the DB entry */
827
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
828
	global $g, $config, $cpzone;
829

    
830
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
831

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

    
855
	/* 
856
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
857
	* We could get an error if the pipe doesn't exist but everything should still be fine
858
	*/
859
	if (!empty($dbent[1])) {
860
		pfSense_pipe_action("pipe delete {$dbent[1]}");
861
		pfSense_pipe_action("pipe delete " . ($dbent[1]+1));
862

    
863
		/* Release the ruleno so it can be reallocated to new clients. */
864
		captiveportal_free_dn_ruleno($dbent[1]);
865
	}
866

    
867
	// XMLRPC Call over to the master Voucher node
868
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
869
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
870
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
871
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
872
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
873
		$remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time);
874
	}
875

    
876
}
877

    
878
/* remove a single client by sessionid */
879
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
880
	global $g, $config;
881

    
882
	$radiusservers = captiveportal_get_radius_servers();
883

    
884
	/* read database */
885
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
886

    
887
	/* find entry */
888
	if (!empty($result)) {
889
		captiveportal_write_db("DELETE FROM captiveportal WHERE sessionid = '{$sessionid}'");
890

    
891
		foreach ($result as $cpentry) {
892
			if (empty($cpentry[10]))
893
				$cpentry[10] = 'first';
894
			captiveportal_disconnect($cpentry, $radiusservers[$cpentry[10]], $term_cause);
895
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
896
		}
897
		unset($result);
898
	}
899
}
900

    
901
/* send RADIUS acct stop for all current clients */
902
function captiveportal_radius_stop_all() {
903
	global $config, $cpzone;
904

    
905
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable']))
906
		return;
907

    
908
	$radiusservers = captiveportal_get_radius_servers();
909
	if (!empty($radiusservers)) {
910
		$cpdb = captiveportal_read_db();
911
		foreach ($cpdb as $cpentry) {
912
			if (empty($cpentry[10]))
913
				$cpentry[10] = 'first';
914
			if (!empty($radiusservers[$cpentry[10]])) {
915
				RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
916
					$cpentry[4], // username
917
					$cpentry[5], // sessionid
918
					$cpentry[0], // start time
919
					$radiusservers[$cpentry[10]],
920
					$cpentry[2], // clientip
921
					$cpentry[3], // clientmac
922
					7); // Admin Reboot
923
			}
924
		}
925
	}
926
}
927

    
928
function captiveportal_passthrumac_configure_entry($macent) {
929

    
930
	$bwUp = empty($macent['bw_up']) ? 0 : $macent['bw_up'];
931
	$bwDown = empty($macent['bw_down']) ? 0 : $macent['bw_down'];
932

    
933
	$ruleno = captiveportal_get_next_ipfw_ruleno();
934
	$pipeno = captiveportal_get_next_dn_ruleno();
935

    
936
	$rules = "";
937
	$pipeup = $pipeno;
938
	pfSense_pipe_action("pipe {$pipeup} config bw {$bwUp}Kbit/s queue 100 buckets 16");
939
	$pipedown = $pipeno + 1;
940
	pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
941
	$rules .= "add {$ruleno} pipe {$pipeup} ip from any to any MAC {$macent['mac']} any\n";
942
	$ruleno++;
943
	$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC any {$macent['mac']}\n";
944

    
945
	return $rules;
946
}
947

    
948
function captiveportal_passthrumac_configure($lock = false) {
949
	global $config, $g, $cpzone;
950

    
951
	$rules = "";
952

    
953
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
954
		$macdb = array();
955
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
956
			$rules .= captiveportal_passthrumac_configure_entry($macent);
957
			$macdb[$macent['mac']][$cpzone]['active']  = true;
958

    
959
		}
960
	}
961

    
962
	return $rules;
963
}
964

    
965
function captiveportal_passthrumac_findbyname($username) {
966
	global $config, $cpzone;
967

    
968
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
969
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
970
			if ($macent['username'] == $username)
971
				return $macent;
972
		}
973
	}
974
	return NULL;
975
}
976

    
977
/* 
978
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
979
 */
980
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
981
	global $g;
982

    
983
	/*  Instead of copying this entire function for something
984
	 *  easy such as hostname vs ip address add this check
985
	 */
986
	if ($ishostname === true) {
987
		if (!$g['booting']) {
988
			$ipaddress = gethostbyname($ipent['hostname']);
989
			if (!is_ipaddr($ipaddress)) 
990
				return;
991
		}
992
	} else
993
		$ipaddress = $ipent['ip'];
994

    
995
	$rules = "";
996
	$cp_filterdns_conf = "";
997
	$enBwup = empty($ipent['bw_up']) ? 0 : intval($ipent['bw_up']);
998
	$enBwdown = empty($ipent['bw_down']) ? 0 : intval($ipent['bw_down']);
999

    
1000
	$pipeno = captiveportal_get_next_dn_ruleno();
1001
	pfSense_pipe_action("pipe {$pipeno} config bw {$enBwup}Kbit/s queue 100 buckets 16");
1002
	$pipedown = $pipeno + 1;
1003
	pfSense_pipe_action("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16");
1004
	if ($ishostname === true) {
1005
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
1006
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
1007
		if (!is_ipaddr($ipaddress))
1008
			return array("", $cp_filterdns_conf);
1009
	}
1010
	$subnet = "";
1011
	if (!empty($ipent['sn']))
1012
		$subnet = "/{$ipent['sn']}";
1013
	$rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
1014
	$rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
1015

    
1016
	if ($ishostname === true)
1017
		return array($rules, $cp_filterdns_conf);
1018
	else
1019
		return $rules;
1020
}
1021

    
1022
function captiveportal_allowedhostname_configure() {
1023
	global $config, $g, $cpzone;
1024

    
1025
	$rules = "";
1026
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1027
		$rules = "\n# captiveportal_allowedhostname_configure()\n";
1028
		$cp_filterdns_conf = "";
1029
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) {
1030
			$tmprules = captiveportal_allowedip_configure_entry($hostnameent, true);
1031
			$rules .= $tmprules[0];
1032
			$cp_filterdns_conf .= $tmprules[1];
1033
		}
1034
		$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1035
		@file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1036
		unset($cp_filterdns_conf);
1037
		if (isvalidpid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid"))
1038
			sigkillbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid", "HUP");
1039
		else
1040
			mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzone} -d 1");
1041
	} else {
1042
		killbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1043
		@unlink("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1044
	}
1045

    
1046
	return $rules;
1047
}
1048

    
1049
function captiveportal_allowedip_configure() {
1050
	global $config, $g, $cpzone;
1051

    
1052
	$rules = "";
1053
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1054
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1055
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1056
	}
1057

    
1058
	return $rules;
1059
}
1060

    
1061
/* get last activity timestamp given client IP address */
1062
function captiveportal_get_last_activity($ip) {
1063
	global $cpzone;
1064

    
1065
	$ipfwoutput = pfSense_ipfw_getTablestats($cpzone, 1, $ip);
1066
	/* Reading only from one of the tables is enough of approximation. */
1067
	if (is_array($ipfwoutput)) {
1068
		return $ipfwoutput['timestamp'];
1069
	}
1070

    
1071
	return 0;
1072
}
1073

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

    
1077
	/* generate radius server database */
1078
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1079
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1080
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1081
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1082
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1083
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1084

    
1085
		if ($config['captiveportal'][$cpzone]['radiusport'])
1086
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1087
		else
1088
			$radiusport = 1812;
1089
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1090
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1091
		else
1092
			$radiusacctport = 1813;
1093
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1094
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1095
		else
1096
			$radiusport2 = 1812;
1097
		if ($config['captiveportal'][$cpzone]['radiusport3'])
1098
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1099
		else
1100
			$radiusport3 = 1812;
1101
		if ($config['captiveportal'][$cpzone]['radiusport4'])
1102
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1103
		else
1104
			$radiusport4 = 1812;
1105

    
1106
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1107
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1108
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1109
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1110

    
1111
		$cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX);
1112
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
1113
		if (!$fd) {
1114
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1115
			unlock($cprdsrvlck);
1116
			return 1;
1117
		}
1118
		if (isset($radiusip))
1119
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
1120
		if (isset($radiusip2))
1121
			fwrite($fd,"\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
1122
		if (isset($radiusip3))
1123
			fwrite($fd,"\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
1124
		if (isset($radiusip4))
1125
			fwrite($fd,"\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
1126
		
1127

    
1128
		fclose($fd);
1129
		unlock($cprdsrvlck);
1130
	}
1131
}
1132

    
1133
/* read RADIUS servers into array */
1134
function captiveportal_get_radius_servers() {
1135
	global $g, $cpzone;
1136

    
1137
	$cprdsrvlck = lock("captiveportalradius{$cpzone}");
1138
	if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
1139
		$radiusservers = array();
1140
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", 
1141
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1142
		if ($cpradiusdb) {
1143
			foreach($cpradiusdb as $cpradiusentry) {
1144
				$line = trim($cpradiusentry);
1145
				if ($line) {
1146
					$radsrv = array();
1147
						list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key'], $context) = explode(",",$line);
1148
				}
1149
				if (empty($context)) {
1150
					if (!is_array($radiusservers['first']))
1151
						$radiusservers['first'] = array();
1152
					$radiusservers['first'] = $radsrv;
1153
				} else {
1154
					if (!is_array($radiusservers[$context]))
1155
						$radiusservers[$context] = array();
1156
					$radiusservers[$context][] = $radsrv;
1157
				}
1158
			}
1159
		}
1160
		unlock($cprdsrvlck);
1161
		return $radiusservers;
1162
	}
1163

    
1164
	unlock($cprdsrvlck);
1165
	return false;
1166
}
1167

    
1168
/* log successful captive portal authentication to syslog */
1169
/* part of this code from php.net */
1170
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1171
	// Log it
1172
	if (!$message)
1173
		$message = "$status: $user, $mac, $ip";
1174
	else {
1175
		$message = trim($message);
1176
		$message = "$status: $user, $mac, $ip, $message";
1177
	}
1178
	captiveportal_syslog($message);
1179
}
1180

    
1181
/* log simple messages to syslog */
1182
function captiveportal_syslog($message) {
1183
	$message = trim($message);
1184
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1185
	// Log it
1186
	syslog(LOG_INFO, $message);
1187
	closelog();
1188
}
1189

    
1190
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1191
	global $g, $config;
1192

    
1193
	$pipeno = captiveportal_get_next_dn_ruleno();
1194

    
1195
	/* If the pool is empty, return appropriate message and fail authentication */
1196
	if (is_null($pipeno)) {
1197
		$auth_list = array();
1198
		$auth_list['auth_val'] = 1;
1199
		$auth_list['error'] = "System reached maximum login capacity";
1200
		return $auth_list;
1201
	}
1202

    
1203
	$radiusservers = captiveportal_get_radius_servers();
1204

    
1205
	if (is_null($radiusctx))
1206
		$radiusctx = 'first';
1207

    
1208
	$auth_list = RADIUS_AUTHENTICATION($username,
1209
		$password,
1210
		$radiusservers[$radiusctx],
1211
		$clientip,
1212
		$clientmac,
1213
		$pipeno);
1214

    
1215
	if ($auth_list['auth_val'] == 2) {
1216
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1217
		$sessionid = portal_allow($clientip,
1218
			$clientmac,
1219
			$username,
1220
			$password,
1221
			$auth_list,
1222
			$pipeno,
1223
			$radiusctx);
1224
	}
1225

    
1226
	return $auth_list;
1227
}
1228

    
1229
function captiveportal_opendb() {
1230
	global $g, $cpzone;
1231

    
1232
	if (file_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db"))
1233
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1234
	else {
1235
		$errormsg = "";
1236
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1237
		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) ", $errormsg)) {
1238
			@sqlite_exec($DB, "CREATE UNIQUE INDEX idx_active ON captiveportal (sessionid, username)");
1239
			@sqlite_exec($DB, "CREATE INDEX user ON captiveportal (username)");
1240
			@sqlite_exec($DB, "CREATE INDEX ip ON captiveportal (ip)");
1241
			@sqlite_exec($DB, "CREATE INDEX starttime ON captiveportal (allow_time)");
1242
			@sqlite_exec($DB, "CREATE INDEX serviceid ON captiveportal (serviceid)");
1243
		} else
1244
			captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$errormsg}");
1245
	}
1246

    
1247
	return $DB;
1248
}
1249

    
1250
/* read captive portal DB into array */
1251
function captiveportal_read_db($query = "") {
1252

    
1253
	$DB = captiveportal_opendb();
1254
	if ($DB) {
1255
		sqlite_exec($DB, "BEGIN");
1256
		if (!empty($query))
1257
			$cpdb = @sqlite_array_query($DB, "SELECT * FROM captiveportal {$query}", SQLITE_NUM);
1258
		else {
1259
			$response = @sqlite_unbuffered_query($DB, "SELECT * FROM captiveportal", SQLITE_NUM);
1260
			$cpdb = @sqlite_fetch_all($response, SQLITE_NUM);
1261
		}
1262
		sqlite_exec($DB, "END");
1263
		@sqlite_close($DB);
1264
	}
1265
	if (!$cpdb)
1266
		$cpdb = array();
1267

    
1268
	return $cpdb;
1269
}
1270

    
1271
function captiveportal_remove_entries($remove) {
1272

    
1273
	if (!is_array($remove) || empty($remove))
1274
		return;
1275

    
1276
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1277
	foreach($remove as $idx => $unindex) {
1278
		$query .= "'{$unindex}'";
1279
		if ($idx < (count($remove) - 1))
1280
			$query .= ",";
1281
	}
1282
	$query .= ")";
1283
	captiveportal_write_db($query);
1284
}
1285

    
1286
/* write captive portal DB */
1287
function captiveportal_write_db($queries) {
1288
	global $g;
1289

    
1290
	if (is_array($queries))
1291
		$query = implode(";", $queries);
1292
	else
1293
		$query = $queries;
1294

    
1295
	$DB = captiveportal_opendb();
1296
	if ($DB) {
1297
		$error_msg = "";
1298
		sqlite_exec($DB, "BEGIN TRANSACTION");
1299
		$result = @sqlite_exec($DB, $query, $error_msg);
1300
		if (!$result)
1301
			captiveportal_syslog("Trying to modify DB returned error: {$error_msg}");
1302
		else
1303
			sqlite_exec($DB, "END TRANSACTION");
1304
		@sqlite_close($DB);
1305
		return $result;
1306
	} else
1307
		return true;
1308
}
1309

    
1310
function captiveportal_write_elements() {
1311
	global $g, $config, $cpzone;
1312
	
1313
	$cpcfg = $config['captiveportal'][$cpzone];
1314

    
1315
	/* delete any existing elements */
1316
	if (is_dir($g['captiveportal_element_path'])) {
1317
		$dh = opendir($g['captiveportal_element_path']);
1318
		while (($file = readdir($dh)) !== false) {
1319
			if ($file != "." && $file != "..")
1320
				unlink($g['captiveportal_element_path'] . "/" . $file);
1321
		}
1322
		closedir($dh);
1323
	} else {
1324
		@mkdir($g['captiveportal_element_path']);
1325
	}
1326

    
1327
	if (is_array($cpcfg['element'])) {
1328
		conf_mount_rw();
1329
		foreach ($cpcfg['element'] as $data) {
1330
			if (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1331
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1332
				return 1;
1333
			}
1334
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1335
			@symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1336
		}
1337
		conf_mount_ro();
1338
	}
1339
	
1340
	return 0;
1341
}
1342

    
1343
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1344
	global $config, $g;
1345

    
1346
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1347
	$ruleno = 0;
1348
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1349
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1350
		for ($ridx = $rulenos_start; $ridx < $rulenos_range_max; $ridx++) {
1351
			if ($rules[$ridx]) {
1352
				$ridx++;
1353
				continue;
1354
			}
1355
			$ruleno = $ridx;
1356
			$rules[$ridx] = "used";
1357
			$rules[++$ridx] = "used";
1358
			break;
1359
		}
1360
	} else {
1361
		$rules = array_pad(array(), $rulenos_range_max, false);
1362
		$rules[$rulenos_start] = "used";
1363
		$rules[++$rulenos_start] = "used";
1364
		$ruleno = $rulenos_start;
1365
	}
1366
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1367
	unlock($cpruleslck);
1368

    
1369
	return $ruleno;
1370
}
1371

    
1372
function captiveportal_free_dn_ruleno($ruleno) {
1373
	global $config, $g;
1374

    
1375
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1376
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1377
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1378
		$rules[$ruleno] = false;
1379
		$rules[++$ruleno] = false;
1380
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1381
	}
1382
	unlock($cpruleslck);
1383
}
1384

    
1385
function captiveportal_get_dn_passthru_ruleno($value) {
1386
	global $config, $g, $cpzone;
1387

    
1388
	$cpcfg = $config['captiveportal'][$cpzone];
1389
	if(!isset($cpcfg['enable']))
1390
		return NULL;
1391

    
1392
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1393
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1394
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1395
		$ruleno = intval(`/sbin/ipfw -x {$cpzone} show | /usr/bin/grep {$value} |  /usr/bin/grep -v grep | /usr/bin/cut -d " " -f 5 | /usr/bin/head -n 1`);
1396
		if ($rules[$ruleno]) {
1397
			unlock($cpruleslck);
1398
			return $ruleno;
1399
		}
1400
	}
1401

    
1402
	unlock($cpruleslck);
1403
	return NULL;
1404
}
1405

    
1406
/*
1407
 * This function will calculate the lowest free firewall ruleno
1408
 * within the range specified based on the actual logged on users
1409
 *
1410
 */
1411
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1412
	global $config, $g, $cpzone;
1413

    
1414
	$cpcfg = $config['captiveportal'][$cpzone];
1415
	if(!isset($cpcfg['enable']))
1416
		return NULL;
1417

    
1418
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1419
	$ruleno = 0;
1420
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1421
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1422
		for ($ridx = 2; $ridx < ($rulenos_range_max - $rulenos_start); $ridx++) {
1423
			if ($rules[$ridx]) {
1424
				/* 
1425
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1426
				 * and the out pipe ruleno + 1.
1427
				 */
1428
				$ridx++;
1429
				continue;
1430
			}
1431
			$ruleno = $ridx;
1432
			$rules[$ridx] = "used";
1433
			$rules[++$ridx] = "used";
1434
			break;
1435
		}
1436
	} else {
1437
		$rules = array_pad(array(), $rulenos_range_max, false);
1438
		$rules[$rulenos_start] = "used";
1439
		$rules[++$rulenos_start] = "used";
1440
		$ruleno = 2;
1441
	}
1442
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1443
	unlock($cpruleslck);
1444
	return $ruleno;
1445
}
1446

    
1447
function captiveportal_free_ipfw_ruleno($ruleno) {
1448
	global $config, $g, $cpzone;
1449

    
1450
	$cpcfg = $config['captiveportal'][$cpzone];
1451
	if(!isset($cpcfg['enable']))
1452
		return NULL;
1453

    
1454
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1455
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1456
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1457
		$rules[$ruleno] = false;
1458
		$rules[++$ruleno] = false;
1459
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1460
	}
1461
	unlock($cpruleslck);
1462
}
1463

    
1464
function captiveportal_get_ipfw_passthru_ruleno($value) {
1465
	global $config, $g, $cpzone;
1466

    
1467
	$cpcfg = $config['captiveportal'][$cpzone];
1468
	if(!isset($cpcfg['enable']))
1469
		return NULL;
1470

    
1471
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1472
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1473
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1474
		$ruleno = intval(`/sbin/ipfw -x {$cpzone} show | /usr/bin/grep {$value} |  /usr/bin/grep -v grep | /usr/bin/cut -d " " -f 1 | /usr/bin/head -n 1`);
1475
		if ($rules[$ruleno]) {
1476
			unlock($cpruleslck);
1477
			return $ruleno;
1478
		}
1479
	}
1480

    
1481
	unlock($cpruleslck);
1482
	return NULL;
1483
}
1484

    
1485
/**
1486
 * This function will calculate the traffic produced by a client
1487
 * based on its firewall rule
1488
 *
1489
 * Point of view: NAS
1490
 *
1491
 * Input means: from the client
1492
 * Output means: to the client
1493
 *
1494
 */
1495

    
1496
function getVolume($ip) {
1497
	global $config, $cpzone;
1498

    
1499
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1500
	$volume = array();
1501
	// Initialize vars properly, since we don't want NULL vars
1502
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1503

    
1504
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 1, $ip);
1505
	if (is_array($ipfw)) {
1506
		if ($reverse) {
1507
			$volume['output_pkts'] = $ipfw['packets'];
1508
			$volume['output_bytes'] = $ipfw['bytes'];
1509
		}
1510
		else {
1511
			$volume['input_pkts'] = $ipfw['packets'];
1512
			$volume['input_bytes'] = $ipfw['bytes'];
1513
		}
1514
	}
1515

    
1516
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 2, $ip);
1517
	if (is_array($ipfw)) {
1518
		if ($reverse) {
1519
			$volume['input_pkts'] = $ipfw['packets'];
1520
			$volume['input_bytes'] = $ipfw['bytes'];
1521
		}
1522
		else {
1523
			$volume['output_pkts'] = $ipfw['packets'];
1524
			$volume['output_bytes'] = $ipfw['bytes'];
1525
		}
1526
	}
1527

    
1528
	return $volume;
1529
}
1530

    
1531
/**
1532
 * Get the NAS-IP-Address based on the current wan address
1533
 *
1534
 * Use functions in interfaces.inc to find this out
1535
 *
1536
 */
1537

    
1538
function getNasIP()
1539
{
1540
	global $config, $cpzone;
1541

    
1542
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1543
			$nasIp = get_interface_ip();
1544
	} else {
1545
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1546
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1547
		else
1548
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1549
	}
1550
		
1551
	if(!is_ipaddr($nasIp))
1552
		$nasIp = "0.0.0.0";
1553

    
1554
	return $nasIp;
1555
}
1556

    
1557
function portal_ip_from_client_ip($cliip) {
1558
	global $config, $cpzone;
1559

    
1560
	$isipv6 = is_ipaddrv6($cliip);
1561
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1562
	foreach ($interfaces as $cpif) {
1563
		if ($isipv6) {
1564
			$ip = get_interface_ipv6($cpif);
1565
			$sn = get_interface_subnetv6($cpif);
1566
		} else {
1567
			$ip = get_interface_ip($cpif);
1568
			$sn = get_interface_subnet($cpif);
1569
		}
1570
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1571
			return $ip;
1572
	}
1573

    
1574
	$inet = ($isipv6) ? '-inet6' : '-inet';
1575
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1576
	$iface = trim($iface, "\n");
1577
	if (!empty($iface)) {
1578
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1579
		if (is_ipaddr($ip))
1580
			return $ip;
1581
	}
1582

    
1583
	// doesn't match up to any particular interface
1584
	// so let's set the portal IP to what PHP says 
1585
	// the server IP issuing the request is. 
1586
	// allows same behavior as 1.2.x where IP isn't 
1587
	// in the subnet of any CP interface (static routes, etc.)
1588
	// rather than forcing to DNS hostname resolution
1589
	$ip = $_SERVER['SERVER_ADDR'];
1590
	if (is_ipaddr($ip))
1591
		return $ip;
1592

    
1593
	return false;
1594
}
1595

    
1596
function portal_hostname_from_client_ip($cliip) {
1597
	global $config, $cpzone;
1598

    
1599
	$cpcfg = $config['captiveportal'][$cpzone];
1600

    
1601
	if (isset($cpcfg['httpslogin'])) {
1602
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
1603
		$ourhostname = $cpcfg['httpsname'];
1604
		
1605
		if ($listenporthttps != 443)
1606
			$ourhostname .= ":" . $listenporthttps;
1607
	} else {
1608
		$listenporthttp  = $cpcfg['listenporthttp']  ? $cpcfg['listenporthttp']  : $cpcfg['zoneid'];
1609
		$ifip = portal_ip_from_client_ip($cliip);
1610
		if (!$ifip)
1611
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1612
		else
1613
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1614
		
1615
		if ($listenporthttp != 80)
1616
			$ourhostname .= ":" . $listenporthttp;
1617
	}
1618
	
1619
	return $ourhostname;
1620
}
1621

    
1622
/* functions move from index.php */
1623

    
1624
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1625
	global $g, $config, $cpzone;
1626

    
1627
	/* Get captive portal layout */
1628
	if ($type == "redir") {
1629
		header("Location: {$redirurl}");
1630
		return;
1631
	} else if ($type == "login")
1632
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1633
	else
1634
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1635

    
1636
	$cpcfg = $config['captiveportal'][$cpzone];
1637

    
1638
	/* substitute the PORTAL_REDIRURL variable */
1639
	if ($cpcfg['preauthurl']) {
1640
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1641
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1642
	}
1643

    
1644
	/* substitute other variables */
1645
	$ourhostname = portal_hostname_from_client_ip($clientip);
1646
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1647
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1648
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1649

    
1650
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1651
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1652
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1653
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1654
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1655

    
1656
	// Special handling case for captive portal master page so that it can be ran 
1657
	// through the PHP interpreter using the include method above.  We convert the
1658
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1659
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1660
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1661
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1662
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1663
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1664
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1665
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1666

    
1667
	echo $htmltext;
1668
}
1669

    
1670
function portal_mac_radius($clientmac,$clientip) {
1671
	global $config, $cpzone;
1672

    
1673
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1674

    
1675
	/* authentication against the radius server */
1676
	$username = mac_format($clientmac);
1677
	$auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1678
	if ($auth_list['auth_val'] == 2)
1679
		return TRUE;
1680

    
1681
	if (!empty($auth_list['url_redirection']))
1682
		portal_reply_page($auth_list['url_redirection'], "redir");
1683

    
1684
	return FALSE;
1685
}
1686

    
1687
function captiveportal_reapply_attributes($cpentry, $attributes) {
1688
	global $config, $cpzone, $g;
1689

    
1690
	$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1691
	$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1692
	$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1693
	$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1694
	$bw_up_pipeno = $cpentry[1];
1695
	$bw_down_pipeno = $cpentry[1]+1;
1696

    
1697
	pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1698
	pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1699
	//captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_BANDWIDTH_REAPPLY", "{$bw_up}/{$bw_down}");
1700

    
1701
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1702
}
1703

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

    
1707
	// Ensure we create an array if we are missing attributes
1708
	if (!is_array($attributes))
1709
		$attributes = array();
1710

    
1711
	unset($sessionid);
1712

    
1713
	/* Do not allow concurrent login execution. */
1714
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1715

    
1716
	if ($attributes['voucher'])
1717
		$remaining_time = $attributes['session_timeout'];
1718

    
1719
	$writecfg = false;
1720
	/* Find an existing session */
1721
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
1722
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1723
			$mac = captiveportal_passthrumac_findbyname($username);
1724
			if (!empty($mac)) {
1725
				if ($_POST['replacemacpassthru']) {
1726
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
1727
						if ($macent['mac'] == $mac['mac']) {
1728
							$macrules = "";
1729
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1730
							$pipeno = captiveportal_get_dn_passthru_ruleno($mac['mac']);
1731
							if ($ruleno) {
1732
								captiveportal_free_ipfw_ruleno($ruleno);
1733
								$macrules .= "delete {$ruleno}\n";
1734
								++$ruleno;
1735
								$macrules .= "delete {$ruleno}\n";
1736
							}
1737
							if ($pipeno) {
1738
								captiveportal_free_dn_ruleno($pipeno);
1739
								$macrules .= "pipe delete {$pipeno}\n";
1740
								++$pipeno;
1741
								$macrules .= "pipe delete {$pipeno}\n";
1742
							}
1743
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1744
							$mac['mac'] = $clientmac;
1745
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1746
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1747
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1748
							mwexec("/sbin/ipfw -x {$cpzone} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1749
							$writecfg = true;
1750
							$sessionid = true;
1751
							break;
1752
						}
1753
					}
1754
				} else {
1755
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1756
						$clientmac, $clientip, $username, $password);
1757
					unlock($cpdblck);
1758
					return;
1759
				}
1760
			}
1761
		}
1762
	}
1763

    
1764
	/* read in client database */
1765
	$query = "WHERE ip = '{$clientip}'";
1766
	$tmpusername = strtolower($username);
1767
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins']))
1768
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
1769
	$cpdb = captiveportal_read_db($query);
1770

    
1771
	/* Snapshot the timestamp */
1772
	$allow_time = time();
1773
	$radiusservers = captiveportal_get_radius_servers();
1774
	$unsetindexes = array();
1775
	if (is_null($radiusctx))
1776
		$radiusctx = 'first';
1777

    
1778
	foreach ($cpdb as $cpentry) {
1779
		if (empty($cpentry[10]))
1780
			$cpentry[10] = 'first';
1781
		/* on the same ip */
1782
		if ($cpentry[2] == $clientip) {
1783
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac)
1784
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1785
			else
1786
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
1787
			$sessionid = $cpentry[5];
1788
			break;
1789
		}
1790
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1791
			// user logged in with an active voucher. Check for how long and calculate 
1792
			// how much time we can give him (voucher credit - used time)
1793
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1794
			if ($remaining_time < 0)    // just in case. 
1795
				$remaining_time = 0;
1796

    
1797
			/* This user was already logged in so we disconnect the old one */
1798
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1799
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1800
			$unsetindexes[] = $cpentry[5];
1801
			break;
1802
		}
1803
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1804
			/* on the same username */
1805
			if (strcasecmp($cpentry[4], $username) == 0) {
1806
				/* This user was already logged in so we disconnect the old one */
1807
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1808
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1809
				$unsetindexes[] = $cpentry[5];
1810
				break;
1811
			}
1812
		}
1813
	}
1814
	unset($cpdb);
1815

    
1816
	if (!empty($unsetindexes))
1817
		captiveportal_remove_entries($unsetindexes);
1818

    
1819
	if ($attributes['voucher'] && $remaining_time <= 0)
1820
		return 0;       // voucher already used and no time left
1821

    
1822
	if (!isset($sessionid)) {
1823
		/* generate unique session ID */
1824
		$tod = gettimeofday();
1825
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1826

    
1827
		if ($passthrumac) {
1828
			$mac = array();
1829
			$mac['mac'] = $clientmac;
1830
			$mac['ip'] = $clientip; /* Used only for logging */
1831
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
1832
				$mac['username'] = $username;
1833
				if ($attributes['voucher'])
1834
					$mac['logintype'] = "voucher";
1835
			}
1836
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1837
			if (!empty($bw_up))
1838
				$mac['bw_up'] = $bw_up;
1839
			if (!empty($bw_down))
1840
				$mac['bw_down'] = $bw_down;
1841
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
1842
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
1843
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1844
			unlock($cpdblck);
1845
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1846
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1847
			mwexec("/sbin/ipfw -x {$cpzone}-q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1848
			$writecfg = true;
1849
		} else {
1850
			/* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
1851
			if (is_null($pipeno))
1852
				$pipeno = captiveportal_get_next_dn_ruleno();
1853

    
1854
			/* if the pool is empty, return appropriate message and exit */
1855
			if (is_null($pipeno)) {
1856
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1857
				log_error("WARNING!  Captive portal has reached maximum login capacity");
1858
				unlock($cpdblck);
1859
				return;
1860
			}
1861

    
1862
			$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1863
			$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1864
			$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1865
			$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1866

    
1867
			$bw_up_pipeno = $pipeno;
1868
			$bw_down_pipeno = $pipeno + 1;
1869
			//$bw_up /= 1000; // Scale to Kbit/s
1870
			pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1871
			pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1872

    
1873
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
1874
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1875
				pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
1876
			else
1877
				pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
1878

    
1879
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1880
				pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
1881
			else
1882
				pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
1883

    
1884
			if ($attributes['voucher'])
1885
				$attributes['session_timeout'] = $remaining_time;
1886
			
1887
			/* handle empty attributes */
1888
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
1889
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
1890
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
1891
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
1892

    
1893
			/* escape username */
1894
			$safe_username = sqlite_escape_string($username);
1895

    
1896
			/* encode password in Base64 just in case it contains commas */
1897
			$bpassword = base64_encode($password);
1898
			$insertquery  = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval) ";
1899
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
1900
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval})";
1901

    
1902
			/* store information to database */
1903
			captiveportal_write_db($insertquery);
1904
			unlock($cpdblck);
1905

    
1906
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
1907
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
1908
				if ($acct_val == 1)
1909
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1910
			}
1911
		}
1912
	} else
1913
		unlock($cpdblck);
1914

    
1915
	if ($writecfg == true)
1916
		write_config();
1917

    
1918
	/* redirect user to desired destination */
1919
	if (!empty($attributes['url_redirection']))
1920
		$my_redirurl = $attributes['url_redirection'];
1921
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
1922
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
1923
	else
1924
		$my_redirurl = $redirurl;
1925

    
1926
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
1927
		$ourhostname = portal_hostname_from_client_ip($clientip);
1928
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
1929
		$logouturl = "{$protocol}{$ourhostname}/";
1930

    
1931
		if (isset($attributes['reply_message']))
1932
			$message = $attributes['reply_message'];
1933
		else
1934
			$message = 0;
1935

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

    
1938
	} else {
1939
		header("Location: " . $my_redirurl);
1940
	}
1941

    
1942
	return $sessionid;
1943
}
1944

    
1945

    
1946
/*
1947
 * Used for when pass-through credits are enabled.
1948
 * Returns true when there was at least one free login to deduct for the MAC.
1949
 * Expired entries are removed as they are seen.
1950
 * Active entries are updated according to the configuration.
1951
 */
1952
function portal_consume_passthrough_credit($clientmac) {
1953
	global $config, $cpzone;
1954

    
1955
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
1956
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
1957
	else
1958
		return false;
1959

    
1960
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
1961
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
1962
	else
1963
		return false;
1964

    
1965
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1966
		return false;
1967

    
1968
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
1969

    
1970
	/*
1971
	 * Read database of used MACs.  Lines are a comma-separated list
1972
	 * of the time, MAC, then the count of pass-through credits remaining.
1973
	 */
1974
	$usedmacs = captiveportal_read_usedmacs_db();
1975

    
1976
	$currenttime = time();
1977
	$found = false;
1978
	foreach ($usedmacs as $key => $usedmac) {
1979
		$usedmac = explode(",", $usedmac);
1980

    
1981
		if ($usedmac[1] == $clientmac) {
1982
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
1983
				if ($usedmac[2] < 1) {
1984
					if ($updatetimeouts) {
1985
						$usedmac[0] = $currenttime;
1986
						unset($usedmacs[$key]);
1987
						$usedmacs[] = implode(",", $usedmac);
1988
						captiveportal_write_usedmacs_db($usedmacs);
1989
					}
1990

    
1991
					return false;
1992
				} else {
1993
					$usedmac[2] -= 1;
1994
					$usedmacs[$key] = implode(",", $usedmac);
1995
				}
1996

    
1997
				$found = true;
1998
			} else
1999
				unset($usedmacs[$key]);
2000

    
2001
			break;
2002
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2003
				unset($usedmacs[$key]);
2004
	}
2005

    
2006
	if (!$found) {
2007
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2008
		$usedmacs[] = implode(",", $usedmac);
2009
	}
2010

    
2011
	captiveportal_write_usedmacs_db($usedmacs);
2012
	return true;
2013
}
2014

    
2015
function captiveportal_read_usedmacs_db() {
2016
	global $g, $cpzone;
2017

    
2018
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2019
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2020
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2021
		if (!$usedmacs)
2022
			$usedmacs = array();
2023
	} else
2024
		$usedmacs = array();
2025

    
2026
	unlock($cpumaclck);
2027
	return $usedmacs;
2028
}
2029

    
2030
function captiveportal_write_usedmacs_db($usedmacs) {
2031
	global $g, $cpzone;
2032

    
2033
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2034
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2035
	unlock($cpumaclck);
2036
}
2037

    
2038
function captiveportal_send_server_accounting($off = false) {
2039
	global $cpzone, $config;
2040

    
2041
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
2042
		return;
2043
	}
2044
	if ($off) {
2045
		$racct = new Auth_RADIUS_Acct_Off;
2046
	} else {
2047
		$racct = new Auth_RADIUS_Acct_On;
2048
	}
2049
	$radiusservers = captiveportal_get_radius_servers();
2050
	if (empty($radiusservers)) {
2051
		return;
2052
	}
2053
	foreach ($radiusservers['first'] as $radsrv) {
2054
		// Add a new server to our instance
2055
		$racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
2056
	}
2057
	if (PEAR::isError($racct->start())) {
2058
		$retvalue['acct_val'] = 1;
2059
		$retvalue['error'] = $racct->getMessage();
2060

    
2061
		// If we encounter an error immediately stop this function and go back
2062
		$racct->close();
2063
		return $retvalue;
2064
	}
2065
	// Send request
2066
	$result = $racct->send();
2067
	// Evaluation of the response
2068
	// 5 -> Accounting-Response
2069
	// See RFC2866 for this.
2070
	if (PEAR::isError($result)) {
2071
		$retvalue['acct_val'] = 1;
2072
		$retvalue['error'] = $result->getMessage();
2073
	} else if ($result === true) {
2074
		$retvalue['acct_val'] = 5 ;
2075
	} else {
2076
		$retvalue['acct_val'] = 1 ;
2077
	}
2078

    
2079
	$racct->close();
2080
	return $retvalue;
2081
}
2082
?>
(8-8/66)