Projet

Général

Profil

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

univnautes / etc / inc / captiveportal.inc @ 26d060bc

1
<?php
2
/*
3
	captiveportal.inc
4
	part of pfSense (https://www.pfsense.org)
5
	Copyright (C) 2004-2011 Scott Ullrich <sullrich@gmail.com>
6
	Copyright (C) 2009-2012 Ermal Lu�i <eri@pfsense.org>
7
	Copyright (C) 2003-2006 Manuel Kasper <mk@neon1.net>.
8

    
9
	originally part of m0n0wall (http://m0n0.ch/wall)
10
	All rights reserved.
11

    
12
	Redistribution and use in source and binary forms, with or without
13
	modification, are permitted provided that the following conditions are met:
14

    
15
	1. Redistributions of source code must retain the above copyright notice,
16
	   this list of conditions and the following disclaimer.
17

    
18
	2. Redistributions in binary form must reproduce the above copyright
19
	   notice, this list of conditions and the following disclaimer in the
20
	   documentation and/or other materials provided with the distribution.
21

    
22
	THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
23
	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
24
	AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25
	AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
26
	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27
	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28
	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29
	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31
	POSSIBILITY OF SUCH DAMAGE.
32

    
33
	This version of captiveportal.inc has been modified by Rob Parker
34
	<rob.parker@keycom.co.uk> to include changes for per-user bandwidth management
35
	via returned RADIUS attributes. This page has been modified to delete any
36
	added rules which may have been created by other per-user code (index.php, etc).
37
	These changes are (c) 2004 Keycom PLC.
38
	
39
	pfSense_BUILDER_BINARIES:	/sbin/ipfw	/sbin/sysctl	/sbin/route
40
	pfSense_BUILDER_BINARIES:	/usr/local/sbin/lighttpd	/usr/local/bin/minicron /sbin/pfctl
41
	pfSense_BUILDER_BINARIES:	/bin/hostname	/bin/cp 
42
	pfSense_MODULE: captiveportal
43
*/
44

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

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

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

    
103
EOD;
104

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

    
112
EOD;
113
	}
114

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

    
142
EOD;
143

    
144
	return $htmltext;
145
}
146

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

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

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

    
168
function captiveportal_configure() {
169
	global $config, $cpzone, $cpzoneid;
170

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

    
180
function captiveportal_configure_zone($cpcfg) {
181
	global $config, $g, $cpzone, $cpzoneid;
182

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

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

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

    
195
		/* init ipfw rules */
196
		captiveportal_init_rules(true);
197

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

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

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

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

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

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

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

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

    
295
document.location.href="<?=\$my_redirurl;?>";
296
-->
297
</script>
298
</body>
299
</html>
300

    
301
EOD;
302
		}
303

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

    
311
		/* write elements */
312
		captiveportal_write_elements();
313

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

    
318
		/* start up the webserving daemon */
319
		captiveportal_init_webgui_zone($cpcfg);
320

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

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

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

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

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

    
347
		captiveportal_radius_stop_all();
348

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

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

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

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

    
382
	unlock($captiveportallck);
383
	
384
	return 0;
385
}
386

    
387
function captiveportal_init_webgui() {
388
	global $config, $cpzone;
389

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

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

    
407
function captiveportal_init_webgui_zone($cpcfg) {
408
	global $g, $config, $cpzone;
409

    
410
	if (!isset($cpcfg['enable']))
411
		return;
412

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

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

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

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

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

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

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

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

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

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

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

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

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

    
508
EOD;
509

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

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

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

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

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

    
570
EOD;
571

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

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

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

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

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

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

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

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

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

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

    
622
	$radiussrvs = captiveportal_get_radius_servers();
623

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

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

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

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

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

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

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

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

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

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

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

    
762
	captiveportal_prune_old_automac();
763

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

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

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

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

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

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

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

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

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

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

    
887
}
888

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

    
893
	$radiusservers = captiveportal_get_radius_servers();
894

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

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

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

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

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

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

    
939
function captiveportal_passthrumac_configure_entry($macent) {
940
	global $config, $g, $cpzone;
941

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

    
953
	$ruleno = captiveportal_get_next_ipfw_ruleno();
954

    
955
	if ($macent['action'] == 'pass') {
956
		$pipeno = captiveportal_get_next_dn_ruleno();
957

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

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

    
968
	return $rules;
969
}
970

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

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

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

    
980
		captiveportal_free_ipfw_ruleno($ruleno);
981

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

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

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

    
994
	return $rules;
995
}
996

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

    
1000
	$rules = "";
1001

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

    
1006
	return $rules;
1007
}
1008

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

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

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

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

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

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

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

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

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

    
1099
	return $rules;
1100
}
1101

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

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

    
1111
	return $rules;
1112
}
1113

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

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

    
1124
	return 0;
1125
}
1126

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1249
	$pipeno = captiveportal_get_next_dn_ruleno();
1250

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

    
1259
	$radiusservers = captiveportal_get_radius_servers();
1260

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

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

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

    
1284
	return $auth_list;
1285
}
1286

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

    
1290
	if (file_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db"))
1291
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1292
	else {
1293
		$errormsg = "";
1294
		$DB = @sqlite_open("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1295
		if (@sqlite_exec($DB, "CREATE TABLE captiveportal (allow_time INTEGER, pipeno INTEGER, ip TEXT, mac TEXT, username TEXT, sessionid TEXT, bpassword TEXT, session_timeout INTEGER, idle_timeout INTEGER, session_terminate_time INTEGER, interim_interval INTEGER, radiusctx TEXT) ", $errormsg)) {
1296
			@sqlite_exec($DB, "CREATE UNIQUE INDEX idx_active ON captiveportal (sessionid, username)");
1297
			@sqlite_exec($DB, "CREATE INDEX user ON captiveportal (username)");
1298
			@sqlite_exec($DB, "CREATE INDEX ip ON captiveportal (ip)");
1299
			@sqlite_exec($DB, "CREATE INDEX starttime ON captiveportal (allow_time)");
1300
			@sqlite_exec($DB, "CREATE INDEX serviceid ON captiveportal (serviceid)");
1301
		} else
1302
			captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$errormsg}");
1303
	}
1304

    
1305
	return $DB;
1306
}
1307

    
1308
/* read captive portal DB into array */
1309
function captiveportal_read_db($query = "") {
1310

    
1311
	$DB = captiveportal_opendb();
1312
	if ($DB) {
1313
		sqlite_exec($DB, "BEGIN");
1314
		if (!empty($query))
1315
			$cpdb = @sqlite_array_query($DB, "SELECT * FROM captiveportal {$query}", SQLITE_NUM);
1316
		else {
1317
			$response = @sqlite_unbuffered_query($DB, "SELECT * FROM captiveportal", SQLITE_NUM);
1318
			$cpdb = @sqlite_fetch_all($response, SQLITE_NUM);
1319
		}
1320
		sqlite_exec($DB, "END");
1321
		@sqlite_close($DB);
1322
	}
1323
	if (!$cpdb)
1324
		$cpdb = array();
1325

    
1326
	return $cpdb;
1327
}
1328

    
1329
function captiveportal_remove_entries($remove) {
1330

    
1331
	if (!is_array($remove) || empty($remove))
1332
		return;
1333

    
1334
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1335
	foreach($remove as $idx => $unindex) {
1336
		$query .= "'{$unindex}'";
1337
		if ($idx < (count($remove) - 1))
1338
			$query .= ",";
1339
	}
1340
	$query .= ")";
1341
	captiveportal_write_db($query);
1342
}
1343

    
1344
/* write captive portal DB */
1345
function captiveportal_write_db($queries) {
1346
	global $g;
1347

    
1348
	if (is_array($queries))
1349
		$query = implode(";", $queries);
1350
	else
1351
		$query = $queries;
1352

    
1353
	$DB = captiveportal_opendb();
1354
	if ($DB) {
1355
		$error_msg = "";
1356
		sqlite_exec($DB, "BEGIN TRANSACTION");
1357
		$result = @sqlite_exec($DB, $query, $error_msg);
1358
		if (!$result)
1359
			captiveportal_syslog("Trying to modify DB returned error: {$error_msg}");
1360
		else
1361
			sqlite_exec($DB, "END TRANSACTION");
1362
		@sqlite_close($DB);
1363
		return $result;
1364
	} else
1365
		return true;
1366
}
1367

    
1368
function captiveportal_write_elements() {
1369
	global $g, $config, $cpzone;
1370
	
1371
	$cpcfg = $config['captiveportal'][$cpzone];
1372

    
1373
	if (!is_dir($g['captiveportal_element_path']))
1374
		@mkdir($g['captiveportal_element_path']);
1375

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

    
1392
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1393
	global $cpzone;
1394

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

    
1414
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1415
	global $config, $g, $cpzone;
1416

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

    
1444
	return $ruleno;
1445
}
1446

    
1447
function captiveportal_free_dn_ruleno($ruleno) {
1448
	global $config, $g;
1449

    
1450
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1451
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1452
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1453
		$rules[$ruleno] = false;
1454
		$ruleno++;
1455
		$rules[$ruleno] = false;
1456
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1457
		unset($rules);
1458
	}
1459
	unlock($cpruleslck);
1460
}
1461

    
1462
function captiveportal_get_dn_passthru_ruleno($value) {
1463
	global $config, $g, $cpzone, $cpzoneid;
1464

    
1465
	$cpcfg = $config['captiveportal'][$cpzone];
1466
	if(!isset($cpcfg['enable']))
1467
		return NULL;
1468

    
1469
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1470
	$ruleno = NULL;
1471
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1472
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1473
		unset($output);
1474
		$_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);
1475
		$ruleno = intval($output[0]);
1476
		if (!$rules[$ruleno])
1477
			$ruleno = NULL;
1478
		unset($rules);
1479
	}
1480
	unlock($cpruleslck);
1481

    
1482
	return $ruleno;
1483
}
1484

    
1485
/*
1486
 * This function will calculate the lowest free firewall ruleno
1487
 * within the range specified based on the actual logged on users
1488
 *
1489
 */
1490
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1491
	global $config, $g, $cpzone;
1492

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

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

    
1528
	return $ruleno;
1529
}
1530

    
1531
function captiveportal_free_ipfw_ruleno($ruleno) {
1532
	global $config, $g, $cpzone;
1533

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

    
1538
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1539
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1540
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1541
		$rules[$ruleno] = false;
1542
		$ruleno++;
1543
		$rules[$ruleno] = false;
1544
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1545
		unset($rules);
1546
	}
1547
	unlock($cpruleslck);
1548
}
1549

    
1550
function captiveportal_get_ipfw_passthru_ruleno($value) {
1551
	global $config, $g, $cpzone, $cpzoneid;
1552

    
1553
	$cpcfg = $config['captiveportal'][$cpzone];
1554
	if(!isset($cpcfg['enable']))
1555
		return NULL;
1556

    
1557
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1558
	$ruleno = NULL;
1559
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1560
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1561
		unset($output);
1562
		$_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);
1563
		$ruleno = intval($output[0]);
1564
		if (!$rules[$ruleno])
1565
			$ruleno = NULL;
1566
		unset($rules);
1567
	}
1568
	unlock($cpruleslck);
1569

    
1570
	return $ruleno;
1571
}
1572

    
1573
/**
1574
 * This function will calculate the traffic produced by a client
1575
 * based on its firewall rule
1576
 *
1577
 * Point of view: NAS
1578
 *
1579
 * Input means: from the client
1580
 * Output means: to the client
1581
 *
1582
 */
1583

    
1584
function getVolume($ip, $mac = NULL) {
1585
	global $config, $cpzone;
1586

    
1587
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1588
	$volume = array();
1589
	// Initialize vars properly, since we don't want NULL vars
1590
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1591

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

    
1604
	$ipfw = pfSense_ipfw_getTablestats($cpzone, 2, $ip);
1605
	if (is_array($ipfw)) {
1606
		if ($reverse) {
1607
			$volume['input_pkts'] = $ipfw['packets'];
1608
			$volume['input_bytes'] = $ipfw['bytes'];
1609
		}
1610
		else {
1611
			$volume['output_pkts'] = $ipfw['packets'];
1612
			$volume['output_bytes'] = $ipfw['bytes'];
1613
		}
1614
	}
1615

    
1616
	return $volume;
1617
}
1618

    
1619
/**
1620
 * Get the NAS-IP-Address based on the current wan address
1621
 *
1622
 * Use functions in interfaces.inc to find this out
1623
 *
1624
 */
1625

    
1626
function getNasIP()
1627
{
1628
	global $config, $cpzone;
1629

    
1630
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1631
			$nasIp = get_interface_ip();
1632
	} else {
1633
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1634
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1635
		else
1636
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1637
	}
1638
		
1639
	if(!is_ipaddr($nasIp))
1640
		$nasIp = "0.0.0.0";
1641

    
1642
	return $nasIp;
1643
}
1644

    
1645
function portal_ip_from_client_ip($cliip) {
1646
	global $config, $cpzone;
1647

    
1648
	$isipv6 = is_ipaddrv6($cliip);
1649
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1650
	foreach ($interfaces as $cpif) {
1651
		if ($isipv6) {
1652
			$ip = get_interface_ipv6($cpif);
1653
			$sn = get_interface_subnetv6($cpif);
1654
		} else {
1655
			$ip = get_interface_ip($cpif);
1656
			$sn = get_interface_subnet($cpif);
1657
		}
1658
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1659
			return $ip;
1660
	}
1661

    
1662
	$inet = ($isipv6) ? '-inet6' : '-inet';
1663
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1664
	$iface = trim($iface, "\n");
1665
	if (!empty($iface)) {
1666
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1667
		if (is_ipaddr($ip))
1668
			return $ip;
1669
	}
1670

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

    
1681
	return false;
1682
}
1683

    
1684
function portal_hostname_from_client_ip($cliip) {
1685
	global $config, $cpzone;
1686

    
1687
	$cpcfg = $config['captiveportal'][$cpzone];
1688

    
1689
	if (isset($cpcfg['httpslogin'])) {
1690
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
1691
		$ourhostname = $cpcfg['httpsname'];
1692
		
1693
		if ($listenporthttps != 443)
1694
			$ourhostname .= ":" . $listenporthttps;
1695
	} else {
1696
		$listenporthttp  = $cpcfg['listenporthttp']  ? $cpcfg['listenporthttp']  : $cpcfg['zoneid'];
1697
		$ifip = portal_ip_from_client_ip($cliip);
1698
		if (!$ifip)
1699
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1700
		else
1701
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1702
		
1703
		if ($listenporthttp != 80)
1704
			$ourhostname .= ":" . $listenporthttp;
1705
	}
1706
	
1707
	return $ourhostname;
1708
}
1709

    
1710
/* functions move from index.php */
1711

    
1712
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1713
	global $g, $config, $cpzone;
1714

    
1715
	/* Get captive portal layout */
1716
	if ($type == "redir") {
1717
		header("Location: {$redirurl}");
1718
		return;
1719
	} else if ($type == "login")
1720
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1721
	else
1722
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1723

    
1724
	$cpcfg = $config['captiveportal'][$cpzone];
1725

    
1726
	/* substitute the PORTAL_REDIRURL variable */
1727
	if ($cpcfg['preauthurl']) {
1728
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1729
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1730
	}
1731

    
1732
	/* substitute other variables */
1733
	$ourhostname = portal_hostname_from_client_ip($clientip);
1734
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1735
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1736
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1737

    
1738
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1739
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1740
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1741
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1742
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1743

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

    
1755
	echo $htmltext;
1756
}
1757

    
1758
function portal_mac_radius($clientmac,$clientip) {
1759
	global $config, $cpzone;
1760

    
1761
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1762

    
1763
	/* authentication against the radius server */
1764
	$username = mac_format($clientmac);
1765
	$auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1766
	if ($auth_list['auth_val'] == 2)
1767
		return TRUE;
1768

    
1769
	if (!empty($auth_list['url_redirection']))
1770
		portal_reply_page($auth_list['url_redirection'], "redir");
1771

    
1772
	return FALSE;
1773
}
1774

    
1775
function captiveportal_reapply_attributes($cpentry, $attributes) {
1776
	global $config, $cpzone, $g;
1777

    
1778
	$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1779
	$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1780
	$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1781
	$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1782
	$bw_up_pipeno = $cpentry[1];
1783
	$bw_down_pipeno = $cpentry[1]+1;
1784

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

    
1789
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1790
}
1791

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

    
1795
	// Ensure we create an array if we are missing attributes
1796
	if (!is_array($attributes))
1797
		$attributes = array();
1798

    
1799
	unset($sessionid);
1800

    
1801
	/* Do not allow concurrent login execution. */
1802
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1803

    
1804
	if ($attributes['voucher'])
1805
		$remaining_time = $attributes['session_timeout'];
1806

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

    
1853
	/* read in client database */
1854
	$query = "WHERE ip = '{$clientip}'";
1855
	$tmpusername = strtolower($username);
1856
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins']))
1857
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
1858
	$cpdb = captiveportal_read_db($query);
1859

    
1860
	/* Snapshot the timestamp */
1861
	$allow_time = time();
1862
	$radiusservers = captiveportal_get_radius_servers();
1863
	$unsetindexes = array();
1864
	if (is_null($radiusctx))
1865
		$radiusctx = 'first';
1866

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

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

    
1906
	if (!empty($unsetindexes))
1907
		captiveportal_remove_entries($unsetindexes);
1908

    
1909
	if ($attributes['voucher'] && $remaining_time <= 0)
1910
		return 0;       // voucher already used and no time left
1911

    
1912
	if (!isset($sessionid)) {
1913
		/* generate unique session ID */
1914
		$tod = gettimeofday();
1915
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1916

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

    
1945
			/* if the pool is empty, return appropriate message and exit */
1946
			if (is_null($pipeno)) {
1947
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1948
				log_error("Zone: {$cpzone} - WARNING!  Captive portal has reached maximum login capacity");
1949
				unlock($cpdblck);
1950
				return;
1951
			}
1952

    
1953
			$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1954
			$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1955
			$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1956
			$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1957

    
1958
			$bw_up_pipeno = $pipeno;
1959
			$bw_down_pipeno = $pipeno + 1;
1960
			//$bw_up /= 1000; // Scale to Kbit/s
1961
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1962
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1963

    
1964
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
1965
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1966
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
1967
			else
1968
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
1969

    
1970
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1971
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
1972
			else
1973
				$_gb = @pfSense_ipfw_Tableaction($cpzone, IP_FW_TABLE_ADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
1974

    
1975
			if ($attributes['voucher'])
1976
				$attributes['session_timeout'] = $remaining_time;
1977
			
1978
			/* handle empty attributes */
1979
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
1980
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
1981
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
1982
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
1983

    
1984
			/* escape username */
1985
			$safe_username = sqlite_escape_string($username);
1986

    
1987
			/* encode password in Base64 just in case it contains commas */
1988
			$bpassword = base64_encode($password);
1989
			$insertquery  = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, radiusctx) ";
1990
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
1991
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, '{$radiusctx}')";
1992

    
1993
			/* store information to database */
1994
			captiveportal_write_db($insertquery);
1995
			unlock($cpdblck);
1996
			unset($insertquery, $bpassword);
1997

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

    
2009
		unlock($cpdblck);
2010
	}
2011

    
2012
	if ($writecfg == true)
2013
		write_config();
2014

    
2015
	/* redirect user to desired destination */
2016
	if (!empty($attributes['url_redirection']))
2017
		$my_redirurl = $attributes['url_redirection'];
2018
	else if (!empty($redirurl))
2019
		$my_redirurl = $redirurl;
2020
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
2021
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
2022

    
2023
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
2024
		$ourhostname = portal_hostname_from_client_ip($clientip);
2025
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
2026
		$logouturl = "{$protocol}{$ourhostname}/";
2027

    
2028
		if (isset($attributes['reply_message']))
2029
			$message = $attributes['reply_message'];
2030
		else
2031
			$message = 0;
2032

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

    
2035
	} else {
2036
		portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
2037
	}
2038

    
2039
	return $sessionid;
2040
}
2041

    
2042

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

    
2052
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
2053
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
2054
	else
2055
		return false;
2056

    
2057
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
2058
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2059
	else
2060
		return false;
2061

    
2062
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
2063
		return false;
2064

    
2065
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2066

    
2067
	/*
2068
	 * Read database of used MACs.  Lines are a comma-separated list
2069
	 * of the time, MAC, then the count of pass-through credits remaining.
2070
	 */
2071
	$usedmacs = captiveportal_read_usedmacs_db();
2072

    
2073
	$currenttime = time();
2074
	$found = false;
2075
	foreach ($usedmacs as $key => $usedmac) {
2076
		$usedmac = explode(",", $usedmac);
2077

    
2078
		if ($usedmac[1] == $clientmac) {
2079
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2080
				if ($usedmac[2] < 1) {
2081
					if ($updatetimeouts) {
2082
						$usedmac[0] = $currenttime;
2083
						unset($usedmacs[$key]);
2084
						$usedmacs[] = implode(",", $usedmac);
2085
						captiveportal_write_usedmacs_db($usedmacs);
2086
					}
2087

    
2088
					return false;
2089
				} else {
2090
					$usedmac[2] -= 1;
2091
					$usedmacs[$key] = implode(",", $usedmac);
2092
				}
2093

    
2094
				$found = true;
2095
			} else
2096
				unset($usedmacs[$key]);
2097

    
2098
			break;
2099
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2100
				unset($usedmacs[$key]);
2101
	}
2102

    
2103
	if (!$found) {
2104
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2105
		$usedmacs[] = implode(",", $usedmac);
2106
	}
2107

    
2108
	captiveportal_write_usedmacs_db($usedmacs);
2109
	return true;
2110
}
2111

    
2112
function captiveportal_read_usedmacs_db() {
2113
	global $g, $cpzone;
2114

    
2115
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2116
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2117
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2118
		if (!$usedmacs)
2119
			$usedmacs = array();
2120
	} else
2121
		$usedmacs = array();
2122

    
2123
	unlock($cpumaclck);
2124
	return $usedmacs;
2125
}
2126

    
2127
function captiveportal_write_usedmacs_db($usedmacs) {
2128
	global $g, $cpzone;
2129

    
2130
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2131
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2132
	unlock($cpumaclck);
2133
}
2134

    
2135
function captiveportal_blocked_mac($mac) {
2136
	global $config, $g, $cpzone;
2137

    
2138
	if (empty($mac) || !is_macaddr($mac))
2139
		return false;
2140

    
2141
	if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
2142
		return false;
2143

    
2144
	foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac)
2145
		if (($passthrumac['action'] == 'block') &&
2146
		    ($passthrumac['mac'] == strtolower($mac)))
2147
			return true;
2148

    
2149
	return false;
2150

    
2151
}
2152

    
2153
function captiveportal_send_server_accounting($off = false) {
2154
	global $cpzone, $config;
2155

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

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

    
2194
	$racct->close();
2195
	return $retvalue;
2196
}
2197
?>
(8-8/67)