Project

General

Profile

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

    
119
EOD;
120

    
121
	if (isset($config['voucher'][$cpzone]['enable'])) {
122
		$translated_text = gettext("Enter Voucher Code:");
123
		$htmltext .= <<<EOD
124
									<tr>
125
										<td align="right">{$translated_text} </td>
126
										<td><input name="auth_voucher" type="text" style="border:1px dashed;" size="22"></td>
127
									</tr>
128

    
129
EOD;
130
	}
131

    
132
	$translated_text = gettext("Continue");
133
	$htmltext .= <<<EOD
134
									<tr>
135
										<td colspan="2"><center><input name="accept" type="submit" value="{$translated_text}"></center></td>
136
									</tr>
137
								</table>
138
								</div>
139
							</center>
140
							</div>
141
						</td>
142
					</tr>
143
					</table>
144
					</center>
145
					</div>
146
					</center>
147
				</td>
148
			</tr>
149
			</table>
150
			</center>
151
			</div>
152
		</td>
153
	</tr>
154
	</table>
155
	</center>
156
</form>
157
</body>
158
</html>
159

    
160
EOD;
161

    
162
	return $htmltext;
163
}
164

    
165
function captiveportal_load_modules() {
166
	global $config;
167

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

    
180
	/* Always load dummynet now that even allowed ip and mac passthrough use it. */
181
	if (!is_module_loaded("dummynet.ko")) {
182
		mwexec("/sbin/kldload dummynet");
183
		set_sysctl(array("net.inet.ip.dummynet.io_fast" => "1", "net.inet.ip.dummynet.hash_size" => "256"));
184
	}
185
	unmute_kernel_msgs();
186
}
187

    
188
function captiveportal_configure() {
189
	global $config, $cpzone, $cpzoneid;
190

    
191
	if (is_array($config['captiveportal'])) {
192
		foreach ($config['captiveportal'] as $cpkey => $cp) {
193
			$cpzone = $cpkey;
194
			$cpzoneid = $cp['zoneid'];
195
			captiveportal_configure_zone($cp);
196
		}
197
	}
198
}
199

    
200
function captiveportal_configure_zone($cpcfg) {
201
	global $config, $g, $cpzone, $cpzoneid;
202

    
203
	$captiveportallck = lock("captiveportal{$cpzone}", LOCK_EX);
204

    
205
	if (isset($cpcfg['enable'])) {
206

    
207
		if (platform_booting()) {
208
			echo "Starting captive portal({$cpcfg['zone']})... ";
209

    
210
			/* remove old information */
211
			unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
212
		} else {
213
			captiveportal_syslog("Reconfiguring captive portal({$cpcfg['zone']}).");
214
		}
215

    
216
		/* init ipfw rules */
217
		captiveportal_init_rules(true);
218

    
219
		/* kill any running minicron */
220
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
221

    
222
		/* initialize minicron interval value */
223
		$croninterval = $cpcfg['croninterval'] ? $cpcfg['croninterval'] : 60;
224

    
225
		/* double check if the $croninterval is numeric and at least 10 seconds. If not we set it to 60 to avoid problems */
226
		if ((!is_numeric($croninterval)) || ($croninterval < 10)) {
227
			$croninterval = 60;
228
		}
229

    
230
		/* write portal page */
231
		if (is_array($cpcfg['page']) && $cpcfg['page']['htmltext']) {
232
			$htmltext = base64_decode($cpcfg['page']['htmltext']);
233
		} else {
234
			/* example/template page */
235
			$htmltext = get_default_captive_portal_html();
236
		}
237

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

    
257
		/* write error page */
258
		if (is_array($cpcfg['page']) && $cpcfg['page']['errtext']) {
259
			$errtext = base64_decode($cpcfg['page']['errtext']);
260
		} else {
261
			/* example page  */
262
			$errtext = get_default_captive_portal_html();
263
		}
264

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

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

    
319
document.location.href="<?=\$my_redirurl;?>";
320
//]]>
321
</script>
322
</body>
323
</html>
324

    
325
EOD;
326
		}
327

    
328
		$fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html", "w");
329
		if ($fd) {
330
			fwrite($fd, $logouttext);
331
			fclose($fd);
332
		}
333
		unset($logouttext);
334

    
335
		/* write elements */
336
		captiveportal_write_elements();
337

    
338
		/* kill any running CP nginx instances */
339
		killbypid("{$g['varrun_path']}/nginx-{$cpzone}-CaptivePortal.pid");
340
		killbypid("{$g['varrun_path']}/nginx-{$cpzone}-CaptivePortal-SSL.pid");
341

    
342
		/* start up the webserving daemon */
343
		captiveportal_init_webgui_zone($cpcfg);
344

    
345
		/* Kill any existing prunecaptiveportal processes */
346
		if (file_exists("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid")) {
347
			killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
348
		}
349

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

    
354
		/* generate radius server database */
355
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
356
		captiveportal_init_radius_servers();
357

    
358
		if (platform_booting()) {
359
			/* send Accounting-On to server */
360
			captiveportal_send_server_accounting();
361
			echo "done\n";
362
		}
363

    
364
	} else {
365
		killbypid("{$g['varrun_path']}/nginx-{$cpzone}-CaptivePortal.pid");
366
		killbypid("{$g['varrun_path']}/nginx-{$cpzone}-CaptivePortal-SSL.pid");
367
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
368
		@unlink("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
369
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
370
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
371

    
372
		captiveportal_radius_stop_all();
373

    
374
		/* send Accounting-Off to server */
375
		if (!platform_booting()) {
376
			captiveportal_send_server_accounting(true);
377
		}
378

    
379
		/* remove old information */
380
		unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
381
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
382
		unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
383
		/* Release allocated pipes for this zone */
384
		captiveportal_free_dnrules();
385

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

    
388
		if (empty($config['captiveportal'])) {
389
			set_single_sysctl("net.link.ether.ipfw", "0");
390
		} else {
391
			/* Deactivate ipfw(4) if not needed */
392
			$cpactive = false;
393
			if (is_array($config['captiveportal'])) {
394
				foreach ($config['captiveportal'] as $cpkey => $cp) {
395
					if (isset($cp['enable'])) {
396
						$cpactive = true;
397
						break;
398
					}
399
				}
400
			}
401
			if ($cpactive === false) {
402
				set_single_sysctl("net.link.ether.ipfw", "0");
403
			}
404
		}
405
	}
406

    
407
	unlock($captiveportallck);
408

    
409
	return 0;
410
}
411

    
412
function captiveportal_init_webgui() {
413
	global $config, $cpzone;
414

    
415
	if (is_array($config['captiveportal'])) {
416
		foreach ($config['captiveportal'] as $cpkey => $cp) {
417
			$cpzone = $cpkey;
418
			captiveportal_init_webgui_zone($cp);
419
		}
420
	}
421
}
422

    
423
function captiveportal_init_webgui_zonename($zone) {
424
	global $config, $cpzone;
425

    
426
	if (isset($config['captiveportal'][$zone])) {
427
		$cpzone = $zone;
428
		captiveportal_init_webgui_zone($config['captiveportal'][$zone]);
429
	}
430
}
431

    
432
function captiveportal_init_webgui_zone($cpcfg) {
433
	global $g, $config, $cpzone;
434

    
435
	if (!isset($cpcfg['enable'])) {
436
		return;
437
	}
438

    
439
	if (isset($cpcfg['httpslogin'])) {
440
		$cert = lookup_cert($cpcfg['certref']);
441
		$crt = base64_decode($cert['crt']);
442
		$key = base64_decode($cert['prv']);
443
		$ca = ca_chain($cert);
444

    
445
		/* generate nginx configuration */
446
		if (!empty($cpcfg['listenporthttps'])) {
447
			$listenporthttps = $cpcfg['listenporthttps'];
448
		} else {
449
			$listenporthttps = 8001 + $cpcfg['zoneid'];
450
		}
451
		system_generate_nginx_config("{$g['varetc_path']}/nginx-{$cpzone}-CaptivePortal-SSL.conf",
452
			$crt, $key, $ca, "nginx-{$cpzone}-CaptivePortal-SSL.pid", $listenporthttps, "/usr/local/captiveportal",
453
			"cert-{$cpzone}-portal.pem", "ca-{$cpzone}-portal.pem", $cpzone);
454
	}
455

    
456
	/* generate nginx configuration */
457
	if (!empty($cpcfg['listenporthttp'])) {
458
		$listenporthttp = $cpcfg['listenporthttp'];
459
	} else {
460
		$listenporthttp = 8000 + $cpcfg['zoneid'];
461
	}
462
	system_generate_nginx_config("{$g['varetc_path']}/nginx-{$cpzone}-CaptivePortal.conf",
463
		"", "", "", "nginx-{$cpzone}-CaptivePortal.pid", $listenporthttp, "/usr/local/captiveportal",
464
		"", "", $cpzone);
465

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

    
470
	/* fire up https instance */
471
	if (isset($cpcfg['httpslogin'])) {
472
		@unlink("{$g['varrun']}/nginx-{$cpzone}-CaptivePortal-SSL.pid");
473
		$res = mwexec("/usr/local/sbin/nginx -c {$g['varetc_path']}/nginx-{$cpzone}-CaptivePortal-SSL.conf");
474
	}
475
}
476

    
477
function captiveportal_init_rules_byinterface($interface) {
478
	global $cpzone, $cpzoneid, $config;
479

    
480
	if (!is_array($config['captiveportal'])) {
481
		return;
482
	}
483

    
484
	foreach ($config['captiveportal'] as $cpkey => $cp) {
485
		$cpzone = $cpkey;
486
		$cpzoneid = $cp['zoneid'];
487
		$cpinterfaces = explode(",", $cp['interface']);
488
		if (in_array($interface, $cpinterfaces)) {
489
			captiveportal_init_rules();
490
			break;
491
		}
492
	}
493
}
494

    
495
/* reinit will disconnect all users, be careful! */
496
function captiveportal_init_rules($reinit = false) {
497
	global $config, $g, $cpzone, $cpzoneid;
498

    
499
	if (!isset($config['captiveportal'][$cpzone]['enable'])) {
500
		return;
501
	}
502

    
503
	captiveportal_load_modules();
504
	mwexec("/sbin/ipfw zone {$cpzoneid} create", true);
505

    
506
	/* Cleanup so nothing is leaked */
507
	captiveportal_free_dnrules();
508
	unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
509

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

    
540
	if ($reinit == false) {
541
		$captiveportallck = lock("captiveportal{$cpzone}");
542
	}
543

    
544
	$cprules = <<<EOD
545

    
546
flush
547
add 65291 allow pfsync from any to any
548
add 65292 allow carp from any to any
549

    
550
# layer 2: pass ARP
551
add 65301 pass layer2 mac-type arp,rarp
552
# pfsense requires for WPA
553
add 65302 pass layer2 mac-type 0x888e,0x88c7
554
# PPP Over Ethernet Session Stage/Discovery Stage
555
add 65303 pass layer2 mac-type 0x8863,0x8864
556

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

    
560
EOD;
561

    
562
	$rulenum = 65310;
563
	/* These tables contain host ips */
564
	$cprules .= "add {$rulenum} pass ip from any to table(100) in\n";
565
	$rulenum++;
566
	$cprules .= "add {$rulenum} pass ip from table(100) to any out\n";
567
	$rulenum++;
568
	foreach ($cpips as $cpip) {
569
		$cprules .= "table 100 add {$cpip}\n";
570
	}
571
	$cprules .= "add {$rulenum} pass ip from any to 255.255.255.255 in\n";
572
	$rulenum++;
573
	$cprules .= "add {$rulenum} pass ip from 255.255.255.255 to any out\n";
574
	$rulenum++;
575

    
576
	/* Allowed ips */
577
	$cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any in\n";
578
	$rulenum++;
579
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) in\n";
580
	$rulenum++;
581
	$cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any out\n";
582
	$rulenum++;
583
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) out\n";
584
	$rulenum++;
585

    
586
	/* Authenticated users rules. */
587
	$cprules .= "add {$rulenum} pipe tablearg ip from table(1) to any in\n";
588
	$rulenum++;
589
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(2) out\n";
590
	$rulenum++;
591

    
592
	if (!empty($config['captiveportal'][$cpzone]['listenporthttp'])) {
593
		$listenporthttp = $config['captiveportal'][$cpzone]['listenporthttp'];
594
	} else {
595
		$listenporthttp = 8000 + $cpzoneid;
596
	}
597

    
598
	if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
599
		if (!empty($config['captiveportal'][$cpzone]['listenporthttps'])) {
600
			$listenporthttps = $config['captiveportal'][$cpzone]['listenporthttps'];
601
		} else {
602
			$listenporthttps = 8001 + $cpzoneid;
603
		}
604
		if (!isset($config['captiveportal'][$cpzone]['nohttpsforwards'])) {
605
			$cprules .= "add 65531 fwd 127.0.0.1,{$listenporthttps} tcp from any to any dst-port 443 in\n";
606
		}
607
	}
608

    
609
	$cprules .= <<<EOD
610

    
611
# redirect non-authenticated clients to captive portal
612
add 65532 fwd 127.0.0.1,{$listenporthttp} tcp from any to any dst-port 80 in
613
# let the responses from the captive portal web server back out
614
add 65533 pass tcp from any to any out
615
# block everything else
616
add 65534 deny all from any to any
617

    
618
EOD;
619

    
620
	/* generate passthru mac database */
621
	$cprules .= captiveportal_passthrumac_configure(true);
622
	$cprules .= "\n";
623

    
624
	/* allowed ipfw rules to make allowed ip work */
625
	$cprules .= captiveportal_allowedip_configure();
626

    
627
	/* allowed ipfw rules to make allowed hostnames work */
628
	$cprules .= captiveportal_allowedhostname_configure();
629

    
630
	/* load rules */
631
	file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
632
	mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
633
	//@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
634
	unset($cprules);
635

    
636
	if ($reinit == false) {
637
		unlock($captiveportallck);
638
	}
639
}
640

    
641
/*
642
 * Remove clients that have been around for longer than the specified amount of time
643
 * db file structure:
644
 * timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time,interim_interval
645
 * (password is in Base64 and only saved when reauthentication is enabled)
646
 */
647
function captiveportal_prune_old() {
648
	global $g, $config, $cpzone, $cpzoneid;
649

    
650
	if (empty($cpzone)) {
651
		return;
652
	}
653

    
654
	$cpcfg = $config['captiveportal'][$cpzone];
655
	$vcpcfg = $config['voucher'][$cpzone];
656

    
657
	/* check for expired entries */
658
	$idletimeout = 0;
659
	$timeout = 0;
660
	if (!empty($cpcfg['timeout']) && is_numeric($cpcfg['timeout'])) {
661
		$timeout = $cpcfg['timeout'] * 60;
662
	}
663

    
664
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout'])) {
665
		$idletimeout = $cpcfg['idletimeout'] * 60;
666
	}
667

    
668
	/* Is there any job to do? */
669
	if (!$timeout && !$idletimeout && !isset($cpcfg['reauthenticate']) &&
670
	    !isset($cpcfg['radiussession_timeout']) && !isset($vcpcfg['enable'])) {
671
		return;
672
	}
673

    
674
	$radiussrvs = captiveportal_get_radius_servers();
675

    
676
	/* Read database */
677
	/* NOTE: while this can be simplified in non radius case keep as is for now */
678
	$cpdb = captiveportal_read_db();
679

    
680
	$unsetindexes = array();
681
	$voucher_needs_sync = false;
682
	/*
683
	 * Snapshot the time here to use for calculation to speed up the process.
684
	 * If something is missed next run will catch it!
685
	 */
686
	$pruning_time = time();
687
	foreach ($cpdb as $cpentry) {
688
		$stop_time = $pruning_time;
689

    
690
		$timedout = false;
691
		$term_cause = 1;
692
		if (empty($cpentry[11])) {
693
			$cpentry[11] = 'first';
694
		}
695
		$radiusservers = $radiussrvs[$cpentry[11]];
696

    
697
		/* hard timeout? */
698
		if ($timeout) {
699
			if (($pruning_time - $cpentry[0]) >= $timeout) {
700
				$timedout = true;
701
				$term_cause = 5; // Session-Timeout
702
			}
703
		}
704

    
705
		/* Session-Terminate-Time */
706
		if (!$timedout && !empty($cpentry[9])) {
707
			if ($pruning_time >= $cpentry[9]) {
708
				$timedout = true;
709
				$term_cause = 5; // Session-Timeout
710
			}
711
		}
712

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

    
729
		/* if vouchers are configured, activate session timeouts */
730
		if (!$timedout && isset($vcpcfg['enable']) && !empty($cpentry[7])) {
731
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
732
				$timedout = true;
733
				$term_cause = 5; // Session-Timeout
734
				$voucher_needs_sync = true;
735
			}
736
		}
737

    
738
		/* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
739
		if (!$timedout && isset($cpcfg['radiussession_timeout']) && !empty($cpentry[7])) {
740
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
741
				$timedout = true;
742
				$term_cause = 5; // Session-Timeout
743
			}
744
		}
745

    
746
		if ($timedout) {
747
			captiveportal_disconnect($cpentry, $radiusservers, $term_cause, $stop_time);
748
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
749
			$unsetindexes[] = $cpentry[5];
750
		}
751

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

    
812
			/* check this user against RADIUS again */
813
			if (isset($cpcfg['reauthenticate'])) {
814
				$auth_list = RADIUS_AUTHENTICATION($cpentry[4], // username
815
					base64_decode($cpentry[6]), // password
816
					$radiusservers,
817
					$cpentry[2], // clientip
818
					$cpentry[3], // clientmac
819
					$cpentry[1]); // ruleno
820
				if ($auth_list['auth_val'] == 3) {
821
					captiveportal_disconnect($cpentry, $radiusservers, 17);
822
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
823
					$unsetindexes[] = $cpentry[5];
824
				} else if ($auth_list['auth_val'] == 2) {
825
					captiveportal_reapply_attributes($cpentry, $auth_list);
826
				}
827
			}
828
		}
829
	}
830
	unset($cpdb);
831

    
832
	captiveportal_prune_old_automac();
833

    
834
	if ($voucher_needs_sync == true) {
835
		/* Trigger a sync of the vouchers on config */
836
		send_event("service sync vouchers");
837
	}
838

    
839
	/* write database */
840
	if (!empty($unsetindexes)) {
841
		captiveportal_remove_entries($unsetindexes);
842
	}
843
}
844

    
845
function captiveportal_prune_old_automac() {
846
	global $g, $config, $cpzone, $cpzoneid;
847

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

    
910
/* remove a single client according to the DB entry */
911
function captiveportal_disconnect($dbent, $radiusservers, $term_cause = 1, $stop_time = null) {
912
	global $g, $config, $cpzone, $cpzoneid;
913

    
914
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
915

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

    
937
	if (is_ipaddr($dbent[2])) {
938
		/* Delete client's ip entry from tables 1 and 2. */
939
		$clientsn = (is_ipaddrv6($dbent[2])) ? 128 : 32;
940
		pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XDEL, 1, $dbent[2], $clientsn, $dbent[3]);
941
		pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XDEL, 2, $dbent[2], $clientsn, $dbent[3]);
942
		/* XXX: Redundant?! Ensure all pf(4) states are killed. */
943
		$_gb = @pfSense_kill_states($dbent[2]);
944
		$_gb = @pfSense_kill_srcstates($dbent[2]);
945
	}
946

    
947
	/*
948
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
949
	* We could get an error if the pipe doesn't exist but everything should still be fine
950
	*/
951
	if (!empty($dbent[1])) {
952
		$_gb = @pfSense_pipe_action("pipe delete {$dbent[1]}");
953
		$_gb = @pfSense_pipe_action("pipe delete " . ($dbent[1]+1));
954

    
955
		/* Release the ruleno so it can be reallocated to new clients. */
956
		captiveportal_free_dn_ruleno($dbent[1]);
957
	}
958

    
959
	// XMLRPC Call over to the master Voucher node
960
	if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
961
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
962
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
963
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
964
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
965
		$remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time);
966
	}
967

    
968
}
969

    
970
/* remove a single client by sessionid */
971
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
972
	global $g, $config;
973

    
974
	$sessionid = SQLite3::escapeString($sessionid);
975
	$radiusservers = captiveportal_get_radius_servers();
976

    
977
	/* read database */
978
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
979

    
980
	/* find entry */
981
	if (!empty($result)) {
982
		captiveportal_write_db("DELETE FROM captiveportal WHERE sessionid = '{$sessionid}'");
983

    
984
		foreach ($result as $cpentry) {
985
			if (empty($cpentry[11])) {
986
				$cpentry[11] = 'first';
987
			}
988
			captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], $term_cause);
989
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
990
		}
991
		unset($result);
992
	}
993
}
994

    
995
/* send RADIUS acct stop for all current clients */
996
function captiveportal_radius_stop_all() {
997
	global $config, $cpzone;
998

    
999
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
1000
		return;
1001
	}
1002

    
1003
	$radiusservers = captiveportal_get_radius_servers();
1004
	if (!empty($radiusservers)) {
1005
		$cpdb = captiveportal_read_db();
1006
		foreach ($cpdb as $cpentry) {
1007
			if (empty($cpentry[11])) {
1008
				$cpentry[11] = 'first';
1009
			}
1010
			if (!empty($radiusservers[$cpentry[11]])) {
1011
				RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
1012
					$cpentry[4], // username
1013
					$cpentry[5], // sessionid
1014
					$cpentry[0], // start time
1015
					$radiusservers[$cpentry[11]],
1016
					$cpentry[2], // clientip
1017
					$cpentry[3], // clientmac
1018
					7); // Admin Reboot
1019
			}
1020
		}
1021
	}
1022
}
1023

    
1024
function captiveportal_passthrumac_configure_entry($macent, $pipeinrule = false) {
1025
	global $config, $g, $cpzone;
1026

    
1027
	$bwUp = 0;
1028
	if (!empty($macent['bw_up'])) {
1029
		$bwUp = $macent['bw_up'];
1030
	} else if (!empty($config['captiveportal'][$cpzone]['bwdefaultup'])) {
1031
		$bwUp = $config['captiveportal'][$cpzone]['bwdefaultup'];
1032
	}
1033
	$bwDown = 0;
1034
	if (!empty($macent['bw_down'])) {
1035
		$bwDown = $macent['bw_down'];
1036
	} else if (!empty($config['captiveportal'][$cpzone]['bwdefaultdn'])) {
1037
		$bwDown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
1038
	}
1039

    
1040
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1041

    
1042
	if ($macent['action'] == 'pass') {
1043
		$rules = "";
1044
		$pipeno = captiveportal_get_next_dn_ruleno();
1045

    
1046
		$pipeup = $pipeno;
1047
		if ($pipeinrule == true) {
1048
			$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16");
1049
		} else {
1050
			$rules .= "pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16\n";
1051
		}
1052

    
1053
		$pipedown = $pipeno + 1;
1054
		if ($pipeinrule == true) {
1055
			$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
1056
		} else {
1057
			$rules .= "pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16\n";
1058
		}
1059

    
1060
		$rules .= "add {$ruleno} pipe {$pipeup} ip from any to any MAC any {$macent['mac']}\n";
1061
		$ruleno++;
1062
		$rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC {$macent['mac']} any\n";
1063
	}
1064

    
1065
	return $rules;
1066
}
1067

    
1068
function captiveportal_passthrumac_delete_entry($macent) {
1069
	$rules = "";
1070

    
1071
	if ($macent['action'] == 'pass') {
1072
		$ruleno = captiveportal_get_ipfw_passthru_ruleno($macent['mac']);
1073

    
1074
		if (!$ruleno) {
1075
			return $rules;
1076
		}
1077

    
1078
		captiveportal_free_ipfw_ruleno($ruleno);
1079

    
1080
		$rules .= "delete {$ruleno}\n";
1081
		$rules .= "delete " . ++$ruleno . "\n";
1082

    
1083
		$pipeno = captiveportal_get_dn_passthru_ruleno($macent['mac']);
1084

    
1085
		if (!empty($pipeno)) {
1086
			captiveportal_free_dn_ruleno($pipeno);
1087
			$rules .= "pipe delete " . $pipeno . "\n";
1088
			$rules .= "pipe delete " . ++$pipeno . "\n";
1089
		}
1090
	}
1091

    
1092
	return $rules;
1093
}
1094

    
1095
function captiveportal_passthrumac_configure($filename = false, $startindex = 0, $stopindex = 0) {
1096
	global $config, $g, $cpzone;
1097

    
1098
	$rules = "";
1099

    
1100
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1101
		if ($stopindex > 0) {
1102
			$fd = fopen($filename, "w");
1103
			for ($idx = $startindex; $idx <= $stopindex; $idx++) {
1104
				if (isset($config['captiveportal'][$cpzone]['passthrumac'][$idx])) {
1105
					$rules = captiveportal_passthrumac_configure_entry($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1106
					fwrite($fd, $rules);
1107
				}
1108
			}
1109
			fclose($fd);
1110

    
1111
			return;
1112
		} else {
1113
			$nentries = count($config['captiveportal'][$cpzone]['passthrumac']);
1114
			if ($nentries > 2000) {
1115
				$nloops = $nentries / 1000;
1116
				$remainder= $nentries % 1000;
1117
				for ($i = 0; $i < $nloops; $i++) {
1118
					mwexec_bg("/usr/local/sbin/fcgicli -f /etc/rc.captiveportal_configure_mac -d \"cpzone={$cpzone}&startidx=" . ($i * 1000) . "&stopidx=" . ((($i+1) * 1000) - 1) . "\"");
1119
				}
1120
				if ($remainder > 0) {
1121
					mwexec_bg("/usr/local/sbin/fcgicli -f /etc/rc.captiveportal_configure_mac -d \"cpzone={$cpzone}&startidx=" . ($i * 1000) . "&stopidx=" . (($i* 1000) + $remainder) ."\"");
1122
				}
1123
			} else {
1124
				foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1125
					$rules .= captiveportal_passthrumac_configure_entry($macent, true);
1126
				}
1127
			}
1128
		}
1129
	}
1130

    
1131
	return $rules;
1132
}
1133

    
1134
function captiveportal_passthrumac_findbyname($username) {
1135
	global $config, $cpzone;
1136

    
1137
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1138
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1139
			if ($macent['username'] == $username) {
1140
				return $macent;
1141
			}
1142
		}
1143
	}
1144
	return NULL;
1145
}
1146

    
1147
/*
1148
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1149
 */
1150
function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
1151
	global $g;
1152

    
1153
	/*  Instead of copying this entire function for something
1154
	 *  easy such as hostname vs ip address add this check
1155
	 */
1156
	if ($ishostname === true) {
1157
		if (!platform_booting()) {
1158
			$ipaddress = gethostbyname($ipent['hostname']);
1159
			if (!is_ipaddr($ipaddress)) {
1160
				return;
1161
			}
1162
		} else {
1163
			$ipaddress = "";
1164
		}
1165
	} else {
1166
		$ipaddress = $ipent['ip'];
1167
	}
1168

    
1169
	$rules = "";
1170
	$cp_filterdns_conf = "";
1171
	$enBwup = 0;
1172
	if (!empty($ipent['bw_up'])) {
1173
		$enBwup = intval($ipent['bw_up']);
1174
	} else if (!empty($config['captiveportal'][$cpzone]['bwdefaultup'])) {
1175
		$enBwup = $config['captiveportal'][$cpzone]['bwdefaultup'];
1176
	}
1177
	$enBwdown = 0;
1178
	if (!empty($ipent['bw_down'])) {
1179
		$enBwdown = intval($ipent['bw_down']);
1180
	} else if (!empty($config['captiveportal'][$cpzone]['bwdefaultdn'])) {
1181
		$enBwdown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
1182
	}
1183

    
1184
	$pipeno = captiveportal_get_next_dn_ruleno();
1185
	$_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$enBwup}Kbit/s queue 100 buckets 16");
1186
	$pipedown = $pipeno + 1;
1187
	$_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16");
1188
	if ($ishostname === true) {
1189
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
1190
		$cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
1191
		if (!is_ipaddr($ipaddress)) {
1192
			return array("", $cp_filterdns_conf);
1193
		}
1194
	}
1195
	$subnet = "";
1196
	if (!empty($ipent['sn'])) {
1197
		$subnet = "/{$ipent['sn']}";
1198
	}
1199
	$rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
1200
	$rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
1201

    
1202
	if ($ishostname === true) {
1203
		return array($rules, $cp_filterdns_conf);
1204
	} else {
1205
		return $rules;
1206
	}
1207
}
1208

    
1209
function captiveportal_allowedhostname_configure() {
1210
	global $config, $g, $cpzone, $cpzoneid;
1211

    
1212
	$rules = "";
1213
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1214
		$rules = "\n# captiveportal_allowedhostname_configure()\n";
1215
		$cp_filterdns_conf = "";
1216
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) {
1217
			$tmprules = captiveportal_allowedip_configure_entry($hostnameent, true);
1218
			$rules .= $tmprules[0];
1219
			$cp_filterdns_conf .= $tmprules[1];
1220
		}
1221
		$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1222
		@file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1223
		unset($cp_filterdns_conf);
1224
		if (isvalidpid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid")) {
1225
			sigkillbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid", "HUP");
1226
		} else {
1227
			mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzoneid} -d 1");
1228
		}
1229
	} else {
1230
		killbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1231
		@unlink("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1232
	}
1233

    
1234
	return $rules;
1235
}
1236

    
1237
function captiveportal_allowedip_configure() {
1238
	global $config, $g, $cpzone;
1239

    
1240
	$rules = "";
1241
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1242
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) {
1243
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1244
		}
1245
	}
1246

    
1247
	return $rules;
1248
}
1249

    
1250
/* get last activity timestamp given client IP address */
1251
function captiveportal_get_last_activity($ip, $mac = NULL, $table = 1) {
1252
	global $cpzoneid;
1253

    
1254
	$ipfwoutput = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, $table, $ip, $mac);
1255
	/* Reading only from one of the tables is enough of approximation. */
1256
	if (is_array($ipfwoutput)) {
1257
		/* Workaround for #46652 */
1258
		if ($ipfwoutput['packets'] > 0) {
1259
			return $ipfwoutput['timestamp'];
1260
		} else {
1261
			return 0;
1262
		}
1263
	}
1264

    
1265
	return 0;
1266
}
1267

    
1268
function captiveportal_init_radius_servers() {
1269
	global $config, $g, $cpzone;
1270

    
1271
	/* generate radius server database */
1272
	if ($config['captiveportal'][$cpzone]['radiusip'] &&
1273
	    (!isset($config['captiveportal'][$cpzone]['auth_method']) || $config['captiveportal'][$cpzone]['auth_method'] == "radius")) {
1274
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1275
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1276
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1277
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1278

    
1279
		if ($config['captiveportal'][$cpzone]['radiusport']) {
1280
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1281
		} else {
1282
			$radiusport = 1812;
1283
		}
1284
		if ($config['captiveportal'][$cpzone]['radiusacctport']) {
1285
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1286
		} else {
1287
			$radiusacctport = 1813;
1288
		}
1289
		if ($config['captiveportal'][$cpzone]['radiusport2']) {
1290
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1291
		} else {
1292
			$radiusport2 = 1812;
1293
		}
1294
		if ($config['captiveportal'][$cpzone]['radiusport3']) {
1295
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1296
		} else {
1297
			$radiusport3 = 1812;
1298
		}
1299
		if ($config['captiveportal'][$cpzone]['radiusport4']) {
1300
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1301
		} else {
1302
			$radiusport4 = 1812;
1303
		}
1304

    
1305
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1306
		$radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
1307
		$radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
1308
		$radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
1309

    
1310
		$cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX);
1311
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
1312
		if (!$fd) {
1313
			captiveportal_syslog("Error: cannot open RADIUS DB file in captiveportal_configure().\n");
1314
			unlock($cprdsrvlck);
1315
			return 1;
1316
		}
1317
		if (isset($radiusip)) {
1318
			fwrite($fd, $radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
1319
		}
1320
		if (isset($radiusip2)) {
1321
			fwrite($fd, "\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
1322
		}
1323
		if (isset($radiusip3)) {
1324
			fwrite($fd, "\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
1325
		}
1326
		if (isset($radiusip4)) {
1327
			fwrite($fd, "\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
1328
		}
1329

    
1330
		fclose($fd);
1331
		unlock($cprdsrvlck);
1332
	}
1333
}
1334

    
1335
/* read RADIUS servers into array */
1336
function captiveportal_get_radius_servers() {
1337
	global $g, $cpzone;
1338

    
1339
	$cprdsrvlck = lock("captiveportalradius{$cpzone}");
1340
	if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
1341
		$radiusservers = array();
1342
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db",
1343
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1344
		if ($cpradiusdb) {
1345
			foreach ($cpradiusdb as $cpradiusentry) {
1346
				$line = trim($cpradiusentry);
1347
				if ($line) {
1348
					$radsrv = array();
1349
					list($radsrv['ipaddr'], $radsrv['port'], $radsrv['acctport'], $radsrv['key'], $context) = explode(",", $line);
1350
				}
1351
				if (empty($context)) {
1352
					if (!is_array($radiusservers['first'])) {
1353
						$radiusservers['first'] = array();
1354
					}
1355
					$radiusservers['first'] = $radsrv;
1356
				} else {
1357
					if (!is_array($radiusservers[$context])) {
1358
						$radiusservers[$context] = array();
1359
					}
1360
					$radiusservers[$context][] = $radsrv;
1361
				}
1362
			}
1363
		}
1364
		unlock($cprdsrvlck);
1365
		return $radiusservers;
1366
	}
1367

    
1368
	unlock($cprdsrvlck);
1369
	return false;
1370
}
1371

    
1372
/* log successful captive portal authentication to syslog */
1373
/* part of this code from php.net */
1374
function captiveportal_logportalauth($user, $mac, $ip, $status, $message = null) {
1375
	// Log it
1376
	if (!$message) {
1377
		$message = "{$status}: {$user}, {$mac}, {$ip}";
1378
	} else {
1379
		$message = trim($message);
1380
		$message = "{$status}: {$user}, {$mac}, {$ip}, {$message}";
1381
	}
1382
	captiveportal_syslog($message);
1383
}
1384

    
1385
/* log simple messages to syslog */
1386
function captiveportal_syslog($message) {
1387
	global $cpzone;
1388

    
1389
	$message = trim($message);
1390
	$message = "Zone: {$cpzone} - {$message}";
1391
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1392
	// Log it
1393
	syslog(LOG_INFO, $message);
1394
	closelog();
1395
}
1396

    
1397
function radius($username, $password, $clientip, $clientmac, $type, $radiusctx = null) {
1398
	global $g, $config, $cpzoneid;
1399

    
1400
	$pipeno = captiveportal_get_next_dn_ruleno();
1401

    
1402
	/* If the pool is empty, return appropriate message and fail authentication */
1403
	if (empty($pipeno)) {
1404
		$auth_list = array();
1405
		$auth_list['auth_val'] = 1;
1406
		$auth_list['error'] = "System reached maximum login capacity";
1407
		return $auth_list;
1408
	}
1409

    
1410
	$radiusservers = captiveportal_get_radius_servers();
1411

    
1412
	if (is_null($radiusctx)) {
1413
		$radiusctx = 'first';
1414
	}
1415

    
1416
	$auth_list = RADIUS_AUTHENTICATION($username,
1417
		$password,
1418
		$radiusservers[$radiusctx],
1419
		$clientip,
1420
		$clientmac,
1421
		$pipeno);
1422

    
1423
	if ($auth_list['auth_val'] == 2) {
1424
		captiveportal_logportalauth($username, $clientmac, $clientip, $type);
1425
		$sessionid = portal_allow($clientip,
1426
			$clientmac,
1427
			$username,
1428
			$password,
1429
			$auth_list,
1430
			$pipeno,
1431
			$radiusctx);
1432
	} else {
1433
		captiveportal_free_dn_ruleno($pipeno);
1434
	}
1435

    
1436
	return $auth_list;
1437
}
1438

    
1439
function captiveportal_opendb() {
1440
	global $g, $config, $cpzone, $cpzoneid;
1441

    
1442
	$db_path = "{$g['vardb_path']}/captiveportal{$cpzone}.db";
1443
	$createquery = "CREATE TABLE IF NOT EXISTS captiveportal (" .
1444
				"allow_time INTEGER, pipeno INTEGER, ip TEXT, mac TEXT, username TEXT, " .
1445
				"sessionid TEXT, bpassword TEXT, session_timeout INTEGER, idle_timeout INTEGER, " .
1446
				"session_terminate_time INTEGER, interim_interval INTEGER, radiusctx TEXT); " .
1447
			"CREATE UNIQUE INDEX IF NOT EXISTS idx_active ON captiveportal (sessionid, username); " .
1448
			"CREATE INDEX IF NOT EXISTS user ON captiveportal (username); " .
1449
			"CREATE INDEX IF NOT EXISTS ip ON captiveportal (ip); " .
1450
			"CREATE INDEX IF NOT EXISTS starttime ON captiveportal (allow_time)";
1451

    
1452
	try {
1453
		$DB = new SQLite3($db_path);
1454
		$DB->busyTimeout(60000);
1455
	} catch (Exception $e) {
1456
		captiveportal_syslog("Could not open {$db_path} as an sqlite database for {$cpzone}. Error message: " . $e->getMessage() . " -- Trying again.");
1457
		unlink_if_exists($db_path);
1458
		try {
1459
			$DB = new SQLite3($db_path);
1460
			$DB->busyTimeout(60000);
1461
		} catch (Exception $e) {
1462
			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.");
1463
			return;
1464
		}
1465
	}
1466

    
1467
	if (!$DB) {
1468
		captiveportal_syslog("Could not open {$db_path} as an sqlite database for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Trying again.");
1469
		unlink_if_exists($db_path);
1470
		$DB = new SQLite3($db_path);
1471
		$DB->busyTimeout(60000);
1472
		if (!$DB) {
1473
			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.");
1474
			return;
1475
		}
1476
	}
1477

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

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

    
1507
	return $DB;
1508
}
1509

    
1510
/* read captive portal DB into array */
1511
function captiveportal_read_db($query = "") {
1512
	$cpdb = array();
1513

    
1514
	$DB = captiveportal_opendb();
1515
	if ($DB) {
1516
		$response = $DB->query("SELECT * FROM captiveportal {$query}");
1517
		if ($response != FALSE) {
1518
			while ($row = $response->fetchArray()) {
1519
				$cpdb[] = $row;
1520
			}
1521
		}
1522
		$DB->close();
1523
	}
1524

    
1525
	return $cpdb;
1526
}
1527

    
1528
function captiveportal_remove_entries($remove) {
1529

    
1530
	if (!is_array($remove) || empty($remove)) {
1531
		return;
1532
	}
1533

    
1534
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1535
	foreach ($remove as $idx => $unindex) {
1536
		$query .= "'{$unindex}'";
1537
		if ($idx < (count($remove) - 1)) {
1538
			$query .= ",";
1539
		}
1540
	}
1541
	$query .= ")";
1542
	captiveportal_write_db($query);
1543
}
1544

    
1545
/* write captive portal DB */
1546
function captiveportal_write_db($queries) {
1547
	global $g;
1548

    
1549
	if (is_array($queries)) {
1550
		$query = implode(";", $queries);
1551
	} else {
1552
		$query = $queries;
1553
	}
1554

    
1555
	$DB = captiveportal_opendb();
1556
	if ($DB) {
1557
		$DB->exec("BEGIN TRANSACTION");
1558
		$result = $DB->exec($query);
1559
		if (!$result) {
1560
			captiveportal_syslog("Trying to modify DB returned error: {$DB->lastErrorMsg()}");
1561
		} else {
1562
			$DB->exec("END TRANSACTION");
1563
		}
1564
		$DB->close();
1565
		return $result;
1566
	} else {
1567
		return true;
1568
	}
1569
}
1570

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

    
1574
	$cpcfg = $config['captiveportal'][$cpzone];
1575

    
1576
	if (!is_dir($g['captiveportal_element_path'])) {
1577
		@mkdir($g['captiveportal_element_path']);
1578
	}
1579

    
1580
	if (is_array($cpcfg['element'])) {
1581
		conf_mount_rw();
1582
		foreach ($cpcfg['element'] as $data) {
1583
			/* Do not attempt to decode or write out empty files. */
1584
			if (empty($data['content']) || empty(base64_decode($data['content']))) {
1585
				unlink_if_exists("{$g['captiveportal_element_path']}/{$data['name']}");
1586
				touch("{$g['captiveportal_element_path']}/{$data['name']}");
1587
			} elseif (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1588
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1589
				return 1;
1590
			}
1591
			if (!file_exists("{$g['captiveportal_path']}/{$data['name']}")) {
1592
				@symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1593
			}
1594
		}
1595
		conf_mount_ro();
1596
	}
1597

    
1598
	return 0;
1599
}
1600

    
1601
function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
1602
	global $cpzone;
1603

    
1604
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1605
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1606
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1607
		$ridx = $rulenos_start;
1608
		while ($ridx < $rulenos_range_max) {
1609
			if ($rules[$ridx] == $cpzone) {
1610
				$rules[$ridx] = false;
1611
				$ridx++;
1612
				$rules[$ridx] = false;
1613
				$ridx++;
1614
			} else {
1615
				$ridx += 2;
1616
			}
1617
		}
1618
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1619
		unset($rules);
1620
	}
1621
	unlock($cpruleslck);
1622
}
1623

    
1624
function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1625
	global $config, $g, $cpzone;
1626

    
1627
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1628
	$ruleno = 0;
1629
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1630
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1631
		$ridx = $rulenos_start;
1632
		while ($ridx < $rulenos_range_max) {
1633
			if (empty($rules[$ridx])) {
1634
				$ruleno = $ridx;
1635
				$rules[$ridx] = $cpzone;
1636
				$ridx++;
1637
				$rules[$ridx] = $cpzone;
1638
				break;
1639
			} else {
1640
				$ridx += 2;
1641
			}
1642
		}
1643
	} else {
1644
		$rules = array_pad(array(), $rulenos_range_max, false);
1645
		$ruleno = $rulenos_start;
1646
		$rules[$rulenos_start] = $cpzone;
1647
		$rulenos_start++;
1648
		$rules[$rulenos_start] = $cpzone;
1649
	}
1650
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1651
	unlock($cpruleslck);
1652
	unset($rules);
1653

    
1654
	return $ruleno;
1655
}
1656

    
1657
function captiveportal_free_dn_ruleno($ruleno) {
1658
	global $config, $g;
1659

    
1660
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1661
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1662
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1663
		$rules[$ruleno] = false;
1664
		$ruleno++;
1665
		$rules[$ruleno] = false;
1666
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1667
		unset($rules);
1668
	}
1669
	unlock($cpruleslck);
1670
}
1671

    
1672
function captiveportal_get_dn_passthru_ruleno($value) {
1673
	global $config, $g, $cpzone, $cpzoneid;
1674

    
1675
	$cpcfg = $config['captiveportal'][$cpzone];
1676
	if (!isset($cpcfg['enable'])) {
1677
		return NULL;
1678
	}
1679

    
1680
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1681
	$ruleno = NULL;
1682
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1683
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1684
		unset($output);
1685
		$_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);
1686
		$ruleno = intval($output[0]);
1687
		if (!$rules[$ruleno]) {
1688
			$ruleno = NULL;
1689
		}
1690
		unset($rules);
1691
	}
1692
	unlock($cpruleslck);
1693

    
1694
	return $ruleno;
1695
}
1696

    
1697
/*
1698
 * This function will calculate the lowest free firewall ruleno
1699
 * within the range specified based on the actual logged on users
1700
 *
1701
 */
1702
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
1703
	global $config, $g, $cpzone;
1704

    
1705
	$cpcfg = $config['captiveportal'][$cpzone];
1706
	if (!isset($cpcfg['enable'])) {
1707
		return NULL;
1708
	}
1709

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

    
1741
	return $ruleno;
1742
}
1743

    
1744
function captiveportal_free_ipfw_ruleno($ruleno) {
1745
	global $config, $g, $cpzone;
1746

    
1747
	$cpcfg = $config['captiveportal'][$cpzone];
1748
	if (!isset($cpcfg['enable'])) {
1749
		return NULL;
1750
	}
1751

    
1752
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1753
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1754
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1755
		$rules[$ruleno] = false;
1756
		$ruleno++;
1757
		$rules[$ruleno] = false;
1758
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1759
		unset($rules);
1760
	}
1761
	unlock($cpruleslck);
1762
}
1763

    
1764
function captiveportal_get_ipfw_passthru_ruleno($value) {
1765
	global $config, $g, $cpzone, $cpzoneid;
1766

    
1767
	$cpcfg = $config['captiveportal'][$cpzone];
1768
	if (!isset($cpcfg['enable'])) {
1769
		return NULL;
1770
	}
1771

    
1772
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1773
	$ruleno = NULL;
1774
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1775
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1776
		unset($output);
1777
		$_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);
1778
		$ruleno = intval($output[0]);
1779
		if (!$rules[$ruleno]) {
1780
			$ruleno = NULL;
1781
		}
1782
		unset($rules);
1783
	}
1784
	unlock($cpruleslck);
1785

    
1786
	return $ruleno;
1787
}
1788

    
1789
/**
1790
 * This function will calculate the traffic produced by a client
1791
 * based on its firewall rule
1792
 *
1793
 * Point of view: NAS
1794
 *
1795
 * Input means: from the client
1796
 * Output means: to the client
1797
 *
1798
 */
1799

    
1800
function getVolume($ip, $mac = NULL) {
1801
	global $config, $cpzone, $cpzoneid;
1802

    
1803
	$reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
1804
	$volume = array();
1805
	// Initialize vars properly, since we don't want NULL vars
1806
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1807

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

    
1820
	$ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 2, $ip, $mac);
1821
	if (is_array($ipfw)) {
1822
		if ($reverse) {
1823
			$volume['input_pkts'] = $ipfw['packets'];
1824
			$volume['input_bytes'] = $ipfw['bytes'];
1825
		}
1826
		else {
1827
			$volume['output_pkts'] = $ipfw['packets'];
1828
			$volume['output_bytes'] = $ipfw['bytes'];
1829
		}
1830
	}
1831

    
1832
	return $volume;
1833
}
1834

    
1835
/**
1836
 * Get the NAS-IP-Address based on the current wan address
1837
 *
1838
 * Use functions in interfaces.inc to find this out
1839
 *
1840
 */
1841

    
1842
function getNasIP() {
1843
	global $config, $cpzone;
1844

    
1845
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1846
			$nasIp = get_interface_ip();
1847
	} else {
1848
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1849
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1850
		} else {
1851
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1852
		}
1853
	}
1854

    
1855
	if (!is_ipaddr($nasIp)) {
1856
		$nasIp = "0.0.0.0";
1857
	}
1858

    
1859
	return $nasIp;
1860
}
1861

    
1862
function portal_ip_from_client_ip($cliip) {
1863
	global $config, $cpzone;
1864

    
1865
	$isipv6 = is_ipaddrv6($cliip);
1866
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1867
	foreach ($interfaces as $cpif) {
1868
		if ($isipv6) {
1869
			$ip = get_interface_ipv6($cpif);
1870
			$sn = get_interface_subnetv6($cpif);
1871
		} else {
1872
			$ip = get_interface_ip($cpif);
1873
			$sn = get_interface_subnet($cpif);
1874
		}
1875
		if (ip_in_subnet($cliip, "{$ip}/{$sn}")) {
1876
			return $ip;
1877
		}
1878
	}
1879

    
1880
	$inet = ($isipv6) ? '-inet6' : '-inet';
1881
	$iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1882
	$iface = trim($iface, "\n");
1883
	if (!empty($iface)) {
1884
		$ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
1885
		if (is_ipaddr($ip)) {
1886
			return $ip;
1887
		}
1888
	}
1889

    
1890
	// doesn't match up to any particular interface
1891
	// so let's set the portal IP to what PHP says
1892
	// the server IP issuing the request is.
1893
	// allows same behavior as 1.2.x where IP isn't
1894
	// in the subnet of any CP interface (static routes, etc.)
1895
	// rather than forcing to DNS hostname resolution
1896
	$ip = $_SERVER['SERVER_ADDR'];
1897
	if (is_ipaddr($ip)) {
1898
		return $ip;
1899
	}
1900

    
1901
	return false;
1902
}
1903

    
1904
function portal_hostname_from_client_ip($cliip) {
1905
	global $config, $cpzone;
1906

    
1907
	$cpcfg = $config['captiveportal'][$cpzone];
1908

    
1909
	if (isset($cpcfg['httpslogin'])) {
1910
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 8001);
1911
		$ourhostname = $cpcfg['httpsname'];
1912

    
1913
		if ($listenporthttps != 443) {
1914
			$ourhostname .= ":" . $listenporthttps;
1915
		}
1916
	} else {
1917
		$listenporthttp = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : ($cpcfg['zoneid'] + 8000);
1918
		$ifip = portal_ip_from_client_ip($cliip);
1919
		if (!$ifip) {
1920
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1921
		} else {
1922
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1923
		}
1924

    
1925
		if ($listenporthttp != 80) {
1926
			$ourhostname .= ":" . $listenporthttp;
1927
		}
1928
	}
1929

    
1930
	return $ourhostname;
1931
}
1932

    
1933
/* functions move from index.php */
1934

    
1935
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1936
	global $g, $config, $cpzone;
1937

    
1938
	/* Get captive portal layout */
1939
	if ($type == "redir") {
1940
		header("Location: {$redirurl}");
1941
		return;
1942
	} else if ($type == "login") {
1943
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1944
	} else {
1945
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1946
	}
1947

    
1948
	$cpcfg = $config['captiveportal'][$cpzone];
1949

    
1950
	/* substitute the PORTAL_REDIRURL variable */
1951
	if ($cpcfg['preauthurl']) {
1952
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1953
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1954
	}
1955

    
1956
	/* substitute other variables */
1957
	$ourhostname = portal_hostname_from_client_ip($clientip);
1958
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1959
	$htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
1960
	$htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
1961

    
1962
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1963
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1964
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1965
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1966
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1967

    
1968
	// Special handling case for captive portal master page so that it can be ran
1969
	// through the PHP interpreter using the include method above.  We convert the
1970
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1971
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1972
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1973
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1974
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1975
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1976
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1977
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1978

    
1979
	echo $htmltext;
1980
}
1981

    
1982
function portal_mac_radius($clientmac, $clientip) {
1983
	global $config, $cpzone;
1984

    
1985
	$radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1986

    
1987
	/* authentication against the radius server */
1988
	$username = mac_format($clientmac);
1989
	$auth_list = radius($username, $radmac_secret, $clientip, $clientmac, "MACHINE LOGIN");
1990
	if ($auth_list['auth_val'] == 2) {
1991
		return TRUE;
1992
	}
1993

    
1994
	if (!empty($auth_list['url_redirection'])) {
1995
		portal_reply_page($auth_list['url_redirection'], "redir");
1996
	}
1997

    
1998
	return FALSE;
1999
}
2000

    
2001
function captiveportal_reapply_attributes($cpentry, $attributes) {
2002
	global $config, $cpzone, $g;
2003

    
2004
	if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
2005
		$dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
2006
		$dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
2007
	} else {
2008
		$dwfaultbw_up = $dwfaultbw_down = 0;
2009
	}
2010
	$bw_up = !empty($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
2011
	$bw_down = !empty($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
2012
	$bw_up_pipeno = $cpentry[1];
2013
	$bw_down_pipeno = $cpentry[1]+1;
2014

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

    
2019
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
2020
}
2021

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

    
2025
	// Ensure we create an array if we are missing attributes
2026
	if (!is_array($attributes)) {
2027
		$attributes = array();
2028
	}
2029

    
2030
	unset($sessionid);
2031

    
2032
	/* Do not allow concurrent login execution. */
2033
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
2034

    
2035
	if ($attributes['voucher']) {
2036
		$remaining_time = $attributes['session_timeout'];
2037
	// Set RADIUS-Attribute to Voucher to prevent ReAuth-Reqeuest for Vouchers Bug: #2155
2038
		$radiusctx="voucher";
2039
	}
2040

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

    
2087
	/* read in client database */
2088
	$query = "WHERE ip = '{$clientip}'";
2089
	$tmpusername = strtolower($username);
2090
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) {
2091
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
2092
	}
2093
	$cpdb = captiveportal_read_db($query);
2094

    
2095
	/* Snapshot the timestamp */
2096
	$allow_time = time();
2097
	$radiusservers = captiveportal_get_radius_servers();
2098
	$unsetindexes = array();
2099
	if (is_null($radiusctx)) {
2100
		$radiusctx = 'first';
2101
	}
2102

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

    
2124
			/* This user was already logged in so we disconnect the old one */
2125
			captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], 13);
2126
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - TERMINATING OLD SESSION");
2127
			$unsetindexes[] = $cpentry[5];
2128
			break;
2129
		} elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
2130
			/* on the same username */
2131
			if (strcasecmp($cpentry[4], $username) == 0) {
2132
				/* This user was already logged in so we disconnect the old one */
2133
				captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], 13);
2134
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - TERMINATING OLD SESSION");
2135
				$unsetindexes[] = $cpentry[5];
2136
				break;
2137
			}
2138
		}
2139
	}
2140
	unset($cpdb);
2141

    
2142
	if (!empty($unsetindexes)) {
2143
		captiveportal_remove_entries($unsetindexes);
2144
	}
2145

    
2146
	if ($attributes['voucher'] && $remaining_time <= 0) {
2147
		return 0;       // voucher already used and no time left
2148
	}
2149

    
2150
	if (!isset($sessionid)) {
2151
		/* generate unique session ID */
2152
		$tod = gettimeofday();
2153
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
2154

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

    
2192
			/* if the pool is empty, return appropriate message and exit */
2193
			if (is_null($pipeno)) {
2194
				portal_reply_page($redirurl, "error", "System reached maximum login capacity");
2195
				log_error("Zone: {$cpzone} - WARNING!  Captive portal has reached maximum login capacity");
2196
				unlock($cpdblck);
2197
				return;
2198
			}
2199

    
2200
			if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
2201
				$dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
2202
				$dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
2203
			} else {
2204
				$dwfaultbw_up = $dwfaultbw_down = 0;
2205
			}
2206
			$bw_up = !empty($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
2207
			$bw_down = !empty($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
2208

    
2209
			$bw_up_pipeno = $pipeno;
2210
			$bw_down_pipeno = $pipeno + 1;
2211
			//$bw_up /= 1000; // Scale to Kbit/s
2212
			$_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
2213
			$_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
2214

    
2215
			$clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
2216
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
2217
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
2218
			} else {
2219
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
2220
			}
2221

    
2222
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
2223
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
2224
			} else {
2225
				$_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
2226
			}
2227

    
2228
			if ($attributes['voucher']) {
2229
				$attributes['session_timeout'] = $remaining_time;
2230
			}
2231

    
2232
			/* handle empty attributes */
2233
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
2234
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
2235
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
2236
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
2237

    
2238
			/* escape username */
2239
			$safe_username = SQLite3::escapeString($username);
2240

    
2241
			/* encode password in Base64 just in case it contains commas */
2242
			$bpassword = base64_encode($password);
2243
			$insertquery = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, radiusctx) ";
2244
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
2245
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, '{$radiusctx}')";
2246

    
2247
			/* store information to database */
2248
			captiveportal_write_db($insertquery);
2249
			unlock($cpdblck);
2250
			unset($insertquery, $bpassword);
2251

    
2252
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
2253
				$acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
2254
				if ($acct_val == 1) {
2255
					captiveportal_logportalauth($username, $clientmac, $clientip, $type, "RADIUS ACCOUNTING FAILED");
2256
				}
2257
			}
2258
		}
2259
	} else {
2260
		/* NOTE: #3062-11 If the pipeno has been allocated free it to not DoS the CP and maintain proper operation as in radius() case */
2261
		if (!is_null($pipeno)) {
2262
			captiveportal_free_dn_ruleno($pipeno);
2263
		}
2264

    
2265
		unlock($cpdblck);
2266
	}
2267

    
2268
	if ($writecfg == true) {
2269
		write_config();
2270
	}
2271

    
2272
	/* redirect user to desired destination */
2273
	if (!empty($attributes['url_redirection'])) {
2274
		$my_redirurl = $attributes['url_redirection'];
2275
	} else if (!empty($redirurl)) {
2276
		$my_redirurl = $redirurl;
2277
	} else if (!empty($config['captiveportal'][$cpzone]['redirurl'])) {
2278
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
2279
	}
2280

    
2281
	if (isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
2282
		$ourhostname = portal_hostname_from_client_ip($clientip);
2283
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
2284
		$logouturl = "{$protocol}{$ourhostname}/";
2285

    
2286
		if (isset($attributes['reply_message'])) {
2287
			$message = $attributes['reply_message'];
2288
		} else {
2289
			$message = 0;
2290
		}
2291

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

    
2294
	} else {
2295
		portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
2296
	}
2297

    
2298
	return $sessionid;
2299
}
2300

    
2301

    
2302
/*
2303
 * Used for when pass-through credits are enabled.
2304
 * Returns true when there was at least one free login to deduct for the MAC.
2305
 * Expired entries are removed as they are seen.
2306
 * Active entries are updated according to the configuration.
2307
 */
2308
function portal_consume_passthrough_credit($clientmac) {
2309
	global $config, $cpzone;
2310

    
2311
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count'])) {
2312
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
2313
	} else {
2314
		return false;
2315
	}
2316

    
2317
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout'])) {
2318
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2319
	} else {
2320
		return false;
2321
	}
2322

    
2323
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac) {
2324
		return false;
2325
	}
2326

    
2327
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2328

    
2329
	/*
2330
	 * Read database of used MACs.  Lines are a comma-separated list
2331
	 * of the time, MAC, then the count of pass-through credits remaining.
2332
	 */
2333
	$usedmacs = captiveportal_read_usedmacs_db();
2334

    
2335
	$currenttime = time();
2336
	$found = false;
2337
	foreach ($usedmacs as $key => $usedmac) {
2338
		$usedmac = explode(",", $usedmac);
2339

    
2340
		if ($usedmac[1] == $clientmac) {
2341
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2342
				if ($usedmac[2] < 1) {
2343
					if ($updatetimeouts) {
2344
						$usedmac[0] = $currenttime;
2345
						unset($usedmacs[$key]);
2346
						$usedmacs[] = implode(",", $usedmac);
2347
						captiveportal_write_usedmacs_db($usedmacs);
2348
					}
2349

    
2350
					return false;
2351
				} else {
2352
					$usedmac[2] -= 1;
2353
					$usedmacs[$key] = implode(",", $usedmac);
2354
				}
2355

    
2356
				$found = true;
2357
			} else {
2358
				unset($usedmacs[$key]);
2359
			}
2360

    
2361
			break;
2362
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime) {
2363
			unset($usedmacs[$key]);
2364
		}
2365
	}
2366

    
2367
	if (!$found) {
2368
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2369
		$usedmacs[] = implode(",", $usedmac);
2370
	}
2371

    
2372
	captiveportal_write_usedmacs_db($usedmacs);
2373
	return true;
2374
}
2375

    
2376
function captiveportal_read_usedmacs_db() {
2377
	global $g, $cpzone;
2378

    
2379
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2380
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2381
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2382
		if (!$usedmacs) {
2383
			$usedmacs = array();
2384
		}
2385
	} else {
2386
		$usedmacs = array();
2387
	}
2388

    
2389
	unlock($cpumaclck);
2390
	return $usedmacs;
2391
}
2392

    
2393
function captiveportal_write_usedmacs_db($usedmacs) {
2394
	global $g, $cpzone;
2395

    
2396
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2397
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2398
	unlock($cpumaclck);
2399
}
2400

    
2401
function captiveportal_blocked_mac($mac) {
2402
	global $config, $g, $cpzone;
2403

    
2404
	if (empty($mac) || !is_macaddr($mac)) {
2405
		return false;
2406
	}
2407

    
2408
	if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
2409
		return false;
2410
	}
2411

    
2412
	foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac) {
2413
		if (($passthrumac['action'] == 'block') &&
2414
		    ($passthrumac['mac'] == strtolower($mac))) {
2415
			return true;
2416
		}
2417
	}
2418

    
2419
	return false;
2420

    
2421
}
2422

    
2423
function captiveportal_send_server_accounting($off = false) {
2424
	global $cpzone, $config;
2425

    
2426
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
2427
		return;
2428
	}
2429
	if ($off) {
2430
		$racct = new Auth_RADIUS_Acct_Off;
2431
	} else {
2432
		$racct = new Auth_RADIUS_Acct_On;
2433
	}
2434
	$radiusservers = captiveportal_get_radius_servers();
2435
	if (empty($radiusservers)) {
2436
		return;
2437
	}
2438
	foreach ($radiusservers['first'] as $radsrv) {
2439
		// Add a new server to our instance
2440
		$racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
2441
	}
2442
	if (PEAR::isError($racct->start())) {
2443
		$retvalue['acct_val'] = 1;
2444
		$retvalue['error'] = $racct->getMessage();
2445

    
2446
		// If we encounter an error immediately stop this function and go back
2447
		$racct->close();
2448
		return $retvalue;
2449
	}
2450
	// Send request
2451
	$result = $racct->send();
2452
	// Evaluation of the response
2453
	// 5 -> Accounting-Response
2454
	// See RFC2866 for this.
2455
	if (PEAR::isError($result)) {
2456
		$retvalue['acct_val'] = 1;
2457
		$retvalue['error'] = $result->getMessage();
2458
	} else if ($result === true) {
2459
		$retvalue['acct_val'] = 5 ;
2460
	} else {
2461
		$retvalue['acct_val'] = 1 ;
2462
	}
2463

    
2464
	$racct->close();
2465
	return $retvalue;
2466
}
2467

    
2468
function captiveportal_isip_logged($clientip) {
2469
	global $g, $cpzone;
2470

    
2471
	/* read in client database */
2472
	$query = "WHERE ip = '{$clientip}'";
2473
	$cpdb = captiveportal_read_db($query);
2474
	foreach ($cpdb as $cpentry) {
2475
		return $cpentry;
2476
	}
2477
}
2478
?>
(7-7/65)