Project

General

Profile

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

    
212
		/* generate radius server database */
213
		if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
214
				($config['captiveportal']['auth_method'] == "radius"))) {
215
			$radiusip = $config['captiveportal']['radiusip'];
216

    
217
			if ($config['captiveportal']['radiusport'])
218
				$radiusport = $config['captiveportal']['radiusport'];
219
			else
220
				$radiusport = 1812;
221

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

    
227
			$radiuskey = $config['captiveportal']['radiuskey'];
228

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

    
239
		if ($g['booting'])
240
			echo "done\n";
241
		
242
	} else {
243
		killbypid("{$g['varrun_path']}/mini_httpd.cp.pid");
244
		killbypid("{$g['varrun_path']}/mini_httpd.cps.pid");
245
		killbypid("{$g['varrun_path']}/minicron.pid");
246

    
247
		captiveportal_radius_stop_all();
248

    
249
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
250

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

    
265
function captiveportal_rules_generate() {
266
	global $config, $g;
267
	
268
	$cpifn = $config['captiveportal']['interface'];
269
	$cpif = $config['interfaces'][$cpifn]['if'];
270
	$cpip = $config['interfaces'][$cpifn]['ipaddr'];
271

    
272
	/* note: the captive portal daemon inserts all pass rules for authenticated
273
	   clients as skipto 50000 rules to make traffic shaping work */
274

    
275
	$cprules = "";
276

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

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

    
298
EOD;
299
	}
300

    
301
	$cprules .= <<<EOD
302
# skip to traffic shaper if not on captive portal interface
303
add 1000 set 1 skipto 50000 all from any to any not layer2 not via $cpif
304
# pass all layer2 traffic on other interfaces
305
add 1001 set 1 pass layer2 not via $cpif
306

    
307
# layer 2: pass ARP
308
add 1100 set 1 pass layer2 mac-type arp
309
# layer 2: block anything else non-IP
310
add 1101 set 1 deny layer2 not mac-type ip
311
# layer 2: check if MAC addresses of authenticated clients are correct
312
add 1102 set 1 skipto 20000 layer2
313

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

    
321
# allow access to our DNS forwarder
322
add 1300 set 1 pass udp from any to $cpip 53 in
323
add 1301 set 1 pass udp from $cpip 53 to any out
324

    
325
# allow access to our web server
326
add 1302 set 1 pass tcp from any to $cpip 8000 in
327
add 1303 set 1 pass tcp from $cpip 8000 to any out
328

    
329
EOD;
330

    
331
	if (isset($config['captiveportal']['httpslogin'])) {
332
		$cprules .= <<<EOD
333
add 1304 set 1 pass tcp from any to $cpip 8001 in
334
add 1305 set 1 pass tcp from $cpip 8001 to any out
335

    
336
EOD;
337
	}
338
	
339
	$cprules .= <<<EOD
340

    
341
# ... 10000-19899: rules per authenticated client go here...
342

    
343
# redirect non-authenticated clients to captive portal
344
add 19900 set 1 fwd 127.0.0.1,8000 tcp from any to any 80 in
345
# let the responses from the captive portal web server back out
346
add 19901 set 1 pass tcp from any 80 to any out
347
# block everything else
348
add 19902 set 1 deny all from any to any
349

    
350
# ... 20000-29899: layer2 block rules per authenticated client go here...
351

    
352
# pass everything else on layer2
353
add 29900 set 1 pass all from any to any layer2
354

    
355
EOD;
356

    
357
	return $cprules;
358
}
359

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

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

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

    
522
/* send RADIUS acct stop for all current clients */
523
function captiveportal_radius_stop_all() {
524
	global $g, $config;
525

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

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

    
593
function captiveportal_allowedip_configure() {
594
	global $config, $g;
595
	
596
	captiveportal_lock();
597

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

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

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

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

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

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

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

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

    
759
/* unlock configuration file */
760
function captiveportal_unlock() {
761
	
762
	global $g;
763
	
764
	$lockfile = "{$g['varrun_path']}/captiveportal.lock";
765
	
766
	if (file_exists($lockfile))
767
		unlink($lockfile);
768
}
769

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

    
780
?>
(4-4/26)