Project

General

Profile

Download (74.4 KB) Statistics
| Branch: | Tag: | Revision:
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 (platform_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
		if (is_array($cpcfg['passthrumac'])) {
198
			$nentries = count($cpcfg['passthrumac']);
199
			if ($nentries > 2000) {
200
				if (!set_time_limit(0))
201
					log_error("Execution time limit may be reached while reconfiguring zone = {$cpzone} due to many passthrough entries!");
202
			}
203
		}
204
					
205
		/* init ipfw rules */
206
		captiveportal_init_rules(true);
207

    
208
		/* kill any running minicron */
209
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
210

    
211
		/* initialize minicron interval value */
212
		$croninterval = $cpcfg['croninterval'] ? $cpcfg['croninterval'] : 60;
213

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

    
218
		/* write portal page */
219
		if (is_array($cpcfg['page']) && $cpcfg['page']['htmltext'])
220
			$htmltext = base64_decode($cpcfg['page']['htmltext']);
221
		else {
222
			/* example/template page */
223
			$htmltext = get_default_captive_portal_html();
224
		}
225

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

    
246
		/* write error page */
247
		if (is_array($cpcfg['page']) && $cpcfg['page']['errtext'])
248
			$errtext = base64_decode($cpcfg['page']['errtext']);
249
		else {
250
			/* example page  */
251
			$errtext = get_default_captive_portal_html();
252
		}
253

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

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

    
305
document.location.href="<?=\$my_redirurl;?>";
306
//]]>
307
</script>
308
</body>
309
</html>
310

    
311
EOD;
312
		}
313

    
314
		$fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html", "w");
315
		if ($fd) {
316
			fwrite($fd, $logouttext);
317
			fclose($fd);
318
		}
319
		unset($logouttext);
320

    
321
		/* write elements */
322
		captiveportal_write_elements();
323

    
324
		/* kill any running mini_httpd */
325
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
326
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
327

    
328
		/* start up the webserving daemon */
329
		captiveportal_init_webgui_zone($cpcfg);
330

    
331
		/* Kill any existing prunecaptiveportal processes */
332
		if (file_exists("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid"))
333
			killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
334

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

    
339
		/* generate radius server database */
340
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
341
		captiveportal_init_radius_servers();
342

    
343
		if (platform_booting()) {
344
			/* send Accounting-On to server */
345
			captiveportal_send_server_accounting();
346
			echo "done\n";
347
		}
348

    
349
	} else {
350
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
351
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
352
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
353
		@unlink("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
354
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
355
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
356

    
357
		captiveportal_radius_stop_all();
358

    
359
		/* send Accounting-Off to server */
360
		if (!platform_booting()) {
361
			captiveportal_send_server_accounting(true);
362
		}
363

    
364
		/* remove old information */
365
		unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
366
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
367
		unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
368
		/* Release allocated pipes for this zone */
369
		captiveportal_free_dnrules();
370

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

    
373
		if (empty($config['captiveportal']))
374
			set_single_sysctl("net.link.ether.ipfw", "0");
375
		else {
376
			/* Deactivate ipfw(4) if not needed */
377
			$cpactive = false;
378
			if (is_array($config['captiveportal'])) {
379
				foreach ($config['captiveportal'] as $cpkey => $cp) {
380
					if (isset($cp['enable'])) {
381
						$cpactive = true;
382
						break;
383
					}
384
				}
385
			}
386
			if ($cpactive === false)
387
				set_single_sysctl("net.link.ether.ipfw", "0");
388

    
389
		}
390
	}
391

    
392
	/* XXX: Same as in rc.php_init_setup */
393
	set_time_limit(900);
394
	unlock($captiveportallck);
395
	
396
	return 0;
397
}
398

    
399
function captiveportal_init_webgui() {
400
	global $config, $cpzone;
401

    
402
	if (is_array($config['captiveportal'])) {
403
		foreach ($config['captiveportal'] as $cpkey => $cp) {
404
			$cpzone = $cpkey;
405
			captiveportal_init_webgui_zone($cp);
406
		}
407
	}
408
}
409

    
410
function captiveportal_init_webgui_zonename($zone) {
411
	global $config, $cpzone;
412
	
413
	if (isset($config['captiveportal'][$zone])) {
414
		$cpzone = $zone;
415
		captiveportal_init_webgui_zone($config['captiveportal'][$zone]);
416
	}
417
}
418

    
419
function captiveportal_init_webgui_zone($cpcfg) {
420
	global $g, $config, $cpzone;
421

    
422
	if (!isset($cpcfg['enable']))
423
		return;
424

    
425
	if (isset($cpcfg['httpslogin'])) {
426
		$cert = lookup_cert($cpcfg['certref']);
427
		$crt = base64_decode($cert['crt']);
428
		$key = base64_decode($cert['prv']);
429
		$ca = ca_chain($cert);
430

    
431
		/* generate lighttpd configuration */
432
		if (!empty($cpcfg['listenporthttps']))
433
			$listenporthttps = $cpcfg['listenporthttps'];
434
		else
435
			$listenporthttps = 8001 + $cpcfg['zoneid'];
436
		system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf",
437
			$crt, $key, $ca, "lighty-{$cpzone}-CaptivePortal-SSL.pid", $listenporthttps, "/usr/local/captiveportal",
438
			"cert-{$cpzone}-portal.pem", "ca-{$cpzone}-portal.pem", $cpzone);
439
	}
440

    
441
	/* generate lighttpd configuration */
442
	if (!empty($cpcfg['listenporthttp']))
443
		$listenporthttp = $cpcfg['listenporthttp'];
444
	else
445
		$listenporthttp = 8000 + $cpcfg['zoneid'];
446
	system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal.conf",
447
		"", "", "", "lighty-{$cpzone}-CaptivePortal.pid", $listenporthttp, "/usr/local/captiveportal",
448
		"", "", $cpzone);
449

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

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

    
461
function captiveportal_init_rules_byinterface($interface) {
462
	global $cpzone, $cpzoneid, $config;
463

    
464
	if (!is_array($config['captiveportal']))
465
		return;
466

    
467
	foreach ($config['captiveportal'] as $cpkey => $cp) {
468
		$cpzone = $cpkey;
469
		$cpzoneid = $cp['zoneid'];
470
		$cpinterfaces = explode(",", $cp['interface']);
471
		if (in_array($interface, $cpinterfaces)) {
472
			captiveportal_init_rules();
473
			break;
474
		}
475
	}
476
}
477

    
478
/* reinit will disconnect all users, be careful! */
479
function captiveportal_init_rules($reinit = false) {
480
	global $config, $g, $cpzone, $cpzoneid;
481

    
482
	if (!isset($config['captiveportal'][$cpzone]['enable']))
483
		return;
484

    
485
	captiveportal_load_modules();
486
	mwexec("/sbin/ipfw zone {$cpzoneid} create", true);
487

    
488
	/* Cleanup so nothing is leaked */
489
	captiveportal_free_dnrules();
490
	unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
491

    
492
	$cpips = array();
493
	$ifaces = get_configured_interface_list();
494
	$cpinterfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
495
	$firsttime = 0;
496
	foreach ($cpinterfaces as $cpifgrp) {
497
		if (!isset($ifaces[$cpifgrp]))
498
			continue;
499
		$tmpif = get_real_interface($cpifgrp);
500
		if (!empty($tmpif)) {
501
			$cpipm = get_interface_ip($cpifgrp);
502
			if (is_ipaddr($cpipm)) {
503
				$carpif = link_ip_to_carp_interface($cpipm);
504
				if (!empty($carpif)) {
505
					$carpsif = explode(" ", $carpif);
506
					foreach ($carpsif as $cpcarp) {
507
						mwexec("/sbin/ipfw zone {$cpzoneid} madd {$cpcarp}", true);
508
						$carpip = find_interface_ip($cpcarp);
509
						if (is_ipaddr($carpip))
510
							$cpips[] = $carpip;
511
					}
512
				}
513
				$cpips[] = $cpipm;
514
			}
515
			mwexec("/sbin/ipfw zone {$cpzoneid} madd {$tmpif}", true);
516
		}
517
	}
518
	if (count($cpips) > 0) {
519
		$cpactive = true;
520
	} else
521
		return false;
522

    
523
	if ($reinit == false)
524
		$captiveportallck = lock("captiveportal{$cpzone}");
525

    
526
	$cprules =	"add 65291 allow pfsync from any to any\n";
527
	$cprules .= "add 65292 allow carp from any to any\n";
528

    
529
	$cprules .= <<<EOD
530
# layer 2: pass ARP
531
add 65301 pass layer2 mac-type arp,rarp
532
# pfsense requires for WPA
533
add 65302 pass layer2 mac-type 0x888e,0x88c7
534
# PPP Over Ethernet Session Stage/Discovery Stage
535
add 65303 pass layer2 mac-type 0x8863,0x8864
536

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

    
540
EOD;
541

    
542
	$rulenum = 65310;
543
	$ipcount = 0;
544
	$ips = "";
545
	foreach ($cpips as $cpip) {
546
		if($ipcount == 0) {
547
			$ips = "{$cpip} ";
548
		} else {
549
			$ips .= "or {$cpip} ";
550
		}
551
		$ipcount++;
552
	}
553
	$ips = "{ 255.255.255.255 or {$ips} }";
554
	$cprules .= "add {$rulenum} pass ip from any to {$ips} in\n";
555
	$rulenum++;
556
	$cprules .= "add {$rulenum} pass ip from {$ips} to any out\n";
557
	$rulenum++;
558
	$cprules .= "add {$rulenum} pass icmp from {$ips} to any out icmptype 0\n";
559
	$rulenum++;
560
	$cprules .= "add {$rulenum} pass icmp from any to {$ips} in icmptype 8 \n";
561
	$rulenum++;
562
	/* Allowed ips */
563
	$cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any in\n";
564
	$rulenum++;
565
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) in\n";
566
	$rulenum++;
567
	$cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any out\n";
568
	$rulenum++;
569
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) out\n";
570
	$rulenum++;
571

    
572
	/* Authenticated users rules. */
573
	$cprules .= "add {$rulenum} pipe tablearg ip from table(1) to any in\n";
574
	$rulenum++;
575
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(2) out\n";
576
	$rulenum++;
577

    
578
	if (!empty($config['captiveportal'][$cpzone]['listenporthttp']))
579
		$listenporthttp = $config['captiveportal'][$cpzone]['listenporthttp'];
580
	else
581
		$listenporthttp = 8000 + $cpzoneid;
582

    
583
	if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
584
		if (!empty($config['captiveportal'][$cpzone]['listenporthttps']))
585
			$listenporthttps = $config['captiveportal'][$cpzone]['listenporthttps'];
586
		else
587
			$listenporthttps = 8001 + $cpzoneid;
588
			if (!isset($config['captiveportal'][$cpzone]['nohttpsforwards'])) {
589
				$cprules .= "add 65531 fwd 127.0.0.1,{$listenporthttps} tcp from any to any dst-port 443 in\n";
590
			}
591
	}
592
	
593
	$cprules .= <<<EOD
594

    
595
# redirect non-authenticated clients to captive portal
596
add 65532 fwd 127.0.0.1,{$listenporthttp} tcp from any to any dst-port 80 in 
597
# let the responses from the captive portal web server back out
598
add 65533 pass tcp from any to any out
599
# block everything else
600
add 65534 deny all from any to any
601

    
602
EOD;
603

    
604
	/* generate passthru mac database */
605
	$cprules .= captiveportal_passthrumac_configure(true);
606
	$cprules .= "\n";
607

    
608
	/* allowed ipfw rules to make allowed ip work */
609
	$cprules .= captiveportal_allowedip_configure();
610

    
611
	/* allowed ipfw rules to make allowed hostnames work */
612
	$cprules .= captiveportal_allowedhostname_configure();
613
	
614
	/* load rules */
615
	$cprules = "flush\n{$cprules}";
616
	file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
617
	mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
618
	//@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
619
	unset($cprules, $tmprules);
620

    
621
	if ($reinit == false)
622
		unlock($captiveportallck);
623
}
624

    
625
/* 
626
 * Remove clients that have been around for longer than the specified amount of time
627
 * db file structure:
628
 * timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time,interim_interval
629
 * (password is in Base64 and only saved when reauthentication is enabled)
630
 */
631
function captiveportal_prune_old() {
632
	global $g, $config, $cpzone, $cpzoneid;
633

    
634
	if (empty($cpzone))
635
		return;
636

    
637
	$cpcfg = $config['captiveportal'][$cpzone];
638
	$vcpcfg = $config['voucher'][$cpzone];
639

    
640
	/* check for expired entries */
641
	$idletimeout = 0;
642
	$timeout = 0;
643
	if (!empty($cpcfg['timeout']) && is_numeric($cpcfg['timeout']))
644
		$timeout = $cpcfg['timeout'] * 60;
645

    
646
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout']))
647
		$idletimeout = $cpcfg['idletimeout'] * 60;
648

    
649
	/* Is there any job to do? */
650
	if (!$timeout && !$idletimeout && !isset($cpcfg['reauthenticate']) &&
651
	    !isset($cpcfg['radiussession_timeout']) && !isset($vcpcfg['enable']))
652
		return;
653

    
654
	$radiussrvs = captiveportal_get_radius_servers();
655

    
656
	/* Read database */
657
	/* NOTE: while this can be simplified in non radius case keep as is for now */
658
	$cpdb = captiveportal_read_db();
659

    
660
	$unsetindexes = array();
661
	$voucher_needs_sync = false;
662
	/* 
663
	 * Snapshot the time here to use for calculation to speed up the process.
664
	 * If something is missed next run will catch it!
665
	 */
666
	$pruning_time = time();
667
	$stop_time = $pruning_time;
668
	foreach ($cpdb as $cpentry) {
669

    
670
		$timedout = false;
671
		$term_cause = 1;
672
		if (empty($cpentry[11]))
673
			$cpentry[11] = 'first';
674
		$radiusservers = $radiussrvs[$cpentry[11]];
675

    
676
		/* hard timeout? */
677
		if ($timeout) {
678
			if (($pruning_time - $cpentry[0]) >= $timeout) {
679
				$timedout = true;
680
				$term_cause = 5; // Session-Timeout
681
			}
682
		}
683

    
684
		/* Session-Terminate-Time */
685
		if (!$timedout && !empty($cpentry[9])) {
686
			if ($pruning_time >= $cpentry[9]) {
687
				$timedout = true;
688
				$term_cause = 5; // Session-Timeout
689
			}
690
		}
691

    
692
		/* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
693
		$uidletimeout = (is_numeric($cpentry[8])) ? $cpentry[8] : $idletimeout;
694
		/* if an idle timeout is specified, get last activity timestamp from ipfw */
695
		if (!$timedout && $uidletimeout > 0) {
696
			$lastact = captiveportal_get_last_activity($cpentry[2], $cpentry[3]);
697
			/*	If the user has logged on but not sent any traffic they will never be logged out.
698
			 *	We "fix" this by setting lastact to the login timestamp. 
699
			 */
700
			$lastact = $lastact ? $lastact : $cpentry[0];
701
			if ($lastact && (($pruning_time - $lastact) >= $uidletimeout)) {
702
				$timedout = true;
703
				$term_cause = 4; // Idle-Timeout
704
				$stop_time = $lastact; // Entry added to comply with WISPr
705
			}
706
		}
707

    
708
		/* if vouchers are configured, activate session timeouts */
709
		if (!$timedout && isset($vcpcfg['enable']) && !empty($cpentry[7])) {
710
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
711
				$timedout = true;
712
				$term_cause = 5; // Session-Timeout
713
				$voucher_needs_sync = true;
714
			}
715
		}
716

    
717
		/* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
718
		if (!$timedout && isset($cpcfg['radiussession_timeout']) && !empty($cpentry[7])) {
719
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
720
				$timedout = true;
721
				$term_cause = 5; // Session-Timeout
722
			}
723
		}
724

    
725
		if ($timedout) {
726
			captiveportal_disconnect($cpentry, $radiusservers,$term_cause,$stop_time);
727
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
728
			$unsetindexes[] = $cpentry[5];
729
		}
730

    
731
		/* do periodic RADIUS reauthentication? */
732
		if (!$timedout && !empty($radiusservers)) {
733
			if (isset($cpcfg['radacct_enable'])) {
734
				if ($cpcfg['reauthenticateacct'] == "stopstart") {
735
					/* stop and restart accounting */
736
					RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
737
						$cpentry[4], // username
738
						$cpentry[5], // sessionid
739
						$cpentry[0], // start time
740
						$radiusservers,
741
						$cpentry[2], // clientip
742
						$cpentry[3], // clientmac
743
						10); // NAS Request
744
					$clientsn = (is_ipaddrv6($cpentry[2])) ? 128 : 32;
745
					$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XZEROENTRY, 1, $cpentry[2], $clientsn, $cpentry[3]);
746
					$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XZEROENTRY, 2, $cpentry[2], $clientsn, $cpentry[3]);
747
					RADIUS_ACCOUNTING_START($cpentry[1], // ruleno
748
						$cpentry[4], // username
749
						$cpentry[5], // sessionid
750
						$radiusservers,
751
						$cpentry[2], // clientip
752
						$cpentry[3]); // clientmac
753
				} else if ($cpcfg['reauthenticateacct'] == "interimupdate") {
754
					$session_time = $pruning_time - $cpentry[0];
755
					if (!empty($cpentry[10]) && $cpentry[10] > 60)
756
						$interval = $cpentry[10];
757
					else
758
						$interval = 0;
759
					$past_interval_min = ($session_time > $interval);
760
					if ($interval != 0)
761
						$within_interval = ($session_time % $interval >= 0 && $session_time % $interval <= 59);
762
					if ($interval === 0 || ($interval > 0 && $past_interval_min && $within_interval)) {
763
						RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
764
							$cpentry[4], // username
765
							$cpentry[5], // sessionid
766
							$cpentry[0], // start time
767
							$radiusservers,
768
							$cpentry[2], // clientip
769
							$cpentry[3], // clientmac
770
							10, // NAS Request
771
							true); // Interim Updates
772
					}
773
				}
774
			}
775

    
776
			/* check this user against RADIUS again */
777
			if (isset($cpcfg['reauthenticate'])) {
778
				$auth_list = RADIUS_AUTHENTICATION($cpentry[4], // username
779
					base64_decode($cpentry[6]), // password
780
					$radiusservers,
781
					$cpentry[2], // clientip
782
					$cpentry[3], // clientmac
783
					$cpentry[1]); // ruleno
784
				if ($auth_list['auth_val'] == 3) {
785
					captiveportal_disconnect($cpentry, $radiusservers, 17);
786
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
787
					$unsetindexes[] = $cpentry[5];
788
				} else if ($auth_list['auth_val'] == 2)
789
					captiveportal_reapply_attributes($cpentry, $auth_list);
790
			}
791
		}
792
	}
793
	unset($cpdb);
794

    
795
	captiveportal_prune_old_automac();
796

    
797
	if ($voucher_needs_sync == true)
798
		/* Trigger a sync of the vouchers on config */
799
		send_event("service sync vouchers");
800

    
801
	/* write database */
802
	if (!empty($unsetindexes))
803
		captiveportal_remove_entries($unsetindexes);
804
}
805

    
806
function captiveportal_prune_old_automac() {
807
	global $g, $config, $cpzone, $cpzoneid;
808

    
809
	if (is_array($config['captiveportal'][$cpzone]['passthrumac']) && isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
810
		$tmpvoucherdb = array();
811
		$macrules = "";
812
		$writecfg = false;
813
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $eid => $emac) {
814
			if ($emac['logintype'] == "voucher") {
815
				if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) {
816
					if (isset($tmpvoucherdb[$emac['username']])) {
817
						$temac = $config['captiveportal'][$cpzone]['passthrumac'][$tmpvoucherdb[$emac['username']]];
818
						$ruleno = captiveportal_get_ipfw_passthru_ruleno($temac['mac']);
819
						$pipeno = captiveportal_get_dn_passthru_ruleno($temac['mac']);
820
						if ($ruleno) {
821
							captiveportal_free_ipfw_ruleno($ruleno);
822
							$macrules .= "delete {$ruleno}";
823
							++$ruleno;
824
							$macrules .= "delete {$ruleno}";
825
						}
826
						if ($pipeno) {
827
							captiveportal_free_dn_ruleno($pipeno);
828
							$macrules .= "pipe delete {$pipeno}\n";
829
							++$pipeno;
830
							$macrules .= "pipe delete {$pipeno}\n";
831
						}
832
						$writecfg = true;
833
						captiveportal_logportalauth($temac['username'], $temac['mac'], $temac['ip'], "DUPLICATE {$temac['username']} LOGIN - TERMINATING OLD SESSION");
834
						unset($config['captiveportal'][$cpzone]['passthrumac'][$tmpvoucherdb[$emac['username']]]);
835
					}
836
					$tmpvoucherdb[$emac['username']] = $eid;
837
				}
838
				if (voucher_auth($emac['username']) <= 0) {
839
					$ruleno = captiveportal_get_ipfw_passthru_ruleno($emac['mac']);
840
					$pipeno = captiveportal_get_dn_passthru_ruleno($emac['mac']);
841
					if ($ruleno) {
842
						captiveportal_free_ipfw_ruleno($ruleno);
843
						$macrules .= "delete {$ruleno}";
844
						++$ruleno;
845
						$macrules .= "delete {$ruleno}";
846
					}
847
					if ($pipeno) {
848
						captiveportal_free_dn_ruleno($pipeno);
849
						$macrules .= "pipe delete {$pipeno}\n";
850
						++$pipeno;
851
						$macrules .= "pipe delete {$pipeno}\n";
852
					}
853
					$writecfg = true;
854
					captiveportal_logportalauth($emac['username'], $emac['mac'], $emac['ip'], "EXPIRED {$emac['username']} LOGIN - TERMINATING SESSION");
855
					unset($config['captiveportal'][$cpzone]['passthrumac'][$eid]);
856
				}
857
			}
858
		}
859
		unset($tmpvoucherdb);
860
		if (!empty($macrules)) {
861
			@file_put_contents("{$g['tmp_path']}/macentry.prunerules.tmp", $macrules);
862
			unset($macrules);
863
			mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry.prunerules.tmp");
864
		}
865
		if ($writecfg === true)
866
			write_config("Prune session for auto-added macs");
867
	}
868
}
869

    
870
/* remove a single client according to the DB entry */
871
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
872
	global $g, $config, $cpzone, $cpzoneid;
873

    
874
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
875

    
876
	/* this client needs to be deleted - remove ipfw rules */
877
	if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers)) {
878
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
879
			$dbent[4], // username
880
			$dbent[5], // sessionid
881
			$dbent[0], // start time
882
			$radiusservers,
883
			$dbent[2], // clientip
884
			$dbent[3], // clientmac
885
			$term_cause, // Acct-Terminate-Cause
886
			false,
887
			$stop_time);
888
	}
889
	
890
	if (is_ipaddr($dbent[2])) {
891
		/* Delete client's ip entry from tables 1 and 2. */
892
		$clientsn = (is_ipaddrv6($dbent[2])) ? 128 : 32;
893
		pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XDEL, 1, $dbent[2], $clientsn, $dbent[3]);
894
		pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XDEL, 2, $dbent[2], $clientsn, $dbent[3]);
895
		/* XXX: Redundant?! Ensure all pf(4) states are killed. */
896
		$_gb = @pfSense_kill_states($dbent[2]);
897
		$_gb = @pfSense_kill_srcstates($dbent[2]);
898
	}
899

    
900
	/* 
901
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
902
	* We could get an error if the pipe doesn't exist but everything should still be fine
903
	*/
904
	if (!empty($dbent[1])) {
905
		$_gb = @pfSense_pipe_action("pipe delete {$dbent[1]}");
906
		$_gb = @pfSense_pipe_action("pipe delete " . ($dbent[1]+1));
907

    
908
		/* Release the ruleno so it can be reallocated to new clients. */
909
		captiveportal_free_dn_ruleno($dbent[1]);
910
	}
911

    
912
	// XMLRPC Call over to the master Voucher node
913
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
914
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
915
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
916
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
917
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
918
		$remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time);
919
	}
920

    
921
}
922

    
923
/* remove a single client by sessionid */
924
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
925
	global $g, $config;
926

    
927
	$radiusservers = captiveportal_get_radius_servers();
928

    
929
	/* read database */
930
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
931

    
932
	/* find entry */
933
	if (!empty($result)) {
934
		captiveportal_write_db("DELETE FROM captiveportal WHERE sessionid = '{$sessionid}'");
935

    
936
		foreach ($result as $cpentry) {
937
			if (empty($cpentry[11]))
938
				$cpentry[11] = 'first';
939
			captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], $term_cause);
940
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
941
		}
942
		unset($result);
943
	}
944
}
945

    
946
/* send RADIUS acct stop for all current clients */
947
function captiveportal_radius_stop_all() {
948
	global $config, $cpzone;
949

    
950
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable']))
951
		return;
952

    
953
	$radiusservers = captiveportal_get_radius_servers();
954
	if (!empty($radiusservers)) {
955
		$cpdb = captiveportal_read_db();
956
		foreach ($cpdb as $cpentry) {
957
			if (empty($cpentry[11]))
958
				$cpentry[11] = 'first';
959
			if (!empty($radiusservers[$cpentry[11]])) {
960
				RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
961
					$cpentry[4], // username
962
					$cpentry[5], // sessionid
963
					$cpentry[0], // start time
964
					$radiusservers[$cpentry[11]],
965
					$cpentry[2], // clientip
966
					$cpentry[3], // clientmac
967
					7); // Admin Reboot
968
			}
969
		}
970
	}
971
}
972

    
973
function captiveportal_passthrumac_configure_entry($macent, $pipeinrule = false) {
974
	global $config, $g, $cpzone;
975

    
976
	$bwUp = 0;
977
	if (!empty($macent['bw_up']))
978
		$bwUp = $macent['bw_up'];
979
	else if (!empty($config['captiveportal'][$cpzone]['bwdefaultup']))
980
		$bwUp = $config['captiveportal'][$cpzone]['bwdefaultup'];
981
	$bwDown = 0;
982
	if (!empty($macent['bw_down']))
983
		$bwDown = $macent['bw_down'];
984
	else if (!empty($config['captiveportal'][$cpzone]['bwdefaultdn']))
985
		$bwDown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
986

    
987
	$ruleno = captiveportal_get_next_ipfw_ruleno();
988

    
989
	if ($macent['action'] == 'pass') {
990
		$rules = "";
991
		$pipeno = captiveportal_get_next_dn_ruleno();
992

    
993
		$pipeup = $pipeno;
994
		if ($pipeinrule == true)
995
			$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16");
996
		else
997
			$rules .= "pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16\n";
998
			
999
		$pipedown = $pipeno + 1;
1000
		if ($pipeinrule == true)
1001
			$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
1002
		else
1003
			$rules .= "pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16\n";
1004

    
1005
		$rules .= "add {$ruleno} pipe {$pipeup} ip from any to any MAC any {$macent['mac']}\n";
1006
		$ruleno++;
1007
		$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC {$macent['mac']} any\n";
1008
	}
1009

    
1010
	return $rules;
1011
}
1012

    
1013
function captiveportal_passthrumac_delete_entry($macent) {
1014
	$rules = "";
1015

    
1016
	if ($macent['action'] == 'pass') {
1017
		$ruleno = captiveportal_get_ipfw_passthru_ruleno($macent['mac']);
1018

    
1019
		if (!$ruleno)
1020
			return $rules;
1021

    
1022
		captiveportal_free_ipfw_ruleno($ruleno);
1023

    
1024
		$rules .= "delete {$ruleno}\n";
1025
		$rules .= "delete " . ++$ruleno . "\n";
1026

    
1027
		$pipeno = captiveportal_get_dn_passthru_ruleno($macent['mac']);
1028

    
1029
		if (!empty($pipeno)) {
1030
			captiveportal_free_dn_ruleno($pipeno);
1031
			$rules .= "pipe delete " . $pipeno . "\n";
1032
			$rules .= "pipe delete " . ++$pipeno . "\n";
1033
		}
1034
	}
1035

    
1036
	return $rules;
1037
}
1038

    
1039
function captiveportal_passthrumac_configure($lock = false) {
1040
	global $config, $g, $cpzone;
1041

    
1042
	$rules = "";
1043

    
1044
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1045
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1046
			$rules .= captiveportal_passthrumac_configure_entry($macent, true);
1047
		}
1048
	}
1049

    
1050
	return $rules;
1051
}
1052

    
1053
function captiveportal_passthrumac_findbyname($username) {
1054
	global $config, $cpzone;
1055

    
1056
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1057
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1058
			if ($macent['username'] == $username)
1059
				return $macent;
1060
		}
1061
	}
1062
	return NULL;
1063
}
1064

    
1065
/* 
1066
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1067
 */
1068
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
1069
	global $g;
1070

    
1071
	/*  Instead of copying this entire function for something
1072
	 *  easy such as hostname vs ip address add this check
1073
	 */
1074
	if ($ishostname === true) {
1075
		if (!platform_booting()) {
1076
			$ipaddress = gethostbyname($ipent['hostname']);
1077
			if (!is_ipaddr($ipaddress)) 
1078
				return;
1079
		} else
1080
			$ipaddress = "";
1081
	} else
1082
		$ipaddress = $ipent['ip'];
1083

    
1084
	$rules = "";
1085
	$cp_filterdns_conf = "";
1086
	$enBwup = 0;
1087
	if (!empty($ipent['bw_up']))
1088
		$enBwup = intval($ipent['bw_up']);
1089
	else if (!empty($config['captiveportal'][$cpzone]['bwdefaultup']))
1090
		$enBwup = $config['captiveportal'][$cpzone]['bwdefaultup'];
1091
	$enBwdown = 0;
1092
	if (!empty($ipent['bw_down']))
1093
		$enBwdown = intval($ipent['bw_down']);
1094
	else if (!empty($config['captiveportal'][$cpzone]['bwdefaultdn']))
1095
		$enBwdown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
1096

    
1097
	$pipeno = captiveportal_get_next_dn_ruleno();
1098
	$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$enBwup}Kbit/s queue 100 buckets 16");
1099
	$pipedown = $pipeno + 1;
1100
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16");
1101
	if ($ishostname === true) {
1102
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
1103
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
1104
		if (!is_ipaddr($ipaddress))
1105
			return array("", $cp_filterdns_conf);
1106
	}
1107
	$subnet = "";
1108
	if (!empty($ipent['sn']))
1109
		$subnet = "/{$ipent['sn']}";
1110
	$rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
1111
	$rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
1112

    
1113
	if ($ishostname === true)
1114
		return array($rules, $cp_filterdns_conf);
1115
	else
1116
		return $rules;
1117
}
1118

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

    
1122
	$rules = "";
1123
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1124
		$rules = "\n# captiveportal_allowedhostname_configure()\n";
1125
		$cp_filterdns_conf = "";
1126
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) {
1127
			$tmprules = captiveportal_allowedip_configure_entry($hostnameent, true);
1128
			$rules .= $tmprules[0];
1129
			$cp_filterdns_conf .= $tmprules[1];
1130
		}
1131
		$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1132
		@file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1133
		unset($cp_filterdns_conf);
1134
		if (isvalidpid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid"))
1135
			sigkillbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid", "HUP");
1136
		else
1137
			mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzone} -d 1");
1138
	} else {
1139
		killbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1140
		@unlink("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1141
	}
1142

    
1143
	return $rules;
1144
}
1145

    
1146
function captiveportal_allowedip_configure() {
1147
	global $config, $g, $cpzone;
1148

    
1149
	$rules = "";
1150
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1151
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1152
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1153
	}
1154

    
1155
	return $rules;
1156
}
1157

    
1158
/* get last activity timestamp given client IP address */
1159
function captiveportal_get_last_activity($ip, $mac = NULL, $table = 1) {
1160
	global $cpzoneid;
1161

    
1162
	$ipfwoutput = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, $table, $ip, $mac);
1163
	/* Reading only from one of the tables is enough of approximation. */
1164
	if (is_array($ipfwoutput)) {
1165
		return $ipfwoutput['timestamp'];
1166
	}
1167

    
1168
	return 0;
1169
}
1170

    
1171
function captiveportal_init_radius_servers() {
1172
	global $config, $g, $cpzone;
1173

    
1174
	/* generate radius server database */
1175
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1176
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1177
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1178
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1179
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1180
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1181

    
1182
		if ($config['captiveportal'][$cpzone]['radiusport'])
1183
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1184
		else
1185
			$radiusport = 1812;
1186
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1187
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1188
		else
1189
			$radiusacctport = 1813;
1190
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1191
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1192
		else
1193
			$radiusport2 = 1812;
1194
		if ($config['captiveportal'][$cpzone]['radiusport3'])
1195
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1196
		else
1197
			$radiusport3 = 1812;
1198
		if ($config['captiveportal'][$cpzone]['radiusport4'])
1199
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1200
		else
1201
			$radiusport4 = 1812;
1202

    
1203
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1204
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1205
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1206
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1207

    
1208
		$cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX);
1209
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
1210
		if (!$fd) {
1211
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1212
			unlock($cprdsrvlck);
1213
			return 1;
1214
		}
1215
		if (isset($radiusip))
1216
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
1217
		if (isset($radiusip2))
1218
			fwrite($fd,"\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
1219
		if (isset($radiusip3))
1220
			fwrite($fd,"\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
1221
		if (isset($radiusip4))
1222
			fwrite($fd,"\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
1223
		
1224

    
1225
		fclose($fd);
1226
		unlock($cprdsrvlck);
1227
	}
1228
}
1229

    
1230
/* read RADIUS servers into array */
1231
function captiveportal_get_radius_servers() {
1232
	global $g, $cpzone;
1233

    
1234
	$cprdsrvlck = lock("captiveportalradius{$cpzone}");
1235
	if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
1236
		$radiusservers = array();
1237
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", 
1238
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1239
		if ($cpradiusdb) {
1240
			foreach($cpradiusdb as $cpradiusentry) {
1241
				$line = trim($cpradiusentry);
1242
				if ($line) {
1243
					$radsrv = array();
1244
						list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key'], $context) = explode(",",$line);
1245
				}
1246
				if (empty($context)) {
1247
					if (!is_array($radiusservers['first']))
1248
						$radiusservers['first'] = array();
1249
					$radiusservers['first'] = $radsrv;
1250
				} else {
1251
					if (!is_array($radiusservers[$context]))
1252
						$radiusservers[$context] = array();
1253
					$radiusservers[$context][] = $radsrv;
1254
				}
1255
			}
1256
		}
1257
		unlock($cprdsrvlck);
1258
		return $radiusservers;
1259
	}
1260

    
1261
	unlock($cprdsrvlck);
1262
	return false;
1263
}
1264

    
1265
/* log successful captive portal authentication to syslog */
1266
/* part of this code from php.net */
1267
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1268
	// Log it
1269
	if (!$message)
1270
		$message = "{$status}: {$user}, {$mac}, {$ip}";
1271
	else {
1272
		$message = trim($message);
1273
		$message = "{$status}: {$user}, {$mac}, {$ip}, {$message}";
1274
	}
1275
	captiveportal_syslog($message);
1276
}
1277

    
1278
/* log simple messages to syslog */
1279
function captiveportal_syslog($message) {
1280
	global $cpzone;
1281

    
1282
	$message = trim($message);
1283
	$message = "Zone: {$cpzone} - {$message}";
1284
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1285
	// Log it
1286
	syslog(LOG_INFO, $message);
1287
	closelog();
1288
}
1289

    
1290
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1291
	global $g, $config, $cpzoneid;
1292

    
1293
	$pipeno = captiveportal_get_next_dn_ruleno();
1294

    
1295
	/* If the pool is empty, return appropriate message and fail authentication */
1296
	if (empty($pipeno)) {
1297
		$auth_list = array();
1298
		$auth_list['auth_val'] = 1;
1299
		$auth_list['error'] = "System reached maximum login capacity";
1300
		return $auth_list;
1301
	}
1302

    
1303
	$radiusservers = captiveportal_get_radius_servers();
1304

    
1305
	if (is_null($radiusctx))
1306
		$radiusctx = 'first';
1307

    
1308
	$auth_list = RADIUS_AUTHENTICATION($username,
1309
		$password,
1310
		$radiusservers[$radiusctx],
1311
		$clientip,
1312
		$clientmac,
1313
		$pipeno);
1314

    
1315
	if ($auth_list['auth_val'] == 2) {
1316
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1317
		$sessionid = portal_allow($clientip,
1318
			$clientmac,
1319
			$username,
1320
			$password,
1321
			$auth_list,
1322
			$pipeno,
1323
			$radiusctx);
1324
	} else {
1325
	         captiveportal_free_dn_ruleno($pipeno);
1326
	       }
1327

    
1328
	return $auth_list;
1329
}
1330

    
1331
function captiveportal_opendb() {
1332
	global $g, $cpzone;
1333

    
1334
	$DB = new SQLite3("{$g['vardb_path']}/captiveportal{$cpzone}.db");
1335
	if (! $DB->exec("CREATE TABLE IF NOT EXISTS captiveportal (" .
1336
				"allow_time INTEGER, pipeno INTEGER, ip TEXT, mac TEXT, username TEXT, " .
1337
				"sessionid TEXT, bpassword TEXT, session_timeout INTEGER, idle_timeout INTEGER, " .
1338
				"session_terminate_time INTEGER, interim_interval INTEGER, radiusctx TEXT); " .
1339
			"CREATE UNIQUE INDEX IF NOT EXISTS idx_active ON captiveportal (sessionid, username); " .
1340
			"CREATE INDEX IF NOT EXISTS user ON captiveportal (username); " .
1341
			"CREATE INDEX IF NOT EXISTS ip ON captiveportal (ip); " .
1342
			"CREATE INDEX IF NOT EXISTS starttime ON captiveportal (allow_time)"))
1343
		captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$DB->lastErrorMsg()}");
1344

    
1345
	return $DB;
1346
}
1347

    
1348
/* read captive portal DB into array */
1349
function captiveportal_read_db($query = "") {
1350
	$cpdb = array();
1351

    
1352
	$DB = captiveportal_opendb();
1353
	if ($DB) {
1354
		$response = $DB->query("SELECT * FROM captiveportal {$query}");
1355
		if ($response != FALSE) {
1356
			while ($row = $response->fetchArray())
1357
				$cpdb[] = $row;
1358
		}
1359
		$DB->close();
1360
	}
1361

    
1362
	return $cpdb;
1363
}
1364

    
1365
function captiveportal_remove_entries($remove) {
1366

    
1367
	if (!is_array($remove) || empty($remove))
1368
		return;
1369

    
1370
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1371
	foreach($remove as $idx => $unindex) {
1372
		$query .= "'{$unindex}'";
1373
		if ($idx < (count($remove) - 1))
1374
			$query .= ",";
1375
	}
1376
	$query .= ")";
1377
	captiveportal_write_db($query);
1378
}
1379

    
1380
/* write captive portal DB */
1381
function captiveportal_write_db($queries) {
1382
	global $g;
1383

    
1384
	if (is_array($queries))
1385
		$query = implode(";", $queries);
1386
	else
1387
		$query = $queries;
1388

    
1389
	$DB = captiveportal_opendb();
1390
	if ($DB) {
1391
		$DB->exec("BEGIN TRANSACTION");
1392
		$result = $DB->exec($query);
1393
		if (!$result)
1394
			captiveportal_syslog("Trying to modify DB returned error: {$DB->lastErrorMsg()}");
1395
		else
1396
			$DB->exec("END TRANSACTION");
1397
		$DB->close();
1398
		return $result;
1399
	} else
1400
		return true;
1401
}
1402

    
1403
function captiveportal_write_elements() {
1404
	global $g, $config, $cpzone;
1405
	
1406
	$cpcfg = $config['captiveportal'][$cpzone];
1407

    
1408
	if (!is_dir($g['captiveportal_element_path']))
1409
		@mkdir($g['captiveportal_element_path']);
1410

    
1411
	if (is_array($cpcfg['element'])) {
1412
		conf_mount_rw();
1413
		foreach ($cpcfg['element'] as $data) {
1414
			if (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1415
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1416
				return 1;
1417
			}
1418
			if (!file_exists("{$g['captiveportal_path']}/{$data['name']}"))
1419
				@symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1420
		}
1421
		conf_mount_ro();
1422
	}
1423
	
1424
	return 0;
1425
}
1426

    
1427
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1428
	global $cpzone;
1429

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

    
1449
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1450
	global $config, $g, $cpzone;
1451

    
1452
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1453
	$ruleno = 0;
1454
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1455
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1456
		$ridx = $rulenos_start;
1457
		while ($ridx < $rulenos_range_max) {
1458
			if (empty($rules[$ridx])) {
1459
				$ruleno = $ridx;
1460
				$rules[$ridx] = $cpzone;
1461
				$ridx++;
1462
				$rules[$ridx] = $cpzone;
1463
				break;
1464
			} else {
1465
				$ridx += 2;
1466
			}
1467
		}
1468
	} else {
1469
		$rules = array_pad(array(), $rulenos_range_max, false);
1470
		$ruleno = $rulenos_start;
1471
		$rules[$rulenos_start] = $cpzone;
1472
		$rulenos_start++;
1473
		$rules[$rulenos_start] = $cpzone;
1474
	}
1475
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1476
	unlock($cpruleslck);
1477
	unset($rules);
1478

    
1479
	return $ruleno;
1480
}
1481

    
1482
function captiveportal_free_dn_ruleno($ruleno) {
1483
	global $config, $g;
1484

    
1485
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1486
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1487
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1488
		$rules[$ruleno] = false;
1489
		$ruleno++;
1490
		$rules[$ruleno] = false;
1491
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1492
		unset($rules);
1493
	}
1494
	unlock($cpruleslck);
1495
}
1496

    
1497
function captiveportal_get_dn_passthru_ruleno($value) {
1498
	global $config, $g, $cpzone, $cpzoneid;
1499

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

    
1504
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1505
	$ruleno = NULL;
1506
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1507
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1508
		unset($output);
1509
		$_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);
1510
		$ruleno = intval($output[0]);
1511
		if (!$rules[$ruleno])
1512
			$ruleno = NULL;
1513
		unset($rules);
1514
	}
1515
	unlock($cpruleslck);
1516

    
1517
	return $ruleno;
1518
}
1519

    
1520
/*
1521
 * This function will calculate the lowest free firewall ruleno
1522
 * within the range specified based on the actual logged on users
1523
 *
1524
 */
1525
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1526
	global $config, $g, $cpzone;
1527

    
1528
	$cpcfg = $config['captiveportal'][$cpzone];
1529
	if(!isset($cpcfg['enable']))
1530
		return NULL;
1531

    
1532
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1533
	$ruleno = 0;
1534
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1535
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1536
		$ridx = $rulenos_start;
1537
		while ($ridx < $rulenos_range_max) {
1538
			if (empty($rules[$ridx])) {
1539
				$ruleno = $ridx;
1540
				$rules[$ridx] = $cpzone;
1541
				$ridx++;
1542
				$rules[$ridx] = $cpzone;
1543
				break;
1544
			} else {
1545
				/* 
1546
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1547
				 * and the out pipe ruleno + 1.
1548
				 */
1549
				$ridx += 2;
1550
			}
1551
		}
1552
	} else {
1553
		$rules = array_pad(array(), $rulenos_range_max, false);
1554
		$ruleno = $rulenos_start;
1555
		$rules[$rulenos_start] = $cpzone;
1556
		$rulenos_start++;
1557
		$rules[$rulenos_start] = $cpzone;
1558
	}
1559
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1560
	unlock($cpruleslck);
1561
	unset($rules);
1562

    
1563
	return $ruleno;
1564
}
1565

    
1566
function captiveportal_free_ipfw_ruleno($ruleno) {
1567
	global $config, $g, $cpzone;
1568

    
1569
	$cpcfg = $config['captiveportal'][$cpzone];
1570
	if(!isset($cpcfg['enable']))
1571
		return NULL;
1572

    
1573
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1574
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1575
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1576
		$rules[$ruleno] = false;
1577
		$ruleno++;
1578
		$rules[$ruleno] = false;
1579
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1580
		unset($rules);
1581
	}
1582
	unlock($cpruleslck);
1583
}
1584

    
1585
function captiveportal_get_ipfw_passthru_ruleno($value) {
1586
	global $config, $g, $cpzone, $cpzoneid;
1587

    
1588
	$cpcfg = $config['captiveportal'][$cpzone];
1589
	if(!isset($cpcfg['enable']))
1590
		return NULL;
1591

    
1592
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1593
	$ruleno = NULL;
1594
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1595
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1596
		unset($output);
1597
		$_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);
1598
		$ruleno = intval($output[0]);
1599
		if (!$rules[$ruleno])
1600
			$ruleno = NULL;
1601
		unset($rules);
1602
	}
1603
	unlock($cpruleslck);
1604

    
1605
	return $ruleno;
1606
}
1607

    
1608
/**
1609
 * This function will calculate the traffic produced by a client
1610
 * based on its firewall rule
1611
 *
1612
 * Point of view: NAS
1613
 *
1614
 * Input means: from the client
1615
 * Output means: to the client
1616
 *
1617
 */
1618

    
1619
function getVolume($ip, $mac = NULL) {
1620
	global $config, $cpzone, $cpzoneid;
1621

    
1622
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1623
	$volume = array();
1624
	// Initialize vars properly, since we don't want NULL vars
1625
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1626

    
1627
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 1, $ip, $mac);
1628
	if (is_array($ipfw)) {
1629
		if ($reverse) {
1630
			$volume['output_pkts'] = $ipfw['packets'];
1631
			$volume['output_bytes'] = $ipfw['bytes'];
1632
		}
1633
		else {
1634
			$volume['input_pkts'] = $ipfw['packets'];
1635
			$volume['input_bytes'] = $ipfw['bytes'];
1636
		}
1637
	}
1638

    
1639
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 2, $ip, $mac);
1640
	if (is_array($ipfw)) {
1641
		if ($reverse) {
1642
			$volume['input_pkts'] = $ipfw['packets'];
1643
			$volume['input_bytes'] = $ipfw['bytes'];
1644
		}
1645
		else {
1646
			$volume['output_pkts'] = $ipfw['packets'];
1647
			$volume['output_bytes'] = $ipfw['bytes'];
1648
		}
1649
	}
1650

    
1651
	return $volume;
1652
}
1653

    
1654
/**
1655
 * Get the NAS-IP-Address based on the current wan address
1656
 *
1657
 * Use functions in interfaces.inc to find this out
1658
 *
1659
 */
1660

    
1661
function getNasIP()
1662
{
1663
	global $config, $cpzone;
1664

    
1665
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1666
			$nasIp = get_interface_ip();
1667
	} else {
1668
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1669
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1670
		else
1671
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1672
	}
1673
		
1674
	if(!is_ipaddr($nasIp))
1675
		$nasIp = "0.0.0.0";
1676

    
1677
	return $nasIp;
1678
}
1679

    
1680
function portal_ip_from_client_ip($cliip) {
1681
	global $config, $cpzone;
1682

    
1683
	$isipv6 = is_ipaddrv6($cliip);
1684
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1685
	foreach ($interfaces as $cpif) {
1686
		if ($isipv6) {
1687
			$ip = get_interface_ipv6($cpif);
1688
			$sn = get_interface_subnetv6($cpif);
1689
		} else {
1690
			$ip = get_interface_ip($cpif);
1691
			$sn = get_interface_subnet($cpif);
1692
		}
1693
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1694
			return $ip;
1695
	}
1696

    
1697
	$inet = ($isipv6) ? '-inet6' : '-inet';
1698
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1699
	$iface = trim($iface, "\n");
1700
	if (!empty($iface)) {
1701
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1702
		if (is_ipaddr($ip))
1703
			return $ip;
1704
	}
1705

    
1706
	// doesn't match up to any particular interface
1707
	// so let's set the portal IP to what PHP says 
1708
	// the server IP issuing the request is. 
1709
	// allows same behavior as 1.2.x where IP isn't 
1710
	// in the subnet of any CP interface (static routes, etc.)
1711
	// rather than forcing to DNS hostname resolution
1712
	$ip = $_SERVER['SERVER_ADDR'];
1713
	if (is_ipaddr($ip))
1714
		return $ip;
1715

    
1716
	return false;
1717
}
1718

    
1719
function portal_hostname_from_client_ip($cliip) {
1720
	global $config, $cpzone;
1721

    
1722
	$cpcfg = $config['captiveportal'][$cpzone];
1723

    
1724
	if (isset($cpcfg['httpslogin'])) {
1725
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 8001);
1726
		$ourhostname = $cpcfg['httpsname'];
1727
		
1728
		if ($listenporthttps != 443)
1729
			$ourhostname .= ":" . $listenporthttps;
1730
	} else {
1731
		$listenporthttp  = $cpcfg['listenporthttp']  ? $cpcfg['listenporthttp']  : ($cpcfg['zoneid'] + 8000);
1732
		$ifip = portal_ip_from_client_ip($cliip);
1733
		if (!$ifip)
1734
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1735
		else
1736
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1737
		
1738
		if ($listenporthttp != 80)
1739
			$ourhostname .= ":" . $listenporthttp;
1740
	}
1741
	
1742
	return $ourhostname;
1743
}
1744

    
1745
/* functions move from index.php */
1746

    
1747
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1748
	global $g, $config, $cpzone;
1749

    
1750
	/* Get captive portal layout */
1751
	if ($type == "redir") {
1752
		header("Location: {$redirurl}");
1753
		return;
1754
	} else if ($type == "login")
1755
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1756
	else
1757
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1758

    
1759
	$cpcfg = $config['captiveportal'][$cpzone];
1760

    
1761
	/* substitute the PORTAL_REDIRURL variable */
1762
	if ($cpcfg['preauthurl']) {
1763
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1764
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1765
	}
1766

    
1767
	/* substitute other variables */
1768
	$ourhostname = portal_hostname_from_client_ip($clientip);
1769
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1770
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1771
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1772

    
1773
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1774
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1775
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1776
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1777
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1778

    
1779
	// Special handling case for captive portal master page so that it can be ran 
1780
	// through the PHP interpreter using the include method above.  We convert the
1781
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1782
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1783
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1784
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1785
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1786
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1787
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1788
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1789

    
1790
	echo $htmltext;
1791
}
1792

    
1793
function portal_mac_radius($clientmac,$clientip) {
1794
	global $config, $cpzone;
1795

    
1796
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1797

    
1798
	/* authentication against the radius server */
1799
	$username = mac_format($clientmac);
1800
	$auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1801
	if ($auth_list['auth_val'] == 2)
1802
		return TRUE;
1803

    
1804
	if (!empty($auth_list['url_redirection']))
1805
		portal_reply_page($auth_list['url_redirection'], "redir");
1806

    
1807
	return FALSE;
1808
}
1809

    
1810
function captiveportal_reapply_attributes($cpentry, $attributes) {
1811
	global $config, $cpzone, $g;
1812

    
1813
	if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
1814
		$dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1815
		$dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1816
	} else
1817
		$dwfaultbw_up = $dwfaultbw_down = 0;
1818
	$bw_up = !empty($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1819
	$bw_down = !empty($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1820
	$bw_up_pipeno = $cpentry[1];
1821
	$bw_down_pipeno = $cpentry[1]+1;
1822

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

    
1827
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1828
}
1829

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

    
1833
	// Ensure we create an array if we are missing attributes
1834
	if (!is_array($attributes))
1835
		$attributes = array();
1836

    
1837
	unset($sessionid);
1838

    
1839
	/* Do not allow concurrent login execution. */
1840
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1841

    
1842
	if ($attributes['voucher'])
1843
		$remaining_time = $attributes['session_timeout'];
1844

    
1845
	$writecfg = false;
1846
	/* Find an existing session */
1847
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
1848
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1849
			$mac = captiveportal_passthrumac_findbyname($username);
1850
			if (!empty($mac)) {
1851
				if ($_POST['replacemacpassthru']) {
1852
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
1853
						if ($macent['mac'] == $mac['mac']) {
1854
							$macrules = "";
1855
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1856
							$pipeno = captiveportal_get_dn_passthru_ruleno($mac['mac']);
1857
							if ($ruleno) {
1858
								captiveportal_free_ipfw_ruleno($ruleno);
1859
								$macrules .= "delete {$ruleno}\n";
1860
								++$ruleno;
1861
								$macrules .= "delete {$ruleno}\n";
1862
							}
1863
							if ($pipeno) {
1864
								captiveportal_free_dn_ruleno($pipeno);
1865
								$macrules .= "pipe delete {$pipeno}\n";
1866
								++$pipeno;
1867
								$macrules .= "pipe delete {$pipeno}\n";
1868
							}
1869
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1870
							$mac['action'] = 'pass';
1871
							$mac['mac'] = $clientmac;
1872
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1873
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1874
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1875
							mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1876
							$writecfg = true;
1877
							$sessionid = true;
1878
							break;
1879
						}
1880
					}
1881
				} else {
1882
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1883
						$clientmac, $clientip, $username, $password);
1884
					unlock($cpdblck);
1885
					return;
1886
				}
1887
			}
1888
		}
1889
	}
1890

    
1891
	/* read in client database */
1892
	$query = "WHERE ip = '{$clientip}'";
1893
	$tmpusername = strtolower($username);
1894
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins']))
1895
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
1896
	$cpdb = captiveportal_read_db($query);
1897

    
1898
	/* Snapshot the timestamp */
1899
	$allow_time = time();
1900
	$radiusservers = captiveportal_get_radius_servers();
1901
	$unsetindexes = array();
1902
	if (is_null($radiusctx))
1903
		$radiusctx = 'first';
1904

    
1905
	foreach ($cpdb as $cpentry) {
1906
		if (empty($cpentry[11])) {
1907
			$cpentry[11] = 'first';
1908
		}
1909
		/* on the same ip */
1910
		if ($cpentry[2] == $clientip) {
1911
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac)
1912
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1913
			else
1914
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
1915
			$sessionid = $cpentry[5];
1916
			break;
1917
		}
1918
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1919
			// user logged in with an active voucher. Check for how long and calculate 
1920
			// how much time we can give him (voucher credit - used time)
1921
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1922
			if ($remaining_time < 0)    // just in case. 
1923
				$remaining_time = 0;
1924

    
1925
			/* This user was already logged in so we disconnect the old one */
1926
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[11]],13);
1927
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1928
			$unsetindexes[] = $cpentry[5];
1929
			break;
1930
		}
1931
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1932
			/* on the same username */
1933
			if (strcasecmp($cpentry[4], $username) == 0) {
1934
				/* This user was already logged in so we disconnect the old one */
1935
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[11]],13);
1936
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1937
				$unsetindexes[] = $cpentry[5];
1938
				break;
1939
			}
1940
		}
1941
	}
1942
	unset($cpdb);
1943

    
1944
	if (!empty($unsetindexes))
1945
		captiveportal_remove_entries($unsetindexes);
1946

    
1947
	if ($attributes['voucher'] && $remaining_time <= 0)
1948
		return 0;       // voucher already used and no time left
1949

    
1950
	if (!isset($sessionid)) {
1951
		/* generate unique session ID */
1952
		$tod = gettimeofday();
1953
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1954

    
1955
		if ($passthrumac) {
1956
			$mac = array();
1957
			$mac['action'] = 'pass';
1958
			$mac['mac'] = $clientmac;
1959
			$mac['ip'] = $clientip; /* Used only for logging */
1960
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
1961
				$mac['username'] = $username;
1962
				if ($attributes['voucher'])
1963
					$mac['logintype'] = "voucher";
1964
			}
1965
			if ($username == "unauthenticated")
1966
				$mac['descr'] =  "Auto-added";
1967
			else
1968
				$mac['descr'] =  "Auto-added for user {$username}";
1969
			if (!empty($bw_up))
1970
				$mac['bw_up'] = $bw_up;
1971
			if (!empty($bw_down))
1972
				$mac['bw_down'] = $bw_down;
1973
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
1974
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
1975
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1976
			unlock($cpdblck);
1977
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1978
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1979
			mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1980
			$writecfg = true;
1981
		} else {
1982
			/* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
1983
			if (is_null($pipeno))
1984
				$pipeno = captiveportal_get_next_dn_ruleno();
1985

    
1986
			/* if the pool is empty, return appropriate message and exit */
1987
			if (is_null($pipeno)) {
1988
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1989
				log_error("Zone: {$cpzone} - WARNING!  Captive portal has reached maximum login capacity");
1990
				unlock($cpdblck);
1991
				return;
1992
			}
1993

    
1994
			if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
1995
				$dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1996
				$dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1997
			} else
1998
				$dwfaultbw_up = $dwfaultbw_down = 0;
1999
			$bw_up = !empty($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
2000
			$bw_down = !empty($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
2001

    
2002
			$bw_up_pipeno = $pipeno;
2003
			$bw_down_pipeno = $pipeno + 1;
2004
			//$bw_up /= 1000; // Scale to Kbit/s
2005
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
2006
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
2007

    
2008
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
2009
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
2010
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
2011
			else
2012
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
2013

    
2014
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
2015
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
2016
			else
2017
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
2018

    
2019
			if ($attributes['voucher'])
2020
				$attributes['session_timeout'] = $remaining_time;
2021
			
2022
			/* handle empty attributes */
2023
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
2024
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
2025
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
2026
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
2027

    
2028
			/* escape username */
2029
			$safe_username = SQLite3::escapeString($username);
2030

    
2031
			/* encode password in Base64 just in case it contains commas */
2032
			$bpassword = base64_encode($password);
2033
			$insertquery  = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, radiusctx) ";
2034
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
2035
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, '{$radiusctx}')";
2036

    
2037
			/* store information to database */
2038
			captiveportal_write_db($insertquery);
2039
			unlock($cpdblck);
2040
			unset($insertquery, $bpassword);
2041

    
2042
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
2043
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
2044
				if ($acct_val == 1)
2045
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
2046
			}
2047
		}
2048
	} else {
2049
		/* NOTE: #3062-11 If the pipeno has been allocated free it to not DoS the CP and maintain proper operation as in radius() case */
2050
		if (!is_null($pipeno))
2051
			captiveportal_free_dn_ruleno($pipeno);
2052

    
2053
		unlock($cpdblck);
2054
	}
2055

    
2056
	if ($writecfg == true)
2057
		write_config();
2058

    
2059
	/* redirect user to desired destination */
2060
	if (!empty($attributes['url_redirection']))
2061
		$my_redirurl = $attributes['url_redirection'];
2062
	else if (!empty($redirurl))
2063
		$my_redirurl = $redirurl;
2064
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
2065
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
2066

    
2067
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
2068
		$ourhostname = portal_hostname_from_client_ip($clientip);
2069
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
2070
		$logouturl = "{$protocol}{$ourhostname}/";
2071

    
2072
		if (isset($attributes['reply_message']))
2073
			$message = $attributes['reply_message'];
2074
		else
2075
			$message = 0;
2076

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

    
2079
	} else {
2080
		portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
2081
	}
2082

    
2083
	return $sessionid;
2084
}
2085

    
2086

    
2087
/*
2088
 * Used for when pass-through credits are enabled.
2089
 * Returns true when there was at least one free login to deduct for the MAC.
2090
 * Expired entries are removed as they are seen.
2091
 * Active entries are updated according to the configuration.
2092
 */
2093
function portal_consume_passthrough_credit($clientmac) {
2094
	global $config, $cpzone;
2095

    
2096
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
2097
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
2098
	else
2099
		return false;
2100

    
2101
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
2102
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2103
	else
2104
		return false;
2105

    
2106
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
2107
		return false;
2108

    
2109
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2110

    
2111
	/*
2112
	 * Read database of used MACs.  Lines are a comma-separated list
2113
	 * of the time, MAC, then the count of pass-through credits remaining.
2114
	 */
2115
	$usedmacs = captiveportal_read_usedmacs_db();
2116

    
2117
	$currenttime = time();
2118
	$found = false;
2119
	foreach ($usedmacs as $key => $usedmac) {
2120
		$usedmac = explode(",", $usedmac);
2121

    
2122
		if ($usedmac[1] == $clientmac) {
2123
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2124
				if ($usedmac[2] < 1) {
2125
					if ($updatetimeouts) {
2126
						$usedmac[0] = $currenttime;
2127
						unset($usedmacs[$key]);
2128
						$usedmacs[] = implode(",", $usedmac);
2129
						captiveportal_write_usedmacs_db($usedmacs);
2130
					}
2131

    
2132
					return false;
2133
				} else {
2134
					$usedmac[2] -= 1;
2135
					$usedmacs[$key] = implode(",", $usedmac);
2136
				}
2137

    
2138
				$found = true;
2139
			} else
2140
				unset($usedmacs[$key]);
2141

    
2142
			break;
2143
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2144
				unset($usedmacs[$key]);
2145
	}
2146

    
2147
	if (!$found) {
2148
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2149
		$usedmacs[] = implode(",", $usedmac);
2150
	}
2151

    
2152
	captiveportal_write_usedmacs_db($usedmacs);
2153
	return true;
2154
}
2155

    
2156
function captiveportal_read_usedmacs_db() {
2157
	global $g, $cpzone;
2158

    
2159
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2160
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2161
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2162
		if (!$usedmacs)
2163
			$usedmacs = array();
2164
	} else
2165
		$usedmacs = array();
2166

    
2167
	unlock($cpumaclck);
2168
	return $usedmacs;
2169
}
2170

    
2171
function captiveportal_write_usedmacs_db($usedmacs) {
2172
	global $g, $cpzone;
2173

    
2174
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2175
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2176
	unlock($cpumaclck);
2177
}
2178

    
2179
function captiveportal_blocked_mac($mac) {
2180
	global $config, $g, $cpzone;
2181

    
2182
	if (empty($mac) || !is_macaddr($mac))
2183
		return false;
2184

    
2185
	if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
2186
		return false;
2187

    
2188
	foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac)
2189
		if (($passthrumac['action'] == 'block') &&
2190
		    ($passthrumac['mac'] == strtolower($mac)))
2191
			return true;
2192

    
2193
	return false;
2194

    
2195
}
2196

    
2197
function captiveportal_send_server_accounting($off = false) {
2198
	global $cpzone, $config;
2199

    
2200
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
2201
		return;
2202
	}
2203
	if ($off) {
2204
		$racct = new Auth_RADIUS_Acct_Off;
2205
	} else {
2206
		$racct = new Auth_RADIUS_Acct_On;
2207
	}
2208
	$radiusservers = captiveportal_get_radius_servers();
2209
	if (empty($radiusservers)) {
2210
		return;
2211
	}
2212
	foreach ($radiusservers['first'] as $radsrv) {
2213
		// Add a new server to our instance
2214
		$racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
2215
	}
2216
	if (PEAR::isError($racct->start())) {
2217
		$retvalue['acct_val'] = 1;
2218
		$retvalue['error'] = $racct->getMessage();
2219

    
2220
		// If we encounter an error immediately stop this function and go back
2221
		$racct->close();
2222
		return $retvalue;
2223
	}
2224
	// Send request
2225
	$result = $racct->send();
2226
	// Evaluation of the response
2227
	// 5 -> Accounting-Response
2228
	// See RFC2866 for this.
2229
	if (PEAR::isError($result)) {
2230
		$retvalue['acct_val'] = 1;
2231
		$retvalue['error'] = $result->getMessage();
2232
	} else if ($result === true) {
2233
		$retvalue['acct_val'] = 5 ;
2234
	} else {
2235
		$retvalue['acct_val'] = 1 ;
2236
	}
2237

    
2238
	$racct->close();
2239
	return $retvalue;
2240
}
2241
?>
(7-7/67)