Project

General

Profile

Download (22.7 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	captiveportal.inc
4
	part of m0n0wall (http://m0n0.ch/wall)
5
	
6
	Copyright (C) 2003-2005 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
function captiveportal_configure() {
43
	global $config, $g;
44
	
45
	if (isset($config['captiveportal']['enable']) &&
46
		(($config['captiveportal']['interface'] == "lan") ||
47
			isset($config['interfaces'][$config['captiveportal']['interface']]['enable']))) {
48
	
49
		if ($g['booting'])
50
			echo "Starting captive portal... ";
51
		
52
		/* kill any running mini_httpd */
53
		killbypid("{$g['varrun_path']}/mini_httpd.cp.pid");
54
		killbypid("{$g['varrun_path']}/mini_httpd.cps.pid");
55
		
56
		/* kill any running minicron */
57
		killbypid("{$g['varrun_path']}/minicron.pid");
58
		
59
		/* generate ipfw rules */
60
		$cprules = captiveportal_rules_generate();
61
		
62
		/* make sure ipfw is loaded */
63
		mwexec("/sbin/kldload ipfw");
64
		
65
		/* stop accounting on all clients */
66
		captiveportal_radius_stop_all();
67

    
68
		/* remove old information */
69
		unlink_if_exists("{$g['vardb_path']}/captiveportal.nextrule");
70
		unlink_if_exists("{$g['vardb_path']}/captiveportal.db");
71
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
72
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip.db");
73
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius.db");
74
		
75
		/* write portal page */
76
		if ($config['captiveportal']['page']['htmltext'])
77
			$htmltext = base64_decode($config['captiveportal']['page']['htmltext']);
78
		else {
79
			/* example/template page */
80
			$htmltext = <<<EOD
81
<html>
82
<head>
83
<title>pfSense captive portal</title>
84
</head>
85
<body>
86
<center>
87
<h2>pfSense captive portal</h2>
88
Welcome to the pfSense Captive Portal!  This is the default page since a custom page has not been defined.
89
<p>
90
<form method="post" action="\$PORTAL_ACTION\$">
91
<input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$">
92
<table>
93
   <tr><td>Username:</td><td><input name="auth_user" type="text"></td></tr>
94
   <tr><td>Password:</td><td><input name="auth_pass" type="password"></td></tr>
95
   <tr><td>&nbsp;</td></tr>
96
   <tr>
97
     <td colspan="2">
98
	<center><input name="accept" type="submit" value="Continue"></center>	
99
     </td>
100
   </tr>
101
</table>
102
</center>
103
</form>
104
</body>
105
</html>
106

    
107
EOD;
108
		}
109

    
110
		$fd = @fopen("{$g['varetc_path']}/captiveportal.html", "w");
111
		if ($fd) {
112
			fwrite($fd, $htmltext);
113
			fclose($fd);	
114
		}
115
		
116
		/* write error page */
117
		if ($config['captiveportal']['page']['errtext'])
118
			$errtext = base64_decode($config['captiveportal']['page']['errtext']);
119
		else {
120
			/* example page */
121
			$errtext = <<<EOD
122
<html>
123
<head>
124
<title>Authentication error</title>
125
</head>
126
<body>
127
<font color="#cc0000"><h2>Authentication error</h2></font>
128
<b>
129
Username and/or password invalid.
130
<br><br>
131
<a href="javascript:history.back()">Go back</a>
132
</b>
133
</body>
134
</html>
135

    
136
EOD;
137
		}
138

    
139
		$fd = @fopen("{$g['varetc_path']}/captiveportal-error.html", "w");
140
		if ($fd) {
141
			fwrite($fd, $errtext);
142
			fclose($fd);	
143
		}
144

    
145
		/* load rules */
146
		mwexec("/sbin/ipfw -f delete set 1");
147
		mwexec("/sbin/ipfw -f delete set 2");
148
		mwexec("/sbin/ipfw -f delete set 3");
149
		
150
		/* XXX - seems like ipfw cannot accept rules directly on stdin,
151
		   so we have to write them to a temporary file first */
152
		$fd = @fopen("{$g['tmp_path']}/ipfw.cp.rules", "w");
153
		if (!$fd) {
154
			printf("Cannot open ipfw.cp.rules in captiveportal_configure()\n");
155
			return 1;
156
		}
157
			
158
		fwrite($fd, $cprules);
159
		fclose($fd);
160
		
161
		mwexec("/sbin/ipfw {$g['tmp_path']}/ipfw.cp.rules");
162
		
163
		unlink("{$g['tmp_path']}/ipfw.cp.rules");
164
		
165
		/* filter on layer2 as well so we can check MAC addresses */
166
		mwexec("/sbin/sysctl net.link.ether.ipfw=1");
167
		
168
		chdir($g['captiveportal_path']);
169

    
170
		$memory = get_memory();
171
		$avail = $memory[0];
172
		if($avail > 0 and $avail < 60) {
173
			$procs = 16;
174
		} else if($avail > 60 and $avail < 120) {
175
			$procs = 24;
176
		} else if($avail > 120 and $avail < 160) {
177
			$procs = 32;
178
		} else if($avail > 160 and $avail < 250) {
179
			$procs = 48;
180
		} else if($avail > 250 and $avail < 380) {
181
			$procs = 56;
182
		} else if($avail > 380 and $avail < 500) {
183
			$procs = 72;
184
		} else if($avail > 500 and $avail < 680) {
185
			$procs = 80;
186
		} else {
187
			$procs = 16;
188
		}	
189
	
190
		/* TEMPORARY!  FAST_CGI reports _FALSE_ client ip
191
		 * 	       addresses.
192
		 */
193
		$use_fastcgi = false;
194

    
195
		if(isset($config['captiveportal']['httpslogin'])) {
196
			$cert = base64_decode($config['captiveportal']['certificate']);
197
			$key = base64_decode($config['captiveportal']['private-key']);
198
		}
199

    
200
		/* generate lighttpd configuration */
201
		system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal.conf",
202
			$cert, $key, "lighty-CaptivePortal.pid", "8000", "/usr/local/captiveportal/",
203
				"cert-portal.pem", "1", $procs, $use_fastcgi, true);
204
	
205
		/* attempt to start lighttpd */
206
		$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-CaptivePortal.conf");
207
			
208
		/* start pruning process (interval = 60 seconds) */
209
		mwexec("/usr/local/bin/minicron 60 {$g['varrun_path']}/minicron.pid " .
210
			"/etc/rc.prunecaptiveportal");
211
		
212
		/* generate passthru mac database */
213
		captiveportal_passthrumac_configure();
214
		/* create allowed ip database and insert ipfw rules to make it so */
215
		captiveportal_allowedip_configure();
216

    
217
		/* generate radius server database */
218
		if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
219
				($config['captiveportal']['auth_method'] == "radius"))) {
220
			$radiusip = $config['captiveportal']['radiusip'];
221

    
222
			if ($config['captiveportal']['radiusport'])
223
				$radiusport = $config['captiveportal']['radiusport'];
224
			else
225
				$radiusport = 1812;
226

    
227
			if ($config['captiveportal']['radiusacctport'])
228
				$radiusacctport = $config['captiveportal']['radiusacctport'];
229
			else
230
				$radiusacctport = 1813;
231

    
232
			$radiuskey = $config['captiveportal']['radiuskey'];
233

    
234
			$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db", "w");
235
			if (!$fd) {
236
				printf("Error: cannot open radius DB file in captiveportal_configure().\n");
237
				return 1;
238
			} else {
239
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey);
240
			}
241
			fclose($fd);
242
		}
243

    
244
		if ($g['booting'])
245
			echo "done\n";
246
		
247
	} else {
248
		killbypid("{$g['varrun_path']}/mini_httpd.cp.pid");
249
		killbypid("{$g['varrun_path']}/mini_httpd.cps.pid");
250
		killbypid("{$g['varrun_path']}/minicron.pid");
251

    
252
		captiveportal_radius_stop_all();
253

    
254
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
255

    
256
		if (!isset($config['shaper']['enable'])) {
257
			/* unload ipfw */
258
			mwexec("/sbin/kldunload ipfw");
259
		} else {
260
			/* shaper is on - just remove our rules */
261
			mwexec("/sbin/ipfw -f delete set 1");
262
			mwexec("/sbin/ipfw -f delete set 2");
263
			mwexec("/sbin/ipfw -f delete set 3");
264
		}
265
	}
266
	
267
	return 0;
268
}
269

    
270
function captiveportal_rules_generate() {
271
	global $config, $g;
272
	
273
	$cpifn = $config['captiveportal']['interface'];
274
	$cpif = $config['interfaces'][$cpifn]['if'];
275
	$cpip = $config['interfaces'][$cpifn]['ipaddr'];
276

    
277
	/* note: the captive portal daemon inserts all pass rules for authenticated
278
	   clients as skipto 50000 rules to make traffic shaping work */
279

    
280
	$cprules = "";
281

    
282
	/*   allow nat redirects to work  see
283
	     http://cvstrac.pfsense.com/tktview?tn=651
284
	 */
285
	$iflist = array("lan" => "LAN", "wan" => "WAN");
286
	$captive_portal_interface = strtoupper($cpifn);
287
	for ($i = 1; isset($config['interfaces']['opt' . $i]); $i++) 
288
		$iflist['opt' . $i] = "OPT{$i}";	
289
	foreach ($iflist as $ifent => $ifname) {
290
		if($captive_portal_interface == strtoupper($ifname))
291
			continue;
292
		$int = convert_friendly_interface_to_real_interface_name($ifname);
293
		$cprules .= "add 30 set 1 skipto 50000 all from any to any in via {$int} keep-state\n";
294
	}
295

    
296
	/* captive portal on LAN interface? */
297
	if ($cpifn == "lan") {
298
		/* add anti-lockout rules */
299
		$cprules .= <<<EOD
300
add 500 pass all from $cpip to any out via $cpif
301
add 501 set 1 pass all from any to $cpip in via $cpif
302

    
303
EOD;
304
	}
305

    
306
	$cprules .= <<<EOD
307
# skip to traffic shaper if not on captive portal interface
308
add 1000 set 1 skipto 50000 all from any to any not layer2 not via $cpif
309
# pass all layer2 traffic on other interfaces
310
add 1001 set 1 pass layer2 not via $cpif
311

    
312
# layer 2: pass ARP
313
add 1100 set 1 pass layer2 mac-type arp
314
# layer 2: block anything else non-IP
315
add 1101 set 1 deny layer2 not mac-type ip
316
# layer 2: check if MAC addresses of authenticated clients are correct
317
add 1102 set 1 skipto 20000 layer2
318

    
319
# allow access to our DHCP server (which needs to be able to ping clients as well)
320
add 1200 set 1 pass udp from any 68 to 255.255.255.255 67 in
321
add 1201 set 1 pass udp from any 68 to $cpip 67 in
322
add 1202 set 1 pass udp from $cpip 67 to any 68 out
323
add 1203 set 1 pass icmp from $cpip to any out icmptype 8
324
add 1204 set 1 pass icmp from any to $cpip in icmptype 0
325

    
326
# allow access to our DNS forwarder
327
add 1300 set 1 pass udp from any to $cpip 53 in
328
add 1301 set 1 pass udp from $cpip 53 to any out
329

    
330
# allow access to our web server
331
add 1302 set 1 pass tcp from any to $cpip 8000 in
332
add 1303 set 1 pass tcp from $cpip 8000 to any out
333

    
334
EOD;
335

    
336
	if (isset($config['captiveportal']['httpslogin'])) {
337
		$cprules .= <<<EOD
338
add 1304 set 1 pass tcp from any to $cpip 8001 in
339
add 1305 set 1 pass tcp from $cpip 8001 to any out
340

    
341
EOD;
342
	}
343
	
344
	$cprules .= <<<EOD
345

    
346
# ... 10000-19899: rules per authenticated client go here...
347

    
348
# redirect non-authenticated clients to captive portal
349
add 19900 set 1 fwd 127.0.0.1,8000 tcp from any to any 80 in
350
# let the responses from the captive portal web server back out
351
add 19901 set 1 pass tcp from any 80 to any out
352
# block everything else
353
add 19902 set 1 deny all from any to any
354

    
355
# ... 20000-29899: layer2 block rules per authenticated client go here...
356

    
357
# pass everything else on layer2
358
add 29900 set 1 pass all from any to any layer2
359

    
360
EOD;
361

    
362
	return $cprules;
363
}
364

    
365
/* remove clients that have been around for longer than the specified amount of time */
366
/* db file structure: timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password */
367
/* (password is in Base64 and only saved when reauthentication is enabled) */
368
function captiveportal_prune_old() {
369
	
370
	global $g, $config;
371
	
372
	/* check for expired entries */
373
	if ($config['captiveportal']['timeout'])
374
		$timeout = $config['captiveportal']['timeout'] * 60;
375
	else
376
		$timeout = 0;
377
		
378
	if ($config['captiveportal']['idletimeout'])
379
		$idletimeout = $config['captiveportal']['idletimeout'] * 60;
380
	else
381
		$idletimeout = 0;
382
	
383
	if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']))
384
		return;
385
	
386
	captiveportal_lock();
387
	
388
	/* read database */
389
	$cpdb = captiveportal_read_db();
390
	
391
	$radiusservers = captiveportal_get_radius_servers();
392
	
393
	for ($i = 0; $i < count($cpdb); $i++) {
394
		
395
		$timedout = false;
396
		
397
		/* hard timeout? */
398
		if ($timeout) {
399
			if ((time() - $cpdb[$i][0]) >= $timeout)
400
				$timedout = true;	
401
		}
402
		
403
		/* if an idle timeout is specified, get last activity timestamp from ipfw */
404
		if (!$timedout && $idletimeout) {
405
			$lastact = captiveportal_get_last_activity($cpdb[$i][1]);
406
			if ($lastact && ((time() - $lastact) >= $idletimeout))
407
				$timedout = true;
408
		}
409
		
410
		if ($timedout) {
411
			captiveportal_disconnect($cpdb[$i], $radiusservers);
412
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
413
			unset($cpdb[$i]);
414
		}
415
		
416
		/* do periodic RADIUS reauthentication? */
417
		if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
418
			($radiusservers !== false)) {
419
		
420
			if (isset($config['captiveportal']['radacct_enable'])) {
421
				if ($config['captiveportal']['reauthenticateacct'] == "stopstart") {
422
					/* stop and restart accounting */
423
					RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
424
										   $cpdb[$i][4], // username
425
										   $cpdb[$i][5], // sessionid
426
										   $cpdb[$i][0], // start time
427
										   $radiusservers[0]['ipaddr'],
428
										   $radiusservers[0]['acctport'],
429
										   $radiusservers[0]['key'],
430
										   $cpdb[$i][2]); //clientip
431
					exec("/sbin/ipfw zero {$cpdb[$i][1]}");
432
					RADIUS_ACCOUNTING_START($cpdb[$i][4],
433
											$cpdb[$i][5],
434
											$radiusservers[0]['ipaddr'],
435
											$radiusservers[0]['acctport'],
436
											$radiusservers[0]['key'],
437
											$cpdb[$i][2]);
438
				} else if ($config['captiveportal']['reauthenticateacct'] == "interimupdate") {
439
					RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
440
										   $cpdb[$i][4], // username
441
										   $cpdb[$i][5], // sessionid
442
										   $cpdb[$i][0], // start time
443
										   $radiusservers[0]['ipaddr'],
444
										   $radiusservers[0]['acctport'],
445
										   $radiusservers[0]['key'],
446
										   $cpdb[$i][2], //clientip
447
										   true);
448
				}
449
			}
450
		
451
			/* check this user against RADIUS again */
452
			$auth_val = RADIUS_AUTHENTICATION($cpdb[$i][4],
453
										  base64_decode($cpdb[$i][6]),
454
							  			  $radiusservers[0]['ipaddr'],
455
							  			  $radiusservers[0]['port'],
456
							  			  $radiusservers[0]['key']);
457
			
458
			if ($auth_val == 3) {
459
				captiveportal_disconnect($cpdb[$i], $radiusservers);
460
				captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT");
461
				unset($cpdb[$i]);
462
			}
463
		}
464
	}
465
	
466
	/* write database */
467
	captiveportal_write_db($cpdb);
468
	
469
	captiveportal_unlock();
470
}
471

    
472
/* remove a single client according to the DB entry */
473
function captiveportal_disconnect($dbent, $radiusservers) {
474
	
475
	global $g, $config;
476
	
477
	/* this client needs to be deleted - remove ipfw rules */
478
	if (isset($config['captiveportal']['radacct_enable']) && isset($radiusservers[0])) {
479
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
480
							   $dbent[4], // username
481
							   $dbent[5], // sessionid
482
							   $dbent[0], // start time
483
							   $radiusservers[0]['ipaddr'],
484
							   $radiusservers[0]['acctport'],
485
							   $radiusservers[0]['key'],
486
							   $dbent[2]); //clientip
487
	}
488
	
489
	mwexec("/sbin/ipfw delete " . $dbent[1] . " " . ($dbent[1]+10000));
490
	
491
	//KEYCOM: we need to delete +40500 and +45500 as well...
492
	//these are the rule numbers we use to control traffic shaping for each logged in user via captive portal
493
	//we only need to remove our rules if peruserbw is turned on.
494
	if (isset($config['captiveportal']['peruserbw'])) {
495
		mwexec("/sbin/ipfw delete " . ($dbent[1]+40500));
496
		mwexec("/sbin/ipfw delete " . ($dbent[1]+45500));
497
	}
498
}
499

    
500
/* remove a single client by ipfw rule number */
501
function captiveportal_disconnect_client($id) {
502
	
503
	global $g, $config;
504
	
505
	captiveportal_lock();
506
	
507
	/* read database */
508
	$cpdb = captiveportal_read_db();
509
	$radiusservers = captiveportal_get_radius_servers();
510
	
511
	/* find entry */	
512
	for ($i = 0; $i < count($cpdb); $i++) {
513
		if ($cpdb[$i][1] == $id) {
514
			captiveportal_disconnect($cpdb[$i], $radiusservers);
515
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "DISCONNECT");
516
			unset($cpdb[$i]);
517
			break;
518
		}
519
	}
520
	
521
	/* write database */
522
	captiveportal_write_db($cpdb);
523
	
524
	captiveportal_unlock();
525
}
526

    
527
/* send RADIUS acct stop for all current clients */
528
function captiveportal_radius_stop_all() {
529
	global $g, $config;
530

    
531
	captiveportal_lock();
532
	$cpdb = captiveportal_read_db();
533
	
534
	$radiusservers = captiveportal_get_radius_servers();
535
	
536
	if (isset($radiusservers[0])) {
537
		for ($i = 0; $i < count($cpdb); $i++) {
538
			RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
539
								   $cpdb[$i][4], // username
540
								   $cpdb[$i][5], // sessionid
541
								   $cpdb[$i][0], // start time
542
								   $radiusservers[0]['ipaddr'],
543
								   $radiusservers[0]['acctport'],
544
								   $radiusservers[0]['key'],
545
								   $cpdb[$i][2]); //clientip
546
		}
547
	}
548
	captiveportal_unlock();
549
}
550

    
551
function captiveportal_passthrumac_configure() {
552
	global $config, $g;
553
	
554
	captiveportal_lock();
555
	
556
	/* clear out passthru macs, if necessary */
557
	unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
558
	
559
	if (is_array($config['captiveportal']['passthrumac'])) {
560
		
561
		$fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db", "w");
562
		if (!$fd) {
563
			printf("Error: cannot open passthru mac DB file in captiveportal_passthrumac_configure().\n");
564
			captiveportal_unlock();
565
			return 1;		
566
		}
567
		
568
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
569
			/* record passthru mac so it can be recognized and let thru */
570
			fwrite($fd, $macent['mac'] . "\n");
571
		}
572
		
573
		fclose($fd); 
574
	}
575
	
576
	/*    pass through mac entries should always exist.  the reason
577
	 *    for this is because we do not have native mac address filtering
578
         *    mechanisms.  this allows us to filter by mac address easily
579
	 *    and get around this limitation.   I consider this a bug in
580
         *    m0n0wall and pfSense as m0n0wall does not have native mac 
581
         *    filtering mechanisms as well. -Scott Ullrich
582
         */
583
	if (is_array($config['captiveportal']['passthrumac'])) {
584
		mwexec("/sbin/ipfw delete 50");
585
		foreach($config['captiveportal']['passthrumac'] as $ptm) {
586
			/* create the pass through mac entry */
587
			//system("echo /sbin/ipfw add 50 skipto 65535 ip from any to any MAC {$ptm['mac']} any > /tmp/cp");
588
			mwexec("/sbin/ipfw add 50 skipto 29900 ip from any to any MAC {$ptm['mac']} any keep-state");
589
			mwexec("/sbin/ipfw add 50 skipto 29900 ip from any to any MAC any {$ptm['mac']} keep-state");
590
		}
591
	}
592
	
593
	captiveportal_unlock();
594
	
595
	return 0;
596
}
597

    
598
function captiveportal_allowedip_configure() {
599
	global $config, $g;
600
	
601
	captiveportal_lock();
602

    
603
	/* clear out existing allowed ips, if necessary */
604
	if (file_exists("{$g['vardb_path']}/captiveportal_ip.db")) {
605
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "r");
606
		if ($fd) {
607
			while (!feof($fd)) {
608
				$line = trim(fgets($fd));
609
				if ($line) {
610
					list($ip,$rule) = explode(",",$line);
611
					mwexec("/sbin/ipfw delete $rule");
612
				}	
613
			}
614
		}
615
		fclose($fd);
616
		unlink("{$g['vardb_path']}/captiveportal_ip.db");
617
	}
618

    
619
	/* get next ipfw rule number */
620
	if (file_exists("{$g['vardb_path']}/captiveportal.nextrule"))
621
		$ruleno = trim(file_get_contents("{$g['vardb_path']}/captiveportal.nextrule"));
622
	if (!$ruleno)
623
		$ruleno = 10000;	/* first rule number */
624
	
625
	if (is_array($config['captiveportal']['allowedip'])) {
626
		
627
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "w");
628
		if (!$fd) {
629
			printf("Error: cannot open allowed ip DB file in captiveportal_allowedip_configure().\n");
630
			captiveportal_unlock();
631
			return 1;		
632
		}
633
		
634
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
635
		
636
			/* record allowed ip so it can be recognized and removed later */
637
			fwrite($fd, $ipent['ip'] . "," . $ruleno ."\n");
638
			
639
			/* insert ipfw rule to allow ip thru */
640
			if ($ipent['dir'] == "from") {
641
				mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any in");
642
				mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " out");
643
			} else {
644
				mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " in");
645
				mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any out");
646
			}
647
			
648
			$ruleno++;
649
			if ($ruleno > 19899)
650
				$ruleno = 10000;
651
		}
652
		
653
		fclose($fd); 
654

    
655
		/* write next rule number */
656
		$fd = @fopen("{$g['vardb_path']}/captiveportal.nextrule", "w");
657
		if ($fd) {
658
			fwrite($fd, $ruleno);
659
			fclose($fd);
660
		}
661
	}
662
	
663
	captiveportal_unlock();
664
	return 0;
665
}
666

    
667
/* get last activity timestamp given ipfw rule number */
668
function captiveportal_get_last_activity($ruleno) {
669
	
670
	exec("/sbin/ipfw -T list {$ruleno} 2>/dev/null", $ipfwoutput);
671
	
672
	/* in */
673
	if ($ipfwoutput[0]) {
674
		$ri = explode(" ", $ipfwoutput[0]);
675
		if ($ri[1])
676
			return $ri[1];
677
	}
678
	
679
	return 0;
680
}
681

    
682
/* read captive portal DB into array */
683
function captiveportal_read_db() {
684
	
685
	global $g;
686
	
687
	$cpdb = array();
688
	$fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
689
	if ($fd) {
690
		while (!feof($fd)) {
691
			$line = trim(fgets($fd));
692
			if ($line) {
693
				$cpdb[] = explode(",", $line);
694
			}	
695
		}
696
		fclose($fd);
697
	}
698
	return $cpdb;
699
}
700

    
701
/* write captive portal DB */
702
function captiveportal_write_db($cpdb) {
703
	
704
	global $g;
705
	
706
	$fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
707
	if ($fd) {
708
		foreach ($cpdb as $cpent) {
709
			fwrite($fd, join(",", $cpent) . "\n");
710
		}
711
		fclose($fd);
712
	}
713
}
714

    
715
/* read RADIUS servers into array */
716
function captiveportal_get_radius_servers() {
717
	
718
	global $g;
719
	
720
	if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
721
	   	$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db","r");
722
		if ($fd) {
723
			$radiusservers = array();
724
			while (!feof($fd)) {
725
				$line = trim(fgets($fd));
726
				if ($line) {
727
					$radsrv = array();
728
					list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
729
					$radiusservers[] = $radsrv;
730
				}
731
			}
732
			fclose($fd);
733
			
734
			return $radiusservers;
735
		}
736
	}
737
	
738
	return false;
739
}
740

    
741
/* lock captive portal information, decide that the lock file is stale after
742
   10 seconds */
743
function captiveportal_lock() {
744
	
745
	global $g;
746
	
747
	$lockfile = "{$g['varrun_path']}/captiveportal.lock";
748
	
749
	$n = 0;
750
	while ($n < 10) {
751
		/* open the lock file in append mode to avoid race condition */
752
		if ($fd = @fopen($lockfile, "x")) {
753
			/* succeeded */
754
			fclose($fd);
755
			return;
756
		} else {
757
			/* file locked, wait and try again */
758
			sleep(1);
759
			$n++;
760
		}
761
	}
762
}
763

    
764
/* unlock configuration file */
765
function captiveportal_unlock() {
766
	
767
	global $g;
768
	
769
	$lockfile = "{$g['varrun_path']}/captiveportal.lock";
770
	
771
	if (file_exists($lockfile))
772
		unlink($lockfile);
773
}
774

    
775
/* log successful captive portal authentication to syslog */
776
/* part of this code from php.net */
777
function captiveportal_logportalauth($user,$mac,$ip,$status) {
778
	define_syslog_variables();
779
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
780
	// Log it
781
	syslog(LOG_INFO, "$status: $user, $mac, $ip");
782
	closelog();
783
}
784

    
785
?>
(4-4/26)