Project

General

Profile

Download (45.4 KB) Statistics
| Branch: | Tag: | Revision:
1 5b237745 Scott Ullrich
<?php
2
/*
3
	captiveportal.inc
4 b260c8e0 Scott Ullrich
	part of pfSense (http://www.pfSense.org)
5 36254e4a Scott Ullrich
6 b260c8e0 Scott Ullrich
	originally part of m0n0wall (http://m0n0.ch/wall)
7
8
	Copyright (C) 2010 Scott Ullrich <sullrich@gmail.com>
9
	Copyright (C) 2009 Ermal Lu?i <ermal.luci@gmail.com>
10 0bd34ed6 Scott Ullrich
	Copyright (C) 2003-2006 Manuel Kasper <mk@neon1.net>.
11 5b237745 Scott Ullrich
	All rights reserved.
12 36254e4a Scott Ullrich
13 5b237745 Scott Ullrich
	Redistribution and use in source and binary forms, with or without
14
	modification, are permitted provided that the following conditions are met:
15 36254e4a Scott Ullrich
16 5b237745 Scott Ullrich
	1. Redistributions of source code must retain the above copyright notice,
17
	   this list of conditions and the following disclaimer.
18 36254e4a Scott Ullrich
19 5b237745 Scott Ullrich
	2. Redistributions in binary form must reproduce the above copyright
20
	   notice, this list of conditions and the following disclaimer in the
21
	   documentation and/or other materials provided with the distribution.
22 36254e4a Scott Ullrich
23 5b237745 Scott Ullrich
	THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
24
	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
25
	AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26
	AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
27
	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
	POSSIBILITY OF SUCH DAMAGE.
33 ca83c6ea Scott Ullrich
34 9699028a Scott Ullrich
	This version of captiveportal.inc has been modified by Rob Parker
35
	<rob.parker@keycom.co.uk> to include changes for per-user bandwidth management
36
	via returned RADIUS attributes. This page has been modified to delete any
37
	added rules which may have been created by other per-user code (index.php, etc).
38
	These changes are (c) 2004 Keycom PLC.
39 523855b0 Scott Ullrich
	
40 bf444c34 Ermal
	pfSense_BUILDER_BINARIES:	/sbin/ipfw	/sbin/sysctl	/sbin/kldunload
41 523855b0 Scott Ullrich
	pfSense_BUILDER_BINARIES:	/usr/local/sbin/lighttpd	/usr/local/bin/minicron	/sbin/pfctl
42
	pfSense_BUILDER_BINARIES:	/bin/hostname	/bin/cp	
43
	pfSense_MODULE:	captiveportal
44 9699028a Scott Ullrich
*/
45 36254e4a Scott Ullrich
46 5b237745 Scott Ullrich
/* include all configuration functions */
47 a55cdcc0 Ermal Lu?i
require_once("config.inc");
48
require_once("functions.inc");
49 71fdaecd Ermal
require_once("filter.inc");
50 856e58a6 Scott Ullrich
require_once("radius.inc");
51 156487ed Ermal Lu?i
require_once("voucher.inc");
52 0bd34ed6 Scott Ullrich
53 5b237745 Scott Ullrich
function captiveportal_configure() {
54
	global $config, $g;
55 36254e4a Scott Ullrich
56 dedf51a2 Ermal Lu?i
	$captiveportallck = lock('captiveportal');
57 f8b11310 Ermal Lu?i
	
58
	if (isset($config['captiveportal']['enable'])) {
59 36254e4a Scott Ullrich
60 3db19cf1 Scott Ullrich
		if ($g['booting'])
61
			echo "Starting captive portal... ";
62 36254e4a Scott Ullrich
63 5b237745 Scott Ullrich
		/* kill any running mini_httpd */
64 23a0c341 Scott Ullrich
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
65 63fff79b Scott Ullrich
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal-SSL.pid");
66 36254e4a Scott Ullrich
67 6ce61a8f Ermal
		/* remove old information */
68
		unlink_if_exists("{$g['vardb_path']}/captiveportal.db");
69
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
70
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip.db");
71
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius.db");
72
73
		/* setup new database in case someone tries to access the status -> captive portal page */
74
		touch("{$g['vardb_path']}/captiveportal.db");
75
76 c6c92abf Scott Ullrich
		/* kill any running minicron */
77
		killbypid("{$g['varrun_path']}/minicron.pid");
78 36254e4a Scott Ullrich
79 769e254e Ermal
		/* init ipfw rules */
80 1d9e9cca Ermal
		captiveportal_init_rules(true);
81 36254e4a Scott Ullrich
82 5b237745 Scott Ullrich
		/* stop accounting on all clients */
83 dedf51a2 Ermal Lu?i
		captiveportal_radius_stop_all(true);
84 5b237745 Scott Ullrich
85 0bd34ed6 Scott Ullrich
		/* initialize minicron interval value */
86
		$croninterval = $config['captiveportal']['croninterval'] ? $config['captiveportal']['croninterval'] : 60;
87
88
		/* double check if the $croninterval is numeric and at least 10 seconds. If not we set it to 60 to avoid problems */
89
		if ((!is_numeric($croninterval)) || ($croninterval < 10)) { $croninterval = 60; }
90
91 5b237745 Scott Ullrich
		/* write portal page */
92
		if ($config['captiveportal']['page']['htmltext'])
93
			$htmltext = base64_decode($config['captiveportal']['page']['htmltext']);
94
		else {
95
			/* example/template page */
96
			$htmltext = <<<EOD
97 b260c8e0 Scott Ullrich
<html> 
98
	<body> 
99
		<form method="post" action="$PORTAL_ACTION$"> 
100
		<input name="redirurl" type="hidden" value="$PORTAL_REDIRURL$">
101
			<center>
102
				<table cellpadding="6" cellspacing="0" width="550" height="380" style="border:1px solid #000000">
103
					<tr height="10" bgcolor="#990000">
104
						<td style="border-bottom:1px solid #000000">
105
							<font color='white'>
106
								<b>
107
									{$g['product_name']} captive portal
108
								</b>
109
							</font>
110
						</td>
111
					</tr>
112
					<tr>
113
						<td>
114
							<div id="mainlevel">
115
								<center>
116
									<table width="100%" border="0" cellpadding="5" cellspacing="0">
117
								 		<tr>
118
								    		<td>
119
												<center>
120
													<div id="mainarea">
121
														<center>
122
															<table width="100%" border="0" cellpadding="5" cellspacing="5">
123
																<tr>
124
										     						<td>
125
																		<div id="maindivarea">
126
																			<center>
127
																				<div id='statusbox'>
128
																					<font color='red' face='arial' size='+1'>
129
																						<b>
130
																							$PORTAL_MESSAGE$
131
																						</b>
132
																					</font>
133
																				</div>
134
																				<br/>
135
																				<div id='loginbox'>
136
																					<table>
137
																					   <tr><td colspan="2"><center>Welcome to the {$g['product_name']} Captive Portal!</td></tr>
138
																					   <tr><td>&nbsp;</td></tr>
139
																					   <tr><td align="right">Username:</td><td><input name="auth_user" type="text" style="border: 1px dashed;"></td></tr>
140
																					   <tr><td align="right">Password:</td><td><input name="auth_pass" type="password" style="border: 1px dashed;"></td></tr>
141
																					   <tr><td>&nbsp;</td></tr>
142
																					   <tr>
143
																					     <td colspan="2">
144
																							<center><input name="accept" type="submit" value="Continue"></center>
145
																					     </td>
146
																					   </tr>
147
																					</table>
148
																				</div>
149
																			</center>
150
																		</div>
151
										     						</td>
152
																</tr>
153
															</table>
154
														</center>
155
													</div>
156
												</center>
157
											</td>
158
										</tr>
159
									</table>
160
								</center>
161
							</div>
162
						</td>
163
					</tr>
164
				</table>
165
			</center>
166
		</form>
167
	</body> 
168 5b237745 Scott Ullrich
</html>
169
170 0bd34ed6 Scott Ullrich
171
172 5b237745 Scott Ullrich
EOD;
173
		}
174
175
		$fd = @fopen("{$g['varetc_path']}/captiveportal.html", "w");
176
		if ($fd) {
177 7a7e94a7 Scott Ullrich
			// Special case handling.  Convert so that we can pass this page
178
			// through the PHP interpreter later without clobbering the vars.
179
			$htmltext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $htmltext);
180
			$htmltext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $htmltext);
181
			$htmltext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $htmltext);
182
			$htmltext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $htmltext);
183
			$htmltext = str_replace("\$ORIGINAL_PORTAL_IP\$", "#ORIGINAL_PORTAL_IP#", $htmltext);
184
			$htmltext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $htmltext);
185 5b237745 Scott Ullrich
			fwrite($fd, $htmltext);
186 36254e4a Scott Ullrich
			fclose($fd);
187 5b237745 Scott Ullrich
		}
188 36254e4a Scott Ullrich
189 5b237745 Scott Ullrich
		/* write error page */
190
		if ($config['captiveportal']['page']['errtext'])
191
			$errtext = base64_decode($config['captiveportal']['page']['errtext']);
192
		else {
193
			/* example page */
194
			$errtext = <<<EOD
195 b260c8e0 Scott Ullrich
<html> 
196
	<body> 
197
		<form method="post" action="$PORTAL_ACTION$"> 
198
		<input name="redirurl" type="hidden" value="$PORTAL_REDIRURL$">
199
			<center>
200
				<table cellpadding="6" cellspacing="0" width="550" height="380" style="border:1px solid #000000">
201
					<tr height="10" bgcolor="#990000">
202
						<td style="border-bottom:1px solid #000000">
203
							<font color='white'>
204
								<b>
205
									{$g['product_name']} captive portal
206
								</b>
207
							</font>
208
						</td>
209
					</tr>
210
					<tr>
211
						<td>
212
							<div id="mainlevel">
213
								<center>
214
									<table width="100%" border="0" cellpadding="5" cellspacing="0">
215
								 		<tr>
216
								    		<td>
217
												<center>
218
													<div id="mainarea">
219
														<center>
220
															<table width="100%" border="0" cellpadding="5" cellspacing="5">
221
																<tr>
222
										     						<td>
223
																		<div id="maindivarea">
224
																			<center>
225
																				<div id='statusbox'>
226
																					<font color='red' face='arial' size='+1'>
227
																						<b>
228
																							$PORTAL_MESSAGE$
229
																						</b>
230
																					</font>
231
																				</div>
232
																				<br/>
233
																				<div id='loginbox'>
234
																					<table>
235
																					   <tr><td colspan="2"><center>Welcome to the {$g['product_name']} Captive Portal!</td></tr>
236
																					   <tr><td>&nbsp;</td></tr>
237
																					   <tr><td align="right">Username:</td><td><input name="auth_user" type="text" style="border: 1px dashed;"></td></tr>
238
																					   <tr><td align="right">Password:</td><td><input name="auth_pass" type="password" style="border: 1px dashed;"></td></tr>
239
																					   <tr><td>&nbsp;</td></tr>
240
																					   <tr>
241
																					     <td colspan="2">
242
																							<center><input name="accept" type="submit" value="Continue"></center>
243
																					     </td>
244
																					   </tr>
245
																					</table>
246
																				</div>
247
																			</center>
248
																		</div>
249
										     						</td>
250
																</tr>
251
															</table>
252
														</center>
253
													</div>
254
												</center>
255
											</td>
256
										</tr>
257
									</table>
258
								</center>
259
							</div>
260
						</td>
261
					</tr>
262
				</table>
263
			</center>
264
		</form>
265
	</body> 
266 5b237745 Scott Ullrich
</html>
267
268
EOD;
269
		}
270
271
		$fd = @fopen("{$g['varetc_path']}/captiveportal-error.html", "w");
272
		if ($fd) {
273 7a7e94a7 Scott Ullrich
			// Special case handling.  Convert so that we can pass this page
274
			// through the PHP interpreter later without clobbering the vars.
275
			$errtext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $errtext);
276
			$errtext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $errtext);
277
			$errtext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $errtext);
278
			$errtext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $errtext);
279
			$errtext = str_replace("\$ORIGINAL_PORTAL_IP\$", "#ORIGINAL_PORTAL_IP#", $errtext);
280
			$errtext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $errtext);
281 5b237745 Scott Ullrich
			fwrite($fd, $errtext);
282 36254e4a Scott Ullrich
			fclose($fd);
283 5b237745 Scott Ullrich
		}
284 36254e4a Scott Ullrich
285 5b87b24e Ermal
		/* write error page */
286
		if ($config['captiveportal']['page']['logouttext'])
287
			$logouttext = base64_decode($config['captiveportal']['page']['logouttext']);
288
		else {
289
			/* example page */
290
			$logouttext = <<<EOD
291
<HTML>
292
<HEAD><TITLE>Redirecting...</TITLE></HEAD>
293
<BODY>
294
<SPAN STYLE="font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">
295
<B>Redirecting to <A HREF="{$my_redirurl}">{$my_redirurl}</A>...</B>
296
</SPAN>
297
<SCRIPT LANGUAGE="JavaScript">
298
<!--
299
LogoutWin = window.open('', 'Logout', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=256,height=64');
300
if (LogoutWin) {
301
    LogoutWin.document.write('<HTML>');
302
    LogoutWin.document.write('<HEAD><TITLE>Logout</TITLE></HEAD>') ;
303
    LogoutWin.document.write('<BODY BGCOLOR="#435370">');
304
    LogoutWin.document.write('<DIV ALIGN="center" STYLE="color: #ffffff; font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">') ;
305
    LogoutWin.document.write('<B>Click the button below to disconnect</B><P>');
306
    LogoutWin.document.write('<FORM METHOD="POST" ACTION="{$logouturl}">');
307
    LogoutWin.document.write('<INPUT NAME="logout_id" TYPE="hidden" VALUE="{$sessionid}">');
308
    LogoutWin.document.write('<INPUT NAME="logout" TYPE="submit" VALUE="Logout">');
309
    LogoutWin.document.write('</FORM>');
310
    LogoutWin.document.write('</DIV></BODY>');
311
    LogoutWin.document.write('</HTML>');
312
    LogoutWin.document.close();
313
}
314
315
document.location.href="{$my_redirurl}";
316
-->
317
</SCRIPT>
318
</BODY>
319
</HTML>
320
321
EOD;
322
		}
323
324
		$fd = @fopen("{$g['varetc_path']}/captiveportal-logout.html", "w");
325
		if ($fd) {
326
			fwrite($fd, $logouttext);
327
			fclose($fd);
328
		}
329 0bd34ed6 Scott Ullrich
		/* write elements */
330
		captiveportal_write_elements();
331 5b237745 Scott Ullrich
332 769e254e Ermal
		/* start up the webserving daemon */
333
		captiveportal_init_webgui();
334 36254e4a Scott Ullrich
335 0bd34ed6 Scott Ullrich
		/* start pruning process (interval defaults to 60 seconds) */
336
		mwexec("/usr/local/bin/minicron $croninterval {$g['varrun_path']}/minicron.pid " .
337 c6c92abf Scott Ullrich
			"/etc/rc.prunecaptiveportal");
338 36254e4a Scott Ullrich
339 d99f7864 Scott Ullrich
		/* generate radius server database */
340
		if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
341
				($config['captiveportal']['auth_method'] == "radius"))) {
342
			$radiusip = $config['captiveportal']['radiusip'];
343
			$radiusip2 = ($config['captiveportal']['radiusip2']) ? $config['captiveportal']['radiusip2'] : null;
344
345
			if ($config['captiveportal']['radiusport'])
346
				$radiusport = $config['captiveportal']['radiusport'];
347
			else
348
				$radiusport = 1812;
349
350
			if ($config['captiveportal']['radiusacctport'])
351
				$radiusacctport = $config['captiveportal']['radiusacctport'];
352
			else
353
				$radiusacctport = 1813;
354
355
			if ($config['captiveportal']['radiusport2'])
356
				$radiusport2 = $config['captiveportal']['radiusport2'];
357
			else
358
				$radiusport2 = 1812;
359
360
			$radiuskey = $config['captiveportal']['radiuskey'];
361
			$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
362
363
			$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db", "w");
364
			if (!$fd) {
365
				printf("Error: cannot open radius DB file in captiveportal_configure().\n");
366
				return 1;
367
			} else if (isset($radiusip2, $radiuskey2)) {
368
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . "\n"
369
					 . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2);
370
			} else {
371
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey);
372
			}
373
			fclose($fd);
374
		}
375 36254e4a Scott Ullrich
376 d99f7864 Scott Ullrich
		if ($g['booting'])
377
			echo "done\n";
378 36254e4a Scott Ullrich
379 5b237745 Scott Ullrich
	} else {
380 23a0c341 Scott Ullrich
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
381 c6c92abf Scott Ullrich
		killbypid("{$g['varrun_path']}/minicron.pid");
382 12ee8fe4 Scott Ullrich
383 dedf51a2 Ermal Lu?i
		captiveportal_radius_stop_all(true);
384 3db19cf1 Scott Ullrich
385
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
386
387 5209079f Ermal Luçi
		/* unload ipfw */
388 faebbab3 Scott Ullrich
		if (is_module_loaded("ipfw.ko"))		
389
			mwexec("/sbin/kldunload ipfw.ko");
390 85250056 Ermal Lu?i
		$listifs = get_configured_interface_list_by_realif();
391 bbc6768b Ermal Lu?i
		foreach ($listifs as $listrealif => $listif) {
392 7c587b9f Ermal Lu?i
			if (!empty($listrealif)) {
393 da9d6701 Ermal
				if (does_interface_exist($listrealif)) {
394 bf444c34 Ermal
					pfSense_interface_flags($listrealif, -IFF_IPFW_FILTER);
395 da9d6701 Ermal
					$carpif = link_ip_to_carp_interface(find_interface_ip($listrealif));
396
                        		if (!empty($carpif)) {
397
						$carpsif = explode(" ", $carpif);
398
						foreach ($carpsif as $cpcarp)
399 bf444c34 Ermal
							pfSense_interface_flags($cpcarp, -IFF_IPFW_FILTER);
400 da9d6701 Ermal
					}
401 2ee45728 Ermal Lu?i
				}
402 bbc6768b Ermal Lu?i
			}
403
		}
404 3db19cf1 Scott Ullrich
	}
405 36254e4a Scott Ullrich
406 f8b11310 Ermal Lu?i
	unlock($captiveportallck);
407 c3214b80 Scott Ullrich
	
408 5b237745 Scott Ullrich
	return 0;
409
}
410
411 769e254e Ermal
function captiveportal_init_webgui() {
412
	global $g, $config;
413
414
	 if (!isset($config['captiveportal']['enable']))
415
                return;
416
417
	if ($config['captiveportal']['maxproc'])
418
		$maxproc = $config['captiveportal']['maxproc'];
419
	else
420
		$maxproc = 16;
421
422
	$use_fastcgi = true;
423
424
	if (isset($config['captiveportal']['httpslogin'])) {
425
		$cert = base64_decode($config['captiveportal']['certificate']);
426
		if (isset($config['captiveportal']['cacertificate']))
427
			$cacert = base64_decode($config['captiveportal']['cacertificate']);
428
		else
429
			$cacert = "";
430
		$key = base64_decode($config['captiveportal']['private-key']);
431
		/* generate lighttpd configuration */
432
		system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal-SSL.conf",
433
			$cert, $key, $cacert, "lighty-CaptivePortal-ssl.pid", "8001", "/usr/local/captiveportal/",
434
			"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
435
	}
436
437
	/* generate lighttpd configuration */
438
	system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal.conf",
439
		"", "", "", "lighty-CaptivePortal.pid", "8000", "/usr/local/captiveportal/",
440
		"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
441
442
	/* attempt to start lighttpd */
443
	$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-CaptivePortal.conf");
444
445
	/* fire up https instance */
446
	if (isset($config['captiveportal']['httpslogin']))
447
		$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-CaptivePortal-SSL.conf");
448
}
449
450 1d9e9cca Ermal
function captiveportal_init_rules($reinit = false) {
451 3db19cf1 Scott Ullrich
	global $config, $g;
452 36254e4a Scott Ullrich
453 769e254e Ermal
	if (!isset($config['captiveportal']['enable']))
454
		return;
455
456
	$cpips = array();
457
	$ifaces = get_configured_interface_list();
458
	foreach ($ifaces as $kiface => $kiface2) {
459
		$tmpif = get_real_interface($kiface);
460
		pfSense_interface_flags($tmpif, -IFF_IPFW_FILTER);
461
	}
462
	$cpinterfaces = explode(",", $config['captiveportal']['interface']);
463
	$firsttime = 0;
464
	foreach ($cpinterfaces as $cpifgrp) {
465
		if (!isset($ifaces[$cpifgrp]))
466
			continue;
467
		$tmpif = get_real_interface($cpifgrp);
468
		if (!empty($tmpif)) {
469
			if ($firsttime > 0)
470
				$cpinterface .= " or ";
471
			$cpinterface .= "via {$tmpif}";
472
			$firsttime = 1;
473
			$cpipm = get_interface_ip($cpifgrp);
474
			if (is_ipaddr($cpipm)) {
475
				$carpif = link_ip_to_carp_interface($cpipm);
476
				if (!empty($carpif)) {
477
					$carpsif = explode(" ", $carpif);
478
					foreach ($carpsif as $cpcarp) {
479
						pfSense_interface_flags($cpcarp, IFF_IPFW_FILTER);
480
						$carpip = find_interface_ip($cpcarp);
481
						if (is_ipaddr($carpip))
482
							$cpips[] = $carpip;
483
					}
484
				}
485
				$cpips[] = $cpipm;
486
				pfSense_interface_flags($tmpif, IFF_IPFW_FILTER);
487
			}
488
		}
489
	}
490
	if (count($cpips) > 0) {
491
		$cpactive = true;
492
		$cpinterface = "{ {$cpinterface} } ";
493
        } else
494
		return false;
495
496 eade409a Ermal
	if ($reinit == false)
497
		$captiveportallck = lock('captiveportal');
498
499 1d9e9cca Ermal
	/* init dummynet/ipfw rules number database */
500
	captiveportal_init_ipfw_ruleno();
501
502 769e254e Ermal
	/* make sure ipfw is loaded */
503
	if (!is_module_loaded("ipfw.ko"))
504
		filter_load_ipfw();
505
	/* Always load dummynet now that even allowed ip and mac passthrough use it. */
506
	if (!is_module_loaded("dummynet.ko"))
507
		mwexec("/sbin/kldload dummynet");
508
509 b01792a0 Ermal
	$cprules =  "add 65291 set 1 allow pfsync from any to any\n";
510
	$cprules .= "add 65292 set 1 allow carp from any to any\n";
511 181a843c Scott Ullrich
512 3db19cf1 Scott Ullrich
	$cprules .= <<<EOD
513 b01792a0 Ermal
# add 65300 set 1 skipto 65534 all from any to any not layer2
514 3db19cf1 Scott Ullrich
# layer 2: pass ARP
515 b01792a0 Ermal
add 65301 set 1 pass layer2 mac-type arp
516 b9d1d810 Scott Ullrich
# pfsense requires for WPA
517 b01792a0 Ermal
add 65302 set 1 pass layer2 mac-type 0x888e
518
add 65303 set 1 pass layer2 mac-type 0x88c7
519 684c787e Scott Ullrich
520 36254e4a Scott Ullrich
# PPP Over Ethernet Discovery Stage
521 b01792a0 Ermal
add 65304 set 1 pass layer2 mac-type 0x8863
522 684c787e Scott Ullrich
# PPP Over Ethernet Session Stage
523 b01792a0 Ermal
add 65305 set 1 pass layer2 mac-type 0x8864
524 55f5c311 Ermal Lu?i
# Allow WPA
525 b01792a0 Ermal
add 65306 set 1 pass layer2 mac-type 0x888e
526 684c787e Scott Ullrich
527 3db19cf1 Scott Ullrich
# layer 2: block anything else non-IP
528 b01792a0 Ermal
add 65307 set 1 deny layer2 not mac-type ip
529 3db19cf1 Scott Ullrich
530
EOD;
531
532 b01792a0 Ermal
	$rulenum = 65310;
533 fb516dda Chris Buechler
	$ipcount = 0;
534
	foreach ($cpips as $cpip) {
535
		if($ipcount == 0) {
536
			$ips = "{$cpip} ";
537
		} else {
538
			$ips .= "or {$cpip} ";
539
		}
540
		$ipcount++;
541
	}
542 2f27dffd Ermal
	$ips = "{ {$ips} }";
543 746e60c9 Ermal
	$cprules .= "add {$rulenum} set 1 pass ip from any to {$ips} in\n";
544 2f27dffd Ermal
	$rulenum++;
545 746e60c9 Ermal
	$cprules .= "add {$rulenum} set 1 pass ip from {$ips} to any out\n";
546 2f27dffd Ermal
	$rulenum++;
547
	$cprules .= "add {$rulenum} set 1 pass icmp from {$ips} to any out icmptype 0\n";
548
	$rulenum++;
549
	$cprules .= "add {$rulenum} set 1 pass icmp from any to {$ips} in icmptype 8 \n";
550
	$rulenum++;
551 b01792a0 Ermal
	/* Allowed ips */
552
	$cprules .= "add {$rulenum} allow ip from table(3) to any in\n";
553
	$rulenum++;
554
	$cprules .= "add {$rulenum} allow ip from any to table(4) out\n";
555
	$rulenum++;
556
	$cprules .= "add {$rulenum} pipe tablearg ip from table(5) to any in\n";
557
	$rulenum++;
558
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(6) out\n";
559
	$rulenum++;
560
	$cprules .= "add {$rulenum} allow ip from any to table(7) in\n";
561
	$rulenum++;
562
	$cprules .= "add {$rulenum} allow ip from table(8) to any out\n";
563
	$rulenum++;
564
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(9) in\n";
565
	$rulenum++;
566
	$cprules .= "add {$rulenum} pipe tablearg ip from table(10) to any out\n";
567
	$rulenum++;
568
569
	/* Authenticated users rules. */
570 f9f71ad3 Ermal Lu?i
	if (isset($config['captiveportal']['peruserbw'])) {
571 6ce61a8f Ermal
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from table(1) to any in\n";
572 f9f71ad3 Ermal Lu?i
		$rulenum++;
573 6ce61a8f Ermal
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from any to table(2) out\n";
574 f9f71ad3 Ermal Lu?i
		$rulenum++;
575
	} else {
576 6ce61a8f Ermal
		$cprules .= "add {$rulenum} set 1 allow ip from table(1) to any in\n";
577 f9f71ad3 Ermal Lu?i
                $rulenum++;
578 6ce61a8f Ermal
                $cprules .= "add {$rulenum} set 1 allow ip from any to table(2) out\n";
579 f9f71ad3 Ermal Lu?i
                $rulenum++;
580
	}
581
	
582
       $cprules .= <<<EOD
583 5480497a Scott Ullrich
584 d44bccc7 Scott Ullrich
# redirect non-authenticated clients to captive portal
585 6ce61a8f Ermal
add 65531 set 1 fwd 127.0.0.1,8000 tcp from any to any in
586 3db19cf1 Scott Ullrich
# let the responses from the captive portal web server back out
587 6ce61a8f Ermal
add 65532 set 1 pass tcp from any to any out
588 3db19cf1 Scott Ullrich
# block everything else
589 6ce61a8f Ermal
add 65533 set 1 deny all from any to any
590 3db19cf1 Scott Ullrich
# pass everything else on layer2
591 6ce61a8f Ermal
add 65534 set 1 pass all from any to any layer2
592 3db19cf1 Scott Ullrich
593
EOD;
594
595 769e254e Ermal
	/* generate passthru mac database */
596
	$cprules .= captiveportal_passthrumac_configure(true);
597
	$cprules .= "\n";
598
	/* allowed ipfw rules to make allowed ip work */
599
	$cprules .= captiveportal_allowedip_configure();
600
601
	/* load rules */
602 1d9e9cca Ermal
	if ($reinit == true)
603
		$cprules = "table all flush\nflush\n{$cprules}";
604
	else {
605
		$tmprules = "table 3 flush\n";
606
		$tmprules .= "table 4 flush\n";
607
		$tmprules .= "table 5 flush\n";
608
		$tmprules .= "table 6 flush\n";
609
		$tmprules .= "table 7 flush\n";
610
		$tmprules .= "table 8 flush\n";
611
		$tmprules .= "table 9 flush\n";
612
		$tmprules .= "table 10 flush\n";
613
		$tmprules .= "flush\n";
614
		$cprules = "{$tmprules}\n{$cprules}";
615
	}
616 eade409a Ermal
617
	file_put_contents("{$g['tmp_path']}/ipfw.cp.rules", $cprules);
618
	mwexec("/sbin/ipfw -q {$g['tmp_path']}/ipfw.cp.rules", true);
619
	@unlink("{$g['tmp_path']}/ipfw.cp.rules");
620
621
	if ($reinit == false)
622
		unlock($captiveportallck);
623
624 769e254e Ermal
625
	/* filter on layer2 as well so we can check MAC addresses */
626
	mwexec("/sbin/sysctl net.link.ether.ipfw=1");
627
628
	return $cprules;
629 3db19cf1 Scott Ullrich
}
630
631 5b237745 Scott Ullrich
/* remove clients that have been around for longer than the specified amount of time */
632 36254e4a Scott Ullrich
/* db file structure:
633 0bd34ed6 Scott Ullrich
timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time */
634
635 3db19cf1 Scott Ullrich
/* (password is in Base64 and only saved when reauthentication is enabled) */
636 5b237745 Scott Ullrich
function captiveportal_prune_old() {
637 0bd34ed6 Scott Ullrich
638 23c4f978 Scott Ullrich
    global $g, $config;
639
640
    /* check for expired entries */
641
    if ($config['captiveportal']['timeout'])
642
        $timeout = $config['captiveportal']['timeout'] * 60;
643
    else
644
        $timeout = 0;
645
646
    if ($config['captiveportal']['idletimeout'])
647
        $idletimeout = $config['captiveportal']['idletimeout'] * 60;
648
    else
649
        $idletimeout = 0;
650
651 336e3c1c Charlie
    if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && 
652
		!isset($config['captiveportal']['radiussession_timeout']) && !isset($config['voucher']['enable']))
653 23c4f978 Scott Ullrich
        return;
654
655 dedf51a2 Ermal Lu?i
    $captiveportallck = lock('captiveportal');
656 23c4f978 Scott Ullrich
657
    /* read database */
658
    $cpdb = captiveportal_read_db();
659
660
    $radiusservers = captiveportal_get_radius_servers();
661
662 2d53158f stompro
    /*  To make sure we iterate over ALL accounts on every run the count($cpdb) is moved
663
     *  outside of the loop. Otherwise the loop would evaluate count() on every iteration
664
     *  and since $i would increase and count() would decrement they would meet before we
665
     *  had a chance to iterate over all accounts.
666 f56a73f1 Scott Ullrich
     */
667 8e51cc6a Ermal Lu?i
    $unsetindexes = array();
668 f56a73f1 Scott Ullrich
    $no_users = count($cpdb);
669
    for ($i = 0; $i < $no_users; $i++) {
670 23c4f978 Scott Ullrich
671
        $timedout = false;
672
        $term_cause = 1;
673
674
        /* hard timeout? */
675
        if ($timeout) {
676
            if ((time() - $cpdb[$i][0]) >= $timeout) {
677
                $timedout = true;
678
                $term_cause = 5; // Session-Timeout
679
            }
680
        }
681
682
        /* Session-Terminate-Time */
683
        if (!$timedout && !empty($cpdb[$i][9])) {
684
            if (time() >= $cpdb[$i][9]) {
685
                $timedout = true;
686
                $term_cause = 5; // Session-Timeout
687
            }
688
        }
689
690
        /* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
691
        $idletimeout = (is_numeric($cpdb[$i][8])) ? $cpdb[$i][8] : $idletimeout;
692
        /* if an idle timeout is specified, get last activity timestamp from ipfw */
693
        if (!$timedout && $idletimeout) {
694 f9f71ad3 Ermal Lu?i
            $lastact = captiveportal_get_last_activity($cpdb[$i][2]);
695 2d53158f stompro
			/*  If the user has logged on but not sent any traffic they will never be logged out.
696
			 *  We "fix" this by setting lastact to the login timestamp. 
697 f56a73f1 Scott Ullrich
			 */
698
			$lastact = $lastact ? $lastact : $cpdb[$i][0];
699 23c4f978 Scott Ullrich
            if ($lastact && ((time() - $lastact) >= $idletimeout)) {
700
                $timedout = true;
701
                $term_cause = 4; // Idle-Timeout
702
                $stop_time = $lastact; // Entry added to comply with WISPr
703
            }
704
        }
705
706 336e3c1c Charlie
	/* if vouchers are configured, activate session timeouts */
707
	if (!$timedout && isset($config['voucher']['enable']) && !empty($cpdb[$i][7])) {
708
		if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
709
			$timedout = true;
710
			$term_cause = 5; // Session-Timeout
711
		}
712
	}
713
714 23c4f978 Scott Ullrich
        /* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
715
        if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpdb[$i][7])) {
716
            if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
717
                $timedout = true;
718
                $term_cause = 5; // Session-Timeout
719
            }
720
        }
721
722
        if ($timedout) {
723
            captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
724
            captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
725 8e51cc6a Ermal Lu?i
	    $unsetindexes[$i] = $i;
726 23c4f978 Scott Ullrich
        }
727
728
        /* do periodic RADIUS reauthentication? */
729
        if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
730 40b48c6c Ermal Lu?i
            !empty($radiusservers)) {
731 23c4f978 Scott Ullrich
732
            if (isset($config['captiveportal']['radacct_enable'])) {
733
                if ($config['captiveportal']['reauthenticateacct'] == "stopstart") {
734
                    /* stop and restart accounting */
735
                    RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
736
                                           $cpdb[$i][4], // username
737
                                           $cpdb[$i][5], // sessionid
738
                                           $cpdb[$i][0], // start time
739 40b48c6c Ermal Lu?i
                                           $radiusservers,
740 23c4f978 Scott Ullrich
                                           $cpdb[$i][2], // clientip
741
                                           $cpdb[$i][3], // clientmac
742
                                           10); // NAS Request
743 6ce61a8f Ermal
                    exec("/sbin/ipfw table 1 entryzerostats {$cpdb[$i][2]}");
744
                    exec("/sbin/ipfw table 2 entryzerostats {$cpdb[$i][2]}");
745 23c4f978 Scott Ullrich
                    RADIUS_ACCOUNTING_START($cpdb[$i][1], // ruleno
746
                                            $cpdb[$i][4], // username
747
                                            $cpdb[$i][5], // sessionid
748 40b48c6c Ermal Lu?i
                                            $radiusservers,
749 23c4f978 Scott Ullrich
                                            $cpdb[$i][2], // clientip
750
                                            $cpdb[$i][3]); // clientmac
751
                } else if ($config['captiveportal']['reauthenticateacct'] == "interimupdate") {
752
                    RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
753
                                           $cpdb[$i][4], // username
754
                                           $cpdb[$i][5], // sessionid
755
                                           $cpdb[$i][0], // start time
756 40b48c6c Ermal Lu?i
                                           $radiusservers,
757 23c4f978 Scott Ullrich
                                           $cpdb[$i][2], // clientip
758
                                           $cpdb[$i][3], // clientmac
759
                                           10, // NAS Request
760
                                           true); // Interim Updates
761
                }
762
            }
763
764
            /* check this user against RADIUS again */
765
            $auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
766
                                          base64_decode($cpdb[$i][6]), // password
767
                                            $radiusservers,
768
                                          $cpdb[$i][2], // clientip
769
                                          $cpdb[$i][3], // clientmac
770
                                          $cpdb[$i][1]); // ruleno
771
772
            if ($auth_list['auth_val'] == 3) {
773
                captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
774
                captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
775 8e51cc6a Ermal Lu?i
	        $unsetindexes[$i] = $i;
776 23c4f978 Scott Ullrich
            }
777
        }
778
    }
779 8e51cc6a Ermal Lu?i
    /* This is a kludge to overcome some php weirdness */
780
    foreach($unsetindexes as $unsetindex)
781
	unset($cpdb[$unsetindex]);
782 23c4f978 Scott Ullrich
783
    /* write database */
784
    captiveportal_write_db($cpdb);
785
786 dedf51a2 Ermal Lu?i
    unlock($captiveportallck);
787 5b237745 Scott Ullrich
}
788
789 3db19cf1 Scott Ullrich
/* remove a single client according to the DB entry */
790 0bd34ed6 Scott Ullrich
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
791 36254e4a Scott Ullrich
792 d99f7864 Scott Ullrich
	global $g, $config;
793
794
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
795
796
	/* this client needs to be deleted - remove ipfw rules */
797 40b48c6c Ermal Lu?i
	if (isset($config['captiveportal']['radacct_enable']) && !empty($radiusservers)) {
798 d99f7864 Scott Ullrich
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
799
							   $dbent[4], // username
800
							   $dbent[5], // sessionid
801
							   $dbent[0], // start time
802 40b48c6c Ermal Lu?i
							   $radiusservers,
803 d99f7864 Scott Ullrich
							   $dbent[2], // clientip
804
							   $dbent[3], // clientmac
805
							   $term_cause, // Acct-Terminate-Cause
806
							   false,
807
							   $stop_time);
808
	}
809 2d53158f stompro
	/* Delete client's ip entry from tables 3 and 4. */
810 6ce61a8f Ermal
	mwexec("/sbin/ipfw table 1 delete {$dbent[2]}");
811
	mwexec("/sbin/ipfw table 2 delete {$dbent[2]}");
812 1dbe445a Ermal
813 6ce61a8f Ermal
	/* Release the ruleno so it can be reallocated to new clients. */
814
	captiveportal_free_ipfw_ruleno($dbent[1]);
815 f9f71ad3 Ermal Lu?i
816
	/* 
817
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
818
	* We could get an error if the pipe doesn't exist but everything should still be fine
819
	*/
820
	if (isset($config['captiveportal']['peruserbw'])) {
821
		mwexec("/sbin/ipfw pipe " . ($dbent[1]+20000) . " delete");
822
		mwexec("/sbin/ipfw pipe " . ($dbent[1]+20001) . " delete");
823
	}
824 7a7abeba Scott Ullrich
825 6ce61a8f Ermal
	/* XXX: Redundant?! Ensure all pf(4) states are killed. */
826 7a7abeba Scott Ullrich
	mwexec("pfctl -k {$dbent[2]}");
827 b1cc2eb2 Ermal Luçi
	mwexec("pfctl -K {$dbent[2]}");
828 7a7abeba Scott Ullrich
829 3db19cf1 Scott Ullrich
}
830 12ee8fe4 Scott Ullrich
831 3db19cf1 Scott Ullrich
/* remove a single client by ipfw rule number */
832 0bd34ed6 Scott Ullrich
function captiveportal_disconnect_client($id,$term_cause = 1) {
833 36254e4a Scott Ullrich
834 d99f7864 Scott Ullrich
	global $g, $config;
835 36254e4a Scott Ullrich
836 dedf51a2 Ermal Lu?i
	$captiveportallck = lock('captiveportal');
837 36254e4a Scott Ullrich
838 d99f7864 Scott Ullrich
	/* read database */
839
	$cpdb = captiveportal_read_db();
840
	$radiusservers = captiveportal_get_radius_servers();
841
842
	/* find entry */
843 889b0934 Ermal Lu?i
	$tmpindex = 0;
844 6ce61a8f Ermal
	$cpdbcount = count($cpdb);
845
	for ($i = 0; $i < $cpdbcount; $i++) {
846 d99f7864 Scott Ullrich
		if ($cpdb[$i][1] == $id) {
847
			captiveportal_disconnect($cpdb[$i], $radiusservers, $term_cause);
848
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "DISCONNECT");
849 dd35bb5a Chris Buechler
			unset($cpdb[$i]);
850 d99f7864 Scott Ullrich
			break;
851
		}
852 dd35bb5a Chris Buechler
	}		
853 36254e4a Scott Ullrich
854 d99f7864 Scott Ullrich
	/* write database */
855
	captiveportal_write_db($cpdb);
856 36254e4a Scott Ullrich
857 dedf51a2 Ermal Lu?i
	unlock($captiveportallck);
858 5b237745 Scott Ullrich
}
859
860
/* send RADIUS acct stop for all current clients */
861 dedf51a2 Ermal Lu?i
function captiveportal_radius_stop_all($lock = false) {
862 d99f7864 Scott Ullrich
	global $g, $config;
863
864
	if (!isset($config['captiveportal']['radacct_enable']))
865
		return;
866
867 90455aeb Ermal Lu?i
	if (!$lock)
868 dedf51a2 Ermal Lu?i
		$captiveportallck = lock('captiveportal');
869
870 d99f7864 Scott Ullrich
	$cpdb = captiveportal_read_db();
871
872
	$radiusservers = captiveportal_get_radius_servers();
873 40b48c6c Ermal Lu?i
	if (!empty($radiusservers)) {
874 d99f7864 Scott Ullrich
		for ($i = 0; $i < count($cpdb); $i++) {
875
			RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
876
								   $cpdb[$i][4], // username
877
								   $cpdb[$i][5], // sessionid
878
								   $cpdb[$i][0], // start time
879 40b48c6c Ermal Lu?i
								   $radiusservers,
880 d99f7864 Scott Ullrich
								   $cpdb[$i][2], // clientip
881
								   $cpdb[$i][3], // clientmac
882
								   7); // Admin Reboot
883
		}
884
	}
885 90455aeb Ermal Lu?i
	if (!$lock)
886 dedf51a2 Ermal Lu?i
		unlock($captiveportallck);
887 5b237745 Scott Ullrich
}
888
889 d5ae560d Ermal
function captiveportal_passthrumac_configure_entry($macent) {
890
	$rules = "";
891
        $enBwup = isset($macent['bw_up']);
892
        $enBwdown = isset($macent['bw_down']);
893
	$actionup = "allow";
894
	$actiondown = "allow";
895
896
        if ($enBwup && $enBwdown)
897
                $ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
898
        else
899
                $ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
900
901
	if ($enBwup) {
902
                $bw_up = $ruleno + 20000;
903
                $rules .= "pipe {$bw_up} config bw {$macent['bw_up']}Kbit/s queue 100\n";
904
		$actionup = "pipe {$bw_up}";
905
        }
906
        if ($enBwdown) {
907
		$bw_down = $ruleno + 20001;
908
		$rules .= "pipe {$bw_down} config bw {$macent['bw_down']}Kbit/s queue 100\n";
909
		$actiondown = "pipe {$bw_down}";
910
        }
911 ffcf81bb Chris Buechler
	$rules .= "add {$ruleno} {$actiondown} ip from any to any MAC {$macent['mac']} any\n";
912 d5ae560d Ermal
	$ruleno++;
913 ffcf81bb Chris Buechler
	$rules .= "add {$ruleno} {$actionup} ip from any to any MAC any {$macent['mac']}\n";
914 d5ae560d Ermal
915
	return $rules;
916
}
917
918 dedf51a2 Ermal Lu?i
function captiveportal_passthrumac_configure($lock = false) {
919 5b237745 Scott Ullrich
	global $config, $g;
920 36254e4a Scott Ullrich
921 d5ae560d Ermal
	$rules = "";
922 36254e4a Scott Ullrich
923 5b237745 Scott Ullrich
	if (is_array($config['captiveportal']['passthrumac'])) {
924 1dbe445a Ermal
		$macdb = array();
925 5b237745 Scott Ullrich
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
926 d5ae560d Ermal
			$rules .= captiveportal_passthrumac_configure_entry($macent);
927 1dbe445a Ermal
			$macdb[$macent['mac']]['active']  = true;
928
929 5b237745 Scott Ullrich
		}
930
	}
931 0bd34ed6 Scott Ullrich
932 d5ae560d Ermal
	return $rules;
933 5b237745 Scott Ullrich
}
934
935 fac13a5e Ermal
function captiveportal_passthrumac_findbyname($username) {
936
	global $config;
937
938
	if (is_array($config['captiveportal']['passthrumac'])) {
939
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
940
			if ($macent['username'] == $username)
941
				return $macent;
942
		}
943
	}
944
	return NULL;
945
}
946
947 b01792a0 Ermal
/* 
948
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
949
 * table (5=IN)/(6=OUT) hold allowed ip's with bw limit.
950
 */
951
function captiveportal_allowedip_configure_entry($ipent) {
952
953
	$rules = "";
954
	$enBwup = isset($ipent['bw_up']);
955
	$enBwdown = isset($ipent['bw_down']);
956
	$bw_up = "";
957
        $bw_down = "";
958
        $tablein = array();
959
        $tableout = array();
960
961
	if ($enBwup && $enBwdown)
962
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
963
	else
964
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
965
966
        if ($ipent['dir'] == "from") {
967
        	if ($enBwup)
968
                	$tablein[] = 5;
969
                else
970
                	$tablein[] = 3;
971
                if ($enBwdown)
972
                        $tableout[] = 6;
973
                else
974
                        $tableout[] = 4;
975
        } else if ($ipent['dir'] == "to") {
976
                if ($enBwup)
977
                	$tablein[] = 9;
978
                else
979
                        $tablein[] = 7;
980
                if ($enBwdown)
981
                        $tableout[] = 10;
982
                else
983
                        $tableout[] = 8;
984
        } else if ($ipent['dir'] == "both") {
985
                if ($enBwup) {
986
                        $tablein[] = 5;
987
                        $tablein[] = 9;
988
                } else {
989
                        $tablein[] = 3;
990
                        $tablein[] = 7;
991
                }
992
        	if ($enBwdown) {
993
                        $tableout[] = 6;
994
                        $tableout[] = 10;
995
                } else {
996
                        $tableout[] = 4;
997
                	$tableout[] = 8;
998
                }
999
        }
1000
        if ($enBwup) {
1001
                $bw_up = $ruleno + 20000;
1002
        	$rules .= "pipe {$bw_up} config bw {$ipent['bw_up']}Kbit/s queue 100\n";
1003
        }
1004 d6a0379d Ermal
	$subnet = "";
1005
	if (!empty($ipent['sn']))
1006
		$subnet = "/{$ipent['sn']}";
1007 b01792a0 Ermal
	foreach ($tablein as $table)
1008 d6a0379d Ermal
               $rules .= "table {$table} add {$ipent['ip']}{$subnet} {$bw_up}\n";
1009 b01792a0 Ermal
        if ($enBwdown) {
1010
               $bw_down = $ruleno + 20001;
1011
               $rules .= "pipe {$bw_down} config bw {$ipent['bw_down']}Kbit/s queue 100\n";
1012
        }
1013
        foreach ($tableout as $table)
1014 d6a0379d Ermal
        	$rules .= "table {$table} add {$ipent['ip']}{$subnet} {$bw_down}\n";
1015 b01792a0 Ermal
1016
	return $rules;
1017
}
1018
1019 cb0a2913 Ermal Lu?i
function captiveportal_allowedip_configure() {
1020 5b237745 Scott Ullrich
	global $config, $g;
1021 36254e4a Scott Ullrich
1022 6ce61a8f Ermal
	$rules = "";
1023 5b237745 Scott Ullrich
	if (is_array($config['captiveportal']['allowedip'])) {
1024 cb0a2913 Ermal Lu?i
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
1025 b01792a0 Ermal
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1026 cb0a2913 Ermal Lu?i
		}
1027
	}
1028 36254e4a Scott Ullrich
1029 6ce61a8f Ermal
	return $rules;
1030 5b237745 Scott Ullrich
}
1031
1032 2d53158f stompro
/* get last activity timestamp given client IP address */
1033 f9f71ad3 Ermal Lu?i
function captiveportal_get_last_activity($ip) {
1034 36254e4a Scott Ullrich
1035 d99f7864 Scott Ullrich
	$ipfwoutput = "";
1036 36254e4a Scott Ullrich
1037 6ce61a8f Ermal
	exec("/sbin/ipfw table 1 entrystats {$ip} 2>/dev/null", $ipfwoutput);
1038 f9f71ad3 Ermal Lu?i
	/* Reading only from one of the tables is enough of approximation. */
1039 d99f7864 Scott Ullrich
	if ($ipfwoutput[0]) {
1040
		$ri = explode(" ", $ipfwoutput[0]);
1041 f9f71ad3 Ermal Lu?i
		if ($ri[4])
1042
			return $ri[4];
1043 d99f7864 Scott Ullrich
	}
1044 36254e4a Scott Ullrich
1045 d99f7864 Scott Ullrich
	return 0;
1046 5b237745 Scott Ullrich
}
1047
1048
/* read RADIUS servers into array */
1049
function captiveportal_get_radius_servers() {
1050 0bd34ed6 Scott Ullrich
1051
        global $g;
1052
1053
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
1054 2f70eac7 Ermal Lu?i
                $radiusservers = array();
1055 a48acf9a Ermal Lu?i
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius.db", 
1056
			FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1057
		if ($cpradiusdb)
1058 2f70eac7 Ermal Lu?i
		foreach($cpradiusdb as $cpradiusentry) {
1059
                	$line = trim($cpradiusentry);
1060
                        if ($line) {
1061
                        	$radsrv = array();
1062
                                list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
1063
                        	$radiusservers[] = $radsrv;
1064 0bd34ed6 Scott Ullrich
                        }
1065 2f70eac7 Ermal Lu?i
		}
1066 0bd34ed6 Scott Ullrich
1067 2f70eac7 Ermal Lu?i
		return $radiusservers;
1068 0bd34ed6 Scott Ullrich
        }
1069
1070
        return false;
1071 5b237745 Scott Ullrich
}
1072
1073 3db19cf1 Scott Ullrich
/* log successful captive portal authentication to syslog */
1074
/* part of this code from php.net */
1075 0bd34ed6 Scott Ullrich
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1076 d99f7864 Scott Ullrich
	$message = trim($message);
1077
	// Log it
1078
	if (!$message)
1079 f56a73f1 Scott Ullrich
		$message = "$status: $user, $mac, $ip";
1080 d99f7864 Scott Ullrich
	else
1081 f56a73f1 Scott Ullrich
		$message = "$status: $user, $mac, $ip, $message";
1082
	captiveportal_syslog($message);
1083
	closelog();
1084
}
1085
1086
/* log simple messages to syslog */
1087
function captiveportal_syslog($message) {
1088
	define_syslog_variables();
1089
	$message = trim($message);
1090
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1091
	// Log it
1092
	syslog(LOG_INFO, $message);
1093 d99f7864 Scott Ullrich
	closelog();
1094 3db19cf1 Scott Ullrich
}
1095
1096 0bd34ed6 Scott Ullrich
function radius($username,$password,$clientip,$clientmac,$type) {
1097 d44bccc7 Scott Ullrich
    global $g, $config;
1098 d99f7864 Scott Ullrich
1099 d44bccc7 Scott Ullrich
    /* Start locking from the beginning of an authentication session */
1100 dedf51a2 Ermal Lu?i
    $captiveportallck = lock('captiveportal');
1101 0bd34ed6 Scott Ullrich
1102 d44bccc7 Scott Ullrich
    $ruleno = captiveportal_get_next_ipfw_ruleno();
1103
1104 2d53158f stompro
    /* If the pool is empty, return appropriate message and fail authentication */
1105 d44bccc7 Scott Ullrich
    if (is_null($ruleno)) {
1106
        $auth_list = array();
1107
        $auth_list['auth_val'] = 1;
1108
        $auth_list['error'] = "System reached maximum login capacity";
1109 dedf51a2 Ermal Lu?i
        unlock($captiveportallck);
1110 d44bccc7 Scott Ullrich
        return $auth_list;
1111
    }
1112
1113 2f70eac7 Ermal Lu?i
    /*
1114
     * Drop the lock since radius takes some time to finish.
1115
     * The implementation is reentrant so we gain speed with this.
1116
     */
1117
    unlock($captiveportallck);
1118
1119 d44bccc7 Scott Ullrich
    $radiusservers = captiveportal_get_radius_servers();
1120
1121
    $auth_list = RADIUS_AUTHENTICATION($username,
1122
                    $password,
1123
                    $radiusservers,
1124
                    $clientip,
1125
                    $clientmac,
1126
                    $ruleno);
1127
1128 2f70eac7 Ermal Lu?i
    $captiveportallck = lock('captiveportal');
1129
1130 d44bccc7 Scott Ullrich
    if ($auth_list['auth_val'] == 2) {
1131
        captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1132
        $sessionid = portal_allow($clientip,
1133
                    $clientmac,
1134
                    $username,
1135
                    $password,
1136
                    $auth_list,
1137
                    $ruleno);
1138
    }
1139 9befcca7 Ermal Lu?i
1140 dedf51a2 Ermal Lu?i
    unlock($captiveportallck);
1141 d44bccc7 Scott Ullrich
1142
    return $auth_list;
1143 0bd34ed6 Scott Ullrich
1144
}
1145
1146
/* read captive portal DB into array */
1147
function captiveportal_read_db() {
1148
1149
        global $g;
1150
1151
        $cpdb = array();
1152
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
1153
        if ($fd) {
1154
                while (!feof($fd)) {
1155
                        $line = trim(fgets($fd));
1156
                        if ($line) {
1157
                                $cpdb[] = explode(",", $line);
1158
                        }
1159
                }
1160
                fclose($fd);
1161
        }
1162
        return $cpdb;
1163
}
1164
1165
/* write captive portal DB */
1166
function captiveportal_write_db($cpdb) {
1167 d44bccc7 Scott Ullrich
                 
1168 0bd34ed6 Scott Ullrich
        global $g;
1169 d44bccc7 Scott Ullrich
                
1170 0bd34ed6 Scott Ullrich
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
1171 d44bccc7 Scott Ullrich
        if ($fd) { 
1172 0bd34ed6 Scott Ullrich
                foreach ($cpdb as $cpent) {
1173
                        fwrite($fd, join(",", $cpent) . "\n");
1174 d44bccc7 Scott Ullrich
                }       
1175 0bd34ed6 Scott Ullrich
                fclose($fd);
1176 d44bccc7 Scott Ullrich
        }       
1177 0bd34ed6 Scott Ullrich
}
1178
1179
function captiveportal_write_elements() {
1180 769e254e Ermal
	global $g, $config;
1181 d44bccc7 Scott Ullrich
    
1182 769e254e Ermal
	/* delete any existing elements */
1183
	if (is_dir($g['captiveportal_element_path'])) {
1184
		$dh = opendir($g['captiveportal_element_path']);
1185
		while (($file = readdir($dh)) !== false) {
1186
			if ($file != "." && $file != "..")
1187
				unlink($g['captiveportal_element_path'] . "/" . $file);
1188
		}
1189
		closedir($dh);
1190
	} else
1191
		@mkdir($g['captiveportal_element_path']);
1192
1193 1fadb31d Scott Ullrich
	if (is_array($config['captiveportal']['element'])) {
1194
		conf_mount_rw();
1195
		foreach ($config['captiveportal']['element'] as $data) {
1196
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
1197
			if (!$fd) {
1198
				printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
1199
				return 1;
1200
			}
1201
			$decoded = base64_decode($data['content']);
1202
			fwrite($fd,$decoded);
1203
			fclose($fd);
1204
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1205
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1206
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
1207
		}
1208
		conf_mount_ro();
1209
	}
1210 d44bccc7 Scott Ullrich
    
1211 769e254e Ermal
	return 0;
1212 0bd34ed6 Scott Ullrich
}
1213
1214 6ce61a8f Ermal
function captiveportal_init_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
1215
	global $g;
1216
1217
	@unlink("{$g['vardb_path']}/captiveportal.rules");
1218
	$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1219
	file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1220
}
1221
1222 920cafaf Scott Ullrich
/*
1223
 * This function will calculate the lowest free firewall ruleno
1224 f9f71ad3 Ermal Lu?i
 * within the range specified based on the actual logged on users
1225 920cafaf Scott Ullrich
 *
1226
 */
1227 b01792a0 Ermal
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899, $usebw = false) {
1228 f9f71ad3 Ermal Lu?i
	global $config, $g;
1229 6ce61a8f Ermal
1230 de752609 Scott Ullrich
	if(!isset($config['captiveportal']['enable']))
1231 01d57b8c Scott Ullrich
		return NULL;
1232 6ce61a8f Ermal
1233 f9f71ad3 Ermal Lu?i
	$ruleno = 0;
1234 6ce61a8f Ermal
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1235
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1236
		for ($ridx = 2; $ridx < ($rulenos_range_max - $rulenos_start); $ridx++) {
1237
			if ($rules[$ridx]) {
1238
				/* 
1239
	 			 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1240
	 			 * and the out pipe ruleno + 1. This removes limitation that where present in 
1241
	 			 * previous version of the peruserbw.
1242
	 			 */
1243
				if (isset($config['captiveportal']['peruserbw']))
1244
					$ridx++;
1245
				continue;
1246
			}
1247
			$ruleno = $ridx;
1248
			$rules[$ridx] = "used";
1249 b01792a0 Ermal
			if (isset($config['captiveportal']['peruserbw']) || $usebw == true)
1250 6ce61a8f Ermal
				$rules[++$ridx] = "used";
1251
			break;
1252
		}
1253
	} else {
1254
		$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1255
		$rules[2] = "used";
1256
		$ruleno = 2;
1257
	}
1258
	file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1259
	return $ruleno;
1260
}
1261
1262 b01792a0 Ermal
function captiveportal_free_ipfw_ruleno($ruleno, $usedbw = false) {
1263 6ce61a8f Ermal
	global $config, $g;
1264
1265
	if(!isset($config['captiveportal']['enable']))
1266
		return NULL;
1267
1268
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1269
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1270
		$rules[$ruleno] = false;
1271 b01792a0 Ermal
		if (isset($config['captiveportal']['peruserbw']) || $usedbw == true)
1272 6ce61a8f Ermal
			$rules[++$ruleno] = false;
1273
		file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1274 f9f71ad3 Ermal Lu?i
	}
1275 6ce61a8f Ermal
}
1276
1277 d5ae560d Ermal
function captiveportal_get_ipfw_passthru_ruleno($value) {
1278 6ce61a8f Ermal
	global $config, $g;
1279
1280
	if(!isset($config['captiveportal']['enable']))
1281
                return NULL;
1282
1283
        if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1284
                $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1285 d5ae560d Ermal
		$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`);
1286 6ce61a8f Ermal
		if ($rules[$ruleno])
1287
			return $ruleno;
1288
        }
1289
1290 f9f71ad3 Ermal Lu?i
	return NULL;
1291 920cafaf Scott Ullrich
}
1292
1293 360d815d Scott Ullrich
/**
1294
 * This function will calculate the traffic produced by a client
1295
 * based on its firewall rule
1296
 *
1297
 * Point of view: NAS
1298
 *
1299
 * Input means: from the client
1300
 * Output means: to the client
1301
 *
1302
 */
1303
1304 f9f71ad3 Ermal Lu?i
function getVolume($ip) {
1305 360d815d Scott Ullrich
1306
    $volume = array();
1307
1308
    // Initialize vars properly, since we don't want NULL vars
1309
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1310
1311
    // Ingress
1312 f9f71ad3 Ermal Lu?i
    $ipfwin = "";
1313
    $ipfwout = "";
1314
    $matchesin = "";
1315
    $matchesout = "";
1316 6ce61a8f Ermal
    exec("/sbin/ipfw table 1 entrystats {$ip}", $ipfwin);
1317 f9f71ad3 Ermal Lu?i
    if ($ipfwin[0]) {
1318 523855b0 Scott Ullrich
		$ipfwin = split(" ", $ipfwin[0]);
1319
		$volume['input_pkts'] = $ipfwin[2];
1320
		$volume['input_bytes'] = $ipfwin[3];
1321 f9f71ad3 Ermal Lu?i
    }
1322
1323 6ce61a8f Ermal
    exec("/sbin/ipfw table 2 entrystats {$ip}", $ipfwout);
1324 f9f71ad3 Ermal Lu?i
    if ($ipfwout[0]) {
1325
        $ipfwout = split(" ", $ipfwout[0]);
1326
        $volume['output_pkts'] = $ipfwout[2];
1327
        $volume['output_bytes'] = $ipfwout[3];
1328
    }
1329 360d815d Scott Ullrich
1330
    return $volume;
1331
}
1332
1333 856e58a6 Scott Ullrich
/**
1334
 * Get the NAS-Identifier
1335
 *
1336
 * We will use our local hostname to make up the nas_id
1337
 */
1338
function getNasID()
1339
{
1340 84e5047d Scott Ullrich
    $nasId = "";
1341 856e58a6 Scott Ullrich
    exec("/bin/hostname", $nasId);
1342
    if(!$nasId[0])
1343 36d0358b Scott Ullrich
        $nasId[0] = "{$g['product_name']}";
1344 856e58a6 Scott Ullrich
    return $nasId[0];
1345
}
1346
1347
/**
1348
 * Get the NAS-IP-Address based on the current wan address
1349
 *
1350
 * Use functions in interfaces.inc to find this out
1351
 *
1352
 */
1353
1354
function getNasIP()
1355
{
1356 64c0462b Ermal
	global $config;
1357
1358 36ff7f81 Ermal
	if (empty($config['captiveportal']['radiussrcip_attribute']))
1359
    		$nasIp = get_interface_ip();
1360
	else {
1361 4a756e9b Ermal
		if (is_ipaddr($config['captiveportal']['radiussrcip_attribute']))
1362
                        $nasIp = $config['captiveportal']['radiussrcip_attribute'];
1363
                else
1364
                        $nasIp = get_interface_ip($config['captiveportal']['radiussrcip_attribute']);
1365 36ff7f81 Ermal
	}
1366 64c0462b Ermal
		
1367
    	if(!is_ipaddr($nasIp))
1368
        	$nasIp = "0.0.0.0";
1369
1370
	return $nasIp;
1371 856e58a6 Scott Ullrich
}
1372
1373 f8b11310 Ermal Lu?i
function portal_ip_from_client_ip($cliip) {
1374
	global $config;
1375
1376
	$interfaces = explode(",", $config['captiveportal']['interface']);
1377
	foreach ($interfaces as $cpif) {
1378
		$ip = get_interface_ip($cpif);
1379
		$sn = get_interface_subnet($cpif);
1380
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1381
			return $ip;
1382
	}
1383
1384 cc125e13 Chris Buechler
	// doesn't match up to any particular interface
1385
	// so let's set the portal IP to what PHP says 
1386
	// the server IP issuing the request is. 
1387
	// allows same behavior as 1.2.x where IP isn't 
1388
	// in the subnet of any CP interface (static routes, etc.)
1389
	// rather than forcing to DNS hostname resolution
1390
	$ip = $_SERVER['SERVER_ADDR'];
1391
	if (is_ipaddr($ip))
1392
		return $ip;
1393
1394 f8b11310 Ermal Lu?i
	return false;
1395
}
1396
1397 b260c8e0 Scott Ullrich
?>