Project

General

Profile

Download (74.5 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
		/* Workaround for #46652 */
1166
		if ($ipfwoutput['packets'] > 0) { 
1167
			return $ipfwoutput['timestamp'];
1168
		} else {
1169
			return 0;
1170
		}
1171
	}
1172

    
1173
	return 0;
1174
}
1175

    
1176
function captiveportal_init_radius_servers() {
1177
	global $config, $g, $cpzone;
1178

    
1179
	/* generate radius server database */
1180
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1181
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1182
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1183
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1184
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1185
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1186

    
1187
		if ($config['captiveportal'][$cpzone]['radiusport'])
1188
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1189
		else
1190
			$radiusport = 1812;
1191
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1192
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1193
		else
1194
			$radiusacctport = 1813;
1195
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1196
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1197
		else
1198
			$radiusport2 = 1812;
1199
		if ($config['captiveportal'][$cpzone]['radiusport3'])
1200
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1201
		else
1202
			$radiusport3 = 1812;
1203
		if ($config['captiveportal'][$cpzone]['radiusport4'])
1204
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1205
		else
1206
			$radiusport4 = 1812;
1207

    
1208
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1209
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1210
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1211
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1212

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

    
1230
		fclose($fd);
1231
		unlock($cprdsrvlck);
1232
	}
1233
}
1234

    
1235
/* read RADIUS servers into array */
1236
function captiveportal_get_radius_servers() {
1237
	global $g, $cpzone;
1238

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

    
1266
	unlock($cprdsrvlck);
1267
	return false;
1268
}
1269

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

    
1283
/* log simple messages to syslog */
1284
function captiveportal_syslog($message) {
1285
	global $cpzone;
1286

    
1287
	$message = trim($message);
1288
	$message = "Zone: {$cpzone} - {$message}";
1289
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1290
	// Log it
1291
	syslog(LOG_INFO, $message);
1292
	closelog();
1293
}
1294

    
1295
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1296
	global $g, $config, $cpzoneid;
1297

    
1298
	$pipeno = captiveportal_get_next_dn_ruleno();
1299

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

    
1308
	$radiusservers = captiveportal_get_radius_servers();
1309

    
1310
	if (is_null($radiusctx))
1311
		$radiusctx = 'first';
1312

    
1313
	$auth_list = RADIUS_AUTHENTICATION($username,
1314
		$password,
1315
		$radiusservers[$radiusctx],
1316
		$clientip,
1317
		$clientmac,
1318
		$pipeno);
1319

    
1320
	if ($auth_list['auth_val'] == 2) {
1321
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1322
		$sessionid = portal_allow($clientip,
1323
			$clientmac,
1324
			$username,
1325
			$password,
1326
			$auth_list,
1327
			$pipeno,
1328
			$radiusctx);
1329
	} else {
1330
	         captiveportal_free_dn_ruleno($pipeno);
1331
	       }
1332

    
1333
	return $auth_list;
1334
}
1335

    
1336
function captiveportal_opendb() {
1337
	global $g, $cpzone;
1338

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

    
1350
	return $DB;
1351
}
1352

    
1353
/* read captive portal DB into array */
1354
function captiveportal_read_db($query = "") {
1355
	$cpdb = array();
1356

    
1357
	$DB = captiveportal_opendb();
1358
	if ($DB) {
1359
		$response = $DB->query("SELECT * FROM captiveportal {$query}");
1360
		if ($response != FALSE) {
1361
			while ($row = $response->fetchArray())
1362
				$cpdb[] = $row;
1363
		}
1364
		$DB->close();
1365
	}
1366

    
1367
	return $cpdb;
1368
}
1369

    
1370
function captiveportal_remove_entries($remove) {
1371

    
1372
	if (!is_array($remove) || empty($remove))
1373
		return;
1374

    
1375
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1376
	foreach($remove as $idx => $unindex) {
1377
		$query .= "'{$unindex}'";
1378
		if ($idx < (count($remove) - 1))
1379
			$query .= ",";
1380
	}
1381
	$query .= ")";
1382
	captiveportal_write_db($query);
1383
}
1384

    
1385
/* write captive portal DB */
1386
function captiveportal_write_db($queries) {
1387
	global $g;
1388

    
1389
	if (is_array($queries))
1390
		$query = implode(";", $queries);
1391
	else
1392
		$query = $queries;
1393

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

    
1408
function captiveportal_write_elements() {
1409
	global $g, $config, $cpzone;
1410
	
1411
	$cpcfg = $config['captiveportal'][$cpzone];
1412

    
1413
	if (!is_dir($g['captiveportal_element_path']))
1414
		@mkdir($g['captiveportal_element_path']);
1415

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

    
1432
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1433
	global $cpzone;
1434

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

    
1454
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1455
	global $config, $g, $cpzone;
1456

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

    
1484
	return $ruleno;
1485
}
1486

    
1487
function captiveportal_free_dn_ruleno($ruleno) {
1488
	global $config, $g;
1489

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

    
1502
function captiveportal_get_dn_passthru_ruleno($value) {
1503
	global $config, $g, $cpzone, $cpzoneid;
1504

    
1505
	$cpcfg = $config['captiveportal'][$cpzone];
1506
	if(!isset($cpcfg['enable']))
1507
		return NULL;
1508

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

    
1522
	return $ruleno;
1523
}
1524

    
1525
/*
1526
 * This function will calculate the lowest free firewall ruleno
1527
 * within the range specified based on the actual logged on users
1528
 *
1529
 */
1530
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1531
	global $config, $g, $cpzone;
1532

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

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

    
1568
	return $ruleno;
1569
}
1570

    
1571
function captiveportal_free_ipfw_ruleno($ruleno) {
1572
	global $config, $g, $cpzone;
1573

    
1574
	$cpcfg = $config['captiveportal'][$cpzone];
1575
	if(!isset($cpcfg['enable']))
1576
		return NULL;
1577

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

    
1590
function captiveportal_get_ipfw_passthru_ruleno($value) {
1591
	global $config, $g, $cpzone, $cpzoneid;
1592

    
1593
	$cpcfg = $config['captiveportal'][$cpzone];
1594
	if(!isset($cpcfg['enable']))
1595
		return NULL;
1596

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

    
1610
	return $ruleno;
1611
}
1612

    
1613
/**
1614
 * This function will calculate the traffic produced by a client
1615
 * based on its firewall rule
1616
 *
1617
 * Point of view: NAS
1618
 *
1619
 * Input means: from the client
1620
 * Output means: to the client
1621
 *
1622
 */
1623

    
1624
function getVolume($ip, $mac = NULL) {
1625
	global $config, $cpzone, $cpzoneid;
1626

    
1627
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1628
	$volume = array();
1629
	// Initialize vars properly, since we don't want NULL vars
1630
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1631

    
1632
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 1, $ip, $mac);
1633
	if (is_array($ipfw)) {
1634
		if ($reverse) {
1635
			$volume['output_pkts'] = $ipfw['packets'];
1636
			$volume['output_bytes'] = $ipfw['bytes'];
1637
		}
1638
		else {
1639
			$volume['input_pkts'] = $ipfw['packets'];
1640
			$volume['input_bytes'] = $ipfw['bytes'];
1641
		}
1642
	}
1643

    
1644
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 2, $ip, $mac);
1645
	if (is_array($ipfw)) {
1646
		if ($reverse) {
1647
			$volume['input_pkts'] = $ipfw['packets'];
1648
			$volume['input_bytes'] = $ipfw['bytes'];
1649
		}
1650
		else {
1651
			$volume['output_pkts'] = $ipfw['packets'];
1652
			$volume['output_bytes'] = $ipfw['bytes'];
1653
		}
1654
	}
1655

    
1656
	return $volume;
1657
}
1658

    
1659
/**
1660
 * Get the NAS-IP-Address based on the current wan address
1661
 *
1662
 * Use functions in interfaces.inc to find this out
1663
 *
1664
 */
1665

    
1666
function getNasIP()
1667
{
1668
	global $config, $cpzone;
1669

    
1670
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1671
			$nasIp = get_interface_ip();
1672
	} else {
1673
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1674
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1675
		else
1676
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1677
	}
1678
		
1679
	if(!is_ipaddr($nasIp))
1680
		$nasIp = "0.0.0.0";
1681

    
1682
	return $nasIp;
1683
}
1684

    
1685
function portal_ip_from_client_ip($cliip) {
1686
	global $config, $cpzone;
1687

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

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

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

    
1721
	return false;
1722
}
1723

    
1724
function portal_hostname_from_client_ip($cliip) {
1725
	global $config, $cpzone;
1726

    
1727
	$cpcfg = $config['captiveportal'][$cpzone];
1728

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

    
1750
/* functions move from index.php */
1751

    
1752
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1753
	global $g, $config, $cpzone;
1754

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

    
1764
	$cpcfg = $config['captiveportal'][$cpzone];
1765

    
1766
	/* substitute the PORTAL_REDIRURL variable */
1767
	if ($cpcfg['preauthurl']) {
1768
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1769
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1770
	}
1771

    
1772
	/* substitute other variables */
1773
	$ourhostname = portal_hostname_from_client_ip($clientip);
1774
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1775
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1776
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1777

    
1778
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1779
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1780
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1781
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1782
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1783

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

    
1795
	echo $htmltext;
1796
}
1797

    
1798
function portal_mac_radius($clientmac,$clientip) {
1799
	global $config, $cpzone;
1800

    
1801
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1802

    
1803
	/* authentication against the radius server */
1804
	$username = mac_format($clientmac);
1805
	$auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1806
	if ($auth_list['auth_val'] == 2)
1807
		return TRUE;
1808

    
1809
	if (!empty($auth_list['url_redirection']))
1810
		portal_reply_page($auth_list['url_redirection'], "redir");
1811

    
1812
	return FALSE;
1813
}
1814

    
1815
function captiveportal_reapply_attributes($cpentry, $attributes) {
1816
	global $config, $cpzone, $g;
1817

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

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

    
1832
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1833
}
1834

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

    
1838
	// Ensure we create an array if we are missing attributes
1839
	if (!is_array($attributes))
1840
		$attributes = array();
1841

    
1842
	unset($sessionid);
1843

    
1844
	/* Do not allow concurrent login execution. */
1845
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1846

    
1847
	if ($attributes['voucher'])
1848
		$remaining_time = $attributes['session_timeout'];
1849

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

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

    
1903
	/* Snapshot the timestamp */
1904
	$allow_time = time();
1905
	$radiusservers = captiveportal_get_radius_servers();
1906
	$unsetindexes = array();
1907
	if (is_null($radiusctx))
1908
		$radiusctx = 'first';
1909

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

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

    
1949
	if (!empty($unsetindexes))
1950
		captiveportal_remove_entries($unsetindexes);
1951

    
1952
	if ($attributes['voucher'] && $remaining_time <= 0)
1953
		return 0;       // voucher already used and no time left
1954

    
1955
	if (!isset($sessionid)) {
1956
		/* generate unique session ID */
1957
		$tod = gettimeofday();
1958
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1959

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

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

    
1999
			if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
2000
				$dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
2001
				$dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
2002
			} else
2003
				$dwfaultbw_up = $dwfaultbw_down = 0;
2004
			$bw_up = !empty($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
2005
			$bw_down = !empty($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
2006

    
2007
			$bw_up_pipeno = $pipeno;
2008
			$bw_down_pipeno = $pipeno + 1;
2009
			//$bw_up /= 1000; // Scale to Kbit/s
2010
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
2011
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
2012

    
2013
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
2014
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
2015
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
2016
			else
2017
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
2018

    
2019
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
2020
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
2021
			else
2022
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
2023

    
2024
			if ($attributes['voucher'])
2025
				$attributes['session_timeout'] = $remaining_time;
2026
			
2027
			/* handle empty attributes */
2028
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
2029
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
2030
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
2031
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
2032

    
2033
			/* escape username */
2034
			$safe_username = SQLite3::escapeString($username);
2035

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

    
2042
			/* store information to database */
2043
			captiveportal_write_db($insertquery);
2044
			unlock($cpdblck);
2045
			unset($insertquery, $bpassword);
2046

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

    
2058
		unlock($cpdblck);
2059
	}
2060

    
2061
	if ($writecfg == true)
2062
		write_config();
2063

    
2064
	/* redirect user to desired destination */
2065
	if (!empty($attributes['url_redirection']))
2066
		$my_redirurl = $attributes['url_redirection'];
2067
	else if (!empty($redirurl))
2068
		$my_redirurl = $redirurl;
2069
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
2070
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
2071

    
2072
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
2073
		$ourhostname = portal_hostname_from_client_ip($clientip);
2074
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
2075
		$logouturl = "{$protocol}{$ourhostname}/";
2076

    
2077
		if (isset($attributes['reply_message']))
2078
			$message = $attributes['reply_message'];
2079
		else
2080
			$message = 0;
2081

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

    
2084
	} else {
2085
		portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
2086
	}
2087

    
2088
	return $sessionid;
2089
}
2090

    
2091

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

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

    
2106
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
2107
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2108
	else
2109
		return false;
2110

    
2111
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
2112
		return false;
2113

    
2114
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2115

    
2116
	/*
2117
	 * Read database of used MACs.  Lines are a comma-separated list
2118
	 * of the time, MAC, then the count of pass-through credits remaining.
2119
	 */
2120
	$usedmacs = captiveportal_read_usedmacs_db();
2121

    
2122
	$currenttime = time();
2123
	$found = false;
2124
	foreach ($usedmacs as $key => $usedmac) {
2125
		$usedmac = explode(",", $usedmac);
2126

    
2127
		if ($usedmac[1] == $clientmac) {
2128
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2129
				if ($usedmac[2] < 1) {
2130
					if ($updatetimeouts) {
2131
						$usedmac[0] = $currenttime;
2132
						unset($usedmacs[$key]);
2133
						$usedmacs[] = implode(",", $usedmac);
2134
						captiveportal_write_usedmacs_db($usedmacs);
2135
					}
2136

    
2137
					return false;
2138
				} else {
2139
					$usedmac[2] -= 1;
2140
					$usedmacs[$key] = implode(",", $usedmac);
2141
				}
2142

    
2143
				$found = true;
2144
			} else
2145
				unset($usedmacs[$key]);
2146

    
2147
			break;
2148
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2149
				unset($usedmacs[$key]);
2150
	}
2151

    
2152
	if (!$found) {
2153
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2154
		$usedmacs[] = implode(",", $usedmac);
2155
	}
2156

    
2157
	captiveportal_write_usedmacs_db($usedmacs);
2158
	return true;
2159
}
2160

    
2161
function captiveportal_read_usedmacs_db() {
2162
	global $g, $cpzone;
2163

    
2164
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2165
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2166
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2167
		if (!$usedmacs)
2168
			$usedmacs = array();
2169
	} else
2170
		$usedmacs = array();
2171

    
2172
	unlock($cpumaclck);
2173
	return $usedmacs;
2174
}
2175

    
2176
function captiveportal_write_usedmacs_db($usedmacs) {
2177
	global $g, $cpzone;
2178

    
2179
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2180
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2181
	unlock($cpumaclck);
2182
}
2183

    
2184
function captiveportal_blocked_mac($mac) {
2185
	global $config, $g, $cpzone;
2186

    
2187
	if (empty($mac) || !is_macaddr($mac))
2188
		return false;
2189

    
2190
	if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
2191
		return false;
2192

    
2193
	foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac)
2194
		if (($passthrumac['action'] == 'block') &&
2195
		    ($passthrumac['mac'] == strtolower($mac)))
2196
			return true;
2197

    
2198
	return false;
2199

    
2200
}
2201

    
2202
function captiveportal_send_server_accounting($off = false) {
2203
	global $cpzone, $config;
2204

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

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

    
2243
	$racct->close();
2244
	return $retvalue;
2245
}
2246
?>
(7-7/67)