Project

General

Profile

Bug #2633 » captiveportal.inc

Captive Portal Script (captiveportal.inc) - Carlos Pereira, 10/22/2012 06:07 PM

 
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 Ermal Lu?i <ermal.luci@gmail.com>
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	/sbin/kldunload
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
	// Detect if vouchers are being used and default to the voucher page
55
	if(isset($config['voucher'][$cpzone]['enable'])) {
56
			$htmltext = <<<EOD
57
<html> 
58
	<body> 
59
		<form method="post" action="\$PORTAL_ACTION\$"> 
60
			<input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$"> 
61
			<input name="zone" type="hidden" value="\$PORTAL_ZONE\$"> 
62
			<center>
63
				<table cellpadding="6" cellspacing="0" width="550" height="380" style="border:1px solid #000000">
64
					<tr height="10" bgcolor="#990000">
65
						<td style="border-bottom:1px solid #000000">
66
							<font color='white'>
67
								<b>
68
									Guest Voucher code required to continue
69
								</b>
70
							</font>
71
						</td>
72
					</tr>
73
					<tr>
74
						<td>
75
							<div id="mainlevel">
76
								<center>
77
									<table width="100%" border="0" cellpadding="5" cellspacing="0">
78
										<tr>
79
											<td>
80
												<center>
81
													<div id="mainarea">
82
														<center>
83
															<table width="100%" border="0" cellpadding="5" cellspacing="5">
84
																<tr>
85
																	<td>
86
																		<div id="maindivarea">
87
																			<center>
88
																				<div id='statusbox'>
89
																					<font color='red' face='arial' size='+1'>
90
																						<b>
91
																							\$PORTAL_MESSAGE\$
92
																						</b>
93
																					</font>
94
																				</div>
95
																				<p/>
96
																				<div id='loginbox'>
97
																					Enter Voucher Code: 
98
																					<input name="auth_voucher" type="text" style="border:1px dashed;" size="22"> 
99
																					<input name="accept" type="submit" value="Continue"> 
100
																				</div>
101
																			</center>
102
																		</div>
103
																	</td>
104
																</tr>
105
															</table>
106
														</center>
107
													</div>
108
												</center>
109
											</td>
110
										</tr>
111
									</table>
112
								</center>
113
							</div>
114
						</td>
115
					</tr>
116
				</table>
117
			</center>
118
		</form>
119
	</body> 
120
</html>
121

    
122
EOD;
123
		return $htmltext;
124
	}
125

    
126
	// Vouchers are not found, return the normal user/pass auth page
127
	$htmltext = <<<EOD
128
<html> 
129
	<body> 
130
		<form method="post" action="\$PORTAL_ACTION\$"> 
131
		<input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$">
132
		<input name="zone" type="hidden" value="\$PORTAL_ZONE\$">
133
			<center>
134
				<table cellpadding="6" cellspacing="0" width="550" height="380" style="border:1px solid #000000">
135
					<tr height="10" bgcolor="#990000">
136
						<td style="border-bottom:1px solid #000000">
137
							<font color='white'>
138
								<b>
139
									{$g['product_name']} captive portal
140
								</b>
141
							</font>
142
						</td>
143
					</tr>
144
					<tr>
145
						<td>
146
							<div id="mainlevel">
147
								<center>
148
									<table width="100%" border="0" cellpadding="5" cellspacing="0">
149
										<tr>
150
											<td>
151
												<center>
152
													<div id="mainarea">
153
														<center>
154
															<table width="100%" border="0" cellpadding="5" cellspacing="5">
155
																<tr>
156
																	<td>
157
																		<div id="maindivarea">
158
																			<center>
159
																				<div id='statusbox'>
160
																					<font color='red' face='arial' size='+1'>
161
																						<b>
162
																							\$PORTAL_MESSAGE\$
163
																						</b>
164
																					</font>
165
																				</div>
166
																				<br/>
167
																				<div id='loginbox'>
168
																					<table>
169
																					   <tr><td colspan="2"><center>Welcome to the {$g['product_name']} Captive Portal!</td></tr>
170
																					   <tr><td>&nbsp;</td></tr>
171
																					   <tr><td align="right">Username:</td><td><input name="auth_user" type="text" style="border: 1px dashed;"></td></tr>
172
																					   <tr><td align="right">Password:</td><td><input name="auth_pass" type="password" style="border: 1px dashed;"></td></tr>
173
																					   <tr><td>&nbsp;</td></tr>
174
																					   <tr>
175
																						 <td colspan="2">
176
																							<center><input name="accept" type="submit" value="Continue"></center>
177
																						 </td>
178
																					   </tr>
179
																					</table>
180
																				</div>
181
																			</center>
182
																		</div>
183
																	</td>
184
																</tr>
185
															</table>
186
														</center>
187
													</div>
188
												</center>
189
											</td>
190
										</tr>
191
									</table>
192
								</center>
193
							</div>
194
						</td>
195
					</tr>
196
				</table>
197
			</center>
198
		</form>
199
	</body> 
200
</html>
201

    
202
EOD;
203

    
204
	return $htmltext;
205
}
206

    
207
function captiveportal_configure() {
208
	global $config, $cpzone;
209

    
210
	if (is_array($config['captiveportal'])) {
211
		mwexec("/sbin/sysctl net.link.ether.ipfw=1");
212
		foreach ($config['captiveportal'] as $cpkey => $cp) {
213
			$cpzone = $cpkey;
214
			captiveportal_configure_zone($cp);
215
		}
216
	} else
217
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
218
}
219

    
220
function captiveportal_ipfw_set_context($cpzone) {
221
	mwexec("/usr/local/sbin/ipfw_context -s {$cpzone}", true);
222
}
223

    
224
function captiveportal_configure_zone($cpcfg) {
225
	global $config, $g, $cpzone;
226

    
227
	$captiveportallck = lock("captiveportal", LOCK_EX);
228
	
229
	if (isset($cpcfg['enable'])) {
230

    
231
		if ($g['booting'])
232
			echo "Starting captive portal({$cpcfg['zone']})... ";
233
		else
234
			captiveportal_syslog("Restarting captive portal({$cpcfg['zone']}).");
235

    
236
		/* kill any running mini_httpd */
237
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
238
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
239

    
240
		/* remove old information */
241
		unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.db");
242
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac_{$cpzone}.db");
243
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip_{$cpzone}.db");
244
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
245

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

    
249
		/* kill any running minicron */
250
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
251

    
252
		mwexec("/usr/local/sbin/ipfw_context -a {$cpzone}", true);
253
		captiveportal_ipfw_set_context($cpzone);
254

    
255
		/* init ipfw rules */
256
		captiveportal_init_rules(true);
257

    
258
		/* initialize minicron interval value */
259
		$croninterval = $cpcfg['croninterval'] ? $cpcfg['croninterval'] : 60;
260

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

    
265
		/* write portal page */
266
		if (is_array($cpcfg['page']) && $cpcfg['page']['htmltext'])
267
			$htmltext = base64_decode($cpcfg['page']['htmltext']);
268
		else {
269
			/* example/template page */
270
			$htmltext = get_default_captive_portal_html();
271
		}
272

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

    
292
		/* write error page */
293
		if (is_array($cpcfg['page']) && $cpcfg['page']['errtext'])
294
			$errtext = base64_decode($cpcfg['page']['errtext']);
295
		else {
296
			/* example page  */
297
			$errtext = get_default_captive_portal_html();
298
		}
299

    
300
		$fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html", "w");
301
		if ($fd) {
302
			// Special case handling.  Convert so that we can pass this page
303
			// through the PHP interpreter later without clobbering the vars.
304
			$errtext = str_replace("\$PORTAL_ZONE\$", "#PORTAL_ZONE#", $errtext);
305
			$errtext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $errtext);
306
			$errtext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $errtext);
307
			$errtext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $errtext);
308
			$errtext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $errtext);
309
			$errtext = str_replace("\$ORIGINAL_PORTAL_IP\$", "#ORIGINAL_PORTAL_IP#", $errtext);
310
			$errtext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $errtext);
311
			if($cpcfg['preauthurl']) {
312
				$errtext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $errtext);
313
				$errtext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $errtext);
314
			}
315
			fwrite($fd, $errtext);
316
			fclose($fd);
317
		}
318

    
319
		/* write logout page */
320
		if (is_array($cpcfg['page']) && $cpcfg['page']['logouttext'])
321
			$logouttext = base64_decode($cpcfg['page']['logouttext']);
322
		else {
323
			/* example page */
324
			$logouttext = <<<EOD
325
<HTML>
326
<HEAD><TITLE>Redirecting...</TITLE></HEAD>
327
<BODY>
328
<SPAN STYLE="font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">
329
<B>Redirecting to <A HREF="<?=\$my_redirurl;?>"><?=\$my_redirurl;?></A>...</B>
330
</SPAN>
331
<SCRIPT LANGUAGE="JavaScript">
332
<!--
333
LogoutWin = window.open('', 'Logout', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=256,height=64');
334
if (LogoutWin) {
335
	LogoutWin.document.write('<HTML>');
336
	LogoutWin.document.write('<HEAD><TITLE>Logout</TITLE></HEAD>') ;
337
	LogoutWin.document.write('<BODY BGCOLOR="#435370">');
338
	LogoutWin.document.write('<DIV ALIGN="center" STYLE="color: #ffffff; font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">') ;
339
	LogoutWin.document.write('<B>Click the button below to disconnect</B><P>');
340
	LogoutWin.document.write('<FORM METHOD="POST" ACTION="<?=\$logouturl;?>">');
341
	LogoutWin.document.write('<INPUT NAME="logout_id" TYPE="hidden" VALUE="<?=\$sessionid;?>">');
342
	LogoutWin.document.write('<INPUT NAME="zone" TYPE="hidden" VALUE="<?=\$cpzone;?>">');
343
	LogoutWin.document.write('<INPUT NAME="logout" TYPE="submit" VALUE="Logout">');
344
	LogoutWin.document.write('</FORM>');
345
	LogoutWin.document.write('</DIV></BODY>');
346
	LogoutWin.document.write('</HTML>');
347
	LogoutWin.document.close();
348
}
349

    
350
document.location.href="<?=\$my_redirurl;?>";
351
-->
352
</SCRIPT>
353
</BODY>
354
</HTML>
355

    
356
EOD;
357
		}
358

    
359
		$fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html", "w");
360
		if ($fd) {
361
			fwrite($fd, $logouttext);
362
			fclose($fd);
363
		}
364
		/* write elements */
365
		captiveportal_write_elements();
366

    
367
		/* start up the webserving daemon */
368
		captiveportal_init_webgui_zone($cpcfg);
369

    
370
		/* Kill any existing prunecaptiveportal processes */
371
		if (file_exists("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid"))
372
			killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
373

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

    
378
		/* generate radius server database */
379
		captiveportal_init_radius_servers();
380

    
381
		if ($g['booting'])
382
			echo "done\n";
383

    
384
	} else {
385
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
386
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
387
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
388
		@unlink("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
389
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
390
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
391
		/* remove old information */
392
		unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.db");
393
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac_{$cpzone}.db");
394
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip_{$cpzone}.db");
395
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
396

    
397
		captiveportal_radius_stop_all();
398

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

    
401
		if (empty($config['captiveportal']))
402
			mwexec("/sbin/sysctl net.link.ether.ipfw=0");
403

    
404
		/* unload ipfw */
405
		$listifs = get_configured_interface_list();
406
		$cpinterfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
407
		foreach ($cpinterfaces as $cpifgrp) {
408
			if (!isset($listifs[$cpifgrp]))
409
				continue;
410
			$listrealif = get_real_interface($cpifgrp);
411
			if (does_interface_exist($listrealif)) {
412
				pfSense_interface_flags($listrealif, -IFF_IPFW_FILTER);
413
				$carpif = link_ip_to_carp_interface(find_interface_ip($listrealif));
414
				if (!empty($carpif)) {
415
					$carpsif = explode(" ", $carpif);
416
					foreach ($carpsif as $cpcarp)
417
						pfSense_interface_flags($cpcarp, -IFF_IPFW_FILTER);
418
				}
419
			}
420
		}
421
	}
422

    
423
	unlock($captiveportallck);
424
	
425
	return 0;
426
}
427

    
428
function captiveportal_init_webgui() {
429
	global $config, $cpzone;
430

    
431
	if (is_array($config['captiveportal'])) {
432
		foreach ($config['captiveportal'] as $cpkey => $cp) {
433
			$cpzone = $cpkey;
434
			captiveportal_init_webgui_zone($cp);
435
		}
436
	}
437
}
438

    
439
function captiveportal_init_webgui_zonename($zone) {
440
	global $config, $cpzone;
441
	
442
	if (isset($config['captiveportal'][$zone])) {
443
		$cpzone = $zone;
444
		captiveportal_init_webgui_zone($config['captiveportal'][$zone]);
445
	}
446
}
447

    
448
function captiveportal_init_webgui_zone($cpcfg) {
449
	global $g, $config, $cpzone;
450

    
451
	if (!isset($cpcfg['enable']))
452
		return;
453

    
454
	$use_fastcgi = true;
455

    
456
	if (isset($cpcfg['httpslogin'])) {
457
		$cert = lookup_cert($cpcfg['certref']);
458
		$crt = base64_decode($cert['crt']);
459
		$key = base64_decode($cert['prv']);
460
		$ca = ca_chain($cert);
461
    
462
		/* generate lighttpd configuration */
463
		/* CHANGE: generate certificate files per zone allowing for multiple ssl zones with different certificates */ 
464
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
465
		system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf",
466
			$crt, $key, $ca, "lighty-{$cpzone}-CaptivePortal-SSL.pid", $listenporthttps, "/usr/local/captiveportal",
467
			"cert-portal-{$cpzone}.pem", "ca-portal-{$cpzone}.pem", "1", $use_fastcgi, $cpzone);
468
	}
469

    
470
	/* generate lighttpd configuration */
471
	$listenporthttp = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : $cpcfg['zoneid'];
472
	system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal.conf",
473
		"", "", "", "lighty-{$cpzone}-CaptivePortal.pid", $listenporthttp, "/usr/local/captiveportal",
474
		"cert-portal-{$cpzone}.pem", "ca-portal-{$cpzone}.pem", "1", $use_fastcgi, $cpzone);
475

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

    
479
	/* fire up https instance */
480
	if (isset($cpcfg['httpslogin']))
481
		$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf");
482
}
483

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

    
488
	if (!isset($config['captiveportal'][$cpzone]['enable']))
489
		return;
490

    
491
	$cpips = array();
492
	$ifaces = get_configured_interface_list();
493
	$cpinterfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
494
	$firsttime = 0;
495
	foreach ($cpinterfaces as $cpifgrp) {
496
		if (!isset($ifaces[$cpifgrp]))
497
			continue;
498
		$tmpif = get_real_interface($cpifgrp);
499
		if (!empty($tmpif)) {
500
			$cpipm = get_interface_ip($cpifgrp);
501
			if (is_ipaddr($cpipm)) {
502
				$carpif = link_ip_to_carp_interface($cpipm);
503
				if (!empty($carpif)) {
504
					$carpsif = explode(" ", $carpif);
505
					foreach ($carpsif as $cpcarp) {
506
						mwexec("/usr/local/sbin/ipfw_context -a {$cpzone} -n {$cpcarp}", true);
507
						pfSense_interface_flags($cpcarp, IFF_IPFW_FILTER);
508
						$carpip = find_interface_ip($cpcarp);
509
						if (is_ipaddr($carpip))
510
							$cpips[] = $carpip;
511
					}
512
				}
513
				$cpips[] = $cpipm;
514
			}
515
			mwexec("/usr/local/sbin/ipfw_context -a {$cpzone} -n {$tmpif}", true);
516
			pfSense_interface_flags($tmpif, IFF_IPFW_FILTER);
517
		}
518
	}
519
	if (count($cpips) > 0) {
520
		$cpactive = true;
521
	} else
522
		return false;
523

    
524
	if ($reinit == false)
525
		$captiveportallck = lock("captiveportal");
526

    
527
	/* init dummynet/ipfw rules number database */
528
	captiveportal_init_ipfw_ruleno();
529

    
530
	/* make sure ipfw is loaded */
531
	if (!is_module_loaded("ipfw.ko"))
532
		filter_load_ipfw();
533
	/* Always load dummynet now that even allowed ip and mac passthrough use it. */
534
	if (!is_module_loaded("dummynet.ko"))
535
		mwexec("/sbin/kldload dummynet");
536

    
537
	$cprules =	"add 65291 set 1 allow pfsync from any to any\n";
538
	$cprules .= "add 65292 set 1 allow carp from any to any\n";
539

    
540
	$cprules .= <<<EOD
541
# add 65300 set 1 skipto 65534 all from any to any not layer2
542
# layer 2: pass ARP
543
add 65301 set 1 pass layer2 mac-type arp
544
# pfsense requires for WPA
545
add 65302 set 1 pass layer2 mac-type 0x888e
546
add 65303 set 1 pass layer2 mac-type 0x88c7
547

    
548
# PPP Over Ethernet Discovery Stage
549
add 65304 set 1 pass layer2 mac-type 0x8863
550
# PPP Over Ethernet Session Stage
551
add 65305 set 1 pass layer2 mac-type 0x8864
552

    
553
# layer 2: block anything else non-IP
554
add 65307 set 1 deny layer2 not mac-type ip
555

    
556
EOD;
557

    
558
	$rulenum = 65310;
559
	$ipcount = 0;
560
	$ips = "";
561
	foreach ($cpips as $cpip) {
562
		if($ipcount == 0) {
563
			$ips = "{$cpip} ";
564
		} else {
565
			$ips .= "or {$cpip} ";
566
		}
567
		$ipcount++;
568
	}
569
	$ips = "{ 255.255.255.255 or {$ips} }";
570
	$cprules .= "add {$rulenum} set 1 pass ip from any to {$ips} in\n";
571
	$rulenum++;
572
	$cprules .= "add {$rulenum} set 1 pass ip from {$ips} to any out\n";
573
	$rulenum++;
574
	$cprules .= "add {$rulenum} set 1 pass icmp from {$ips} to any out icmptype 0\n";
575
	$rulenum++;
576
	$cprules .= "add {$rulenum} set 1 pass icmp from any to {$ips} in icmptype 8 \n";
577
	$rulenum++;
578
	/* Allowed ips */
579
	$cprules .= "add {$rulenum} allow ip from table(3) to any in\n";
580
	$rulenum++;
581
	$cprules .= "add {$rulenum} allow ip from any to table(4) out\n";
582
	$rulenum++;
583
	$cprules .= "add {$rulenum} pipe tablearg ip from table(5) to any in\n";
584
	$rulenum++;
585
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(6) out\n";
586
	$rulenum++;
587
	$cprules .= "add {$rulenum} allow ip from any to table(7) in\n";
588
	$rulenum++;
589
	$cprules .= "add {$rulenum} allow ip from table(8) to any out\n";
590
	$rulenum++;
591
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(9) in\n";
592
	$rulenum++;
593
	$cprules .= "add {$rulenum} pipe tablearg ip from table(10) to any out\n";
594
	$rulenum++;
595

    
596
	/* Authenticated users rules. */
597
	if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
598
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from table(1) to any in\n";
599
		$rulenum++;
600
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from any to table(2) out\n";
601
		$rulenum++;
602
	} else {
603
		$cprules .= "add {$rulenum} set 1 allow ip from table(1) to any in\n";
604
		$rulenum++;
605
		$cprules .= "add {$rulenum} set 1 allow ip from any to table(2) out\n";
606
		$rulenum++;
607
	}
608

    
609
	
610
	$listenporthttp =
611
		$config['captiveportal'][$cpzone]['listenporthttp'] ?
612
		$config['captiveportal'][$cpzone]['listenporthttp'] :
613
		$config['captiveportal'][$cpzone]['zoneid'];
614
	
615
	$cprules .= <<<EOD
616

    
617
# redirect non-authenticated clients to captive portal
618
add 65531 set 1 fwd 127.0.0.1,{$listenporthttp} tcp from any to any in
619
# let the responses from the captive portal web server back out
620
add 65532 set 1 pass tcp from any to any out
621
# block everything else
622
add 65533 set 1 deny all from any to any
623
# pass everything else on layer2
624
add 65534 set 1 pass all from any to any layer2
625

    
626
EOD;
627

    
628
	/* generate passthru mac database */
629
	$cprules .= captiveportal_passthrumac_configure(true);
630
	$cprules .= "\n";
631

    
632
	/* allowed ipfw rules to make allowed ip work */
633
	$cprules .= captiveportal_allowedip_configure();
634

    
635
	/* allowed ipfw rules to make allowed hostnames work */
636
	$cprules .= captiveportal_allowedhostname_configure();
637
	
638
	/* load rules */
639
	if ($reinit == true)
640
		$cprules = "table all flush\nflush\n{$cprules}";
641
	else {
642
		$tmprules = "table 3 flush\n";
643
		$tmprules .= "table 4 flush\n";
644
		$tmprules .= "table 5 flush\n";
645
		$tmprules .= "table 6 flush\n";
646
		$tmprules .= "table 7 flush\n";
647
		$tmprules .= "table 8 flush\n";
648
		$tmprules .= "table 9 flush\n";
649
		$tmprules .= "table 10 flush\n";
650
		$tmprules .= "flush\n";
651
		$cprules = "{$tmprules}\n{$cprules}";
652
	}
653

    
654
	file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
655
	captiveportal_ipfw_set_context($cpzone);
656
	mwexec("/sbin/ipfw -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
657
	//@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
658

    
659
	if ($reinit == false)
660
		unlock($captiveportallck);
661

    
662
	/* activate ipfw(4) so CP can work */
663
	mwexec("/sbin/sysctl net.link.ether.ipfw=1");
664
	/* Make sure not re-entrancy is allowed in ipfw(4) */
665
	mwexec("/sbin/sysctl net.inet.ip.fw.one_pass=1");
666

    
667
	return $cprules;
668
}
669

    
670
/* remove clients that have been around for longer than the specified amount of time
671
 * db file structure:
672
 * timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time
673
 * (password is in Base64 and only saved when reauthentication is enabled)
674
 */
675
function captiveportal_prune_old() {
676
	global $g, $config, $cpzone;
677

    
678
	if (empty($cpzone))
679
		return;
680

    
681
	/* check for expired entries */
682
	if (empty($config['captiveportal'][$cpzone]['timeout']) ||
683
	!is_numeric($config['captiveportal'][$cpzone]['timeout']))
684
		$timeout = 0;
685
	else
686
		$timeout = $config['captiveportal'][$cpzone]['timeout'] * 60;
687

    
688
	if (empty($config['captiveportal'][$cpzone]['idletimeout']) ||
689
	!is_numeric($config['captiveportal'][$cpzone]['idletimeout']))
690
		$idletimeout = 0;
691
	else
692
		$idletimeout = $config['captiveportal'][$cpzone]['idletimeout'] * 60;
693

    
694
	if (!$timeout && !$idletimeout && !isset($config['captiveportal'][$cpzone]['reauthenticate']) && 
695
	!isset($config['captiveportal'][$cpzone]['radiussession_timeout']) && !isset($config['voucher'][$cpzone]['enable']))
696
		return;
697

    
698
	$radiussrvs = captiveportal_get_radius_servers();
699

    
700
	/* read database */
701
	$cpdb = captiveportal_read_db();
702

    
703
	/*	To make sure we iterate over ALL accounts on every run the count($cpdb) is moved
704
	 *	outside of the loop. Otherwise the loop would evaluate count() on every iteration
705
	 *	and since $i would increase and count() would decrement they would meet before we
706
	 *	had a chance to iterate over all accounts.
707
	 */
708
	$unsetindexes = array();
709
	$voucher_needs_sync = false;
710
	/* 
711
	 * Snapshot the time here to use for calculation to speed up the process.
712
	 * If something is missed next run will catch it!
713
	 */
714
	$pruning_time = time();
715
	$stop_time = $pruning_time;
716
	foreach ($cpdb as $cpentry) {
717

    
718
		$timedout = false;
719
		$term_cause = 1;
720
		if (empty($cpentry[10]))
721
			$cpentry[10] = 'first';
722
		$radiusservers = $radiussrvs[$cpentry[10]];
723

    
724
		/* hard timeout? */
725
		if ($timeout) {
726
			if (($pruning_time - $cpentry[0]) >= $timeout) {
727
				$timedout = true;
728
				$term_cause = 5; // Session-Timeout
729
			}
730
		}
731

    
732
		/* Session-Terminate-Time */
733
		if (!$timedout && !empty($cpentry[9])) {
734
			if ($pruning_time >= $cpentry[9]) {
735
				$timedout = true;
736
				$term_cause = 5; // Session-Timeout
737
			}
738
		}
739

    
740
		/* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
741
		$uidletimeout = (is_numeric($cpentry[8])) ? $cpentry[8] : $idletimeout;
742
		/* if an idle timeout is specified, get last activity timestamp from ipfw */
743
		if (!$timedout && $uidletimeout) {
744
			$lastact = captiveportal_get_last_activity($cpentry[2]);
745
			/*	If the user has logged on but not sent any traffic they will never be logged out.
746
			 *	We "fix" this by setting lastact to the login timestamp. 
747
			 */
748
			$lastact = $lastact ? $lastact : $cpentry[0];
749
			if ($lastact && (($pruning_time - $lastact) >= $uidletimeout)) {
750
				$timedout = true;
751
				$term_cause = 4; // Idle-Timeout
752
				$stop_time = $lastact; // Entry added to comply with WISPr
753
			}
754
		}
755

    
756
		/* if vouchers are configured, activate session timeouts */
757
		if (!$timedout && isset($config['voucher'][$cpzone]['enable']) && !empty($cpentry[7])) {
758
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
759
				$timedout = true;
760
				$term_cause = 5; // Session-Timeout
761
				$voucher_needs_sync = true;
762
			}
763
		}
764

    
765
		/* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
766
		if (!$timedout && isset($config['captiveportal'][$cpzone]['radiussession_timeout']) && !empty($cpentry[7])) {
767
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
768
				$timedout = true;
769
				$term_cause = 5; // Session-Timeout
770
			}
771
		}
772

    
773
		if ($timedout) {
774
			captiveportal_disconnect($cpentry, $radiusservers,$term_cause,$stop_time);
775
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
776
			$unsetindexes[] = $cpentry[5];
777
		}
778

    
779
		/* do periodic RADIUS reauthentication? */
780
		if (!$timedout && !empty($radiusservers)) {
781
			if (isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
782
				if ($config['captiveportal'][$cpzone]['reauthenticateacct'] == "stopstart") {
783
					/* stop and restart accounting */
784
					RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
785
						$cpentry[4], // username
786
						$cpentry[5], // sessionid
787
						$cpentry[0], // start time
788
						$radiusservers,
789
						$cpentry[2], // clientip
790
						$cpentry[3], // clientmac
791
						10); // NAS Request
792
					captiveportal_ipfw_set_context($cpzone);
793
					exec("/sbin/ipfw table 1 entryzerostats {$cpentry[2]}");
794
					exec("/sbin/ipfw table 2 entryzerostats {$cpentry[2]}");
795
					RADIUS_ACCOUNTING_START($cpentry[1], // ruleno
796
						$cpentry[4], // username
797
						$cpentry[5], // sessionid
798
						$radiusservers,
799
						$cpentry[2], // clientip
800
						$cpentry[3]); // clientmac
801
				} else if ($config['captiveportal'][$cpzone]['reauthenticateacct'] == "interimupdate") {
802
					RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
803
						$cpentry[4], // username
804
						$cpentry[5], // sessionid
805
						$cpentry[0], // start time
806
						$radiusservers,
807
						$cpentry[2], // clientip
808
						$cpentry[3], // clientmac
809
						10, // NAS Request
810
						true); // Interim Updates
811
				}
812
			}
813

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

    
832
	if ($voucher_needs_sync == true)
833
		/* Triger a sync of the vouchers on config */
834
		send_event("service sync vouchers");
835

    
836
	/* write database */
837
	if (!empty($unsetindexes))
838
		captiveportal_write_db($cpdb, false, $unsetindexes);
839
}
840

    
841
/* remove a single client according to the DB entry */
842
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
843
	global $g, $config, $cpzone;
844

    
845
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
846

    
847
	/* Do not allow concurrent login execution. */
848
	$cpdblck = lock("captiveportaldb", LOCK_EX);
849
		
850
	/* this client needs to be deleted - remove ipfw rules */
851
	if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers)) {
852
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
853
			$dbent[4], // username
854
			$dbent[5], // sessionid
855
			$dbent[0], // start time
856
			$radiusservers,
857
			$dbent[2], // clientip
858
			$dbent[3], // clientmac
859
			$term_cause, // Acct-Terminate-Cause
860
			false,
861
			$stop_time);
862
	}
863
	
864
	if (is_ipaddr($dbent[2])) {
865
		captiveportal_ipfw_set_context($cpzone);
866
		/* Delete client's ip entry from tables 3 and 4. */
867
		mwexec("/sbin/ipfw table 1 delete {$dbent[2]}");
868
		mwexec("/sbin/ipfw table 2 delete {$dbent[2]}");
869
		/* XXX: Redundant?! Ensure all pf(4) states are killed. */
870
		mwexec("pfctl -k {$dbent[2]}");
871
		mwexec("pfctl -K {$dbent[2]}");
872
	}
873

    
874
	/* 
875
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
876
	* We could get an error if the pipe doesn't exist but everything should still be fine
877
	*/
878
	if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
879
		captiveportal_ipfw_set_context($cpzone);
880
		mwexec("/sbin/ipfw pipe " . ($dbent[1]+20000) . " delete");
881
		mwexec("/sbin/ipfw pipe " . ($dbent[1]+20001) . " delete");
882
	}
883

    
884
	/* Release the ruleno so it can be reallocated to new clients. */
885
	captiveportal_free_ipfw_ruleno($dbent[1]);
886
	
887
	/* Release the concurrent execution lock */
888
	unlock($cpdblck);
889

    
890
	// XMLRPC Call over to the master Voucher node
891
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
892
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
893
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
894
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
895
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
896
		$remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time);
897
	}
898

    
899
}
900

    
901
/* remove a single client by sessionid */
902
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
903
	global $g, $config, $cpzone;
904

    
905
	$radiusservers = captiveportal_get_radius_servers();
906
	$unsetindex = array();
907

    
908
	/* read database */
909
	$cpdb = captiveportal_read_db();
910

    
911
	/* find entry */
912
	if (isset($cpdb[$sessionid])) {
913
		$cpentry = $cpdb[$sessionid];
914
		/* write database */
915
		$unsetindex[] = $sessionid;
916
		captiveportal_write_db($cpdb, false, $unsetindex);
917
		if (empty($cpentry[10]))
918
			$cpentry[10] = 'first';
919
		captiveportal_disconnect($cpentry, $radiusservers[$cpentry[10]], $term_cause);
920
		captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
921
	}		
922
}
923

    
924
/* send RADIUS acct stop for all current clients */
925
function captiveportal_radius_stop_all() {
926
	global $config, $cpzone;
927

    
928
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable']))
929
		return;
930

    
931
	$radiusservers = captiveportal_get_radius_servers();
932
	if (!empty($radiusservers)) {
933
		$cpdb = captiveportal_read_db();
934
		foreach ($cpdb as $cpentry) {
935
			if (empty($cpentry[10]))
936
				$cpentry[10] = 'first';
937
			if (!empty($radiusservers[$cpentry[10]])) {
938
				RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
939
					$cpentry[4], // username
940
					$cpentry[5], // sessionid
941
					$cpentry[0], // start time
942
					$radiusservers[$cpentry[10]],
943
					$cpentry[2], // clientip
944
					$cpentry[3], // clientmac
945
					7); // Admin Reboot
946
			}
947
		}
948
	}
949
}
950

    
951
function captiveportal_passthrumac_configure_entry($macent) {
952
	$rules = "";
953
	$enBwup = isset($macent['bw_up']);
954
	$enBwdown = isset($macent['bw_down']);
955
	$actionup = "allow";
956
	$actiondown = "allow";
957

    
958
	$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
959

    
960
	if ($enBwup) {
961
		$bw_up = $ruleno + 20000;
962
		$rules .= "pipe {$bw_up} config bw {$macent['bw_up']}Kbit/s queue 100\n";
963
		$actionup = "pipe {$bw_up}";
964
	}
965
	if ($enBwdown) {
966
		$bw_down = $ruleno + 20001;
967
		$rules .= "pipe {$bw_down} config bw {$macent['bw_down']}Kbit/s queue 100\n";
968
		$actiondown = "pipe {$bw_down}";
969
	}
970
	$rules .= "add {$ruleno} {$actiondown} ip from any to any MAC {$macent['mac']} any\n";
971
	$ruleno++;
972
	$rules .= "add {$ruleno} {$actionup} ip from any to any MAC any {$macent['mac']}\n";
973

    
974
	return $rules;
975
}
976

    
977
function captiveportal_passthrumac_configure($lock = false) {
978
	global $config, $g, $cpzone;
979

    
980
	$rules = "";
981

    
982
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
983
		$macdb = array();
984
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
985
			$rules .= captiveportal_passthrumac_configure_entry($macent);
986
			$macdb[$macent['mac']][$cpzone]['active']  = true;
987

    
988
		}
989
	}
990

    
991
	return $rules;
992
}
993

    
994
function captiveportal_passthrumac_findbyname($username) {
995
	global $config, $cpzone;
996

    
997
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
998
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
999
			if ($macent['username'] == $username)
1000
				return $macent;
1001
		}
1002
	}
1003
	return NULL;
1004
}
1005

    
1006
/* 
1007
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1008
 * table (5=IN)/(6=OUT) hold allowed ip's with bw limit.
1009
 */
1010
function captiveportal_allowedip_configure_entry($ipent) {
1011

    
1012
	/* This function can deal with hostname or ipaddress */
1013
	if($ipent['ip']) 	
1014
		$ipaddress = $ipent['ip'];
1015

    
1016
	/*  Instead of copying this entire function for something
1017
	 *  easy such as hostname vs ip address add this check
1018
	 */
1019
	if($ipent['hostname']) {
1020
		$ipaddress = gethostbyname($ipent['hostname']);
1021
		if(!is_ipaddr($ipaddress)) 
1022
			return;
1023
	}
1024

    
1025
	$rules = "";
1026
	$enBwup = intval($ipent['bw_up']);
1027
	$enBwdown = intval($ipent['bw_down']);
1028
	$bw_up = "";
1029
	$bw_down = "";
1030
	$tablein = array();
1031
	$tableout = array();
1032

    
1033
	if (intval($enBwup) > 0 or intval($enBwdown) > 0)
1034
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
1035
	else
1036
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
1037

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

    
1088
	return $rules;
1089
}
1090

    
1091
/* 
1092
	Adds a dnsfilter entry and watches for hostname changes.
1093
	A change results in reloading the ruleset.
1094
*/
1095
function setup_dnsfilter_entries() {
1096
	global $g, $config, $cpzone;
1097

    
1098
	$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1099
	$cp_filterdns_conf = "";
1100
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1101
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent)  {
1102
			$cp_filterdns_conf .= "ipfw {$hostnameent['hostname']} 3\n";
1103
			$cp_filterdns_conf .= "ipfw {$hostnameent['hostname']} 4\n";
1104
			$cp_filterdns_conf .= "ipfw {$hostnameent['hostname']} 7\n";
1105
			$cp_filterdns_conf .= "ipfw {$hostnameent['hostname']} 8\n";
1106
		}
1107
	}
1108
	file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1109
	killbypid("{$g['tmp_path']}/filterdns-{$cpzone}-cpah.pid");
1110
	mwexec("/usr/local/sbin/filterdns -p {$g['tmp_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzone} -d 1");
1111
}
1112

    
1113
function captiveportal_allowedhostname_configure() {
1114
	global $config, $g, $cpzone;
1115

    
1116
	$rules = "\n# captiveportal_allowedhostname_configure()\n";
1117
	setup_dnsfilter_entries();
1118
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1119
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) 
1120
			$rules .= captiveportal_allowedip_configure_entry($hostnameent);
1121
	}
1122
	return $rules;
1123
}
1124

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

    
1128
	$rules = "";
1129
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1130
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1131
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1132
	}
1133

    
1134
	return $rules;
1135
}
1136

    
1137
/* get last activity timestamp given client IP address */
1138
function captiveportal_get_last_activity($ip) {
1139
	global $cpzone;
1140

    
1141
	$ipfwoutput = "";
1142

    
1143
	captiveportal_ipfw_set_context($cpzone);
1144
	exec("/sbin/ipfw table 1 entrystats {$ip} 2>/dev/null", $ipfwoutput);
1145
	/* Reading only from one of the tables is enough of approximation. */
1146
	if ($ipfwoutput[0]) {
1147
		$ri = explode(" ", $ipfwoutput[0]);
1148
		if ($ri[4])
1149
			return $ri[4];
1150
	}
1151

    
1152
	return 0;
1153
}
1154

    
1155
function captiveportal_init_radius_servers() {
1156
	global $config, $g, $cpzone;
1157

    
1158
	/* generate radius server database */
1159
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1160
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1161
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1162
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1163
		$radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
1164
		$radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
1165

    
1166
		if ($config['captiveportal'][$cpzone]['radiusport'])
1167
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1168
		else
1169
			$radiusport = 1812;
1170
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1171
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1172
		else
1173
			$radiusacctport = 1813;
1174
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1175
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1176
		else
1177
			$radiusport2 = 1812;
1178
		if ($config['captiveportal'][$cpzone]['radiusport3'])
1179
			$radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
1180
		else
1181
			$radiusport3 = 1812;
1182
		if ($config['captiveportal'][$cpzone]['radiusport4'])
1183
			$radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
1184
		else
1185
			$radiusport4 = 1812;
1186

    
1187
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1188
		$radiuskey2 = ($config['captiveportal'][$cpzone]['radiuskey2']) ? $config['captiveportal'][$cpzone]['radiuskey2'] : null;
1189
		$radiuskey3 = ($config['captiveportal'][$cpzone]['radiuskey3']) ? $config['captiveportal'][$cpzone]['radiuskey3'] : null;
1190
		$radiuskey4 = ($config['captiveportal'][$cpzone]['radiuskey4']) ? $config['captiveportal'][$cpzone]['radiuskey4'] : null;
1191

    
1192
		$cprdsrvlck = lock("captiveportalradius", LOCK_EX);
1193
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
1194
		if (!$fd) {
1195
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1196
			unlock($cprdsrvlck);
1197
			return 1;
1198
		}
1199
		if (isset($radiusip, $radiuskey))
1200
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
1201
		if (isset($radiusip2, $radiuskey2))
1202
			fwrite($fd,"\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
1203
		if (isset($radiusip3, $radiuskey3))
1204
			fwrite($fd,"\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
1205
		if (isset($radiusip4, $radiuskey4))
1206
			fwrite($fd,"\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
1207
		
1208

    
1209
		fclose($fd);
1210
		unlock($cprdsrvlck);
1211
	}
1212
}
1213

    
1214
/* read RADIUS servers into array */
1215
function captiveportal_get_radius_servers() {
1216
	global $g, $cpzone;
1217

    
1218
	$cprdsrvlck = lock("captiveportalradius");
1219
	if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
1220
		$radiusservers = array();
1221
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", 
1222
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1223
		if ($cpradiusdb) {
1224
			foreach($cpradiusdb as $cpradiusentry) {
1225
				$line = trim($cpradiusentry);
1226
				if ($line) {
1227
					$radsrv = array();
1228
						list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key'], $context) = explode(",",$line);
1229
				}
1230
				if (empty($context)) {
1231
					if (!is_array($radiusservers['first']))
1232
						$radiusservers['first'] = array();
1233
					$radiusservers['first'] = $radsrv;
1234
				} else {
1235
					if (!is_array($radiusservers[$context]))
1236
						$radiusservers[$context] = array();
1237
					$radiusservers[$context][] = $radsrv;
1238
				}
1239
			}
1240
		}
1241
		unlock($cprdsrvlck);
1242
		return $radiusservers;
1243
	}
1244

    
1245
	unlock($cprdsrvlck);
1246
	return false;
1247
}
1248

    
1249
/* log successful captive portal authentication to syslog */
1250
/* part of this code from php.net */
1251
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1252
	// Log it
1253
	if (!$message)
1254
		$message = "$status: $user, $mac, $ip";
1255
	else {
1256
		$message = trim($message);
1257
		$message = "$status: $user, $mac, $ip, $message";
1258
	}
1259
	captiveportal_syslog($message);
1260
}
1261

    
1262
/* log simple messages to syslog */
1263
function captiveportal_syslog($message) {
1264
	$message = trim($message);
1265
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1266
	// Log it
1267
	syslog(LOG_INFO, $message);
1268
	closelog();
1269
}
1270

    
1271
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1272
	global $g, $config;
1273

    
1274
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1275

    
1276
	/* If the pool is empty, return appropriate message and fail authentication */
1277
	if (is_null($ruleno)) {
1278
		$auth_list = array();
1279
		$auth_list['auth_val'] = 1;
1280
		$auth_list['error'] = "System reached maximum login capacity";
1281
		return $auth_list;
1282
	}
1283

    
1284
	$radiusservers = captiveportal_get_radius_servers();
1285

    
1286
	if (is_null($radiusctx))
1287
		$radiusctx = 'first';
1288

    
1289
	$auth_list = RADIUS_AUTHENTICATION($username,
1290
		$password,
1291
		$radiusservers[$radiusctx],
1292
		$clientip,
1293
		$clientmac,
1294
		$ruleno);
1295

    
1296
	if ($auth_list['auth_val'] == 2) {
1297
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1298
		$sessionid = portal_allow($clientip,
1299
			$clientmac,
1300
			$username,
1301
			$password,
1302
			$auth_list,
1303
			$ruleno,
1304
			$radiusctx);
1305
	}
1306

    
1307
	return $auth_list;
1308
}
1309

    
1310
/* read captive portal DB into array */
1311
function captiveportal_read_db($locked = false, $index = 5 /* sessionid by default */) {
1312
	global $g, $cpzone;
1313

    
1314
	$cpdb = array();
1315

    
1316
	if ($locked == false)
1317
		$cpdblck = lock("captiveportaldb");
1318
	$fd = @fopen("{$g['vardb_path']}/captiveportal_{$cpzone}.db", "r");
1319
	if ($fd) {
1320
		while (!feof($fd)) {
1321
			$line = trim(fgets($fd));
1322
			if ($line) {
1323
				$cpe = explode(",", $line);
1324
				/* Hash by session id */
1325
				$cpdb[$cpe[$index]] = $cpe;
1326
			}
1327
		}
1328
		fclose($fd);
1329
	}
1330
	if ($locked == false)
1331
		unlock($cpdblck);
1332
	return $cpdb;
1333
}
1334

    
1335
/* write captive portal DB */
1336
function captiveportal_write_db($cpdb, $locked = false, $remove = false) {
1337
	global $g, $cpzone;
1338

    
1339
	if ($locked == false)
1340
		$cpdblck = lock("captiveportaldb", LOCK_EX);
1341

    
1342
	if (is_array($remove)) {
1343
		if (!empty($remove)) {
1344
			$cpdb = captiveportal_read_db(true);
1345
			foreach ($remove as $key) {
1346
				if (is_array($key))
1347
					log_error("Captive portal Array passed as unset index: " . print_r($key, true));
1348
				else
1349
					unset($cpdb[$key]);
1350
			}
1351
		} else {
1352
			if ($locked == false)
1353
				unlock($cpdblck);
1354
			return; //This makes sure no record removal calls
1355
		}
1356
	}
1357
	$fd = @fopen("{$g['vardb_path']}/captiveportal_{$cpzone}.db", "w");
1358
	if ($fd) {
1359
		foreach ($cpdb as $cpent) {
1360
			fwrite($fd, join(",", $cpent) . "\n");
1361
		}
1362
		fclose($fd);
1363
	}
1364
	if ($locked == false)
1365
		unlock($cpdblck);
1366
}
1367

    
1368
function captiveportal_write_elements() {
1369
	global $g, $config, $cpzone;
1370
	
1371
	$cpcfg = $config['captiveportal'][$cpzone];
1372

    
1373
	/* delete any existing elements */
1374
	if (is_dir($g['captiveportal_element_path'])) {
1375
		$dh = opendir($g['captiveportal_element_path']);
1376
		while (($file = readdir($dh)) !== false) {
1377
			if ($file != "." && $file != "..")
1378
				unlink($g['captiveportal_element_path'] . "/" . $file);
1379
		}
1380
		closedir($dh);
1381
	} else {
1382
		@mkdir($g['captiveportal_element_path']);
1383
	}
1384

    
1385
	if (is_array($cpcfg['element'])) {
1386
		conf_mount_rw();
1387
		foreach ($cpcfg['element'] as $data) {
1388
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
1389
			if (!$fd) {
1390
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1391
				return 1;
1392
			}
1393
			$decoded = base64_decode($data['content']);
1394
			fwrite($fd,$decoded);
1395
			fclose($fd);
1396
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1397
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1398
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
1399
		}
1400
		conf_mount_ro();
1401
	}
1402
	
1403
	return 0;
1404
}
1405

    
1406
function captiveportal_init_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
1407
	global $g, $cpzone;
1408

    
1409
	@unlink("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
1410
	$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1411
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1412
}
1413

    
1414
/*
1415
 * This function will calculate the lowest free firewall ruleno
1416
 * within the range specified based on the actual logged on users
1417
 *
1418
 */
1419
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899, $usebw = false) {
1420
	global $config, $g, $cpzone;
1421

    
1422
	$cpcfg = $config['captiveportal'][$cpzone];
1423
	if(!isset($cpcfg['enable']))
1424
		return NULL;
1425

    
1426
	$cpruleslck = lock("captiveportalrules", LOCK_EX);
1427
	$ruleno = 0;
1428
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1429
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1430
		for ($ridx = 2; $ridx < ($rulenos_range_max - $rulenos_start); $ridx++) {
1431
			if ($rules[$ridx]) {
1432
				/* 
1433
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1434
				 * and the out pipe ruleno + 1. This removes limitation that where present in 
1435
				 * previous version of the peruserbw.
1436
				 */
1437
				if (isset($cpcfg['peruserbw']) || $usebw == true)
1438
					$ridx++;
1439
				continue;
1440
			}
1441
			$ruleno = $ridx;
1442
			$rules[$ridx] = "used";
1443
			if (isset($cpcfg['peruserbw']) || $usebw == true)
1444
				$rules[++$ridx] = "used";
1445
			break;
1446
		}
1447
	} else {
1448
		$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1449
		$rules[2] = "used";
1450
		$ruleno = 2;
1451
	}
1452
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1453
	unlock($cpruleslck);
1454
	return $ruleno;
1455
}
1456

    
1457
function captiveportal_free_ipfw_ruleno($ruleno, $usedbw = false) {
1458
	global $config, $g, $cpzone;
1459

    
1460
	$cpcfg = $config['captiveportal'][$cpzone];
1461
	if(!isset($cpcfg['enable']))
1462
		return NULL;
1463

    
1464
	$cpruleslck = lock("captiveportalrules", LOCK_EX);
1465
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1466
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1467
		$rules[$ruleno] = false;
1468
		if (isset($cpcfg['peruserbw']) || $usedbw == true)
1469
			$rules[++$ruleno] = false;
1470
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1471
	}
1472
	unlock($cpruleslck);
1473
}
1474

    
1475
function captiveportal_get_ipfw_passthru_ruleno($value) {
1476
	global $config, $g, $cpzone;
1477

    
1478
	$cpcfg = $config['captiveportal'][$cpzone];
1479
	if(!isset($cpcfg['enable']))
1480
		return NULL;
1481

    
1482
	$cpruleslck = lock("captiveportalrules", LOCK_EX);
1483
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1484
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1485
		captiveportal_ipfw_set_context($cpzone);
1486
		$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`);
1487
		if ($rules[$ruleno]) {
1488
			unlock($cpruleslck);
1489
			return $ruleno;
1490
		}
1491
	}
1492

    
1493
	unlock($cpruleslck);
1494
	return NULL;
1495
}
1496

    
1497
/**
1498
 * This function will calculate the traffic produced by a client
1499
 * based on its firewall rule
1500
 *
1501
 * Point of view: NAS
1502
 *
1503
 * Input means: from the client
1504
 * Output means: to the client
1505
 *
1506
 */
1507

    
1508
function getVolume($ip) {
1509
	global $cpzone;
1510

    
1511
	$volume = array();
1512

    
1513
	// Initialize vars properly, since we don't want NULL vars
1514
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1515

    
1516
	// Ingress
1517
	$ipfwin = "";
1518
	$ipfwout = "";
1519
	$matchesin = "";
1520
	$matchesout = "";
1521
	captiveportal_ipfw_set_context($cpzone);
1522
	exec("/sbin/ipfw table 1 entrystats {$ip}", $ipfwin);
1523
	if ($ipfwin[0]) {
1524
		$ipfwin = explode(" ", $ipfwin[0]);
1525
		$volume['input_pkts'] = $ipfwin[2];
1526
		$volume['input_bytes'] = $ipfwin[3];
1527
	}
1528

    
1529
	exec("/sbin/ipfw table 2 entrystats {$ip}", $ipfwout);
1530
	if ($ipfwout[0]) {
1531
		$ipfwout = explode(" ", $ipfwout[0]);
1532
		$volume['output_pkts'] = $ipfwout[2];
1533
		$volume['output_bytes'] = $ipfwout[3];
1534
	}
1535

    
1536
	return $volume;
1537
}
1538

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

    
1553
/**
1554
 * Get the NAS-IP-Address based on the current wan address
1555
 *
1556
 * Use functions in interfaces.inc to find this out
1557
 *
1558
 */
1559

    
1560
function getNasIP()
1561
{
1562
	global $config, $cpzone;
1563

    
1564
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1565
			$nasIp = get_interface_ip();
1566
	} else {
1567
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1568
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1569
		else
1570
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1571
	}
1572
		
1573
	if(!is_ipaddr($nasIp))
1574
		$nasIp = "0.0.0.0";
1575

    
1576
	return $nasIp;
1577
}
1578

    
1579
function portal_ip_from_client_ip($cliip) {
1580
	global $config, $cpzone;
1581

    
1582
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1583
	foreach ($interfaces as $cpif) {
1584
		$ip = get_interface_ip($cpif);
1585
		$sn = get_interface_subnet($cpif);
1586
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1587
			return $ip;
1588
	}
1589

    
1590
	$iface = exec_command("/sbin/route -n get {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1591
	$iface = trim($iface, "\n");
1592
	if (!empty($iface)) {
1593
		$ip = find_interface_ip($iface);
1594
		if (is_ipaddr($ip))
1595
			return $ip;
1596
	}
1597

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

    
1608
	return false;
1609
}
1610

    
1611
/* functions move from index.php */
1612

    
1613
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1614
	global $g, $config, $cpzone;
1615

    
1616
	/* Get captive portal layout */
1617
	if ($type == "redir") {
1618
		header("Location: {$redirurl}");
1619
		return;
1620
	} else if ($type == "login")
1621
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1622
	else
1623
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1624

    
1625
	$cpcfg = $config['captiveportal'][$cpzone];
1626

    
1627
	/* substitute the PORTAL_REDIRURL variable */
1628
	if ($config['captiveportal'][$cpzone]['preauthurl']) {
1629
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$config['captiveportal'][$cpzone]['preauthurl']}", $htmltext);
1630
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$config['captiveportal'][$cpzone]['preauthurl']}", $htmltext);
1631
	}
1632

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

    
1649
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1650
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1651
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1652
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1653
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1654

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

    
1666
    echo $htmltext;
1667
}
1668

    
1669
function portal_mac_radius($clientmac,$clientip) {
1670
    global $config, $cpzone;
1671

    
1672
    $radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1673

    
1674
    /* authentication against the radius server */
1675
    $username = mac_format($clientmac);
1676
    $auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1677
    if ($auth_list['auth_val'] == 2)
1678
        return TRUE;
1679
    if (!empty($auth_list['url_redirection']))
1680
	portal_reply_page($auth_list['url_redirection'], "redir");
1681

    
1682
    return FALSE;
1683
}
1684

    
1685
function captiveportal_reapply_attributes($cpentry, $attributes) {
1686
	global $config, $cpzone, $g;
1687
                         
1688
        /* Add rules for traffic shaping
1689
         * We don't need to add extra rules since traffic will pass due to the following kernel option
1690
         * net.inet.ip.fw.one_pass: 1
1691
         */
1692
        $peruserbw = isset($config['captiveportal'][$cpzone]['peruserbw']);
1693
                
1694
        $bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $config['captiveportal'][$cpzone]['bwdefaultup'];
1695
        $bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $config['captiveportal'][$cpzone]['bwdefaultdn'];
1696
        $bw_up_pipeno = $cpentry[1]+20000;
1697
        $bw_down_pipeno = $cpentry[1]+20001;
1698
        $commands = "";
1699

    
1700
        if ($peruserbw && !empty($bw_up) && is_numeric($bw_up)) {
1701
                $commands .= "pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100\n";
1702
        
1703
                if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
1704
                        $commands .= "table 1 del {$cpentry[2]} mac {$cpentry[3]}\n";
1705
                        $commands .= "table 1 add {$cpentry[2]} mac {$cpentry[3]} {$bw_up_pipeno}\n";
1706
                } else {
1707
                        $commands .= "table 1 del {$cpentry[2]}\n";
1708
                        $commands .= "table 1 add {$cpentry[2]} {$bw_up_pipeno}\n";
1709
                }
1710
        }
1711
        if ($peruserbw && !empty($bw_down) && is_numeric($bw_down)) {
1712
                $commands .= "pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100\n";
1713
                        
1714
                if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
1715
                        $commands .= "table 2 del {$cpentry[2]} mac {$cpentry[3]}\n";
1716
                        $commands .= "table 2 add {$cpentry[2]} mac {$cpentry[3]} {$bw_down_pipeno}\n";
1717
                } else {
1718
                        $commands .= "table 2 del {$cpentry[2]}\n";
1719
                        $commands .= "table 2 add {$cpentry[2]} {$bw_down_pipeno}\n";
1720
                }
1721
        }
1722

    
1723
        if (!empty($commands)) {
1724
                @file_put_contents("{$g['tmp_path']}/reattribute{$cpzone}.rule.tmp", $commands);
1725
                captiveportal_ipfw_set_context($cpzone);
1726
                mwexec("/sbin/ipfw -q {$g['tmp_path']}/reattribute{$cpzone}.rule.tmp");
1727
                //captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_BANDWIDTH_REAPPLY", "{$bw_up}/{$bw_down}");
1728
        }
1729

    
1730
        unset($bw_up_pipeno, $bw_Down_pipeno, $bw_up, $bw_down);
1731
}
1732

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

    
1735
	global $redirurl, $g, $config, $type, $passthrumac, $_POST, $cpzone;
1736

    
1737
	/* See if a ruleno is passed, if not start sessions because this means there isn't one atm */
1738
	if ($ruleno == null)
1739
		$ruleno = captiveportal_get_next_ipfw_ruleno();
1740

    
1741
	/* if the pool is empty, return appropriate message and exit */
1742
	if (is_null($ruleno)) {
1743
		portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1744
		log_error("WARNING!  Captive portal has reached maximum login capacity");
1745
		exit;
1746
	}
1747

    
1748
	// Ensure we create an array if we are missing attributes
1749
	if (!is_array($attributes))
1750
		$attributes = array();
1751

    
1752
	$radiusservers = captiveportal_get_radius_servers();
1753

    
1754
	/* Do not allow concurrent login execution. */
1755
	$cpdblck = lock("captiveportaldb", LOCK_EX);
1756

    
1757
	unset($sessionid);
1758

    
1759
	/* read in client database */
1760
	$cpdb = captiveportal_read_db(true);
1761

    
1762
	if ($attributes['voucher'])
1763
		$remaining_time = $attributes['session_timeout'];
1764

    
1765
	$writecfg = false;
1766
	/* Find an existing session */
1767
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
1768
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1769
			$mac = captiveportal_passthrumac_findbyname($username);
1770
			if (!empty($mac)) {
1771
				if ($_POST['replacemacpassthru']) {
1772
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
1773
						if ($macent['mac'] == $mac['mac']) {
1774
							$macrules = "";
1775
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1776
                                			if ($ruleno) {
1777
								captiveportal_free_ipfw_ruleno($ruleno, true);
1778
                                        			$macrules .= "delete {$ruleno}\n";
1779
								++$ruleno;
1780
                                        			$macrules .= "delete {$ruleno}\n";
1781
                                			}
1782
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1783
							$mac['mac'] = $clientmac;
1784
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1785
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1786
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1787
							captiveportal_ipfw_set_context($cpzone);
1788
							mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1789
							$writecfg = true;
1790
							$sessionid = true;
1791
							break;
1792
						}
1793
					}
1794
                                } else {
1795
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1796
						$clientmac, $clientip, $username, $password);
1797
					exit;
1798
				}
1799
			}
1800
		}
1801
	}
1802

    
1803
	/* Snapshot the timestamp */
1804
	$allow_time = time();
1805
	if (is_null($radiusctx))
1806
		$radiusctx = 'first';
1807
	foreach ($cpdb as $sid => $cpentry) {
1808
		if (empty($cpentry[10]))
1809
			$cpentry[10] = 'first';
1810
		/* on the same ip */
1811
		if ($cpentry[2] == $clientip) {
1812
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac)   
1813
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1814
			else
1815
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
1816
			$sessionid = $sid;
1817
			break;
1818
		}
1819
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1820
			// user logged in with an active voucher. Check for how long and calculate 
1821
			// how much time we can give him (voucher credit - used time)
1822
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1823
			if ($remaining_time < 0)    // just in case. 
1824
				$remaining_time = 0;
1825

    
1826
			/* This user was already logged in so we disconnect the old one */
1827
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1828
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1829
			unset($cpdb[$sid]);
1830
			break;
1831
		}
1832
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1833
			/* on the same username */
1834
			if (strcasecmp($cpentry[4], $username) == 0) {
1835
				/* This user was already logged in so we disconnect the old one */
1836
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1837
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1838
				unset($cpdb[$sid]);
1839
				break;
1840
			}
1841
		}
1842
	}
1843

    
1844
	if ($attributes['voucher'] && $remaining_time <= 0)
1845
		return 0;       // voucher already used and no time left
1846

    
1847
	if (!isset($sessionid)) {
1848
		/* generate unique session ID */
1849
		$tod = gettimeofday();
1850
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1851

    
1852
		/* Add rules for traffic shaping
1853
		 * We don't need to add extra rules since traffic will pass due to the following kernel option
1854
		 * net.inet.ip.fw.one_pass: 1
1855
		 */
1856
		$peruserbw = isset($config['captiveportal'][$cpzone]['peruserbw']);
1857

    
1858
		$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $config['captiveportal'][$cpzone]['bwdefaultup'];
1859
		$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $config['captiveportal'][$cpzone]['bwdefaultdn'];
1860

    
1861
		if ($passthrumac) {
1862
			$mac = array();
1863
			$mac['mac'] = $clientmac;
1864
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername']))
1865
				$mac['username'] = $username;
1866
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1867
			if (!empty($bw_up))
1868
				$mac['bw_up'] = $bw_up;
1869
			if (!empty($bw_down))
1870
				$mac['bw_down'] = $bw_down;
1871
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
1872
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
1873
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1874
			unlock($cpdblck);
1875
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1876
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1877
			captiveportal_ipfw_set_context($cpzone);
1878
			mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1879
			$writecfg = true;
1880
		} else {
1881
			captiveportal_ipfw_set_context($cpzone);
1882

    
1883
			if ($peruserbw && !empty($bw_up) && is_numeric($bw_up)) {
1884
				$bw_up_pipeno = $ruleno + 20000;
1885
				//$bw_up /= 1000; // Scale to Kbit/s
1886
				mwexec("/sbin/ipfw pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100");
1887

    
1888
				if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1889
					mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac} {$bw_up_pipeno}");
1890
				else
1891
					mwexec("/sbin/ipfw table 1 add {$clientip} {$bw_up_pipeno}");
1892
			} else {
1893
				if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1894
					mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac}");
1895
				else
1896
					mwexec("/sbin/ipfw table 1 add {$clientip}");
1897
			}
1898
			if ($peruserbw && !empty($bw_down) && is_numeric($bw_down)) {
1899
				$bw_down_pipeno = $ruleno + 20001;
1900
				//$bw_down /= 1000; // Scale to Kbit/s
1901
				mwexec("/sbin/ipfw pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100");
1902

    
1903
				if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1904
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac} {$bw_down_pipeno}");
1905
				else
1906
					mwexec("/sbin/ipfw table 2 add {$clientip} {$bw_down_pipeno}");
1907
			} else {
1908
				if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1909
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac}");
1910
				else
1911
					mwexec("/sbin/ipfw table 2 add {$clientip}");
1912
			}
1913

    
1914
			if ($attributes['voucher'])
1915
				$attributes['session_timeout'] = $remaining_time;
1916

    
1917
			/* encode password in Base64 just in case it contains commas */
1918
			$bpassword = base64_encode($password);
1919
			$cpdb[] = array($allow_time, $ruleno, $clientip, $clientmac, $username, $sessionid, $bpassword,
1920
				$attributes['session_timeout'], $attributes['idle_timeout'], $attributes['session_terminate_time'], $radiusctx);
1921

    
1922
			/* rewrite information to database */
1923
			captiveportal_write_db($cpdb, true);
1924
			unlock($cpdblck);
1925

    
1926
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
1927
				$acct_val = RADIUS_ACCOUNTING_START($ruleno,
1928
                                		$username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
1929
				if ($acct_val == 1)
1930
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1931
			}
1932
		}
1933
	} else
1934
		unlock($cpdblck);
1935

    
1936
	if ($writecfg == true)
1937
		write_config();
1938

    
1939
	/* redirect user to desired destination */
1940
	if (!empty($attributes['url_redirection']))
1941
		$my_redirurl = $attributes['url_redirection'];
1942
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
1943
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
1944
	else
1945
		$my_redirurl = $redirurl;
1946

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

    
1949
		if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
1950
			$httpsport =
1951
				$config['captiveportal'][$cpzone]['listenporthttps'] ?
1952
				$config['captiveportal'][$cpzone]['listenporthttps'] :
1953
				($config['captiveportal'][$cpzone]['zoneid'] + 1);
1954
			$logouturl = "https://{$config['captiveportal']['httpsname']}:{$httpsport}/";
1955
		} else {
1956
			$ifip = portal_ip_from_client_ip($clientip);
1957
			$httpport =
1958
				$config['captiveportal'][$cpzone]['listenporthttp'] ?
1959
				$config['captiveportal'][$cpzone]['listenporthttp'] :
1960
				$config['captiveportal'][$cpzone]['zoneid'];
1961
			if (!$ifip)
1962
				$ourhostname = $config['system']['hostname'] . ":{$httpport}";
1963
			else
1964
				$ourhostname = "{$ifip}:{$httpport}";
1965
			$logouturl = "http://{$ourhostname}/";
1966
		}
1967

    
1968
		if (isset($attributes['reply_message']))
1969
			$message = $attributes['reply_message'];
1970
		else
1971
			$message = 0;
1972

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

    
1975
	} else {
1976
		header("Location: " . $my_redirurl);
1977
	}
1978

    
1979
	return $sessionid;
1980
}
1981

    
1982

    
1983
/*
1984
 * Used for when pass-through credits are enabled.
1985
 * Returns true when there was at least one free login to deduct for the MAC.
1986
 * Expired entries are removed as they are seen.
1987
 * Active entries are updated according to the configuration.
1988
 */
1989
function portal_consume_passthrough_credit($clientmac) {
1990
	global $config, $cpzone;
1991

    
1992
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
1993
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
1994
	else
1995
		return false;
1996

    
1997
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
1998
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
1999
	else
2000
		return false;
2001

    
2002
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
2003
		return false;
2004

    
2005
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2006

    
2007
	/*
2008
	 * Read database of used MACs.  Lines are a comma-separated list
2009
	 * of the time, MAC, then the count of pass-through credits remaining.
2010
	 */
2011
	$usedmacs = captiveportal_read_usedmacs_db();
2012

    
2013
	$currenttime = time();
2014
	$found = false;
2015
	foreach ($usedmacs as $key => $usedmac) {
2016
		$usedmac = explode(",", $usedmac);
2017

    
2018
		if ($usedmac[1] == $clientmac) {
2019
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2020
				if ($usedmac[2] < 1) {
2021
					if ($updatetimeouts) {
2022
						$usedmac[0] = $currenttime;
2023
						unset($usedmacs[$key]);
2024
						$usedmacs[] = implode(",", $usedmac);
2025
						captiveportal_write_usedmacs_db($usedmacs);
2026
					}
2027

    
2028
					return false;
2029
				} else {
2030
					$usedmac[2] -= 1;
2031
					$usedmacs[$key] = implode(",", $usedmac);
2032
				}
2033

    
2034
				$found = true;
2035
			} else
2036
				unset($usedmacs[$key]);
2037

    
2038
			break;
2039
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2040
				unset($usedmacs[$key]);
2041
	}
2042

    
2043
	if (!$found) {
2044
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2045
		$usedmacs[] = implode(",", $usedmac);
2046
	}
2047

    
2048
	captiveportal_write_usedmacs_db($usedmacs);
2049
	return true;
2050
}
2051

    
2052
function captiveportal_read_usedmacs_db() {
2053
	global $g, $cpzone;
2054

    
2055
	$cpumaclck = lock("captiveusedmacs");
2056
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2057
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2058
		if (!$usedmacs)
2059
			$usedmacs = array();
2060
	} else
2061
		$usedmacs = array();
2062

    
2063
	unlock($cpumaclck);
2064
	return $usedmacs;
2065
}
2066

    
2067
function captiveportal_write_usedmacs_db($usedmacs) {
2068
	global $g, $cpzone;
2069

    
2070
	$cpumaclck = lock("captiveusedmacs", LOCK_EX);
2071
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2072
	unlock($cpumaclck);
2073
}
2074

    
2075
?>
(2-2/3)