Project

General

Profile

Download (29.2 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	captiveportal.inc
4
	part of m0n0wall (http://m0n0.ch/wall)
5
	
6
	Copyright (C) 2003-2006 Manuel Kasper <mk@neon1.net>.
7
	All rights reserved.
8
	
9
	Redistribution and use in source and binary forms, with or without
10
	modification, are permitted provided that the following conditions are met:
11
	
12
	1. Redistributions of source code must retain the above copyright notice,
13
	   this list of conditions and the following disclaimer.
14
	
15
	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
	
19
	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

    
30
	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
	
37
/* include all configuration functions */
38
require_once("functions.inc");
39
require_once("radius_authentication.inc");
40
require_once("radius_accounting.inc");
41

    
42
$lockfile = "{$g['varrun_path']}/captiveportal.lock";
43

    
44
function captiveportal_configure() {
45
	global $config, $g;
46
	
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
		/* kill any running mini_httpd */
55
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
56
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal-SSL.pid");
57
		
58
		/* kill any running minicron */
59
		killbypid("{$g['varrun_path']}/minicron.pid");
60
		
61
		/* generate ipfw rules */
62
		$cprules = captiveportal_rules_generate();
63
		
64
		/* make sure ipfw is loaded */
65
		mwexec("/sbin/kldload ipfw");
66
		
67
		/* stop accounting on all clients */
68
		captiveportal_radius_stop_all();
69

    
70
		/* 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
		/* 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
		
83
		/* 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
<title>pfSense captive portal</title>
92
</head>
93
<body>
94
<center>
95
<h2>pfSense captive portal</h2>
96
Welcome to the pfSense Captive Portal!  This is the default page since a custom page has not been defined.
97
<p>
98
<form method="post" action="\$PORTAL_ACTION\$">
99
<input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$">
100
<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
   <tr><td>&nbsp;</td></tr>
104
   <tr>
105
     <td colspan="2">
106
	<center><input name="accept" type="submit" value="Continue"></center>
107
     </td>
108
   </tr>
109
</table>
110
</center>
111
</form>
112
</body>
113
</html>
114

    
115

    
116

    
117
EOD;
118
		}
119

    
120
		$fd = @fopen("{$g['varetc_path']}/captiveportal.html", "w");
121
		if ($fd) {
122
			fwrite($fd, $htmltext);
123
			fclose($fd);	
124
		}
125
		
126
		/* 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
			fclose($fd);	
153
		}
154
		
155
		/* write elements */
156
		captiveportal_write_elements();
157

    
158
		/* 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
		/* ipfw cannot accept rules directly on stdin,
164
		   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
		chdir($g['captiveportal_path']);
182
	
183
		/* TEMPORARY!  FAST_CGI reports _FALSE_ client ip
184
		 * 	       addresses.
185
		 */
186
		$use_fastcgi = false;
187

    
188
		if ($config['captiveportal']['maxproc'])
189
			$maxproc = $config['captiveportal']['maxproc'];
190
		else
191
			$maxproc = 16;
192
					
193
		if(isset($config['captiveportal']['httpslogin'])) {
194
			$cert = base64_decode($config['captiveportal']['certificate']);
195
			$key = base64_decode($config['captiveportal']['private-key']);
196
			/* 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
		}
201
		
202
		/* generate lighttpd configuration */
203
		system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal.conf",
204
			"", "", "lighty-CaptivePortal.pid", "8000", "/usr/local/captiveportal/",
205
				"cert-portal.pem", "1", $maxproc, $use_fastcgi, true);
206
		
207
		/* attempt to start lighttpd */
208
		$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-CaptivePortal.conf");
209
		
210
		/* 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
		/* start pruning process (interval defaults to 60 seconds) */
215
		mwexec("/usr/local/bin/minicron $croninterval {$g['varrun_path']}/minicron.pid " .
216
			"/etc/rc.prunecaptiveportal");
217
		
218
		/* generate passthru mac database */
219
		captiveportal_passthrumac_configure();
220
		/* create allowed ip database and insert ipfw rules to make it so */
221
		captiveportal_allowedip_configure();
222
		
223
		/* generate radius server database */
224
		if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
225
				($config['captiveportal']['auth_method'] == "radius"))) {
226
			$radiusip = $config['captiveportal']['radiusip'];
227
			$radiusip2 = ($config['captiveportal']['radiusip2']) ? $config['captiveportal']['radiusip2'] : null;
228
			
229
			if ($config['captiveportal']['radiusport'])
230
				$radiusport = $config['captiveportal']['radiusport'];
231
			else
232
				$radiusport = 1812;
233

    
234
			if ($config['captiveportal']['radiusacctport'])
235
				$radiusacctport = $config['captiveportal']['radiusacctport'];
236
			else
237
				$radiusacctport = 1813;
238

    
239
			if ($config['captiveportal']['radiusport2'])
240
				$radiusport2 = $config['captiveportal']['radiusport2'];
241
			else
242
				$radiusport2 = 1812;
243

    
244
			$radiuskey = $config['captiveportal']['radiuskey'];
245
			$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
246

    
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
			} else if (isset($radiusip2, $radiuskey2)) {
252
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . "\n"
253
					 . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2);
254
			} else {
255
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey);
256
			}
257
			fclose($fd);
258
		}
259

    
260
		if ($g['booting'])
261
			echo "done\n";
262
		
263
	} else {
264
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
265
		killbypid("{$g['varrun_path']}/minicron.pid");
266

    
267
		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
	return 0;
283
}
284

    
285
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

    
297
	/*   allow nat redirects to work  see
298
	     http://cvstrac.pfsense.com/tktview?tn=651
299
	 */
300
	$iflist = array("lan" => "LAN", "wan" => "WAN");
301
	$captive_portal_interface = strtoupper($cpifn);
302
	for ($i = 1; isset($config['interfaces']['opt' . $i]); $i++) 
303
		$iflist['opt' . $i] = "OPT{$i}";	
304
	foreach ($iflist as $ifent => $ifname) {
305
		if($captive_portal_interface == strtoupper($ifname))
306
			continue;
307
		$int = convert_friendly_interface_to_real_interface_name($ifname);
308
		$cprules .= "add 30 set 1 skipto 50000 all from any to any in via {$int} keep-state\n";
309
	}
310
	
311
	/* captive portal on LAN interface? */
312
	if ($cpifn == "lan") {
313
		/* add anti-lockout rules */
314
		$cprules .= <<<EOD
315
add 500 set 1 pass all from $cpip to any out via $cpif
316
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
# pfsense requires for WPA
330
add 1100 set 1 pass layer2 mac-type 0x888e
331

    
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
# 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

    
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
# let the responses from the captive portal web server back out
384
add 19903 set 1 pass tcp from any 80 to any out
385
# block everything else
386
add 19904 set 1 deny all from any to any
387

    
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
/* remove clients that have been around for longer than the specified amount of time */
399
/* db file structure: 
400
timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time */
401

    
402
/* (password is in Base64 and only saved when reauthentication is enabled) */
403
function captiveportal_prune_old() {
404
	
405
	global $g, $config;
406

    
407
	/* check for expired entries */
408
	if ($config['captiveportal']['timeout'])
409
		$timeout = $config['captiveportal']['timeout'] * 60;
410
	else
411
		$timeout = 0;
412
		
413
	if ($config['captiveportal']['idletimeout'])
414
		$idletimeout = $config['captiveportal']['idletimeout'] * 60;
415
	else
416
		$idletimeout = 0;
417
	
418
	if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']))
419
		return;
420
	
421
	captiveportal_lock();
422
	
423
	/* read database */
424
	$cpdb = captiveportal_read_db();
425
	
426
	$radiusservers = captiveportal_get_radius_servers();
427
	
428
	for ($i = 0; $i < count($cpdb); $i++) {
429
		
430
		$timedout = false;
431
		$term_cause = 1;
432
		
433
		/* hard timeout? */
434
		if ($timeout) {
435
			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
		}
448
		
449
		/* 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
		/* 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
			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
				$timedout = true;
465
				$term_cause = 5; // Session-Timeout
466
			}
467
		}
468
		
469
		if ($timedout) {
470
			captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
471
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
472
			unset($cpdb[$i]);
473
		}
474
		
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
										   $cpdb[$i][2], // clientip
490
										   $cpdb[$i][3], // clientmac
491
										   10); // NAS Request
492
					exec("/sbin/ipfw zero {$cpdb[$i][1]}");
493
					RADIUS_ACCOUNTING_START($cpdb[$i][1], // ruleno
494
											$cpdb[$i][4], // username
495
											$cpdb[$i][5], // sessionid
496
											$radiusservers[0]['ipaddr'],
497
											$radiusservers[0]['acctport'],
498
											$radiusservers[0]['key'],
499
											$cpdb[$i][2], // clientip
500
											$cpdb[$i][3]); // clientmac
501
				} 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
										   $cpdb[$i][2], // clientip
510
										   $cpdb[$i][3], // clientmac
511
										   10, // NAS Request
512
										   true); // Interim Updates
513
				}
514
			}
515
		
516
			/* check this user against RADIUS again */
517
			$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
			
524
			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
				unset($cpdb[$i]);
528
			}
529
		}
530
	}
531
	
532
	/* write database */
533
	captiveportal_write_db($cpdb);
534
	
535
	captiveportal_unlock();
536
}
537

    
538
/* remove a single client according to the DB entry */
539
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
540
	
541
	global $g, $config;
542

    
543
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
544
	
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
							   $dbent[2], // clientip
555
							   $dbent[3], // clientmac
556
							   $term_cause, // Acct-Terminate-Cause
557
							   false,
558
							   $stop_time);
559
	}
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

    
572
/* remove a single client by ipfw rule number */
573
function captiveportal_disconnect_client($id,$term_cause = 1) {
574
	
575
	global $g, $config;
576
	
577
	captiveportal_lock();
578
	
579
	/* read database */
580
	$cpdb = captiveportal_read_db();
581
	$radiusservers = captiveportal_get_radius_servers();
582
	
583
	/* find entry */	
584
	for ($i = 0; $i < count($cpdb); $i++) {
585
		if ($cpdb[$i][1] == $id) {
586
			captiveportal_disconnect($cpdb[$i], $radiusservers, $term_cause);
587
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "DISCONNECT");
588
			unset($cpdb[$i]);
589
			break;
590
		}
591
	}
592
	
593
	/* write database */
594
	captiveportal_write_db($cpdb);
595
	
596
	captiveportal_unlock();
597
}
598

    
599
/* send RADIUS acct stop for all current clients */
600
function captiveportal_radius_stop_all() {
601
	global $g, $config;
602
	
603
	if (!isset($config['captiveportal']['radacct_enable']))
604
		return;
605

    
606
	captiveportal_lock();
607
	$cpdb = captiveportal_read_db();
608
	
609
	$radiusservers = captiveportal_get_radius_servers();
610
	
611
	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
								   $radiusservers[0]['key'],
620
								   $cpdb[$i][2], // clientip
621
								   $cpdb[$i][3], // clientmac
622
								   7); // Admin Reboot
623
		}
624
	}
625
	captiveportal_unlock();
626
}
627

    
628
function captiveportal_passthrumac_configure() {
629
	global $config, $g;
630
	
631
	captiveportal_lock();
632
	
633
	/* clear out passthru macs, if necessary */
634
	unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
635
	
636
	if (is_array($config['captiveportal']['passthrumac'])) {
637
		
638
		$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
			captiveportal_unlock();
642
			return 1;		
643
		}
644
		
645
		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
		
650
		fclose($fd); 
651
	}
652

    
653
	/*    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
			//system("echo /sbin/ipfw add 50 skipto 65535 ip from any to any MAC {$ptm['mac']} any > /tmp/cp");
665
			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
		}
668
	}
669

    
670
	captiveportal_unlock();
671
	
672
	return 0;
673
}
674

    
675
function captiveportal_allowedip_configure() {
676
	global $config, $g;
677
	
678
	captiveportal_lock();
679

    
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
				if ($line) {
687
					list($ip,$rule) = explode(",",$line);
688
					mwexec("/sbin/ipfw delete $rule");
689
				}	
690
			}
691
		}
692
		fclose($fd);
693
		unlink("{$g['vardb_path']}/captiveportal_ip.db");
694
	}
695

    
696
	/* 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
	if (is_array($config['captiveportal']['allowedip'])) {
703
		
704
		$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
			captiveportal_unlock();
708
			return 1;		
709
		}
710
		
711
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
712
		
713
			/* record allowed ip so it can be recognized and removed later */
714
			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
		}
729
		
730
		fclose($fd); 
731

    
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
	
740
	captiveportal_unlock();
741
	return 0;
742
}
743

    
744
/* get last activity timestamp given ipfw rule number */
745
function captiveportal_get_last_activity($ruleno) {
746
	
747
	$ipfwoutput = "";
748
	
749
	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
	return 0;
759
}
760

    
761
/* read RADIUS servers into array */
762
function captiveportal_get_radius_servers() {
763

    
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
}
786

    
787
/* lock captive portal information, decide that the lock file is stale after
788
   10 seconds */
789
function captiveportal_lock() {
790

    
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
}
807

    
808
/* unlock captive portal information file */
809
function captiveportal_unlock() {
810

    
811
        global $lockfile;
812

    
813
        if (file_exists($lockfile))
814
                unlink($lockfile);
815
}
816

    
817
/* log successful captive portal authentication to syslog */
818
/* part of this code from php.net */
819
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
820
	define_syslog_variables();
821
	$message = trim($message);
822
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
823
	// Log it
824
	if (!$message)
825
	syslog(LOG_INFO, "$status: $user, $mac, $ip");
826
	else
827
	syslog(LOG_INFO, "$status: $user, $mac, $ip, $message");
828
	closelog();
829
}
830

    
831
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
		conf_mount_rw();
924
		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
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
934
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
935
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
936
		}
937
		conf_mount_ro();
938
	}
939
	
940
	return 0;
941
}
942

    
943
?>
(4-4/27)