Project

General

Profile

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

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

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

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

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

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

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

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

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

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

    
103
EOD;
104

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

    
112
EOD;
113
	}
114

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

    
144
EOD;
145

    
146
	return $htmltext;
147
}
148

    
149
function captiveportal_load_modules() {
150
        global $config;
151

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

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

    
169
        /* XXX: This are not used in pfSense, if needed can be tuned 
170
        if($config['system']['maximumstates'] <> "" && is_numeric($config['system']['maximumstates'])) {
171
                mwexec("sysctl net.inet.ip.fw.dyn_max={$config['system']['maximumstates']}");
172
        } else {
173
                mwexec("sysctl net.inet.ip.fw.dyn_max=10000");
174
        }
175
	*/
176
}
177

    
178
function captiveportal_configure() {
179
	global $config, $cpzone;
180

    
181
	if (is_array($config['captiveportal'])) {
182
		foreach ($config['captiveportal'] as $cpkey => $cp) {
183
			$cpzone = $cpkey;
184
			captiveportal_configure_zone($cp);
185
		}
186
	} else
187
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
188
}
189

    
190
function captiveportal_ipfw_set_context($cpzone) {
191
	mwexec("/usr/local/sbin/ipfw_context -s {$cpzone}", true);
192
}
193

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

    
197
	$captiveportallck = lock("captiveportal{$cpzone}", LOCK_EX);
198
	
199
	if (isset($cpcfg['enable'])) {
200

    
201
		if ($g['booting'])
202
			echo "Starting captive portal({$cpcfg['zone']})... ";
203
		else
204
			captiveportal_syslog("Restarting captive portal({$cpcfg['zone']}).");
205

    
206
		/* kill any running mini_httpd */
207
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
208
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
209

    
210
		/* remove old information */
211
		unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.db");
212
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac_{$cpzone}.db");
213
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip_{$cpzone}.db");
214
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
215

    
216
		/* setup new database in case someone tries to access the status -> captive portal page */
217
		touch("{$g['vardb_path']}/captiveportal_{$cpzone}.db");
218

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

    
222
		/* init ipfw rules */
223
		captiveportal_init_rules(true);
224

    
225
		/* initialize minicron interval value */
226
		$croninterval = $cpcfg['croninterval'] ? $cpcfg['croninterval'] : 60;
227

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

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

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

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

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

    
288
		/* write logout page */
289
		if (is_array($cpcfg['page']) && $cpcfg['page']['logouttext'])
290
			$logouttext = base64_decode($cpcfg['page']['logouttext']);
291
		else {
292
			/* example page */
293
			$logouttext = <<<EOD
294
<HTML>
295
<HEAD><TITLE>Redirecting...</TITLE></HEAD>
296
<BODY>
297
<SPAN STYLE="font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">
298
<B>Redirecting to <A HREF="<?=\$my_redirurl;?>"><?=\$my_redirurl;?></A>...</B>
299
</SPAN>
300
<SCRIPT LANGUAGE="JavaScript">
301
<!--
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>Logout</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>Click the button below to disconnect</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="Logout">');
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
		/* start up the webserving daemon */
339
		captiveportal_init_webgui_zone($cpcfg);
340

    
341
		/* Kill any existing prunecaptiveportal processes */
342
		if (file_exists("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid"))
343
			killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
344

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

    
349
		/* generate radius server database */
350
		captiveportal_init_radius_servers();
351

    
352
		if ($g['booting'])
353
			echo "done\n";
354

    
355
	} else {
356
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
357
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
358
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
359
		@unlink("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
360
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
361
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
362
		/* remove old information */
363
		unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.db");
364
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac_{$cpzone}.db");
365
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip_{$cpzone}.db");
366
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
367

    
368
		captiveportal_radius_stop_all();
369

    
370
		mwexec("/usr/local/sbin/ipfw_context -d {$cpzone}", true);
371

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

    
388
		/* unload ipfw */
389
		$listifs = get_configured_interface_list();
390
		$cpinterfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
391
		foreach ($cpinterfaces as $cpifgrp) {
392
			if (!isset($listifs[$cpifgrp]))
393
				continue;
394
			$listrealif = get_real_interface($cpifgrp);
395
			if (does_interface_exist($listrealif)) {
396
				pfSense_interface_flags($listrealif, -IFF_IPFW_FILTER);
397
				$carpif = link_ip_to_carp_interface(find_interface_ip($listrealif));
398
				if (!empty($carpif)) {
399
					$carpsif = explode(" ", $carpif);
400
					foreach ($carpsif as $cpcarp)
401
						pfSense_interface_flags($cpcarp, -IFF_IPFW_FILTER);
402
				}
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
	$use_fastcgi = true;
439

    
440
	if (isset($cpcfg['httpslogin'])) {
441
		$cert = lookup_cert($cpcfg['certref']);
442
		$crt = base64_decode($cert['crt']);
443
		$key = base64_decode($cert['prv']);
444
		$ca = ca_chain($cert);
445
    
446
		/* generate lighttpd configuration */
447
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
448
		system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf",
449
			$crt, $key, $ca, "lighty-{$cpzone}-CaptivePortal-SSL.pid", $listenporthttps, "/usr/local/captiveportal",
450
			"cert-portal.pem", "ca-portal.pem", "1", $use_fastcgi, $cpzone);
451
	}
452

    
453
	/* generate lighttpd configuration */
454
	$listenporthttp = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : $cpcfg['zoneid'];
455
	system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal.conf",
456
		"", "", "", "lighty-{$cpzone}-CaptivePortal.pid", $listenporthttp, "/usr/local/captiveportal",
457
		"cert-portal.pem", "ca-portal.pem", "1", $use_fastcgi, $cpzone);
458

    
459
	/* attempt to start lighttpd */
460
	$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal.conf");
461

    
462
	/* fire up https instance */
463
	if (isset($cpcfg['httpslogin']))
464
		$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf");
465
}
466

    
467
/* reinit will disconnect all users, be careful! */
468
function captiveportal_init_rules($reinit = false) {
469
	global $config, $g, $cpzone;
470

    
471
	if (!isset($config['captiveportal'][$cpzone]['enable']))
472
		return;
473

    
474
	captiveportal_load_modules();
475
	mwexec("/usr/local/sbin/ipfw_context -a {$cpzone}", true);
476
	captiveportal_ipfw_set_context($cpzone);
477

    
478
	$cpips = array();
479
	$ifaces = get_configured_interface_list();
480
	$cpinterfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
481
	$firsttime = 0;
482
	foreach ($cpinterfaces as $cpifgrp) {
483
		if (!isset($ifaces[$cpifgrp]))
484
			continue;
485
		$tmpif = get_real_interface($cpifgrp);
486
		if (!empty($tmpif)) {
487
			$cpipm = get_interface_ip($cpifgrp);
488
			if (is_ipaddr($cpipm)) {
489
				$carpif = link_ip_to_carp_interface($cpipm);
490
				if (!empty($carpif)) {
491
					$carpsif = explode(" ", $carpif);
492
					foreach ($carpsif as $cpcarp) {
493
						mwexec("/usr/local/sbin/ipfw_context -a {$cpzone} -n {$cpcarp}", true);
494
						pfSense_interface_flags($cpcarp, IFF_IPFW_FILTER);
495
						$carpip = find_interface_ip($cpcarp);
496
						if (is_ipaddr($carpip))
497
							$cpips[] = $carpip;
498
					}
499
				}
500
				$cpips[] = $cpipm;
501
			}
502
			mwexec("/usr/local/sbin/ipfw_context -a {$cpzone} -n {$tmpif}", true);
503
			pfSense_interface_flags($tmpif, IFF_IPFW_FILTER);
504
		}
505
	}
506
	if (count($cpips) > 0) {
507
		$cpactive = true;
508
	} else
509
		return false;
510

    
511
	if ($reinit == false)
512
		$captiveportallck = lock("captiveportal{$cpzone}");
513

    
514
	/* init dummynet/ipfw rules number database */
515
	captiveportal_init_ipfw_ruleno();
516

    
517
	$cprules =	"add 65291 set 1 allow pfsync from any to any\n";
518
	$cprules .= "add 65292 set 1 allow carp from any to any\n";
519

    
520
	$cprules .= <<<EOD
521
# add 65300 set 1 skipto 65534 all from any to any not layer2
522
# layer 2: pass ARP
523
add 65301 set 1 pass layer2 mac-type arp
524
# pfsense requires for WPA
525
add 65302 set 1 pass layer2 mac-type 0x888e
526
add 65303 set 1 pass layer2 mac-type 0x88c7
527

    
528
# PPP Over Ethernet Discovery Stage
529
add 65304 set 1 pass layer2 mac-type 0x8863
530
# PPP Over Ethernet Session Stage
531
add 65305 set 1 pass layer2 mac-type 0x8864
532

    
533
# layer 2: block anything else non-IP
534
add 65307 set 1 deny layer2 not mac-type ip
535

    
536
EOD;
537

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

    
576
	/* Authenticated users rules. */
577
	$cprules .= "add {$rulenum} set 1 pipe tablearg ip from table(1) to any in\n";
578
	$rulenum++;
579
	$cprules .= "add {$rulenum} set 1 pipe tablearg ip from any to table(2) out\n";
580
	$rulenum++;
581
	
582
	$listenporthttp =
583
		$config['captiveportal'][$cpzone]['listenporthttp'] ?
584
		$config['captiveportal'][$cpzone]['listenporthttp'] :
585
		$config['captiveportal'][$cpzone]['zoneid'];
586
	
587
	$cprules .= <<<EOD
588

    
589
# redirect non-authenticated clients to captive portal
590
add 65531 set 1 fwd 127.0.0.1,{$listenporthttp} tcp from any to any in
591
# let the responses from the captive portal web server back out
592
add 65532 set 1 pass tcp from any to any out
593
# block everything else
594
add 65533 set 1 deny all from any to any
595
# pass everything else on layer2
596
add 65534 set 1 pass all from any to any layer2
597

    
598
EOD;
599

    
600
	/* generate passthru mac database */
601
	$cprules .= captiveportal_passthrumac_configure(true);
602
	$cprules .= "\n";
603

    
604
	/* allowed ipfw rules to make allowed ip work */
605
	$cprules .= captiveportal_allowedip_configure();
606

    
607
	/* allowed ipfw rules to make allowed hostnames work */
608
	$cprules .= captiveportal_allowedhostname_configure();
609
	
610
	/* load rules */
611
	if ($reinit == true)
612
		$cprules = "table all flush\nflush\n{$cprules}";
613
	else {
614
		$tmprules = "table 3 flush\n";
615
		$tmprules .= "table 4 flush\n";
616
		$tmprules .= "table 5 flush\n";
617
		$tmprules .= "table 6 flush\n";
618
		$tmprules .= "table 7 flush\n";
619
		$tmprules .= "table 8 flush\n";
620
		$tmprules .= "table 9 flush\n";
621
		$tmprules .= "table 10 flush\n";
622
		$tmprules .= "flush\n";
623
		$cprules = "{$tmprules}\n{$cprules}";
624
	}
625

    
626
	file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
627
	captiveportal_ipfw_set_context($cpzone);
628
	mwexec("/sbin/ipfw -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
629
	//@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
630
	unset($cprules, $tmprules);
631

    
632
	if ($reinit == false)
633
		unlock($captiveportallck);
634

    
635
	/* activate ipfw(4) so CP can work */
636
	mwexec("/sbin/sysctl net.link.ether.ipfw=1");
637
	/* Make sure not re-entrancy is allowed in ipfw(4) */
638
	mwexec("/sbin/sysctl net.inet.ip.fw.one_pass=1");
639
}
640

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

    
649
	if (empty($cpzone))
650
		return;
651

    
652
	/* check for expired entries */
653
	if (empty($config['captiveportal'][$cpzone]['timeout']) ||
654
	!is_numeric($config['captiveportal'][$cpzone]['timeout']))
655
		$timeout = 0;
656
	else
657
		$timeout = $config['captiveportal'][$cpzone]['timeout'] * 60;
658

    
659
	if (empty($config['captiveportal'][$cpzone]['idletimeout']) ||
660
	!is_numeric($config['captiveportal'][$cpzone]['idletimeout']))
661
		$idletimeout = 0;
662
	else
663
		$idletimeout = $config['captiveportal'][$cpzone]['idletimeout'] * 60;
664

    
665
	if (!$timeout && !$idletimeout && !isset($config['captiveportal'][$cpzone]['reauthenticate']) && 
666
	!isset($config['captiveportal'][$cpzone]['radiussession_timeout']) && !isset($config['voucher'][$cpzone]['enable']))
667
		return;
668

    
669
	$radiussrvs = captiveportal_get_radius_servers();
670

    
671
	/* read database */
672
	$cpdb = captiveportal_read_db();
673

    
674
	/*	To make sure we iterate over ALL accounts on every run the count($cpdb) is moved
675
	 *	outside of the loop. Otherwise the loop would evaluate count() on every iteration
676
	 *	and since $i would increase and count() would decrement they would meet before we
677
	 *	had a chance to iterate over all accounts.
678
	 */
679
	$unsetindexes = array();
680
	$voucher_needs_sync = false;
681
	/* 
682
	 * Snapshot the time here to use for calculation to speed up the process.
683
	 * If something is missed next run will catch it!
684
	 */
685
	$pruning_time = time();
686
	$stop_time = $pruning_time;
687
	foreach ($cpdb as $cpentry) {
688

    
689
		$timedout = false;
690
		$term_cause = 1;
691
		if (empty($cpentry[10]))
692
			$cpentry[10] = 'first';
693
		$radiusservers = $radiussrvs[$cpentry[10]];
694

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

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

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

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

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

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

    
750
		/* do periodic RADIUS reauthentication? */
751
		if (!$timedout && !empty($radiusservers)) {
752
			if (isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
753
				if ($config['captiveportal'][$cpzone]['reauthenticateacct'] == "stopstart") {
754
					/* stop and restart accounting */
755
					RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
756
						$cpentry[4], // username
757
						$cpentry[5], // sessionid
758
						$cpentry[0], // start time
759
						$radiusservers,
760
						$cpentry[2], // clientip
761
						$cpentry[3], // clientmac
762
						10); // NAS Request
763
					captiveportal_ipfw_set_context($cpzone);
764
					exec("/sbin/ipfw table 1 entryzerostats {$cpentry[2]}");
765
					exec("/sbin/ipfw table 2 entryzerostats {$cpentry[2]}");
766
					RADIUS_ACCOUNTING_START($cpentry[1], // ruleno
767
						$cpentry[4], // username
768
						$cpentry[5], // sessionid
769
						$radiusservers,
770
						$cpentry[2], // clientip
771
						$cpentry[3]); // clientmac
772
				} else if ($config['captiveportal'][$cpzone]['reauthenticateacct'] == "interimupdate") {
773
					RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
774
						$cpentry[4], // username
775
						$cpentry[5], // sessionid
776
						$cpentry[0], // start time
777
						$radiusservers,
778
						$cpentry[2], // clientip
779
						$cpentry[3], // clientmac
780
						10, // NAS Request
781
						true); // Interim Updates
782
				}
783
			}
784

    
785
			/* check this user against RADIUS again */
786
			if (isset($config['captiveportal'][$cpzone]['reauthenticate'])) {
787
				$auth_list = RADIUS_AUTHENTICATION($cpentry[4], // username
788
					base64_decode($cpentry[6]), // password
789
					$radiusservers,
790
					$cpentry[2], // clientip
791
					$cpentry[3], // clientmac
792
					$cpentry[1]); // ruleno
793
				if ($auth_list['auth_val'] == 3) {
794
					captiveportal_disconnect($cpentry, $radiusservers, 17);
795
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
796
					$unsetindexes[] = $cpentry[5];
797
				} else if ($auth_list['auth_val'] == 2)
798
					captiveportal_reapply_attributes($cpentry, $auth_list);
799
			}
800
		}
801
	}
802

    
803
	captiveportal_prune_old_automac();
804

    
805
	if ($voucher_needs_sync == true)
806
		/* Triger a sync of the vouchers on config */
807
		send_event("service sync vouchers");
808

    
809
	/* write database */
810
	if (!empty($unsetindexes))
811
		captiveportal_write_db($cpdb, false, $unsetindexes);
812
}
813

    
814
function captiveportal_prune_old_automac() {
815
	global $g, $config, $cpzone;
816

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

    
861
/* remove a single client according to the DB entry */
862
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
863
	global $g, $config, $cpzone;
864

    
865
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
866

    
867
	/* this client needs to be deleted - remove ipfw rules */
868
	if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers)) {
869
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
870
			$dbent[4], // username
871
			$dbent[5], // sessionid
872
			$dbent[0], // start time
873
			$radiusservers,
874
			$dbent[2], // clientip
875
			$dbent[3], // clientmac
876
			$term_cause, // Acct-Terminate-Cause
877
			false,
878
			$stop_time);
879
	}
880
	
881
	if (is_ipaddr($dbent[2])) {
882
		captiveportal_ipfw_set_context($cpzone);
883
		/* Delete client's ip entry from tables 3 and 4. */
884
		mwexec("/sbin/ipfw table 1 delete {$dbent[2]}");
885
		mwexec("/sbin/ipfw table 2 delete {$dbent[2]}");
886
		/* XXX: Redundant?! Ensure all pf(4) states are killed. */
887
		mwexec("pfctl -k {$dbent[2]}");
888
		mwexec("pfctl -K {$dbent[2]}");
889
	}
890

    
891
	/* 
892
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
893
	* We could get an error if the pipe doesn't exist but everything should still be fine
894
	*/
895
	captiveportal_ipfw_set_context($cpzone);
896
	mwexec("/sbin/ipfw pipe " . ($dbent[1]+20000) . " delete");
897
	mwexec("/sbin/ipfw pipe " . ($dbent[1]+20001) . " delete");
898

    
899
	/* Release the ruleno so it can be reallocated to new clients. */
900
	captiveportal_free_ipfw_ruleno($dbent[1]);
901

    
902
	// XMLRPC Call over to the master Voucher node
903
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
904
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
905
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
906
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
907
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
908
		$remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time);
909
	}
910

    
911
}
912

    
913
/* remove a single client by sessionid */
914
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
915
	global $g, $config, $cpzone;
916

    
917
	$radiusservers = captiveportal_get_radius_servers();
918
	$unsetindex = array();
919

    
920
	/* read database */
921
	$cpdb = captiveportal_read_db();
922

    
923
	/* find entry */
924
	if (isset($cpdb[$sessionid])) {
925
		$cpentry = $cpdb[$sessionid];
926
		/* write database */
927
		$unsetindex[] = $sessionid;
928
		captiveportal_write_db($cpdb, false, $unsetindex);
929
		if (empty($cpentry[10]))
930
			$cpentry[10] = 'first';
931
		captiveportal_disconnect($cpentry, $radiusservers[$cpentry[10]], $term_cause);
932
		captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
933
	}		
934
}
935

    
936
/* send RADIUS acct stop for all current clients */
937
function captiveportal_radius_stop_all() {
938
	global $config, $cpzone;
939

    
940
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable']))
941
		return;
942

    
943
	$radiusservers = captiveportal_get_radius_servers();
944
	if (!empty($radiusservers)) {
945
		$cpdb = captiveportal_read_db();
946
		foreach ($cpdb as $cpentry) {
947
			if (empty($cpentry[10]))
948
				$cpentry[10] = 'first';
949
			if (!empty($radiusservers[$cpentry[10]])) {
950
				RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
951
					$cpentry[4], // username
952
					$cpentry[5], // sessionid
953
					$cpentry[0], // start time
954
					$radiusservers[$cpentry[10]],
955
					$cpentry[2], // clientip
956
					$cpentry[3], // clientmac
957
					7); // Admin Reboot
958
			}
959
		}
960
	}
961
}
962

    
963
function captiveportal_passthrumac_configure_entry($macent) {
964
	$rules = "";
965
	$enBwup = isset($macent['bw_up']);
966
	$enBwdown = isset($macent['bw_down']);
967
	$actionup = "allow";
968
	$actiondown = "allow";
969

    
970
	$ruleno = captiveportal_get_next_ipfw_ruleno();
971

    
972
	if ($enBwup) {
973
		$bw_up = $ruleno + 20000;
974
		$rules .= "pipe {$bw_up} config bw {$macent['bw_up']}Kbit/s queue 100\n";
975
		$actionup = "pipe {$bw_up}";
976
	}
977
	if ($enBwdown) {
978
		$bw_down = $ruleno + 20001;
979
		$rules .= "pipe {$bw_down} config bw {$macent['bw_down']}Kbit/s queue 100\n";
980
		$actiondown = "pipe {$bw_down}";
981
	}
982
	$rules .= "add {$ruleno} {$actiondown} ip from any to any MAC {$macent['mac']} any\n";
983
	$ruleno++;
984
	$rules .= "add {$ruleno} {$actionup} ip from any to any MAC any {$macent['mac']}\n";
985

    
986
	return $rules;
987
}
988

    
989
function captiveportal_passthrumac_configure($lock = false) {
990
	global $config, $g, $cpzone;
991

    
992
	$rules = "";
993

    
994
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
995
		$macdb = array();
996
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
997
			$rules .= captiveportal_passthrumac_configure_entry($macent);
998
			$macdb[$macent['mac']][$cpzone]['active']  = true;
999

    
1000
		}
1001
	}
1002

    
1003
	return $rules;
1004
}
1005

    
1006
function captiveportal_passthrumac_findbyname($username) {
1007
	global $config, $cpzone;
1008

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

    
1018
/* 
1019
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1020
 * table (5=IN)/(6=OUT) hold allowed ip's with bw limit.
1021
 */
1022
function captiveportal_allowedip_configure_entry($ipent) {
1023

    
1024
	/* This function can deal with hostname or ipaddress */
1025
	if($ipent['ip']) 	
1026
		$ipaddress = $ipent['ip'];
1027

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

    
1037
	$rules = "";
1038
	$enBwup = intval($ipent['bw_up']);
1039
	$enBwdown = intval($ipent['bw_down']);
1040
	$bw_up = "";
1041
	$bw_down = "";
1042
	$tablein = array();
1043
	$tableout = array();
1044

    
1045
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1046

    
1047
	if ($ipent['dir'] == "from") {
1048
		if ($enBwup)
1049
			$tablein[] = 5;
1050
		else
1051
			$tablein[] = 3;
1052
		if ($enBwdown)
1053
			$tableout[] = 6;
1054
		else
1055
			$tableout[] = 4;
1056
	} else if ($ipent['dir'] == "to") {
1057
		if ($enBwup)
1058
			$tablein[] = 9;
1059
		else
1060
			$tablein[] = 7;
1061
		if ($enBwdown)
1062
			$tableout[] = 10;
1063
		else
1064
			$tableout[] = 8;
1065
	} else if ($ipent['dir'] == "both") {
1066
		if ($enBwup) {
1067
			$tablein[] = 5;
1068
			$tablein[] = 9;
1069
		} else {
1070
			$tablein[] = 3;
1071
			$tablein[] = 7;
1072
		}
1073
		if ($enBwdown) {
1074
			$tableout[] = 6;
1075
			$tableout[] = 10;
1076
		} else {
1077
			$tableout[] = 4;
1078
			$tableout[] = 8;
1079
		}
1080
	}
1081
	if ($enBwup) {
1082
		$bw_up = $ruleno + 20000;
1083
		$rules .= "pipe {$bw_up} config bw {$ipent['bw_up']}Kbit/s queue 100\n";
1084
	}
1085
	$subnet = "";
1086
	if (!empty($ipent['sn']))
1087
		$subnet = "/{$ipent['sn']}";
1088
	foreach ($tablein as $table)
1089
		$rules .= "table {$table} add {$ipaddress}{$subnet} {$bw_up}\n";
1090
	if ($enBwdown) {
1091
		$bw_down = $ruleno + 20001;
1092
		$rules .= "pipe {$bw_down} config bw {$ipent['bw_down']}Kbit/s queue 100\n";
1093
	}
1094
	foreach ($tableout as $table)
1095
		$rules .= "table {$table} add {$ipaddress}{$subnet} {$bw_down}\n";
1096

    
1097
	return $rules;
1098
}
1099

    
1100
/* 
1101
	Adds a dnsfilter entry and watches for hostname changes.
1102
	A change results in reloading the ruleset.
1103
*/
1104
function setup_dnsfilter_entries() {
1105
	global $g, $config, $cpzone;
1106

    
1107
	$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1108
	$cp_filterdns_conf = "";
1109
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1110
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent)  {
1111
			$cp_filterdns_conf .= "ipfw {$hostnameent['hostname']} 3\n";
1112
			$cp_filterdns_conf .= "ipfw {$hostnameent['hostname']} 4\n";
1113
			$cp_filterdns_conf .= "ipfw {$hostnameent['hostname']} 7\n";
1114
			$cp_filterdns_conf .= "ipfw {$hostnameent['hostname']} 8\n";
1115
		}
1116
	}
1117
	file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1118
	unset($cp_filterdns_conf);
1119
	killbypid("{$g['tmp_path']}/filterdns-{$cpzone}-cpah.pid");
1120
	mwexec("/usr/local/sbin/filterdns -p {$g['tmp_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzone} -d 1");
1121
}
1122

    
1123
function captiveportal_allowedhostname_configure() {
1124
	global $config, $g, $cpzone;
1125

    
1126
	$rules = "\n# captiveportal_allowedhostname_configure()\n";
1127
	setup_dnsfilter_entries();
1128
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1129
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) 
1130
			$rules .= captiveportal_allowedip_configure_entry($hostnameent);
1131
	}
1132
	return $rules;
1133
}
1134

    
1135
function captiveportal_allowedip_configure() {
1136
	global $config, $g, $cpzone;
1137

    
1138
	$rules = "";
1139
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1140
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1141
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1142
	}
1143

    
1144
	return $rules;
1145
}
1146

    
1147
/* get last activity timestamp given client IP address */
1148
function captiveportal_get_last_activity($ip) {
1149
	global $cpzone;
1150

    
1151
	$ipfwoutput = "";
1152

    
1153
	captiveportal_ipfw_set_context($cpzone);
1154
	exec("/sbin/ipfw table 1 entrystats {$ip} 2>/dev/null", $ipfwoutput);
1155
	/* Reading only from one of the tables is enough of approximation. */
1156
	if ($ipfwoutput[0]) {
1157
		$ri = explode(" ", $ipfwoutput[0]);
1158
		if ($ri[4])
1159
			return $ri[4];
1160
	}
1161

    
1162
	return 0;
1163
}
1164

    
1165
function captiveportal_init_radius_servers() {
1166
	global $config, $g, $cpzone;
1167

    
1168
	/* generate radius server database */
1169
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1170
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1171
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1172
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1173
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1174
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1175

    
1176
		if ($config['captiveportal'][$cpzone]['radiusport'])
1177
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1178
		else
1179
			$radiusport = 1812;
1180
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1181
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1182
		else
1183
			$radiusacctport = 1813;
1184
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1185
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1186
		else
1187
			$radiusport2 = 1812;
1188
		if ($config['captiveportal'][$cpzone]['radiusport3'])
1189
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1190
		else
1191
			$radiusport3 = 1812;
1192
		if ($config['captiveportal'][$cpzone]['radiusport4'])
1193
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1194
		else
1195
			$radiusport4 = 1812;
1196

    
1197
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1198
		$radiuskey2 = ($config['captiveportal'][$cpzone]['radiuskey2']) ? $config['captiveportal'][$cpzone]['radiuskey2'] : null;
1199
		$radiuskey3 = ($config['captiveportal'][$cpzone]['radiuskey3']) ? $config['captiveportal'][$cpzone]['radiuskey3'] : null;
1200
		$radiuskey4 = ($config['captiveportal'][$cpzone]['radiuskey4']) ? $config['captiveportal'][$cpzone]['radiuskey4'] : null;
1201

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

    
1219
		fclose($fd);
1220
		unlock($cprdsrvlck);
1221
	}
1222
}
1223

    
1224
/* read RADIUS servers into array */
1225
function captiveportal_get_radius_servers() {
1226
	global $g, $cpzone;
1227

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

    
1255
	unlock($cprdsrvlck);
1256
	return false;
1257
}
1258

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

    
1272
/* log simple messages to syslog */
1273
function captiveportal_syslog($message) {
1274
	$message = trim($message);
1275
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1276
	// Log it
1277
	syslog(LOG_INFO, $message);
1278
	closelog();
1279
}
1280

    
1281
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1282
	global $g, $config;
1283

    
1284
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1285

    
1286
	/* If the pool is empty, return appropriate message and fail authentication */
1287
	if (is_null($ruleno)) {
1288
		$auth_list = array();
1289
		$auth_list['auth_val'] = 1;
1290
		$auth_list['error'] = "System reached maximum login capacity";
1291
		return $auth_list;
1292
	}
1293

    
1294
	$radiusservers = captiveportal_get_radius_servers();
1295

    
1296
	if (is_null($radiusctx))
1297
		$radiusctx = 'first';
1298

    
1299
	$auth_list = RADIUS_AUTHENTICATION($username,
1300
		$password,
1301
		$radiusservers[$radiusctx],
1302
		$clientip,
1303
		$clientmac,
1304
		$ruleno);
1305

    
1306
	if ($auth_list['auth_val'] == 2) {
1307
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1308
		$sessionid = portal_allow($clientip,
1309
			$clientmac,
1310
			$username,
1311
			$password,
1312
			$auth_list,
1313
			$ruleno,
1314
			$radiusctx);
1315
	}
1316

    
1317
	return $auth_list;
1318
}
1319

    
1320
/* read captive portal DB into array */
1321
function captiveportal_read_db($locked = false, $index = 5 /* sessionid by default */) {
1322
	global $g, $cpzone;
1323

    
1324
	$cpdb = array();
1325

    
1326
	if ($locked == false)
1327
		$cpdblck = lock("captiveportaldb{$cpzone}");
1328
	$fd = @fopen("{$g['vardb_path']}/captiveportal_{$cpzone}.db", "r");
1329
	if ($fd) {
1330
		while (!feof($fd)) {
1331
			$line = trim(fgets($fd));
1332
			if ($line) {
1333
				$cpe = explode(",", $line);
1334
				/* Hash by session id */
1335
				$cpdb[$cpe[$index]] = $cpe;
1336
			}
1337
		}
1338
		fclose($fd);
1339
	}
1340
	if ($locked == false)
1341
		unlock($cpdblck);
1342
	return $cpdb;
1343
}
1344

    
1345
/* write captive portal DB */
1346
function captiveportal_write_db($cpdb, $locked = false, $remove = false) {
1347
	global $g, $cpzone;
1348

    
1349
	if ($locked == false)
1350
		$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1351

    
1352
	if (is_array($remove)) {
1353
		if (!empty($remove)) {
1354
			$cpdb = captiveportal_read_db(true);
1355
			foreach ($remove as $key) {
1356
				if (is_array($key))
1357
					log_error("Captive portal Array passed as unset index: " . print_r($key, true));
1358
				else
1359
					unset($cpdb[$key]);
1360
			}
1361
		} else {
1362
			if ($locked == false)
1363
				unlock($cpdblck);
1364
			return; //This makes sure no record removal calls
1365
		}
1366
	}
1367
	$fd = @fopen("{$g['vardb_path']}/captiveportal_{$cpzone}.db", "w");
1368
	if ($fd) {
1369
		foreach ($cpdb as $cpent) {
1370
			fwrite($fd, join(",", $cpent) . "\n");
1371
		}
1372
		fclose($fd);
1373
	}
1374
	if ($locked == false)
1375
		unlock($cpdblck);
1376
}
1377

    
1378
function captiveportal_write_elements() {
1379
	global $g, $config, $cpzone;
1380
	
1381
	$cpcfg = $config['captiveportal'][$cpzone];
1382

    
1383
	/* delete any existing elements */
1384
	if (is_dir($g['captiveportal_element_path'])) {
1385
		$dh = opendir($g['captiveportal_element_path']);
1386
		while (($file = readdir($dh)) !== false) {
1387
			if ($file != "." && $file != "..")
1388
				unlink($g['captiveportal_element_path'] . "/" . $file);
1389
		}
1390
		closedir($dh);
1391
	} else {
1392
		@mkdir($g['captiveportal_element_path']);
1393
	}
1394

    
1395
	if (is_array($cpcfg['element'])) {
1396
		conf_mount_rw();
1397
		foreach ($cpcfg['element'] as $data) {
1398
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
1399
			if (!$fd) {
1400
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1401
				return 1;
1402
			}
1403
			$decoded = base64_decode($data['content']);
1404
			fwrite($fd,$decoded);
1405
			fclose($fd);
1406
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1407
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1408
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
1409
		}
1410
		conf_mount_ro();
1411
	}
1412
	
1413
	return 0;
1414
}
1415

    
1416
function captiveportal_init_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
1417
	global $g, $cpzone;
1418

    
1419
	@unlink("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
1420
	$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1421
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1422
}
1423

    
1424
/*
1425
 * This function will calculate the lowest free firewall ruleno
1426
 * within the range specified based on the actual logged on users
1427
 *
1428
 */
1429
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
1430
	global $config, $g, $cpzone;
1431

    
1432
	$cpcfg = $config['captiveportal'][$cpzone];
1433
	if(!isset($cpcfg['enable']))
1434
		return NULL;
1435

    
1436
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1437
	$ruleno = 0;
1438
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1439
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1440
		for ($ridx = 2; $ridx < ($rulenos_range_max - $rulenos_start); $ridx++) {
1441
			if ($rules[$ridx]) {
1442
				/* 
1443
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1444
				 * and the out pipe ruleno + 1.
1445
				 */
1446
				$ridx++;
1447
				continue;
1448
			}
1449
			$ruleno = $ridx;
1450
			$rules[$ridx] = "used";
1451
			$rules[++$ridx] = "used";
1452
			break;
1453
		}
1454
	} else {
1455
		$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1456
		$rules[2] = "used";
1457
		$ruleno = 2;
1458
	}
1459
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1460
	unlock($cpruleslck);
1461
	return $ruleno;
1462
}
1463

    
1464
function captiveportal_free_ipfw_ruleno($ruleno, $usedbw = false) {
1465
	global $config, $g, $cpzone;
1466

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

    
1471
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1472
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1473
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1474
		$rules[$ruleno] = false;
1475
		$rules[++$ruleno] = false;
1476
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1477
	}
1478
	unlock($cpruleslck);
1479
}
1480

    
1481
function captiveportal_get_ipfw_passthru_ruleno($value) {
1482
	global $config, $g, $cpzone;
1483

    
1484
	$cpcfg = $config['captiveportal'][$cpzone];
1485
	if(!isset($cpcfg['enable']))
1486
		return NULL;
1487

    
1488
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1489
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1490
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1491
		captiveportal_ipfw_set_context($cpzone);
1492
		$ruleno = intval(`/sbin/ipfw show | /usr/bin/grep {$value} |  /usr/bin/grep -v grep | /usr/bin/cut -d " " -f 1 | /usr/bin/head -n 1`);
1493
		if ($rules[$ruleno]) {
1494
			unlock($cpruleslck);
1495
			return $ruleno;
1496
		}
1497
	}
1498

    
1499
	unlock($cpruleslck);
1500
	return NULL;
1501
}
1502

    
1503
/**
1504
 * This function will calculate the traffic produced by a client
1505
 * based on its firewall rule
1506
 *
1507
 * Point of view: NAS
1508
 *
1509
 * Input means: from the client
1510
 * Output means: to the client
1511
 *
1512
 */
1513

    
1514
function getVolume($ip) {
1515
	global $cpzone;
1516

    
1517
	$volume = array();
1518

    
1519
	// Initialize vars properly, since we don't want NULL vars
1520
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1521

    
1522
	// Ingress
1523
	$ipfwin = "";
1524
	$ipfwout = "";
1525
	$matchesin = "";
1526
	$matchesout = "";
1527
	captiveportal_ipfw_set_context($cpzone);
1528
	exec("/sbin/ipfw table 1 entrystats {$ip}", $ipfwin);
1529
	if ($ipfwin[0]) {
1530
		$ipfwin = explode(" ", $ipfwin[0]);
1531
		$volume['input_pkts'] = $ipfwin[2];
1532
		$volume['input_bytes'] = $ipfwin[3];
1533
	}
1534

    
1535
	exec("/sbin/ipfw table 2 entrystats {$ip}", $ipfwout);
1536
	if ($ipfwout[0]) {
1537
		$ipfwout = explode(" ", $ipfwout[0]);
1538
		$volume['output_pkts'] = $ipfwout[2];
1539
		$volume['output_bytes'] = $ipfwout[3];
1540
	}
1541

    
1542
	return $volume;
1543
}
1544

    
1545
/**
1546
 * Get the NAS-Identifier
1547
 *
1548
 * We will use our local hostname to make up the nas_id
1549
 */
1550
function getNasID()
1551
{
1552
	$nasId = "";
1553
	exec("/bin/hostname", $nasId);
1554
	if(!$nasId[0])
1555
		$nasId[0] = "{$g['product_name']}";
1556
	return $nasId[0];
1557
}
1558

    
1559
/**
1560
 * Get the NAS-IP-Address based on the current wan address
1561
 *
1562
 * Use functions in interfaces.inc to find this out
1563
 *
1564
 */
1565

    
1566
function getNasIP()
1567
{
1568
	global $config, $cpzone;
1569

    
1570
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1571
			$nasIp = get_interface_ip();
1572
	} else {
1573
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1574
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1575
		else
1576
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1577
	}
1578
		
1579
	if(!is_ipaddr($nasIp))
1580
		$nasIp = "0.0.0.0";
1581

    
1582
	return $nasIp;
1583
}
1584

    
1585
function portal_ip_from_client_ip($cliip) {
1586
	global $config, $cpzone;
1587

    
1588
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1589
	foreach ($interfaces as $cpif) {
1590
		$ip = get_interface_ip($cpif);
1591
		$sn = get_interface_subnet($cpif);
1592
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1593
			return $ip;
1594
	}
1595

    
1596
	$iface = exec_command("/sbin/route -n get {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1597
	$iface = trim($iface, "\n");
1598
	if (!empty($iface)) {
1599
		$ip = find_interface_ip($iface);
1600
		if (is_ipaddr($ip))
1601
			return $ip;
1602
	}
1603

    
1604
	// doesn't match up to any particular interface
1605
	// so let's set the portal IP to what PHP says 
1606
	// the server IP issuing the request is. 
1607
	// allows same behavior as 1.2.x where IP isn't 
1608
	// in the subnet of any CP interface (static routes, etc.)
1609
	// rather than forcing to DNS hostname resolution
1610
	$ip = $_SERVER['SERVER_ADDR'];
1611
	if (is_ipaddr($ip))
1612
		return $ip;
1613

    
1614
	return false;
1615
}
1616

    
1617
/* functions move from index.php */
1618

    
1619
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1620
	global $g, $config, $cpzone;
1621

    
1622
	/* Get captive portal layout */
1623
	if ($type == "redir") {
1624
		header("Location: {$redirurl}");
1625
		return;
1626
	} else if ($type == "login")
1627
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1628
	else
1629
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1630

    
1631
	$cpcfg = $config['captiveportal'][$cpzone];
1632

    
1633
	/* substitute the PORTAL_REDIRURL variable */
1634
	if ($config['captiveportal'][$cpzone]['preauthurl']) {
1635
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$config['captiveportal'][$cpzone]['preauthurl']}", $htmltext);
1636
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$config['captiveportal'][$cpzone]['preauthurl']}", $htmltext);
1637
	}
1638

    
1639
	/* substitute other variables */
1640
	if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
1641
		$httpsport = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
1642
		$htmltext = str_replace("\$PORTAL_ACTION\$", "https://{$config['captiveportal'][$cpzone]['httpsname']}:{$httpsport}/", $htmltext);
1643
		$htmltext = str_replace("#PORTAL_ACTION#", "https://{$config['captiveportal'][$cpzone]['httpsname']}:{$httpsport}/", $htmltext);
1644
	} else {
1645
		$httpport = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : $cpcfg['zoneid'];
1646
		$ifip = portal_ip_from_client_ip($clientip);
1647
		if (!$ifip)
1648
			$ourhostname = $config['system']['hostname'] . ":{$httpport}";
1649
		else
1650
			$ourhostname = "{$ifip}:{$httpport}";
1651
		$htmltext = str_replace("\$PORTAL_ACTION\$", "http://{$ourhostname}/", $htmltext);
1652
		$htmltext = str_replace("#PORTAL_ACTION#", "http://{$ourhostname}/", $htmltext);
1653
	}
1654

    
1655
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1656
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1657
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1658
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1659
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1660

    
1661
	// Special handling case for captive portal master page so that it can be ran 
1662
	// through the PHP interpreter using the include method above.  We convert the
1663
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1664
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1665
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1666
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1667
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1668
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1669
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1670
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1671

    
1672
    echo $htmltext;
1673
}
1674

    
1675
function portal_mac_radius($clientmac,$clientip) {
1676
    global $config, $cpzone;
1677

    
1678
    $radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1679

    
1680
    /* authentication against the radius server */
1681
    $username = mac_format($clientmac);
1682
    $auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1683
    if ($auth_list['auth_val'] == 2)
1684
        return TRUE;
1685
    if (!empty($auth_list['url_redirection']))
1686
	portal_reply_page($auth_list['url_redirection'], "redir");
1687

    
1688
    return FALSE;
1689
}
1690

    
1691
function captiveportal_reapply_attributes($cpentry, $attributes) {
1692
	global $config, $cpzone, $g;
1693
                         
1694
	$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1695
	$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1696
        $bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1697
        $bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1698
        $bw_up_pipeno = $cpentry[1]+20000;
1699
        $bw_down_pipeno = $cpentry[1]+20001;
1700

    
1701
        $commands = "";
1702
	$commands .= "pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100\n";
1703
	$commands .= "pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100\n";
1704
	@file_put_contents("{$g['tmp_path']}/reattribute{$cpzone}.rule.tmp", $commands);
1705
	captiveportal_ipfw_set_context($cpzone);
1706
	mwexec("/sbin/ipfw -q {$g['tmp_path']}/reattribute{$cpzone}.rule.tmp");
1707
	//captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_BANDWIDTH_REAPPLY", "{$bw_up}/{$bw_down}");
1708

    
1709
        unset($bw_up_pipeno, $bw_Down_pipeno, $bw_up, $bw_down);
1710
}
1711

    
1712
function portal_allow($clientip,$clientmac,$username,$password = null, $attributes = null, $ruleno = null, $radiusctx = null)  {
1713

    
1714
	global $redirurl, $g, $config, $type, $passthrumac, $_POST, $cpzone;
1715

    
1716
	/* See if a ruleno is passed, if not start sessions because this means there isn't one atm */
1717
	if ($ruleno == null)
1718
		$ruleno = captiveportal_get_next_ipfw_ruleno();
1719

    
1720
	/* if the pool is empty, return appropriate message and exit */
1721
	if (is_null($ruleno)) {
1722
		portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1723
		log_error("WARNING!  Captive portal has reached maximum login capacity");
1724
		exit;
1725
	}
1726

    
1727
	// Ensure we create an array if we are missing attributes
1728
	if (!is_array($attributes))
1729
		$attributes = array();
1730

    
1731
	$radiusservers = captiveportal_get_radius_servers();
1732

    
1733
	/* Do not allow concurrent login execution. */
1734
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1735

    
1736
	unset($sessionid);
1737

    
1738
	/* read in client database */
1739
	$cpdb = captiveportal_read_db(true);
1740

    
1741
	if ($attributes['voucher'])
1742
		$remaining_time = $attributes['session_timeout'];
1743

    
1744
	$writecfg = false;
1745
	/* Find an existing session */
1746
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
1747
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1748
			$mac = captiveportal_passthrumac_findbyname($username);
1749
			if (!empty($mac)) {
1750
				if ($_POST['replacemacpassthru']) {
1751
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
1752
						if ($macent['mac'] == $mac['mac']) {
1753
							$macrules = "";
1754
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1755
                                			if ($ruleno) {
1756
								captiveportal_free_ipfw_ruleno($ruleno, true);
1757
                                        			$macrules .= "delete {$ruleno}\n";
1758
								++$ruleno;
1759
                                        			$macrules .= "delete {$ruleno}\n";
1760
                                			}
1761
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1762
							$mac['mac'] = $clientmac;
1763
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1764
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1765
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1766
							captiveportal_ipfw_set_context($cpzone);
1767
							mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1768
							$writecfg = true;
1769
							$sessionid = true;
1770
							break;
1771
						}
1772
					}
1773
                                } else {
1774
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1775
						$clientmac, $clientip, $username, $password);
1776
					exit;
1777
				}
1778
			}
1779
		}
1780
	}
1781

    
1782
	/* Snapshot the timestamp */
1783
	$allow_time = time();
1784
	if (is_null($radiusctx))
1785
		$radiusctx = 'first';
1786
	foreach ($cpdb as $sid => $cpentry) {
1787
		if (empty($cpentry[10]))
1788
			$cpentry[10] = 'first';
1789
		/* on the same ip */
1790
		if ($cpentry[2] == $clientip) {
1791
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac)   
1792
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1793
			else
1794
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
1795
			$sessionid = $sid;
1796
			break;
1797
		}
1798
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1799
			// user logged in with an active voucher. Check for how long and calculate 
1800
			// how much time we can give him (voucher credit - used time)
1801
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1802
			if ($remaining_time < 0)    // just in case. 
1803
				$remaining_time = 0;
1804

    
1805
			/* This user was already logged in so we disconnect the old one */
1806
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1807
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1808
			unset($cpdb[$sid]);
1809
			break;
1810
		}
1811
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1812
			/* on the same username */
1813
			if (strcasecmp($cpentry[4], $username) == 0) {
1814
				/* This user was already logged in so we disconnect the old one */
1815
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1816
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1817
				unset($cpdb[$sid]);
1818
				break;
1819
			}
1820
		}
1821
	}
1822

    
1823
	if ($attributes['voucher'] && $remaining_time <= 0)
1824
		return 0;       // voucher already used and no time left
1825

    
1826
	if (!isset($sessionid)) {
1827
		/* generate unique session ID */
1828
		$tod = gettimeofday();
1829
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1830

    
1831
		$dwfaultbw_up = isset($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1832
		$dwfaultbw_down = isset($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1833
		$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
1834
		$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
1835

    
1836
		if ($passthrumac) {
1837
			$mac = array();
1838
			$mac['mac'] = $clientmac;
1839
			$mac['ip'] = $clientip; /* Used only for logging */
1840
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
1841
				$mac['username'] = $username;
1842
				if ($attributes['voucher'])
1843
					$mac['logintype'] = "voucher";
1844
			}
1845
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1846
			if (!empty($bw_up))
1847
				$mac['bw_up'] = $bw_up;
1848
			if (!empty($bw_down))
1849
				$mac['bw_down'] = $bw_down;
1850
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
1851
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
1852
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1853
			unlock($cpdblck);
1854
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1855
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1856
			captiveportal_ipfw_set_context($cpzone);
1857
			mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1858
			$writecfg = true;
1859
		} else {
1860
			captiveportal_ipfw_set_context($cpzone);
1861

    
1862
			$bw_up_pipeno = $ruleno + 20000;
1863
			//$bw_up /= 1000; // Scale to Kbit/s
1864
			mwexec("/sbin/ipfw pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100");
1865

    
1866
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1867
				mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac} {$bw_up_pipeno}");
1868
			else
1869
				mwexec("/sbin/ipfw table 1 add {$clientip} {$bw_up_pipeno}");
1870

    
1871
			$bw_down_pipeno = $ruleno + 20001;
1872
			//$bw_down /= 1000; // Scale to Kbit/s
1873
			mwexec("/sbin/ipfw pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100");
1874

    
1875
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1876
				mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac} {$bw_down_pipeno}");
1877
			else
1878
				mwexec("/sbin/ipfw table 2 add {$clientip} {$bw_down_pipeno}");
1879

    
1880
			if ($attributes['voucher'])
1881
				$attributes['session_timeout'] = $remaining_time;
1882

    
1883
			/* encode password in Base64 just in case it contains commas */
1884
			$bpassword = base64_encode($password);
1885
			$cpdb[] = array($allow_time, $ruleno, $clientip, $clientmac, $username, $sessionid, $bpassword,
1886
				$attributes['session_timeout'], $attributes['idle_timeout'], $attributes['session_terminate_time'], $radiusctx);
1887

    
1888
			/* rewrite information to database */
1889
			captiveportal_write_db($cpdb, true);
1890
			unlock($cpdblck);
1891

    
1892
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
1893
				$acct_val = RADIUS_ACCOUNTING_START($ruleno,
1894
                                		$username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
1895
				if ($acct_val == 1)
1896
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1897
			}
1898
		}
1899
	} else
1900
		unlock($cpdblck);
1901

    
1902
	if ($writecfg == true)
1903
		write_config();
1904

    
1905
	/* redirect user to desired destination */
1906
	if (!empty($attributes['url_redirection']))
1907
		$my_redirurl = $attributes['url_redirection'];
1908
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
1909
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
1910
	else
1911
		$my_redirurl = $redirurl;
1912

    
1913
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
1914

    
1915
		if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
1916
			$httpsport =
1917
				$config['captiveportal'][$cpzone]['listenporthttps'] ?
1918
				$config['captiveportal'][$cpzone]['listenporthttps'] :
1919
				($config['captiveportal'][$cpzone]['zoneid'] + 1);
1920
			$logouturl = "https://{$config['captiveportal']['httpsname']}:{$httpsport}/";
1921
		} else {
1922
			$ifip = portal_ip_from_client_ip($clientip);
1923
			$httpport =
1924
				$config['captiveportal'][$cpzone]['listenporthttp'] ?
1925
				$config['captiveportal'][$cpzone]['listenporthttp'] :
1926
				$config['captiveportal'][$cpzone]['zoneid'];
1927
			if (!$ifip)
1928
				$ourhostname = $config['system']['hostname'] . ":{$httpport}";
1929
			else
1930
				$ourhostname = "{$ifip}:{$httpport}";
1931
			$logouturl = "http://{$ourhostname}/";
1932
		}
1933

    
1934
		if (isset($attributes['reply_message']))
1935
			$message = $attributes['reply_message'];
1936
		else
1937
			$message = 0;
1938

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

    
1941
	} else {
1942
		header("Location: " . $my_redirurl);
1943
	}
1944

    
1945
	return $sessionid;
1946
}
1947

    
1948

    
1949
/*
1950
 * Used for when pass-through credits are enabled.
1951
 * Returns true when there was at least one free login to deduct for the MAC.
1952
 * Expired entries are removed as they are seen.
1953
 * Active entries are updated according to the configuration.
1954
 */
1955
function portal_consume_passthrough_credit($clientmac) {
1956
	global $config, $cpzone;
1957

    
1958
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
1959
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
1960
	else
1961
		return false;
1962

    
1963
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
1964
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
1965
	else
1966
		return false;
1967

    
1968
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1969
		return false;
1970

    
1971
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
1972

    
1973
	/*
1974
	 * Read database of used MACs.  Lines are a comma-separated list
1975
	 * of the time, MAC, then the count of pass-through credits remaining.
1976
	 */
1977
	$usedmacs = captiveportal_read_usedmacs_db();
1978

    
1979
	$currenttime = time();
1980
	$found = false;
1981
	foreach ($usedmacs as $key => $usedmac) {
1982
		$usedmac = explode(",", $usedmac);
1983

    
1984
		if ($usedmac[1] == $clientmac) {
1985
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
1986
				if ($usedmac[2] < 1) {
1987
					if ($updatetimeouts) {
1988
						$usedmac[0] = $currenttime;
1989
						unset($usedmacs[$key]);
1990
						$usedmacs[] = implode(",", $usedmac);
1991
						captiveportal_write_usedmacs_db($usedmacs);
1992
					}
1993

    
1994
					return false;
1995
				} else {
1996
					$usedmac[2] -= 1;
1997
					$usedmacs[$key] = implode(",", $usedmac);
1998
				}
1999

    
2000
				$found = true;
2001
			} else
2002
				unset($usedmacs[$key]);
2003

    
2004
			break;
2005
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2006
				unset($usedmacs[$key]);
2007
	}
2008

    
2009
	if (!$found) {
2010
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2011
		$usedmacs[] = implode(",", $usedmac);
2012
	}
2013

    
2014
	captiveportal_write_usedmacs_db($usedmacs);
2015
	return true;
2016
}
2017

    
2018
function captiveportal_read_usedmacs_db() {
2019
	global $g, $cpzone;
2020

    
2021
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2022
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2023
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2024
		if (!$usedmacs)
2025
			$usedmacs = array();
2026
	} else
2027
		$usedmacs = array();
2028

    
2029
	unlock($cpumaclck);
2030
	return $usedmacs;
2031
}
2032

    
2033
function captiveportal_write_usedmacs_db($usedmacs) {
2034
	global $g, $cpzone;
2035

    
2036
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2037
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2038
	unlock($cpumaclck);
2039
}
2040

    
2041
?>
(8-8/68)