Project

General

Profile

Download (70.7 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
	}
157
	/* Activate layer2 filtering */
158
	mwexec("/sbin/sysctl net.link.ether.ipfw=1 net.inet.ip.fw.one_pass=1");
159

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
309
EOD;
310
		}
311

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

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

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

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

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

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

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

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

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

    
355
		captiveportal_radius_stop_all();
356

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
509
EOD;
510

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

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

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

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

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

    
566
EOD;
567

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

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

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

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

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

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

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

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

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

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

    
618
	$radiussrvs = captiveportal_get_radius_servers();
619

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

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

    
640
		$timedout = false;
641
		$term_cause = 1;
642
		if (empty($cpentry[10]))
643
			$cpentry[10] = 'first';
644
		$radiusservers = $radiussrvs[$cpentry[10]];
645

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

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

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

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

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

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

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

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

    
763
	captiveportal_prune_old_automac();
764

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

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

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

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

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

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

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

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

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

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

    
885
}
886

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

    
891
	$radiusservers = captiveportal_get_radius_servers();
892

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

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

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

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

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

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

    
937
function captiveportal_passthrumac_configure_entry($macent) {
938

    
939
	$bwUp = empty($macent['bw_up']) ? 0 : $macent['bw_up'];
940
	$bwDown = empty($macent['bw_down']) ? 0 : $macent['bw_down'];
941

    
942
	$ruleno = captiveportal_get_next_ipfw_ruleno();
943
	$pipeno = captiveportal_get_next_dn_ruleno();
944

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

    
954
	return $rules;
955
}
956

    
957
function captiveportal_passthrumac_configure($lock = false) {
958
	global $config, $g, $cpzone;
959

    
960
	$rules = "";
961

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

    
968
		}
969
	}
970

    
971
	return $rules;
972
}
973

    
974
function captiveportal_passthrumac_findbyname($username) {
975
	global $config, $cpzone;
976

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

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

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

    
1005
	$rules = "";
1006
	$cp_filterdns_conf = "";
1007
	$enBwup = empty($ipent['bw_up']) ? 0 : intval($ipent['bw_up']);
1008
	$enBwdown = empty($ipent['bw_down']) ? 0 : intval($ipent['bw_down']);
1009

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

    
1026
	if ($ishostname === true)
1027
		return array($rules, $cp_filterdns_conf);
1028
	else
1029
		return $rules;
1030
}
1031

    
1032
function captiveportal_allowedhostname_configure() {
1033
	global $config, $g, $cpzone;
1034

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

    
1056
	return $rules;
1057
}
1058

    
1059
function captiveportal_allowedip_configure() {
1060
	global $config, $g, $cpzone;
1061

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

    
1068
	return $rules;
1069
}
1070

    
1071
/* get last activity timestamp given client IP address */
1072
function captiveportal_get_last_activity($ip) {
1073
	global $cpzone;
1074

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

    
1081
	return 0;
1082
}
1083

    
1084
function captiveportal_init_radius_servers() {
1085
	global $config, $g, $cpzone;
1086

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

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

    
1116
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1117
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1118
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1119
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1120

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

    
1138
		fclose($fd);
1139
		unlock($cprdsrvlck);
1140
	}
1141
}
1142

    
1143
/* read RADIUS servers into array */
1144
function captiveportal_get_radius_servers() {
1145
	global $g, $cpzone;
1146

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

    
1174
	unlock($cprdsrvlck);
1175
	return false;
1176
}
1177

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

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

    
1200
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1201
	global $g, $config;
1202

    
1203
	$pipeno = captiveportal_get_next_dn_ruleno();
1204

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

    
1213
	$radiusservers = captiveportal_get_radius_servers();
1214

    
1215
	if (is_null($radiusctx))
1216
		$radiusctx = 'first';
1217

    
1218
	$auth_list = RADIUS_AUTHENTICATION($username,
1219
		$password,
1220
		$radiusservers[$radiusctx],
1221
		$clientip,
1222
		$clientmac,
1223
		$pipeno);
1224

    
1225
	if ($auth_list['auth_val'] == 2) {
1226
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1227
		$sessionid = portal_allow($clientip,
1228
			$clientmac,
1229
			$username,
1230
			$password,
1231
			$auth_list,
1232
			$pipeno,
1233
			$radiusctx);
1234
	} else {
1235
	         captiveportal_free_dn_ruleno($pipeno);
1236
	       }
1237

    
1238
	return $auth_list;
1239
}
1240

    
1241
function captiveportal_opendb() {
1242
	global $g, $cpzone;
1243

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

    
1259
	return $DB;
1260
}
1261

    
1262
/* read captive portal DB into array */
1263
function captiveportal_read_db($query = "") {
1264

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

    
1280
	return $cpdb;
1281
}
1282

    
1283
function captiveportal_remove_entries($remove) {
1284

    
1285
	if (!is_array($remove) || empty($remove))
1286
		return;
1287

    
1288
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1289
	foreach($remove as $idx => $unindex) {
1290
		$query .= "'{$unindex}'";
1291
		if ($idx < (count($remove) - 1))
1292
			$query .= ",";
1293
	}
1294
	$query .= ")";
1295
	captiveportal_write_db($query);
1296
}
1297

    
1298
/* write captive portal DB */
1299
function captiveportal_write_db($queries) {
1300
	global $g;
1301

    
1302
	if (is_array($queries))
1303
		$query = implode(";", $queries);
1304
	else
1305
		$query = $queries;
1306

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

    
1322
function captiveportal_write_elements() {
1323
	global $g, $config, $cpzone;
1324
	
1325
	$cpcfg = $config['captiveportal'][$cpzone];
1326

    
1327
	if (!is_dir($g['captiveportal_element_path']))
1328
		@mkdir($g['captiveportal_element_path']);
1329

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

    
1346
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1347
	global $cpzone;
1348

    
1349
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1350
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1351
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1352
		for ($ridx = $rulenos_start; $ridx < $rulenos_range_max; $ridx++) {
1353
			if ($rules[$ridx] == $cpzone) {
1354
				$rules[$ridx] = false;
1355
				$ridx++;
1356
				$rules[$ridx] = false;
1357
			}
1358
		}
1359
	}
1360
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1361
	unlock($cpruleslck);
1362
}
1363

    
1364
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1365
	global $config, $g, $cpzone;
1366

    
1367
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1368
	$ruleno = 0;
1369
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1370
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1371
		for ($ridx = $rulenos_start; $ridx < $rulenos_range_max; $ridx++) {
1372
			if ($rules[$ridx]) {
1373
				$ridx++;
1374
				continue;
1375
			}
1376
			$ruleno = $ridx;
1377
			$rules[$ridx] = $cpzone;
1378
			$rules[++$ridx] = $cpzone;
1379
			break;
1380
		}
1381
	} else {
1382
		$rules = array_pad(array(), $rulenos_range_max, false);
1383
		$ruleno = $rulenos_start;
1384
		$rules[$rulenos_start] = $cpzone;
1385
		$rules[++$rulenos_start] = $cpzone;
1386
	}
1387
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1388
	unlock($cpruleslck);
1389

    
1390
	return $ruleno;
1391
}
1392

    
1393
function captiveportal_free_dn_ruleno($ruleno) {
1394
	global $config, $g;
1395

    
1396
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1397
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1398
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1399
		$rules[$ruleno] = false;
1400
		$rules[++$ruleno] = false;
1401
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1402
	}
1403
	unlock($cpruleslck);
1404
}
1405

    
1406
function captiveportal_get_dn_passthru_ruleno($value) {
1407
	global $config, $g, $cpzone;
1408

    
1409
	$cpcfg = $config['captiveportal'][$cpzone];
1410
	if(!isset($cpcfg['enable']))
1411
		return NULL;
1412

    
1413
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1414
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1415
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1416
		$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`);
1417
		if ($rules[$ruleno]) {
1418
			unlock($cpruleslck);
1419
			return $ruleno;
1420
		}
1421
	}
1422

    
1423
	unlock($cpruleslck);
1424
	return NULL;
1425
}
1426

    
1427
/*
1428
 * This function will calculate the lowest free firewall ruleno
1429
 * within the range specified based on the actual logged on users
1430
 *
1431
 */
1432
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1433
	global $config, $g, $cpzone;
1434

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

    
1439
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1440
	$ruleno = 0;
1441
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1442
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1443
		for ($ridx = 2; $ridx < ($rulenos_range_max - $rulenos_start); $ridx++) {
1444
			if ($rules[$ridx]) {
1445
				/* 
1446
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1447
				 * and the out pipe ruleno + 1.
1448
				 */
1449
				$ridx++;
1450
				continue;
1451
			}
1452
			$ruleno = $ridx;
1453
			$rules[$ridx] = "used";
1454
			$rules[++$ridx] = "used";
1455
			break;
1456
		}
1457
	} else {
1458
		$rules = array_pad(array(), $rulenos_range_max, false);
1459
		$rules[$rulenos_start] = "used";
1460
		$rules[++$rulenos_start] = "used";
1461
		$ruleno = 2;
1462
	}
1463
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1464
	unlock($cpruleslck);
1465
	return $ruleno;
1466
}
1467

    
1468
function captiveportal_free_ipfw_ruleno($ruleno) {
1469
	global $config, $g, $cpzone;
1470

    
1471
	$cpcfg = $config['captiveportal'][$cpzone];
1472
	if(!isset($cpcfg['enable']))
1473
		return NULL;
1474

    
1475
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1476
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1477
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1478
		$rules[$ruleno] = false;
1479
		$rules[++$ruleno] = false;
1480
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1481
	}
1482
	unlock($cpruleslck);
1483
}
1484

    
1485
function captiveportal_get_ipfw_passthru_ruleno($value) {
1486
	global $config, $g, $cpzone;
1487

    
1488
	$cpcfg = $config['captiveportal'][$cpzone];
1489
	if(!isset($cpcfg['enable']))
1490
		return NULL;
1491

    
1492
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1493
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1494
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1495
		$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`);
1496
		if ($rules[$ruleno]) {
1497
			unlock($cpruleslck);
1498
			return $ruleno;
1499
		}
1500
	}
1501

    
1502
	unlock($cpruleslck);
1503
	return NULL;
1504
}
1505

    
1506
/**
1507
 * This function will calculate the traffic produced by a client
1508
 * based on its firewall rule
1509
 *
1510
 * Point of view: NAS
1511
 *
1512
 * Input means: from the client
1513
 * Output means: to the client
1514
 *
1515
 */
1516

    
1517
function getVolume($ip) {
1518
	global $config, $cpzone;
1519

    
1520
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1521
	$volume = array();
1522
	// Initialize vars properly, since we don't want NULL vars
1523
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1524

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

    
1537
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 2, $ip);
1538
	if (is_array($ipfw)) {
1539
		if ($reverse) {
1540
			$volume['input_pkts'] = $ipfw['packets'];
1541
			$volume['input_bytes'] = $ipfw['bytes'];
1542
		}
1543
		else {
1544
			$volume['output_pkts'] = $ipfw['packets'];
1545
			$volume['output_bytes'] = $ipfw['bytes'];
1546
		}
1547
	}
1548

    
1549
	return $volume;
1550
}
1551

    
1552
/**
1553
 * Get the NAS-IP-Address based on the current wan address
1554
 *
1555
 * Use functions in interfaces.inc to find this out
1556
 *
1557
 */
1558

    
1559
function getNasIP()
1560
{
1561
	global $config, $cpzone;
1562

    
1563
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1564
			$nasIp = get_interface_ip();
1565
	} else {
1566
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1567
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1568
		else
1569
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1570
	}
1571
		
1572
	if(!is_ipaddr($nasIp))
1573
		$nasIp = "0.0.0.0";
1574

    
1575
	return $nasIp;
1576
}
1577

    
1578
function portal_ip_from_client_ip($cliip) {
1579
	global $config, $cpzone;
1580

    
1581
	$isipv6 = is_ipaddrv6($cliip);
1582
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1583
	foreach ($interfaces as $cpif) {
1584
		if ($isipv6) {
1585
			$ip = get_interface_ipv6($cpif);
1586
			$sn = get_interface_subnetv6($cpif);
1587
		} else {
1588
			$ip = get_interface_ip($cpif);
1589
			$sn = get_interface_subnet($cpif);
1590
		}
1591
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1592
			return $ip;
1593
	}
1594

    
1595
	$inet = ($isipv6) ? '-inet6' : '-inet';
1596
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1597
	$iface = trim($iface, "\n");
1598
	if (!empty($iface)) {
1599
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1600
		if (is_ipaddr($ip))
1601
			return $ip;
1602
	}
1603

    
1604
	// doesn't match up to any particular interface
1605
	// so let's set the portal IP to what PHP says 
1606
	// the server IP issuing the request is. 
1607
	// allows same behavior as 1.2.x where IP isn't 
1608
	// in the subnet of any CP interface (static routes, etc.)
1609
	// rather than forcing to DNS hostname resolution
1610
	$ip = $_SERVER['SERVER_ADDR'];
1611
	if (is_ipaddr($ip))
1612
		return $ip;
1613

    
1614
	return false;
1615
}
1616

    
1617
function portal_hostname_from_client_ip($cliip) {
1618
	global $config, $cpzone;
1619

    
1620
	$cpcfg = $config['captiveportal'][$cpzone];
1621

    
1622
	if (isset($cpcfg['httpslogin'])) {
1623
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
1624
		$ourhostname = $cpcfg['httpsname'];
1625
		
1626
		if ($listenporthttps != 443)
1627
			$ourhostname .= ":" . $listenporthttps;
1628
	} else {
1629
		$listenporthttp  = $cpcfg['listenporthttp']  ? $cpcfg['listenporthttp']  : $cpcfg['zoneid'];
1630
		$ifip = portal_ip_from_client_ip($cliip);
1631
		if (!$ifip)
1632
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1633
		else
1634
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1635
		
1636
		if ($listenporthttp != 80)
1637
			$ourhostname .= ":" . $listenporthttp;
1638
	}
1639
	
1640
	return $ourhostname;
1641
}
1642

    
1643
/* functions move from index.php */
1644

    
1645
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1646
	global $g, $config, $cpzone;
1647

    
1648
	/* Get captive portal layout */
1649
	if ($type == "redir") {
1650
		header("Location: {$redirurl}");
1651
		return;
1652
	} else if ($type == "login")
1653
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1654
	else
1655
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1656

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

    
1659
	/* substitute the PORTAL_REDIRURL variable */
1660
	if ($cpcfg['preauthurl']) {
1661
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1662
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1663
	}
1664

    
1665
	/* substitute other variables */
1666
	$ourhostname = portal_hostname_from_client_ip($clientip);
1667
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1668
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1669
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1670

    
1671
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1672
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1673
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1674
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1675
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1676

    
1677
	// Special handling case for captive portal master page so that it can be ran 
1678
	// through the PHP interpreter using the include method above.  We convert the
1679
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1680
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1681
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1682
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1683
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1684
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1685
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1686
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1687

    
1688
	echo $htmltext;
1689
}
1690

    
1691
function portal_mac_radius($clientmac,$clientip) {
1692
	global $config, $cpzone;
1693

    
1694
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1695

    
1696
	/* authentication against the radius server */
1697
	$username = mac_format($clientmac);
1698
	$auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1699
	if ($auth_list['auth_val'] == 2)
1700
		return TRUE;
1701

    
1702
	if (!empty($auth_list['url_redirection']))
1703
		portal_reply_page($auth_list['url_redirection'], "redir");
1704

    
1705
	return FALSE;
1706
}
1707

    
1708
function captiveportal_reapply_attributes($cpentry, $attributes) {
1709
	global $config, $cpzone, $g;
1710

    
1711
	$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1712
	$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1713
	$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1714
	$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1715
	$bw_up_pipeno = $cpentry[1];
1716
	$bw_down_pipeno = $cpentry[1]+1;
1717

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

    
1722
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1723
}
1724

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

    
1728
	// Ensure we create an array if we are missing attributes
1729
	if (!is_array($attributes))
1730
		$attributes = array();
1731

    
1732
	unset($sessionid);
1733

    
1734
	/* Do not allow concurrent login execution. */
1735
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1736

    
1737
	if ($attributes['voucher'])
1738
		$remaining_time = $attributes['session_timeout'];
1739

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

    
1785
	/* read in client database */
1786
	$query = "WHERE ip = '{$clientip}'";
1787
	$tmpusername = strtolower($username);
1788
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins']))
1789
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
1790
	$cpdb = captiveportal_read_db($query);
1791

    
1792
	/* Snapshot the timestamp */
1793
	$allow_time = time();
1794
	$radiusservers = captiveportal_get_radius_servers();
1795
	$unsetindexes = array();
1796
	if (is_null($radiusctx))
1797
		$radiusctx = 'first';
1798

    
1799
	foreach ($cpdb as $cpentry) {
1800
		if (empty($cpentry[10]))
1801
			$cpentry[10] = 'first';
1802
		/* on the same ip */
1803
		if ($cpentry[2] == $clientip) {
1804
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac)
1805
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1806
			else
1807
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
1808
			$sessionid = $cpentry[5];
1809
			break;
1810
		}
1811
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1812
			// user logged in with an active voucher. Check for how long and calculate 
1813
			// how much time we can give him (voucher credit - used time)
1814
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1815
			if ($remaining_time < 0)    // just in case. 
1816
				$remaining_time = 0;
1817

    
1818
			/* This user was already logged in so we disconnect the old one */
1819
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1820
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1821
			$unsetindexes[] = $cpentry[5];
1822
			break;
1823
		}
1824
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1825
			/* on the same username */
1826
			if (strcasecmp($cpentry[4], $username) == 0) {
1827
				/* This user was already logged in so we disconnect the old one */
1828
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1829
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1830
				$unsetindexes[] = $cpentry[5];
1831
				break;
1832
			}
1833
		}
1834
	}
1835
	unset($cpdb);
1836

    
1837
	if (!empty($unsetindexes))
1838
		captiveportal_remove_entries($unsetindexes);
1839

    
1840
	if ($attributes['voucher'] && $remaining_time <= 0)
1841
		return 0;       // voucher already used and no time left
1842

    
1843
	if (!isset($sessionid)) {
1844
		/* generate unique session ID */
1845
		$tod = gettimeofday();
1846
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1847

    
1848
		if ($passthrumac) {
1849
			$mac = array();
1850
			$mac['mac'] = $clientmac;
1851
			$mac['ip'] = $clientip; /* Used only for logging */
1852
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
1853
				$mac['username'] = $username;
1854
				if ($attributes['voucher'])
1855
					$mac['logintype'] = "voucher";
1856
			}
1857
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1858
			if (!empty($bw_up))
1859
				$mac['bw_up'] = $bw_up;
1860
			if (!empty($bw_down))
1861
				$mac['bw_down'] = $bw_down;
1862
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
1863
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
1864
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1865
			unlock($cpdblck);
1866
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1867
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1868
			mwexec("/sbin/ipfw -x {$cpzone}-q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1869
			$writecfg = true;
1870
		} else {
1871
			/* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
1872
			if (is_null($pipeno))
1873
				$pipeno = captiveportal_get_next_dn_ruleno();
1874

    
1875
			/* if the pool is empty, return appropriate message and exit */
1876
			if (is_null($pipeno)) {
1877
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1878
				log_error("WARNING!  Captive portal has reached maximum login capacity");
1879
				unlock($cpdblck);
1880
				return;
1881
			}
1882

    
1883
			$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1884
			$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1885
			$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1886
			$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1887

    
1888
			$bw_up_pipeno = $pipeno;
1889
			$bw_down_pipeno = $pipeno + 1;
1890
			//$bw_up /= 1000; // Scale to Kbit/s
1891
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1892
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1893

    
1894
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
1895
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1896
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
1897
			else
1898
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
1899

    
1900
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1901
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
1902
			else
1903
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
1904

    
1905
			if ($attributes['voucher'])
1906
				$attributes['session_timeout'] = $remaining_time;
1907
			
1908
			/* handle empty attributes */
1909
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
1910
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
1911
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
1912
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
1913

    
1914
			/* escape username */
1915
			$safe_username = sqlite_escape_string($username);
1916

    
1917
			/* encode password in Base64 just in case it contains commas */
1918
			$bpassword = base64_encode($password);
1919
			$insertquery  = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval) ";
1920
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
1921
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval})";
1922

    
1923
			/* store information to database */
1924
			captiveportal_write_db($insertquery);
1925
			unlock($cpdblck);
1926

    
1927
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
1928
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
1929
				if ($acct_val == 1)
1930
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1931
			}
1932
		}
1933
	} else
1934
		unlock($cpdblck);
1935

    
1936
	if ($writecfg == true)
1937
		write_config();
1938

    
1939
	/* redirect user to desired destination */
1940
	if (!empty($attributes['url_redirection']))
1941
		$my_redirurl = $attributes['url_redirection'];
1942
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
1943
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
1944
	else
1945
		$my_redirurl = $redirurl;
1946

    
1947
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
1948
		$ourhostname = portal_hostname_from_client_ip($clientip);
1949
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
1950
		$logouturl = "{$protocol}{$ourhostname}/";
1951

    
1952
		if (isset($attributes['reply_message']))
1953
			$message = $attributes['reply_message'];
1954
		else
1955
			$message = 0;
1956

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

    
1959
	} else {
1960
		header("Location: " . $my_redirurl);
1961
	}
1962

    
1963
	return $sessionid;
1964
}
1965

    
1966

    
1967
/*
1968
 * Used for when pass-through credits are enabled.
1969
 * Returns true when there was at least one free login to deduct for the MAC.
1970
 * Expired entries are removed as they are seen.
1971
 * Active entries are updated according to the configuration.
1972
 */
1973
function portal_consume_passthrough_credit($clientmac) {
1974
	global $config, $cpzone;
1975

    
1976
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
1977
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
1978
	else
1979
		return false;
1980

    
1981
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
1982
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
1983
	else
1984
		return false;
1985

    
1986
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1987
		return false;
1988

    
1989
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
1990

    
1991
	/*
1992
	 * Read database of used MACs.  Lines are a comma-separated list
1993
	 * of the time, MAC, then the count of pass-through credits remaining.
1994
	 */
1995
	$usedmacs = captiveportal_read_usedmacs_db();
1996

    
1997
	$currenttime = time();
1998
	$found = false;
1999
	foreach ($usedmacs as $key => $usedmac) {
2000
		$usedmac = explode(",", $usedmac);
2001

    
2002
		if ($usedmac[1] == $clientmac) {
2003
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2004
				if ($usedmac[2] < 1) {
2005
					if ($updatetimeouts) {
2006
						$usedmac[0] = $currenttime;
2007
						unset($usedmacs[$key]);
2008
						$usedmacs[] = implode(",", $usedmac);
2009
						captiveportal_write_usedmacs_db($usedmacs);
2010
					}
2011

    
2012
					return false;
2013
				} else {
2014
					$usedmac[2] -= 1;
2015
					$usedmacs[$key] = implode(",", $usedmac);
2016
				}
2017

    
2018
				$found = true;
2019
			} else
2020
				unset($usedmacs[$key]);
2021

    
2022
			break;
2023
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2024
				unset($usedmacs[$key]);
2025
	}
2026

    
2027
	if (!$found) {
2028
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2029
		$usedmacs[] = implode(",", $usedmac);
2030
	}
2031

    
2032
	captiveportal_write_usedmacs_db($usedmacs);
2033
	return true;
2034
}
2035

    
2036
function captiveportal_read_usedmacs_db() {
2037
	global $g, $cpzone;
2038

    
2039
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2040
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2041
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2042
		if (!$usedmacs)
2043
			$usedmacs = array();
2044
	} else
2045
		$usedmacs = array();
2046

    
2047
	unlock($cpumaclck);
2048
	return $usedmacs;
2049
}
2050

    
2051
function captiveportal_write_usedmacs_db($usedmacs) {
2052
	global $g, $cpzone;
2053

    
2054
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2055
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2056
	unlock($cpumaclck);
2057
}
2058

    
2059
function captiveportal_send_server_accounting($off = false) {
2060
	global $cpzone, $config;
2061

    
2062
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
2063
		return;
2064
	}
2065
	if ($off) {
2066
		$racct = new Auth_RADIUS_Acct_Off;
2067
	} else {
2068
		$racct = new Auth_RADIUS_Acct_On;
2069
	}
2070
	$radiusservers = captiveportal_get_radius_servers();
2071
	if (empty($radiusservers)) {
2072
		return;
2073
	}
2074
	foreach ($radiusservers['first'] as $radsrv) {
2075
		// Add a new server to our instance
2076
		$racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
2077
	}
2078
	if (PEAR::isError($racct->start())) {
2079
		$retvalue['acct_val'] = 1;
2080
		$retvalue['error'] = $racct->getMessage();
2081

    
2082
		// If we encounter an error immediately stop this function and go back
2083
		$racct->close();
2084
		return $retvalue;
2085
	}
2086
	// Send request
2087
	$result = $racct->send();
2088
	// Evaluation of the response
2089
	// 5 -> Accounting-Response
2090
	// See RFC2866 for this.
2091
	if (PEAR::isError($result)) {
2092
		$retvalue['acct_val'] = 1;
2093
		$retvalue['error'] = $result->getMessage();
2094
	} else if ($result === true) {
2095
		$retvalue['acct_val'] = 5 ;
2096
	} else {
2097
		$retvalue['acct_val'] = 1 ;
2098
	}
2099

    
2100
	$racct->close();
2101
	return $retvalue;
2102
}
2103
?>
(8-8/66)