Projet

Général

Profil

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

univnautes / etc / inc / captiveportal.inc @ 41fac9b9

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
        /* allowed ipfw rules for univnautes whitelist */
591
	$cprules .= <<<EOD
592
# accept every traffic from table 42 = univnautes whitelist
593
add 65500 allow ip from any to table(42) in
594
add 65501 allow ip from table(42) to any out
595
table 42 flush
596
EOD;
597
	
598
	/* load rules */
599
	$cprules = "flush\n{$cprules}";
600
	file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
601
	mwexec("/sbin/ipfw -x {$cpzone} -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
602
	//@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
603
	unset($cprules, $tmprules);
604

    
605
	if ($reinit == false)
606
		unlock($captiveportallck);
607
}
608

    
609
/* 
610
 * Remove clients that have been around for longer than the specified amount of time
611
 * db file structure:
612
 * timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time,interim_interval
613
 * (password is in Base64 and only saved when reauthentication is enabled)
614
 */
615
function captiveportal_prune_old() {
616
	global $g, $config, $cpzone;
617

    
618
	if (empty($cpzone))
619
		return;
620

    
621
	$cpcfg = $config['captiveportal'][$cpzone];
622
	$vcpcfg = $config['voucher'][$cpzone];
623

    
624
	/* check for expired entries */
625
	$idletimeout = 0;
626
	$timeout = 0;
627
	if (!empty($cpcfg['timeout']) && is_numeric($cpcfg['timeout']))
628
		$timeout = $cpcfg['timeout'] * 60;
629

    
630
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout']))
631
		$idletimeout = $cpcfg['idletimeout'] * 60;
632

    
633
	/* Is there any job to do? */
634
	if (!$timeout && !$idletimeout && !isset($cpcfg['reauthenticate']) &&
635
	    !isset($cpcfg['radiussession_timeout']) && !isset($vcpcfg['enable']))
636
		return;
637

    
638
	$radiussrvs = captiveportal_get_radius_servers();
639

    
640
	/* Read database */
641
	/* NOTE: while this can be simplified in non radius case keep as is for now */
642
	$cpdb = captiveportal_read_db();
643

    
644
	$unsetindexes = array();
645
	$voucher_needs_sync = false;
646
	/* 
647
	 * Snapshot the time here to use for calculation to speed up the process.
648
	 * If something is missed next run will catch it!
649
	 */
650
	$pruning_time = time();
651
	$stop_time = $pruning_time;
652
	foreach ($cpdb as $cpentry) {
653

    
654
		$timedout = false;
655
		$term_cause = 1;
656
		if (empty($cpentry[11]))
657
			$cpentry[11] = 'first';
658
		$radiusservers = $radiussrvs[$cpentry[11]];
659

    
660
		/* hard timeout? */
661
		if ($timeout) {
662
			if (($pruning_time - $cpentry[0]) >= $timeout) {
663
				$timedout = true;
664
				$term_cause = 5; // Session-Timeout
665
			}
666
		}
667

    
668
		/* Session-Terminate-Time */
669
		if (!$timedout && !empty($cpentry[9])) {
670
			if ($pruning_time >= $cpentry[9]) {
671
				$timedout = true;
672
				$term_cause = 5; // Session-Timeout
673
			}
674
		}
675

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

    
692
		/* if vouchers are configured, activate session timeouts */
693
		if (!$timedout && isset($vcpcfg['enable']) && !empty($cpentry[7])) {
694
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
695
				$timedout = true;
696
				$term_cause = 5; // Session-Timeout
697
				$voucher_needs_sync = true;
698
			}
699
		}
700

    
701
		/* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
702
		if (!$timedout && isset($cpcfg['radiussession_timeout']) && !empty($cpentry[7])) {
703
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
704
				$timedout = true;
705
				$term_cause = 5; // Session-Timeout
706
			}
707
		}
708

    
709
		if ($timedout) {
710
			captiveportal_disconnect($cpentry, $radiusservers,$term_cause,$stop_time);
711
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
712
			$unsetindexes[] = $cpentry[5];
713
		}
714

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

    
759
			/* check this user against RADIUS again */
760
			if (isset($cpcfg['reauthenticate'])) {
761
				$auth_list = RADIUS_AUTHENTICATION($cpentry[4], // username
762
					base64_decode($cpentry[6]), // password
763
					$radiusservers,
764
					$cpentry[2], // clientip
765
					$cpentry[3], // clientmac
766
					$cpentry[1]); // ruleno
767
				if ($auth_list['auth_val'] == 3) {
768
					captiveportal_disconnect($cpentry, $radiusservers, 17);
769
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
770
					$unsetindexes[] = $cpentry[5];
771
				} else if ($auth_list['auth_val'] == 2)
772
					captiveportal_reapply_attributes($cpentry, $auth_list);
773
			}
774
		}
775
	}
776
	unset($cpdb);
777

    
778
	captiveportal_prune_old_automac();
779

    
780
	if ($voucher_needs_sync == true)
781
		/* Triger a sync of the vouchers on config */
782
		send_event("service sync vouchers");
783

    
784
	/* write database */
785
	if (!empty($unsetindexes))
786
		captiveportal_remove_entries($unsetindexes);
787
}
788

    
789
function captiveportal_prune_old_automac() {
790
	global $g, $config, $cpzone;
791

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

    
853
/* remove a single client according to the DB entry */
854
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
855
	global $g, $config, $cpzone;
856

    
857
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
858

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

    
882
	/* 
883
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
884
	* We could get an error if the pipe doesn't exist but everything should still be fine
885
	*/
886
	if (!empty($dbent[1])) {
887
		$_gb = @pfSense_pipe_action("pipe delete {$dbent[1]}");
888
		$_gb = @pfSense_pipe_action("pipe delete " . ($dbent[1]+1));
889

    
890
		/* Release the ruleno so it can be reallocated to new clients. */
891
		captiveportal_free_dn_ruleno($dbent[1]);
892
	}
893

    
894
	// XMLRPC Call over to the master Voucher node
895
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
896
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
897
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
898
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
899
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
900
		$remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time);
901
	}
902

    
903
}
904

    
905
/* remove a single client by sessionid */
906
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
907
	global $g, $config;
908

    
909
	$radiusservers = captiveportal_get_radius_servers();
910

    
911
	/* read database */
912
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
913

    
914
	/* find entry */
915
	if (!empty($result)) {
916
		captiveportal_write_db("DELETE FROM captiveportal WHERE sessionid = '{$sessionid}'");
917

    
918
		foreach ($result as $cpentry) {
919
			if (empty($cpentry[11]))
920
				$cpentry[11] = 'first';
921
			captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], $term_cause);
922
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
923
		}
924
		unset($result);
925
	}
926
}
927

    
928
/* send RADIUS acct stop for all current clients */
929
function captiveportal_radius_stop_all() {
930
	global $config, $cpzone;
931

    
932
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable']))
933
		return;
934

    
935
	$radiusservers = captiveportal_get_radius_servers();
936
	if (!empty($radiusservers)) {
937
		$cpdb = captiveportal_read_db();
938
		foreach ($cpdb as $cpentry) {
939
			if (empty($cpentry[11]))
940
				$cpentry[11] = 'first';
941
			if (!empty($radiusservers[$cpentry[11]])) {
942
				RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
943
					$cpentry[4], // username
944
					$cpentry[5], // sessionid
945
					$cpentry[0], // start time
946
					$radiusservers[$cpentry[11]],
947
					$cpentry[2], // clientip
948
					$cpentry[3], // clientmac
949
					7); // Admin Reboot
950
			}
951
		}
952
	}
953
}
954

    
955
function captiveportal_passthrumac_configure_entry($macent) {
956
	global $cpzone, $config;
957

    
958
	$bwUp = 0;
959
	if (!empty($macent['bw_up']))
960
		$bwUp = $macent['bw_up'];
961
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultup']))
962
		$bwUp = $config['captiveportal'][$cpzone]['bwdefaultup'];
963
	$bwDown = 0;
964
	if (!empty($macent['bw_down']))
965
		$bwDown = $macent['bw_down'];
966
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultdn']))
967
		$bwDown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
968

    
969
	$ruleno = captiveportal_get_next_ipfw_ruleno();
970
	$pipeno = captiveportal_get_next_dn_ruleno();
971

    
972
	$rules = "";
973
	$pipeup = $pipeno;
974
	$_gb = @pfSense_pipe_action("pipe {$pipeup} config bw {$bwUp}Kbit/s queue 100 buckets 16");
975
	$pipedown = $pipeno + 1;
976
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
977
	$rules .= "add {$ruleno} pipe {$pipeup} ip from any to any MAC any {$macent['mac']}\n";
978
	$ruleno++;
979
	$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC {$macent['mac']} any\n";
980

    
981
	return $rules;
982
}
983

    
984
function captiveportal_passthrumac_configure($lock = false) {
985
	global $config, $g, $cpzone;
986

    
987
	$rules = "";
988

    
989
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
990
		$macdb = array();
991
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
992
			$rules .= captiveportal_passthrumac_configure_entry($macent);
993
			$macdb[$macent['mac']][$cpzone]['active']  = true;
994

    
995
		}
996
	}
997

    
998
	return $rules;
999
}
1000

    
1001
function captiveportal_passthrumac_findbyname($username) {
1002
	global $config, $cpzone;
1003

    
1004
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1005
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1006
			if ($macent['username'] == $username)
1007
				return $macent;
1008
		}
1009
	}
1010
	return NULL;
1011
}
1012

    
1013
/* 
1014
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1015
 */
1016
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
1017
	global $g;
1018

    
1019
	/*  Instead of copying this entire function for something
1020
	 *  easy such as hostname vs ip address add this check
1021
	 */
1022
	if ($ishostname === true) {
1023
		if (!$g['booting']) {
1024
			$ipaddress = gethostbyname($ipent['hostname']);
1025
			if (!is_ipaddr($ipaddress)) 
1026
				return;
1027
		} else
1028
			$ipaddress = "";
1029
	} else
1030
		$ipaddress = $ipent['ip'];
1031

    
1032
	$rules = "";
1033
	$cp_filterdns_conf = "";
1034
	$enBwup = 0;
1035
	if (!empty($ipent['bw_up']))
1036
		$enBwup = intval($ipent['bw_up']);
1037
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultup']))
1038
		$enBwup = $config['captiveportal'][$cpzone]['bwdefaultup'];
1039
	$enBwdown = 0;
1040
	if (!empty($ipent['bw_down']))
1041
		$enBwdown = intval($ipent['bw_down']);
1042
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultdn']))
1043
		$enBwdown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
1044

    
1045
	$pipeno = captiveportal_get_next_dn_ruleno();
1046
	$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$enBwup}Kbit/s queue 100 buckets 16");
1047
	$pipedown = $pipeno + 1;
1048
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16");
1049
	if ($ishostname === true) {
1050
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
1051
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
1052
		if (!is_ipaddr($ipaddress))
1053
			return array("", $cp_filterdns_conf);
1054
	}
1055
	$subnet = "";
1056
	if (!empty($ipent['sn']))
1057
		$subnet = "/{$ipent['sn']}";
1058
	$rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
1059
	$rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
1060

    
1061
	if ($ishostname === true)
1062
		return array($rules, $cp_filterdns_conf);
1063
	else
1064
		return $rules;
1065
}
1066

    
1067
function captiveportal_allowedhostname_configure() {
1068
	global $config, $g, $cpzone;
1069

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

    
1091
	return $rules;
1092
}
1093

    
1094
function captiveportal_allowedip_configure() {
1095
	global $config, $g, $cpzone;
1096

    
1097
	$rules = "";
1098
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1099
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1100
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1101
	}
1102

    
1103
	return $rules;
1104
}
1105

    
1106
/* get last activity timestamp given client IP address */
1107
function captiveportal_get_last_activity($ip) {
1108
	global $cpzone;
1109

    
1110
	$ipfwoutput = pfSense_ipfw_getTablestats($cpzone, 1, $ip);
1111
	/* Reading only from one of the tables is enough of approximation. */
1112
	if (is_array($ipfwoutput)) {
1113
		return $ipfwoutput['timestamp'];
1114
	}
1115

    
1116
	return 0;
1117
}
1118

    
1119
function captiveportal_init_radius_servers() {
1120
	global $config, $g, $cpzone;
1121

    
1122
	/* generate radius server database */
1123
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1124
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1125
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1126
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1127
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1128
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1129

    
1130
		if ($config['captiveportal'][$cpzone]['radiusport'])
1131
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1132
		else
1133
			$radiusport = 1812;
1134
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1135
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1136
		else
1137
			$radiusacctport = 1813;
1138
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1139
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1140
		else
1141
			$radiusport2 = 1812;
1142
		if ($config['captiveportal'][$cpzone]['radiusport3'])
1143
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1144
		else
1145
			$radiusport3 = 1812;
1146
		if ($config['captiveportal'][$cpzone]['radiusport4'])
1147
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1148
		else
1149
			$radiusport4 = 1812;
1150

    
1151
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1152
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1153
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1154
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1155

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

    
1173
		fclose($fd);
1174
		unlock($cprdsrvlck);
1175
	}
1176
}
1177

    
1178
/* read RADIUS servers into array */
1179
function captiveportal_get_radius_servers() {
1180
	global $g, $cpzone;
1181

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

    
1209
	unlock($cprdsrvlck);
1210
	return false;
1211
}
1212

    
1213
/* log successful captive portal authentication to syslog */
1214
/* part of this code from php.net */
1215
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1216
	// Log it
1217
	if (!$message)
1218
		$message = "$status: $user, $mac, $ip";
1219
	else {
1220
		$message = trim($message);
1221
		$message = "$status: $user, $mac, $ip, $message";
1222
	}
1223
	captiveportal_syslog($message);
1224
}
1225

    
1226
/* log simple messages to syslog */
1227
function captiveportal_syslog($message) {
1228
	$message = trim($message);
1229
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1230
	// Log it
1231
	syslog(LOG_INFO, $message);
1232
	closelog();
1233
}
1234

    
1235
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1236
	global $g, $config;
1237

    
1238
	$pipeno = captiveportal_get_next_dn_ruleno();
1239

    
1240
	/* If the pool is empty, return appropriate message and fail authentication */
1241
	if (empty($pipeno)) {
1242
		$auth_list = array();
1243
		$auth_list['auth_val'] = 1;
1244
		$auth_list['error'] = "System reached maximum login capacity";
1245
		return $auth_list;
1246
	}
1247

    
1248
	$radiusservers = captiveportal_get_radius_servers();
1249

    
1250
	if (is_null($radiusctx))
1251
		$radiusctx = 'first';
1252

    
1253
	$auth_list = RADIUS_AUTHENTICATION($username,
1254
		$password,
1255
		$radiusservers[$radiusctx],
1256
		$clientip,
1257
		$clientmac,
1258
		$pipeno);
1259

    
1260
	if ($auth_list['auth_val'] == 2) {
1261
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1262
		$sessionid = portal_allow($clientip,
1263
			$clientmac,
1264
			$username,
1265
			$password,
1266
			$auth_list,
1267
			$pipeno,
1268
			$radiusctx);
1269
	} else {
1270
	         captiveportal_free_dn_ruleno($pipeno);
1271
	       }
1272

    
1273
	return $auth_list;
1274
}
1275

    
1276
function captiveportal_opendb() {
1277
	global $g, $cpzone;
1278

    
1279
	if (file_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db"))
1280
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1281
	else {
1282
		$errormsg = "";
1283
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1284
		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)) {
1285
			@sqlite_exec($DB, "CREATE UNIQUE INDEX idx_active ON captiveportal (sessionid, username)");
1286
			@sqlite_exec($DB, "CREATE INDEX user ON captiveportal (username)");
1287
			@sqlite_exec($DB, "CREATE INDEX ip ON captiveportal (ip)");
1288
			@sqlite_exec($DB, "CREATE INDEX starttime ON captiveportal (allow_time)");
1289
			@sqlite_exec($DB, "CREATE INDEX serviceid ON captiveportal (serviceid)");
1290
		} else
1291
			captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$errormsg}");
1292
	}
1293

    
1294
	return $DB;
1295
}
1296

    
1297
/* read captive portal DB into array */
1298
function captiveportal_read_db($query = "") {
1299

    
1300
	$DB = captiveportal_opendb();
1301
	if ($DB) {
1302
		sqlite_exec($DB, "BEGIN");
1303
		if (!empty($query))
1304
			$cpdb = @sqlite_array_query($DB, "SELECT * FROM captiveportal {$query}", SQLITE_NUM);
1305
		else {
1306
			$response = @sqlite_unbuffered_query($DB, "SELECT * FROM captiveportal", SQLITE_NUM);
1307
			$cpdb = @sqlite_fetch_all($response, SQLITE_NUM);
1308
		}
1309
		sqlite_exec($DB, "END");
1310
		@sqlite_close($DB);
1311
	}
1312
	if (!$cpdb)
1313
		$cpdb = array();
1314

    
1315
	return $cpdb;
1316
}
1317

    
1318
function captiveportal_remove_entries($remove) {
1319

    
1320
	if (!is_array($remove) || empty($remove))
1321
		return;
1322

    
1323
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1324
	foreach($remove as $idx => $unindex) {
1325
		$query .= "'{$unindex}'";
1326
		if ($idx < (count($remove) - 1))
1327
			$query .= ",";
1328
	}
1329
	$query .= ")";
1330
	captiveportal_write_db($query);
1331
}
1332

    
1333
/* write captive portal DB */
1334
function captiveportal_write_db($queries) {
1335
	global $g;
1336

    
1337
	if (is_array($queries))
1338
		$query = implode(";", $queries);
1339
	else
1340
		$query = $queries;
1341

    
1342
	$DB = captiveportal_opendb();
1343
	if ($DB) {
1344
		$error_msg = "";
1345
		sqlite_exec($DB, "BEGIN TRANSACTION");
1346
		$result = @sqlite_exec($DB, $query, $error_msg);
1347
		if (!$result)
1348
			captiveportal_syslog("Trying to modify DB returned error: {$error_msg}");
1349
		else
1350
			sqlite_exec($DB, "END TRANSACTION");
1351
		@sqlite_close($DB);
1352
		return $result;
1353
	} else
1354
		return true;
1355
}
1356

    
1357
function captiveportal_write_elements() {
1358
	global $g, $config, $cpzone;
1359
	
1360
	$cpcfg = $config['captiveportal'][$cpzone];
1361

    
1362
	if (!is_dir($g['captiveportal_element_path']))
1363
		@mkdir($g['captiveportal_element_path']);
1364

    
1365
	if (is_array($cpcfg['element'])) {
1366
		conf_mount_rw();
1367
		foreach ($cpcfg['element'] as $data) {
1368
			if (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1369
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1370
				return 1;
1371
			}
1372
			if (!file_exists("{$g['captiveportal_path']}/{$data['name']}"))
1373
				@symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1374
		}
1375
		conf_mount_ro();
1376
	}
1377
	
1378
	return 0;
1379
}
1380

    
1381
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1382
	global $cpzone;
1383

    
1384
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1385
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1386
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1387
		$ridx = $rulenos_start;
1388
		while ($ridx < $rulenos_range_max) {
1389
			if ($rules[$ridx] == $cpzone) {
1390
				$rules[$ridx] = false;
1391
				$ridx++;
1392
				$rules[$ridx] = false;
1393
				$ridx++;
1394
			} else
1395
				$ridx += 2;
1396
		}
1397
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1398
		unset($rules);
1399
	}
1400
	unlock($cpruleslck);
1401
}
1402

    
1403
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1404
	global $config, $g, $cpzone;
1405

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

    
1433
	return $ruleno;
1434
}
1435

    
1436
function captiveportal_free_dn_ruleno($ruleno) {
1437
	global $config, $g;
1438

    
1439
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1440
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1441
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1442
		$rules[$ruleno] = false;
1443
		$ruleno++;
1444
		$rules[$ruleno] = false;
1445
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1446
		unset($rules);
1447
	}
1448
	unlock($cpruleslck);
1449
}
1450

    
1451
function captiveportal_get_dn_passthru_ruleno($value) {
1452
	global $config, $g, $cpzone;
1453

    
1454
	$cpcfg = $config['captiveportal'][$cpzone];
1455
	if(!isset($cpcfg['enable']))
1456
		return NULL;
1457

    
1458
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1459
	$ruleno = NULL;
1460
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1461
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1462
		unset($output);
1463
		$_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);
1464
		$ruleno = intval($output[0]);
1465
		if (!$rules[$ruleno])
1466
			$ruleno = NULL;
1467
		unset($rules);
1468
	}
1469
	unlock($cpruleslck);
1470

    
1471
	return $ruleno;
1472
}
1473

    
1474
/*
1475
 * This function will calculate the lowest free firewall ruleno
1476
 * within the range specified based on the actual logged on users
1477
 *
1478
 */
1479
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1480
	global $config, $g, $cpzone;
1481

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

    
1486
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1487
	$ruleno = 0;
1488
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1489
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1490
		$ridx = $rulenos_start;
1491
		while ($ridx < $rulenos_range_max) {
1492
			if (empty($rules[$ridx])) {
1493
				$ruleno = $ridx;
1494
				$rules[$ridx] = $cpzone;
1495
				$ridx++;
1496
				$rules[$ridx] = $cpzone;
1497
				break;
1498
			} else {
1499
				/* 
1500
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1501
				 * and the out pipe ruleno + 1.
1502
				 */
1503
				$ridx += 2;
1504
			}
1505
		}
1506
	} else {
1507
		$rules = array_pad(array(), $rulenos_range_max, false);
1508
		$ruleno = $rulenos_start;
1509
		$rules[$rulenos_start] = $cpzone;
1510
		$rulenos_start++;
1511
		$rules[$rulenos_start] = $cpzone;
1512
	}
1513
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1514
	unlock($cpruleslck);
1515
	unset($rules);
1516

    
1517
	return $ruleno;
1518
}
1519

    
1520
function captiveportal_free_ipfw_ruleno($ruleno) {
1521
	global $config, $g, $cpzone;
1522

    
1523
	$cpcfg = $config['captiveportal'][$cpzone];
1524
	if(!isset($cpcfg['enable']))
1525
		return NULL;
1526

    
1527
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1528
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1529
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1530
		$rules[$ruleno] = false;
1531
		$ruleno++;
1532
		$rules[$ruleno] = false;
1533
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1534
	}
1535
	unlock($cpruleslck);
1536
	unset($rules);
1537
}
1538

    
1539
function captiveportal_get_ipfw_passthru_ruleno($value) {
1540
	global $config, $g, $cpzone;
1541

    
1542
	$cpcfg = $config['captiveportal'][$cpzone];
1543
	if(!isset($cpcfg['enable']))
1544
		return NULL;
1545

    
1546
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1547
	$ruleno = NULL;
1548
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1549
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1550
		unset($output);
1551
		$_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);
1552
		$ruleno = intval($output[0]);
1553
		if (!$rules[$ruleno])
1554
			$ruleno = NULL;
1555
	}
1556
	unlock($cpruleslck);
1557
	unset($rules);
1558

    
1559
	return $ruleno;
1560
}
1561

    
1562
/**
1563
 * This function will calculate the traffic produced by a client
1564
 * based on its firewall rule
1565
 *
1566
 * Point of view: NAS
1567
 *
1568
 * Input means: from the client
1569
 * Output means: to the client
1570
 *
1571
 */
1572

    
1573
function getVolume($ip) {
1574
	global $config, $cpzone;
1575

    
1576
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1577
	$volume = array();
1578
	// Initialize vars properly, since we don't want NULL vars
1579
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1580

    
1581
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 1, $ip);
1582
	if (is_array($ipfw)) {
1583
		if ($reverse) {
1584
			$volume['output_pkts'] = $ipfw['packets'];
1585
			$volume['output_bytes'] = $ipfw['bytes'];
1586
		}
1587
		else {
1588
			$volume['input_pkts'] = $ipfw['packets'];
1589
			$volume['input_bytes'] = $ipfw['bytes'];
1590
		}
1591
	}
1592

    
1593
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 2, $ip);
1594
	if (is_array($ipfw)) {
1595
		if ($reverse) {
1596
			$volume['input_pkts'] = $ipfw['packets'];
1597
			$volume['input_bytes'] = $ipfw['bytes'];
1598
		}
1599
		else {
1600
			$volume['output_pkts'] = $ipfw['packets'];
1601
			$volume['output_bytes'] = $ipfw['bytes'];
1602
		}
1603
	}
1604

    
1605
	return $volume;
1606
}
1607

    
1608
/**
1609
 * Get the NAS-IP-Address based on the current wan address
1610
 *
1611
 * Use functions in interfaces.inc to find this out
1612
 *
1613
 */
1614

    
1615
function getNasIP()
1616
{
1617
	global $config, $cpzone;
1618

    
1619
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1620
			$nasIp = get_interface_ip();
1621
	} else {
1622
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1623
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1624
		else
1625
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1626
	}
1627
		
1628
	if(!is_ipaddr($nasIp))
1629
		$nasIp = "0.0.0.0";
1630

    
1631
	return $nasIp;
1632
}
1633

    
1634
function portal_ip_from_client_ip($cliip) {
1635
	global $config, $cpzone;
1636

    
1637
	$isipv6 = is_ipaddrv6($cliip);
1638
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1639
	foreach ($interfaces as $cpif) {
1640
		if ($isipv6) {
1641
			$ip = get_interface_ipv6($cpif);
1642
			$sn = get_interface_subnetv6($cpif);
1643
		} else {
1644
			$ip = get_interface_ip($cpif);
1645
			$sn = get_interface_subnet($cpif);
1646
		}
1647
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1648
			return $ip;
1649
	}
1650

    
1651
	$inet = ($isipv6) ? '-inet6' : '-inet';
1652
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1653
	$iface = trim($iface, "\n");
1654
	if (!empty($iface)) {
1655
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1656
		if (is_ipaddr($ip))
1657
			return $ip;
1658
	}
1659

    
1660
	// doesn't match up to any particular interface
1661
	// so let's set the portal IP to what PHP says 
1662
	// the server IP issuing the request is. 
1663
	// allows same behavior as 1.2.x where IP isn't 
1664
	// in the subnet of any CP interface (static routes, etc.)
1665
	// rather than forcing to DNS hostname resolution
1666
	$ip = $_SERVER['SERVER_ADDR'];
1667
	if (is_ipaddr($ip))
1668
		return $ip;
1669

    
1670
	return false;
1671
}
1672

    
1673
function portal_hostname_from_client_ip($cliip) {
1674
	global $config, $cpzone;
1675

    
1676
	$cpcfg = $config['captiveportal'][$cpzone];
1677

    
1678
	if (isset($cpcfg['httpslogin'])) {
1679
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
1680
		if ($cpcfg['auth_method'] == "saml") $listenporthttps = 443;
1681
		$ourhostname = $cpcfg['httpsname'];
1682
		
1683
		if ($listenporthttps != 443)
1684
			$ourhostname .= ":" . $listenporthttps;
1685
	} else {
1686
		$listenporthttp  = $cpcfg['listenporthttp']  ? $cpcfg['listenporthttp']  : $cpcfg['zoneid'];
1687
		$ifip = portal_ip_from_client_ip($cliip);
1688
		if (!$ifip)
1689
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1690
		else
1691
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1692
		
1693
		if ($listenporthttp != 80)
1694
			$ourhostname .= ":" . $listenporthttp;
1695
	}
1696
	
1697
	return $ourhostname;
1698
}
1699

    
1700
/* functions move from index.php */
1701

    
1702
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1703
	global $g, $config, $cpzone;
1704

    
1705
	/* Get captive portal layout */
1706
	if ($type == "redir") {
1707
		header("Location: {$redirurl}");
1708
		return;
1709
	} else if ($type == "login")
1710
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1711
	else
1712
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1713

    
1714
	$cpcfg = $config['captiveportal'][$cpzone];
1715

    
1716
	/* substitute the PORTAL_REDIRURL variable */
1717
	if ($cpcfg['preauthurl']) {
1718
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1719
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1720
	}
1721

    
1722
	/* substitute other variables */
1723
	$ourhostname = portal_hostname_from_client_ip($clientip);
1724
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1725
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1726
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1727

    
1728
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1729
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1730
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1731
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1732
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1733

    
1734
	// Special handling case for captive portal master page so that it can be ran 
1735
	// through the PHP interpreter using the include method above.  We convert the
1736
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1737
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1738
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1739
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1740
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1741
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1742
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1743
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1744

    
1745
	echo $htmltext;
1746
}
1747

    
1748
function portal_mac_radius($clientmac,$clientip) {
1749
	global $config, $cpzone;
1750

    
1751
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1752

    
1753
	/* authentication against the radius server */
1754
	$username = mac_format($clientmac);
1755
	$auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1756
	if ($auth_list['auth_val'] == 2)
1757
		return TRUE;
1758

    
1759
	if (!empty($auth_list['url_redirection']))
1760
		portal_reply_page($auth_list['url_redirection'], "redir");
1761

    
1762
	return FALSE;
1763
}
1764

    
1765
function captiveportal_reapply_attributes($cpentry, $attributes) {
1766
	global $config, $cpzone, $g;
1767

    
1768
	$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1769
	$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1770
	$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1771
	$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1772
	$bw_up_pipeno = $cpentry[1];
1773
	$bw_down_pipeno = $cpentry[1]+1;
1774

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

    
1779
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1780
}
1781

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

    
1785
	// Ensure we create an array if we are missing attributes
1786
	if (!is_array($attributes))
1787
		$attributes = array();
1788

    
1789
	unset($sessionid);
1790

    
1791
	/* Do not allow concurrent login execution. */
1792
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1793

    
1794
	if ($attributes['voucher'])
1795
		$remaining_time = $attributes['session_timeout'];
1796

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

    
1842
	/* read in client database */
1843
	$query = "WHERE ip = '{$clientip}'";
1844
	$tmpusername = strtolower($username);
1845
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins']))
1846
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
1847
	$cpdb = captiveportal_read_db($query);
1848

    
1849
	/* Snapshot the timestamp */
1850
	$allow_time = time();
1851
	$radiusservers = captiveportal_get_radius_servers();
1852
	$unsetindexes = array();
1853
	if (is_null($radiusctx))
1854
		$radiusctx = 'first';
1855

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

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

    
1895
	if (!empty($unsetindexes))
1896
		captiveportal_remove_entries($unsetindexes);
1897

    
1898
	if ($attributes['voucher'] && $remaining_time <= 0)
1899
		return 0;       // voucher already used and no time left
1900

    
1901
	if (!isset($sessionid)) {
1902
		/* generate unique session ID */
1903
		$tod = gettimeofday();
1904
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1905

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

    
1933
			/* if the pool is empty, return appropriate message and exit */
1934
			if (is_null($pipeno)) {
1935
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1936
				log_error("WARNING!  Captive portal has reached maximum login capacity");
1937
				unlock($cpdblck);
1938
				return;
1939
			}
1940

    
1941
			$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1942
			$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1943
			$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1944
			$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1945

    
1946
			$bw_up_pipeno = $pipeno;
1947
			$bw_down_pipeno = $pipeno + 1;
1948
			//$bw_up /= 1000; // Scale to Kbit/s
1949
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1950
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1951

    
1952
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
1953
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1954
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
1955
			else
1956
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
1957

    
1958
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1959
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
1960
			else
1961
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
1962

    
1963
			if ($attributes['voucher'])
1964
				$attributes['session_timeout'] = $remaining_time;
1965
			
1966
			/* handle empty attributes */
1967
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
1968
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
1969
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
1970
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
1971

    
1972
			/* escape username */
1973
			$safe_username = sqlite_escape_string($username);
1974

    
1975
			/* encode password in Base64 just in case it contains commas */
1976
			$bpassword = base64_encode($password);
1977
			$insertquery  = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, radiusctx) ";
1978
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
1979
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, '{$radiusctx}')";
1980

    
1981
			/* store information to database */
1982
			captiveportal_write_db($insertquery);
1983
			unlock($cpdblck);
1984
			unset($insertquery, $bpassword);
1985

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

    
1997
		unlock($cpdblck);
1998
	}
1999

    
2000
	if ($writecfg == true)
2001
		write_config();
2002

    
2003
	/* redirect user to desired destination */
2004
	if (!empty($attributes['url_redirection']))
2005
		$my_redirurl = $attributes['url_redirection'];
2006
	else if (!empty($redirurl))
2007
		$my_redirurl = $redirurl;
2008
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
2009
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
2010

    
2011
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
2012
		$ourhostname = portal_hostname_from_client_ip($clientip);
2013
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
2014
		$logouturl = "{$protocol}{$ourhostname}/";
2015

    
2016
		if (isset($attributes['reply_message']))
2017
			$message = $attributes['reply_message'];
2018
		else
2019
			$message = 0;
2020

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

    
2023
	} else {
2024
		portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
2025
	}
2026

    
2027
	return $sessionid;
2028
}
2029

    
2030

    
2031
/*
2032
 * Used for when pass-through credits are enabled.
2033
 * Returns true when there was at least one free login to deduct for the MAC.
2034
 * Expired entries are removed as they are seen.
2035
 * Active entries are updated according to the configuration.
2036
 */
2037
function portal_consume_passthrough_credit($clientmac) {
2038
	global $config, $cpzone;
2039

    
2040
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
2041
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
2042
	else
2043
		return false;
2044

    
2045
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
2046
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2047
	else
2048
		return false;
2049

    
2050
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
2051
		return false;
2052

    
2053
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2054

    
2055
	/*
2056
	 * Read database of used MACs.  Lines are a comma-separated list
2057
	 * of the time, MAC, then the count of pass-through credits remaining.
2058
	 */
2059
	$usedmacs = captiveportal_read_usedmacs_db();
2060

    
2061
	$currenttime = time();
2062
	$found = false;
2063
	foreach ($usedmacs as $key => $usedmac) {
2064
		$usedmac = explode(",", $usedmac);
2065

    
2066
		if ($usedmac[1] == $clientmac) {
2067
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2068
				if ($usedmac[2] < 1) {
2069
					if ($updatetimeouts) {
2070
						$usedmac[0] = $currenttime;
2071
						unset($usedmacs[$key]);
2072
						$usedmacs[] = implode(",", $usedmac);
2073
						captiveportal_write_usedmacs_db($usedmacs);
2074
					}
2075

    
2076
					return false;
2077
				} else {
2078
					$usedmac[2] -= 1;
2079
					$usedmacs[$key] = implode(",", $usedmac);
2080
				}
2081

    
2082
				$found = true;
2083
			} else
2084
				unset($usedmacs[$key]);
2085

    
2086
			break;
2087
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2088
				unset($usedmacs[$key]);
2089
	}
2090

    
2091
	if (!$found) {
2092
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2093
		$usedmacs[] = implode(",", $usedmac);
2094
	}
2095

    
2096
	captiveportal_write_usedmacs_db($usedmacs);
2097
	return true;
2098
}
2099

    
2100
function captiveportal_read_usedmacs_db() {
2101
	global $g, $cpzone;
2102

    
2103
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2104
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2105
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2106
		if (!$usedmacs)
2107
			$usedmacs = array();
2108
	} else
2109
		$usedmacs = array();
2110

    
2111
	unlock($cpumaclck);
2112
	return $usedmacs;
2113
}
2114

    
2115
function captiveportal_write_usedmacs_db($usedmacs) {
2116
	global $g, $cpzone;
2117

    
2118
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2119
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2120
	unlock($cpumaclck);
2121
}
2122

    
2123
function captiveportal_send_server_accounting($off = false) {
2124
	global $cpzone, $config;
2125

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

    
2146
		// If we encounter an error immediately stop this function and go back
2147
		$racct->close();
2148
		return $retvalue;
2149
	}
2150
	// Send request
2151
	$result = $racct->send();
2152
	// Evaluation of the response
2153
	// 5 -> Accounting-Response
2154
	// See RFC2866 for this.
2155
	if (PEAR::isError($result)) {
2156
		$retvalue['acct_val'] = 1;
2157
		$retvalue['error'] = $result->getMessage();
2158
	} else if ($result === true) {
2159
		$retvalue['acct_val'] = 5 ;
2160
	} else {
2161
		$retvalue['acct_val'] = 1 ;
2162
	}
2163

    
2164
	$racct->close();
2165
	return $retvalue;
2166
}
2167
?>
(8-8/67)