Project

General

Profile

Download (79.5 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	captiveportal.inc
4

    
5
	part of pfSense (https://www.pfsense.org)
6
	Copyright (c) 2004-2016 Electric Sheep Fencing, LLC. All rights reserved.
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
20
	   the documentation and/or other materials provided with the
21
	   distribution.
22

    
23
	3. All advertising materials mentioning features or use of this software
24
	   must display the following acknowledgment:
25
	   "This product includes software developed by the pfSense Project
26
	   for use in the pfSense® software distribution. (http://www.pfsense.org/).
27

    
28
	4. The names "pfSense" and "pfSense Project" must not be used to
29
	   endorse or promote products derived from this software without
30
	   prior written permission. For written permission, please contact
31
	   coreteam@pfsense.org.
32

    
33
	5. Products derived from this software may not be called "pfSense"
34
	   nor may "pfSense" appear in their names without prior written
35
	   permission of the Electric Sheep Fencing, LLC.
36

    
37
	6. Redistributions of any form whatsoever must retain the following
38
	   acknowledgment:
39

    
40
	"This product includes software developed by the pfSense Project
41
	for use in the pfSense software distribution (http://www.pfsense.org/).
42

    
43
	THIS SOFTWARE IS PROVIDED BY THE pfSense PROJECT ``AS IS'' AND ANY
44
	EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
45
	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
46
	PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE pfSense PROJECT OR
47
	ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
48
	SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
49
	NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
50
	LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
51
	HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
52
	STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
53
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
54
	OF THE POSSIBILITY OF SUCH DAMAGE.
55
*/
56

    
57
/* include all configuration functions */
58
require_once("config.inc");
59
require_once("functions.inc");
60
require_once("filter.inc");
61
require_once("radius.inc");
62
require_once("voucher.inc");
63

    
64
function get_default_captive_portal_html() {
65
	global $config, $g, $cpzone;
66

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

    
115
EOD;
116

    
117
	if (isset($config['voucher'][$cpzone]['enable'])) {
118
		$htmltext .= <<<EOD
119
									<tr>
120
										<td align="right">Enter Voucher Code: </td>
121
										<td><input name="auth_voucher" type="text" style="border:1px dashed;" size="22"></td>
122
									</tr>
123

    
124
EOD;
125
	}
126

    
127
	$htmltext .= <<<EOD
128
									<tr>
129
										<td colspan="2"><center><input name="accept" type="submit" value="Continue"></center></td>
130
									</tr>
131
								</table>
132
								</div>
133
							</center>
134
							</div>
135
						</td>
136
					</tr>
137
					</table>
138
					</center>
139
					</div>
140
					</center>
141
				</td>
142
			</tr>
143
			</table>
144
			</center>
145
			</div>
146
		</td>
147
	</tr>
148
	</table>
149
	</center>
150
</form>
151
</body>
152
</html>
153

    
154
EOD;
155

    
156
	return $htmltext;
157
}
158

    
159
function captiveportal_load_modules() {
160
	global $config;
161

    
162
	mute_kernel_msgs();
163
	if (!is_module_loaded("ipfw.ko")) {
164
		mwexec("/sbin/kldload ipfw");
165
		/* make sure ipfw is not on pfil hooks */
166
		set_sysctl(array(
167
			"net.inet.ip.pfil.inbound" => "pf", "net.inet6.ip6.pfil.inbound" => "pf",
168
			"net.inet.ip.pfil.outbound" => "pf", "net.inet6.ip6.pfil.outbound" => "pf")
169
		);
170
	}
171
	/* Activate layer2 filtering */
172
	set_sysctl(array("net.link.ether.ipfw" => "1", "net.inet.ip.fw.one_pass" => "1"));
173

    
174
	/* Always load dummynet now that even allowed ip and mac passthrough use it. */
175
	if (!is_module_loaded("dummynet.ko")) {
176
		mwexec("/sbin/kldload dummynet");
177
		set_sysctl(array("net.inet.ip.dummynet.io_fast" => "1", "net.inet.ip.dummynet.hash_size" => "256"));
178
	}
179
	unmute_kernel_msgs();
180
}
181

    
182
function captiveportal_configure() {
183
	global $config, $cpzone, $cpzoneid;
184

    
185
	if (is_array($config['captiveportal'])) {
186
		foreach ($config['captiveportal'] as $cpkey => $cp) {
187
			$cpzone = $cpkey;
188
			$cpzoneid = $cp['zoneid'];
189
			captiveportal_configure_zone($cp);
190
		}
191
	}
192
}
193

    
194
function captiveportal_configure_zone($cpcfg) {
195
	global $config, $g, $cpzone, $cpzoneid;
196

    
197
	$captiveportallck = lock("captiveportal{$cpzone}", LOCK_EX);
198

    
199
	if (isset($cpcfg['enable'])) {
200

    
201
		if (platform_booting()) {
202
			echo "Starting captive portal({$cpcfg['zone']})... ";
203

    
204
			/* remove old information */
205
			unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
206
		} else {
207
			captiveportal_syslog("Reconfiguring captive portal({$cpcfg['zone']}).");
208
		}
209

    
210
		/* init ipfw rules */
211
		captiveportal_init_rules(true);
212

    
213
		/* kill any running minicron */
214
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
215

    
216
		/* initialize minicron interval value */
217
		$croninterval = $cpcfg['croninterval'] ? $cpcfg['croninterval'] : 60;
218

    
219
		/* double check if the $croninterval is numeric and at least 10 seconds. If not we set it to 60 to avoid problems */
220
		if ((!is_numeric($croninterval)) || ($croninterval < 10)) {
221
			$croninterval = 60;
222
		}
223

    
224
		/* write portal page */
225
		if (is_array($cpcfg['page']) && $cpcfg['page']['htmltext']) {
226
			$htmltext = base64_decode($cpcfg['page']['htmltext']);
227
		} else {
228
			/* example/template page */
229
			$htmltext = get_default_captive_portal_html();
230
		}
231

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

    
251
		/* write error page */
252
		if (is_array($cpcfg['page']) && $cpcfg['page']['errtext']) {
253
			$errtext = base64_decode($cpcfg['page']['errtext']);
254
		} else {
255
			/* example page  */
256
			$errtext = get_default_captive_portal_html();
257
		}
258

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

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

    
309
document.location.href="<?=\$my_redirurl;?>";
310
//]]>
311
</script>
312
</body>
313
</html>
314

    
315
EOD;
316
		}
317

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

    
325
		/* write elements */
326
		captiveportal_write_elements();
327

    
328
		/* kill any running CP nginx instances */
329
		killbypid("{$g['varrun_path']}/nginx-{$cpzone}-CaptivePortal.pid");
330
		killbypid("{$g['varrun_path']}/nginx-{$cpzone}-CaptivePortal-SSL.pid");
331

    
332
		/* start up the webserving daemon */
333
		captiveportal_init_webgui_zone($cpcfg);
334

    
335
		/* Kill any existing prunecaptiveportal processes */
336
		if (file_exists("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid")) {
337
			killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
338
		}
339

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

    
344
		/* generate radius server database */
345
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
346
		captiveportal_init_radius_servers();
347

    
348
		if (platform_booting()) {
349
			/* send Accounting-On to server */
350
			captiveportal_send_server_accounting();
351
			echo "done\n";
352
		}
353

    
354
	} else {
355
		killbypid("{$g['varrun_path']}/nginx-{$cpzone}-CaptivePortal.pid");
356
		killbypid("{$g['varrun_path']}/nginx-{$cpzone}-CaptivePortal-SSL.pid");
357
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
358
		@unlink("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
359
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
360
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
361

    
362
		captiveportal_radius_stop_all();
363

    
364
		/* send Accounting-Off to server */
365
		if (!platform_booting()) {
366
			captiveportal_send_server_accounting(true);
367
		}
368

    
369
		/* remove old information */
370
		unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
371
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
372
		unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
373
		/* Release allocated pipes for this zone */
374
		captiveportal_free_dnrules();
375

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

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

    
397
	unlock($captiveportallck);
398

    
399
	return 0;
400
}
401

    
402
function captiveportal_init_webgui() {
403
	global $config, $cpzone;
404

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

    
413
function captiveportal_init_webgui_zonename($zone) {
414
	global $config, $cpzone;
415

    
416
	if (isset($config['captiveportal'][$zone])) {
417
		$cpzone = $zone;
418
		captiveportal_init_webgui_zone($config['captiveportal'][$zone]);
419
	}
420
}
421

    
422
function captiveportal_init_webgui_zone($cpcfg) {
423
	global $g, $config, $cpzone;
424

    
425
	if (!isset($cpcfg['enable'])) {
426
		return;
427
	}
428

    
429
	if (isset($cpcfg['httpslogin'])) {
430
		$cert = lookup_cert($cpcfg['certref']);
431
		$crt = base64_decode($cert['crt']);
432
		$key = base64_decode($cert['prv']);
433
		$ca = ca_chain($cert);
434

    
435
		/* generate nginx configuration */
436
		if (!empty($cpcfg['listenporthttps'])) {
437
			$listenporthttps = $cpcfg['listenporthttps'];
438
		} else {
439
			$listenporthttps = 8001 + $cpcfg['zoneid'];
440
		}
441
		system_generate_nginx_config("{$g['varetc_path']}/nginx-{$cpzone}-CaptivePortal-SSL.conf",
442
			$crt, $key, $ca, "nginx-{$cpzone}-CaptivePortal-SSL.pid", $listenporthttps, "/usr/local/captiveportal",
443
			"cert-{$cpzone}-portal.pem", "ca-{$cpzone}-portal.pem", $cpzone);
444
	}
445

    
446
	/* generate nginx configuration */
447
	if (!empty($cpcfg['listenporthttp'])) {
448
		$listenporthttp = $cpcfg['listenporthttp'];
449
	} else {
450
		$listenporthttp = 8000 + $cpcfg['zoneid'];
451
	}
452
	system_generate_nginx_config("{$g['varetc_path']}/nginx-{$cpzone}-CaptivePortal.conf",
453
		"", "", "", "nginx-{$cpzone}-CaptivePortal.pid", $listenporthttp, "/usr/local/captiveportal",
454
		"", "", $cpzone);
455

    
456
	@unlink("{$g['varrun']}/nginx-{$cpzone}-CaptivePortal.pid");
457
	/* attempt to start nginx */
458
	$res = mwexec("/usr/local/sbin/nginx -c {$g['varetc_path']}/nginx-{$cpzone}-CaptivePortal.conf");
459

    
460
	/* fire up https instance */
461
	if (isset($cpcfg['httpslogin'])) {
462
		@unlink("{$g['varrun']}/nginx-{$cpzone}-CaptivePortal-SSL.pid");
463
		$res = mwexec("/usr/local/sbin/nginx -c {$g['varetc_path']}/nginx-{$cpzone}-CaptivePortal-SSL.conf");
464
	}
465
}
466

    
467
function captiveportal_init_rules_byinterface($interface) {
468
	global $cpzone, $cpzoneid, $config;
469

    
470
	if (!is_array($config['captiveportal'])) {
471
		return;
472
	}
473

    
474
	foreach ($config['captiveportal'] as $cpkey => $cp) {
475
		$cpzone = $cpkey;
476
		$cpzoneid = $cp['zoneid'];
477
		$cpinterfaces = explode(",", $cp['interface']);
478
		if (in_array($interface, $cpinterfaces)) {
479
			captiveportal_init_rules();
480
			break;
481
		}
482
	}
483
}
484

    
485
/* reinit will disconnect all users, be careful! */
486
function captiveportal_init_rules($reinit = false) {
487
	global $config, $g, $cpzone, $cpzoneid;
488

    
489
	if (!isset($config['captiveportal'][$cpzone]['enable'])) {
490
		return;
491
	}
492

    
493
	captiveportal_load_modules();
494
	mwexec("/sbin/ipfw zone {$cpzoneid} create", true);
495

    
496
	/* Cleanup so nothing is leaked */
497
	captiveportal_free_dnrules();
498
	unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
499

    
500
	$cpips = array();
501
	$ifaces = get_configured_interface_list();
502
	$cpinterfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
503
	$firsttime = 0;
504
	foreach ($cpinterfaces as $cpifgrp) {
505
		if (!isset($ifaces[$cpifgrp])) {
506
			continue;
507
		}
508
		$tmpif = get_real_interface($cpifgrp);
509
		if (!empty($tmpif)) {
510
			$cpipm = get_interface_ip($cpifgrp);
511
			if (is_ipaddr($cpipm)) {
512
				$cpips[] = $cpipm;
513
				if (is_array($config['virtualip']['vip'])) {
514
					foreach ($config['virtualip']['vip'] as $vip) {
515
						if (($vip['interface'] == $cpifgrp) && (($vip['mode'] == "carp") || ($vip['mode'] == "ipalias"))) {
516
							$cpips[] = $vip['subnet'];
517
						}
518
					}
519
				}
520
			}
521
			mwexec("/sbin/ipfw zone {$cpzoneid} madd {$tmpif}", true);
522
		}
523
	}
524
	if (count($cpips) > 0) {
525
		$cpactive = true;
526
	} else {
527
		return false;
528
	}
529

    
530
	if ($reinit == false) {
531
		$captiveportallck = lock("captiveportal{$cpzone}");
532
	}
533

    
534
	$cprules = <<<EOD
535

    
536
flush
537
add 65291 allow pfsync from any to any
538
add 65292 allow carp from any to any
539

    
540
# layer 2: pass ARP
541
add 65301 pass layer2 mac-type arp,rarp
542
# pfsense requires for WPA
543
add 65302 pass layer2 mac-type 0x888e,0x88c7
544
# PPP Over Ethernet Session Stage/Discovery Stage
545
add 65303 pass layer2 mac-type 0x8863,0x8864
546

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

    
550
EOD;
551

    
552
	$rulenum = 65310;
553
	/* These tables contain host ips */
554
	$cprules .= "add {$rulenum} pass ip from any to table(100) in\n";
555
	$rulenum++;
556
	$cprules .= "add {$rulenum} pass ip from table(100) to any out\n";
557
	$rulenum++;
558
	foreach ($cpips as $cpip) {
559
		$cprules .= "table 100 add {$cpip}\n";
560
	}
561
	$cprules .= "add {$rulenum} pass ip from any to 255.255.255.255 in\n";
562
	$rulenum++;
563
	$cprules .= "add {$rulenum} pass ip from 255.255.255.255 to any out\n";
564
	$rulenum++;
565

    
566
	/* Allowed ips */
567
	$cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any in\n";
568
	$rulenum++;
569
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) in\n";
570
	$rulenum++;
571
	$cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any out\n";
572
	$rulenum++;
573
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) out\n";
574
	$rulenum++;
575

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

    
582
	if (!empty($config['captiveportal'][$cpzone]['listenporthttp'])) {
583
		$listenporthttp = $config['captiveportal'][$cpzone]['listenporthttp'];
584
	} else {
585
		$listenporthttp = 8000 + $cpzoneid;
586
	}
587

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

    
599
	$cprules .= <<<EOD
600

    
601
# redirect non-authenticated clients to captive portal
602
add 65532 fwd 127.0.0.1,{$listenporthttp} tcp from any to any dst-port 80 in
603
# let the responses from the captive portal web server back out
604
add 65533 pass tcp from any to any out
605
# block everything else
606
add 65534 deny all from any to any
607

    
608
EOD;
609

    
610
	/* generate passthru mac database */
611
	$cprules .= captiveportal_passthrumac_configure(true);
612
	$cprules .= "\n";
613

    
614
	/* allowed ipfw rules to make allowed ip work */
615
	$cprules .= captiveportal_allowedip_configure();
616

    
617
	/* allowed ipfw rules to make allowed hostnames work */
618
	$cprules .= captiveportal_allowedhostname_configure();
619

    
620
	/* load rules */
621
	file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
622
	mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
623
	//@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
624
	unset($cprules);
625

    
626
	if ($reinit == false) {
627
		unlock($captiveportallck);
628
	}
629
}
630

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

    
640
	if (empty($cpzone)) {
641
		return;
642
	}
643

    
644
	$cpcfg = $config['captiveportal'][$cpzone];
645
	$vcpcfg = $config['voucher'][$cpzone];
646

    
647
	/* check for expired entries */
648
	$idletimeout = 0;
649
	$timeout = 0;
650
	if (!empty($cpcfg['timeout']) && is_numeric($cpcfg['timeout'])) {
651
		$timeout = $cpcfg['timeout'] * 60;
652
	}
653

    
654
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout'])) {
655
		$idletimeout = $cpcfg['idletimeout'] * 60;
656
	}
657

    
658
	/* Is there any job to do? */
659
	if (!$timeout && !$idletimeout && !isset($cpcfg['reauthenticate']) &&
660
	    !isset($cpcfg['radiussession_timeout']) && !isset($vcpcfg['enable'])) {
661
		return;
662
	}
663

    
664
	$radiussrvs = captiveportal_get_radius_servers();
665

    
666
	/* Read database */
667
	/* NOTE: while this can be simplified in non radius case keep as is for now */
668
	$cpdb = captiveportal_read_db();
669

    
670
	$unsetindexes = array();
671
	$voucher_needs_sync = false;
672
	/*
673
	 * Snapshot the time here to use for calculation to speed up the process.
674
	 * If something is missed next run will catch it!
675
	 */
676
	$pruning_time = time();
677
	foreach ($cpdb as $cpentry) {
678
		$stop_time = $pruning_time;
679

    
680
		$timedout = false;
681
		$term_cause = 1;
682
		if (empty($cpentry[11])) {
683
			$cpentry[11] = 'first';
684
		}
685
		$radiusservers = $radiussrvs[$cpentry[11]];
686

    
687
		/* hard timeout? */
688
		if ($timeout) {
689
			if (($pruning_time - $cpentry[0]) >= $timeout) {
690
				$timedout = true;
691
				$term_cause = 5; // Session-Timeout
692
			}
693
		}
694

    
695
		/* Session-Terminate-Time */
696
		if (!$timedout && !empty($cpentry[9])) {
697
			if ($pruning_time >= $cpentry[9]) {
698
				$timedout = true;
699
				$term_cause = 5; // Session-Timeout
700
			}
701
		}
702

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

    
719
		/* if vouchers are configured, activate session timeouts */
720
		if (!$timedout && isset($vcpcfg['enable']) && !empty($cpentry[7])) {
721
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
722
				$timedout = true;
723
				$term_cause = 5; // Session-Timeout
724
				$voucher_needs_sync = true;
725
			}
726
		}
727

    
728
		/* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
729
		if (!$timedout && isset($cpcfg['radiussession_timeout']) && !empty($cpentry[7])) {
730
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
731
				$timedout = true;
732
				$term_cause = 5; // Session-Timeout
733
			}
734
		}
735

    
736
		if ($timedout) {
737
			captiveportal_disconnect($cpentry, $radiusservers, $term_cause, $stop_time);
738
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
739
			$unsetindexes[] = $cpentry[5];
740
		}
741

    
742
		/* do periodic RADIUS reauthentication? */
743
		if (!$timedout && !empty($radiusservers)) {
744
			if (isset($cpcfg['radacct_enable'])) {
745
				if (substr($cpcfg['reauthenticateacct'], 0, 9) == "stopstart") {
746
					/* stop and restart accounting */
747
					if ($cpcfg['reauthenticateacct'] == "stopstartfreeradius") {
748
						$rastart_time = 0;
749
						$rastop_time = 60;
750
					} else {
751
						$rastart_time = $cpentry[0];
752
						$rastop_time = null;
753
					}
754
					RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
755
						$cpentry[4], // username
756
						$cpentry[5], // sessionid
757
						$rastart_time, // start time
758
						$radiusservers,
759
						$cpentry[2], // clientip
760
						$cpentry[3], // clientmac
761
						10, // NAS Request
762
						false, // Not an interim request
763
						$rastop_time); // Stop Time
764
					$clientsn = (is_ipaddrv6($cpentry[2])) ? 128 : 32;
765
					$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XZEROENTRY, 1, $cpentry[2], $clientsn, $cpentry[3]);
766
					$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XZEROENTRY, 2, $cpentry[2], $clientsn, $cpentry[3]);
767
					if ($cpcfg['reauthenticateacct'] == "stopstartfreeradius") {
768
						/* Need to pause here or the FreeRADIUS server gets confused about packet ordering. */
769
						sleep(1);
770
					}
771
					RADIUS_ACCOUNTING_START($cpentry[1], // ruleno
772
						$cpentry[4], // username
773
						$cpentry[5], // sessionid
774
						$radiusservers,
775
						$cpentry[2], // clientip
776
						$cpentry[3]); // clientmac
777
				} else if ($cpcfg['reauthenticateacct'] == "interimupdate") {
778
					$session_time = $pruning_time - $cpentry[0];
779
					if (!empty($cpentry[10]) && $cpentry[10] > 60) {
780
						$interval = $cpentry[10];
781
					} else {
782
						$interval = 0;
783
					}
784
					$past_interval_min = ($session_time > $interval);
785
					if ($interval != 0) {
786
						$within_interval = ($session_time % $interval >= 0 && $session_time % $interval <= 59);
787
					}
788
					if ($interval === 0 || ($interval > 0 && $past_interval_min && $within_interval)) {
789
						RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
790
							$cpentry[4], // username
791
							$cpentry[5], // sessionid
792
							$cpentry[0], // start time
793
							$radiusservers,
794
							$cpentry[2], // clientip
795
							$cpentry[3], // clientmac
796
							10, // NAS Request
797
							true); // Interim Updates
798
					}
799
				}
800
			}
801

    
802
			/* check this user against RADIUS again */
803
			if (isset($cpcfg['reauthenticate'])) {
804
				$auth_list = RADIUS_AUTHENTICATION($cpentry[4], // username
805
					base64_decode($cpentry[6]), // password
806
					$radiusservers,
807
					$cpentry[2], // clientip
808
					$cpentry[3], // clientmac
809
					$cpentry[1]); // ruleno
810
				if ($auth_list['auth_val'] == 3) {
811
					captiveportal_disconnect($cpentry, $radiusservers, 17);
812
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
813
					$unsetindexes[] = $cpentry[5];
814
				} else if ($auth_list['auth_val'] == 2) {
815
					captiveportal_reapply_attributes($cpentry, $auth_list);
816
				}
817
			}
818
		}
819
	}
820
	unset($cpdb);
821

    
822
	captiveportal_prune_old_automac();
823

    
824
	if ($voucher_needs_sync == true) {
825
		/* Trigger a sync of the vouchers on config */
826
		send_event("service sync vouchers");
827
	}
828

    
829
	/* write database */
830
	if (!empty($unsetindexes)) {
831
		captiveportal_remove_entries($unsetindexes);
832
	}
833
}
834

    
835
function captiveportal_prune_old_automac() {
836
	global $g, $config, $cpzone, $cpzoneid;
837

    
838
	if (is_array($config['captiveportal'][$cpzone]['passthrumac']) && isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
839
		$tmpvoucherdb = array();
840
		$macrules = "";
841
		$writecfg = false;
842
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $eid => $emac) {
843
			if ($emac['logintype'] == "voucher") {
844
				if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) {
845
					if (isset($tmpvoucherdb[$emac['username']])) {
846
						$temac = $config['captiveportal'][$cpzone]['passthrumac'][$tmpvoucherdb[$emac['username']]];
847
						$ruleno = captiveportal_get_ipfw_passthru_ruleno($temac['mac']);
848
						$pipeno = captiveportal_get_dn_passthru_ruleno($temac['mac']);
849
						if ($ruleno) {
850
							captiveportal_free_ipfw_ruleno($ruleno);
851
							$macrules .= "delete {$ruleno}";
852
							++$ruleno;
853
							$macrules .= "delete {$ruleno}";
854
						}
855
						if ($pipeno) {
856
							captiveportal_free_dn_ruleno($pipeno);
857
							$macrules .= "pipe delete {$pipeno}\n";
858
							++$pipeno;
859
							$macrules .= "pipe delete {$pipeno}\n";
860
						}
861
						$writecfg = true;
862
						captiveportal_logportalauth($temac['username'], $temac['mac'], $temac['ip'], "DUPLICATE {$temac['username']} LOGIN - TERMINATING OLD SESSION");
863
						unset($config['captiveportal'][$cpzone]['passthrumac'][$tmpvoucherdb[$emac['username']]]);
864
					}
865
					$tmpvoucherdb[$emac['username']] = $eid;
866
				}
867
				if (voucher_auth($emac['username']) <= 0) {
868
					$ruleno = captiveportal_get_ipfw_passthru_ruleno($emac['mac']);
869
					$pipeno = captiveportal_get_dn_passthru_ruleno($emac['mac']);
870
					if ($ruleno) {
871
						captiveportal_free_ipfw_ruleno($ruleno);
872
						$macrules .= "delete {$ruleno}";
873
						++$ruleno;
874
						$macrules .= "delete {$ruleno}";
875
					}
876
					if ($pipeno) {
877
						captiveportal_free_dn_ruleno($pipeno);
878
						$macrules .= "pipe delete {$pipeno}\n";
879
						++$pipeno;
880
						$macrules .= "pipe delete {$pipeno}\n";
881
					}
882
					$writecfg = true;
883
					captiveportal_logportalauth($emac['username'], $emac['mac'], $emac['ip'], "EXPIRED {$emac['username']} LOGIN - TERMINATING SESSION");
884
					unset($config['captiveportal'][$cpzone]['passthrumac'][$eid]);
885
				}
886
			}
887
		}
888
		unset($tmpvoucherdb);
889
		if (!empty($macrules)) {
890
			@file_put_contents("{$g['tmp_path']}/macentry.prunerules.tmp", $macrules);
891
			unset($macrules);
892
			mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry.prunerules.tmp");
893
		}
894
		if ($writecfg === true) {
895
			write_config("Prune session for auto-added macs");
896
		}
897
	}
898
}
899

    
900
/* remove a single client according to the DB entry */
901
function captiveportal_disconnect($dbent, $radiusservers, $term_cause = 1, $stop_time = null) {
902
	global $g, $config, $cpzone, $cpzoneid;
903

    
904
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
905

    
906
	/* this client needs to be deleted - remove ipfw rules */
907
	if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers)) {
908
		if ($config['captiveportal'][$cpzone]['reauthenticateacct'] == "stopstartfreeradius") {
909
			/* Interim updates are on so the session time must be reported as the elapsed time since the previous interim update. */
910
			$session_time = ($stop_time - $dbent[0]) % 60;
911
			$start_time = $stop_time - $session_time;
912
		} else {
913
			$start_time = $dbent[0];
914
		}
915
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
916
			$dbent[4], // username
917
			$dbent[5], // sessionid
918
			$start_time, // start time
919
			$radiusservers,
920
			$dbent[2], // clientip
921
			$dbent[3], // clientmac
922
			$term_cause, // Acct-Terminate-Cause
923
			false,
924
			$stop_time);
925
	}
926

    
927
	if (is_ipaddr($dbent[2])) {
928
		/* Delete client's ip entry from tables 1 and 2. */
929
		$clientsn = (is_ipaddrv6($dbent[2])) ? 128 : 32;
930
		pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XDEL, 1, $dbent[2], $clientsn, $dbent[3]);
931
		pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XDEL, 2, $dbent[2], $clientsn, $dbent[3]);
932
		/* XXX: Redundant?! Ensure all pf(4) states are killed. */
933
		$_gb = @pfSense_kill_states($dbent[2]);
934
		$_gb = @pfSense_kill_srcstates($dbent[2]);
935
	}
936

    
937
	/*
938
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
939
	* We could get an error if the pipe doesn't exist but everything should still be fine
940
	*/
941
	if (!empty($dbent[1])) {
942
		$_gb = @pfSense_pipe_action("pipe delete {$dbent[1]}");
943
		$_gb = @pfSense_pipe_action("pipe delete " . ($dbent[1]+1));
944

    
945
		/* Release the ruleno so it can be reallocated to new clients. */
946
		captiveportal_free_dn_ruleno($dbent[1]);
947
	}
948

    
949
	// XMLRPC Call over to the master Voucher node
950
	if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
951
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
952
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
953
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
954
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
955
		$remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time);
956
	}
957

    
958
}
959

    
960
/* remove a single client by sessionid */
961
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
962
	global $g, $config;
963

    
964
	$sessionid = SQLite3::escapeString($sessionid);
965
	$radiusservers = captiveportal_get_radius_servers();
966

    
967
	/* read database */
968
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
969

    
970
	/* find entry */
971
	if (!empty($result)) {
972
		captiveportal_write_db("DELETE FROM captiveportal WHERE sessionid = '{$sessionid}'");
973

    
974
		foreach ($result as $cpentry) {
975
			if (empty($cpentry[11])) {
976
				$cpentry[11] = 'first';
977
			}
978
			captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], $term_cause);
979
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
980
		}
981
		unset($result);
982
	}
983
}
984

    
985
/* send RADIUS acct stop for all current clients */
986
function captiveportal_radius_stop_all() {
987
	global $config, $cpzone;
988

    
989
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
990
		return;
991
	}
992

    
993
	$radiusservers = captiveportal_get_radius_servers();
994
	if (!empty($radiusservers)) {
995
		$cpdb = captiveportal_read_db();
996
		foreach ($cpdb as $cpentry) {
997
			if (empty($cpentry[11])) {
998
				$cpentry[11] = 'first';
999
			}
1000
			if (!empty($radiusservers[$cpentry[11]])) {
1001
				RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
1002
					$cpentry[4], // username
1003
					$cpentry[5], // sessionid
1004
					$cpentry[0], // start time
1005
					$radiusservers[$cpentry[11]],
1006
					$cpentry[2], // clientip
1007
					$cpentry[3], // clientmac
1008
					7); // Admin Reboot
1009
			}
1010
		}
1011
	}
1012
}
1013

    
1014
function captiveportal_passthrumac_configure_entry($macent, $pipeinrule = false) {
1015
	global $config, $g, $cpzone;
1016

    
1017
	$bwUp = 0;
1018
	if (!empty($macent['bw_up'])) {
1019
		$bwUp = $macent['bw_up'];
1020
	} else if (!empty($config['captiveportal'][$cpzone]['bwdefaultup'])) {
1021
		$bwUp = $config['captiveportal'][$cpzone]['bwdefaultup'];
1022
	}
1023
	$bwDown = 0;
1024
	if (!empty($macent['bw_down'])) {
1025
		$bwDown = $macent['bw_down'];
1026
	} else if (!empty($config['captiveportal'][$cpzone]['bwdefaultdn'])) {
1027
		$bwDown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
1028
	}
1029

    
1030
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1031

    
1032
	if ($macent['action'] == 'pass') {
1033
		$rules = "";
1034
		$pipeno = captiveportal_get_next_dn_ruleno();
1035

    
1036
		$pipeup = $pipeno;
1037
		if ($pipeinrule == true) {
1038
			$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16");
1039
		} else {
1040
			$rules .= "pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16\n";
1041
		}
1042

    
1043
		$pipedown = $pipeno + 1;
1044
		if ($pipeinrule == true) {
1045
			$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
1046
		} else {
1047
			$rules .= "pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16\n";
1048
		}
1049

    
1050
		$rules .= "add {$ruleno} pipe {$pipeup} ip from any to any MAC any {$macent['mac']}\n";
1051
		$ruleno++;
1052
		$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC {$macent['mac']} any\n";
1053
	}
1054

    
1055
	return $rules;
1056
}
1057

    
1058
function captiveportal_passthrumac_delete_entry($macent) {
1059
	$rules = "";
1060

    
1061
	if ($macent['action'] == 'pass') {
1062
		$ruleno = captiveportal_get_ipfw_passthru_ruleno($macent['mac']);
1063

    
1064
		if (!$ruleno) {
1065
			return $rules;
1066
		}
1067

    
1068
		captiveportal_free_ipfw_ruleno($ruleno);
1069

    
1070
		$rules .= "delete {$ruleno}\n";
1071
		$rules .= "delete " . ++$ruleno . "\n";
1072

    
1073
		$pipeno = captiveportal_get_dn_passthru_ruleno($macent['mac']);
1074

    
1075
		if (!empty($pipeno)) {
1076
			captiveportal_free_dn_ruleno($pipeno);
1077
			$rules .= "pipe delete " . $pipeno . "\n";
1078
			$rules .= "pipe delete " . ++$pipeno . "\n";
1079
		}
1080
	}
1081

    
1082
	return $rules;
1083
}
1084

    
1085
function captiveportal_passthrumac_configure($filename = false, $startindex = 0, $stopindex = 0) {
1086
	global $config, $g, $cpzone;
1087

    
1088
	$rules = "";
1089

    
1090
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1091
		if ($stopindex > 0) {
1092
			$fd = fopen($filename, "w");
1093
			for ($idx = $startindex; $idx <= $stopindex; $idx++) {
1094
				if (isset($config['captiveportal'][$cpzone]['passthrumac'][$idx])) {
1095
					$rules = captiveportal_passthrumac_configure_entry($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1096
					fwrite($fd, $rules);
1097
				}
1098
			}
1099
			fclose($fd);
1100

    
1101
			return;
1102
		} else {
1103
			$nentries = count($config['captiveportal'][$cpzone]['passthrumac']);
1104
			if ($nentries > 2000) {
1105
				$nloops = $nentries / 1000;
1106
				$remainder= $nentries % 1000;
1107
				for ($i = 0; $i < $nloops; $i++) {
1108
					mwexec_bg("/usr/local/sbin/fcgicli -f /etc/rc.captiveportal_configure_mac -d \"cpzone={$cpzone}&startidx=" . ($i * 1000) . "&stopidx=" . ((($i+1) * 1000) - 1) . "\"");
1109
				}
1110
				if ($remainder > 0) {
1111
					mwexec_bg("/usr/local/sbin/fcgicli -f /etc/rc.captiveportal_configure_mac -d \"cpzone={$cpzone}&startidx=" . ($i * 1000) . "&stopidx=" . (($i* 1000) + $remainder) ."\"");
1112
				}
1113
			} else {
1114
				foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1115
					$rules .= captiveportal_passthrumac_configure_entry($macent, true);
1116
				}
1117
			}
1118
		}
1119
	}
1120

    
1121
	return $rules;
1122
}
1123

    
1124
function captiveportal_passthrumac_findbyname($username) {
1125
	global $config, $cpzone;
1126

    
1127
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1128
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1129
			if ($macent['username'] == $username) {
1130
				return $macent;
1131
			}
1132
		}
1133
	}
1134
	return NULL;
1135
}
1136

    
1137
/*
1138
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1139
 */
1140
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
1141
	global $g;
1142

    
1143
	/*  Instead of copying this entire function for something
1144
	 *  easy such as hostname vs ip address add this check
1145
	 */
1146
	if ($ishostname === true) {
1147
		if (!platform_booting()) {
1148
			$ipaddress = gethostbyname($ipent['hostname']);
1149
			if (!is_ipaddr($ipaddress)) {
1150
				return;
1151
			}
1152
		} else {
1153
			$ipaddress = "";
1154
		}
1155
	} else {
1156
		$ipaddress = $ipent['ip'];
1157
	}
1158

    
1159
	$rules = "";
1160
	$cp_filterdns_conf = "";
1161
	$enBwup = 0;
1162
	if (!empty($ipent['bw_up'])) {
1163
		$enBwup = intval($ipent['bw_up']);
1164
	} else if (!empty($config['captiveportal'][$cpzone]['bwdefaultup'])) {
1165
		$enBwup = $config['captiveportal'][$cpzone]['bwdefaultup'];
1166
	}
1167
	$enBwdown = 0;
1168
	if (!empty($ipent['bw_down'])) {
1169
		$enBwdown = intval($ipent['bw_down']);
1170
	} else if (!empty($config['captiveportal'][$cpzone]['bwdefaultdn'])) {
1171
		$enBwdown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
1172
	}
1173

    
1174
	$pipeno = captiveportal_get_next_dn_ruleno();
1175
	$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$enBwup}Kbit/s queue 100 buckets 16");
1176
	$pipedown = $pipeno + 1;
1177
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16");
1178
	if ($ishostname === true) {
1179
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
1180
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
1181
		if (!is_ipaddr($ipaddress)) {
1182
			return array("", $cp_filterdns_conf);
1183
		}
1184
	}
1185
	$subnet = "";
1186
	if (!empty($ipent['sn'])) {
1187
		$subnet = "/{$ipent['sn']}";
1188
	}
1189
	$rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
1190
	$rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
1191

    
1192
	if ($ishostname === true) {
1193
		return array($rules, $cp_filterdns_conf);
1194
	} else {
1195
		return $rules;
1196
	}
1197
}
1198

    
1199
function captiveportal_allowedhostname_configure() {
1200
	global $config, $g, $cpzone, $cpzoneid;
1201

    
1202
	$rules = "";
1203
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1204
		$rules = "\n# captiveportal_allowedhostname_configure()\n";
1205
		$cp_filterdns_conf = "";
1206
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) {
1207
			$tmprules = captiveportal_allowedip_configure_entry($hostnameent, true);
1208
			$rules .= $tmprules[0];
1209
			$cp_filterdns_conf .= $tmprules[1];
1210
		}
1211
		$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1212
		@file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1213
		unset($cp_filterdns_conf);
1214
		if (isvalidpid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid")) {
1215
			sigkillbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid", "HUP");
1216
		} else {
1217
			mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzoneid} -d 1");
1218
		}
1219
	} else {
1220
		killbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1221
		@unlink("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1222
	}
1223

    
1224
	return $rules;
1225
}
1226

    
1227
function captiveportal_allowedip_configure() {
1228
	global $config, $g, $cpzone;
1229

    
1230
	$rules = "";
1231
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1232
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) {
1233
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1234
		}
1235
	}
1236

    
1237
	return $rules;
1238
}
1239

    
1240
/* get last activity timestamp given client IP address */
1241
function captiveportal_get_last_activity($ip, $mac = NULL, $table = 1) {
1242
	global $cpzoneid;
1243

    
1244
	$ipfwoutput = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, $table, $ip, $mac);
1245
	/* Reading only from one of the tables is enough of approximation. */
1246
	if (is_array($ipfwoutput)) {
1247
		/* Workaround for #46652 */
1248
		if ($ipfwoutput['packets'] > 0) {
1249
			return $ipfwoutput['timestamp'];
1250
		} else {
1251
			return 0;
1252
		}
1253
	}
1254

    
1255
	return 0;
1256
}
1257

    
1258
function captiveportal_init_radius_servers() {
1259
	global $config, $g, $cpzone;
1260

    
1261
	/* generate radius server database */
1262
	if ($config['captiveportal'][$cpzone]['radiusip'] &&
1263
	    (!isset($config['captiveportal'][$cpzone]['auth_method']) || $config['captiveportal'][$cpzone]['auth_method'] == "radius")) {
1264
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1265
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1266
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1267
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1268

    
1269
		if ($config['captiveportal'][$cpzone]['radiusport']) {
1270
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1271
		} else {
1272
			$radiusport = 1812;
1273
		}
1274
		if ($config['captiveportal'][$cpzone]['radiusacctport']) {
1275
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1276
		} else {
1277
			$radiusacctport = 1813;
1278
		}
1279
		if ($config['captiveportal'][$cpzone]['radiusport2']) {
1280
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1281
		} else {
1282
			$radiusport2 = 1812;
1283
		}
1284
		if ($config['captiveportal'][$cpzone]['radiusport3']) {
1285
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1286
		} else {
1287
			$radiusport3 = 1812;
1288
		}
1289
		if ($config['captiveportal'][$cpzone]['radiusport4']) {
1290
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1291
		} else {
1292
			$radiusport4 = 1812;
1293
		}
1294

    
1295
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1296
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1297
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1298
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1299

    
1300
		$cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX);
1301
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
1302
		if (!$fd) {
1303
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1304
			unlock($cprdsrvlck);
1305
			return 1;
1306
		}
1307
		if (isset($radiusip)) {
1308
			fwrite($fd, $radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
1309
		}
1310
		if (isset($radiusip2)) {
1311
			fwrite($fd, "\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
1312
		}
1313
		if (isset($radiusip3)) {
1314
			fwrite($fd, "\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
1315
		}
1316
		if (isset($radiusip4)) {
1317
			fwrite($fd, "\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
1318
		}
1319

    
1320
		fclose($fd);
1321
		unlock($cprdsrvlck);
1322
	}
1323
}
1324

    
1325
/* read RADIUS servers into array */
1326
function captiveportal_get_radius_servers() {
1327
	global $g, $cpzone;
1328

    
1329
	$cprdsrvlck = lock("captiveportalradius{$cpzone}");
1330
	if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
1331
		$radiusservers = array();
1332
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db",
1333
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1334
		if ($cpradiusdb) {
1335
			foreach ($cpradiusdb as $cpradiusentry) {
1336
				$line = trim($cpradiusentry);
1337
				if ($line) {
1338
					$radsrv = array();
1339
					list($radsrv['ipaddr'], $radsrv['port'], $radsrv['acctport'], $radsrv['key'], $context) = explode(",", $line);
1340
				}
1341
				if (empty($context)) {
1342
					if (!is_array($radiusservers['first'])) {
1343
						$radiusservers['first'] = array();
1344
					}
1345
					$radiusservers['first'] = $radsrv;
1346
				} else {
1347
					if (!is_array($radiusservers[$context])) {
1348
						$radiusservers[$context] = array();
1349
					}
1350
					$radiusservers[$context][] = $radsrv;
1351
				}
1352
			}
1353
		}
1354
		unlock($cprdsrvlck);
1355
		return $radiusservers;
1356
	}
1357

    
1358
	unlock($cprdsrvlck);
1359
	return false;
1360
}
1361

    
1362
/* log successful captive portal authentication to syslog */
1363
/* part of this code from php.net */
1364
function captiveportal_logportalauth($user, $mac, $ip, $status, $message = null) {
1365
	// Log it
1366
	if (!$message) {
1367
		$message = "{$status}: {$user}, {$mac}, {$ip}";
1368
	} else {
1369
		$message = trim($message);
1370
		$message = "{$status}: {$user}, {$mac}, {$ip}, {$message}";
1371
	}
1372
	captiveportal_syslog($message);
1373
}
1374

    
1375
/* log simple messages to syslog */
1376
function captiveportal_syslog($message) {
1377
	global $cpzone;
1378

    
1379
	$message = trim($message);
1380
	$message = "Zone: {$cpzone} - {$message}";
1381
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1382
	// Log it
1383
	syslog(LOG_INFO, $message);
1384
	closelog();
1385
}
1386

    
1387
function radius($username, $password, $clientip, $clientmac, $type, $radiusctx = null) {
1388
	global $g, $config, $cpzoneid;
1389

    
1390
	$pipeno = captiveportal_get_next_dn_ruleno();
1391

    
1392
	/* If the pool is empty, return appropriate message and fail authentication */
1393
	if (empty($pipeno)) {
1394
		$auth_list = array();
1395
		$auth_list['auth_val'] = 1;
1396
		$auth_list['error'] = "System reached maximum login capacity";
1397
		return $auth_list;
1398
	}
1399

    
1400
	$radiusservers = captiveportal_get_radius_servers();
1401

    
1402
	if (is_null($radiusctx)) {
1403
		$radiusctx = 'first';
1404
	}
1405

    
1406
	$auth_list = RADIUS_AUTHENTICATION($username,
1407
		$password,
1408
		$radiusservers[$radiusctx],
1409
		$clientip,
1410
		$clientmac,
1411
		$pipeno);
1412

    
1413
	if ($auth_list['auth_val'] == 2) {
1414
		captiveportal_logportalauth($username, $clientmac, $clientip, $type);
1415
		$sessionid = portal_allow($clientip,
1416
			$clientmac,
1417
			$username,
1418
			$password,
1419
			$auth_list,
1420
			$pipeno,
1421
			$radiusctx);
1422
	} else {
1423
		captiveportal_free_dn_ruleno($pipeno);
1424
	}
1425

    
1426
	return $auth_list;
1427
}
1428

    
1429
function captiveportal_opendb() {
1430
	global $g, $config, $cpzone, $cpzoneid;
1431

    
1432
	$db_path = "{$g['vardb_path']}/captiveportal{$cpzone}.db";
1433
	$createquery = "CREATE TABLE IF NOT EXISTS captiveportal (" .
1434
				"allow_time INTEGER, pipeno INTEGER, ip TEXT, mac TEXT, username TEXT, " .
1435
				"sessionid TEXT, bpassword TEXT, session_timeout INTEGER, idle_timeout INTEGER, " .
1436
				"session_terminate_time INTEGER, interim_interval INTEGER, radiusctx TEXT); " .
1437
			"CREATE UNIQUE INDEX IF NOT EXISTS idx_active ON captiveportal (sessionid, username); " .
1438
			"CREATE INDEX IF NOT EXISTS user ON captiveportal (username); " .
1439
			"CREATE INDEX IF NOT EXISTS ip ON captiveportal (ip); " .
1440
			"CREATE INDEX IF NOT EXISTS starttime ON captiveportal (allow_time)";
1441

    
1442
	try {
1443
		$DB = new SQLite3($db_path);
1444
		$DB->busyTimeout(60000);
1445
	} catch (Exception $e) {
1446
		captiveportal_syslog("Could not open {$db_path} as an sqlite database for {$cpzone}. Error message: " . $e->getMessage() . " -- Trying again.");
1447
		unlink_if_exists($db_path);
1448
		try {
1449
			$DB = new SQLite3($db_path);
1450
			$DB->busyTimeout(60000);
1451
		} catch (Exception $e) {
1452
			captiveportal_syslog("Still could not open {$db_path} as an sqlite database for {$cpzone}. Error message: " . $e->getMessage() . " -- Remove the database file manually and ensure there is enough free space.");
1453
			return;
1454
		}
1455
	}
1456

    
1457
	if (!$DB) {
1458
		captiveportal_syslog("Could not open {$db_path} as an sqlite database for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Trying again.");
1459
		unlink_if_exists($db_path);
1460
		$DB = new SQLite3($db_path);
1461
		$DB->busyTimeout(60000);
1462
		if (!$DB) {
1463
			captiveportal_syslog("Still could not open {$db_path} as an sqlite database for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Remove the database file manually and ensure there is enough free space.");
1464
			return;
1465
		}
1466
	}
1467

    
1468
	if (! $DB->exec($createquery)) {
1469
		captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$DB->lastErrorMsg()}. Resetting and trying again.");
1470

    
1471
		/* If unable to initialize the database, reset and try again. */
1472
		$DB->close();
1473
		unset($DB);
1474
		unlink_if_exists($db_path);
1475
		$DB = new SQLite3($db_path);
1476
		$DB->busyTimeout(60000);
1477
		if ($DB->exec($createquery)) {
1478
			captiveportal_syslog("Successfully reinitialized tables for {$cpzone} -- database has been reset.");
1479
			if (!is_numericint($cpzoneid)) {
1480
				if (is_array($config['captiveportal'])) {
1481
					foreach ($config['captiveportal'] as $cpkey => $cp) {
1482
						if ($cpzone == $cp['zone']) {
1483
							$cpzoneid = $cp['zoneid'];
1484
						}
1485
					}
1486
				}
1487
			}
1488
			if (is_numericint($cpzoneid)) {
1489
				mwexec("/sbin/ipfw -x $cpzoneid table all flush");
1490
				captiveportal_syslog("Flushed tables for {$cpzone} after database reset.");
1491
			}
1492
		} else {
1493
			captiveportal_syslog("Still unable to create tables for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Remove the database file manually and try again.");
1494
		}
1495
	}
1496

    
1497
	return $DB;
1498
}
1499

    
1500
/* read captive portal DB into array */
1501
function captiveportal_read_db($query = "") {
1502
	$cpdb = array();
1503

    
1504
	$DB = captiveportal_opendb();
1505
	if ($DB) {
1506
		$response = $DB->query("SELECT * FROM captiveportal {$query}");
1507
		if ($response != FALSE) {
1508
			while ($row = $response->fetchArray()) {
1509
				$cpdb[] = $row;
1510
			}
1511
		}
1512
		$DB->close();
1513
	}
1514

    
1515
	return $cpdb;
1516
}
1517

    
1518
function captiveportal_remove_entries($remove) {
1519

    
1520
	if (!is_array($remove) || empty($remove)) {
1521
		return;
1522
	}
1523

    
1524
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1525
	foreach ($remove as $idx => $unindex) {
1526
		$query .= "'{$unindex}'";
1527
		if ($idx < (count($remove) - 1)) {
1528
			$query .= ",";
1529
		}
1530
	}
1531
	$query .= ")";
1532
	captiveportal_write_db($query);
1533
}
1534

    
1535
/* write captive portal DB */
1536
function captiveportal_write_db($queries) {
1537
	global $g;
1538

    
1539
	if (is_array($queries)) {
1540
		$query = implode(";", $queries);
1541
	} else {
1542
		$query = $queries;
1543
	}
1544

    
1545
	$DB = captiveportal_opendb();
1546
	if ($DB) {
1547
		$DB->exec("BEGIN TRANSACTION");
1548
		$result = $DB->exec($query);
1549
		if (!$result) {
1550
			captiveportal_syslog("Trying to modify DB returned error: {$DB->lastErrorMsg()}");
1551
		} else {
1552
			$DB->exec("END TRANSACTION");
1553
		}
1554
		$DB->close();
1555
		return $result;
1556
	} else {
1557
		return true;
1558
	}
1559
}
1560

    
1561
function captiveportal_write_elements() {
1562
	global $g, $config, $cpzone;
1563

    
1564
	$cpcfg = $config['captiveportal'][$cpzone];
1565

    
1566
	if (!is_dir($g['captiveportal_element_path'])) {
1567
		@mkdir($g['captiveportal_element_path']);
1568
	}
1569

    
1570
	if (is_array($cpcfg['element'])) {
1571
		conf_mount_rw();
1572
		foreach ($cpcfg['element'] as $data) {
1573
			/* Do not attempt to decode or write out empty files. */
1574
			if (empty($data['content']) || empty(base64_decode($data['content']))) {
1575
				unlink_if_exists("{$g['captiveportal_element_path']}/{$data['name']}");
1576
				touch("{$g['captiveportal_element_path']}/{$data['name']}");
1577
			} elseif (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1578
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1579
				return 1;
1580
			}
1581
			if (!file_exists("{$g['captiveportal_path']}/{$data['name']}")) {
1582
				@symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1583
			}
1584
		}
1585
		conf_mount_ro();
1586
	}
1587

    
1588
	return 0;
1589
}
1590

    
1591
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1592
	global $cpzone;
1593

    
1594
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1595
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1596
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1597
		$ridx = $rulenos_start;
1598
		while ($ridx < $rulenos_range_max) {
1599
			if ($rules[$ridx] == $cpzone) {
1600
				$rules[$ridx] = false;
1601
				$ridx++;
1602
				$rules[$ridx] = false;
1603
				$ridx++;
1604
			} else {
1605
				$ridx += 2;
1606
			}
1607
		}
1608
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1609
		unset($rules);
1610
	}
1611
	unlock($cpruleslck);
1612
}
1613

    
1614
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1615
	global $config, $g, $cpzone;
1616

    
1617
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1618
	$ruleno = 0;
1619
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1620
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1621
		$ridx = $rulenos_start;
1622
		while ($ridx < $rulenos_range_max) {
1623
			if (empty($rules[$ridx])) {
1624
				$ruleno = $ridx;
1625
				$rules[$ridx] = $cpzone;
1626
				$ridx++;
1627
				$rules[$ridx] = $cpzone;
1628
				break;
1629
			} else {
1630
				$ridx += 2;
1631
			}
1632
		}
1633
	} else {
1634
		$rules = array_pad(array(), $rulenos_range_max, false);
1635
		$ruleno = $rulenos_start;
1636
		$rules[$rulenos_start] = $cpzone;
1637
		$rulenos_start++;
1638
		$rules[$rulenos_start] = $cpzone;
1639
	}
1640
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1641
	unlock($cpruleslck);
1642
	unset($rules);
1643

    
1644
	return $ruleno;
1645
}
1646

    
1647
function captiveportal_free_dn_ruleno($ruleno) {
1648
	global $config, $g;
1649

    
1650
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1651
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1652
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1653
		$rules[$ruleno] = false;
1654
		$ruleno++;
1655
		$rules[$ruleno] = false;
1656
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1657
		unset($rules);
1658
	}
1659
	unlock($cpruleslck);
1660
}
1661

    
1662
function captiveportal_get_dn_passthru_ruleno($value) {
1663
	global $config, $g, $cpzone, $cpzoneid;
1664

    
1665
	$cpcfg = $config['captiveportal'][$cpzone];
1666
	if (!isset($cpcfg['enable'])) {
1667
		return NULL;
1668
	}
1669

    
1670
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1671
	$ruleno = NULL;
1672
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1673
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1674
		unset($output);
1675
		$_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);
1676
		$ruleno = intval($output[0]);
1677
		if (!$rules[$ruleno]) {
1678
			$ruleno = NULL;
1679
		}
1680
		unset($rules);
1681
	}
1682
	unlock($cpruleslck);
1683

    
1684
	return $ruleno;
1685
}
1686

    
1687
/*
1688
 * This function will calculate the lowest free firewall ruleno
1689
 * within the range specified based on the actual logged on users
1690
 *
1691
 */
1692
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1693
	global $config, $g, $cpzone;
1694

    
1695
	$cpcfg = $config['captiveportal'][$cpzone];
1696
	if (!isset($cpcfg['enable'])) {
1697
		return NULL;
1698
	}
1699

    
1700
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1701
	$ruleno = 0;
1702
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1703
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1704
		$ridx = $rulenos_start;
1705
		while ($ridx < $rulenos_range_max) {
1706
			if (empty($rules[$ridx])) {
1707
				$ruleno = $ridx;
1708
				$rules[$ridx] = $cpzone;
1709
				$ridx++;
1710
				$rules[$ridx] = $cpzone;
1711
				break;
1712
			} else {
1713
				/*
1714
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno
1715
				 * and the out pipe ruleno + 1.
1716
				 */
1717
				$ridx += 2;
1718
			}
1719
		}
1720
	} else {
1721
		$rules = array_pad(array(), $rulenos_range_max, false);
1722
		$ruleno = $rulenos_start;
1723
		$rules[$rulenos_start] = $cpzone;
1724
		$rulenos_start++;
1725
		$rules[$rulenos_start] = $cpzone;
1726
	}
1727
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1728
	unlock($cpruleslck);
1729
	unset($rules);
1730

    
1731
	return $ruleno;
1732
}
1733

    
1734
function captiveportal_free_ipfw_ruleno($ruleno) {
1735
	global $config, $g, $cpzone;
1736

    
1737
	$cpcfg = $config['captiveportal'][$cpzone];
1738
	if (!isset($cpcfg['enable'])) {
1739
		return NULL;
1740
	}
1741

    
1742
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1743
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1744
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1745
		$rules[$ruleno] = false;
1746
		$ruleno++;
1747
		$rules[$ruleno] = false;
1748
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1749
		unset($rules);
1750
	}
1751
	unlock($cpruleslck);
1752
}
1753

    
1754
function captiveportal_get_ipfw_passthru_ruleno($value) {
1755
	global $config, $g, $cpzone, $cpzoneid;
1756

    
1757
	$cpcfg = $config['captiveportal'][$cpzone];
1758
	if (!isset($cpcfg['enable'])) {
1759
		return NULL;
1760
	}
1761

    
1762
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1763
	$ruleno = NULL;
1764
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1765
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1766
		unset($output);
1767
		$_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);
1768
		$ruleno = intval($output[0]);
1769
		if (!$rules[$ruleno]) {
1770
			$ruleno = NULL;
1771
		}
1772
		unset($rules);
1773
	}
1774
	unlock($cpruleslck);
1775

    
1776
	return $ruleno;
1777
}
1778

    
1779
/**
1780
 * This function will calculate the traffic produced by a client
1781
 * based on its firewall rule
1782
 *
1783
 * Point of view: NAS
1784
 *
1785
 * Input means: from the client
1786
 * Output means: to the client
1787
 *
1788
 */
1789

    
1790
function getVolume($ip, $mac = NULL) {
1791
	global $config, $cpzone, $cpzoneid;
1792

    
1793
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1794
	$volume = array();
1795
	// Initialize vars properly, since we don't want NULL vars
1796
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1797

    
1798
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 1, $ip, $mac);
1799
	if (is_array($ipfw)) {
1800
		if ($reverse) {
1801
			$volume['output_pkts'] = $ipfw['packets'];
1802
			$volume['output_bytes'] = $ipfw['bytes'];
1803
		}
1804
		else {
1805
			$volume['input_pkts'] = $ipfw['packets'];
1806
			$volume['input_bytes'] = $ipfw['bytes'];
1807
		}
1808
	}
1809

    
1810
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 2, $ip, $mac);
1811
	if (is_array($ipfw)) {
1812
		if ($reverse) {
1813
			$volume['input_pkts'] = $ipfw['packets'];
1814
			$volume['input_bytes'] = $ipfw['bytes'];
1815
		}
1816
		else {
1817
			$volume['output_pkts'] = $ipfw['packets'];
1818
			$volume['output_bytes'] = $ipfw['bytes'];
1819
		}
1820
	}
1821

    
1822
	return $volume;
1823
}
1824

    
1825
/**
1826
 * Get the NAS-IP-Address based on the current wan address
1827
 *
1828
 * Use functions in interfaces.inc to find this out
1829
 *
1830
 */
1831

    
1832
function getNasIP() {
1833
	global $config, $cpzone;
1834

    
1835
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1836
			$nasIp = get_interface_ip();
1837
	} else {
1838
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1839
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1840
		} else {
1841
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1842
		}
1843
	}
1844

    
1845
	if (!is_ipaddr($nasIp)) {
1846
		$nasIp = "0.0.0.0";
1847
	}
1848

    
1849
	return $nasIp;
1850
}
1851

    
1852
function portal_ip_from_client_ip($cliip) {
1853
	global $config, $cpzone;
1854

    
1855
	$isipv6 = is_ipaddrv6($cliip);
1856
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1857
	foreach ($interfaces as $cpif) {
1858
		if ($isipv6) {
1859
			$ip = get_interface_ipv6($cpif);
1860
			$sn = get_interface_subnetv6($cpif);
1861
		} else {
1862
			$ip = get_interface_ip($cpif);
1863
			$sn = get_interface_subnet($cpif);
1864
		}
1865
		if (ip_in_subnet($cliip, "{$ip}/{$sn}")) {
1866
			return $ip;
1867
		}
1868
	}
1869

    
1870
	$inet = ($isipv6) ? '-inet6' : '-inet';
1871
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1872
	$iface = trim($iface, "\n");
1873
	if (!empty($iface)) {
1874
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1875
		if (is_ipaddr($ip)) {
1876
			return $ip;
1877
		}
1878
	}
1879

    
1880
	// doesn't match up to any particular interface
1881
	// so let's set the portal IP to what PHP says
1882
	// the server IP issuing the request is.
1883
	// allows same behavior as 1.2.x where IP isn't
1884
	// in the subnet of any CP interface (static routes, etc.)
1885
	// rather than forcing to DNS hostname resolution
1886
	$ip = $_SERVER['SERVER_ADDR'];
1887
	if (is_ipaddr($ip)) {
1888
		return $ip;
1889
	}
1890

    
1891
	return false;
1892
}
1893

    
1894
function portal_hostname_from_client_ip($cliip) {
1895
	global $config, $cpzone;
1896

    
1897
	$cpcfg = $config['captiveportal'][$cpzone];
1898

    
1899
	if (isset($cpcfg['httpslogin'])) {
1900
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 8001);
1901
		$ourhostname = $cpcfg['httpsname'];
1902

    
1903
		if ($listenporthttps != 443) {
1904
			$ourhostname .= ":" . $listenporthttps;
1905
		}
1906
	} else {
1907
		$listenporthttp = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : ($cpcfg['zoneid'] + 8000);
1908
		$ifip = portal_ip_from_client_ip($cliip);
1909
		if (!$ifip) {
1910
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1911
		} else {
1912
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1913
		}
1914

    
1915
		if ($listenporthttp != 80) {
1916
			$ourhostname .= ":" . $listenporthttp;
1917
		}
1918
	}
1919

    
1920
	return $ourhostname;
1921
}
1922

    
1923
/* functions move from index.php */
1924

    
1925
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1926
	global $g, $config, $cpzone;
1927

    
1928
	/* Get captive portal layout */
1929
	if ($type == "redir") {
1930
		header("Location: {$redirurl}");
1931
		return;
1932
	} else if ($type == "login") {
1933
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1934
	} else {
1935
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1936
	}
1937

    
1938
	$cpcfg = $config['captiveportal'][$cpzone];
1939

    
1940
	/* substitute the PORTAL_REDIRURL variable */
1941
	if ($cpcfg['preauthurl']) {
1942
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1943
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1944
	}
1945

    
1946
	/* substitute other variables */
1947
	$ourhostname = portal_hostname_from_client_ip($clientip);
1948
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1949
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1950
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1951

    
1952
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1953
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1954
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1955
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1956
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1957

    
1958
	// Special handling case for captive portal master page so that it can be ran
1959
	// through the PHP interpreter using the include method above.  We convert the
1960
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1961
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1962
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1963
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1964
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1965
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1966
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1967
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1968

    
1969
	echo $htmltext;
1970
}
1971

    
1972
function portal_mac_radius($clientmac, $clientip) {
1973
	global $config, $cpzone;
1974

    
1975
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1976

    
1977
	/* authentication against the radius server */
1978
	$username = mac_format($clientmac);
1979
	$auth_list = radius($username, $radmac_secret, $clientip, $clientmac, "MACHINE LOGIN");
1980
	if ($auth_list['auth_val'] == 2) {
1981
		return TRUE;
1982
	}
1983

    
1984
	if (!empty($auth_list['url_redirection'])) {
1985
		portal_reply_page($auth_list['url_redirection'], "redir");
1986
	}
1987

    
1988
	return FALSE;
1989
}
1990

    
1991
function captiveportal_reapply_attributes($cpentry, $attributes) {
1992
	global $config, $cpzone, $g;
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
	}
2000
	$bw_up = !empty($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
2001
	$bw_down = !empty($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
2002
	$bw_up_pipeno = $cpentry[1];
2003
	$bw_down_pipeno = $cpentry[1]+1;
2004

    
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
	//captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_BANDWIDTH_REAPPLY", "{$bw_up}/{$bw_down}");
2008

    
2009
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
2010
}
2011

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

    
2015
	// Ensure we create an array if we are missing attributes
2016
	if (!is_array($attributes)) {
2017
		$attributes = array();
2018
	}
2019

    
2020
	unset($sessionid);
2021

    
2022
	/* Do not allow concurrent login execution. */
2023
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
2024

    
2025
	if ($attributes['voucher']) {
2026
		$remaining_time = $attributes['session_timeout'];
2027
	// Set RADIUS-Attribute to Voucher to prevent ReAuth-Reqeuest for Vouchers Bug: #2155
2028
		$radiusctx="voucher";
2029
	}
2030

    
2031
	$writecfg = false;
2032
	/* Find an existing session */
2033
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
2034
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
2035
			$mac = captiveportal_passthrumac_findbyname($username);
2036
			if (!empty($mac)) {
2037
				if ($_POST['replacemacpassthru']) {
2038
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
2039
						if ($macent['mac'] == $mac['mac']) {
2040
							$macrules = "";
2041
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
2042
							$pipeno = captiveportal_get_dn_passthru_ruleno($mac['mac']);
2043
							if ($ruleno) {
2044
								captiveportal_free_ipfw_ruleno($ruleno);
2045
								$macrules .= "delete {$ruleno}\n";
2046
								++$ruleno;
2047
								$macrules .= "delete {$ruleno}\n";
2048
							}
2049
							if ($pipeno) {
2050
								captiveportal_free_dn_ruleno($pipeno);
2051
								$macrules .= "pipe delete {$pipeno}\n";
2052
								++$pipeno;
2053
								$macrules .= "pipe delete {$pipeno}\n";
2054
							}
2055
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
2056
							$mac['action'] = 'pass';
2057
							$mac['mac'] = $clientmac;
2058
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
2059
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
2060
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
2061
							mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
2062
							$writecfg = true;
2063
							$sessionid = true;
2064
							break;
2065
						}
2066
					}
2067
				} else {
2068
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
2069
						$clientmac, $clientip, $username, $password);
2070
					unlock($cpdblck);
2071
					return;
2072
				}
2073
			}
2074
		}
2075
	}
2076

    
2077
	/* read in client database */
2078
	$query = "WHERE ip = '{$clientip}'";
2079
	$tmpusername = strtolower($username);
2080
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) {
2081
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
2082
	}
2083
	$cpdb = captiveportal_read_db($query);
2084

    
2085
	/* Snapshot the timestamp */
2086
	$allow_time = time();
2087
	$radiusservers = captiveportal_get_radius_servers();
2088
	$unsetindexes = array();
2089
	if (is_null($radiusctx)) {
2090
		$radiusctx = 'first';
2091
	}
2092

    
2093
	foreach ($cpdb as $cpentry) {
2094
		if (empty($cpentry[11])) {
2095
			$cpentry[11] = 'first';
2096
		}
2097
		/* on the same ip */
2098
		if ($cpentry[2] == $clientip) {
2099
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac) {
2100
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - REUSING OLD SESSION");
2101
			} else {
2102
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
2103
			}
2104
			$sessionid = $cpentry[5];
2105
			break;
2106
		} elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
2107
			// user logged in with an active voucher. Check for how long and calculate
2108
			// how much time we can give him (voucher credit - used time)
2109
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
2110
			if ($remaining_time < 0) { // just in case.
2111
				$remaining_time = 0;
2112
			}
2113

    
2114
			/* This user was already logged in so we disconnect the old one */
2115
			captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], 13);
2116
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - TERMINATING OLD SESSION");
2117
			$unsetindexes[] = $cpentry[5];
2118
			break;
2119
		} elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
2120
			/* on the same username */
2121
			if (strcasecmp($cpentry[4], $username) == 0) {
2122
				/* This user was already logged in so we disconnect the old one */
2123
				captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], 13);
2124
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - TERMINATING OLD SESSION");
2125
				$unsetindexes[] = $cpentry[5];
2126
				break;
2127
			}
2128
		}
2129
	}
2130
	unset($cpdb);
2131

    
2132
	if (!empty($unsetindexes)) {
2133
		captiveportal_remove_entries($unsetindexes);
2134
	}
2135

    
2136
	if ($attributes['voucher'] && $remaining_time <= 0) {
2137
		return 0;       // voucher already used and no time left
2138
	}
2139

    
2140
	if (!isset($sessionid)) {
2141
		/* generate unique session ID */
2142
		$tod = gettimeofday();
2143
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
2144

    
2145
		if ($passthrumac) {
2146
			$mac = array();
2147
			$mac['action'] = 'pass';
2148
			$mac['mac'] = $clientmac;
2149
			$mac['ip'] = $clientip; /* Used only for logging */
2150
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
2151
				$mac['username'] = $username;
2152
				if ($attributes['voucher']) {
2153
					$mac['logintype'] = "voucher";
2154
				}
2155
			}
2156
			if ($username == "unauthenticated") {
2157
				$mac['descr'] = "Auto-added";
2158
			} else {
2159
				$mac['descr'] = "Auto-added for user {$username}";
2160
			}
2161
			if (!empty($bw_up)) {
2162
				$mac['bw_up'] = $bw_up;
2163
			}
2164
			if (!empty($bw_down)) {
2165
				$mac['bw_down'] = $bw_down;
2166
			}
2167
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
2168
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
2169
			}
2170
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
2171
			unlock($cpdblck);
2172
			$macrules = captiveportal_passthrumac_configure_entry($mac);
2173
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
2174
			mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
2175
			$writecfg = true;
2176
		} else {
2177
			/* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
2178
			if (is_null($pipeno)) {
2179
				$pipeno = captiveportal_get_next_dn_ruleno();
2180
			}
2181

    
2182
			/* if the pool is empty, return appropriate message and exit */
2183
			if (is_null($pipeno)) {
2184
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
2185
				log_error("Zone: {$cpzone} - WARNING!  Captive portal has reached maximum login capacity");
2186
				unlock($cpdblck);
2187
				return;
2188
			}
2189

    
2190
			if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
2191
				$dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
2192
				$dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
2193
			} else {
2194
				$dwfaultbw_up = $dwfaultbw_down = 0;
2195
			}
2196
			$bw_up = !empty($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
2197
			$bw_down = !empty($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
2198

    
2199
			$bw_up_pipeno = $pipeno;
2200
			$bw_down_pipeno = $pipeno + 1;
2201
			//$bw_up /= 1000; // Scale to Kbit/s
2202
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
2203
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
2204

    
2205
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
2206
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
2207
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
2208
			} else {
2209
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
2210
			}
2211

    
2212
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
2213
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
2214
			} else {
2215
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
2216
			}
2217

    
2218
			if ($attributes['voucher']) {
2219
				$attributes['session_timeout'] = $remaining_time;
2220
			}
2221

    
2222
			/* handle empty attributes */
2223
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
2224
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
2225
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
2226
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
2227

    
2228
			/* escape username */
2229
			$safe_username = SQLite3::escapeString($username);
2230

    
2231
			/* encode password in Base64 just in case it contains commas */
2232
			$bpassword = base64_encode($password);
2233
			$insertquery = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, radiusctx) ";
2234
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
2235
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, '{$radiusctx}')";
2236

    
2237
			/* store information to database */
2238
			captiveportal_write_db($insertquery);
2239
			unlock($cpdblck);
2240
			unset($insertquery, $bpassword);
2241

    
2242
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
2243
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
2244
				if ($acct_val == 1) {
2245
					captiveportal_logportalauth($username, $clientmac, $clientip, $type, "RADIUS ACCOUNTING FAILED");
2246
				}
2247
			}
2248
		}
2249
	} else {
2250
		/* NOTE: #3062-11 If the pipeno has been allocated free it to not DoS the CP and maintain proper operation as in radius() case */
2251
		if (!is_null($pipeno)) {
2252
			captiveportal_free_dn_ruleno($pipeno);
2253
		}
2254

    
2255
		unlock($cpdblck);
2256
	}
2257

    
2258
	if ($writecfg == true) {
2259
		write_config();
2260
	}
2261

    
2262
	/* redirect user to desired destination */
2263
	if (!empty($attributes['url_redirection'])) {
2264
		$my_redirurl = $attributes['url_redirection'];
2265
	} else if (!empty($redirurl)) {
2266
		$my_redirurl = $redirurl;
2267
	} else if (!empty($config['captiveportal'][$cpzone]['redirurl'])) {
2268
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
2269
	}
2270

    
2271
	if (isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
2272
		$ourhostname = portal_hostname_from_client_ip($clientip);
2273
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
2274
		$logouturl = "{$protocol}{$ourhostname}/";
2275

    
2276
		if (isset($attributes['reply_message'])) {
2277
			$message = $attributes['reply_message'];
2278
		} else {
2279
			$message = 0;
2280
		}
2281

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

    
2284
	} else {
2285
		portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
2286
	}
2287

    
2288
	return $sessionid;
2289
}
2290

    
2291

    
2292
/*
2293
 * Used for when pass-through credits are enabled.
2294
 * Returns true when there was at least one free login to deduct for the MAC.
2295
 * Expired entries are removed as they are seen.
2296
 * Active entries are updated according to the configuration.
2297
 */
2298
function portal_consume_passthrough_credit($clientmac) {
2299
	global $config, $cpzone;
2300

    
2301
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count'])) {
2302
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
2303
	} else {
2304
		return false;
2305
	}
2306

    
2307
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout'])) {
2308
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2309
	} else {
2310
		return false;
2311
	}
2312

    
2313
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac) {
2314
		return false;
2315
	}
2316

    
2317
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2318

    
2319
	/*
2320
	 * Read database of used MACs.  Lines are a comma-separated list
2321
	 * of the time, MAC, then the count of pass-through credits remaining.
2322
	 */
2323
	$usedmacs = captiveportal_read_usedmacs_db();
2324

    
2325
	$currenttime = time();
2326
	$found = false;
2327
	foreach ($usedmacs as $key => $usedmac) {
2328
		$usedmac = explode(",", $usedmac);
2329

    
2330
		if ($usedmac[1] == $clientmac) {
2331
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2332
				if ($usedmac[2] < 1) {
2333
					if ($updatetimeouts) {
2334
						$usedmac[0] = $currenttime;
2335
						unset($usedmacs[$key]);
2336
						$usedmacs[] = implode(",", $usedmac);
2337
						captiveportal_write_usedmacs_db($usedmacs);
2338
					}
2339

    
2340
					return false;
2341
				} else {
2342
					$usedmac[2] -= 1;
2343
					$usedmacs[$key] = implode(",", $usedmac);
2344
				}
2345

    
2346
				$found = true;
2347
			} else {
2348
				unset($usedmacs[$key]);
2349
			}
2350

    
2351
			break;
2352
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime) {
2353
			unset($usedmacs[$key]);
2354
		}
2355
	}
2356

    
2357
	if (!$found) {
2358
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2359
		$usedmacs[] = implode(",", $usedmac);
2360
	}
2361

    
2362
	captiveportal_write_usedmacs_db($usedmacs);
2363
	return true;
2364
}
2365

    
2366
function captiveportal_read_usedmacs_db() {
2367
	global $g, $cpzone;
2368

    
2369
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2370
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2371
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2372
		if (!$usedmacs) {
2373
			$usedmacs = array();
2374
		}
2375
	} else {
2376
		$usedmacs = array();
2377
	}
2378

    
2379
	unlock($cpumaclck);
2380
	return $usedmacs;
2381
}
2382

    
2383
function captiveportal_write_usedmacs_db($usedmacs) {
2384
	global $g, $cpzone;
2385

    
2386
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2387
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2388
	unlock($cpumaclck);
2389
}
2390

    
2391
function captiveportal_blocked_mac($mac) {
2392
	global $config, $g, $cpzone;
2393

    
2394
	if (empty($mac) || !is_macaddr($mac)) {
2395
		return false;
2396
	}
2397

    
2398
	if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
2399
		return false;
2400
	}
2401

    
2402
	foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac) {
2403
		if (($passthrumac['action'] == 'block') &&
2404
		    ($passthrumac['mac'] == strtolower($mac))) {
2405
			return true;
2406
		}
2407
	}
2408

    
2409
	return false;
2410

    
2411
}
2412

    
2413
function captiveportal_send_server_accounting($off = false) {
2414
	global $cpzone, $config;
2415

    
2416
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
2417
		return;
2418
	}
2419
	if ($off) {
2420
		$racct = new Auth_RADIUS_Acct_Off;
2421
	} else {
2422
		$racct = new Auth_RADIUS_Acct_On;
2423
	}
2424
	$radiusservers = captiveportal_get_radius_servers();
2425
	if (empty($radiusservers)) {
2426
		return;
2427
	}
2428
	foreach ($radiusservers['first'] as $radsrv) {
2429
		// Add a new server to our instance
2430
		$racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
2431
	}
2432
	if (PEAR::isError($racct->start())) {
2433
		$retvalue['acct_val'] = 1;
2434
		$retvalue['error'] = $racct->getMessage();
2435

    
2436
		// If we encounter an error immediately stop this function and go back
2437
		$racct->close();
2438
		return $retvalue;
2439
	}
2440
	// Send request
2441
	$result = $racct->send();
2442
	// Evaluation of the response
2443
	// 5 -> Accounting-Response
2444
	// See RFC2866 for this.
2445
	if (PEAR::isError($result)) {
2446
		$retvalue['acct_val'] = 1;
2447
		$retvalue['error'] = $result->getMessage();
2448
	} else if ($result === true) {
2449
		$retvalue['acct_val'] = 5 ;
2450
	} else {
2451
		$retvalue['acct_val'] = 1 ;
2452
	}
2453

    
2454
	$racct->close();
2455
	return $retvalue;
2456
}
2457

    
2458
function captiveportal_isip_logged($clientip) {
2459
	global $g, $cpzone;
2460

    
2461
	/* read in client database */
2462
	$query = "WHERE ip = '{$clientip}'";
2463
	$cpdb = captiveportal_read_db($query);
2464
	foreach ($cpdb as $cpentry) {
2465
		return $cpentry;
2466
	}
2467
}
2468
?>
(7-7/65)