Project

General

Profile

Download (23.1 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
	<form method="post" action="\$PORTAL_ACTION\$">
94
	<input name="accept" type="submit" value="Continue">
95
	<input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$">
96
   </td></tr>
97
</table>
98
</form>
99
</body>
100
</html>
101

    
102
EOD;
103
		}
104

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

    
131
EOD;
132
		}
133

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

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

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

    
221
		/* generate radius server database */
222
		if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
223
				($config['captiveportal']['auth_method'] == "radius"))) {
224
			$radiusip = $config['captiveportal']['radiusip'];
225

    
226
			if ($config['captiveportal']['radiusport'])
227
				$radiusport = $config['captiveportal']['radiusport'];
228
			else
229
				$radiusport = 1812;
230

    
231
			if ($config['captiveportal']['radiusacctport'])
232
				$radiusacctport = $config['captiveportal']['radiusacctport'];
233
			else
234
				$radiusacctport = 1813;
235

    
236
			$radiuskey = $config['captiveportal']['radiuskey'];
237

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

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

    
256
		captiveportal_radius_stop_all();
257

    
258
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
259

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

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

    
281
	/* note: the captive portal daemon inserts all pass rules for authenticated
282
	   clients as skipto 50000 rules to make traffic shaping work */
283

    
284
	$cprules = "";
285

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

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

    
308
EOD;
309
	}
310

    
311
	$cprules .= <<<EOD
312
# skip to traffic shaper if not on captive portal interface
313
add 1000 set 1 skipto 50000 all from any to any not layer2 not via $cpif
314
# pass all layer2 traffic on other interfaces
315
add 1001 set 1 pass layer2 not via $cpif
316

    
317
# layer 2: pass ARP
318
add 1100 set 1 pass layer2 mac-type arp
319
# layer 2: block anything else non-IP
320
add 1101 set 1 deny layer2 not mac-type ip
321
# layer 2: check if MAC addresses of authenticated clients are correct
322
add 1102 set 1 skipto 20000 layer2
323

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

    
331
# allow access to our DNS forwarder
332
add 1300 set 1 pass udp from any to $cpip 53 in
333
add 1301 set 1 pass udp from $cpip 53 to any out
334

    
335
# allow access to our web server
336
add 1302 set 1 pass tcp from any to $cpip 8000 in
337
add 1303 set 1 pass tcp from $cpip 8000 to any out
338

    
339
EOD;
340

    
341
	if (isset($config['captiveportal']['httpslogin'])) {
342
		$cprules .= <<<EOD
343
add 1304 set 1 pass tcp from any to $cpip 8001 in
344
add 1305 set 1 pass tcp from $cpip 8001 to any out
345

    
346
EOD;
347
	}
348
	
349
	$cprules .= <<<EOD
350

    
351
# ... 10000-19899: rules per authenticated client go here...
352

    
353
# redirect non-authenticated clients to captive portal
354
add 19900 set 1 fwd 127.0.0.1,8000 tcp from any to any 80 in
355
# let the responses from the captive portal web server back out
356
add 19901 set 1 pass tcp from any 80 to any out
357
# block everything else
358
add 19902 set 1 deny all from any to any
359

    
360
# ... 20000-29899: layer2 block rules per authenticated client go here...
361

    
362
# pass everything else on layer2
363
add 29900 set 1 pass all from any to any layer2
364

    
365
EOD;
366

    
367
	return $cprules;
368
}
369

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

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

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

    
532
/* send RADIUS acct stop for all current clients */
533
function captiveportal_radius_stop_all() {
534
	global $g, $config;
535

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

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

    
603
function captiveportal_allowedip_configure() {
604
	global $config, $g;
605
	
606
	captiveportal_lock();
607

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

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

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

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

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

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

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

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

    
769
/* unlock configuration file */
770
function captiveportal_unlock() {
771
	
772
	global $g;
773
	
774
	$lockfile = "{$g['varrun_path']}/captiveportal.lock";
775
	
776
	if (file_exists($lockfile))
777
		unlink($lockfile);
778
}
779

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

    
790
?>
(3-3/25)