Projet

Général

Profil

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

univnautes / etc / inc / captiveportal.inc @ c650b2f7

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) {
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
		$pipeno = captiveportal_get_next_dn_ruleno();
958

    
959
		$pipeup = $pipeno;
960
		$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16");
961
		$pipedown = $pipeno + 1;
962
		$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
963

    
964
		$rules = "add {$ruleno} pipe {$pipeup} ip from any to any MAC any {$macent['mac']}\n";
965
		$ruleno++;
966
		$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC {$macent['mac']} any\n";
967
	}
968

    
969
	return $rules;
970
}
971

    
972
function captiveportal_passthrumac_delete_entry($macent) {
973
	$rules = "";
974

    
975
	if ($macent['action'] == 'pass') {
976
		$ruleno = captiveportal_get_ipfw_passthru_ruleno($macent['mac']);
977

    
978
		if (!$ruleno)
979
			return $rules;
980

    
981
		captiveportal_free_ipfw_ruleno($ruleno);
982

    
983
		$rules .= "delete {$ruleno}\n";
984
		$rules .= "delete " . ++$ruleno . "\n";
985

    
986
		$pipeno = captiveportal_get_dn_passthru_ruleno($macent['mac']);
987

    
988
		if (!empty($pipeno)) {
989
			captiveportal_free_dn_ruleno($pipeno);
990
			$rules .= "pipe delete " . $pipeno . "\n";
991
			$rules .= "pipe delete " . ++$pipeno . "\n";
992
		}
993
	}
994

    
995
	return $rules;
996
}
997

    
998
function captiveportal_passthrumac_configure($lock = false) {
999
	global $config, $g, $cpzone;
1000

    
1001
	$rules = "";
1002

    
1003
	if (is_array($config['captiveportal'][$cpzone]['passthrumac']))
1004
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent)
1005
			$rules .= captiveportal_passthrumac_configure_entry($macent);
1006

    
1007
	return $rules;
1008
}
1009

    
1010
function captiveportal_passthrumac_findbyname($username) {
1011
	global $config, $cpzone;
1012

    
1013
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1014
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1015
			if ($macent['username'] == $username)
1016
				return $macent;
1017
		}
1018
	}
1019
	return NULL;
1020
}
1021

    
1022
/* 
1023
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1024
 */
1025
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
1026
	global $g;
1027

    
1028
	/*  Instead of copying this entire function for something
1029
	 *  easy such as hostname vs ip address add this check
1030
	 */
1031
	if ($ishostname === true) {
1032
		if (!$g['booting']) {
1033
			$ipaddress = gethostbyname($ipent['hostname']);
1034
			if (!is_ipaddr($ipaddress)) 
1035
				return;
1036
		} else
1037
			$ipaddress = "";
1038
	} else
1039
		$ipaddress = $ipent['ip'];
1040

    
1041
	$rules = "";
1042
	$cp_filterdns_conf = "";
1043
	$enBwup = 0;
1044
	if (!empty($ipent['bw_up']))
1045
		$enBwup = intval($ipent['bw_up']);
1046
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultup']))
1047
		$enBwup = $config['captiveportal'][$cpzone]['bwdefaultup'];
1048
	$enBwdown = 0;
1049
	if (!empty($ipent['bw_down']))
1050
		$enBwdown = intval($ipent['bw_down']);
1051
	else if (isset($config['captiveportal'][$cpzone]['bwdefaultdn']))
1052
		$enBwdown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
1053

    
1054
	$pipeno = captiveportal_get_next_dn_ruleno();
1055
	$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$enBwup}Kbit/s queue 100 buckets 16");
1056
	$pipedown = $pipeno + 1;
1057
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16");
1058
	if ($ishostname === true) {
1059
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
1060
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
1061
		if (!is_ipaddr($ipaddress))
1062
			return array("", $cp_filterdns_conf);
1063
	}
1064
	$subnet = "";
1065
	if (!empty($ipent['sn']))
1066
		$subnet = "/{$ipent['sn']}";
1067
	$rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
1068
	$rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
1069

    
1070
	if ($ishostname === true)
1071
		return array($rules, $cp_filterdns_conf);
1072
	else
1073
		return $rules;
1074
}
1075

    
1076
function captiveportal_allowedhostname_configure() {
1077
	global $config, $g, $cpzone;
1078

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

    
1100
	return $rules;
1101
}
1102

    
1103
function captiveportal_allowedip_configure() {
1104
	global $config, $g, $cpzone;
1105

    
1106
	$rules = "";
1107
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1108
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1109
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1110
	}
1111

    
1112
	return $rules;
1113
}
1114

    
1115
/* get last activity timestamp given client IP address */
1116
function captiveportal_get_last_activity($ip, $mac = NULL) {
1117
	global $cpzone;
1118

    
1119
	$ipfwoutput = pfSense_ipfw_getTablestats($cpzone, 1, $ip, $mac);
1120
	/* Reading only from one of the tables is enough of approximation. */
1121
	if (is_array($ipfwoutput)) {
1122
		return $ipfwoutput['timestamp'];
1123
	}
1124

    
1125
	return 0;
1126
}
1127

    
1128
function captiveportal_init_radius_servers() {
1129
	global $config, $g, $cpzone;
1130

    
1131
	/* generate radius server database */
1132
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1133
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1134
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1135
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1136
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1137
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1138

    
1139
		if ($config['captiveportal'][$cpzone]['radiusport'])
1140
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1141
		else
1142
			$radiusport = 1812;
1143
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1144
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1145
		else
1146
			$radiusacctport = 1813;
1147
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1148
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1149
		else
1150
			$radiusport2 = 1812;
1151
		if ($config['captiveportal'][$cpzone]['radiusport3'])
1152
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1153
		else
1154
			$radiusport3 = 1812;
1155
		if ($config['captiveportal'][$cpzone]['radiusport4'])
1156
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1157
		else
1158
			$radiusport4 = 1812;
1159

    
1160
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1161
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1162
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1163
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1164

    
1165
		$cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX);
1166
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
1167
		if (!$fd) {
1168
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1169
			unlock($cprdsrvlck);
1170
			return 1;
1171
		}
1172
		if (isset($radiusip))
1173
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
1174
		if (isset($radiusip2))
1175
			fwrite($fd,"\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
1176
		if (isset($radiusip3))
1177
			fwrite($fd,"\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
1178
		if (isset($radiusip4))
1179
			fwrite($fd,"\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
1180
		
1181

    
1182
		fclose($fd);
1183
		unlock($cprdsrvlck);
1184
	}
1185
}
1186

    
1187
/* read RADIUS servers into array */
1188
function captiveportal_get_radius_servers() {
1189
	global $g, $cpzone;
1190

    
1191
	$cprdsrvlck = lock("captiveportalradius{$cpzone}");
1192
	if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
1193
		$radiusservers = array();
1194
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", 
1195
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1196
		if ($cpradiusdb) {
1197
			foreach($cpradiusdb as $cpradiusentry) {
1198
				$line = trim($cpradiusentry);
1199
				if ($line) {
1200
					$radsrv = array();
1201
						list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key'], $context) = explode(",",$line);
1202
				}
1203
				if (empty($context)) {
1204
					if (!is_array($radiusservers['first']))
1205
						$radiusservers['first'] = array();
1206
					$radiusservers['first'] = $radsrv;
1207
				} else {
1208
					if (!is_array($radiusservers[$context]))
1209
						$radiusservers[$context] = array();
1210
					$radiusservers[$context][] = $radsrv;
1211
				}
1212
			}
1213
		}
1214
		unlock($cprdsrvlck);
1215
		return $radiusservers;
1216
	}
1217

    
1218
	unlock($cprdsrvlck);
1219
	return false;
1220
}
1221

    
1222
/* log successful captive portal authentication to syslog */
1223
/* part of this code from php.net */
1224
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1225
	// Log it
1226
	if (!$message)
1227
		$message = "{$status}: {$user}, {$mac}, {$ip}";
1228
	else {
1229
		$message = trim($message);
1230
		$message = "{$status}: {$user}, {$mac}, {$ip}, {$message}";
1231
	}
1232
	captiveportal_syslog($message);
1233
}
1234

    
1235
/* log simple messages to syslog */
1236
function captiveportal_syslog($message) {
1237
	global $cpzone;
1238

    
1239
	$message = trim($message);
1240
	$message .= "Zone: {$cpzone} - {$message}";
1241
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1242
	// Log it
1243
	syslog(LOG_INFO, $message);
1244
	closelog();
1245
}
1246

    
1247
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1248
	global $g, $config, $cpzoneid;
1249

    
1250
	$pipeno = captiveportal_get_next_dn_ruleno();
1251

    
1252
	/* If the pool is empty, return appropriate message and fail authentication */
1253
	if (empty($pipeno)) {
1254
		$auth_list = array();
1255
		$auth_list['auth_val'] = 1;
1256
		$auth_list['error'] = "System reached maximum login capacity";
1257
		return $auth_list;
1258
	}
1259

    
1260
	$radiusservers = captiveportal_get_radius_servers();
1261

    
1262
	if (is_null($radiusctx))
1263
		$radiusctx = 'first';
1264

    
1265
	$auth_list = RADIUS_AUTHENTICATION($username,
1266
		$password,
1267
		$radiusservers[$radiusctx],
1268
		$clientip,
1269
		$clientmac,
1270
		$pipeno);
1271

    
1272
	if ($auth_list['auth_val'] == 2) {
1273
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1274
		$sessionid = portal_allow($clientip,
1275
			$clientmac,
1276
			$username,
1277
			$password,
1278
			$auth_list,
1279
			$pipeno,
1280
			$radiusctx);
1281
	} else {
1282
	         captiveportal_free_dn_ruleno($pipeno);
1283
	       }
1284

    
1285
	return $auth_list;
1286
}
1287

    
1288
function captiveportal_opendb() {
1289
	global $g, $cpzone;
1290

    
1291
	$DB = new SQLite3("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1292
	if (! $DB->exec("CREATE TABLE IF NOT EXISTS captiveportal (" .
1293
				"allow_time INTEGER, pipeno INTEGER, ip TEXT, mac TEXT, username TEXT, " .
1294
				"sessionid TEXT, bpassword TEXT, session_timeout INTEGER, idle_timeout INTEGER, " .
1295
				"session_terminate_time INTEGER, interim_interval INTEGER, radiusctx TEXT); " .
1296
			"CREATE UNIQUE INDEX IF NOT EXISTS idx_active ON captiveportal (sessionid, username); " .
1297
			"CREATE INDEX IF NOT EXISTS user ON captiveportal (username); " .
1298
			"CREATE INDEX IF NOT EXISTS ip ON captiveportal (ip); " .
1299
			"CREATE INDEX IF NOT EXISTS starttime ON captiveportal (allow_time)"))
1300
		captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$DB->lastErrorMsg()}");
1301

    
1302
	return $DB;
1303
}
1304

    
1305
/* read captive portal DB into array */
1306
function captiveportal_read_db($query = "") {
1307
	$cpdb = array();
1308

    
1309
	$DB = captiveportal_opendb();
1310
	if ($DB) {
1311
		$response = $DB->query("SELECT * FROM captiveportal {$query}");
1312
		while ($row = $response->fetchArray())
1313
			$cpdb[] = $row;
1314
		$DB->close();
1315
	}
1316

    
1317
	return $cpdb;
1318
}
1319

    
1320
function captiveportal_remove_entries($remove) {
1321

    
1322
	if (!is_array($remove) || empty($remove))
1323
		return;
1324

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

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

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

    
1344
	$DB = captiveportal_opendb();
1345
	if ($DB) {
1346
		$DB->exec("BEGIN TRANSACTION");
1347
		$result = $DB->exec($query);
1348
		if (!$result)
1349
			captiveportal_syslog("Trying to modify DB returned error: {$DB->lastErrorMsg()}");
1350
		else
1351
			$DB->exec("END TRANSACTION");
1352
		$DB->close();
1353
		return $result;
1354
	} else
1355
		return true;
1356
}
1357

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

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

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

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

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

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

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

    
1434
	return $ruleno;
1435
}
1436

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

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

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

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

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

    
1472
	return $ruleno;
1473
}
1474

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

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

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

    
1518
	return $ruleno;
1519
}
1520

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

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

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

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

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

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

    
1560
	return $ruleno;
1561
}
1562

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

    
1574
function getVolume($ip, $mac = NULL) {
1575
	global $config, $cpzone;
1576

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

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

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

    
1606
	return $volume;
1607
}
1608

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

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

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

    
1632
	return $nasIp;
1633
}
1634

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

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

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

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

    
1671
	return false;
1672
}
1673

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

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

    
1679
	if (isset($cpcfg['httpslogin'])) {
1680
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 8001);
1681
		$ourhostname = $cpcfg['httpsname'];
1682
		
1683
		if ($listenporthttps != 443)
1684
			$ourhostname .= ":" . $listenporthttps;
1685
	} else {
1686
		$listenporthttp  = $cpcfg['listenporthttp']  ? $cpcfg['listenporthttp']  : ($cpcfg['zoneid'] + 8000);
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, $cpzoneid;
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['action'] = 'pass';
1823
							$mac['mac'] = $clientmac;
1824
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1825
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1826
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1827
							mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1828
							$writecfg = true;
1829
							$sessionid = true;
1830
							break;
1831
						}
1832
					}
1833
				} else {
1834
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1835
						$clientmac, $clientip, $username, $password);
1836
					unlock($cpdblck);
1837
					return;
1838
				}
1839
			}
1840
		}
1841
	}
1842

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1974
			/* escape username */
1975
			$safe_username = SQLite3::escapeString($username);
1976

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

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

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

    
1999
		unlock($cpdblck);
2000
	}
2001

    
2002
	if ($writecfg == true)
2003
		write_config();
2004

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

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

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

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

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

    
2029
	return $sessionid;
2030
}
2031

    
2032

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

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

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

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

    
2055
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2056

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

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

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

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

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

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

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

    
2098
	captiveportal_write_usedmacs_db($usedmacs);
2099
	return true;
2100
}
2101

    
2102
function captiveportal_read_usedmacs_db() {
2103
	global $g, $cpzone;
2104

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

    
2113
	unlock($cpumaclck);
2114
	return $usedmacs;
2115
}
2116

    
2117
function captiveportal_write_usedmacs_db($usedmacs) {
2118
	global $g, $cpzone;
2119

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

    
2125
function captiveportal_blocked_mac($mac) {
2126
	global $config, $g, $cpzone;
2127

    
2128
	if (empty($mac) || !is_macaddr($mac))
2129
		return false;
2130

    
2131
	if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
2132
		return false;
2133

    
2134
	foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac)
2135
		if (($passthrumac['action'] == 'block') &&
2136
		    ($passthrumac['mac'] == strtolower($mac)))
2137
			return true;
2138

    
2139
	return false;
2140

    
2141
}
2142

    
2143
function captiveportal_send_server_accounting($off = false) {
2144
	global $cpzone, $config;
2145

    
2146
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
2147
		return;
2148
	}
2149
	if ($off) {
2150
		$racct = new Auth_RADIUS_Acct_Off;
2151
	} else {
2152
		$racct = new Auth_RADIUS_Acct_On;
2153
	}
2154
	$radiusservers = captiveportal_get_radius_servers();
2155
	if (empty($radiusservers)) {
2156
		return;
2157
	}
2158
	foreach ($radiusservers['first'] as $radsrv) {
2159
		// Add a new server to our instance
2160
		$racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
2161
	}
2162
	if (PEAR::isError($racct->start())) {
2163
		$retvalue['acct_val'] = 1;
2164
		$retvalue['error'] = $racct->getMessage();
2165

    
2166
		// If we encounter an error immediately stop this function and go back
2167
		$racct->close();
2168
		return $retvalue;
2169
	}
2170
	// Send request
2171
	$result = $racct->send();
2172
	// Evaluation of the response
2173
	// 5 -> Accounting-Response
2174
	// See RFC2866 for this.
2175
	if (PEAR::isError($result)) {
2176
		$retvalue['acct_val'] = 1;
2177
		$retvalue['error'] = $result->getMessage();
2178
	} else if ($result === true) {
2179
		$retvalue['acct_val'] = 5 ;
2180
	} else {
2181
		$retvalue['acct_val'] = 1 ;
2182
	}
2183

    
2184
	$racct->close();
2185
	return $retvalue;
2186
}
2187
?>
(8-8/68)