Project

General

Profile

Download (68 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
		}
504
	}
505
	if (count($cpips) > 0) {
506
		$cpactive = true;
507
	} else
508
		return false;
509

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

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

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

    
519
	$cprules .= <<<EOD
520
# add 65300 set 1 skipto 65534 all from any to any not layer2
521
# layer 2: pass ARP
522
add 65301 set 1 pass layer2 mac-type arp,rarp
523
# pfsense requires for WPA
524
add 65302 set 1 pass layer2 mac-type 0x888e,0x88c7
525
# PPP Over Ethernet Session Stage/Discovery Stage
526
add 65303 set 1 pass layer2 mac-type 0x8863,0x8864
527

    
528
# layer 2: block anything else non-IP(v4/v6)
529
add 65307 set 1 deny layer2 not mac-type ip,ipv6
530

    
531
EOD;
532

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

    
571
	/* Authenticated users rules. */
572
	$cprules .= "add {$rulenum} set 1 pipe tablearg ip from table(1) to any in\n";
573
	$rulenum++;
574
	$cprules .= "add {$rulenum} set 1 pipe tablearg ip from any to table(2) out\n";
575
	$rulenum++;
576
	
577
	$listenporthttp =
578
		$config['captiveportal'][$cpzone]['listenporthttp'] ?
579
		$config['captiveportal'][$cpzone]['listenporthttp'] :
580
		$config['captiveportal'][$cpzone]['zoneid'];
581

    
582
	if (isset($cpcfg['httpslogin'])) {
583
		$listenporthttps = $listenporthttp + 1;
584
		$cprules .= "add 65530 set 1 fwd 127.0.0.1,{$listenporthttps} tcp from any to any dst-port 443 in\n";
585
	}
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 dst-port 80 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
/* 
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
645
 * (password is in Base64 and only saved when reauthentication is enabled)
646
 */
647
function captiveportal_prune_old() {
648
	global $g, $config, $cpzone;
649

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

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

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

    
662
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout']))
663
		$idletimeout = $cpcfg['idletimeout'] * 60;
664

    
665
	/* Is there any job to do? */
666
	if (!$timeout && !$idletimeout && !isset($cpcfg['reauthenticate']) && 
667
	    !isset($cpcfg['radiussession_timeout']) && !isset($vcpcfg['enable']))
668
		return;
669

    
670
	$radiussrvs = captiveportal_get_radius_servers();
671

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

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

    
691
		$timedout = false;
692
		$term_cause = 1;
693
		if (empty($cpentry[10]))
694
			$cpentry[10] = 'first';
695
		$radiusservers = $radiussrvs[$cpentry[10]];
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) {
717
			$lastact = captiveportal_get_last_activity($cpentry[2]);
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 ($cpcfg['reauthenticateacct'] == "stopstart") {
756
					/* stop and restart accounting */
757
					RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
758
						$cpentry[4], // username
759
						$cpentry[5], // sessionid
760
						$cpentry[0], // start time
761
						$radiusservers,
762
						$cpentry[2], // clientip
763
						$cpentry[3], // clientmac
764
						10); // NAS Request
765
					captiveportal_ipfw_set_context($cpzone);
766
					exec("/sbin/ipfw table 1 entryzerostats {$cpentry[2]}");
767
					exec("/sbin/ipfw table 2 entryzerostats {$cpentry[2]}");
768
					RADIUS_ACCOUNTING_START($cpentry[1], // ruleno
769
						$cpentry[4], // username
770
						$cpentry[5], // sessionid
771
						$radiusservers,
772
						$cpentry[2], // clientip
773
						$cpentry[3]); // clientmac
774
				} else if ($cpcfg['reauthenticateacct'] == "interimupdate") {
775
					RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
776
						$cpentry[4], // username
777
						$cpentry[5], // sessionid
778
						$cpentry[0], // start time
779
						$radiusservers,
780
						$cpentry[2], // clientip
781
						$cpentry[3], // clientmac
782
						10, // NAS Request
783
						true); // Interim Updates
784
				}
785
			}
786

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

    
805
	captiveportal_prune_old_automac();
806

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

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

    
816
function captiveportal_prune_old_automac() {
817
	global $g, $config, $cpzone;
818

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

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

    
867
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
868

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

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

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

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

    
913
}
914

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

    
919
	$radiusservers = captiveportal_get_radius_servers();
920
	$unsetindex = array();
921

    
922
	/* read database */
923
	$cpdb = captiveportal_read_db();
924

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

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

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

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

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

    
972
	$ruleno = captiveportal_get_next_ipfw_ruleno();
973

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

    
988
	return $rules;
989
}
990

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

    
994
	$rules = "";
995

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

    
1002
		}
1003
	}
1004

    
1005
	return $rules;
1006
}
1007

    
1008
function captiveportal_passthrumac_findbyname($username) {
1009
	global $config, $cpzone;
1010

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

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

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

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

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

    
1047
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1048

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

    
1099
	return $rules;
1100
}
1101

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

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

    
1125
function captiveportal_allowedhostname_configure() {
1126
	global $config, $g, $cpzone;
1127

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

    
1137
function captiveportal_allowedip_configure() {
1138
	global $config, $g, $cpzone;
1139

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

    
1146
	return $rules;
1147
}
1148

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

    
1153
	$ipfwoutput = "";
1154

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

    
1164
	return 0;
1165
}
1166

    
1167
function captiveportal_init_radius_servers() {
1168
	global $config, $g, $cpzone;
1169

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

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

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

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

    
1221
		fclose($fd);
1222
		unlock($cprdsrvlck);
1223
	}
1224
}
1225

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

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

    
1257
	unlock($cprdsrvlck);
1258
	return false;
1259
}
1260

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

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

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

    
1286
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1287

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

    
1296
	$radiusservers = captiveportal_get_radius_servers();
1297

    
1298
	if (is_null($radiusctx))
1299
		$radiusctx = 'first';
1300

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

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

    
1319
	return $auth_list;
1320
}
1321

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

    
1326
	$cpdb = array();
1327

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1483
function captiveportal_get_ipfw_passthru_ruleno($value) {
1484
	global $config, $g, $cpzone;
1485

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

    
1490
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1491
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1492
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1493
		captiveportal_ipfw_set_context($cpzone);
1494
		$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`);
1495
		if ($rules[$ruleno]) {
1496
			unlock($cpruleslck);
1497
			return $ruleno;
1498
		}
1499
	}
1500

    
1501
	unlock($cpruleslck);
1502
	return NULL;
1503
}
1504

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

    
1516
function getVolume($ip) {
1517
	global $cpzone;
1518

    
1519
	$volume = array();
1520

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

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

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

    
1544
	return $volume;
1545
}
1546

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

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

    
1568
function getNasIP()
1569
{
1570
	global $config, $cpzone;
1571

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

    
1584
	return $nasIp;
1585
}
1586

    
1587
function portal_ip_from_client_ip($cliip) {
1588
	global $config, $cpzone;
1589

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

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

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

    
1616
	return false;
1617
}
1618

    
1619
/* functions move from index.php */
1620

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

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

    
1633
	$cpcfg = $config['captiveportal'][$cpzone];
1634

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

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

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

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

    
1674
    echo $htmltext;
1675
}
1676

    
1677
function portal_mac_radius($clientmac,$clientip) {
1678
    global $config, $cpzone;
1679

    
1680
    $radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1681

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

    
1690
    return FALSE;
1691
}
1692

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

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

    
1711
        unset($bw_up_pipeno, $bw_Down_pipeno, $bw_up, $bw_down);
1712
}
1713

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

    
1716
	global $redirurl, $g, $config, $type, $passthrumac, $_POST, $cpzone;
1717

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

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

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

    
1733
	$radiusservers = captiveportal_get_radius_servers();
1734

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

    
1738
	unset($sessionid);
1739

    
1740
	/* read in client database */
1741
	$cpdb = captiveportal_read_db(true);
1742

    
1743
	if ($attributes['voucher'])
1744
		$remaining_time = $attributes['session_timeout'];
1745

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

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

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

    
1826
	if ($attributes['voucher'] && $remaining_time <= 0)
1827
		return 0;       // voucher already used and no time left
1828

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

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

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

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

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

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

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

    
1883
			if ($attributes['voucher'])
1884
				$attributes['session_timeout'] = $remaining_time;
1885

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

    
1891
			/* rewrite information to database */
1892
			captiveportal_write_db($cpdb, true);
1893
			unlock($cpdblck);
1894

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

    
1905
	if ($writecfg == true)
1906
		write_config();
1907

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

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

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

    
1937
		if (isset($attributes['reply_message']))
1938
			$message = $attributes['reply_message'];
1939
		else
1940
			$message = 0;
1941

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

    
1944
	} else {
1945
		header("Location: " . $my_redirurl);
1946
	}
1947

    
1948
	return $sessionid;
1949
}
1950

    
1951

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

    
1961
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
1962
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
1963
	else
1964
		return false;
1965

    
1966
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
1967
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
1968
	else
1969
		return false;
1970

    
1971
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1972
		return false;
1973

    
1974
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
1975

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

    
1982
	$currenttime = time();
1983
	$found = false;
1984
	foreach ($usedmacs as $key => $usedmac) {
1985
		$usedmac = explode(",", $usedmac);
1986

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

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

    
2003
				$found = true;
2004
			} else
2005
				unset($usedmacs[$key]);
2006

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

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

    
2017
	captiveportal_write_usedmacs_db($usedmacs);
2018
	return true;
2019
}
2020

    
2021
function captiveportal_read_usedmacs_db() {
2022
	global $g, $cpzone;
2023

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

    
2032
	unlock($cpumaclck);
2033
	return $usedmacs;
2034
}
2035

    
2036
function captiveportal_write_usedmacs_db($usedmacs) {
2037
	global $g, $cpzone;
2038

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

    
2044
?>
(8-8/68)