Project

General

Profile

Download (70.2 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
			if (is_array($config['captiveportal'])) {
374
				foreach ($config['captiveportal'] as $cpkey => $cp) {
375
					if (isset($cp['enable'])) {
376
						$cpactive = true;
377
						break;
378
					}
379
				}
380
			}
381
			if ($cpactive === false)
382
				mwexec("/sbin/sysctl net.link.ether.ipfw=0");
383
				
384
		}
385
	}
386

    
387
	unlock($captiveportallck);
388
	
389
	return 0;
390
}
391

    
392
function captiveportal_init_webgui() {
393
	global $config, $cpzone;
394

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

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

    
412
function captiveportal_init_webgui_zone($cpcfg) {
413
	global $g, $config, $cpzone;
414

    
415
	if (!isset($cpcfg['enable']))
416
		return;
417

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

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

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

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

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

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

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

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

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

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

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

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

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

    
506
EOD;
507

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

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

    
544
	$listenporthttp =
545
		$config['captiveportal'][$cpzone]['listenporthttp'] ?
546
		$config['captiveportal'][$cpzone]['listenporthttp'] :
547
		$config['captiveportal'][$cpzone]['zoneid'];
548

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

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

    
563
EOD;
564

    
565
	/* generate passthru mac database */
566
	$cprules .= captiveportal_passthrumac_configure(true);
567
	$cprules .= "\n";
568

    
569
	/* allowed ipfw rules to make allowed ip work */
570
	$cprules .= captiveportal_allowedip_configure();
571

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

    
582
	if ($reinit == false)
583
		unlock($captiveportallck);
584
}
585

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

    
595
	if (empty($cpzone))
596
		return;
597

    
598
	$cpcfg = $config['captiveportal'][$cpzone];
599
	$vcpcfg = $config['voucher'][$cpzone];
600

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

    
607
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout']))
608
		$idletimeout = $cpcfg['idletimeout'] * 60;
609

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

    
615
	$radiussrvs = captiveportal_get_radius_servers();
616

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

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

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

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

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

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

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

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

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

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

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

    
760
	captiveportal_prune_old_automac();
761

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

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

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

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

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

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

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

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

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

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

    
882
}
883

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

    
888
	$radiusservers = captiveportal_get_radius_servers();
889

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

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

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

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

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

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

    
934
function captiveportal_passthrumac_configure_entry($macent) {
935

    
936
	$bwUp = empty($macent['bw_up']) ? 0 : $macent['bw_up'];
937
	$bwDown = empty($macent['bw_down']) ? 0 : $macent['bw_down'];
938

    
939
	$ruleno = captiveportal_get_next_ipfw_ruleno();
940
	$pipeno = captiveportal_get_next_dn_ruleno();
941

    
942
	$rules = "";
943
	$pipeup = $pipeno;
944
	$_gb = @pfSense_pipe_action("pipe {$pipeup} config bw {$bwUp}Kbit/s queue 100 buckets 16");
945
	$pipedown = $pipeno + 1;
946
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
947
	$rules .= "add {$ruleno} pipe {$pipeup} ip from any to any MAC {$macent['mac']} any\n";
948
	$ruleno++;
949
	$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC any {$macent['mac']}\n";
950

    
951
	return $rules;
952
}
953

    
954
function captiveportal_passthrumac_configure($lock = false) {
955
	global $config, $g, $cpzone;
956

    
957
	$rules = "";
958

    
959
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
960
		$macdb = array();
961
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
962
			$rules .= captiveportal_passthrumac_configure_entry($macent);
963
			$macdb[$macent['mac']][$cpzone]['active']  = true;
964

    
965
		}
966
	}
967

    
968
	return $rules;
969
}
970

    
971
function captiveportal_passthrumac_findbyname($username) {
972
	global $config, $cpzone;
973

    
974
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
975
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
976
			if ($macent['username'] == $username)
977
				return $macent;
978
		}
979
	}
980
	return NULL;
981
}
982

    
983
/* 
984
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
985
 */
986
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
987
	global $g;
988

    
989
	/*  Instead of copying this entire function for something
990
	 *  easy such as hostname vs ip address add this check
991
	 */
992
	if ($ishostname === true) {
993
		if (!$g['booting']) {
994
			$ipaddress = gethostbyname($ipent['hostname']);
995
			if (!is_ipaddr($ipaddress)) 
996
				return;
997
		} else
998
			$ipaddress = "";
999
	} else
1000
		$ipaddress = $ipent['ip'];
1001

    
1002
	$rules = "";
1003
	$cp_filterdns_conf = "";
1004
	$enBwup = empty($ipent['bw_up']) ? 0 : intval($ipent['bw_up']);
1005
	$enBwdown = empty($ipent['bw_down']) ? 0 : intval($ipent['bw_down']);
1006

    
1007
	$pipeno = captiveportal_get_next_dn_ruleno();
1008
	$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$enBwup}Kbit/s queue 100 buckets 16");
1009
	$pipedown = $pipeno + 1;
1010
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16");
1011
	if ($ishostname === true) {
1012
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
1013
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
1014
		if (!is_ipaddr($ipaddress))
1015
			return array("", $cp_filterdns_conf);
1016
	}
1017
	$subnet = "";
1018
	if (!empty($ipent['sn']))
1019
		$subnet = "/{$ipent['sn']}";
1020
	$rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
1021
	$rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
1022

    
1023
	if ($ishostname === true)
1024
		return array($rules, $cp_filterdns_conf);
1025
	else
1026
		return $rules;
1027
}
1028

    
1029
function captiveportal_allowedhostname_configure() {
1030
	global $config, $g, $cpzone;
1031

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

    
1053
	return $rules;
1054
}
1055

    
1056
function captiveportal_allowedip_configure() {
1057
	global $config, $g, $cpzone;
1058

    
1059
	$rules = "";
1060
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1061
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1062
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1063
	}
1064

    
1065
	return $rules;
1066
}
1067

    
1068
/* get last activity timestamp given client IP address */
1069
function captiveportal_get_last_activity($ip) {
1070
	global $cpzone;
1071

    
1072
	$ipfwoutput = pfSense_ipfw_getTablestats($cpzone, 1, $ip);
1073
	/* Reading only from one of the tables is enough of approximation. */
1074
	if (is_array($ipfwoutput)) {
1075
		return $ipfwoutput['timestamp'];
1076
	}
1077

    
1078
	return 0;
1079
}
1080

    
1081
function captiveportal_init_radius_servers() {
1082
	global $config, $g, $cpzone;
1083

    
1084
	/* generate radius server database */
1085
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1086
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1087
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1088
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1089
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1090
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1091

    
1092
		if ($config['captiveportal'][$cpzone]['radiusport'])
1093
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1094
		else
1095
			$radiusport = 1812;
1096
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1097
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1098
		else
1099
			$radiusacctport = 1813;
1100
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1101
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1102
		else
1103
			$radiusport2 = 1812;
1104
		if ($config['captiveportal'][$cpzone]['radiusport3'])
1105
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1106
		else
1107
			$radiusport3 = 1812;
1108
		if ($config['captiveportal'][$cpzone]['radiusport4'])
1109
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1110
		else
1111
			$radiusport4 = 1812;
1112

    
1113
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1114
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1115
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1116
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1117

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

    
1135
		fclose($fd);
1136
		unlock($cprdsrvlck);
1137
	}
1138
}
1139

    
1140
/* read RADIUS servers into array */
1141
function captiveportal_get_radius_servers() {
1142
	global $g, $cpzone;
1143

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

    
1171
	unlock($cprdsrvlck);
1172
	return false;
1173
}
1174

    
1175
/* log successful captive portal authentication to syslog */
1176
/* part of this code from php.net */
1177
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1178
	// Log it
1179
	if (!$message)
1180
		$message = "$status: $user, $mac, $ip";
1181
	else {
1182
		$message = trim($message);
1183
		$message = "$status: $user, $mac, $ip, $message";
1184
	}
1185
	captiveportal_syslog($message);
1186
}
1187

    
1188
/* log simple messages to syslog */
1189
function captiveportal_syslog($message) {
1190
	$message = trim($message);
1191
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1192
	// Log it
1193
	syslog(LOG_INFO, $message);
1194
	closelog();
1195
}
1196

    
1197
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1198
	global $g, $config;
1199

    
1200
	$pipeno = captiveportal_get_next_dn_ruleno();
1201

    
1202
	/* If the pool is empty, return appropriate message and fail authentication */
1203
	if (is_null($pipeno)) {
1204
		$auth_list = array();
1205
		$auth_list['auth_val'] = 1;
1206
		$auth_list['error'] = "System reached maximum login capacity";
1207
		return $auth_list;
1208
	}
1209

    
1210
	$radiusservers = captiveportal_get_radius_servers();
1211

    
1212
	if (is_null($radiusctx))
1213
		$radiusctx = 'first';
1214

    
1215
	$auth_list = RADIUS_AUTHENTICATION($username,
1216
		$password,
1217
		$radiusservers[$radiusctx],
1218
		$clientip,
1219
		$clientmac,
1220
		$pipeno);
1221

    
1222
	if ($auth_list['auth_val'] == 2) {
1223
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1224
		$sessionid = portal_allow($clientip,
1225
			$clientmac,
1226
			$username,
1227
			$password,
1228
			$auth_list,
1229
			$pipeno,
1230
			$radiusctx);
1231
	}
1232

    
1233
	return $auth_list;
1234
}
1235

    
1236
function captiveportal_opendb() {
1237
	global $g, $cpzone;
1238

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

    
1254
	return $DB;
1255
}
1256

    
1257
/* read captive portal DB into array */
1258
function captiveportal_read_db($query = "") {
1259

    
1260
	$DB = captiveportal_opendb();
1261
	if ($DB) {
1262
		sqlite_exec($DB, "BEGIN");
1263
		if (!empty($query))
1264
			$cpdb = @sqlite_array_query($DB, "SELECT * FROM captiveportal {$query}", SQLITE_NUM);
1265
		else {
1266
			$response = @sqlite_unbuffered_query($DB, "SELECT * FROM captiveportal", SQLITE_NUM);
1267
			$cpdb = @sqlite_fetch_all($response, SQLITE_NUM);
1268
		}
1269
		sqlite_exec($DB, "END");
1270
		@sqlite_close($DB);
1271
	}
1272
	if (!$cpdb)
1273
		$cpdb = array();
1274

    
1275
	return $cpdb;
1276
}
1277

    
1278
function captiveportal_remove_entries($remove) {
1279

    
1280
	if (!is_array($remove) || empty($remove))
1281
		return;
1282

    
1283
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1284
	foreach($remove as $idx => $unindex) {
1285
		$query .= "'{$unindex}'";
1286
		if ($idx < (count($remove) - 1))
1287
			$query .= ",";
1288
	}
1289
	$query .= ")";
1290
	captiveportal_write_db($query);
1291
}
1292

    
1293
/* write captive portal DB */
1294
function captiveportal_write_db($queries) {
1295
	global $g;
1296

    
1297
	if (is_array($queries))
1298
		$query = implode(";", $queries);
1299
	else
1300
		$query = $queries;
1301

    
1302
	$DB = captiveportal_opendb();
1303
	if ($DB) {
1304
		$error_msg = "";
1305
		sqlite_exec($DB, "BEGIN TRANSACTION");
1306
		$result = @sqlite_exec($DB, $query, $error_msg);
1307
		if (!$result)
1308
			captiveportal_syslog("Trying to modify DB returned error: {$error_msg}");
1309
		else
1310
			sqlite_exec($DB, "END TRANSACTION");
1311
		@sqlite_close($DB);
1312
		return $result;
1313
	} else
1314
		return true;
1315
}
1316

    
1317
function captiveportal_write_elements() {
1318
	global $g, $config, $cpzone;
1319
	
1320
	$cpcfg = $config['captiveportal'][$cpzone];
1321

    
1322
	/* delete any existing elements */
1323
	if (is_dir($g['captiveportal_element_path'])) {
1324
		$dh = opendir($g['captiveportal_element_path']);
1325
		while (($file = readdir($dh)) !== false) {
1326
			if ($file != "." && $file != "..")
1327
				unlink($g['captiveportal_element_path'] . "/" . $file);
1328
		}
1329
		closedir($dh);
1330
	} else {
1331
		@mkdir($g['captiveportal_element_path']);
1332
	}
1333

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

    
1350
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1351
	global $config, $g;
1352

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

    
1376
	return $ruleno;
1377
}
1378

    
1379
function captiveportal_free_dn_ruleno($ruleno) {
1380
	global $config, $g;
1381

    
1382
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1383
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1384
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1385
		$rules[$ruleno] = false;
1386
		$rules[++$ruleno] = false;
1387
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1388
	}
1389
	unlock($cpruleslck);
1390
}
1391

    
1392
function captiveportal_get_dn_passthru_ruleno($value) {
1393
	global $config, $g, $cpzone;
1394

    
1395
	$cpcfg = $config['captiveportal'][$cpzone];
1396
	if(!isset($cpcfg['enable']))
1397
		return NULL;
1398

    
1399
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1400
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1401
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1402
		$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`);
1403
		if ($rules[$ruleno]) {
1404
			unlock($cpruleslck);
1405
			return $ruleno;
1406
		}
1407
	}
1408

    
1409
	unlock($cpruleslck);
1410
	return NULL;
1411
}
1412

    
1413
/*
1414
 * This function will calculate the lowest free firewall ruleno
1415
 * within the range specified based on the actual logged on users
1416
 *
1417
 */
1418
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1419
	global $config, $g, $cpzone;
1420

    
1421
	$cpcfg = $config['captiveportal'][$cpzone];
1422
	if(!isset($cpcfg['enable']))
1423
		return NULL;
1424

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

    
1454
function captiveportal_free_ipfw_ruleno($ruleno) {
1455
	global $config, $g, $cpzone;
1456

    
1457
	$cpcfg = $config['captiveportal'][$cpzone];
1458
	if(!isset($cpcfg['enable']))
1459
		return NULL;
1460

    
1461
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1462
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1463
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1464
		$rules[$ruleno] = false;
1465
		$rules[++$ruleno] = false;
1466
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1467
	}
1468
	unlock($cpruleslck);
1469
}
1470

    
1471
function captiveportal_get_ipfw_passthru_ruleno($value) {
1472
	global $config, $g, $cpzone;
1473

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

    
1478
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1479
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1480
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1481
		$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`);
1482
		if ($rules[$ruleno]) {
1483
			unlock($cpruleslck);
1484
			return $ruleno;
1485
		}
1486
	}
1487

    
1488
	unlock($cpruleslck);
1489
	return NULL;
1490
}
1491

    
1492
/**
1493
 * This function will calculate the traffic produced by a client
1494
 * based on its firewall rule
1495
 *
1496
 * Point of view: NAS
1497
 *
1498
 * Input means: from the client
1499
 * Output means: to the client
1500
 *
1501
 */
1502

    
1503
function getVolume($ip) {
1504
	global $config, $cpzone;
1505

    
1506
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1507
	$volume = array();
1508
	// Initialize vars properly, since we don't want NULL vars
1509
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1510

    
1511
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 1, $ip);
1512
	if (is_array($ipfw)) {
1513
		if ($reverse) {
1514
			$volume['output_pkts'] = $ipfw['packets'];
1515
			$volume['output_bytes'] = $ipfw['bytes'];
1516
		}
1517
		else {
1518
			$volume['input_pkts'] = $ipfw['packets'];
1519
			$volume['input_bytes'] = $ipfw['bytes'];
1520
		}
1521
	}
1522

    
1523
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 2, $ip);
1524
	if (is_array($ipfw)) {
1525
		if ($reverse) {
1526
			$volume['input_pkts'] = $ipfw['packets'];
1527
			$volume['input_bytes'] = $ipfw['bytes'];
1528
		}
1529
		else {
1530
			$volume['output_pkts'] = $ipfw['packets'];
1531
			$volume['output_bytes'] = $ipfw['bytes'];
1532
		}
1533
	}
1534

    
1535
	return $volume;
1536
}
1537

    
1538
/**
1539
 * Get the NAS-IP-Address based on the current wan address
1540
 *
1541
 * Use functions in interfaces.inc to find this out
1542
 *
1543
 */
1544

    
1545
function getNasIP()
1546
{
1547
	global $config, $cpzone;
1548

    
1549
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1550
			$nasIp = get_interface_ip();
1551
	} else {
1552
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1553
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1554
		else
1555
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1556
	}
1557
		
1558
	if(!is_ipaddr($nasIp))
1559
		$nasIp = "0.0.0.0";
1560

    
1561
	return $nasIp;
1562
}
1563

    
1564
function portal_ip_from_client_ip($cliip) {
1565
	global $config, $cpzone;
1566

    
1567
	$isipv6 = is_ipaddrv6($cliip);
1568
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1569
	foreach ($interfaces as $cpif) {
1570
		if ($isipv6) {
1571
			$ip = get_interface_ipv6($cpif);
1572
			$sn = get_interface_subnetv6($cpif);
1573
		} else {
1574
			$ip = get_interface_ip($cpif);
1575
			$sn = get_interface_subnet($cpif);
1576
		}
1577
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1578
			return $ip;
1579
	}
1580

    
1581
	$inet = ($isipv6) ? '-inet6' : '-inet';
1582
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1583
	$iface = trim($iface, "\n");
1584
	if (!empty($iface)) {
1585
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1586
		if (is_ipaddr($ip))
1587
			return $ip;
1588
	}
1589

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

    
1600
	return false;
1601
}
1602

    
1603
function portal_hostname_from_client_ip($cliip) {
1604
	global $config, $cpzone;
1605

    
1606
	$cpcfg = $config['captiveportal'][$cpzone];
1607

    
1608
	if (isset($cpcfg['httpslogin'])) {
1609
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
1610
		$ourhostname = $cpcfg['httpsname'];
1611
		
1612
		if ($listenporthttps != 443)
1613
			$ourhostname .= ":" . $listenporthttps;
1614
	} else {
1615
		$listenporthttp  = $cpcfg['listenporthttp']  ? $cpcfg['listenporthttp']  : $cpcfg['zoneid'];
1616
		$ifip = portal_ip_from_client_ip($cliip);
1617
		if (!$ifip)
1618
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1619
		else
1620
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1621
		
1622
		if ($listenporthttp != 80)
1623
			$ourhostname .= ":" . $listenporthttp;
1624
	}
1625
	
1626
	return $ourhostname;
1627
}
1628

    
1629
/* functions move from index.php */
1630

    
1631
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1632
	global $g, $config, $cpzone;
1633

    
1634
	/* Get captive portal layout */
1635
	if ($type == "redir") {
1636
		header("Location: {$redirurl}");
1637
		return;
1638
	} else if ($type == "login")
1639
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1640
	else
1641
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1642

    
1643
	$cpcfg = $config['captiveportal'][$cpzone];
1644

    
1645
	/* substitute the PORTAL_REDIRURL variable */
1646
	if ($cpcfg['preauthurl']) {
1647
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1648
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1649
	}
1650

    
1651
	/* substitute other variables */
1652
	$ourhostname = portal_hostname_from_client_ip($clientip);
1653
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1654
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1655
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1656

    
1657
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1658
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1659
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1660
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1661
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1662

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

    
1674
	echo $htmltext;
1675
}
1676

    
1677
function portal_mac_radius($clientmac,$clientip) {
1678
	global $config, $cpzone;
1679

    
1680
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1681

    
1682
	/* authentication against the radius server */
1683
	$username = mac_format($clientmac);
1684
	$auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1685
	if ($auth_list['auth_val'] == 2)
1686
		return TRUE;
1687

    
1688
	if (!empty($auth_list['url_redirection']))
1689
		portal_reply_page($auth_list['url_redirection'], "redir");
1690

    
1691
	return FALSE;
1692
}
1693

    
1694
function captiveportal_reapply_attributes($cpentry, $attributes) {
1695
	global $config, $cpzone, $g;
1696

    
1697
	$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1698
	$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1699
	$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1700
	$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1701
	$bw_up_pipeno = $cpentry[1];
1702
	$bw_down_pipeno = $cpentry[1]+1;
1703

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

    
1708
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1709
}
1710

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

    
1714
	// Ensure we create an array if we are missing attributes
1715
	if (!is_array($attributes))
1716
		$attributes = array();
1717

    
1718
	unset($sessionid);
1719

    
1720
	/* Do not allow concurrent login execution. */
1721
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1722

    
1723
	if ($attributes['voucher'])
1724
		$remaining_time = $attributes['session_timeout'];
1725

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

    
1771
	/* read in client database */
1772
	$query = "WHERE ip = '{$clientip}'";
1773
	$tmpusername = strtolower($username);
1774
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins']))
1775
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
1776
	$cpdb = captiveportal_read_db($query);
1777

    
1778
	/* Snapshot the timestamp */
1779
	$allow_time = time();
1780
	$radiusservers = captiveportal_get_radius_servers();
1781
	$unsetindexes = array();
1782
	if (is_null($radiusctx))
1783
		$radiusctx = 'first';
1784

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

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

    
1823
	if (!empty($unsetindexes))
1824
		captiveportal_remove_entries($unsetindexes);
1825

    
1826
	if ($attributes['voucher'] && $remaining_time <= 0)
1827
		return 0;       // voucher already used and no time left
1828

    
1829
	if (!isset($sessionid)) {
1830
		/* generate unique session ID */
1831
		$tod = gettimeofday();
1832
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1833

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

    
1861
			/* if the pool is empty, return appropriate message and exit */
1862
			if (is_null($pipeno)) {
1863
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1864
				log_error("WARNING!  Captive portal has reached maximum login capacity");
1865
				unlock($cpdblck);
1866
				return;
1867
			}
1868

    
1869
			$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1870
			$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1871
			$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1872
			$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1873

    
1874
			$bw_up_pipeno = $pipeno;
1875
			$bw_down_pipeno = $pipeno + 1;
1876
			//$bw_up /= 1000; // Scale to Kbit/s
1877
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1878
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1879

    
1880
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
1881
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1882
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
1883
			else
1884
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
1885

    
1886
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1887
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
1888
			else
1889
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
1890

    
1891
			if ($attributes['voucher'])
1892
				$attributes['session_timeout'] = $remaining_time;
1893
			
1894
			/* handle empty attributes */
1895
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
1896
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
1897
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
1898
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
1899

    
1900
			/* escape username */
1901
			$safe_username = sqlite_escape_string($username);
1902

    
1903
			/* encode password in Base64 just in case it contains commas */
1904
			$bpassword = base64_encode($password);
1905
			$insertquery  = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval) ";
1906
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
1907
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval})";
1908

    
1909
			/* store information to database */
1910
			captiveportal_write_db($insertquery);
1911
			unlock($cpdblck);
1912

    
1913
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
1914
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
1915
				if ($acct_val == 1)
1916
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1917
			}
1918
		}
1919
	} else
1920
		unlock($cpdblck);
1921

    
1922
	if ($writecfg == true)
1923
		write_config();
1924

    
1925
	/* redirect user to desired destination */
1926
	if (!empty($attributes['url_redirection']))
1927
		$my_redirurl = $attributes['url_redirection'];
1928
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
1929
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
1930
	else
1931
		$my_redirurl = $redirurl;
1932

    
1933
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
1934
		$ourhostname = portal_hostname_from_client_ip($clientip);
1935
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
1936
		$logouturl = "{$protocol}{$ourhostname}/";
1937

    
1938
		if (isset($attributes['reply_message']))
1939
			$message = $attributes['reply_message'];
1940
		else
1941
			$message = 0;
1942

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

    
1945
	} else {
1946
		header("Location: " . $my_redirurl);
1947
	}
1948

    
1949
	return $sessionid;
1950
}
1951

    
1952

    
1953
/*
1954
 * Used for when pass-through credits are enabled.
1955
 * Returns true when there was at least one free login to deduct for the MAC.
1956
 * Expired entries are removed as they are seen.
1957
 * Active entries are updated according to the configuration.
1958
 */
1959
function portal_consume_passthrough_credit($clientmac) {
1960
	global $config, $cpzone;
1961

    
1962
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
1963
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
1964
	else
1965
		return false;
1966

    
1967
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
1968
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
1969
	else
1970
		return false;
1971

    
1972
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1973
		return false;
1974

    
1975
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
1976

    
1977
	/*
1978
	 * Read database of used MACs.  Lines are a comma-separated list
1979
	 * of the time, MAC, then the count of pass-through credits remaining.
1980
	 */
1981
	$usedmacs = captiveportal_read_usedmacs_db();
1982

    
1983
	$currenttime = time();
1984
	$found = false;
1985
	foreach ($usedmacs as $key => $usedmac) {
1986
		$usedmac = explode(",", $usedmac);
1987

    
1988
		if ($usedmac[1] == $clientmac) {
1989
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
1990
				if ($usedmac[2] < 1) {
1991
					if ($updatetimeouts) {
1992
						$usedmac[0] = $currenttime;
1993
						unset($usedmacs[$key]);
1994
						$usedmacs[] = implode(",", $usedmac);
1995
						captiveportal_write_usedmacs_db($usedmacs);
1996
					}
1997

    
1998
					return false;
1999
				} else {
2000
					$usedmac[2] -= 1;
2001
					$usedmacs[$key] = implode(",", $usedmac);
2002
				}
2003

    
2004
				$found = true;
2005
			} else
2006
				unset($usedmacs[$key]);
2007

    
2008
			break;
2009
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2010
				unset($usedmacs[$key]);
2011
	}
2012

    
2013
	if (!$found) {
2014
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2015
		$usedmacs[] = implode(",", $usedmac);
2016
	}
2017

    
2018
	captiveportal_write_usedmacs_db($usedmacs);
2019
	return true;
2020
}
2021

    
2022
function captiveportal_read_usedmacs_db() {
2023
	global $g, $cpzone;
2024

    
2025
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2026
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2027
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2028
		if (!$usedmacs)
2029
			$usedmacs = array();
2030
	} else
2031
		$usedmacs = array();
2032

    
2033
	unlock($cpumaclck);
2034
	return $usedmacs;
2035
}
2036

    
2037
function captiveportal_write_usedmacs_db($usedmacs) {
2038
	global $g, $cpzone;
2039

    
2040
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2041
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2042
	unlock($cpumaclck);
2043
}
2044

    
2045
function captiveportal_send_server_accounting($off = false) {
2046
	global $cpzone, $config;
2047

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

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

    
2086
	$racct->close();
2087
	return $retvalue;
2088
}
2089
?>
(8-8/66)