Project

General

Profile

Download (70.8 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	captiveportal.inc
4
	part of pfSense (http://www.pfSense.org)
5
	Copyright (C) 2004-2011 Scott Ullrich <sullrich@gmail.com>
6
	Copyright (C) 2009-2012 Ermal Lu�i <eri@pfsense.org>
7
	Copyright (C) 2003-2006 Manuel Kasper <mk@neon1.net>.
8

    
9
	originally part of m0n0wall (http://m0n0.ch/wall)
10
	All rights reserved.
11

    
12
	Redistribution and use in source and binary forms, with or without
13
	modification, are permitted provided that the following conditions are met:
14

    
15
	1. Redistributions of source code must retain the above copyright notice,
16
	   this list of conditions and the following disclaimer.
17

    
18
	2. Redistributions in binary form must reproduce the above copyright
19
	   notice, this list of conditions and the following disclaimer in the
20
	   documentation and/or other materials provided with the distribution.
21

    
22
	THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
23
	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
24
	AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25
	AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
26
	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27
	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28
	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29
	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31
	POSSIBILITY OF SUCH DAMAGE.
32

    
33
	This version of captiveportal.inc has been modified by Rob Parker
34
	<rob.parker@keycom.co.uk> to include changes for per-user bandwidth management
35
	via returned RADIUS attributes. This page has been modified to delete any
36
	added rules which may have been created by other per-user code (index.php, etc).
37
	These changes are (c) 2004 Keycom PLC.
38
	
39
	pfSense_BUILDER_BINARIES:	/sbin/ipfw	/sbin/sysctl
40
	pfSense_BUILDER_BINARIES:	/usr/local/sbin/lighttpd	/usr/local/bin/minicron /sbin/pfctl
41
	pfSense_BUILDER_BINARIES:	/bin/hostname	/bin/cp 
42
	pfSense_MODULE: captiveportal
43
*/
44

    
45
/* include all configuration functions */
46
require_once("config.inc");
47
require_once("functions.inc");
48
require_once("filter.inc");
49
require_once("radius.inc");
50
require_once("voucher.inc");
51

    
52
function get_default_captive_portal_html() {
53
	global $config, $g, $cpzone;
54

    
55
	$htmltext = <<<EOD
56
<html> 
57
<body> 
58
<form method="post" action="\$PORTAL_ACTION\$"> 
59
	<input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$">
60
	<input name="zone" type="hidden" value="\$PORTAL_ZONE\$">
61
	<center>
62
	<table cellpadding="6" cellspacing="0" width="550" height="380" style="border:1px solid #000000">
63
	<tr height="10" bgcolor="#990000">
64
		<td style="border-bottom:1px solid #000000">
65
			<font color='white'>
66
			<b>
67
				{$g['product_name']} captive portal
68
			</b>
69
			</font>
70
		</td>
71
	</tr>
72
	<tr>
73
		<td>
74
			<div id="mainlevel">
75
			<center>
76
			<table width="100%" border="0" cellpadding="5" cellspacing="0">
77
			<tr>
78
				<td>
79
					<center>
80
					<div id="mainarea">
81
					<center>
82
					<table width="100%" border="0" cellpadding="5" cellspacing="5">
83
					<tr>
84
						<td>
85
							<div id="maindivarea">
86
							<center>
87
								<div id='statusbox'>
88
									<font color='red' face='arial' size='+1'>
89
									<b>
90
										\$PORTAL_MESSAGE\$
91
									</b>
92
									</font>
93
								</div>
94
								<br/>
95
								<div id='loginbox'>
96
								<table>
97
									<tr><td colspan="2"><center>Welcome to the {$g['product_name']} Captive Portal!</td></tr>
98
									<tr><td>&nbsp;</td></tr>
99
									<tr><td align="right">Username:</td><td><input name="auth_user" type="text" style="border: 1px dashed;"></td></tr>
100
									<tr><td align="right">Password:</td><td><input name="auth_pass" type="password" style="border: 1px dashed;"></td></tr>
101
									<tr><td>&nbsp;</td></tr>
102

    
103
EOD;
104

    
105
	if(isset($config['voucher'][$cpzone]['enable'])) {
106
	$htmltext .= <<<EOD
107
									<tr>
108
										<td align="right">Enter Voucher Code: </td>
109
										<td><input name="auth_voucher" type="text" style="border:1px dashed;" size="22"></td>
110
									</tr>
111

    
112
EOD;
113
	}
114

    
115
	$htmltext .= <<<EOD
116
									<tr>
117
										<td colspan="2"><center><input name="accept" type="submit" value="Continue"></center></td>
118
									</tr>
119
								</table>
120
								</div>
121
							</center>
122
							</div>
123
						</td>
124
					</tr>
125
					</table>
126
					</center>
127
					</div>
128
					</center>
129
				</td>
130
			</tr>
131
			</table>
132
			</center>
133
			</div>
134
		</td>
135
	</tr>
136
	</table>
137
	</center>
138
</form>
139
</body> 
140
</html>
141

    
142
EOD;
143

    
144
	return $htmltext;
145
}
146

    
147
function captiveportal_load_modules() {
148
	global $config;
149

    
150
	mute_kernel_msgs();
151
	if (!is_module_loaded("ipfw.ko")) {
152
		mwexec("/sbin/kldload ipfw");
153
		/* make sure ipfw is not on pfil hooks */
154
		mwexec("/sbin/sysctl net.inet.ip.pfil.inbound=\"pf\" net.inet6.ip6.pfil.inbound=\"pf\"" .
155
		       " net.inet.ip.pfil.outbound=\"pf\" net.inet6.ip6.pfil.outbound=\"pf\"");
156
	}
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]) && intval($cpentry[10]) > 60)
719
						$interval = intval($cpentry[10]);
720
					else
721
						$interval = 0;
722
					$past_interval_min = ($session_time > $interval);
723
					if (!empty($interval))
724
						$within_interval = ($session_time % $interval >= 0 && $session_time % $interval <= 59);
725
					if (empty($interval) || ($interval > 0 && $past_interval_min && $within_interval)) {
726
						RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
727
							$cpentry[4], // username
728
							$cpentry[5], // sessionid
729
							$cpentry[0], // start time
730
							$radiusservers,
731
							$cpentry[2], // clientip
732
							$cpentry[3], // clientmac
733
							10, // NAS Request
734
							true); // Interim Updates
735
					}
736
				}
737
			}
738

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

    
758
	captiveportal_prune_old_automac();
759

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

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

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

    
772
	if (is_array($config['captiveportal'][$cpzone]['passthrumac']) && isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
773
		$tmpvoucherdb = array();
774
		$macrules = "";
775
		$writecfg = false;
776
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $eid => $emac) {
777
			if ($emac['logintype'] == "voucher") {
778
				if (isset($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
		$ridx = $rulenos_start;
1348
		while ($ridx < $rulenos_range_max) {
1349
			if ($rules[$ridx] == $cpzone) {
1350
				$rules[$ridx] = false;
1351
				$ridx++;
1352
				$rules[$ridx] = false;
1353
				$ridx++;
1354
			} else
1355
				$ridx += 2;
1356
		}
1357
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1358
		unset($rules);
1359
	}
1360
	unlock($cpruleslck);
1361
}
1362

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

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

    
1393
	return $ruleno;
1394
}
1395

    
1396
function captiveportal_free_dn_ruleno($ruleno) {
1397
	global $config, $g;
1398

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

    
1411
function captiveportal_get_dn_passthru_ruleno($value) {
1412
	global $config, $g, $cpzone;
1413

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

    
1418
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1419
	$ruleno = NULL;
1420
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1421
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1422
		unset($output);
1423
		$_gb = exec("/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", $output);
1424
		$ruleno = intval($output[0]);
1425
		if (!$rules[$ruleno])
1426
			$ruleno = NULL;
1427
		unset($rules);
1428
	}
1429
	unlock($cpruleslck);
1430

    
1431
	return $ruleno;
1432
}
1433

    
1434
/*
1435
 * This function will calculate the lowest free firewall ruleno
1436
 * within the range specified based on the actual logged on users
1437
 *
1438
 */
1439
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1440
	global $config, $g, $cpzone;
1441

    
1442
	$cpcfg = $config['captiveportal'][$cpzone];
1443
	if(!isset($cpcfg['enable']))
1444
		return NULL;
1445

    
1446
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1447
	$ruleno = 0;
1448
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1449
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1450
		$ridx = $rulenos_start;
1451
		while ($ridx < $rulenos_range_max) {
1452
			if ($rules[$ridx]) {
1453
				/* 
1454
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1455
				 * and the out pipe ruleno + 1.
1456
				 */
1457
				$ridx += 2;
1458
			} else {
1459
				$ruleno = $ridx;
1460
				$rules[$ridx] = $cpzone;
1461
				$ridx++;
1462
				$rules[$ridx] = $cpzone;
1463
				break;
1464
			}
1465
		}
1466
	} else {
1467
		$rules = array_pad(array(), $rulenos_range_max, false);
1468
		$ruleno = $rulenos_start;
1469
		$rules[$rulenos_start] = $cpzone;
1470
		$rulenos_start++;
1471
		$rules[$rulenos_start] = $cpzone;
1472
	}
1473
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1474
	unlock($cpruleslck);
1475
	unset($rules);
1476

    
1477
	return $ruleno;
1478
}
1479

    
1480
function captiveportal_free_ipfw_ruleno($ruleno) {
1481
	global $config, $g, $cpzone;
1482

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

    
1487
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1488
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1489
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1490
		$rules[$ruleno] = false;
1491
		$ruleno++;
1492
		$rules[$ruleno] = false;
1493
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1494
	}
1495
	unlock($cpruleslck);
1496
	unset($rules);
1497
}
1498

    
1499
function captiveportal_get_ipfw_passthru_ruleno($value) {
1500
	global $config, $g, $cpzone;
1501

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

    
1506
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1507
	$ruleno = NULL;
1508
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1509
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1510
		unset($output);
1511
		$_gb = exec("/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", $output);
1512
		$ruleno = intval($output[0]);
1513
		if (!$rules[$ruleno])
1514
			$ruleno = NULL;
1515
	}
1516
	unlock($cpruleslck);
1517
	unset($rules);
1518

    
1519
	return $ruleno;
1520
}
1521

    
1522
/**
1523
 * This function will calculate the traffic produced by a client
1524
 * based on its firewall rule
1525
 *
1526
 * Point of view: NAS
1527
 *
1528
 * Input means: from the client
1529
 * Output means: to the client
1530
 *
1531
 */
1532

    
1533
function getVolume($ip) {
1534
	global $config, $cpzone;
1535

    
1536
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1537
	$volume = array();
1538
	// Initialize vars properly, since we don't want NULL vars
1539
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1540

    
1541
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 1, $ip);
1542
	if (is_array($ipfw)) {
1543
		if ($reverse) {
1544
			$volume['output_pkts'] = $ipfw['packets'];
1545
			$volume['output_bytes'] = $ipfw['bytes'];
1546
		}
1547
		else {
1548
			$volume['input_pkts'] = $ipfw['packets'];
1549
			$volume['input_bytes'] = $ipfw['bytes'];
1550
		}
1551
	}
1552

    
1553
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 2, $ip);
1554
	if (is_array($ipfw)) {
1555
		if ($reverse) {
1556
			$volume['input_pkts'] = $ipfw['packets'];
1557
			$volume['input_bytes'] = $ipfw['bytes'];
1558
		}
1559
		else {
1560
			$volume['output_pkts'] = $ipfw['packets'];
1561
			$volume['output_bytes'] = $ipfw['bytes'];
1562
		}
1563
	}
1564

    
1565
	return $volume;
1566
}
1567

    
1568
/**
1569
 * Get the NAS-IP-Address based on the current wan address
1570
 *
1571
 * Use functions in interfaces.inc to find this out
1572
 *
1573
 */
1574

    
1575
function getNasIP()
1576
{
1577
	global $config, $cpzone;
1578

    
1579
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1580
			$nasIp = get_interface_ip();
1581
	} else {
1582
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1583
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1584
		else
1585
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1586
	}
1587
		
1588
	if(!is_ipaddr($nasIp))
1589
		$nasIp = "0.0.0.0";
1590

    
1591
	return $nasIp;
1592
}
1593

    
1594
function portal_ip_from_client_ip($cliip) {
1595
	global $config, $cpzone;
1596

    
1597
	$isipv6 = is_ipaddrv6($cliip);
1598
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1599
	foreach ($interfaces as $cpif) {
1600
		if ($isipv6) {
1601
			$ip = get_interface_ipv6($cpif);
1602
			$sn = get_interface_subnetv6($cpif);
1603
		} else {
1604
			$ip = get_interface_ip($cpif);
1605
			$sn = get_interface_subnet($cpif);
1606
		}
1607
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1608
			return $ip;
1609
	}
1610

    
1611
	$inet = ($isipv6) ? '-inet6' : '-inet';
1612
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1613
	$iface = trim($iface, "\n");
1614
	if (!empty($iface)) {
1615
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1616
		if (is_ipaddr($ip))
1617
			return $ip;
1618
	}
1619

    
1620
	// doesn't match up to any particular interface
1621
	// so let's set the portal IP to what PHP says 
1622
	// the server IP issuing the request is. 
1623
	// allows same behavior as 1.2.x where IP isn't 
1624
	// in the subnet of any CP interface (static routes, etc.)
1625
	// rather than forcing to DNS hostname resolution
1626
	$ip = $_SERVER['SERVER_ADDR'];
1627
	if (is_ipaddr($ip))
1628
		return $ip;
1629

    
1630
	return false;
1631
}
1632

    
1633
function portal_hostname_from_client_ip($cliip) {
1634
	global $config, $cpzone;
1635

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

    
1638
	if (isset($cpcfg['httpslogin'])) {
1639
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
1640
		$ourhostname = $cpcfg['httpsname'];
1641
		
1642
		if ($listenporthttps != 443)
1643
			$ourhostname .= ":" . $listenporthttps;
1644
	} else {
1645
		$listenporthttp  = $cpcfg['listenporthttp']  ? $cpcfg['listenporthttp']  : $cpcfg['zoneid'];
1646
		$ifip = portal_ip_from_client_ip($cliip);
1647
		if (!$ifip)
1648
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1649
		else
1650
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1651
		
1652
		if ($listenporthttp != 80)
1653
			$ourhostname .= ":" . $listenporthttp;
1654
	}
1655
	
1656
	return $ourhostname;
1657
}
1658

    
1659
/* functions move from index.php */
1660

    
1661
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1662
	global $g, $config, $cpzone;
1663

    
1664
	/* Get captive portal layout */
1665
	if ($type == "redir") {
1666
		header("Location: {$redirurl}");
1667
		return;
1668
	} else if ($type == "login")
1669
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1670
	else
1671
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1672

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

    
1675
	/* substitute the PORTAL_REDIRURL variable */
1676
	if ($cpcfg['preauthurl']) {
1677
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1678
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1679
	}
1680

    
1681
	/* substitute other variables */
1682
	$ourhostname = portal_hostname_from_client_ip($clientip);
1683
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1684
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1685
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1686

    
1687
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1688
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1689
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1690
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1691
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1692

    
1693
	// Special handling case for captive portal master page so that it can be ran 
1694
	// through the PHP interpreter using the include method above.  We convert the
1695
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1696
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1697
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1698
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1699
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1700
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1701
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1702
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1703

    
1704
	echo $htmltext;
1705
}
1706

    
1707
function portal_mac_radius($clientmac,$clientip) {
1708
	global $config, $cpzone;
1709

    
1710
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1711

    
1712
	/* authentication against the radius server */
1713
	$username = mac_format($clientmac);
1714
	$auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1715
	if ($auth_list['auth_val'] == 2)
1716
		return TRUE;
1717

    
1718
	if (!empty($auth_list['url_redirection']))
1719
		portal_reply_page($auth_list['url_redirection'], "redir");
1720

    
1721
	return FALSE;
1722
}
1723

    
1724
function captiveportal_reapply_attributes($cpentry, $attributes) {
1725
	global $config, $cpzone, $g;
1726

    
1727
	$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1728
	$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1729
	$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1730
	$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1731
	$bw_up_pipeno = $cpentry[1];
1732
	$bw_down_pipeno = $cpentry[1]+1;
1733

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

    
1738
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1739
}
1740

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

    
1744
	// Ensure we create an array if we are missing attributes
1745
	if (!is_array($attributes))
1746
		$attributes = array();
1747

    
1748
	unset($sessionid);
1749

    
1750
	/* Do not allow concurrent login execution. */
1751
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1752

    
1753
	if ($attributes['voucher'])
1754
		$remaining_time = $attributes['session_timeout'];
1755

    
1756
	$writecfg = false;
1757
	/* Find an existing session */
1758
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
1759
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1760
			$mac = captiveportal_passthrumac_findbyname($username);
1761
			if (!empty($mac)) {
1762
				if ($_POST['replacemacpassthru']) {
1763
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
1764
						if ($macent['mac'] == $mac['mac']) {
1765
							$macrules = "";
1766
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1767
							$pipeno = captiveportal_get_dn_passthru_ruleno($mac['mac']);
1768
							if ($ruleno) {
1769
								captiveportal_free_ipfw_ruleno($ruleno);
1770
								$macrules .= "delete {$ruleno}\n";
1771
								++$ruleno;
1772
								$macrules .= "delete {$ruleno}\n";
1773
							}
1774
							if ($pipeno) {
1775
								captiveportal_free_dn_ruleno($pipeno);
1776
								$macrules .= "pipe delete {$pipeno}\n";
1777
								++$pipeno;
1778
								$macrules .= "pipe delete {$pipeno}\n";
1779
							}
1780
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1781
							$mac['mac'] = $clientmac;
1782
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1783
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1784
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1785
							mwexec("/sbin/ipfw -x {$cpzone} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1786
							$writecfg = true;
1787
							$sessionid = true;
1788
							break;
1789
						}
1790
					}
1791
				} else {
1792
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1793
						$clientmac, $clientip, $username, $password);
1794
					unlock($cpdblck);
1795
					return;
1796
				}
1797
			}
1798
		}
1799
	}
1800

    
1801
	/* read in client database */
1802
	$query = "WHERE ip = '{$clientip}'";
1803
	$tmpusername = strtolower($username);
1804
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins']))
1805
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
1806
	$cpdb = captiveportal_read_db($query);
1807

    
1808
	/* Snapshot the timestamp */
1809
	$allow_time = time();
1810
	$radiusservers = captiveportal_get_radius_servers();
1811
	$unsetindexes = array();
1812
	if (is_null($radiusctx))
1813
		$radiusctx = 'first';
1814

    
1815
	foreach ($cpdb as $cpentry) {
1816
		if (empty($cpentry[10]))
1817
			$cpentry[10] = 'first';
1818
		/* on the same ip */
1819
		if ($cpentry[2] == $clientip) {
1820
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac)
1821
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1822
			else
1823
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
1824
			$sessionid = $cpentry[5];
1825
			break;
1826
		}
1827
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1828
			// user logged in with an active voucher. Check for how long and calculate 
1829
			// how much time we can give him (voucher credit - used time)
1830
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1831
			if ($remaining_time < 0)    // just in case. 
1832
				$remaining_time = 0;
1833

    
1834
			/* This user was already logged in so we disconnect the old one */
1835
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1836
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1837
			$unsetindexes[] = $cpentry[5];
1838
			break;
1839
		}
1840
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1841
			/* on the same username */
1842
			if (strcasecmp($cpentry[4], $username) == 0) {
1843
				/* This user was already logged in so we disconnect the old one */
1844
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1845
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1846
				$unsetindexes[] = $cpentry[5];
1847
				break;
1848
			}
1849
		}
1850
	}
1851
	unset($cpdb);
1852

    
1853
	if (!empty($unsetindexes))
1854
		captiveportal_remove_entries($unsetindexes);
1855

    
1856
	if ($attributes['voucher'] && $remaining_time <= 0)
1857
		return 0;       // voucher already used and no time left
1858

    
1859
	if (!isset($sessionid)) {
1860
		/* generate unique session ID */
1861
		$tod = gettimeofday();
1862
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1863

    
1864
		if ($passthrumac) {
1865
			$mac = array();
1866
			$mac['mac'] = $clientmac;
1867
			$mac['ip'] = $clientip; /* Used only for logging */
1868
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
1869
				$mac['username'] = $username;
1870
				if ($attributes['voucher'])
1871
					$mac['logintype'] = "voucher";
1872
			}
1873
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1874
			if (!empty($bw_up))
1875
				$mac['bw_up'] = $bw_up;
1876
			if (!empty($bw_down))
1877
				$mac['bw_down'] = $bw_down;
1878
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
1879
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
1880
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1881
			unlock($cpdblck);
1882
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1883
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1884
			mwexec("/sbin/ipfw -x {$cpzone} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1885
			$writecfg = true;
1886
		} else {
1887
			/* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
1888
			if (is_null($pipeno))
1889
				$pipeno = captiveportal_get_next_dn_ruleno();
1890

    
1891
			/* if the pool is empty, return appropriate message and exit */
1892
			if (is_null($pipeno)) {
1893
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1894
				log_error("WARNING!  Captive portal has reached maximum login capacity");
1895
				unlock($cpdblck);
1896
				return;
1897
			}
1898

    
1899
			$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1900
			$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1901
			$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1902
			$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1903

    
1904
			$bw_up_pipeno = $pipeno;
1905
			$bw_down_pipeno = $pipeno + 1;
1906
			//$bw_up /= 1000; // Scale to Kbit/s
1907
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1908
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1909

    
1910
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
1911
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1912
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
1913
			else
1914
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
1915

    
1916
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1917
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
1918
			else
1919
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
1920

    
1921
			if ($attributes['voucher'])
1922
				$attributes['session_timeout'] = $remaining_time;
1923
			
1924
			/* handle empty attributes */
1925
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
1926
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
1927
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
1928
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
1929

    
1930
			/* escape username */
1931
			$safe_username = sqlite_escape_string($username);
1932

    
1933
			/* encode password in Base64 just in case it contains commas */
1934
			$bpassword = base64_encode($password);
1935
			$insertquery  = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval) ";
1936
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
1937
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval})";
1938

    
1939
			/* store information to database */
1940
			captiveportal_write_db($insertquery);
1941
			unlock($cpdblck);
1942
			unset($insertquery, $bpassword);
1943

    
1944
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
1945
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
1946
				if ($acct_val == 1)
1947
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1948
			}
1949
		}
1950
	} else
1951
		unlock($cpdblck);
1952

    
1953
	if ($writecfg == true)
1954
		write_config();
1955

    
1956
	/* redirect user to desired destination */
1957
	if (!empty($attributes['url_redirection']))
1958
		$my_redirurl = $attributes['url_redirection'];
1959
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
1960
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
1961
	else
1962
		$my_redirurl = $redirurl;
1963

    
1964
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
1965
		$ourhostname = portal_hostname_from_client_ip($clientip);
1966
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
1967
		$logouturl = "{$protocol}{$ourhostname}/";
1968

    
1969
		if (isset($attributes['reply_message']))
1970
			$message = $attributes['reply_message'];
1971
		else
1972
			$message = 0;
1973

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

    
1976
	} else {
1977
		header("Location: " . $my_redirurl);
1978
	}
1979

    
1980
	return $sessionid;
1981
}
1982

    
1983

    
1984
/*
1985
 * Used for when pass-through credits are enabled.
1986
 * Returns true when there was at least one free login to deduct for the MAC.
1987
 * Expired entries are removed as they are seen.
1988
 * Active entries are updated according to the configuration.
1989
 */
1990
function portal_consume_passthrough_credit($clientmac) {
1991
	global $config, $cpzone;
1992

    
1993
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
1994
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
1995
	else
1996
		return false;
1997

    
1998
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
1999
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2000
	else
2001
		return false;
2002

    
2003
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
2004
		return false;
2005

    
2006
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2007

    
2008
	/*
2009
	 * Read database of used MACs.  Lines are a comma-separated list
2010
	 * of the time, MAC, then the count of pass-through credits remaining.
2011
	 */
2012
	$usedmacs = captiveportal_read_usedmacs_db();
2013

    
2014
	$currenttime = time();
2015
	$found = false;
2016
	foreach ($usedmacs as $key => $usedmac) {
2017
		$usedmac = explode(",", $usedmac);
2018

    
2019
		if ($usedmac[1] == $clientmac) {
2020
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2021
				if ($usedmac[2] < 1) {
2022
					if ($updatetimeouts) {
2023
						$usedmac[0] = $currenttime;
2024
						unset($usedmacs[$key]);
2025
						$usedmacs[] = implode(",", $usedmac);
2026
						captiveportal_write_usedmacs_db($usedmacs);
2027
					}
2028

    
2029
					return false;
2030
				} else {
2031
					$usedmac[2] -= 1;
2032
					$usedmacs[$key] = implode(",", $usedmac);
2033
				}
2034

    
2035
				$found = true;
2036
			} else
2037
				unset($usedmacs[$key]);
2038

    
2039
			break;
2040
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2041
				unset($usedmacs[$key]);
2042
	}
2043

    
2044
	if (!$found) {
2045
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2046
		$usedmacs[] = implode(",", $usedmac);
2047
	}
2048

    
2049
	captiveportal_write_usedmacs_db($usedmacs);
2050
	return true;
2051
}
2052

    
2053
function captiveportal_read_usedmacs_db() {
2054
	global $g, $cpzone;
2055

    
2056
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2057
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2058
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2059
		if (!$usedmacs)
2060
			$usedmacs = array();
2061
	} else
2062
		$usedmacs = array();
2063

    
2064
	unlock($cpumaclck);
2065
	return $usedmacs;
2066
}
2067

    
2068
function captiveportal_write_usedmacs_db($usedmacs) {
2069
	global $g, $cpzone;
2070

    
2071
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2072
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2073
	unlock($cpumaclck);
2074
}
2075

    
2076
function captiveportal_send_server_accounting($off = false) {
2077
	global $cpzone, $config;
2078

    
2079
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
2080
		return;
2081
	}
2082
	if ($off) {
2083
		$racct = new Auth_RADIUS_Acct_Off;
2084
	} else {
2085
		$racct = new Auth_RADIUS_Acct_On;
2086
	}
2087
	$radiusservers = captiveportal_get_radius_servers();
2088
	if (empty($radiusservers)) {
2089
		return;
2090
	}
2091
	foreach ($radiusservers['first'] as $radsrv) {
2092
		// Add a new server to our instance
2093
		$racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
2094
	}
2095
	if (PEAR::isError($racct->start())) {
2096
		$retvalue['acct_val'] = 1;
2097
		$retvalue['error'] = $racct->getMessage();
2098

    
2099
		// If we encounter an error immediately stop this function and go back
2100
		$racct->close();
2101
		return $retvalue;
2102
	}
2103
	// Send request
2104
	$result = $racct->send();
2105
	// Evaluation of the response
2106
	// 5 -> Accounting-Response
2107
	// See RFC2866 for this.
2108
	if (PEAR::isError($result)) {
2109
		$retvalue['acct_val'] = 1;
2110
		$retvalue['error'] = $result->getMessage();
2111
	} else if ($result === true) {
2112
		$retvalue['acct_val'] = 5 ;
2113
	} else {
2114
		$retvalue['acct_val'] = 1 ;
2115
	}
2116

    
2117
	$racct->close();
2118
	return $retvalue;
2119
}
2120
?>
(8-8/66)