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	/sbin/route
40
	pfSense_BUILDER_BINARIES:	/usr/local/sbin/lighttpd	/usr/local/bin/minicron /sbin/pfctl
41
	pfSense_BUILDER_BINARIES:	/bin/hostname	/bin/cp 
42
	pfSense_MODULE: captiveportal
43
*/
44

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

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

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

    
103
EOD;
104

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

    
112
EOD;
113
	}
114

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

    
142
EOD;
143

    
144
	return $htmltext;
145
}
146

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

    
150
	mute_kernel_msgs();
151
	if (!is_module_loaded("ipfw.ko")) {
152
		mwexec("/sbin/kldload ipfw");
153
		/* make sure ipfw is not on pfil hooks */
154
		mwexec("/sbin/sysctl net.inet.ip.pfil.inbound=\"pf\" net.inet6.ip6.pfil.inbound=\"pf\"" .
155
		       " net.inet.ip.pfil.outbound=\"pf\" net.inet6.ip6.pfil.outbound=\"pf\"");
156
	}
157
	/* Activate layer2 filtering */
158
	mwexec("/sbin/sysctl net.link.ether.ipfw=1 net.inet.ip.fw.one_pass=1");
159

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
309
EOD;
310
		}
311

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

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

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

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

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

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

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

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

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

    
355
		captiveportal_radius_stop_all();
356

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
509
EOD;
510

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

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

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

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

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

    
566
EOD;
567

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

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

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

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

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

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

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

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

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

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

    
618
	$radiussrvs = captiveportal_get_radius_servers();
619

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

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

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

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

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

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

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

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

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

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

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

    
763
	captiveportal_prune_old_automac();
764

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

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

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

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

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

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

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

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

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

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

    
885
}
886

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

    
891
	$radiusservers = captiveportal_get_radius_servers();
892

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

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

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

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

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

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

    
937
function captiveportal_passthrumac_configure_entry($macent) {
938

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

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

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

    
954
	return $rules;
955
}
956

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

    
960
	$rules = "";
961

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

    
968
		}
969
	}
970

    
971
	return $rules;
972
}
973

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

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

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

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

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

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

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

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

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

    
1056
	return $rules;
1057
}
1058

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

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

    
1068
	return $rules;
1069
}
1070

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

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

    
1081
	return 0;
1082
}
1083

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

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

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

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

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

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

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

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

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

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

    
1191
/* log simple messages to syslog */
1192
function captiveportal_syslog($message) {
1193
	global $cpzone;
1194

    
1195
	$message = trim($message);
1196
	$message .= "Zone: {$cpzone} - {$message}";
1197
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1198
	// Log it
1199
	syslog(LOG_INFO, $message);
1200
	closelog();
1201
}
1202

    
1203
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1204
	global $g, $config;
1205

    
1206
	$pipeno = captiveportal_get_next_dn_ruleno();
1207

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

    
1216
	$radiusservers = captiveportal_get_radius_servers();
1217

    
1218
	if (is_null($radiusctx))
1219
		$radiusctx = 'first';
1220

    
1221
	$auth_list = RADIUS_AUTHENTICATION($username,
1222
		$password,
1223
		$radiusservers[$radiusctx],
1224
		$clientip,
1225
		$clientmac,
1226
		$pipeno);
1227

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

    
1241
	return $auth_list;
1242
}
1243

    
1244
function captiveportal_opendb() {
1245
	global $g, $cpzone;
1246

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

    
1262
	return $DB;
1263
}
1264

    
1265
/* read captive portal DB into array */
1266
function captiveportal_read_db($query = "") {
1267

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

    
1283
	return $cpdb;
1284
}
1285

    
1286
function captiveportal_remove_entries($remove) {
1287

    
1288
	if (!is_array($remove) || empty($remove))
1289
		return;
1290

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

    
1301
/* write captive portal DB */
1302
function captiveportal_write_db($queries) {
1303
	global $g;
1304

    
1305
	if (is_array($queries))
1306
		$query = implode(";", $queries);
1307
	else
1308
		$query = $queries;
1309

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

    
1325
function captiveportal_write_elements() {
1326
	global $g, $config, $cpzone;
1327
	
1328
	$cpcfg = $config['captiveportal'][$cpzone];
1329

    
1330
	if (!is_dir($g['captiveportal_element_path']))
1331
		@mkdir($g['captiveportal_element_path']);
1332

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

    
1349
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1350
	global $cpzone;
1351

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

    
1367
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1368
	global $config, $g, $cpzone;
1369

    
1370
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1371
	$ruleno = 0;
1372
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1373
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1374
		for ($ridx = $rulenos_start; $ridx < $rulenos_range_max; $ridx++) {
1375
			if ($rules[$ridx]) {
1376
				$ridx++;
1377
				continue;
1378
			}
1379
			$ruleno = $ridx;
1380
			$rules[$ridx] = $cpzone;
1381
			$rules[++$ridx] = $cpzone;
1382
			break;
1383
		}
1384
	} else {
1385
		$rules = array_pad(array(), $rulenos_range_max, false);
1386
		$ruleno = $rulenos_start;
1387
		$rules[$rulenos_start] = $cpzone;
1388
		$rules[++$rulenos_start] = $cpzone;
1389
	}
1390
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1391
	unlock($cpruleslck);
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
		$rules[++$ruleno] = false;
1404
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1405
	}
1406
	unlock($cpruleslck);
1407
}
1408

    
1409
function captiveportal_get_dn_passthru_ruleno($value) {
1410
	global $config, $g, $cpzone;
1411

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

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

    
1426
	unlock($cpruleslck);
1427
	return NULL;
1428
}
1429

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

    
1438
	$cpcfg = $config['captiveportal'][$cpzone];
1439
	if(!isset($cpcfg['enable']))
1440
		return NULL;
1441

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

    
1471
function captiveportal_free_ipfw_ruleno($ruleno) {
1472
	global $config, $g, $cpzone;
1473

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

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

    
1488
function captiveportal_get_ipfw_passthru_ruleno($value) {
1489
	global $config, $g, $cpzone;
1490

    
1491
	$cpcfg = $config['captiveportal'][$cpzone];
1492
	if(!isset($cpcfg['enable']))
1493
		return NULL;
1494

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

    
1505
	unlock($cpruleslck);
1506
	return NULL;
1507
}
1508

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

    
1520
function getVolume($ip) {
1521
	global $config, $cpzone;
1522

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

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

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

    
1552
	return $volume;
1553
}
1554

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

    
1562
function getNasIP()
1563
{
1564
	global $config, $cpzone;
1565

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

    
1578
	return $nasIp;
1579
}
1580

    
1581
function portal_ip_from_client_ip($cliip) {
1582
	global $config, $cpzone;
1583

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

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

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

    
1617
	return false;
1618
}
1619

    
1620
function portal_hostname_from_client_ip($cliip) {
1621
	global $config, $cpzone;
1622

    
1623
	$cpcfg = $config['captiveportal'][$cpzone];
1624

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

    
1646
/* functions move from index.php */
1647

    
1648
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1649
	global $g, $config, $cpzone;
1650

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

    
1660
	$cpcfg = $config['captiveportal'][$cpzone];
1661

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

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

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

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

    
1691
	echo $htmltext;
1692
}
1693

    
1694
function portal_mac_radius($clientmac,$clientip) {
1695
	global $config, $cpzone;
1696

    
1697
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1698

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

    
1705
	if (!empty($auth_list['url_redirection']))
1706
		portal_reply_page($auth_list['url_redirection'], "redir");
1707

    
1708
	return FALSE;
1709
}
1710

    
1711
function captiveportal_reapply_attributes($cpentry, $attributes) {
1712
	global $config, $cpzone, $g;
1713

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

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

    
1725
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1726
}
1727

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

    
1731
	// Ensure we create an array if we are missing attributes
1732
	if (!is_array($attributes))
1733
		$attributes = array();
1734

    
1735
	unset($sessionid);
1736

    
1737
	/* Do not allow concurrent login execution. */
1738
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1739

    
1740
	if ($attributes['voucher'])
1741
		$remaining_time = $attributes['session_timeout'];
1742

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

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

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

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

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

    
1840
	if (!empty($unsetindexes))
1841
		captiveportal_remove_entries($unsetindexes);
1842

    
1843
	if ($attributes['voucher'] && $remaining_time <= 0)
1844
		return 0;       // voucher already used and no time left
1845

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

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

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

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

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

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

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

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

    
1917
			/* escape username */
1918
			$safe_username = sqlite_escape_string($username);
1919

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

    
1926
			/* store information to database */
1927
			captiveportal_write_db($insertquery);
1928
			unlock($cpdblck);
1929

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

    
1939
	if ($writecfg == true)
1940
		write_config();
1941

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

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

    
1955
		if (isset($attributes['reply_message']))
1956
			$message = $attributes['reply_message'];
1957
		else
1958
			$message = 0;
1959

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

    
1962
	} else {
1963
		header("Location: " . $my_redirurl);
1964
	}
1965

    
1966
	return $sessionid;
1967
}
1968

    
1969

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

    
1979
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
1980
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
1981
	else
1982
		return false;
1983

    
1984
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
1985
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
1986
	else
1987
		return false;
1988

    
1989
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1990
		return false;
1991

    
1992
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
1993

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

    
2000
	$currenttime = time();
2001
	$found = false;
2002
	foreach ($usedmacs as $key => $usedmac) {
2003
		$usedmac = explode(",", $usedmac);
2004

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

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

    
2021
				$found = true;
2022
			} else
2023
				unset($usedmacs[$key]);
2024

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

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

    
2035
	captiveportal_write_usedmacs_db($usedmacs);
2036
	return true;
2037
}
2038

    
2039
function captiveportal_read_usedmacs_db() {
2040
	global $g, $cpzone;
2041

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

    
2050
	unlock($cpumaclck);
2051
	return $usedmacs;
2052
}
2053

    
2054
function captiveportal_write_usedmacs_db($usedmacs) {
2055
	global $g, $cpzone;
2056

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

    
2062
function captiveportal_send_server_accounting($off = false) {
2063
	global $cpzone, $config;
2064

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

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

    
2103
	$racct->close();
2104
	return $retvalue;
2105
}
2106
?>
(8-8/66)