Projet

Général

Profil

Télécharger (73,2 ko) Statistiques
| Branche: | Tag: | Révision:

univnautes / etc / inc / captiveportal.inc @ 340ce958

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

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

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

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

    
103
EOD;
104

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

    
112
EOD;
113
	}
114

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

    
142
EOD;
143

    
144
	return $htmltext;
145
}
146

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

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

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

    
170
function captiveportal_configure() {
171
	global $config, $cpzone, $cpzoneid;
172

    
173
	if (is_array($config['captiveportal'])) {
174
		foreach ($config['captiveportal'] as $cpkey => $cp) {
175
			$cpzone = $cpkey;
176
			$cpzoneid = $cp['zoneid'];
177
			captiveportal_configure_zone($cp);
178
		}
179
	}
180
}
181

    
182
function captiveportal_configure_zone($cpcfg) {
183
	global $config, $g, $cpzone, $cpzoneid;
184

    
185
	$captiveportallck = lock("captiveportal{$cpzone}", LOCK_EX);
186
	
187
	if (isset($cpcfg['enable'])) {
188

    
189
		if ($g['booting']) {
190
			echo "Starting captive portal({$cpcfg['zone']})... ";
191

    
192
			/* remove old information */
193
			unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
194
		} else
195
			captiveportal_syslog("Reconfiguring captive portal({$cpcfg['zone']}).");
196

    
197
		/* init ipfw rules */
198
		captiveportal_init_rules(true);
199

    
200
		/* kill any running minicron */
201
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
202

    
203
		/* initialize minicron interval value */
204
		$croninterval = $cpcfg['croninterval'] ? $cpcfg['croninterval'] : 60;
205

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

    
210
		/* write portal page */
211
		if (is_array($cpcfg['page']) && $cpcfg['page']['htmltext'])
212
			$htmltext = base64_decode($cpcfg['page']['htmltext']);
213
		else {
214
			/* example/template page */
215
			$htmltext = get_default_captive_portal_html();
216
		}
217

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

    
238
		/* write error page */
239
		if (is_array($cpcfg['page']) && $cpcfg['page']['errtext'])
240
			$errtext = base64_decode($cpcfg['page']['errtext']);
241
		else {
242
			/* example page  */
243
			$errtext = get_default_captive_portal_html();
244
		}
245

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

    
266
		/* write logout page */
267
		if (is_array($cpcfg['page']) && $cpcfg['page']['logouttext'])
268
			$logouttext = base64_decode($cpcfg['page']['logouttext']);
269
		else {
270
			/* example page */
271
			$logouttext = <<<EOD
272
<html>
273
<head><title>Redirecting...</title></head>
274
<body>
275
<span style="font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">
276
<b>Redirecting to <a href="<?=\$my_redirurl;?>"><?=\$my_redirurl;?></a>...</b>
277
</span>
278
<script type="text/javascript">
279
//<![CDATA[
280
LogoutWin = window.open('', 'Logout', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=256,height=64');
281
if (LogoutWin) {
282
	LogoutWin.document.write('<html>');
283
	LogoutWin.document.write('<head><title>Logout</title></head>') ;
284
	LogoutWin.document.write('<body bgcolor="#435370">');
285
	LogoutWin.document.write('<div align="center" style="color: #ffffff; font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">') ;
286
	LogoutWin.document.write('<b>Click the button below to disconnect</b><p />');
287
	LogoutWin.document.write('<form method="POST" action="<?=\$logouturl;?>">');
288
	LogoutWin.document.write('<input name="logout_id" type="hidden" value="<?=\$sessionid;?>" />');
289
	LogoutWin.document.write('<input name="zone" type="hidden" value="<?=\$cpzone;?>" />');
290
	LogoutWin.document.write('<input name="logout" type="submit" value="Logout" />');
291
	LogoutWin.document.write('</form>');
292
	LogoutWin.document.write('</div></body>');
293
	LogoutWin.document.write('</html>');
294
	LogoutWin.document.close();
295
}
296

    
297
document.location.href="<?=\$my_redirurl;?>";
298
//]]>
299
</script>
300
</body>
301
</html>
302

    
303
EOD;
304
		}
305

    
306
		$fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html", "w");
307
		if ($fd) {
308
			fwrite($fd, $logouttext);
309
			fclose($fd);
310
		}
311
		unset($logouttext);
312

    
313
		/* write elements */
314
		captiveportal_write_elements();
315

    
316
		/* kill any running mini_httpd */
317
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
318
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
319

    
320
		/* start up the webserving daemon */
321
		captiveportal_init_webgui_zone($cpcfg);
322

    
323
		/* Kill any existing prunecaptiveportal processes */
324
		if (file_exists("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid"))
325
			killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
326

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

    
331
		/* generate radius server database */
332
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
333
		captiveportal_init_radius_servers();
334

    
335
		if ($g['booting']) {
336
			/* send Accounting-On to server */
337
			captiveportal_send_server_accounting();
338
			echo "done\n";
339
		}
340

    
341
	} else {
342
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
343
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
344
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
345
		@unlink("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
346
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
347
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
348

    
349
		captiveportal_radius_stop_all();
350

    
351
		/* send Accounting-Off to server */
352
		if (!$g['booting']) {
353
			captiveportal_send_server_accounting(true);
354
		}
355

    
356
		/* remove old information */
357
		unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
358
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
359
		unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
360
		/* Release allocated pipes for this zone */
361
		captiveportal_free_dnrules();
362

    
363
		mwexec("/sbin/ipfw zone {$cpzoneid} destroy", true);
364

    
365
		if (empty($config['captiveportal']))
366
			set_single_sysctl("net.link.ether.ipfw", "0");
367
		else {
368
			/* Deactivate ipfw(4) if not needed */
369
			$cpactive = false;
370
			if (is_array($config['captiveportal'])) {
371
				foreach ($config['captiveportal'] as $cpkey => $cp) {
372
					if (isset($cp['enable'])) {
373
						$cpactive = true;
374
						break;
375
					}
376
				}
377
			}
378
			if ($cpactive === false)
379
				set_single_sysctl("net.link.ether.ipfw", "0");
380

    
381
		}
382
	}
383

    
384
	unlock($captiveportallck);
385
	
386
	return 0;
387
}
388

    
389
function captiveportal_init_webgui() {
390
	global $config, $cpzone;
391

    
392
	if (is_array($config['captiveportal'])) {
393
		foreach ($config['captiveportal'] as $cpkey => $cp) {
394
			$cpzone = $cpkey;
395
			captiveportal_init_webgui_zone($cp);
396
		}
397
	}
398
}
399

    
400
function captiveportal_init_webgui_zonename($zone) {
401
	global $config, $cpzone;
402
	
403
	if (isset($config['captiveportal'][$zone])) {
404
		$cpzone = $zone;
405
		captiveportal_init_webgui_zone($config['captiveportal'][$zone]);
406
	}
407
}
408

    
409
function captiveportal_init_webgui_zone($cpcfg) {
410
	global $g, $config, $cpzone;
411

    
412
	if (!isset($cpcfg['enable']))
413
		return;
414

    
415
	if (isset($cpcfg['httpslogin'])) {
416
		$cert = lookup_cert($cpcfg['certref']);
417
		$crt = base64_decode($cert['crt']);
418
		$key = base64_decode($cert['prv']);
419
		$ca = ca_chain($cert);
420

    
421
		/* generate lighttpd configuration */
422
		if (!empty($cpcfg['listenporthttps']))
423
			$listenporthttps = $cpcfg['listenporthttps'];
424
		else
425
			$listenporthttps = 8001 + $cpcfg['zoneid'];
426
		system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf",
427
			$crt, $key, $ca, "lighty-{$cpzone}-CaptivePortal-SSL.pid", $listenporthttps, "/usr/local/captiveportal",
428
			"cert-{$cpzone}-portal.pem", "ca-{$cpzone}-portal.pem", $cpzone);
429
	}
430

    
431
	/* generate lighttpd configuration */
432
	if (!empty($cpcfg['listenporthttp']))
433
		$listenporthttp = $cpcfg['listenporthttp'];
434
	else
435
		$listenporthttp = 8000 + $cpcfg['zoneid'];
436
	system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal.conf",
437
		"", "", "", "lighty-{$cpzone}-CaptivePortal.pid", $listenporthttp, "/usr/local/captiveportal",
438
		"", "", $cpzone);
439

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

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

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

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

    
458
	captiveportal_load_modules();
459
	mwexec("/sbin/ipfw zone {$cpzoneid} create", true);
460

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

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

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

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

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

    
509
EOD;
510

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

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

    
547
	if (!empty($config['captiveportal'][$cpzone]['listenporthttp']))
548
		$listenporthttp = $config['captiveportal'][$cpzone]['listenporthttp'];
549
	else
550
		$listenporthttp = 8000 + $cpzoneid;
551

    
552
	if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
553
		if (!empty($config['captiveportal'][$cpzone]['listenporthttps']))
554
			$listenporthttps = $config['captiveportal'][$cpzone]['listenporthttps'];
555
		else
556
			$listenporthttps = 8001 + $cpzoneid;
557
			if (!isset($config['captiveportal'][$cpzone]['nohttpsforwards'])) {
558
				$cprules .= "add 65531 fwd 127.0.0.1,{$listenporthttps} tcp from any to any dst-port 443 in\n";
559
			}
560
	}
561
	
562
	$cprules .= <<<EOD
563

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

    
571
EOD;
572

    
573
	/* generate passthru mac database */
574
	$cprules .= captiveportal_passthrumac_configure(true);
575
	$cprules .= "\n";
576

    
577
	/* allowed ipfw rules to make allowed ip work */
578
	$cprules .= captiveportal_allowedip_configure();
579

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

    
590
	if ($reinit == false)
591
		unlock($captiveportallck);
592
}
593

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

    
603
	if (empty($cpzone))
604
		return;
605

    
606
	$cpcfg = $config['captiveportal'][$cpzone];
607
	$vcpcfg = $config['voucher'][$cpzone];
608

    
609
	/* check for expired entries */
610
	$idletimeout = 0;
611
	$timeout = 0;
612
	if (!empty($cpcfg['timeout']) && is_numeric($cpcfg['timeout']))
613
		$timeout = $cpcfg['timeout'] * 60;
614

    
615
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout']))
616
		$idletimeout = $cpcfg['idletimeout'] * 60;
617

    
618
	/* Is there any job to do? */
619
	if (!$timeout && !$idletimeout && !isset($cpcfg['reauthenticate']) &&
620
	    !isset($cpcfg['radiussession_timeout']) && !isset($vcpcfg['enable']))
621
		return;
622

    
623
	$radiussrvs = captiveportal_get_radius_servers();
624

    
625
	/* Read database */
626
	/* NOTE: while this can be simplified in non radius case keep as is for now */
627
	$cpdb = captiveportal_read_db();
628

    
629
	$unsetindexes = array();
630
	$voucher_needs_sync = false;
631
	/* 
632
	 * Snapshot the time here to use for calculation to speed up the process.
633
	 * If something is missed next run will catch it!
634
	 */
635
	$pruning_time = time();
636
	$stop_time = $pruning_time;
637
	foreach ($cpdb as $cpentry) {
638

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

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

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

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

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

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

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

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

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

    
763
	captiveportal_prune_old_automac();
764

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

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

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

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

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

    
842
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
843

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

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

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

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

    
888
}
889

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

    
894
	$radiusservers = captiveportal_get_radius_servers();
895

    
896
	/* read database */
897
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
898

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

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

    
913
/* send RADIUS acct stop for all current clients */
914
function captiveportal_radius_stop_all() {
915
	global $config, $cpzone;
916

    
917
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable']))
918
		return;
919

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

    
940
function captiveportal_passthrumac_configure_entry($macent, $pipeinrule = false) {
941
	global $config, $g, $cpzone;
942

    
943
	$bwUp = 0;
944
	if (!empty($macent['bw_up']))
945
		$bwUp = $macent['bw_up'];
946
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultup']))
947
		$bwUp = $config['captiveportal'][$cpzone]['bwdefaultup'];
948
	$bwDown = 0;
949
	if (!empty($macent['bw_down']))
950
		$bwDown = $macent['bw_down'];
951
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultdn']))
952
		$bwDown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
953

    
954
	$ruleno = captiveportal_get_next_ipfw_ruleno();
955

    
956
	if ($macent['action'] == 'pass') {
957
		$rules = "";
958
		$pipeno = captiveportal_get_next_dn_ruleno();
959

    
960
		$pipeup = $pipeno;
961
		if ($pipeinrule == true)
962
			$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16");
963
		else
964
			$rules .= "pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16\n";
965
			
966
		$pipedown = $pipeno + 1;
967
		if ($pipeinrule == true)
968
			$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
969
		else
970
			$rules .= "pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16\n";
971

    
972
		$rules .= "add {$ruleno} pipe {$pipeup} ip from any to any MAC any {$macent['mac']}\n";
973
		$ruleno++;
974
		$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC {$macent['mac']} any\n";
975
	}
976

    
977
	return $rules;
978
}
979

    
980
function captiveportal_passthrumac_delete_entry($macent) {
981
	$rules = "";
982

    
983
	if ($macent['action'] == 'pass') {
984
		$ruleno = captiveportal_get_ipfw_passthru_ruleno($macent['mac']);
985

    
986
		if (!$ruleno)
987
			return $rules;
988

    
989
		captiveportal_free_ipfw_ruleno($ruleno);
990

    
991
		$rules .= "delete {$ruleno}\n";
992
		$rules .= "delete " . ++$ruleno . "\n";
993

    
994
		$pipeno = captiveportal_get_dn_passthru_ruleno($macent['mac']);
995

    
996
		if (!empty($pipeno)) {
997
			captiveportal_free_dn_ruleno($pipeno);
998
			$rules .= "pipe delete " . $pipeno . "\n";
999
			$rules .= "pipe delete " . ++$pipeno . "\n";
1000
		}
1001
	}
1002

    
1003
	return $rules;
1004
}
1005

    
1006
function captiveportal_passthrumac_configure($lock = false) {
1007
	global $config, $g, $cpzone;
1008

    
1009
	$rules = "";
1010

    
1011
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1012
		$nentries = count($config['captiveportal'][$cpzone]['passthrumac']);
1013
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1014
			if ($nentries > 100)
1015
				$rules .= captiveportal_passthrumac_configure_entry($macent, true);
1016
			else
1017
				$rules .= captiveportal_passthrumac_configure_entry($macent);
1018
		}
1019
	}
1020

    
1021
	return $rules;
1022
}
1023

    
1024
function captiveportal_passthrumac_findbyname($username) {
1025
	global $config, $cpzone;
1026

    
1027
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1028
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1029
			if ($macent['username'] == $username)
1030
				return $macent;
1031
		}
1032
	}
1033
	return NULL;
1034
}
1035

    
1036
/* 
1037
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1038
 */
1039
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
1040
	global $g;
1041

    
1042
	/*  Instead of copying this entire function for something
1043
	 *  easy such as hostname vs ip address add this check
1044
	 */
1045
	if ($ishostname === true) {
1046
		if (!$g['booting']) {
1047
			$ipaddress = gethostbyname($ipent['hostname']);
1048
			if (!is_ipaddr($ipaddress)) 
1049
				return;
1050
		} else
1051
			$ipaddress = "";
1052
	} else
1053
		$ipaddress = $ipent['ip'];
1054

    
1055
	$rules = "";
1056
	$cp_filterdns_conf = "";
1057
	$enBwup = 0;
1058
	if (!empty($ipent['bw_up']))
1059
		$enBwup = intval($ipent['bw_up']);
1060
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultup']))
1061
		$enBwup = $config['captiveportal'][$cpzone]['bwdefaultup'];
1062
	$enBwdown = 0;
1063
	if (!empty($ipent['bw_down']))
1064
		$enBwdown = intval($ipent['bw_down']);
1065
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultdn']))
1066
		$enBwdown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
1067

    
1068
	$pipeno = captiveportal_get_next_dn_ruleno();
1069
	$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$enBwup}Kbit/s queue 100 buckets 16");
1070
	$pipedown = $pipeno + 1;
1071
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16");
1072
	if ($ishostname === true) {
1073
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
1074
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
1075
		if (!is_ipaddr($ipaddress))
1076
			return array("", $cp_filterdns_conf);
1077
	}
1078
	$subnet = "";
1079
	if (!empty($ipent['sn']))
1080
		$subnet = "/{$ipent['sn']}";
1081
	$rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
1082
	$rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
1083

    
1084
	if ($ishostname === true)
1085
		return array($rules, $cp_filterdns_conf);
1086
	else
1087
		return $rules;
1088
}
1089

    
1090
function captiveportal_allowedhostname_configure() {
1091
	global $config, $g, $cpzone;
1092

    
1093
	$rules = "";
1094
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1095
		$rules = "\n# captiveportal_allowedhostname_configure()\n";
1096
		$cp_filterdns_conf = "";
1097
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) {
1098
			$tmprules = captiveportal_allowedip_configure_entry($hostnameent, true);
1099
			$rules .= $tmprules[0];
1100
			$cp_filterdns_conf .= $tmprules[1];
1101
		}
1102
		$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1103
		@file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1104
		unset($cp_filterdns_conf);
1105
		if (isvalidpid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid"))
1106
			sigkillbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid", "HUP");
1107
		else
1108
			mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzone} -d 1");
1109
	} else {
1110
		killbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1111
		@unlink("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1112
	}
1113

    
1114
	return $rules;
1115
}
1116

    
1117
function captiveportal_allowedip_configure() {
1118
	global $config, $g, $cpzone;
1119

    
1120
	$rules = "";
1121
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1122
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1123
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1124
	}
1125

    
1126
	return $rules;
1127
}
1128

    
1129
/* get last activity timestamp given client IP address */
1130
function captiveportal_get_last_activity($ip, $mac = NULL) {
1131
	global $cpzoneid;
1132

    
1133
	$ipfwoutput = pfSense_ipfw_getTablestats($cpzoneid, 1, $ip, $mac);
1134
	/* Reading only from one of the tables is enough of approximation. */
1135
	if (is_array($ipfwoutput)) {
1136
		return $ipfwoutput['timestamp'];
1137
	}
1138

    
1139
	return 0;
1140
}
1141

    
1142
function captiveportal_init_radius_servers() {
1143
	global $config, $g, $cpzone;
1144

    
1145
	/* generate radius server database */
1146
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1147
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1148
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1149
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1150
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1151
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1152

    
1153
		if ($config['captiveportal'][$cpzone]['radiusport'])
1154
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1155
		else
1156
			$radiusport = 1812;
1157
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1158
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1159
		else
1160
			$radiusacctport = 1813;
1161
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1162
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1163
		else
1164
			$radiusport2 = 1812;
1165
		if ($config['captiveportal'][$cpzone]['radiusport3'])
1166
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1167
		else
1168
			$radiusport3 = 1812;
1169
		if ($config['captiveportal'][$cpzone]['radiusport4'])
1170
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1171
		else
1172
			$radiusport4 = 1812;
1173

    
1174
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1175
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1176
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1177
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1178

    
1179
		$cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX);
1180
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
1181
		if (!$fd) {
1182
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1183
			unlock($cprdsrvlck);
1184
			return 1;
1185
		}
1186
		if (isset($radiusip))
1187
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
1188
		if (isset($radiusip2))
1189
			fwrite($fd,"\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
1190
		if (isset($radiusip3))
1191
			fwrite($fd,"\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
1192
		if (isset($radiusip4))
1193
			fwrite($fd,"\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
1194
		
1195

    
1196
		fclose($fd);
1197
		unlock($cprdsrvlck);
1198
	}
1199
}
1200

    
1201
/* read RADIUS servers into array */
1202
function captiveportal_get_radius_servers() {
1203
	global $g, $cpzone;
1204

    
1205
	$cprdsrvlck = lock("captiveportalradius{$cpzone}");
1206
	if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
1207
		$radiusservers = array();
1208
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", 
1209
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1210
		if ($cpradiusdb) {
1211
			foreach($cpradiusdb as $cpradiusentry) {
1212
				$line = trim($cpradiusentry);
1213
				if ($line) {
1214
					$radsrv = array();
1215
						list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key'], $context) = explode(",",$line);
1216
				}
1217
				if (empty($context)) {
1218
					if (!is_array($radiusservers['first']))
1219
						$radiusservers['first'] = array();
1220
					$radiusservers['first'] = $radsrv;
1221
				} else {
1222
					if (!is_array($radiusservers[$context]))
1223
						$radiusservers[$context] = array();
1224
					$radiusservers[$context][] = $radsrv;
1225
				}
1226
			}
1227
		}
1228
		unlock($cprdsrvlck);
1229
		return $radiusservers;
1230
	}
1231

    
1232
	unlock($cprdsrvlck);
1233
	return false;
1234
}
1235

    
1236
/* log successful captive portal authentication to syslog */
1237
/* part of this code from php.net */
1238
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1239
	// Log it
1240
	if (!$message)
1241
		$message = "{$status}: {$user}, {$mac}, {$ip}";
1242
	else {
1243
		$message = trim($message);
1244
		$message = "{$status}: {$user}, {$mac}, {$ip}, {$message}";
1245
	}
1246
	captiveportal_syslog($message);
1247
}
1248

    
1249
/* log simple messages to syslog */
1250
function captiveportal_syslog($message) {
1251
	global $cpzone;
1252

    
1253
	$message = trim($message);
1254
	$message = "Zone: {$cpzone} - {$message}";
1255
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1256
	// Log it
1257
	syslog(LOG_INFO, $message);
1258
	closelog();
1259
}
1260

    
1261
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1262
	global $g, $config, $cpzoneid;
1263

    
1264
	$pipeno = captiveportal_get_next_dn_ruleno();
1265

    
1266
	/* If the pool is empty, return appropriate message and fail authentication */
1267
	if (empty($pipeno)) {
1268
		$auth_list = array();
1269
		$auth_list['auth_val'] = 1;
1270
		$auth_list['error'] = "System reached maximum login capacity";
1271
		return $auth_list;
1272
	}
1273

    
1274
	$radiusservers = captiveportal_get_radius_servers();
1275

    
1276
	if (is_null($radiusctx))
1277
		$radiusctx = 'first';
1278

    
1279
	$auth_list = RADIUS_AUTHENTICATION($username,
1280
		$password,
1281
		$radiusservers[$radiusctx],
1282
		$clientip,
1283
		$clientmac,
1284
		$pipeno);
1285

    
1286
	if ($auth_list['auth_val'] == 2) {
1287
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1288
		$sessionid = portal_allow($clientip,
1289
			$clientmac,
1290
			$username,
1291
			$password,
1292
			$auth_list,
1293
			$pipeno,
1294
			$radiusctx);
1295
	} else {
1296
	         captiveportal_free_dn_ruleno($pipeno);
1297
	       }
1298

    
1299
	return $auth_list;
1300
}
1301

    
1302
function captiveportal_opendb() {
1303
	global $g, $cpzone;
1304

    
1305
	$DB = new SQLite3("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1306
	if (! $DB->exec("CREATE TABLE IF NOT EXISTS captiveportal (" .
1307
				"allow_time INTEGER, pipeno INTEGER, ip TEXT, mac TEXT, username TEXT, " .
1308
				"sessionid TEXT, bpassword TEXT, session_timeout INTEGER, idle_timeout INTEGER, " .
1309
				"session_terminate_time INTEGER, interim_interval INTEGER, radiusctx TEXT); " .
1310
			"CREATE UNIQUE INDEX IF NOT EXISTS idx_active ON captiveportal (sessionid, username); " .
1311
			"CREATE INDEX IF NOT EXISTS user ON captiveportal (username); " .
1312
			"CREATE INDEX IF NOT EXISTS ip ON captiveportal (ip); " .
1313
			"CREATE INDEX IF NOT EXISTS starttime ON captiveportal (allow_time)"))
1314
		captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$DB->lastErrorMsg()}");
1315

    
1316
	return $DB;
1317
}
1318

    
1319
/* read captive portal DB into array */
1320
function captiveportal_read_db($query = "") {
1321
	$cpdb = array();
1322

    
1323
	$DB = captiveportal_opendb();
1324
	if ($DB) {
1325
		$response = $DB->query("SELECT * FROM captiveportal {$query}");
1326
		if ($response != FALSE) {
1327
			while ($row = $response->fetchArray())
1328
				$cpdb[] = $row;
1329
		}
1330
		$DB->close();
1331
	}
1332

    
1333
	return $cpdb;
1334
}
1335

    
1336
function captiveportal_remove_entries($remove) {
1337

    
1338
	if (!is_array($remove) || empty($remove))
1339
		return;
1340

    
1341
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1342
	foreach($remove as $idx => $unindex) {
1343
		$query .= "'{$unindex}'";
1344
		if ($idx < (count($remove) - 1))
1345
			$query .= ",";
1346
	}
1347
	$query .= ")";
1348
	captiveportal_write_db($query);
1349
}
1350

    
1351
/* write captive portal DB */
1352
function captiveportal_write_db($queries) {
1353
	global $g;
1354

    
1355
	if (is_array($queries))
1356
		$query = implode(";", $queries);
1357
	else
1358
		$query = $queries;
1359

    
1360
	$DB = captiveportal_opendb();
1361
	if ($DB) {
1362
		$DB->exec("BEGIN TRANSACTION");
1363
		$result = $DB->exec($query);
1364
		if (!$result)
1365
			captiveportal_syslog("Trying to modify DB returned error: {$DB->lastErrorMsg()}");
1366
		else
1367
			$DB->exec("END TRANSACTION");
1368
		$DB->close();
1369
		return $result;
1370
	} else
1371
		return true;
1372
}
1373

    
1374
function captiveportal_write_elements() {
1375
	global $g, $config, $cpzone;
1376
	
1377
	$cpcfg = $config['captiveportal'][$cpzone];
1378

    
1379
	if (!is_dir($g['captiveportal_element_path']))
1380
		@mkdir($g['captiveportal_element_path']);
1381

    
1382
	if (is_array($cpcfg['element'])) {
1383
		conf_mount_rw();
1384
		foreach ($cpcfg['element'] as $data) {
1385
			if (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1386
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1387
				return 1;
1388
			}
1389
			if (!file_exists("{$g['captiveportal_path']}/{$data['name']}"))
1390
				@symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1391
		}
1392
		conf_mount_ro();
1393
	}
1394
	
1395
	return 0;
1396
}
1397

    
1398
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1399
	global $cpzone;
1400

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

    
1420
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1421
	global $config, $g, $cpzone;
1422

    
1423
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1424
	$ruleno = 0;
1425
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1426
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1427
		$ridx = $rulenos_start;
1428
		while ($ridx < $rulenos_range_max) {
1429
			if (empty($rules[$ridx])) {
1430
				$ruleno = $ridx;
1431
				$rules[$ridx] = $cpzone;
1432
				$ridx++;
1433
				$rules[$ridx] = $cpzone;
1434
				break;
1435
			} else {
1436
				$ridx += 2;
1437
			}
1438
		}
1439
	} else {
1440
		$rules = array_pad(array(), $rulenos_range_max, false);
1441
		$ruleno = $rulenos_start;
1442
		$rules[$rulenos_start] = $cpzone;
1443
		$rulenos_start++;
1444
		$rules[$rulenos_start] = $cpzone;
1445
	}
1446
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1447
	unlock($cpruleslck);
1448
	unset($rules);
1449

    
1450
	return $ruleno;
1451
}
1452

    
1453
function captiveportal_free_dn_ruleno($ruleno) {
1454
	global $config, $g;
1455

    
1456
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1457
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1458
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1459
		$rules[$ruleno] = false;
1460
		$ruleno++;
1461
		$rules[$ruleno] = false;
1462
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1463
		unset($rules);
1464
	}
1465
	unlock($cpruleslck);
1466
}
1467

    
1468
function captiveportal_get_dn_passthru_ruleno($value) {
1469
	global $config, $g, $cpzone, $cpzoneid;
1470

    
1471
	$cpcfg = $config['captiveportal'][$cpzone];
1472
	if(!isset($cpcfg['enable']))
1473
		return NULL;
1474

    
1475
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1476
	$ruleno = NULL;
1477
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1478
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1479
		unset($output);
1480
		$_gb = exec("/sbin/ipfw -x {$cpzoneid} show | /usr/bin/grep " . escapeshellarg($value) . " | /usr/bin/grep -v grep | /usr/bin/awk '{print $5}' | /usr/bin/head -n 1", $output);
1481
		$ruleno = intval($output[0]);
1482
		if (!$rules[$ruleno])
1483
			$ruleno = NULL;
1484
		unset($rules);
1485
	}
1486
	unlock($cpruleslck);
1487

    
1488
	return $ruleno;
1489
}
1490

    
1491
/*
1492
 * This function will calculate the lowest free firewall ruleno
1493
 * within the range specified based on the actual logged on users
1494
 *
1495
 */
1496
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1497
	global $config, $g, $cpzone;
1498

    
1499
	$cpcfg = $config['captiveportal'][$cpzone];
1500
	if(!isset($cpcfg['enable']))
1501
		return NULL;
1502

    
1503
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1504
	$ruleno = 0;
1505
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1506
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1507
		$ridx = $rulenos_start;
1508
		while ($ridx < $rulenos_range_max) {
1509
			if (empty($rules[$ridx])) {
1510
				$ruleno = $ridx;
1511
				$rules[$ridx] = $cpzone;
1512
				$ridx++;
1513
				$rules[$ridx] = $cpzone;
1514
				break;
1515
			} else {
1516
				/* 
1517
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1518
				 * and the out pipe ruleno + 1.
1519
				 */
1520
				$ridx += 2;
1521
			}
1522
		}
1523
	} else {
1524
		$rules = array_pad(array(), $rulenos_range_max, false);
1525
		$ruleno = $rulenos_start;
1526
		$rules[$rulenos_start] = $cpzone;
1527
		$rulenos_start++;
1528
		$rules[$rulenos_start] = $cpzone;
1529
	}
1530
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1531
	unlock($cpruleslck);
1532
	unset($rules);
1533

    
1534
	return $ruleno;
1535
}
1536

    
1537
function captiveportal_free_ipfw_ruleno($ruleno) {
1538
	global $config, $g, $cpzone;
1539

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

    
1544
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1545
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1546
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1547
		$rules[$ruleno] = false;
1548
		$ruleno++;
1549
		$rules[$ruleno] = false;
1550
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1551
		unset($rules);
1552
	}
1553
	unlock($cpruleslck);
1554
}
1555

    
1556
function captiveportal_get_ipfw_passthru_ruleno($value) {
1557
	global $config, $g, $cpzone, $cpzoneid;
1558

    
1559
	$cpcfg = $config['captiveportal'][$cpzone];
1560
	if(!isset($cpcfg['enable']))
1561
		return NULL;
1562

    
1563
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1564
	$ruleno = NULL;
1565
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1566
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1567
		unset($output);
1568
		$_gb = exec("/sbin/ipfw -x {$cpzoneid} show | /usr/bin/grep " . escapeshellarg($value) . " | /usr/bin/grep -v grep | /usr/bin/awk '{print $1}' | /usr/bin/head -n 1", $output);
1569
		$ruleno = intval($output[0]);
1570
		if (!$rules[$ruleno])
1571
			$ruleno = NULL;
1572
		unset($rules);
1573
	}
1574
	unlock($cpruleslck);
1575

    
1576
	return $ruleno;
1577
}
1578

    
1579
/**
1580
 * This function will calculate the traffic produced by a client
1581
 * based on its firewall rule
1582
 *
1583
 * Point of view: NAS
1584
 *
1585
 * Input means: from the client
1586
 * Output means: to the client
1587
 *
1588
 */
1589

    
1590
function getVolume($ip, $mac = NULL) {
1591
	global $config, $cpzone, $cpzoneid;
1592

    
1593
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1594
	$volume = array();
1595
	// Initialize vars properly, since we don't want NULL vars
1596
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1597

    
1598
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, 1, $ip, $mac);
1599
	if (is_array($ipfw)) {
1600
		if ($reverse) {
1601
			$volume['output_pkts'] = $ipfw['packets'];
1602
			$volume['output_bytes'] = $ipfw['bytes'];
1603
		}
1604
		else {
1605
			$volume['input_pkts'] = $ipfw['packets'];
1606
			$volume['input_bytes'] = $ipfw['bytes'];
1607
		}
1608
	}
1609

    
1610
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, 2, $ip);
1611
	if (is_array($ipfw)) {
1612
		if ($reverse) {
1613
			$volume['input_pkts'] = $ipfw['packets'];
1614
			$volume['input_bytes'] = $ipfw['bytes'];
1615
		}
1616
		else {
1617
			$volume['output_pkts'] = $ipfw['packets'];
1618
			$volume['output_bytes'] = $ipfw['bytes'];
1619
		}
1620
	}
1621

    
1622
	return $volume;
1623
}
1624

    
1625
/**
1626
 * Get the NAS-IP-Address based on the current wan address
1627
 *
1628
 * Use functions in interfaces.inc to find this out
1629
 *
1630
 */
1631

    
1632
function getNasIP()
1633
{
1634
	global $config, $cpzone;
1635

    
1636
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1637
			$nasIp = get_interface_ip();
1638
	} else {
1639
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1640
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1641
		else
1642
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1643
	}
1644
		
1645
	if(!is_ipaddr($nasIp))
1646
		$nasIp = "0.0.0.0";
1647

    
1648
	return $nasIp;
1649
}
1650

    
1651
function portal_ip_from_client_ip($cliip) {
1652
	global $config, $cpzone;
1653

    
1654
	$isipv6 = is_ipaddrv6($cliip);
1655
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1656
	foreach ($interfaces as $cpif) {
1657
		if ($isipv6) {
1658
			$ip = get_interface_ipv6($cpif);
1659
			$sn = get_interface_subnetv6($cpif);
1660
		} else {
1661
			$ip = get_interface_ip($cpif);
1662
			$sn = get_interface_subnet($cpif);
1663
		}
1664
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1665
			return $ip;
1666
	}
1667

    
1668
	$inet = ($isipv6) ? '-inet6' : '-inet';
1669
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1670
	$iface = trim($iface, "\n");
1671
	if (!empty($iface)) {
1672
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1673
		if (is_ipaddr($ip))
1674
			return $ip;
1675
	}
1676

    
1677
	// doesn't match up to any particular interface
1678
	// so let's set the portal IP to what PHP says 
1679
	// the server IP issuing the request is. 
1680
	// allows same behavior as 1.2.x where IP isn't 
1681
	// in the subnet of any CP interface (static routes, etc.)
1682
	// rather than forcing to DNS hostname resolution
1683
	$ip = $_SERVER['SERVER_ADDR'];
1684
	if (is_ipaddr($ip))
1685
		return $ip;
1686

    
1687
	return false;
1688
}
1689

    
1690
function portal_hostname_from_client_ip($cliip) {
1691
	global $config, $cpzone;
1692

    
1693
	$cpcfg = $config['captiveportal'][$cpzone];
1694

    
1695
	if (isset($cpcfg['httpslogin'])) {
1696
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 8001);
1697
		$ourhostname = $cpcfg['httpsname'];
1698
		
1699
		if ($listenporthttps != 443)
1700
			$ourhostname .= ":" . $listenporthttps;
1701
	} else {
1702
		$listenporthttp  = $cpcfg['listenporthttp']  ? $cpcfg['listenporthttp']  : ($cpcfg['zoneid'] + 8000);
1703
		$ifip = portal_ip_from_client_ip($cliip);
1704
		if (!$ifip)
1705
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1706
		else
1707
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1708
		
1709
		if ($listenporthttp != 80)
1710
			$ourhostname .= ":" . $listenporthttp;
1711
	}
1712
	
1713
	return $ourhostname;
1714
}
1715

    
1716
/* functions move from index.php */
1717

    
1718
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1719
	global $g, $config, $cpzone;
1720

    
1721
	/* Get captive portal layout */
1722
	if ($type == "redir") {
1723
		header("Location: {$redirurl}");
1724
		return;
1725
	} else if ($type == "login")
1726
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1727
	else
1728
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1729

    
1730
	$cpcfg = $config['captiveportal'][$cpzone];
1731

    
1732
	/* substitute the PORTAL_REDIRURL variable */
1733
	if ($cpcfg['preauthurl']) {
1734
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1735
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1736
	}
1737

    
1738
	/* substitute other variables */
1739
	$ourhostname = portal_hostname_from_client_ip($clientip);
1740
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1741
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1742
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1743

    
1744
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1745
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1746
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1747
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1748
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1749

    
1750
	// Special handling case for captive portal master page so that it can be ran 
1751
	// through the PHP interpreter using the include method above.  We convert the
1752
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1753
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1754
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1755
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1756
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1757
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1758
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1759
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1760

    
1761
	echo $htmltext;
1762
}
1763

    
1764
function portal_mac_radius($clientmac,$clientip) {
1765
	global $config, $cpzone;
1766

    
1767
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1768

    
1769
	/* authentication against the radius server */
1770
	$username = mac_format($clientmac);
1771
	$auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1772
	if ($auth_list['auth_val'] == 2)
1773
		return TRUE;
1774

    
1775
	if (!empty($auth_list['url_redirection']))
1776
		portal_reply_page($auth_list['url_redirection'], "redir");
1777

    
1778
	return FALSE;
1779
}
1780

    
1781
function captiveportal_reapply_attributes($cpentry, $attributes) {
1782
	global $config, $cpzone, $g;
1783

    
1784
	$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1785
	$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1786
	$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1787
	$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1788
	$bw_up_pipeno = $cpentry[1];
1789
	$bw_down_pipeno = $cpentry[1]+1;
1790

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

    
1795
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1796
}
1797

    
1798
function portal_allow($clientip,$clientmac,$username,$password = null, $attributes = null, $pipeno = null, $radiusctx = null)  {
1799
	global $redirurl, $g, $config, $type, $passthrumac, $_POST, $cpzone, $cpzoneid;
1800

    
1801
	// Ensure we create an array if we are missing attributes
1802
	if (!is_array($attributes))
1803
		$attributes = array();
1804

    
1805
	unset($sessionid);
1806

    
1807
	/* Do not allow concurrent login execution. */
1808
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1809

    
1810
	if ($attributes['voucher'])
1811
		$remaining_time = $attributes['session_timeout'];
1812

    
1813
	$writecfg = false;
1814
	/* Find an existing session */
1815
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
1816
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1817
			$mac = captiveportal_passthrumac_findbyname($username);
1818
			if (!empty($mac)) {
1819
				if ($_POST['replacemacpassthru']) {
1820
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
1821
						if ($macent['mac'] == $mac['mac']) {
1822
							$macrules = "";
1823
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1824
							$pipeno = captiveportal_get_dn_passthru_ruleno($mac['mac']);
1825
							if ($ruleno) {
1826
								captiveportal_free_ipfw_ruleno($ruleno);
1827
								$macrules .= "delete {$ruleno}\n";
1828
								++$ruleno;
1829
								$macrules .= "delete {$ruleno}\n";
1830
							}
1831
							if ($pipeno) {
1832
								captiveportal_free_dn_ruleno($pipeno);
1833
								$macrules .= "pipe delete {$pipeno}\n";
1834
								++$pipeno;
1835
								$macrules .= "pipe delete {$pipeno}\n";
1836
							}
1837
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1838
							$mac['action'] = 'pass';
1839
							$mac['mac'] = $clientmac;
1840
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1841
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1842
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1843
							mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1844
							$writecfg = true;
1845
							$sessionid = true;
1846
							break;
1847
						}
1848
					}
1849
				} else {
1850
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1851
						$clientmac, $clientip, $username, $password);
1852
					unlock($cpdblck);
1853
					return;
1854
				}
1855
			}
1856
		}
1857
	}
1858

    
1859
	/* read in client database */
1860
	$query = "WHERE ip = '{$clientip}'";
1861
	$tmpusername = strtolower($username);
1862
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins']))
1863
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
1864
	$cpdb = captiveportal_read_db($query);
1865

    
1866
	/* Snapshot the timestamp */
1867
	$allow_time = time();
1868
	$radiusservers = captiveportal_get_radius_servers();
1869
	$unsetindexes = array();
1870
	if (is_null($radiusctx))
1871
		$radiusctx = 'first';
1872

    
1873
	foreach ($cpdb as $cpentry) {
1874
		if (empty($cpentry[11])) {
1875
			$cpentry[11] = 'first';
1876
		}
1877
		/* on the same ip */
1878
		if ($cpentry[2] == $clientip) {
1879
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac)
1880
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1881
			else
1882
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
1883
			$sessionid = $cpentry[5];
1884
			break;
1885
		}
1886
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1887
			// user logged in with an active voucher. Check for how long and calculate 
1888
			// how much time we can give him (voucher credit - used time)
1889
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1890
			if ($remaining_time < 0)    // just in case. 
1891
				$remaining_time = 0;
1892

    
1893
			/* This user was already logged in so we disconnect the old one */
1894
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[11]],13);
1895
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1896
			$unsetindexes[] = $cpentry[5];
1897
			break;
1898
		}
1899
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1900
			/* on the same username */
1901
			if (strcasecmp($cpentry[4], $username) == 0) {
1902
				/* This user was already logged in so we disconnect the old one */
1903
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[11]],13);
1904
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1905
				$unsetindexes[] = $cpentry[5];
1906
				break;
1907
			}
1908
		}
1909
	}
1910
	unset($cpdb);
1911

    
1912
	if (!empty($unsetindexes))
1913
		captiveportal_remove_entries($unsetindexes);
1914

    
1915
	if ($attributes['voucher'] && $remaining_time <= 0)
1916
		return 0;       // voucher already used and no time left
1917

    
1918
	if (!isset($sessionid)) {
1919
		/* generate unique session ID */
1920
		$tod = gettimeofday();
1921
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1922

    
1923
		if ($passthrumac) {
1924
			$mac = array();
1925
			$mac['action'] = 'pass';
1926
			$mac['mac'] = $clientmac;
1927
			$mac['ip'] = $clientip; /* Used only for logging */
1928
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
1929
				$mac['username'] = $username;
1930
				if ($attributes['voucher'])
1931
					$mac['logintype'] = "voucher";
1932
			}
1933
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1934
			if (!empty($bw_up))
1935
				$mac['bw_up'] = $bw_up;
1936
			if (!empty($bw_down))
1937
				$mac['bw_down'] = $bw_down;
1938
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
1939
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
1940
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1941
			unlock($cpdblck);
1942
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1943
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1944
			mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1945
			$writecfg = true;
1946
		} else {
1947
			/* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
1948
			if (is_null($pipeno))
1949
				$pipeno = captiveportal_get_next_dn_ruleno();
1950

    
1951
			/* if the pool is empty, return appropriate message and exit */
1952
			if (is_null($pipeno)) {
1953
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1954
				log_error("Zone: {$cpzone} - WARNING!  Captive portal has reached maximum login capacity");
1955
				unlock($cpdblck);
1956
				return;
1957
			}
1958

    
1959
			$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1960
			$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1961
			$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1962
			$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1963

    
1964
			$bw_up_pipeno = $pipeno;
1965
			$bw_down_pipeno = $pipeno + 1;
1966
			//$bw_up /= 1000; // Scale to Kbit/s
1967
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1968
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1969

    
1970
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
1971
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1972
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
1973
			else
1974
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
1975

    
1976
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1977
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
1978
			else
1979
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
1980

    
1981
			if ($attributes['voucher'])
1982
				$attributes['session_timeout'] = $remaining_time;
1983
			
1984
			/* handle empty attributes */
1985
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
1986
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
1987
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
1988
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
1989

    
1990
			/* escape username */
1991
			$safe_username = SQLite3::escapeString($username);
1992

    
1993
			/* encode password in Base64 just in case it contains commas */
1994
			$bpassword = base64_encode($password);
1995
			$insertquery  = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, radiusctx) ";
1996
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
1997
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, '{$radiusctx}')";
1998

    
1999
			/* store information to database */
2000
			captiveportal_write_db($insertquery);
2001
			unlock($cpdblck);
2002
			unset($insertquery, $bpassword);
2003

    
2004
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
2005
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
2006
				if ($acct_val == 1)
2007
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
2008
			}
2009
		}
2010
	} else {
2011
		/* NOTE: #3062-11 If the pipeno has been allocated free it to not DoS the CP and maintain proper operation as in radius() case */
2012
		if (!is_null($pipeno))
2013
			captiveportal_free_dn_ruleno($pipeno);
2014

    
2015
		unlock($cpdblck);
2016
	}
2017

    
2018
	if ($writecfg == true)
2019
		write_config();
2020

    
2021
	/* redirect user to desired destination */
2022
	if (!empty($attributes['url_redirection']))
2023
		$my_redirurl = $attributes['url_redirection'];
2024
	else if (!empty($redirurl))
2025
		$my_redirurl = $redirurl;
2026
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
2027
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
2028

    
2029
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
2030
		$ourhostname = portal_hostname_from_client_ip($clientip);
2031
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
2032
		$logouturl = "{$protocol}{$ourhostname}/";
2033

    
2034
		if (isset($attributes['reply_message']))
2035
			$message = $attributes['reply_message'];
2036
		else
2037
			$message = 0;
2038

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

    
2041
	} else {
2042
		portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
2043
	}
2044

    
2045
	return $sessionid;
2046
}
2047

    
2048

    
2049
/*
2050
 * Used for when pass-through credits are enabled.
2051
 * Returns true when there was at least one free login to deduct for the MAC.
2052
 * Expired entries are removed as they are seen.
2053
 * Active entries are updated according to the configuration.
2054
 */
2055
function portal_consume_passthrough_credit($clientmac) {
2056
	global $config, $cpzone;
2057

    
2058
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
2059
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
2060
	else
2061
		return false;
2062

    
2063
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
2064
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2065
	else
2066
		return false;
2067

    
2068
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
2069
		return false;
2070

    
2071
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2072

    
2073
	/*
2074
	 * Read database of used MACs.  Lines are a comma-separated list
2075
	 * of the time, MAC, then the count of pass-through credits remaining.
2076
	 */
2077
	$usedmacs = captiveportal_read_usedmacs_db();
2078

    
2079
	$currenttime = time();
2080
	$found = false;
2081
	foreach ($usedmacs as $key => $usedmac) {
2082
		$usedmac = explode(",", $usedmac);
2083

    
2084
		if ($usedmac[1] == $clientmac) {
2085
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2086
				if ($usedmac[2] < 1) {
2087
					if ($updatetimeouts) {
2088
						$usedmac[0] = $currenttime;
2089
						unset($usedmacs[$key]);
2090
						$usedmacs[] = implode(",", $usedmac);
2091
						captiveportal_write_usedmacs_db($usedmacs);
2092
					}
2093

    
2094
					return false;
2095
				} else {
2096
					$usedmac[2] -= 1;
2097
					$usedmacs[$key] = implode(",", $usedmac);
2098
				}
2099

    
2100
				$found = true;
2101
			} else
2102
				unset($usedmacs[$key]);
2103

    
2104
			break;
2105
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2106
				unset($usedmacs[$key]);
2107
	}
2108

    
2109
	if (!$found) {
2110
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2111
		$usedmacs[] = implode(",", $usedmac);
2112
	}
2113

    
2114
	captiveportal_write_usedmacs_db($usedmacs);
2115
	return true;
2116
}
2117

    
2118
function captiveportal_read_usedmacs_db() {
2119
	global $g, $cpzone;
2120

    
2121
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2122
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2123
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2124
		if (!$usedmacs)
2125
			$usedmacs = array();
2126
	} else
2127
		$usedmacs = array();
2128

    
2129
	unlock($cpumaclck);
2130
	return $usedmacs;
2131
}
2132

    
2133
function captiveportal_write_usedmacs_db($usedmacs) {
2134
	global $g, $cpzone;
2135

    
2136
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2137
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2138
	unlock($cpumaclck);
2139
}
2140

    
2141
function captiveportal_blocked_mac($mac) {
2142
	global $config, $g, $cpzone;
2143

    
2144
	if (empty($mac) || !is_macaddr($mac))
2145
		return false;
2146

    
2147
	if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
2148
		return false;
2149

    
2150
	foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac)
2151
		if (($passthrumac['action'] == 'block') &&
2152
		    ($passthrumac['mac'] == strtolower($mac)))
2153
			return true;
2154

    
2155
	return false;
2156

    
2157
}
2158

    
2159
function captiveportal_send_server_accounting($off = false) {
2160
	global $cpzone, $config;
2161

    
2162
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
2163
		return;
2164
	}
2165
	if ($off) {
2166
		$racct = new Auth_RADIUS_Acct_Off;
2167
	} else {
2168
		$racct = new Auth_RADIUS_Acct_On;
2169
	}
2170
	$radiusservers = captiveportal_get_radius_servers();
2171
	if (empty($radiusservers)) {
2172
		return;
2173
	}
2174
	foreach ($radiusservers['first'] as $radsrv) {
2175
		// Add a new server to our instance
2176
		$racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
2177
	}
2178
	if (PEAR::isError($racct->start())) {
2179
		$retvalue['acct_val'] = 1;
2180
		$retvalue['error'] = $racct->getMessage();
2181

    
2182
		// If we encounter an error immediately stop this function and go back
2183
		$racct->close();
2184
		return $retvalue;
2185
	}
2186
	// Send request
2187
	$result = $racct->send();
2188
	// Evaluation of the response
2189
	// 5 -> Accounting-Response
2190
	// See RFC2866 for this.
2191
	if (PEAR::isError($result)) {
2192
		$retvalue['acct_val'] = 1;
2193
		$retvalue['error'] = $result->getMessage();
2194
	} else if ($result === true) {
2195
		$retvalue['acct_val'] = 5 ;
2196
	} else {
2197
		$retvalue['acct_val'] = 1 ;
2198
	}
2199

    
2200
	$racct->close();
2201
	return $retvalue;
2202
}
2203
?>
(8-8/68)