Project

General

Profile

Download (29.2 KB) Statistics
| Branch: | Tag: | Revision:
1 5b237745 Scott Ullrich
<?php
2
/*
3
	captiveportal.inc
4 3db19cf1 Scott Ullrich
	part of m0n0wall (http://m0n0.ch/wall)
5
	
6 0bd34ed6 Scott Ullrich
	Copyright (C) 2003-2006 Manuel Kasper <mk@neon1.net>.
7 5b237745 Scott Ullrich
	All rights reserved.
8 3db19cf1 Scott Ullrich
	
9 5b237745 Scott Ullrich
	Redistribution and use in source and binary forms, with or without
10
	modification, are permitted provided that the following conditions are met:
11 3db19cf1 Scott Ullrich
	
12 5b237745 Scott Ullrich
	1. Redistributions of source code must retain the above copyright notice,
13
	   this list of conditions and the following disclaimer.
14 3db19cf1 Scott Ullrich
	
15 5b237745 Scott Ullrich
	2. Redistributions in binary form must reproduce the above copyright
16
	   notice, this list of conditions and the following disclaimer in the
17
	   documentation and/or other materials provided with the distribution.
18 3db19cf1 Scott Ullrich
	
19 5b237745 Scott Ullrich
	THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
20
	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
21
	AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22
	AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
23
	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
	POSSIBILITY OF SUCH DAMAGE.
29 ca83c6ea Scott Ullrich
30 9699028a Scott Ullrich
	This version of captiveportal.inc has been modified by Rob Parker
31
	<rob.parker@keycom.co.uk> to include changes for per-user bandwidth management
32
	via returned RADIUS attributes. This page has been modified to delete any
33
	added rules which may have been created by other per-user code (index.php, etc).
34
	These changes are (c) 2004 Keycom PLC.
35
*/
36 3db19cf1 Scott Ullrich
	
37 5b237745 Scott Ullrich
/* include all configuration functions */
38
require_once("functions.inc");
39 3db19cf1 Scott Ullrich
require_once("radius_authentication.inc");
40 0bd34ed6 Scott Ullrich
require_once("radius_accounting.inc");
41
42
$lockfile = "{$g['varrun_path']}/captiveportal.lock";
43 5b237745 Scott Ullrich
44
function captiveportal_configure() {
45
	global $config, $g;
46 3db19cf1 Scott Ullrich
	
47
	if (isset($config['captiveportal']['enable']) &&
48
		(($config['captiveportal']['interface'] == "lan") ||
49
			isset($config['interfaces'][$config['captiveportal']['interface']]['enable']))) {
50
	
51
		if ($g['booting'])
52
			echo "Starting captive portal... ";
53
		
54 5b237745 Scott Ullrich
		/* kill any running mini_httpd */
55 23a0c341 Scott Ullrich
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
56 63fff79b Scott Ullrich
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal-SSL.pid");
57 c6c92abf Scott Ullrich
		
58
		/* kill any running minicron */
59
		killbypid("{$g['varrun_path']}/minicron.pid");
60
		
61 3db19cf1 Scott Ullrich
		/* generate ipfw rules */
62
		$cprules = captiveportal_rules_generate();
63
		
64
		/* make sure ipfw is loaded */
65
		mwexec("/sbin/kldload ipfw");
66
		
67 5b237745 Scott Ullrich
		/* stop accounting on all clients */
68 3db19cf1 Scott Ullrich
		captiveportal_radius_stop_all();
69 5b237745 Scott Ullrich
70 0bd34ed6 Scott Ullrich
		/* initialize minicron interval value */
71
		$croninterval = $config['captiveportal']['croninterval'] ? $config['captiveportal']['croninterval'] : 60;
72
73
		/* double check if the $croninterval is numeric and at least 10 seconds. If not we set it to 60 to avoid problems */
74
		if ((!is_numeric($croninterval)) || ($croninterval < 10)) { $croninterval = 60; }
75
76 5b237745 Scott Ullrich
		/* remove old information */
77
		unlink_if_exists("{$g['vardb_path']}/captiveportal.nextrule");
78
		unlink_if_exists("{$g['vardb_path']}/captiveportal.db");
79
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
80
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip.db");
81
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius.db");
82 3db19cf1 Scott Ullrich
		
83 5b237745 Scott Ullrich
		/* write portal page */
84
		if ($config['captiveportal']['page']['htmltext'])
85
			$htmltext = base64_decode($config['captiveportal']['page']['htmltext']);
86
		else {
87
			/* example/template page */
88
			$htmltext = <<<EOD
89
<html>
90
<head>
91 61b040ce Scott Ullrich
<title>pfSense captive portal</title>
92 5b237745 Scott Ullrich
</head>
93 3db19cf1 Scott Ullrich
<body>
94 a515d275 Scott Ullrich
<center>
95 61b040ce Scott Ullrich
<h2>pfSense captive portal</h2>
96 407a29ca Scott Ullrich
Welcome to the pfSense Captive Portal!  This is the default page since a custom page has not been defined.
97 14d2d21b Scott Ullrich
<p>
98 a515d275 Scott Ullrich
<form method="post" action="\$PORTAL_ACTION\$">
99
<input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$">
100 407a29ca Scott Ullrich
<table>
101
   <tr><td>Username:</td><td><input name="auth_user" type="text"></td></tr>
102
   <tr><td>Password:</td><td><input name="auth_pass" type="password"></td></tr>
103 a515d275 Scott Ullrich
   <tr><td>&nbsp;</td></tr>
104 14d2d21b Scott Ullrich
   <tr>
105
     <td colspan="2">
106 0bd34ed6 Scott Ullrich
	<center><input name="accept" type="submit" value="Continue"></center>
107 14d2d21b Scott Ullrich
     </td>
108
   </tr>
109 407a29ca Scott Ullrich
</table>
110 a515d275 Scott Ullrich
</center>
111 407a29ca Scott Ullrich
</form>
112 5b237745 Scott Ullrich
</body>
113
</html>
114
115 0bd34ed6 Scott Ullrich
116
117 5b237745 Scott Ullrich
EOD;
118
		}
119
120
		$fd = @fopen("{$g['varetc_path']}/captiveportal.html", "w");
121
		if ($fd) {
122
			fwrite($fd, $htmltext);
123 3db19cf1 Scott Ullrich
			fclose($fd);	
124 5b237745 Scott Ullrich
		}
125 3db19cf1 Scott Ullrich
		
126 5b237745 Scott Ullrich
		/* write error page */
127
		if ($config['captiveportal']['page']['errtext'])
128
			$errtext = base64_decode($config['captiveportal']['page']['errtext']);
129
		else {
130
			/* example page */
131
			$errtext = <<<EOD
132
<html>
133
<head>
134
<title>Authentication error</title>
135
</head>
136
<body>
137
<font color="#cc0000"><h2>Authentication error</h2></font>
138
<b>
139
Username and/or password invalid.
140
<br><br>
141
<a href="javascript:history.back()">Go back</a>
142
</b>
143
</body>
144
</html>
145
146
EOD;
147
		}
148
149
		$fd = @fopen("{$g['varetc_path']}/captiveportal-error.html", "w");
150
		if ($fd) {
151
			fwrite($fd, $errtext);
152 3db19cf1 Scott Ullrich
			fclose($fd);	
153 5b237745 Scott Ullrich
		}
154 0bd34ed6 Scott Ullrich
		
155
		/* write elements */
156
		captiveportal_write_elements();
157 5b237745 Scott Ullrich
158 3db19cf1 Scott Ullrich
		/* load rules */
159
		mwexec("/sbin/ipfw -f delete set 1");
160
		mwexec("/sbin/ipfw -f delete set 2");
161
		mwexec("/sbin/ipfw -f delete set 3");
162
		
163 63fff79b Scott Ullrich
		/* ipfw cannot accept rules directly on stdin,
164 3db19cf1 Scott Ullrich
		   so we have to write them to a temporary file first */
165
		$fd = @fopen("{$g['tmp_path']}/ipfw.cp.rules", "w");
166
		if (!$fd) {
167
			printf("Cannot open ipfw.cp.rules in captiveportal_configure()\n");
168
			return 1;
169
		}
170
			
171
		fwrite($fd, $cprules);
172
		fclose($fd);
173
		
174
		mwexec("/sbin/ipfw {$g['tmp_path']}/ipfw.cp.rules");
175
		
176
		unlink("{$g['tmp_path']}/ipfw.cp.rules");
177
		
178
		/* filter on layer2 as well so we can check MAC addresses */
179
		mwexec("/sbin/sysctl net.link.ether.ipfw=1");
180
		
181 5b237745 Scott Ullrich
		chdir($g['captiveportal_path']);
182 c6c92abf Scott Ullrich
	
183 877ac35d Scott Ullrich
		/* TEMPORARY!  FAST_CGI reports _FALSE_ client ip
184
		 * 	       addresses.
185
		 */
186
		$use_fastcgi = false;
187 9b5a1292 Scott Ullrich
188
		if ($config['captiveportal']['maxproc'])
189
			$maxproc = $config['captiveportal']['maxproc'];
190
		else
191
			$maxproc = 16;
192
					
193 40b9f8c0 Scott Ullrich
		if(isset($config['captiveportal']['httpslogin'])) {
194
			$cert = base64_decode($config['captiveportal']['certificate']);
195
			$key = base64_decode($config['captiveportal']['private-key']);
196 63fff79b Scott Ullrich
			/* generate lighttpd configuration */
197
			system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal-SSL.conf",
198
				$cert, $key, "lighty-CaptivePortal-ssl.pid", "8001", "/usr/local/captiveportal/",
199
					"cert-portal.pem", "1", $maxproc, $use_fastcgi, true);
200 40b9f8c0 Scott Ullrich
		}
201 0bd34ed6 Scott Ullrich
		
202 877ac35d Scott Ullrich
		/* generate lighttpd configuration */
203
		system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal.conf",
204 63fff79b Scott Ullrich
			"", "", "lighty-CaptivePortal.pid", "8000", "/usr/local/captiveportal/",
205 556d59be Scott Ullrich
				"cert-portal.pem", "1", $maxproc, $use_fastcgi, true);
206 63fff79b Scott Ullrich
		
207 877ac35d Scott Ullrich
		/* attempt to start lighttpd */
208
		$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-CaptivePortal.conf");
209 0bd34ed6 Scott Ullrich
		
210 63fff79b Scott Ullrich
		/* fire up https instance */
211
		if(isset($config['captiveportal']['httpslogin']))
212
			$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-CaptivePortal-SSL.conf");
213
		
214 0bd34ed6 Scott Ullrich
		/* start pruning process (interval defaults to 60 seconds) */
215
		mwexec("/usr/local/bin/minicron $croninterval {$g['varrun_path']}/minicron.pid " .
216 c6c92abf Scott Ullrich
			"/etc/rc.prunecaptiveportal");
217
		
218 5b237745 Scott Ullrich
		/* generate passthru mac database */
219 3db19cf1 Scott Ullrich
		captiveportal_passthrumac_configure();
220
		/* create allowed ip database and insert ipfw rules to make it so */
221
		captiveportal_allowedip_configure();
222 0bd34ed6 Scott Ullrich
		
223 5b237745 Scott Ullrich
		/* generate radius server database */
224 3db19cf1 Scott Ullrich
		if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
225
				($config['captiveportal']['auth_method'] == "radius"))) {
226
			$radiusip = $config['captiveportal']['radiusip'];
227 0bd34ed6 Scott Ullrich
			$radiusip2 = ($config['captiveportal']['radiusip2']) ? $config['captiveportal']['radiusip2'] : null;
228 63fff79b Scott Ullrich
			
229 3db19cf1 Scott Ullrich
			if ($config['captiveportal']['radiusport'])
230
				$radiusport = $config['captiveportal']['radiusport'];
231 5b237745 Scott Ullrich
			else
232
				$radiusport = 1812;
233
234 3db19cf1 Scott Ullrich
			if ($config['captiveportal']['radiusacctport'])
235
				$radiusacctport = $config['captiveportal']['radiusacctport'];
236 5b237745 Scott Ullrich
			else
237
				$radiusacctport = 1813;
238
239 0bd34ed6 Scott Ullrich
			if ($config['captiveportal']['radiusport2'])
240
				$radiusport2 = $config['captiveportal']['radiusport2'];
241
			else
242
				$radiusport2 = 1812;
243
244 5b237745 Scott Ullrich
			$radiuskey = $config['captiveportal']['radiuskey'];
245 0bd34ed6 Scott Ullrich
			$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
246 5b237745 Scott Ullrich
247
			$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db", "w");
248
			if (!$fd) {
249
				printf("Error: cannot open radius DB file in captiveportal_configure().\n");
250
				return 1;
251 0bd34ed6 Scott Ullrich
			} else if (isset($radiusip2, $radiuskey2)) {
252
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . "\n"
253
					 . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2);
254 5b237745 Scott Ullrich
			} else {
255 3db19cf1 Scott Ullrich
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey);
256 5b237745 Scott Ullrich
			}
257 3db19cf1 Scott Ullrich
			fclose($fd);
258 5b237745 Scott Ullrich
		}
259
260 3db19cf1 Scott Ullrich
		if ($g['booting'])
261
			echo "done\n";
262
		
263 5b237745 Scott Ullrich
	} else {
264 23a0c341 Scott Ullrich
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
265 c6c92abf Scott Ullrich
		killbypid("{$g['varrun_path']}/minicron.pid");
266 12ee8fe4 Scott Ullrich
267 3db19cf1 Scott Ullrich
		captiveportal_radius_stop_all();
268
269
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
270
271
		if (!isset($config['shaper']['enable'])) {
272
			/* unload ipfw */
273
			mwexec("/sbin/kldunload ipfw");
274
		} else {
275
			/* shaper is on - just remove our rules */
276
			mwexec("/sbin/ipfw -f delete set 1");
277
			mwexec("/sbin/ipfw -f delete set 2");
278
			mwexec("/sbin/ipfw -f delete set 3");
279
		}
280
	}
281
	
282 5b237745 Scott Ullrich
	return 0;
283
}
284
285 3db19cf1 Scott Ullrich
function captiveportal_rules_generate() {
286
	global $config, $g;
287
	
288
	$cpifn = $config['captiveportal']['interface'];
289
	$cpif = $config['interfaces'][$cpifn]['if'];
290
	$cpip = $config['interfaces'][$cpifn]['ipaddr'];
291
292
	/* note: the captive portal daemon inserts all pass rules for authenticated
293
	   clients as skipto 50000 rules to make traffic shaping work */
294
295
	$cprules = "";
296 181a843c Scott Ullrich
297
	/*   allow nat redirects to work  see
298
	     http://cvstrac.pfsense.com/tktview?tn=651
299
	 */
300
	$iflist = array("lan" => "LAN", "wan" => "WAN");
301 c54d236c Scott Ullrich
	$captive_portal_interface = strtoupper($cpifn);
302 181a843c Scott Ullrich
	for ($i = 1; isset($config['interfaces']['opt' . $i]); $i++) 
303 c54d236c Scott Ullrich
		$iflist['opt' . $i] = "OPT{$i}";	
304 181a843c Scott Ullrich
	foreach ($iflist as $ifent => $ifname) {
305 d66bb68a Scott Ullrich
		if($captive_portal_interface == strtoupper($ifname))
306 181a843c Scott Ullrich
			continue;
307
		$int = convert_friendly_interface_to_real_interface_name($ifname);
308 657f3f15 Scott Ullrich
		$cprules .= "add 30 set 1 skipto 50000 all from any to any in via {$int} keep-state\n";
309 181a843c Scott Ullrich
	}
310 0bd34ed6 Scott Ullrich
	
311 3db19cf1 Scott Ullrich
	/* captive portal on LAN interface? */
312
	if ($cpifn == "lan") {
313
		/* add anti-lockout rules */
314
		$cprules .= <<<EOD
315 0bd34ed6 Scott Ullrich
add 500 set 1 pass all from $cpip to any out via $cpif
316 3db19cf1 Scott Ullrich
add 501 set 1 pass all from any to $cpip in via $cpif
317
318
EOD;
319
	}
320
321
	$cprules .= <<<EOD
322
# skip to traffic shaper if not on captive portal interface
323
add 1000 set 1 skipto 50000 all from any to any not layer2 not via $cpif
324
# pass all layer2 traffic on other interfaces
325
add 1001 set 1 pass layer2 not via $cpif
326
327
# layer 2: pass ARP
328
add 1100 set 1 pass layer2 mac-type arp
329 b9d1d810 Scott Ullrich
# pfsense requires for WPA
330
add 1100 set 1 pass layer2 mac-type 0x888e
331 684c787e Scott Ullrich
332
# PPP Over Ethernet Discovery Stage 
333
add 1100 set 1 pass layer2 mac-type 0x8863
334
# PPP Over Ethernet Session Stage
335
add 1100 set 1 pass layer2 mac-type 0x8864
336
337 3db19cf1 Scott Ullrich
# layer 2: block anything else non-IP
338
add 1101 set 1 deny layer2 not mac-type ip
339
# layer 2: check if MAC addresses of authenticated clients are correct
340
add 1102 set 1 skipto 20000 layer2
341
342
# allow access to our DHCP server (which needs to be able to ping clients as well)
343
add 1200 set 1 pass udp from any 68 to 255.255.255.255 67 in
344
add 1201 set 1 pass udp from any 68 to $cpip 67 in
345
add 1202 set 1 pass udp from $cpip 67 to any 68 out
346
add 1203 set 1 pass icmp from $cpip to any out icmptype 8
347
add 1204 set 1 pass icmp from any to $cpip in icmptype 0
348
349
# allow access to our DNS forwarder
350
add 1300 set 1 pass udp from any to $cpip 53 in
351
add 1301 set 1 pass udp from $cpip 53 to any out
352
353
# allow access to our web server
354
add 1302 set 1 pass tcp from any to $cpip 8000 in
355
add 1303 set 1 pass tcp from $cpip 8000 to any out
356
357
EOD;
358
359
	if (isset($config['captiveportal']['httpslogin'])) {
360
		$cprules .= <<<EOD
361
add 1304 set 1 pass tcp from any to $cpip 8001 in
362
add 1305 set 1 pass tcp from $cpip 8001 to any out
363
364
EOD;
365
	}
366
	
367
	$cprules .= <<<EOD
368
369
# ... 10000-19899: rules per authenticated client go here...
370
371
# redirect non-authenticated clients to captive portal
372
add 19900 set 1 fwd 127.0.0.1,8000 tcp from any to any 80 in
373 5480497a Scott Ullrich
374
# --- for redir ssl
375
# redirect non-authenticated clients to captive portal on ssl
376
add 19901 set 1 fwd 127.0.0.1,8001 tcp from any to any 443 in
377
378
# let the responses from the captive portal web server back out
379
add 19902 set 1 pass tcp from any 443 to any out
380
381
# --- End redir ssl
382
383 3db19cf1 Scott Ullrich
# let the responses from the captive portal web server back out
384 5480497a Scott Ullrich
add 19903 set 1 pass tcp from any 80 to any out
385 3db19cf1 Scott Ullrich
# block everything else
386 5480497a Scott Ullrich
add 19904 set 1 deny all from any to any
387 3db19cf1 Scott Ullrich
388
# ... 20000-29899: layer2 block rules per authenticated client go here...
389
390
# pass everything else on layer2
391
add 29900 set 1 pass all from any to any layer2
392
393
EOD;
394
395
	return $cprules;
396
}
397
398 5b237745 Scott Ullrich
/* remove clients that have been around for longer than the specified amount of time */
399 0bd34ed6 Scott Ullrich
/* db file structure: 
400
timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time */
401
402 3db19cf1 Scott Ullrich
/* (password is in Base64 and only saved when reauthentication is enabled) */
403 5b237745 Scott Ullrich
function captiveportal_prune_old() {
404 3db19cf1 Scott Ullrich
	
405 5b237745 Scott Ullrich
	global $g, $config;
406 0bd34ed6 Scott Ullrich
407 5b237745 Scott Ullrich
	/* check for expired entries */
408
	if ($config['captiveportal']['timeout'])
409
		$timeout = $config['captiveportal']['timeout'] * 60;
410
	else
411
		$timeout = 0;
412 3db19cf1 Scott Ullrich
		
413 5b237745 Scott Ullrich
	if ($config['captiveportal']['idletimeout'])
414
		$idletimeout = $config['captiveportal']['idletimeout'] * 60;
415
	else
416
		$idletimeout = 0;
417 3db19cf1 Scott Ullrich
	
418
	if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']))
419 5b237745 Scott Ullrich
		return;
420 3db19cf1 Scott Ullrich
	
421 5b237745 Scott Ullrich
	captiveportal_lock();
422 3db19cf1 Scott Ullrich
	
423 5b237745 Scott Ullrich
	/* read database */
424
	$cpdb = captiveportal_read_db();
425 3db19cf1 Scott Ullrich
	
426 5b237745 Scott Ullrich
	$radiusservers = captiveportal_get_radius_servers();
427 3db19cf1 Scott Ullrich
	
428 5b237745 Scott Ullrich
	for ($i = 0; $i < count($cpdb); $i++) {
429 3db19cf1 Scott Ullrich
		
430 5b237745 Scott Ullrich
		$timedout = false;
431 0bd34ed6 Scott Ullrich
		$term_cause = 1;
432 a4004399 Scott Ullrich
		
433 3db19cf1 Scott Ullrich
		/* hard timeout? */
434
		if ($timeout) {
435 0bd34ed6 Scott Ullrich
			if ((time() - $cpdb[$i][0]) >= $timeout) {
436
				$timedout = true;
437
				$term_cause = 5; // Session-Timeout
438
			}
439
		}
440
441
		/* Session-Terminate-Time */
442
		if (!$timedout && !empty($cpdb[$i][9])) {
443
			if (time() >= $cpdb[$i][9]) {
444
				$timedout = true;
445
				$term_cause = 5; // Session-Timeout
446
			}
447 3db19cf1 Scott Ullrich
		}
448
		
449 0bd34ed6 Scott Ullrich
		/* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
450
		$idletimeout = (is_numeric($cpdb[$i][8])) ? $cpdb[$i][8] : $idletimeout;
451 3db19cf1 Scott Ullrich
		/* if an idle timeout is specified, get last activity timestamp from ipfw */
452
		if (!$timedout && $idletimeout) {
453
			$lastact = captiveportal_get_last_activity($cpdb[$i][1]);
454 0bd34ed6 Scott Ullrich
			if ($lastact && ((time() - $lastact) >= $idletimeout)) {
455
				$timedout = true;
456
				$term_cause = 4; // Idle-Timeout
457
				$stop_time = $lastact; // Entry added to comply with WISPr
458
			}
459
		}
460
461
		/* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
462
		if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpdb[$i][7])) {
463
			if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
464 3db19cf1 Scott Ullrich
				$timedout = true;
465 0bd34ed6 Scott Ullrich
				$term_cause = 5; // Session-Timeout
466
			}
467 3db19cf1 Scott Ullrich
		}
468
		
469 5b237745 Scott Ullrich
		if ($timedout) {
470 0bd34ed6 Scott Ullrich
			captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
471 3db19cf1 Scott Ullrich
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
472 5b237745 Scott Ullrich
			unset($cpdb[$i]);
473
		}
474 3db19cf1 Scott Ullrich
		
475
		/* do periodic RADIUS reauthentication? */
476
		if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
477
			($radiusservers !== false)) {
478
		
479
			if (isset($config['captiveportal']['radacct_enable'])) {
480
				if ($config['captiveportal']['reauthenticateacct'] == "stopstart") {
481
					/* stop and restart accounting */
482
					RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
483
										   $cpdb[$i][4], // username
484
										   $cpdb[$i][5], // sessionid
485
										   $cpdb[$i][0], // start time
486
										   $radiusservers[0]['ipaddr'],
487
										   $radiusservers[0]['acctport'],
488
										   $radiusservers[0]['key'],
489 0bd34ed6 Scott Ullrich
										   $cpdb[$i][2], // clientip
490
										   $cpdb[$i][3], // clientmac
491
										   10); // NAS Request
492 3db19cf1 Scott Ullrich
					exec("/sbin/ipfw zero {$cpdb[$i][1]}");
493 0bd34ed6 Scott Ullrich
					RADIUS_ACCOUNTING_START($cpdb[$i][1], // ruleno
494
											$cpdb[$i][4], // username
495
											$cpdb[$i][5], // sessionid
496 3db19cf1 Scott Ullrich
											$radiusservers[0]['ipaddr'],
497
											$radiusservers[0]['acctport'],
498
											$radiusservers[0]['key'],
499 0bd34ed6 Scott Ullrich
											$cpdb[$i][2], // clientip
500
											$cpdb[$i][3]); // clientmac
501 3db19cf1 Scott Ullrich
				} else if ($config['captiveportal']['reauthenticateacct'] == "interimupdate") {
502
					RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
503
										   $cpdb[$i][4], // username
504
										   $cpdb[$i][5], // sessionid
505
										   $cpdb[$i][0], // start time
506
										   $radiusservers[0]['ipaddr'],
507
										   $radiusservers[0]['acctport'],
508
										   $radiusservers[0]['key'],
509 0bd34ed6 Scott Ullrich
										   $cpdb[$i][2], // clientip
510
										   $cpdb[$i][3], // clientmac
511
										   10, // NAS Request
512
										   true); // Interim Updates
513 3db19cf1 Scott Ullrich
				}
514
			}
515
		
516
			/* check this user against RADIUS again */
517 0bd34ed6 Scott Ullrich
			$auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
518
										  base64_decode($cpdb[$i][6]), // password
519
							  			  $radiusservers,
520
										  $cpdb[$i][2], // clientip
521
										  $cpdb[$i][3], // clientmac
522
										  $cpdb[$i][1]); // ruleno
523 3db19cf1 Scott Ullrich
			
524 0bd34ed6 Scott Ullrich
			if ($auth_list['auth_val'] == 3) {
525
				captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
526
				captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
527 3db19cf1 Scott Ullrich
				unset($cpdb[$i]);
528
			}
529
		}
530 5b237745 Scott Ullrich
	}
531 3db19cf1 Scott Ullrich
	
532 5b237745 Scott Ullrich
	/* write database */
533
	captiveportal_write_db($cpdb);
534 3db19cf1 Scott Ullrich
	
535 5b237745 Scott Ullrich
	captiveportal_unlock();
536
}
537
538 3db19cf1 Scott Ullrich
/* remove a single client according to the DB entry */
539 0bd34ed6 Scott Ullrich
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
540 3db19cf1 Scott Ullrich
	
541 5b237745 Scott Ullrich
	global $g, $config;
542 0bd34ed6 Scott Ullrich
543
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
544 3db19cf1 Scott Ullrich
	
545
	/* this client needs to be deleted - remove ipfw rules */
546
	if (isset($config['captiveportal']['radacct_enable']) && isset($radiusservers[0])) {
547
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
548
							   $dbent[4], // username
549
							   $dbent[5], // sessionid
550
							   $dbent[0], // start time
551
							   $radiusservers[0]['ipaddr'],
552
							   $radiusservers[0]['acctport'],
553
							   $radiusservers[0]['key'],
554 0bd34ed6 Scott Ullrich
							   $dbent[2], // clientip
555
							   $dbent[3], // clientmac
556
							   $term_cause, // Acct-Terminate-Cause
557
							   false,
558
							   $stop_time);
559 3db19cf1 Scott Ullrich
	}
560
	
561
	mwexec("/sbin/ipfw delete " . $dbent[1] . " " . ($dbent[1]+10000));
562
	
563
	//KEYCOM: we need to delete +40500 and +45500 as well...
564
	//these are the rule numbers we use to control traffic shaping for each logged in user via captive portal
565
	//we only need to remove our rules if peruserbw is turned on.
566
	if (isset($config['captiveportal']['peruserbw'])) {
567
		mwexec("/sbin/ipfw delete " . ($dbent[1]+40500));
568
		mwexec("/sbin/ipfw delete " . ($dbent[1]+45500));
569
	}
570
}
571 12ee8fe4 Scott Ullrich
572 3db19cf1 Scott Ullrich
/* remove a single client by ipfw rule number */
573 0bd34ed6 Scott Ullrich
function captiveportal_disconnect_client($id,$term_cause = 1) {
574 3db19cf1 Scott Ullrich
	
575
	global $g, $config;
576
	
577 5b237745 Scott Ullrich
	captiveportal_lock();
578 3db19cf1 Scott Ullrich
	
579 5b237745 Scott Ullrich
	/* read database */
580
	$cpdb = captiveportal_read_db();
581
	$radiusservers = captiveportal_get_radius_servers();
582 3db19cf1 Scott Ullrich
	
583
	/* find entry */	
584 5b237745 Scott Ullrich
	for ($i = 0; $i < count($cpdb); $i++) {
585
		if ($cpdb[$i][1] == $id) {
586 0bd34ed6 Scott Ullrich
			captiveportal_disconnect($cpdb[$i], $radiusservers, $term_cause);
587 3db19cf1 Scott Ullrich
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "DISCONNECT");
588 5b237745 Scott Ullrich
			unset($cpdb[$i]);
589
			break;
590
		}
591
	}
592 3db19cf1 Scott Ullrich
	
593 5b237745 Scott Ullrich
	/* write database */
594
	captiveportal_write_db($cpdb);
595 3db19cf1 Scott Ullrich
	
596 5b237745 Scott Ullrich
	captiveportal_unlock();
597
}
598
599
/* send RADIUS acct stop for all current clients */
600
function captiveportal_radius_stop_all() {
601
	global $g, $config;
602 0bd34ed6 Scott Ullrich
	
603
	if (!isset($config['captiveportal']['radacct_enable']))
604
		return;
605 5b237745 Scott Ullrich
606 3db19cf1 Scott Ullrich
	captiveportal_lock();
607
	$cpdb = captiveportal_read_db();
608
	
609 5b237745 Scott Ullrich
	$radiusservers = captiveportal_get_radius_servers();
610 3db19cf1 Scott Ullrich
	
611 5b237745 Scott Ullrich
	if (isset($radiusservers[0])) {
612
		for ($i = 0; $i < count($cpdb); $i++) {
613
			RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
614
								   $cpdb[$i][4], // username
615
								   $cpdb[$i][5], // sessionid
616
								   $cpdb[$i][0], // start time
617
								   $radiusservers[0]['ipaddr'],
618
								   $radiusservers[0]['acctport'],
619 9699028a Scott Ullrich
								   $radiusservers[0]['key'],
620 0bd34ed6 Scott Ullrich
								   $cpdb[$i][2], // clientip
621
								   $cpdb[$i][3], // clientmac
622
								   7); // Admin Reboot
623 5b237745 Scott Ullrich
		}
624
	}
625 3db19cf1 Scott Ullrich
	captiveportal_unlock();
626 5b237745 Scott Ullrich
}
627
628
function captiveportal_passthrumac_configure() {
629
	global $config, $g;
630 3db19cf1 Scott Ullrich
	
631
	captiveportal_lock();
632
	
633 5b237745 Scott Ullrich
	/* clear out passthru macs, if necessary */
634 3db19cf1 Scott Ullrich
	unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
635
	
636 5b237745 Scott Ullrich
	if (is_array($config['captiveportal']['passthrumac'])) {
637 3db19cf1 Scott Ullrich
		
638 5b237745 Scott Ullrich
		$fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db", "w");
639
		if (!$fd) {
640
			printf("Error: cannot open passthru mac DB file in captiveportal_passthrumac_configure().\n");
641 3db19cf1 Scott Ullrich
			captiveportal_unlock();
642
			return 1;		
643 5b237745 Scott Ullrich
		}
644 3db19cf1 Scott Ullrich
		
645 5b237745 Scott Ullrich
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
646
			/* record passthru mac so it can be recognized and let thru */
647
			fwrite($fd, $macent['mac'] . "\n");
648
		}
649 3db19cf1 Scott Ullrich
		
650
		fclose($fd); 
651 5b237745 Scott Ullrich
	}
652 0bd34ed6 Scott Ullrich
653 1de584c9 Scott Ullrich
	/*    pass through mac entries should always exist.  the reason
654
	 *    for this is because we do not have native mac address filtering
655
         *    mechanisms.  this allows us to filter by mac address easily
656
	 *    and get around this limitation.   I consider this a bug in
657
         *    m0n0wall and pfSense as m0n0wall does not have native mac 
658
         *    filtering mechanisms as well. -Scott Ullrich
659
         */
660
	if (is_array($config['captiveportal']['passthrumac'])) {
661
		mwexec("/sbin/ipfw delete 50");
662
		foreach($config['captiveportal']['passthrumac'] as $ptm) {
663
			/* create the pass through mac entry */
664 12249cad Scott Ullrich
			//system("echo /sbin/ipfw add 50 skipto 65535 ip from any to any MAC {$ptm['mac']} any > /tmp/cp");
665 5d61b44e Scott Ullrich
			mwexec("/sbin/ipfw add 50 skipto 29900 ip from any to any MAC {$ptm['mac']} any keep-state");
666
			mwexec("/sbin/ipfw add 50 skipto 29900 ip from any to any MAC any {$ptm['mac']} keep-state");
667 1de584c9 Scott Ullrich
		}
668
	}
669 0bd34ed6 Scott Ullrich
670 3db19cf1 Scott Ullrich
	captiveportal_unlock();
671
	
672 5b237745 Scott Ullrich
	return 0;
673
}
674
675
function captiveportal_allowedip_configure() {
676
	global $config, $g;
677 3db19cf1 Scott Ullrich
	
678
	captiveportal_lock();
679 5b237745 Scott Ullrich
680
	/* clear out existing allowed ips, if necessary */
681
	if (file_exists("{$g['vardb_path']}/captiveportal_ip.db")) {
682
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "r");
683
		if ($fd) {
684
			while (!feof($fd)) {
685
				$line = trim(fgets($fd));
686 3db19cf1 Scott Ullrich
				if ($line) {
687 5b237745 Scott Ullrich
					list($ip,$rule) = explode(",",$line);
688 3db19cf1 Scott Ullrich
					mwexec("/sbin/ipfw delete $rule");
689
				}	
690 5b237745 Scott Ullrich
			}
691
		}
692 3db19cf1 Scott Ullrich
		fclose($fd);
693 5b237745 Scott Ullrich
		unlink("{$g['vardb_path']}/captiveportal_ip.db");
694
	}
695
696 3db19cf1 Scott Ullrich
	/* get next ipfw rule number */
697
	if (file_exists("{$g['vardb_path']}/captiveportal.nextrule"))
698
		$ruleno = trim(file_get_contents("{$g['vardb_path']}/captiveportal.nextrule"));
699
	if (!$ruleno)
700
		$ruleno = 10000;	/* first rule number */
701
	
702 5b237745 Scott Ullrich
	if (is_array($config['captiveportal']['allowedip'])) {
703 3db19cf1 Scott Ullrich
		
704 5b237745 Scott Ullrich
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "w");
705
		if (!$fd) {
706
			printf("Error: cannot open allowed ip DB file in captiveportal_allowedip_configure().\n");
707 3db19cf1 Scott Ullrich
			captiveportal_unlock();
708
			return 1;		
709 5b237745 Scott Ullrich
		}
710 3db19cf1 Scott Ullrich
		
711 5b237745 Scott Ullrich
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
712 3db19cf1 Scott Ullrich
		
713 5b237745 Scott Ullrich
			/* record allowed ip so it can be recognized and removed later */
714 3db19cf1 Scott Ullrich
			fwrite($fd, $ipent['ip'] . "," . $ruleno ."\n");
715
			
716
			/* insert ipfw rule to allow ip thru */
717
			if ($ipent['dir'] == "from") {
718
				mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any in");
719
				mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " out");
720
			} else {
721
				mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " in");
722
				mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any out");
723
			}
724
			
725
			$ruleno++;
726
			if ($ruleno > 19899)
727
				$ruleno = 10000;
728 5b237745 Scott Ullrich
		}
729 3db19cf1 Scott Ullrich
		
730
		fclose($fd); 
731 5b237745 Scott Ullrich
732
		/* write next rule number */
733
		$fd = @fopen("{$g['vardb_path']}/captiveportal.nextrule", "w");
734
		if ($fd) {
735
			fwrite($fd, $ruleno);
736
			fclose($fd);
737
		}
738
	}
739 3db19cf1 Scott Ullrich
	
740
	captiveportal_unlock();
741 5b237745 Scott Ullrich
	return 0;
742
}
743
744 3db19cf1 Scott Ullrich
/* get last activity timestamp given ipfw rule number */
745
function captiveportal_get_last_activity($ruleno) {
746
	
747 3e789a8b Scott Ullrich
	$ipfwoutput = "";
748 63fff79b Scott Ullrich
	
749 3db19cf1 Scott Ullrich
	exec("/sbin/ipfw -T list {$ruleno} 2>/dev/null", $ipfwoutput);
750
	
751
	/* in */
752
	if ($ipfwoutput[0]) {
753
		$ri = explode(" ", $ipfwoutput[0]);
754
		if ($ri[1])
755
			return $ri[1];
756
	}
757
	
758 5b237745 Scott Ullrich
	return 0;
759
}
760
761
/* read RADIUS servers into array */
762
function captiveportal_get_radius_servers() {
763 0bd34ed6 Scott Ullrich
764
        global $g;
765
766
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
767
                $fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db","r");
768
                if ($fd) {
769
                        $radiusservers = array();
770
                        while (!feof($fd)) {
771
                                $line = trim(fgets($fd));
772
                                if ($line) {
773
                                        $radsrv = array();
774
                                        list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
775
                                        $radiusservers[] = $radsrv;
776
                                }
777
                        }
778
                        fclose($fd);
779
780
                        return $radiusservers;
781
                }
782
        }
783
784
        return false;
785 5b237745 Scott Ullrich
}
786
787
/* lock captive portal information, decide that the lock file is stale after
788
   10 seconds */
789
function captiveportal_lock() {
790 0bd34ed6 Scott Ullrich
791
        global $lockfile;
792
793
        $n = 0;
794
        while ($n < 10) {
795
                /* open the lock file in append mode to avoid race condition */
796
                if ($fd = @fopen($lockfile, "x")) {
797
                        /* succeeded */
798
                        fclose($fd);
799
                        return;
800
                } else {
801
                        /* file locked, wait and try again */
802
                        sleep(1);
803
                        $n++;
804
                }
805
        }
806 5b237745 Scott Ullrich
}
807
808 0bd34ed6 Scott Ullrich
/* unlock captive portal information file */
809 5b237745 Scott Ullrich
function captiveportal_unlock() {
810 0bd34ed6 Scott Ullrich
811
        global $lockfile;
812
813
        if (file_exists($lockfile))
814
                unlink($lockfile);
815 5b237745 Scott Ullrich
}
816
817 3db19cf1 Scott Ullrich
/* log successful captive portal authentication to syslog */
818
/* part of this code from php.net */
819 0bd34ed6 Scott Ullrich
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
820 3db19cf1 Scott Ullrich
	define_syslog_variables();
821 0bd34ed6 Scott Ullrich
	$message = trim($message);
822 3db19cf1 Scott Ullrich
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
823
	// Log it
824 0bd34ed6 Scott Ullrich
	if (!$message)
825 3db19cf1 Scott Ullrich
	syslog(LOG_INFO, "$status: $user, $mac, $ip");
826 0bd34ed6 Scott Ullrich
	else
827
	syslog(LOG_INFO, "$status: $user, $mac, $ip, $message");
828 3db19cf1 Scott Ullrich
	closelog();
829
}
830
831 0bd34ed6 Scott Ullrich
function radius($username,$password,$clientip,$clientmac,$type) {
832
	global $g, $config;
833
834
	$next_ruleno = get_next_ipfw_ruleno();
835
	$radiusservers = captiveportal_get_radius_servers();
836
	$radacct_enable = isset($config['captiveportal']['radacct_enable']);
837
838
	$auth_list = RADIUS_AUTHENTICATION($username,
839
					$password,
840
					$radiusservers,
841
					$clientip,
842
					$clientmac,
843
					$next_ruleno);
844
845
	if ($auth_list['auth_val'] == 2) {
846
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
847
		$sessionid = portal_allow($clientip,
848
					$clientmac,
849
					$username,
850
					$password,
851
					$auth_list['session_timeout'],
852
					$auth_list['idle_timeout'],
853
					$auth_list['url_redirection'],
854
					$auth_list['session_terminate_time']);
855
856
		if ($radacct_enable) {
857
			$auth_list['acct_val'] = RADIUS_ACCOUNTING_START($next_ruleno,
858
									$username,
859
									$sessionid,
860
									$radiusservers[0]['ipaddr'],
861
									$radiusservers[0]['acctport'],
862
									$radiusservers[0]['key'],
863
									$clientip,
864
									$clientmac);
865
			if ($auth_list['acct_val'] == 1) 
866
				captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
867
		}
868
	}
869
870
	return $auth_list;
871
872
}
873
874
/* read captive portal DB into array */
875
function captiveportal_read_db() {
876
877
        global $g;
878
879
        $cpdb = array();
880
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
881
        if ($fd) {
882
                while (!feof($fd)) {
883
                        $line = trim(fgets($fd));
884
                        if ($line) {
885
                                $cpdb[] = explode(",", $line);
886
                        }
887
                }
888
                fclose($fd);
889
        }
890
        return $cpdb;
891
}
892
893
/* write captive portal DB */
894
function captiveportal_write_db($cpdb) {
895
                 
896
        global $g;
897
                
898
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
899
        if ($fd) { 
900
                foreach ($cpdb as $cpent) {
901
                        fwrite($fd, join(",", $cpent) . "\n");
902
                }       
903
                fclose($fd);
904
        }       
905
}
906
907
function captiveportal_write_elements() {
908
	global $g, $config;
909
	
910
	/* delete any existing elements */
911
	if (is_dir($g['captiveportal_element_path'])) {
912
		$dh = opendir($g['captiveportal_element_path']);
913
		while (($file = readdir($dh)) !== false) {
914
			if ($file != "." && $file != "..")
915
				unlink($g['captiveportal_element_path'] . "/" . $file);
916
		}
917
		closedir($dh);
918
	} else {
919
		mkdir($g['captiveportal_element_path']);
920
	}
921
	
922
	if (is_array($config['captiveportal']['element'])) {
923 f85166d3 Scott Ullrich
		conf_mount_rw();
924 0bd34ed6 Scott Ullrich
		foreach ($config['captiveportal']['element'] as $data) {
925
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
926
			if (!$fd) {
927
				printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
928
				return 1;
929
			}
930
			$decoded = base64_decode($data['content']);
931
			fwrite($fd,$decoded);
932
			fclose($fd);
933 f85166d3 Scott Ullrich
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
934 bf7e3003 Scott Ullrich
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
935 f85166d3 Scott Ullrich
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
936 0bd34ed6 Scott Ullrich
		}
937 f85166d3 Scott Ullrich
		conf_mount_ro();
938 0bd34ed6 Scott Ullrich
	}
939
	
940
	return 0;
941
}
942
943 5480497a Scott Ullrich
?>