Projet

Général

Profil

Télécharger (72,3 ko) Statistiques
| Branche: | Tag: | Révision:

univnautes / etc / inc / captiveportal.inc @ 11fbb86d

1
<?php
2
/*
3
	captiveportal.inc
4
	part of pfSense (https://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
		$documentroot = "/usr/local/captiveportal";
430
		$captive_portal_saml = false;
431
		if ($cpcfg['auth_method'] == "saml") {
432
			$listenporthttps = 443;
433
			$documentroot = "/usr/local/univnautes/sp/www";
434
			$captive_portal_saml = true;
435
		}
436
		system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf",
437
			$crt, $key, $ca, "lighty-{$cpzone}-CaptivePortal-SSL.pid", $listenporthttps, $documentroot,
438
			"cert-{$cpzone}-portal.pem", "ca-{$cpzone}-portal.pem", $cpzone, $captive_portal_saml);
439
	}
440

    
441
	/* generate lighttpd configuration */
442
	$listenporthttp = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : $cpcfg['zoneid'];
443
	system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal.conf",
444
		"", "", "", "lighty-{$cpzone}-CaptivePortal.pid", $listenporthttp, "/usr/local/captiveportal",
445
		"", "", $cpzone);
446

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

    
451
	/* fire up https instance */
452
	if (isset($cpcfg['httpslogin'])) {
453
		@unlink("{$g['varrun']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
454
		$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf");
455

    
456
		if ($cpcfg['auth_method'] == "saml") {
457
			$res = mwexec_bg("/usr/local/univnautes/sp/rc.sh restart");
458
		}
459
	}
460
}
461

    
462
/* reinit will disconnect all users, be careful! */
463
function captiveportal_init_rules($reinit = false) {
464
	global $config, $g, $cpzone;
465

    
466
	if (!isset($config['captiveportal'][$cpzone]['enable']))
467
		return;
468

    
469
	captiveportal_load_modules();
470
	mwexec("/usr/local/sbin/ipfw_context -a {$cpzone}", true);
471

    
472
	$cpips = array();
473
	$ifaces = get_configured_interface_list();
474
	$cpinterfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
475
	$firsttime = 0;
476
	foreach ($cpinterfaces as $cpifgrp) {
477
		if (!isset($ifaces[$cpifgrp]))
478
			continue;
479
		$tmpif = get_real_interface($cpifgrp);
480
		if (!empty($tmpif)) {
481
			$cpipm = get_interface_ip($cpifgrp);
482
			if (is_ipaddr($cpipm)) {
483
				$carpif = link_ip_to_carp_interface($cpipm);
484
				if (!empty($carpif)) {
485
					$carpsif = explode(" ", $carpif);
486
					foreach ($carpsif as $cpcarp) {
487
						mwexec("/usr/local/sbin/ipfw_context -a {$cpzone} -n {$cpcarp}", true);
488
						$carpip = find_interface_ip($cpcarp);
489
						if (is_ipaddr($carpip))
490
							$cpips[] = $carpip;
491
					}
492
				}
493
				$cpips[] = $cpipm;
494
			}
495
			mwexec("/usr/local/sbin/ipfw_context -a {$cpzone} -n {$tmpif}", true);
496
		}
497
	}
498
	if (count($cpips) > 0) {
499
		$cpactive = true;
500
	} else
501
		return false;
502

    
503
	if ($reinit == false)
504
		$captiveportallck = lock("captiveportal{$cpzone}");
505

    
506
	$cprules =	"add 65291 allow pfsync from any to any\n";
507
	$cprules .= "add 65292 allow carp from any to any\n";
508

    
509
	$cprules .= <<<EOD
510
# layer 2: pass ARP
511
add 65301 pass layer2 mac-type arp,rarp
512
# pfsense requires for WPA
513
add 65302 pass layer2 mac-type 0x888e,0x88c7
514
# PPP Over Ethernet Session Stage/Discovery Stage
515
add 65303 pass layer2 mac-type 0x8863,0x8864
516

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

    
520
EOD;
521

    
522
	$rulenum = 65310;
523
	$ipcount = 0;
524
	$ips = "";
525
	foreach ($cpips as $cpip) {
526
		if($ipcount == 0) {
527
			$ips = "{$cpip} ";
528
		} else {
529
			$ips .= "or {$cpip} ";
530
		}
531
		$ipcount++;
532
	}
533
	$ips = "{ 255.255.255.255 or {$ips} }";
534
	$cprules .= "add {$rulenum} pass ip from any to {$ips} in\n";
535
	$rulenum++;
536
	$cprules .= "add {$rulenum} pass ip from {$ips} to any out\n";
537
	$rulenum++;
538
	$cprules .= "add {$rulenum} pass icmp from {$ips} to any out icmptype 0\n";
539
	$rulenum++;
540
	$cprules .= "add {$rulenum} pass icmp from any to {$ips} in icmptype 8 \n";
541
	$rulenum++;
542
	/* Allowed ips */
543
	$cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any in\n";
544
	$rulenum++;
545
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) in\n";
546
	$rulenum++;
547
	$cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any out\n";
548
	$rulenum++;
549
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) out\n";
550
	$rulenum++;
551

    
552
	/* Authenticated users rules. */
553
	$cprules .= "add {$rulenum} pipe tablearg ip from table(1) to any in\n";
554
	$rulenum++;
555
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(2) out\n";
556
	$rulenum++;
557

    
558
	$listenporthttp =
559
		$config['captiveportal'][$cpzone]['listenporthttp'] ?
560
		$config['captiveportal'][$cpzone]['listenporthttp'] :
561
		$config['captiveportal'][$cpzone]['zoneid'];
562

    
563
	if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
564
		$listenporthttps = $listenporthttp + 1;
565
		if ($config['captiveportal'][$cpzone]['auth_method'] == "saml") $listenporthttps = 443;
566
		# $cprules .= "add 65531 fwd 127.0.0.1,{$listenporthttps} tcp from any to any dst-port 443 in\n";
567
	}
568
	
569
	$cprules .= <<<EOD
570

    
571
# redirect non-authenticated clients to captive portal
572
add 65532 fwd 127.0.0.1,{$listenporthttp} tcp from any to any dst-port 80 in 
573
# let the responses from the captive portal web server back out
574
add 65533 pass tcp from any to any out
575
# block everything else
576
add 65534 deny all from any to any
577

    
578
EOD;
579

    
580
	/* generate passthru mac database */
581
	$cprules .= captiveportal_passthrumac_configure(true);
582
	$cprules .= "\n";
583

    
584
	/* allowed ipfw rules to make allowed ip work */
585
	$cprules .= captiveportal_allowedip_configure();
586

    
587
	/* allowed ipfw rules to make allowed hostnames work */
588
	$cprules .= captiveportal_allowedhostname_configure();
589
	
590
	/* load rules */
591
	$cprules = "flush\n{$cprules}";
592
	file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
593
	mwexec("/sbin/ipfw -x {$cpzone} -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
594
	//@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
595
	unset($cprules, $tmprules);
596

    
597
	if ($reinit == false)
598
		unlock($captiveportallck);
599
}
600

    
601
/* 
602
 * Remove clients that have been around for longer than the specified amount of time
603
 * db file structure:
604
 * timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time,interim_interval
605
 * (password is in Base64 and only saved when reauthentication is enabled)
606
 */
607
function captiveportal_prune_old() {
608
	global $g, $config, $cpzone;
609

    
610
	if (empty($cpzone))
611
		return;
612

    
613
	$cpcfg = $config['captiveportal'][$cpzone];
614
	$vcpcfg = $config['voucher'][$cpzone];
615

    
616
	/* check for expired entries */
617
	$idletimeout = 0;
618
	$timeout = 0;
619
	if (!empty($cpcfg['timeout']) && is_numeric($cpcfg['timeout']))
620
		$timeout = $cpcfg['timeout'] * 60;
621

    
622
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout']))
623
		$idletimeout = $cpcfg['idletimeout'] * 60;
624

    
625
	/* Is there any job to do? */
626
	if (!$timeout && !$idletimeout && !isset($cpcfg['reauthenticate']) &&
627
	    !isset($cpcfg['radiussession_timeout']) && !isset($vcpcfg['enable']))
628
		return;
629

    
630
	$radiussrvs = captiveportal_get_radius_servers();
631

    
632
	/* Read database */
633
	/* NOTE: while this can be simplified in non radius case keep as is for now */
634
	$cpdb = captiveportal_read_db();
635

    
636
	$unsetindexes = array();
637
	$voucher_needs_sync = false;
638
	/* 
639
	 * Snapshot the time here to use for calculation to speed up the process.
640
	 * If something is missed next run will catch it!
641
	 */
642
	$pruning_time = time();
643
	$stop_time = $pruning_time;
644
	foreach ($cpdb as $cpentry) {
645

    
646
		$timedout = false;
647
		$term_cause = 1;
648
		if (empty($cpentry[11]))
649
			$cpentry[11] = 'first';
650
		$radiusservers = $radiussrvs[$cpentry[11]];
651

    
652
		/* hard timeout? */
653
		if ($timeout) {
654
			if (($pruning_time - $cpentry[0]) >= $timeout) {
655
				$timedout = true;
656
				$term_cause = 5; // Session-Timeout
657
			}
658
		}
659

    
660
		/* Session-Terminate-Time */
661
		if (!$timedout && !empty($cpentry[9])) {
662
			if ($pruning_time >= $cpentry[9]) {
663
				$timedout = true;
664
				$term_cause = 5; // Session-Timeout
665
			}
666
		}
667

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

    
684
		/* if vouchers are configured, activate session timeouts */
685
		if (!$timedout && isset($vcpcfg['enable']) && !empty($cpentry[7])) {
686
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
687
				$timedout = true;
688
				$term_cause = 5; // Session-Timeout
689
				$voucher_needs_sync = true;
690
			}
691
		}
692

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

    
701
		if ($timedout) {
702
			captiveportal_disconnect($cpentry, $radiusservers,$term_cause,$stop_time);
703
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
704
			$unsetindexes[] = $cpentry[5];
705
		}
706

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

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

    
770
	captiveportal_prune_old_automac();
771

    
772
	if ($voucher_needs_sync == true)
773
		/* Triger a sync of the vouchers on config */
774
		send_event("service sync vouchers");
775

    
776
	/* write database */
777
	if (!empty($unsetindexes))
778
		captiveportal_remove_entries($unsetindexes);
779
}
780

    
781
function captiveportal_prune_old_automac() {
782
	global $g, $config, $cpzone;
783

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

    
845
/* remove a single client according to the DB entry */
846
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
847
	global $g, $config, $cpzone;
848

    
849
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
850

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

    
874
	/* 
875
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
876
	* We could get an error if the pipe doesn't exist but everything should still be fine
877
	*/
878
	if (!empty($dbent[1])) {
879
		$_gb = @pfSense_pipe_action("pipe delete {$dbent[1]}");
880
		$_gb = @pfSense_pipe_action("pipe delete " . ($dbent[1]+1));
881

    
882
		/* Release the ruleno so it can be reallocated to new clients. */
883
		captiveportal_free_dn_ruleno($dbent[1]);
884
	}
885

    
886
	// XMLRPC Call over to the master Voucher node
887
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
888
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
889
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
890
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
891
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
892
		$remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time);
893
	}
894

    
895
}
896

    
897
/* remove a single client by sessionid */
898
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
899
	global $g, $config;
900

    
901
	$radiusservers = captiveportal_get_radius_servers();
902

    
903
	/* read database */
904
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
905

    
906
	/* find entry */
907
	if (!empty($result)) {
908
		captiveportal_write_db("DELETE FROM captiveportal WHERE sessionid = '{$sessionid}'");
909

    
910
		foreach ($result as $cpentry) {
911
			if (empty($cpentry[11]))
912
				$cpentry[11] = 'first';
913
			captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], $term_cause);
914
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
915
		}
916
		unset($result);
917
	}
918
}
919

    
920
/* send RADIUS acct stop for all current clients */
921
function captiveportal_radius_stop_all() {
922
	global $config, $cpzone;
923

    
924
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable']))
925
		return;
926

    
927
	$radiusservers = captiveportal_get_radius_servers();
928
	if (!empty($radiusservers)) {
929
		$cpdb = captiveportal_read_db();
930
		foreach ($cpdb as $cpentry) {
931
			if (empty($cpentry[11]))
932
				$cpentry[11] = 'first';
933
			if (!empty($radiusservers[$cpentry[11]])) {
934
				RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
935
					$cpentry[4], // username
936
					$cpentry[5], // sessionid
937
					$cpentry[0], // start time
938
					$radiusservers[$cpentry[11]],
939
					$cpentry[2], // clientip
940
					$cpentry[3], // clientmac
941
					7); // Admin Reboot
942
			}
943
		}
944
	}
945
}
946

    
947
function captiveportal_passthrumac_configure_entry($macent) {
948
	global $cpzone, $config;
949

    
950
	$bwUp = 0;
951
	if (!empty($macent['bw_up']))
952
		$bwUp = $macent['bw_up'];
953
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultup']))
954
		$bwUp = $config['captiveportal'][$cpzone]['bwdefaultup'];
955
	$bwDown = 0;
956
	if (!empty($macent['bw_down']))
957
		$bwDown = $macent['bw_down'];
958
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultdn']))
959
		$bwDown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
960

    
961
	$ruleno = captiveportal_get_next_ipfw_ruleno();
962
	$pipeno = captiveportal_get_next_dn_ruleno();
963

    
964
	$rules = "";
965
	$pipeup = $pipeno;
966
	$_gb = @pfSense_pipe_action("pipe {$pipeup} config bw {$bwUp}Kbit/s queue 100 buckets 16");
967
	$pipedown = $pipeno + 1;
968
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
969
	$rules .= "add {$ruleno} pipe {$pipeup} ip from any to any MAC any {$macent['mac']}\n";
970
	$ruleno++;
971
	$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC {$macent['mac']} any\n";
972

    
973
	return $rules;
974
}
975

    
976
function captiveportal_passthrumac_configure($lock = false) {
977
	global $config, $g, $cpzone;
978

    
979
	$rules = "";
980

    
981
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
982
		$macdb = array();
983
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
984
			$rules .= captiveportal_passthrumac_configure_entry($macent);
985
			$macdb[$macent['mac']][$cpzone]['active']  = true;
986

    
987
		}
988
	}
989

    
990
	return $rules;
991
}
992

    
993
function captiveportal_passthrumac_findbyname($username) {
994
	global $config, $cpzone;
995

    
996
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
997
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
998
			if ($macent['username'] == $username)
999
				return $macent;
1000
		}
1001
	}
1002
	return NULL;
1003
}
1004

    
1005
/* 
1006
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1007
 */
1008
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
1009
	global $g;
1010

    
1011
	/*  Instead of copying this entire function for something
1012
	 *  easy such as hostname vs ip address add this check
1013
	 */
1014
	if ($ishostname === true) {
1015
		if (!$g['booting']) {
1016
			$ipaddress = gethostbyname($ipent['hostname']);
1017
			if (!is_ipaddr($ipaddress)) 
1018
				return;
1019
		} else
1020
			$ipaddress = "";
1021
	} else
1022
		$ipaddress = $ipent['ip'];
1023

    
1024
	$rules = "";
1025
	$cp_filterdns_conf = "";
1026
	$enBwup = 0;
1027
	if (!empty($ipent['bw_up']))
1028
		$enBwup = intval($ipent['bw_up']);
1029
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultup']))
1030
		$enBwup = $config['captiveportal'][$cpzone]['bwdefaultup'];
1031
	$enBwdown = 0;
1032
	if (!empty($ipent['bw_down']))
1033
		$enBwdown = intval($ipent['bw_down']);
1034
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultdn']))
1035
		$enBwdown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
1036

    
1037
	$pipeno = captiveportal_get_next_dn_ruleno();
1038
	$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$enBwup}Kbit/s queue 100 buckets 16");
1039
	$pipedown = $pipeno + 1;
1040
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16");
1041
	if ($ishostname === true) {
1042
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
1043
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
1044
		if (!is_ipaddr($ipaddress))
1045
			return array("", $cp_filterdns_conf);
1046
	}
1047
	$subnet = "";
1048
	if (!empty($ipent['sn']))
1049
		$subnet = "/{$ipent['sn']}";
1050
	$rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
1051
	$rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
1052

    
1053
	if ($ishostname === true)
1054
		return array($rules, $cp_filterdns_conf);
1055
	else
1056
		return $rules;
1057
}
1058

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

    
1062
	$rules = "";
1063
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1064
		$rules = "\n# captiveportal_allowedhostname_configure()\n";
1065
		$cp_filterdns_conf = "";
1066
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) {
1067
			$tmprules = captiveportal_allowedip_configure_entry($hostnameent, true);
1068
			$rules .= $tmprules[0];
1069
			$cp_filterdns_conf .= $tmprules[1];
1070
		}
1071
		$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1072
		@file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1073
		unset($cp_filterdns_conf);
1074
		if (isvalidpid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid"))
1075
			sigkillbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid", "HUP");
1076
		else
1077
			mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzone} -d 1");
1078
	} else {
1079
		killbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1080
		@unlink("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1081
	}
1082

    
1083
	return $rules;
1084
}
1085

    
1086
function captiveportal_allowedip_configure() {
1087
	global $config, $g, $cpzone;
1088

    
1089
	$rules = "";
1090
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1091
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1092
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1093
	}
1094

    
1095
	return $rules;
1096
}
1097

    
1098
/* get last activity timestamp given client IP address */
1099
function captiveportal_get_last_activity($ip) {
1100
	global $cpzone;
1101

    
1102
	$ipfwoutput = pfSense_ipfw_getTablestats($cpzone, 1, $ip);
1103
	/* Reading only from one of the tables is enough of approximation. */
1104
	if (is_array($ipfwoutput)) {
1105
		return $ipfwoutput['timestamp'];
1106
	}
1107

    
1108
	return 0;
1109
}
1110

    
1111
function captiveportal_init_radius_servers() {
1112
	global $config, $g, $cpzone;
1113

    
1114
	/* generate radius server database */
1115
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1116
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1117
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1118
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1119
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1120
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1121

    
1122
		if ($config['captiveportal'][$cpzone]['radiusport'])
1123
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1124
		else
1125
			$radiusport = 1812;
1126
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1127
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1128
		else
1129
			$radiusacctport = 1813;
1130
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1131
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1132
		else
1133
			$radiusport2 = 1812;
1134
		if ($config['captiveportal'][$cpzone]['radiusport3'])
1135
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1136
		else
1137
			$radiusport3 = 1812;
1138
		if ($config['captiveportal'][$cpzone]['radiusport4'])
1139
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1140
		else
1141
			$radiusport4 = 1812;
1142

    
1143
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1144
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1145
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1146
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1147

    
1148
		$cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX);
1149
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
1150
		if (!$fd) {
1151
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1152
			unlock($cprdsrvlck);
1153
			return 1;
1154
		}
1155
		if (isset($radiusip))
1156
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
1157
		if (isset($radiusip2))
1158
			fwrite($fd,"\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
1159
		if (isset($radiusip3))
1160
			fwrite($fd,"\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
1161
		if (isset($radiusip4))
1162
			fwrite($fd,"\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
1163
		
1164

    
1165
		fclose($fd);
1166
		unlock($cprdsrvlck);
1167
	}
1168
}
1169

    
1170
/* read RADIUS servers into array */
1171
function captiveportal_get_radius_servers() {
1172
	global $g, $cpzone;
1173

    
1174
	$cprdsrvlck = lock("captiveportalradius{$cpzone}");
1175
	if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
1176
		$radiusservers = array();
1177
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", 
1178
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1179
		if ($cpradiusdb) {
1180
			foreach($cpradiusdb as $cpradiusentry) {
1181
				$line = trim($cpradiusentry);
1182
				if ($line) {
1183
					$radsrv = array();
1184
						list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key'], $context) = explode(",",$line);
1185
				}
1186
				if (empty($context)) {
1187
					if (!is_array($radiusservers['first']))
1188
						$radiusservers['first'] = array();
1189
					$radiusservers['first'] = $radsrv;
1190
				} else {
1191
					if (!is_array($radiusservers[$context]))
1192
						$radiusservers[$context] = array();
1193
					$radiusservers[$context][] = $radsrv;
1194
				}
1195
			}
1196
		}
1197
		unlock($cprdsrvlck);
1198
		return $radiusservers;
1199
	}
1200

    
1201
	unlock($cprdsrvlck);
1202
	return false;
1203
}
1204

    
1205
/* log successful captive portal authentication to syslog */
1206
/* part of this code from php.net */
1207
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1208
	// Log it
1209
	if (!$message)
1210
		$message = "$status: $user, $mac, $ip";
1211
	else {
1212
		$message = trim($message);
1213
		$message = "$status: $user, $mac, $ip, $message";
1214
	}
1215
	captiveportal_syslog($message);
1216
}
1217

    
1218
/* log simple messages to syslog */
1219
function captiveportal_syslog($message) {
1220
	$message = trim($message);
1221
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1222
	// Log it
1223
	syslog(LOG_INFO, $message);
1224
	closelog();
1225
}
1226

    
1227
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1228
	global $g, $config;
1229

    
1230
	$pipeno = captiveportal_get_next_dn_ruleno();
1231

    
1232
	/* If the pool is empty, return appropriate message and fail authentication */
1233
	if (empty($pipeno)) {
1234
		$auth_list = array();
1235
		$auth_list['auth_val'] = 1;
1236
		$auth_list['error'] = "System reached maximum login capacity";
1237
		return $auth_list;
1238
	}
1239

    
1240
	$radiusservers = captiveportal_get_radius_servers();
1241

    
1242
	if (is_null($radiusctx))
1243
		$radiusctx = 'first';
1244

    
1245
	$auth_list = RADIUS_AUTHENTICATION($username,
1246
		$password,
1247
		$radiusservers[$radiusctx],
1248
		$clientip,
1249
		$clientmac,
1250
		$pipeno);
1251

    
1252
	if ($auth_list['auth_val'] == 2) {
1253
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1254
		$sessionid = portal_allow($clientip,
1255
			$clientmac,
1256
			$username,
1257
			$password,
1258
			$auth_list,
1259
			$pipeno,
1260
			$radiusctx);
1261
	} else {
1262
	         captiveportal_free_dn_ruleno($pipeno);
1263
	       }
1264

    
1265
	return $auth_list;
1266
}
1267

    
1268
function captiveportal_opendb() {
1269
	global $g, $cpzone;
1270

    
1271
	if (file_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db"))
1272
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1273
	else {
1274
		$errormsg = "";
1275
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1276
		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, radiusctx TEXT) ", $errormsg)) {
1277
			@sqlite_exec($DB, "CREATE UNIQUE INDEX idx_active ON captiveportal (sessionid, username)");
1278
			@sqlite_exec($DB, "CREATE INDEX user ON captiveportal (username)");
1279
			@sqlite_exec($DB, "CREATE INDEX ip ON captiveportal (ip)");
1280
			@sqlite_exec($DB, "CREATE INDEX starttime ON captiveportal (allow_time)");
1281
			@sqlite_exec($DB, "CREATE INDEX serviceid ON captiveportal (serviceid)");
1282
		} else
1283
			captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$errormsg}");
1284
	}
1285

    
1286
	return $DB;
1287
}
1288

    
1289
/* read captive portal DB into array */
1290
function captiveportal_read_db($query = "") {
1291

    
1292
	$DB = captiveportal_opendb();
1293
	if ($DB) {
1294
		sqlite_exec($DB, "BEGIN");
1295
		if (!empty($query))
1296
			$cpdb = @sqlite_array_query($DB, "SELECT * FROM captiveportal {$query}", SQLITE_NUM);
1297
		else {
1298
			$response = @sqlite_unbuffered_query($DB, "SELECT * FROM captiveportal", SQLITE_NUM);
1299
			$cpdb = @sqlite_fetch_all($response, SQLITE_NUM);
1300
		}
1301
		sqlite_exec($DB, "END");
1302
		@sqlite_close($DB);
1303
	}
1304
	if (!$cpdb)
1305
		$cpdb = array();
1306

    
1307
	return $cpdb;
1308
}
1309

    
1310
function captiveportal_remove_entries($remove) {
1311

    
1312
	if (!is_array($remove) || empty($remove))
1313
		return;
1314

    
1315
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1316
	foreach($remove as $idx => $unindex) {
1317
		$query .= "'{$unindex}'";
1318
		if ($idx < (count($remove) - 1))
1319
			$query .= ",";
1320
	}
1321
	$query .= ")";
1322
	captiveportal_write_db($query);
1323
}
1324

    
1325
/* write captive portal DB */
1326
function captiveportal_write_db($queries) {
1327
	global $g;
1328

    
1329
	if (is_array($queries))
1330
		$query = implode(";", $queries);
1331
	else
1332
		$query = $queries;
1333

    
1334
	$DB = captiveportal_opendb();
1335
	if ($DB) {
1336
		$error_msg = "";
1337
		sqlite_exec($DB, "BEGIN TRANSACTION");
1338
		$result = @sqlite_exec($DB, $query, $error_msg);
1339
		if (!$result)
1340
			captiveportal_syslog("Trying to modify DB returned error: {$error_msg}");
1341
		else
1342
			sqlite_exec($DB, "END TRANSACTION");
1343
		@sqlite_close($DB);
1344
		return $result;
1345
	} else
1346
		return true;
1347
}
1348

    
1349
function captiveportal_write_elements() {
1350
	global $g, $config, $cpzone;
1351
	
1352
	$cpcfg = $config['captiveportal'][$cpzone];
1353

    
1354
	if (!is_dir($g['captiveportal_element_path']))
1355
		@mkdir($g['captiveportal_element_path']);
1356

    
1357
	if (is_array($cpcfg['element'])) {
1358
		conf_mount_rw();
1359
		foreach ($cpcfg['element'] as $data) {
1360
			if (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1361
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1362
				return 1;
1363
			}
1364
			if (!file_exists("{$g['captiveportal_path']}/{$data['name']}"))
1365
				@symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1366
		}
1367
		conf_mount_ro();
1368
	}
1369
	
1370
	return 0;
1371
}
1372

    
1373
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1374
	global $cpzone;
1375

    
1376
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1377
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1378
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1379
		$ridx = $rulenos_start;
1380
		while ($ridx < $rulenos_range_max) {
1381
			if ($rules[$ridx] == $cpzone) {
1382
				$rules[$ridx] = false;
1383
				$ridx++;
1384
				$rules[$ridx] = false;
1385
				$ridx++;
1386
			} else
1387
				$ridx += 2;
1388
		}
1389
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1390
		unset($rules);
1391
	}
1392
	unlock($cpruleslck);
1393
}
1394

    
1395
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1396
	global $config, $g, $cpzone;
1397

    
1398
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1399
	$ruleno = 0;
1400
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1401
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1402
		$ridx = $rulenos_start;
1403
		while ($ridx < $rulenos_range_max) {
1404
			if (empty($rules[$ridx])) {
1405
				$ruleno = $ridx;
1406
				$rules[$ridx] = $cpzone;
1407
				$ridx++;
1408
				$rules[$ridx] = $cpzone;
1409
				break;
1410
			} else {
1411
				$ridx += 2;
1412
			}
1413
		}
1414
	} else {
1415
		$rules = array_pad(array(), $rulenos_range_max, false);
1416
		$ruleno = $rulenos_start;
1417
		$rules[$rulenos_start] = $cpzone;
1418
		$rulenos_start++;
1419
		$rules[$rulenos_start] = $cpzone;
1420
	}
1421
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1422
	unlock($cpruleslck);
1423
	unset($rules);
1424

    
1425
	return $ruleno;
1426
}
1427

    
1428
function captiveportal_free_dn_ruleno($ruleno) {
1429
	global $config, $g;
1430

    
1431
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1432
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1433
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1434
		$rules[$ruleno] = false;
1435
		$ruleno++;
1436
		$rules[$ruleno] = false;
1437
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1438
		unset($rules);
1439
	}
1440
	unlock($cpruleslck);
1441
}
1442

    
1443
function captiveportal_get_dn_passthru_ruleno($value) {
1444
	global $config, $g, $cpzone;
1445

    
1446
	$cpcfg = $config['captiveportal'][$cpzone];
1447
	if(!isset($cpcfg['enable']))
1448
		return NULL;
1449

    
1450
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1451
	$ruleno = NULL;
1452
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1453
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1454
		unset($output);
1455
		$_gb = exec("/sbin/ipfw -x {$cpzone} show | /usr/bin/grep {$value} | /usr/bin/grep -v grep | /usr/bin/awk '{print $5}' | /usr/bin/head -n 1", $output);
1456
		$ruleno = intval($output[0]);
1457
		if (!$rules[$ruleno])
1458
			$ruleno = NULL;
1459
		unset($rules);
1460
	}
1461
	unlock($cpruleslck);
1462

    
1463
	return $ruleno;
1464
}
1465

    
1466
/*
1467
 * This function will calculate the lowest free firewall ruleno
1468
 * within the range specified based on the actual logged on users
1469
 *
1470
 */
1471
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
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
	$ruleno = 0;
1480
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1481
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1482
		$ridx = $rulenos_start;
1483
		while ($ridx < $rulenos_range_max) {
1484
			if (empty($rules[$ridx])) {
1485
				$ruleno = $ridx;
1486
				$rules[$ridx] = $cpzone;
1487
				$ridx++;
1488
				$rules[$ridx] = $cpzone;
1489
				break;
1490
			} else {
1491
				/* 
1492
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1493
				 * and the out pipe ruleno + 1.
1494
				 */
1495
				$ridx += 2;
1496
			}
1497
		}
1498
	} else {
1499
		$rules = array_pad(array(), $rulenos_range_max, false);
1500
		$ruleno = $rulenos_start;
1501
		$rules[$rulenos_start] = $cpzone;
1502
		$rulenos_start++;
1503
		$rules[$rulenos_start] = $cpzone;
1504
	}
1505
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1506
	unlock($cpruleslck);
1507
	unset($rules);
1508

    
1509
	return $ruleno;
1510
}
1511

    
1512
function captiveportal_free_ipfw_ruleno($ruleno) {
1513
	global $config, $g, $cpzone;
1514

    
1515
	$cpcfg = $config['captiveportal'][$cpzone];
1516
	if(!isset($cpcfg['enable']))
1517
		return NULL;
1518

    
1519
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1520
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1521
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1522
		$rules[$ruleno] = false;
1523
		$ruleno++;
1524
		$rules[$ruleno] = false;
1525
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1526
	}
1527
	unlock($cpruleslck);
1528
	unset($rules);
1529
}
1530

    
1531
function captiveportal_get_ipfw_passthru_ruleno($value) {
1532
	global $config, $g, $cpzone;
1533

    
1534
	$cpcfg = $config['captiveportal'][$cpzone];
1535
	if(!isset($cpcfg['enable']))
1536
		return NULL;
1537

    
1538
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1539
	$ruleno = NULL;
1540
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1541
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1542
		unset($output);
1543
		$_gb = exec("/sbin/ipfw -x {$cpzone} show | /usr/bin/grep {$value} | /usr/bin/grep -v grep | /usr/bin/awk '{print $1}' | /usr/bin/head -n 1", $output);
1544
		$ruleno = intval($output[0]);
1545
		if (!$rules[$ruleno])
1546
			$ruleno = NULL;
1547
	}
1548
	unlock($cpruleslck);
1549
	unset($rules);
1550

    
1551
	return $ruleno;
1552
}
1553

    
1554
/**
1555
 * This function will calculate the traffic produced by a client
1556
 * based on its firewall rule
1557
 *
1558
 * Point of view: NAS
1559
 *
1560
 * Input means: from the client
1561
 * Output means: to the client
1562
 *
1563
 */
1564

    
1565
function getVolume($ip) {
1566
	global $config, $cpzone;
1567

    
1568
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1569
	$volume = array();
1570
	// Initialize vars properly, since we don't want NULL vars
1571
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1572

    
1573
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 1, $ip);
1574
	if (is_array($ipfw)) {
1575
		if ($reverse) {
1576
			$volume['output_pkts'] = $ipfw['packets'];
1577
			$volume['output_bytes'] = $ipfw['bytes'];
1578
		}
1579
		else {
1580
			$volume['input_pkts'] = $ipfw['packets'];
1581
			$volume['input_bytes'] = $ipfw['bytes'];
1582
		}
1583
	}
1584

    
1585
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 2, $ip);
1586
	if (is_array($ipfw)) {
1587
		if ($reverse) {
1588
			$volume['input_pkts'] = $ipfw['packets'];
1589
			$volume['input_bytes'] = $ipfw['bytes'];
1590
		}
1591
		else {
1592
			$volume['output_pkts'] = $ipfw['packets'];
1593
			$volume['output_bytes'] = $ipfw['bytes'];
1594
		}
1595
	}
1596

    
1597
	return $volume;
1598
}
1599

    
1600
/**
1601
 * Get the NAS-IP-Address based on the current wan address
1602
 *
1603
 * Use functions in interfaces.inc to find this out
1604
 *
1605
 */
1606

    
1607
function getNasIP()
1608
{
1609
	global $config, $cpzone;
1610

    
1611
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1612
			$nasIp = get_interface_ip();
1613
	} else {
1614
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1615
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1616
		else
1617
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1618
	}
1619
		
1620
	if(!is_ipaddr($nasIp))
1621
		$nasIp = "0.0.0.0";
1622

    
1623
	return $nasIp;
1624
}
1625

    
1626
function portal_ip_from_client_ip($cliip) {
1627
	global $config, $cpzone;
1628

    
1629
	$isipv6 = is_ipaddrv6($cliip);
1630
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1631
	foreach ($interfaces as $cpif) {
1632
		if ($isipv6) {
1633
			$ip = get_interface_ipv6($cpif);
1634
			$sn = get_interface_subnetv6($cpif);
1635
		} else {
1636
			$ip = get_interface_ip($cpif);
1637
			$sn = get_interface_subnet($cpif);
1638
		}
1639
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1640
			return $ip;
1641
	}
1642

    
1643
	$inet = ($isipv6) ? '-inet6' : '-inet';
1644
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1645
	$iface = trim($iface, "\n");
1646
	if (!empty($iface)) {
1647
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1648
		if (is_ipaddr($ip))
1649
			return $ip;
1650
	}
1651

    
1652
	// doesn't match up to any particular interface
1653
	// so let's set the portal IP to what PHP says 
1654
	// the server IP issuing the request is. 
1655
	// allows same behavior as 1.2.x where IP isn't 
1656
	// in the subnet of any CP interface (static routes, etc.)
1657
	// rather than forcing to DNS hostname resolution
1658
	$ip = $_SERVER['SERVER_ADDR'];
1659
	if (is_ipaddr($ip))
1660
		return $ip;
1661

    
1662
	return false;
1663
}
1664

    
1665
function portal_hostname_from_client_ip($cliip) {
1666
	global $config, $cpzone;
1667

    
1668
	$cpcfg = $config['captiveportal'][$cpzone];
1669

    
1670
	if (isset($cpcfg['httpslogin'])) {
1671
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
1672
		if ($cpcfg['auth_method'] == "saml") $listenporthttps = 443;
1673
		$ourhostname = $cpcfg['httpsname'];
1674
		
1675
		if ($listenporthttps != 443)
1676
			$ourhostname .= ":" . $listenporthttps;
1677
	} else {
1678
		$listenporthttp  = $cpcfg['listenporthttp']  ? $cpcfg['listenporthttp']  : $cpcfg['zoneid'];
1679
		$ifip = portal_ip_from_client_ip($cliip);
1680
		if (!$ifip)
1681
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1682
		else
1683
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1684
		
1685
		if ($listenporthttp != 80)
1686
			$ourhostname .= ":" . $listenporthttp;
1687
	}
1688
	
1689
	return $ourhostname;
1690
}
1691

    
1692
/* functions move from index.php */
1693

    
1694
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1695
	global $g, $config, $cpzone;
1696

    
1697
	/* Get captive portal layout */
1698
	if ($type == "redir") {
1699
		header("Location: {$redirurl}");
1700
		return;
1701
	} else if ($type == "login")
1702
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1703
	else
1704
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1705

    
1706
	$cpcfg = $config['captiveportal'][$cpzone];
1707

    
1708
	/* substitute the PORTAL_REDIRURL variable */
1709
	if ($cpcfg['preauthurl']) {
1710
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1711
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1712
	}
1713

    
1714
	/* substitute other variables */
1715
	$ourhostname = portal_hostname_from_client_ip($clientip);
1716
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1717
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1718
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1719

    
1720
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1721
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1722
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1723
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1724
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1725

    
1726
	// Special handling case for captive portal master page so that it can be ran 
1727
	// through the PHP interpreter using the include method above.  We convert the
1728
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1729
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1730
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1731
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1732
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1733
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1734
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1735
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1736

    
1737
	echo $htmltext;
1738
}
1739

    
1740
function portal_mac_radius($clientmac,$clientip) {
1741
	global $config, $cpzone;
1742

    
1743
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1744

    
1745
	/* authentication against the radius server */
1746
	$username = mac_format($clientmac);
1747
	$auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1748
	if ($auth_list['auth_val'] == 2)
1749
		return TRUE;
1750

    
1751
	if (!empty($auth_list['url_redirection']))
1752
		portal_reply_page($auth_list['url_redirection'], "redir");
1753

    
1754
	return FALSE;
1755
}
1756

    
1757
function captiveportal_reapply_attributes($cpentry, $attributes) {
1758
	global $config, $cpzone, $g;
1759

    
1760
	$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1761
	$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1762
	$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1763
	$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1764
	$bw_up_pipeno = $cpentry[1];
1765
	$bw_down_pipeno = $cpentry[1]+1;
1766

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

    
1771
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1772
}
1773

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

    
1777
	// Ensure we create an array if we are missing attributes
1778
	if (!is_array($attributes))
1779
		$attributes = array();
1780

    
1781
	unset($sessionid);
1782

    
1783
	/* Do not allow concurrent login execution. */
1784
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1785

    
1786
	if ($attributes['voucher'])
1787
		$remaining_time = $attributes['session_timeout'];
1788

    
1789
	$writecfg = false;
1790
	/* Find an existing session */
1791
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
1792
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1793
			$mac = captiveportal_passthrumac_findbyname($username);
1794
			if (!empty($mac)) {
1795
				if ($_POST['replacemacpassthru']) {
1796
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
1797
						if ($macent['mac'] == $mac['mac']) {
1798
							$macrules = "";
1799
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1800
							$pipeno = captiveportal_get_dn_passthru_ruleno($mac['mac']);
1801
							if ($ruleno) {
1802
								captiveportal_free_ipfw_ruleno($ruleno);
1803
								$macrules .= "delete {$ruleno}\n";
1804
								++$ruleno;
1805
								$macrules .= "delete {$ruleno}\n";
1806
							}
1807
							if ($pipeno) {
1808
								captiveportal_free_dn_ruleno($pipeno);
1809
								$macrules .= "pipe delete {$pipeno}\n";
1810
								++$pipeno;
1811
								$macrules .= "pipe delete {$pipeno}\n";
1812
							}
1813
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1814
							$mac['mac'] = $clientmac;
1815
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1816
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1817
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1818
							mwexec("/sbin/ipfw -x {$cpzone} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1819
							$writecfg = true;
1820
							$sessionid = true;
1821
							break;
1822
						}
1823
					}
1824
				} else {
1825
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1826
						$clientmac, $clientip, $username, $password);
1827
					unlock($cpdblck);
1828
					return;
1829
				}
1830
			}
1831
		}
1832
	}
1833

    
1834
	/* read in client database */
1835
	$query = "WHERE ip = '{$clientip}'";
1836
	$tmpusername = strtolower($username);
1837
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins']))
1838
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
1839
	$cpdb = captiveportal_read_db($query);
1840

    
1841
	/* Snapshot the timestamp */
1842
	$allow_time = time();
1843
	$radiusservers = captiveportal_get_radius_servers();
1844
	$unsetindexes = array();
1845
	if (is_null($radiusctx))
1846
		$radiusctx = 'first';
1847

    
1848
	foreach ($cpdb as $cpentry) {
1849
		if (empty($cpentry[11])) {
1850
			$cpentry[11] = 'first';
1851
		}
1852
		/* on the same ip */
1853
		if ($cpentry[2] == $clientip) {
1854
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac)
1855
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1856
			else
1857
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
1858
			$sessionid = $cpentry[5];
1859
			break;
1860
		}
1861
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1862
			// user logged in with an active voucher. Check for how long and calculate 
1863
			// how much time we can give him (voucher credit - used time)
1864
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1865
			if ($remaining_time < 0)    // just in case. 
1866
				$remaining_time = 0;
1867

    
1868
			/* This user was already logged in so we disconnect the old one */
1869
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[11]],13);
1870
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1871
			$unsetindexes[] = $cpentry[5];
1872
			break;
1873
		}
1874
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1875
			/* on the same username */
1876
			if (strcasecmp($cpentry[4], $username) == 0) {
1877
				/* This user was already logged in so we disconnect the old one */
1878
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[11]],13);
1879
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1880
				$unsetindexes[] = $cpentry[5];
1881
				break;
1882
			}
1883
		}
1884
	}
1885
	unset($cpdb);
1886

    
1887
	if (!empty($unsetindexes))
1888
		captiveportal_remove_entries($unsetindexes);
1889

    
1890
	if ($attributes['voucher'] && $remaining_time <= 0)
1891
		return 0;       // voucher already used and no time left
1892

    
1893
	if (!isset($sessionid)) {
1894
		/* generate unique session ID */
1895
		$tod = gettimeofday();
1896
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1897

    
1898
		if ($passthrumac) {
1899
			$mac = array();
1900
			$mac['mac'] = $clientmac;
1901
			$mac['ip'] = $clientip; /* Used only for logging */
1902
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
1903
				$mac['username'] = $username;
1904
				if ($attributes['voucher'])
1905
					$mac['logintype'] = "voucher";
1906
			}
1907
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1908
			if (!empty($bw_up))
1909
				$mac['bw_up'] = $bw_up;
1910
			if (!empty($bw_down))
1911
				$mac['bw_down'] = $bw_down;
1912
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
1913
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
1914
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1915
			unlock($cpdblck);
1916
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1917
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1918
			mwexec("/sbin/ipfw -x {$cpzone} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1919
			$writecfg = true;
1920
		} else {
1921
			/* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
1922
			if (is_null($pipeno))
1923
				$pipeno = captiveportal_get_next_dn_ruleno();
1924

    
1925
			/* if the pool is empty, return appropriate message and exit */
1926
			if (is_null($pipeno)) {
1927
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1928
				log_error("WARNING!  Captive portal has reached maximum login capacity");
1929
				unlock($cpdblck);
1930
				return;
1931
			}
1932

    
1933
			$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1934
			$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1935
			$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1936
			$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1937

    
1938
			$bw_up_pipeno = $pipeno;
1939
			$bw_down_pipeno = $pipeno + 1;
1940
			//$bw_up /= 1000; // Scale to Kbit/s
1941
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1942
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1943

    
1944
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
1945
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1946
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
1947
			else
1948
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
1949

    
1950
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1951
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
1952
			else
1953
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
1954

    
1955
			if ($attributes['voucher'])
1956
				$attributes['session_timeout'] = $remaining_time;
1957
			
1958
			/* handle empty attributes */
1959
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
1960
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
1961
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
1962
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
1963

    
1964
			/* escape username */
1965
			$safe_username = sqlite_escape_string($username);
1966

    
1967
			/* encode password in Base64 just in case it contains commas */
1968
			$bpassword = base64_encode($password);
1969
			$insertquery  = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, radiusctx) ";
1970
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
1971
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, '{$radiusctx}')";
1972

    
1973
			/* store information to database */
1974
			captiveportal_write_db($insertquery);
1975
			unlock($cpdblck);
1976
			unset($insertquery, $bpassword);
1977

    
1978
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
1979
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
1980
				if ($acct_val == 1)
1981
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1982
			}
1983
		}
1984
	} else {
1985
		/* NOTE: #3062-11 If the pipeno has been allocated free it to not DoS the CP and maintain proper operation as in radius() case */
1986
		if (!is_null($pipeno))
1987
			captiveportal_free_dn_ruleno($pipeno);
1988

    
1989
		unlock($cpdblck);
1990
	}
1991

    
1992
	if ($writecfg == true)
1993
		write_config();
1994

    
1995
	/* redirect user to desired destination */
1996
	if (!empty($attributes['url_redirection']))
1997
		$my_redirurl = $attributes['url_redirection'];
1998
	else if (!empty($redirurl))
1999
		$my_redirurl = $redirurl;
2000
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
2001
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
2002

    
2003
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
2004
		$ourhostname = portal_hostname_from_client_ip($clientip);
2005
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
2006
		$logouturl = "{$protocol}{$ourhostname}/";
2007

    
2008
		if (isset($attributes['reply_message']))
2009
			$message = $attributes['reply_message'];
2010
		else
2011
			$message = 0;
2012

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

    
2015
	} else {
2016
		portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
2017
	}
2018

    
2019
	return $sessionid;
2020
}
2021

    
2022

    
2023
/*
2024
 * Used for when pass-through credits are enabled.
2025
 * Returns true when there was at least one free login to deduct for the MAC.
2026
 * Expired entries are removed as they are seen.
2027
 * Active entries are updated according to the configuration.
2028
 */
2029
function portal_consume_passthrough_credit($clientmac) {
2030
	global $config, $cpzone;
2031

    
2032
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
2033
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
2034
	else
2035
		return false;
2036

    
2037
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
2038
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2039
	else
2040
		return false;
2041

    
2042
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
2043
		return false;
2044

    
2045
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2046

    
2047
	/*
2048
	 * Read database of used MACs.  Lines are a comma-separated list
2049
	 * of the time, MAC, then the count of pass-through credits remaining.
2050
	 */
2051
	$usedmacs = captiveportal_read_usedmacs_db();
2052

    
2053
	$currenttime = time();
2054
	$found = false;
2055
	foreach ($usedmacs as $key => $usedmac) {
2056
		$usedmac = explode(",", $usedmac);
2057

    
2058
		if ($usedmac[1] == $clientmac) {
2059
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2060
				if ($usedmac[2] < 1) {
2061
					if ($updatetimeouts) {
2062
						$usedmac[0] = $currenttime;
2063
						unset($usedmacs[$key]);
2064
						$usedmacs[] = implode(",", $usedmac);
2065
						captiveportal_write_usedmacs_db($usedmacs);
2066
					}
2067

    
2068
					return false;
2069
				} else {
2070
					$usedmac[2] -= 1;
2071
					$usedmacs[$key] = implode(",", $usedmac);
2072
				}
2073

    
2074
				$found = true;
2075
			} else
2076
				unset($usedmacs[$key]);
2077

    
2078
			break;
2079
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2080
				unset($usedmacs[$key]);
2081
	}
2082

    
2083
	if (!$found) {
2084
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2085
		$usedmacs[] = implode(",", $usedmac);
2086
	}
2087

    
2088
	captiveportal_write_usedmacs_db($usedmacs);
2089
	return true;
2090
}
2091

    
2092
function captiveportal_read_usedmacs_db() {
2093
	global $g, $cpzone;
2094

    
2095
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2096
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2097
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2098
		if (!$usedmacs)
2099
			$usedmacs = array();
2100
	} else
2101
		$usedmacs = array();
2102

    
2103
	unlock($cpumaclck);
2104
	return $usedmacs;
2105
}
2106

    
2107
function captiveportal_write_usedmacs_db($usedmacs) {
2108
	global $g, $cpzone;
2109

    
2110
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2111
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2112
	unlock($cpumaclck);
2113
}
2114

    
2115
function captiveportal_send_server_accounting($off = false) {
2116
	global $cpzone, $config;
2117

    
2118
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
2119
		return;
2120
	}
2121
	if ($off) {
2122
		$racct = new Auth_RADIUS_Acct_Off;
2123
	} else {
2124
		$racct = new Auth_RADIUS_Acct_On;
2125
	}
2126
	$radiusservers = captiveportal_get_radius_servers();
2127
	if (empty($radiusservers)) {
2128
		return;
2129
	}
2130
	foreach ($radiusservers['first'] as $radsrv) {
2131
		// Add a new server to our instance
2132
		$racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
2133
	}
2134
	if (PEAR::isError($racct->start())) {
2135
		$retvalue['acct_val'] = 1;
2136
		$retvalue['error'] = $racct->getMessage();
2137

    
2138
		// If we encounter an error immediately stop this function and go back
2139
		$racct->close();
2140
		return $retvalue;
2141
	}
2142
	// Send request
2143
	$result = $racct->send();
2144
	// Evaluation of the response
2145
	// 5 -> Accounting-Response
2146
	// See RFC2866 for this.
2147
	if (PEAR::isError($result)) {
2148
		$retvalue['acct_val'] = 1;
2149
		$retvalue['error'] = $result->getMessage();
2150
	} else if ($result === true) {
2151
		$retvalue['acct_val'] = 5 ;
2152
	} else {
2153
		$retvalue['acct_val'] = 1 ;
2154
	}
2155

    
2156
	$racct->close();
2157
	return $retvalue;
2158
}
2159
?>
(8-8/67)