Project

General

Profile

Download (28.6 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(isset($config['captiveportal']['httpslogin'])) {
189
			$cert = base64_decode($config['captiveportal']['certificate']);
190
			$key = base64_decode($config['captiveportal']['private-key']);
191
			/* generate lighttpd configuration */
192
			system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal-SSL.conf",
193
				$cert, $key, "lighty-CaptivePortal-ssl.pid", "8001", "/usr/local/captiveportal/",
194
					"cert-portal.pem", "1", $maxproc, $use_fastcgi, true);
195
		}
196
		
197
		if ($config['captiveportal']['maxproc'])
198
			$maxproc = $config['captiveportal']['maxproc'];
199
		else
200
			$maxproc = 16;
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
# layer 2: block anything else non-IP
330
add 1101 set 1 deny layer2 not mac-type ip
331
# layer 2: check if MAC addresses of authenticated clients are correct
332
add 1102 set 1 skipto 20000 layer2
333

    
334
# allow access to our DHCP server (which needs to be able to ping clients as well)
335
add 1200 set 1 pass udp from any 68 to 255.255.255.255 67 in
336
add 1201 set 1 pass udp from any 68 to $cpip 67 in
337
add 1202 set 1 pass udp from $cpip 67 to any 68 out
338
add 1203 set 1 pass icmp from $cpip to any out icmptype 8
339
add 1204 set 1 pass icmp from any to $cpip in icmptype 0
340

    
341
# allow access to our DNS forwarder
342
add 1300 set 1 pass udp from any to $cpip 53 in
343
add 1301 set 1 pass udp from $cpip 53 to any out
344

    
345
# allow access to our web server
346
add 1302 set 1 pass tcp from any to $cpip 8000 in
347
add 1303 set 1 pass tcp from $cpip 8000 to any out
348

    
349
EOD;
350

    
351
	if (isset($config['captiveportal']['httpslogin'])) {
352
		$cprules .= <<<EOD
353
add 1304 set 1 pass tcp from any to $cpip 8001 in
354
add 1305 set 1 pass tcp from $cpip 8001 to any out
355

    
356
EOD;
357
	}
358
	
359
	$cprules .= <<<EOD
360

    
361
# ... 10000-19899: rules per authenticated client go here...
362

    
363
# redirect non-authenticated clients to captive portal
364
add 19900 set 1 fwd 127.0.0.1,8000 tcp from any to any 80 in
365
# let the responses from the captive portal web server back out
366
add 19901 set 1 pass tcp from any 80 to any out
367
# block everything else
368
add 19902 set 1 deny all from any to any
369

    
370
# ... 20000-29899: layer2 block rules per authenticated client go here...
371

    
372
# pass everything else on layer2
373
add 29900 set 1 pass all from any to any layer2
374

    
375
EOD;
376

    
377
	return $cprules;
378
}
379

    
380
/* remove clients that have been around for longer than the specified amount of time */
381
/* db file structure: 
382
timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time */
383

    
384
/* (password is in Base64 and only saved when reauthentication is enabled) */
385
function captiveportal_prune_old() {
386
	
387
	global $g, $config;
388

    
389
	/* check for expired entries */
390
	if ($config['captiveportal']['timeout'])
391
		$timeout = $config['captiveportal']['timeout'] * 60;
392
	else
393
		$timeout = 0;
394
		
395
	if ($config['captiveportal']['idletimeout'])
396
		$idletimeout = $config['captiveportal']['idletimeout'] * 60;
397
	else
398
		$idletimeout = 0;
399
	
400
	if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']))
401
		return;
402
	
403
	captiveportal_lock();
404
	
405
	/* read database */
406
	$cpdb = captiveportal_read_db();
407
	
408
	$radiusservers = captiveportal_get_radius_servers();
409
	
410
	for ($i = 0; $i < count($cpdb); $i++) {
411
		
412
		$timedout = false;
413
		$term_cause = 1;
414
		
415
		/* hard timeout? */
416
		if ($timeout) {
417
			if ((time() - $cpdb[$i][0]) >= $timeout) {
418
				$timedout = true;
419
				$term_cause = 5; // Session-Timeout
420
			}
421
		}
422

    
423
		/* Session-Terminate-Time */
424
		if (!$timedout && !empty($cpdb[$i][9])) {
425
			if (time() >= $cpdb[$i][9]) {
426
				$timedout = true;
427
				$term_cause = 5; // Session-Timeout
428
			}
429
		}
430
		
431
		/* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
432
		$idletimeout = (is_numeric($cpdb[$i][8])) ? $cpdb[$i][8] : $idletimeout;
433
		/* if an idle timeout is specified, get last activity timestamp from ipfw */
434
		if (!$timedout && $idletimeout) {
435
			$lastact = captiveportal_get_last_activity($cpdb[$i][1]);
436
			if ($lastact && ((time() - $lastact) >= $idletimeout)) {
437
				$timedout = true;
438
				$term_cause = 4; // Idle-Timeout
439
				$stop_time = $lastact; // Entry added to comply with WISPr
440
			}
441
		}
442

    
443
		/* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
444
		if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpdb[$i][7])) {
445
			if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
446
				$timedout = true;
447
				$term_cause = 5; // Session-Timeout
448
			}
449
		}
450
		
451
		if ($timedout) {
452
			captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
453
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
454
			unset($cpdb[$i]);
455
		}
456
		
457
		/* do periodic RADIUS reauthentication? */
458
		if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
459
			($radiusservers !== false)) {
460
		
461
			if (isset($config['captiveportal']['radacct_enable'])) {
462
				if ($config['captiveportal']['reauthenticateacct'] == "stopstart") {
463
					/* stop and restart accounting */
464
					RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
465
										   $cpdb[$i][4], // username
466
										   $cpdb[$i][5], // sessionid
467
										   $cpdb[$i][0], // start time
468
										   $radiusservers[0]['ipaddr'],
469
										   $radiusservers[0]['acctport'],
470
										   $radiusservers[0]['key'],
471
										   $cpdb[$i][2], // clientip
472
										   $cpdb[$i][3], // clientmac
473
										   10); // NAS Request
474
					exec("/sbin/ipfw zero {$cpdb[$i][1]}");
475
					RADIUS_ACCOUNTING_START($cpdb[$i][1], // ruleno
476
											$cpdb[$i][4], // username
477
											$cpdb[$i][5], // sessionid
478
											$radiusservers[0]['ipaddr'],
479
											$radiusservers[0]['acctport'],
480
											$radiusservers[0]['key'],
481
											$cpdb[$i][2], // clientip
482
											$cpdb[$i][3]); // clientmac
483
				} else if ($config['captiveportal']['reauthenticateacct'] == "interimupdate") {
484
					RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
485
										   $cpdb[$i][4], // username
486
										   $cpdb[$i][5], // sessionid
487
										   $cpdb[$i][0], // start time
488
										   $radiusservers[0]['ipaddr'],
489
										   $radiusservers[0]['acctport'],
490
										   $radiusservers[0]['key'],
491
										   $cpdb[$i][2], // clientip
492
										   $cpdb[$i][3], // clientmac
493
										   10, // NAS Request
494
										   true); // Interim Updates
495
				}
496
			}
497
		
498
			/* check this user against RADIUS again */
499
			$auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
500
										  base64_decode($cpdb[$i][6]), // password
501
							  			  $radiusservers,
502
										  $cpdb[$i][2], // clientip
503
										  $cpdb[$i][3], // clientmac
504
										  $cpdb[$i][1]); // ruleno
505
			
506
			if ($auth_list['auth_val'] == 3) {
507
				captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
508
				captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
509
				unset($cpdb[$i]);
510
			}
511
		}
512
	}
513
	
514
	/* write database */
515
	captiveportal_write_db($cpdb);
516
	
517
	captiveportal_unlock();
518
}
519

    
520
/* remove a single client according to the DB entry */
521
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
522
	
523
	global $g, $config;
524

    
525
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
526
	
527
	/* this client needs to be deleted - remove ipfw rules */
528
	if (isset($config['captiveportal']['radacct_enable']) && isset($radiusservers[0])) {
529
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
530
							   $dbent[4], // username
531
							   $dbent[5], // sessionid
532
							   $dbent[0], // start time
533
							   $radiusservers[0]['ipaddr'],
534
							   $radiusservers[0]['acctport'],
535
							   $radiusservers[0]['key'],
536
							   $dbent[2], // clientip
537
							   $dbent[3], // clientmac
538
							   $term_cause, // Acct-Terminate-Cause
539
							   false,
540
							   $stop_time);
541
	}
542
	
543
	mwexec("/sbin/ipfw delete " . $dbent[1] . " " . ($dbent[1]+10000));
544
	
545
	//KEYCOM: we need to delete +40500 and +45500 as well...
546
	//these are the rule numbers we use to control traffic shaping for each logged in user via captive portal
547
	//we only need to remove our rules if peruserbw is turned on.
548
	if (isset($config['captiveportal']['peruserbw'])) {
549
		mwexec("/sbin/ipfw delete " . ($dbent[1]+40500));
550
		mwexec("/sbin/ipfw delete " . ($dbent[1]+45500));
551
	}
552
}
553

    
554
/* remove a single client by ipfw rule number */
555
function captiveportal_disconnect_client($id,$term_cause = 1) {
556
	
557
	global $g, $config;
558
	
559
	captiveportal_lock();
560
	
561
	/* read database */
562
	$cpdb = captiveportal_read_db();
563
	$radiusservers = captiveportal_get_radius_servers();
564
	
565
	/* find entry */	
566
	for ($i = 0; $i < count($cpdb); $i++) {
567
		if ($cpdb[$i][1] == $id) {
568
			captiveportal_disconnect($cpdb[$i], $radiusservers, $term_cause);
569
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "DISCONNECT");
570
			unset($cpdb[$i]);
571
			break;
572
		}
573
	}
574
	
575
	/* write database */
576
	captiveportal_write_db($cpdb);
577
	
578
	captiveportal_unlock();
579
}
580

    
581
/* send RADIUS acct stop for all current clients */
582
function captiveportal_radius_stop_all() {
583
	global $g, $config;
584
	
585
	if (!isset($config['captiveportal']['radacct_enable']))
586
		return;
587

    
588
	captiveportal_lock();
589
	$cpdb = captiveportal_read_db();
590
	
591
	$radiusservers = captiveportal_get_radius_servers();
592
	
593
	if (isset($radiusservers[0])) {
594
		for ($i = 0; $i < count($cpdb); $i++) {
595
			RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
596
								   $cpdb[$i][4], // username
597
								   $cpdb[$i][5], // sessionid
598
								   $cpdb[$i][0], // start time
599
								   $radiusservers[0]['ipaddr'],
600
								   $radiusservers[0]['acctport'],
601
								   $radiusservers[0]['key'],
602
								   $cpdb[$i][2], // clientip
603
								   $cpdb[$i][3], // clientmac
604
								   7); // Admin Reboot
605
		}
606
	}
607
	captiveportal_unlock();
608
}
609

    
610
function captiveportal_passthrumac_configure() {
611
	global $config, $g;
612
	
613
	captiveportal_lock();
614
	
615
	/* clear out passthru macs, if necessary */
616
	unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
617
	
618
	if (is_array($config['captiveportal']['passthrumac'])) {
619
		
620
		$fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db", "w");
621
		if (!$fd) {
622
			printf("Error: cannot open passthru mac DB file in captiveportal_passthrumac_configure().\n");
623
			captiveportal_unlock();
624
			return 1;		
625
		}
626
		
627
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
628
			/* record passthru mac so it can be recognized and let thru */
629
			fwrite($fd, $macent['mac'] . "\n");
630
		}
631
		
632
		fclose($fd); 
633
	}
634

    
635
	/*    pass through mac entries should always exist.  the reason
636
	 *    for this is because we do not have native mac address filtering
637
         *    mechanisms.  this allows us to filter by mac address easily
638
	 *    and get around this limitation.   I consider this a bug in
639
         *    m0n0wall and pfSense as m0n0wall does not have native mac 
640
         *    filtering mechanisms as well. -Scott Ullrich
641
         */
642
	if (is_array($config['captiveportal']['passthrumac'])) {
643
		mwexec("/sbin/ipfw delete 50");
644
		foreach($config['captiveportal']['passthrumac'] as $ptm) {
645
			/* create the pass through mac entry */
646
			//system("echo /sbin/ipfw add 50 skipto 65535 ip from any to any MAC {$ptm['mac']} any > /tmp/cp");
647
			mwexec("/sbin/ipfw add 50 skipto 29900 ip from any to any MAC {$ptm['mac']} any keep-state");
648
			mwexec("/sbin/ipfw add 50 skipto 29900 ip from any to any MAC any {$ptm['mac']} keep-state");
649
		}
650
	}
651

    
652
	captiveportal_unlock();
653
	
654
	return 0;
655
}
656

    
657
function captiveportal_allowedip_configure() {
658
	global $config, $g;
659
	
660
	captiveportal_lock();
661

    
662
	/* clear out existing allowed ips, if necessary */
663
	if (file_exists("{$g['vardb_path']}/captiveportal_ip.db")) {
664
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "r");
665
		if ($fd) {
666
			while (!feof($fd)) {
667
				$line = trim(fgets($fd));
668
				if ($line) {
669
					list($ip,$rule) = explode(",",$line);
670
					mwexec("/sbin/ipfw delete $rule");
671
				}	
672
			}
673
		}
674
		fclose($fd);
675
		unlink("{$g['vardb_path']}/captiveportal_ip.db");
676
	}
677

    
678
	/* get next ipfw rule number */
679
	if (file_exists("{$g['vardb_path']}/captiveportal.nextrule"))
680
		$ruleno = trim(file_get_contents("{$g['vardb_path']}/captiveportal.nextrule"));
681
	if (!$ruleno)
682
		$ruleno = 10000;	/* first rule number */
683
	
684
	if (is_array($config['captiveportal']['allowedip'])) {
685
		
686
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "w");
687
		if (!$fd) {
688
			printf("Error: cannot open allowed ip DB file in captiveportal_allowedip_configure().\n");
689
			captiveportal_unlock();
690
			return 1;		
691
		}
692
		
693
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
694
		
695
			/* record allowed ip so it can be recognized and removed later */
696
			fwrite($fd, $ipent['ip'] . "," . $ruleno ."\n");
697
			
698
			/* insert ipfw rule to allow ip thru */
699
			if ($ipent['dir'] == "from") {
700
				mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any in");
701
				mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " out");
702
			} else {
703
				mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " in");
704
				mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any out");
705
			}
706
			
707
			$ruleno++;
708
			if ($ruleno > 19899)
709
				$ruleno = 10000;
710
		}
711
		
712
		fclose($fd); 
713

    
714
		/* write next rule number */
715
		$fd = @fopen("{$g['vardb_path']}/captiveportal.nextrule", "w");
716
		if ($fd) {
717
			fwrite($fd, $ruleno);
718
			fclose($fd);
719
		}
720
	}
721
	
722
	captiveportal_unlock();
723
	return 0;
724
}
725

    
726
/* get last activity timestamp given ipfw rule number */
727
function captiveportal_get_last_activity($ruleno) {
728
	
729
	$ipfwoutput = "";
730
	
731
	exec("/sbin/ipfw -T list {$ruleno} 2>/dev/null", $ipfwoutput);
732
	
733
	/* in */
734
	if ($ipfwoutput[0]) {
735
		$ri = explode(" ", $ipfwoutput[0]);
736
		if ($ri[1])
737
			return $ri[1];
738
	}
739
	
740
	return 0;
741
}
742

    
743
/* read RADIUS servers into array */
744
function captiveportal_get_radius_servers() {
745

    
746
        global $g;
747

    
748
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
749
                $fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db","r");
750
                if ($fd) {
751
                        $radiusservers = array();
752
                        while (!feof($fd)) {
753
                                $line = trim(fgets($fd));
754
                                if ($line) {
755
                                        $radsrv = array();
756
                                        list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
757
                                        $radiusservers[] = $radsrv;
758
                                }
759
                        }
760
                        fclose($fd);
761

    
762
                        return $radiusservers;
763
                }
764
        }
765

    
766
        return false;
767
}
768

    
769
/* lock captive portal information, decide that the lock file is stale after
770
   10 seconds */
771
function captiveportal_lock() {
772

    
773
        global $lockfile;
774

    
775
        $n = 0;
776
        while ($n < 10) {
777
                /* open the lock file in append mode to avoid race condition */
778
                if ($fd = @fopen($lockfile, "x")) {
779
                        /* succeeded */
780
                        fclose($fd);
781
                        return;
782
                } else {
783
                        /* file locked, wait and try again */
784
                        sleep(1);
785
                        $n++;
786
                }
787
        }
788
}
789

    
790
/* unlock captive portal information file */
791
function captiveportal_unlock() {
792

    
793
        global $lockfile;
794

    
795
        if (file_exists($lockfile))
796
                unlink($lockfile);
797
}
798

    
799
/* log successful captive portal authentication to syslog */
800
/* part of this code from php.net */
801
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
802
	define_syslog_variables();
803
	$message = trim($message);
804
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
805
	// Log it
806
	if (!$message)
807
	syslog(LOG_INFO, "$status: $user, $mac, $ip");
808
	else
809
	syslog(LOG_INFO, "$status: $user, $mac, $ip, $message");
810
	closelog();
811
}
812

    
813
function radius($username,$password,$clientip,$clientmac,$type) {
814
	global $g, $config;
815

    
816
	$next_ruleno = get_next_ipfw_ruleno();
817
	$radiusservers = captiveportal_get_radius_servers();
818
	$radacct_enable = isset($config['captiveportal']['radacct_enable']);
819

    
820
	$auth_list = RADIUS_AUTHENTICATION($username,
821
					$password,
822
					$radiusservers,
823
					$clientip,
824
					$clientmac,
825
					$next_ruleno);
826

    
827
	if ($auth_list['auth_val'] == 2) {
828
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
829
		$sessionid = portal_allow($clientip,
830
					$clientmac,
831
					$username,
832
					$password,
833
					$auth_list['session_timeout'],
834
					$auth_list['idle_timeout'],
835
					$auth_list['url_redirection'],
836
					$auth_list['session_terminate_time']);
837

    
838
		if ($radacct_enable) {
839
			$auth_list['acct_val'] = RADIUS_ACCOUNTING_START($next_ruleno,
840
									$username,
841
									$sessionid,
842
									$radiusservers[0]['ipaddr'],
843
									$radiusservers[0]['acctport'],
844
									$radiusservers[0]['key'],
845
									$clientip,
846
									$clientmac);
847
			if ($auth_list['acct_val'] == 1) 
848
				captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
849
		}
850
	}
851

    
852
	return $auth_list;
853

    
854
}
855

    
856
/* read captive portal DB into array */
857
function captiveportal_read_db() {
858

    
859
        global $g;
860

    
861
        $cpdb = array();
862
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
863
        if ($fd) {
864
                while (!feof($fd)) {
865
                        $line = trim(fgets($fd));
866
                        if ($line) {
867
                                $cpdb[] = explode(",", $line);
868
                        }
869
                }
870
                fclose($fd);
871
        }
872
        return $cpdb;
873
}
874

    
875
/* write captive portal DB */
876
function captiveportal_write_db($cpdb) {
877
                 
878
        global $g;
879
                
880
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
881
        if ($fd) { 
882
                foreach ($cpdb as $cpent) {
883
                        fwrite($fd, join(",", $cpent) . "\n");
884
                }       
885
                fclose($fd);
886
        }       
887
}
888

    
889
function captiveportal_write_elements() {
890
	global $g, $config;
891
	
892
	/* delete any existing elements */
893
	if (is_dir($g['captiveportal_element_path'])) {
894
		$dh = opendir($g['captiveportal_element_path']);
895
		while (($file = readdir($dh)) !== false) {
896
			if ($file != "." && $file != "..")
897
				unlink($g['captiveportal_element_path'] . "/" . $file);
898
		}
899
		closedir($dh);
900
	} else {
901
		mkdir($g['captiveportal_element_path']);
902
	}
903
	
904
	if (is_array($config['captiveportal']['element'])) {
905
		conf_mount_rw();
906
		foreach ($config['captiveportal']['element'] as $data) {
907
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
908
			if (!$fd) {
909
				printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
910
				return 1;
911
			}
912
			$decoded = base64_decode($data['content']);
913
			fwrite($fd,$decoded);
914
			fclose($fd);
915
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
916
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
917
		}
918
		conf_mount_ro();
919
	}
920
	
921
	return 0;
922
}
923

    
924
?>
(4-4/27)