Project

General

Profile

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

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

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

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

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

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

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

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

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

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

    
758
	captiveportal_prune_old_automac();
759

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

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

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

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

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

    
834
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
835

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

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

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

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

    
880
}
881

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

    
886
	$radiusservers = captiveportal_get_radius_servers();
887

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

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

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

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

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

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

    
932
function captiveportal_passthrumac_configure_entry($macent) {
933

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

    
937
	$ruleno = captiveportal_get_next_ipfw_ruleno();
938
	$pipeno = captiveportal_get_next_dn_ruleno();
939

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

    
949
	return $rules;
950
}
951

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

    
955
	$rules = "";
956

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

    
963
		}
964
	}
965

    
966
	return $rules;
967
}
968

    
969
function captiveportal_passthrumac_findbyname($username) {
970
	global $config, $cpzone;
971

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

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

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

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

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

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

    
1027
function captiveportal_allowedhostname_configure() {
1028
	global $config, $g, $cpzone;
1029

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

    
1051
	return $rules;
1052
}
1053

    
1054
function captiveportal_allowedip_configure() {
1055
	global $config, $g, $cpzone;
1056

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

    
1063
	return $rules;
1064
}
1065

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

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

    
1076
	return 0;
1077
}
1078

    
1079
function captiveportal_init_radius_servers() {
1080
	global $config, $g, $cpzone;
1081

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

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

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

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

    
1133
		fclose($fd);
1134
		unlock($cprdsrvlck);
1135
	}
1136
}
1137

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

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

    
1169
	unlock($cprdsrvlck);
1170
	return false;
1171
}
1172

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

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

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

    
1198
	$pipeno = captiveportal_get_next_dn_ruleno();
1199

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

    
1208
	$radiusservers = captiveportal_get_radius_servers();
1209

    
1210
	if (is_null($radiusctx))
1211
		$radiusctx = 'first';
1212

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

    
1220
	if ($auth_list['auth_val'] == 2) {
1221
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1222
		$sessionid = portal_allow($clientip,
1223
			$clientmac,
1224
			$username,
1225
			$password,
1226
			$auth_list,
1227
			$pipeno,
1228
			$radiusctx);
1229
	} else {
1230
	         captiveportal_free_dn_ruleno($pipeno);
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
	if (!is_dir($g['captiveportal_element_path']))
1323
		@mkdir($g['captiveportal_element_path']);
1324

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

    
1341
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1342
	global $cpzone;
1343

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

    
1359
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1360
	global $config, $g, $cpzone;
1361

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

    
1385
	return $ruleno;
1386
}
1387

    
1388
function captiveportal_free_dn_ruleno($ruleno) {
1389
	global $config, $g;
1390

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

    
1402
function captiveportal_get_dn_passthru_ruleno($value) {
1403
	global $config, $g, $cpzone;
1404

    
1405
	$cpcfg = $config['captiveportal'][$cpzone];
1406
	if(!isset($cpcfg['enable']))
1407
		return NULL;
1408

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

    
1419
	unlock($cpruleslck);
1420
	return NULL;
1421
}
1422

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

    
1431
	$cpcfg = $config['captiveportal'][$cpzone];
1432
	if(!isset($cpcfg['enable']))
1433
		return NULL;
1434

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

    
1464
function captiveportal_free_ipfw_ruleno($ruleno) {
1465
	global $config, $g, $cpzone;
1466

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

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

    
1481
function captiveportal_get_ipfw_passthru_ruleno($value) {
1482
	global $config, $g, $cpzone;
1483

    
1484
	$cpcfg = $config['captiveportal'][$cpzone];
1485
	if(!isset($cpcfg['enable']))
1486
		return NULL;
1487

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

    
1498
	unlock($cpruleslck);
1499
	return NULL;
1500
}
1501

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

    
1513
function getVolume($ip) {
1514
	global $config, $cpzone;
1515

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

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

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

    
1545
	return $volume;
1546
}
1547

    
1548
/**
1549
 * Get the NAS-IP-Address based on the current wan address
1550
 *
1551
 * Use functions in interfaces.inc to find this out
1552
 *
1553
 */
1554

    
1555
function getNasIP()
1556
{
1557
	global $config, $cpzone;
1558

    
1559
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1560
			$nasIp = get_interface_ip();
1561
	} else {
1562
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1563
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1564
		else
1565
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1566
	}
1567
		
1568
	if(!is_ipaddr($nasIp))
1569
		$nasIp = "0.0.0.0";
1570

    
1571
	return $nasIp;
1572
}
1573

    
1574
function portal_ip_from_client_ip($cliip) {
1575
	global $config, $cpzone;
1576

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

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

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

    
1610
	return false;
1611
}
1612

    
1613
function portal_hostname_from_client_ip($cliip) {
1614
	global $config, $cpzone;
1615

    
1616
	$cpcfg = $config['captiveportal'][$cpzone];
1617

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

    
1639
/* functions move from index.php */
1640

    
1641
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1642
	global $g, $config, $cpzone;
1643

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

    
1653
	$cpcfg = $config['captiveportal'][$cpzone];
1654

    
1655
	/* substitute the PORTAL_REDIRURL variable */
1656
	if ($cpcfg['preauthurl']) {
1657
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1658
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1659
	}
1660

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

    
1667
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1668
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1669
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1670
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1671
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1672

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

    
1684
	echo $htmltext;
1685
}
1686

    
1687
function portal_mac_radius($clientmac,$clientip) {
1688
	global $config, $cpzone;
1689

    
1690
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1691

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

    
1698
	if (!empty($auth_list['url_redirection']))
1699
		portal_reply_page($auth_list['url_redirection'], "redir");
1700

    
1701
	return FALSE;
1702
}
1703

    
1704
function captiveportal_reapply_attributes($cpentry, $attributes) {
1705
	global $config, $cpzone, $g;
1706

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

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

    
1718
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1719
}
1720

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

    
1724
	// Ensure we create an array if we are missing attributes
1725
	if (!is_array($attributes))
1726
		$attributes = array();
1727

    
1728
	unset($sessionid);
1729

    
1730
	/* Do not allow concurrent login execution. */
1731
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1732

    
1733
	if ($attributes['voucher'])
1734
		$remaining_time = $attributes['session_timeout'];
1735

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

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

    
1788
	/* Snapshot the timestamp */
1789
	$allow_time = time();
1790
	$radiusservers = captiveportal_get_radius_servers();
1791
	$unsetindexes = array();
1792
	if (is_null($radiusctx))
1793
		$radiusctx = 'first';
1794

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

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

    
1833
	if (!empty($unsetindexes))
1834
		captiveportal_remove_entries($unsetindexes);
1835

    
1836
	if ($attributes['voucher'] && $remaining_time <= 0)
1837
		return 0;       // voucher already used and no time left
1838

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

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

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

    
1879
			$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1880
			$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1881
			$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1882
			$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1883

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

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

    
1896
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1897
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
1898
			else
1899
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
1900

    
1901
			if ($attributes['voucher'])
1902
				$attributes['session_timeout'] = $remaining_time;
1903
			
1904
			/* handle empty attributes */
1905
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
1906
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
1907
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
1908
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
1909

    
1910
			/* escape username */
1911
			$safe_username = sqlite_escape_string($username);
1912

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

    
1919
			/* store information to database */
1920
			captiveportal_write_db($insertquery);
1921
			unlock($cpdblck);
1922

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

    
1932
	if ($writecfg == true)
1933
		write_config();
1934

    
1935
	/* redirect user to desired destination */
1936
	if (!empty($attributes['url_redirection']))
1937
		$my_redirurl = $attributes['url_redirection'];
1938
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
1939
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
1940
	else
1941
		$my_redirurl = $redirurl;
1942

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

    
1948
		if (isset($attributes['reply_message']))
1949
			$message = $attributes['reply_message'];
1950
		else
1951
			$message = 0;
1952

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

    
1955
	} else {
1956
		header("Location: " . $my_redirurl);
1957
	}
1958

    
1959
	return $sessionid;
1960
}
1961

    
1962

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

    
1972
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
1973
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
1974
	else
1975
		return false;
1976

    
1977
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
1978
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
1979
	else
1980
		return false;
1981

    
1982
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1983
		return false;
1984

    
1985
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
1986

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

    
1993
	$currenttime = time();
1994
	$found = false;
1995
	foreach ($usedmacs as $key => $usedmac) {
1996
		$usedmac = explode(",", $usedmac);
1997

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

    
2008
					return false;
2009
				} else {
2010
					$usedmac[2] -= 1;
2011
					$usedmacs[$key] = implode(",", $usedmac);
2012
				}
2013

    
2014
				$found = true;
2015
			} else
2016
				unset($usedmacs[$key]);
2017

    
2018
			break;
2019
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2020
				unset($usedmacs[$key]);
2021
	}
2022

    
2023
	if (!$found) {
2024
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2025
		$usedmacs[] = implode(",", $usedmac);
2026
	}
2027

    
2028
	captiveportal_write_usedmacs_db($usedmacs);
2029
	return true;
2030
}
2031

    
2032
function captiveportal_read_usedmacs_db() {
2033
	global $g, $cpzone;
2034

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

    
2043
	unlock($cpumaclck);
2044
	return $usedmacs;
2045
}
2046

    
2047
function captiveportal_write_usedmacs_db($usedmacs) {
2048
	global $g, $cpzone;
2049

    
2050
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2051
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2052
	unlock($cpumaclck);
2053
}
2054

    
2055
function captiveportal_send_server_accounting($off = false) {
2056
	global $cpzone, $config;
2057

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

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

    
2096
	$racct->close();
2097
	return $retvalue;
2098
}
2099
?>
(8-8/66)