Project

General

Profile

Download (22.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-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
<h2>pfSense captive portal</h2>
87
Welcome to the pfSense Captive Portal!  This is the default page since a custom page has not been defined.
88
<form method="post" action="$PORTAL_ACTION$">
89
<table>
90
   <tr><td>Username:</td><td><input name="auth_user" type="text"></td></tr>
91
   <tr><td>Password:</td><td><input name="auth_pass" type="password"></td></tr>
92
   <tr><td colspan="2">
93
	<input name="redirurl" type="hidden" value="$PORTAL_REDIRURL$">
94
	<input name="accept" type="submit" value="Continue">
95
   </td></tr>
96
</table>
97
</form>
98
<form method="post" action="\$PORTAL_ACTION\$">
99
  <input name="accept" type="submit" value="Continue">
100
  <input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$">
101
</form>
102
</body>
103
</html>
104

    
105
EOD;
106
		}
107

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

    
134
EOD;
135
		}
136

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

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

    
168
		$memory = get_memory();
169
		$avail = $memory[0];
170
		if($avail > 0 and $avail < 60) {
171
			$procs = 16;
172
		} else if($avail > 60 and $avail < 120) {
173
			$procs = 24;
174
		} else if($avail > 120 and $avail < 160) {
175
			$procs = 32;
176
		} else if($avail > 160 and $avail < 250) {
177
			$procs = 48;
178
		} else if($avail > 250 and $avail < 380) {
179
			$procs = 56;
180
		} else if($avail > 380 and $avail < 500) {
181
			$procs = 72;
182
		} else if($avail > 500 and $avail < 680) {
183
			$procs = 80;
184
		} else {
185
			$procs = 16;
186
		}	
187
	
188
		/* start web server */
189
		mwexec("/usr/local/sbin/mini_httpd -a -M 0 -u root -maxproc {$procs}" .
190
			" -p 8000 -i {$g['varrun_path']}/mini_httpd.cp.pid");
191
		
192
		/* fire up another one for HTTPS if requested */
193
		if (isset($config['captiveportal']['httpslogin']) &&
194
			$config['captiveportal']['certificate'] && $config['captiveportal']['private-key']) {
195
			
196
			$cert = base64_decode($config['captiveportal']['certificate']);
197
			$key = base64_decode($config['captiveportal']['private-key']);
198
			
199
			$fd = fopen("{$g['varetc_path']}/cert-portal.pem", "w");
200
			if (!$fd) {
201
				printf("Error: cannot open cert-portal.pem in system_webgui_start().\n");
202
				return 1;
203
			}
204
			chmod("{$g['varetc_path']}/cert-portal.pem", 0600);
205
			fwrite($fd, $cert);
206
			fwrite($fd, "\n");
207
			fwrite($fd, $key);
208
			fclose($fd);
209
			
210
			mwexec("/usr/local/sbin/mini_httpd -S -a -M 0 -E {$g['varetc_path']}/cert-portal.pem" .
211
				" -u root -maxproc 16 -p 8001" .
212
				" -i {$g['varrun_path']}/mini_httpd.cps.pid");
213
		}
214
			
215
		/* start pruning process (interval = 60 seconds) */
216
		mwexec("/usr/local/bin/minicron 60 {$g['varrun_path']}/minicron.pid " .
217
			"/etc/rc.prunecaptiveportal");
218
		
219
		/* generate passthru mac database */
220
		captiveportal_passthrumac_configure();
221
		/* create allowed ip database and insert ipfw rules to make it so */
222
		captiveportal_allowedip_configure();
223

    
224
		/* generate radius server database */
225
		if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
226
				($config['captiveportal']['auth_method'] == "radius"))) {
227
			$radiusip = $config['captiveportal']['radiusip'];
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
			$radiuskey = $config['captiveportal']['radiuskey'];
240

    
241
			$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db", "w");
242
			if (!$fd) {
243
				printf("Error: cannot open radius DB file in captiveportal_configure().\n");
244
				return 1;
245
			} else {
246
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey);
247
			}
248
			fclose($fd);
249
		}
250

    
251
		if ($g['booting'])
252
			echo "done\n";
253
		
254
	} else {
255
		killbypid("{$g['varrun_path']}/mini_httpd.cp.pid");
256
		killbypid("{$g['varrun_path']}/mini_httpd.cps.pid");
257
		killbypid("{$g['varrun_path']}/minicron.pid");
258

    
259
		captiveportal_radius_stop_all();
260

    
261
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
262

    
263
		if (!isset($config['shaper']['enable'])) {
264
			/* unload ipfw */
265
			mwexec("/sbin/kldunload ipfw");
266
		} else {
267
			/* shaper is on - just remove our rules */
268
			mwexec("/sbin/ipfw -f delete set 1");
269
			mwexec("/sbin/ipfw -f delete set 2");
270
			mwexec("/sbin/ipfw -f delete set 3");
271
		}
272
	}
273
	
274
	return 0;
275
}
276

    
277
function captiveportal_rules_generate() {
278
	global $config, $g;
279
	
280
	$cpifn = $config['captiveportal']['interface'];
281
	$cpif = $config['interfaces'][$cpifn]['if'];
282
	$cpip = $config['interfaces'][$cpifn]['ipaddr'];
283

    
284
	/* note: the captive portal daemon inserts all pass rules for authenticated
285
	   clients as skipto 50000 rules to make traffic shaping work */
286

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

    
296
EOD;
297
	}
298

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

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

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

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

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

    
327
EOD;
328

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

    
334
EOD;
335
	}
336
	
337
	$cprules .= <<<EOD
338

    
339
# ... 10000-19899: rules per authenticated client go here...
340

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

    
348
# ... 20000-29899: layer2 block rules per authenticated client go here...
349

    
350
# pass everything else on layer2
351
add 29900 set 1 pass all from any to any layer2
352

    
353
EOD;
354

    
355
	return $cprules;
356
}
357

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

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

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

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

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

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

    
591
function captiveportal_allowedip_configure() {
592
	global $config, $g;
593
	
594
	captiveportal_lock();
595

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

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

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

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

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

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

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

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

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

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

    
778
?>
(3-3/25)