Project

General

Profile

Download (27.9 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	captiveportal.inc
4
	part of m0n0wall (http://m0n0.ch/wall)
5
	
6
	Copyright (C) 2003-2006 Manuel Kasper <mk@neon1.net>.
7
	All rights reserved.
8
	
9
	Redistribution and use in source and binary forms, with or without
10
	modification, are permitted provided that the following conditions are met:
11
	
12
	1. Redistributions of source code must retain the above copyright notice,
13
	   this list of conditions and the following disclaimer.
14
	
15
	2. Redistributions in binary form must reproduce the above copyright
16
	   notice, this list of conditions and the following disclaimer in the
17
	   documentation and/or other materials provided with the distribution.
18
	
19
	THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
20
	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
21
	AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22
	AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
23
	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
	POSSIBILITY OF SUCH DAMAGE.
29

    
30
	This version of captiveportal.inc has been modified by Rob Parker
31
	<rob.parker@keycom.co.uk> to include changes for per-user bandwidth management
32
	via returned RADIUS attributes. This page has been modified to delete any
33
	added rules which may have been created by other per-user code (index.php, etc).
34
	These changes are (c) 2004 Keycom PLC.
35
*/
36
	
37
/* include all configuration functions */
38
require_once("functions.inc");
39
require_once("radius_authentication.inc");
40
require_once("radius_accounting.inc");
41

    
42
$lockfile = "{$g['varrun_path']}/captiveportal.lock";
43

    
44
function captiveportal_configure() {
45
	global $config, $g;
46
	
47
	if (isset($config['captiveportal']['enable']) &&
48
		(($config['captiveportal']['interface'] == "lan") ||
49
			isset($config['interfaces'][$config['captiveportal']['interface']]['enable']))) {
50
	
51
		if ($g['booting'])
52
			echo "Starting captive portal... ";
53
		
54
		/* kill any running mini_httpd */
55
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
56
		
57
		/* kill any running minicron */
58
		killbypid("{$g['varrun_path']}/minicron.pid");
59
		
60
		/* generate ipfw rules */
61
		$cprules = captiveportal_rules_generate();
62
		
63
		/* make sure ipfw is loaded */
64
		mwexec("/sbin/kldload ipfw");
65
		
66
		/* stop accounting on all clients */
67
		captiveportal_radius_stop_all();
68

    
69
		/* initialize minicron interval value */
70
		$croninterval = $config['captiveportal']['croninterval'] ? $config['captiveportal']['croninterval'] : 60;
71

    
72
		/* double check if the $croninterval is numeric and at least 10 seconds. If not we set it to 60 to avoid problems */
73
		if ((!is_numeric($croninterval)) || ($croninterval < 10)) { $croninterval = 60; }
74

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

    
114

    
115

    
116
EOD;
117
		}
118

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

    
145
EOD;
146
		}
147

    
148
		$fd = @fopen("{$g['varetc_path']}/captiveportal-error.html", "w");
149
		if ($fd) {
150
			fwrite($fd, $errtext);
151
			fclose($fd);	
152
		}
153
		
154
		/* write elements */
155
		captiveportal_write_elements();
156

    
157
		/* load rules */
158
		mwexec("/sbin/ipfw -f delete set 1");
159
		mwexec("/sbin/ipfw -f delete set 2");
160
		mwexec("/sbin/ipfw -f delete set 3");
161
		
162
		/* XXX - seems like ipfw cannot accept rules directly on stdin,
163
		   so we have to write them to a temporary file first */
164
		$fd = @fopen("{$g['tmp_path']}/ipfw.cp.rules", "w");
165
		if (!$fd) {
166
			printf("Cannot open ipfw.cp.rules in captiveportal_configure()\n");
167
			return 1;
168
		}
169
			
170
		fwrite($fd, $cprules);
171
		fclose($fd);
172
		
173
		mwexec("/sbin/ipfw {$g['tmp_path']}/ipfw.cp.rules");
174
		
175
		unlink("{$g['tmp_path']}/ipfw.cp.rules");
176
		
177
		/* filter on layer2 as well so we can check MAC addresses */
178
		mwexec("/sbin/sysctl net.link.ether.ipfw=1");
179
		
180
		chdir($g['captiveportal_path']);
181
	
182
		/* TEMPORARY!  FAST_CGI reports _FALSE_ client ip
183
		 * 	       addresses.
184
		 */
185
		$use_fastcgi = false;
186

    
187
		if(isset($config['captiveportal']['httpslogin'])) {
188
			$cert = base64_decode($config['captiveportal']['certificate']);
189
			$key = base64_decode($config['captiveportal']['private-key']);
190
		}
191
		
192
		if ($config['captiveportal']['maxproc'])
193
			$maxproc = $config['captiveportal']['maxproc'];
194
		else
195
			$maxproc = 16;
196

    
197
		/* generate lighttpd configuration */
198
		system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal.conf",
199
			$cert, $key, "lighty-CaptivePortal.pid", "8000", "/usr/local/captiveportal/",
200
				"cert-portal.pem", "1", $maxproc, $use_fastcgi, true);
201
					
202
		/* attempt to start lighttpd */
203
		$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-CaptivePortal.conf");
204
		
205
		/* start pruning process (interval defaults to 60 seconds) */
206
		mwexec("/usr/local/bin/minicron $croninterval {$g['varrun_path']}/minicron.pid " .
207
			"/etc/rc.prunecaptiveportal");
208
		
209
		/* generate passthru mac database */
210
		captiveportal_passthrumac_configure();
211
		/* create allowed ip database and insert ipfw rules to make it so */
212
		captiveportal_allowedip_configure();
213
		
214
		/* generate radius server database */
215
		if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
216
				($config['captiveportal']['auth_method'] == "radius"))) {
217
			$radiusip = $config['captiveportal']['radiusip'];
218
			$radiusip2 = ($config['captiveportal']['radiusip2']) ? $config['captiveportal']['radiusip2'] : null;
219

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

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

    
230
			if ($config['captiveportal']['radiusport2'])
231
				$radiusport2 = $config['captiveportal']['radiusport2'];
232
			else
233
				$radiusport2 = 1812;
234

    
235
			$radiuskey = $config['captiveportal']['radiuskey'];
236
			$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
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 if (isset($radiusip2, $radiuskey2)) {
243
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . "\n"
244
					 . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2);
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']}/lighty-CaptivePortal.pid");
256
		killbypid("{$g['varrun_path']}/minicron.pid");
257

    
258
		captiveportal_radius_stop_all();
259

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

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

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

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

    
286
	$cprules = "";
287

    
288
	/*   allow nat redirects to work  see
289
	     http://cvstrac.pfsense.com/tktview?tn=651
290
	 */
291
	$iflist = array("lan" => "LAN", "wan" => "WAN");
292
	$captive_portal_interface = strtoupper($cpifn);
293
	for ($i = 1; isset($config['interfaces']['opt' . $i]); $i++) 
294
		$iflist['opt' . $i] = "OPT{$i}";	
295
	foreach ($iflist as $ifent => $ifname) {
296
		if($captive_portal_interface == strtoupper($ifname))
297
			continue;
298
		$int = convert_friendly_interface_to_real_interface_name($ifname);
299
		$cprules .= "add 30 set 1 skipto 50000 all from any to any in via {$int} keep-state\n";
300
	}
301
	
302
	/* captive portal on LAN interface? */
303
	if ($cpifn == "lan") {
304
		/* add anti-lockout rules */
305
		$cprules .= <<<EOD
306
add 500 set 1 pass all from $cpip to any out via $cpif
307
add 501 set 1 pass all from any to $cpip in via $cpif
308

    
309
EOD;
310
	}
311

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

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

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

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

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

    
340
EOD;
341

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

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

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

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

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

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

    
366
EOD;
367

    
368
	return $cprules;
369
}
370

    
371
/* remove clients that have been around for longer than the specified amount of time */
372
/* db file structure: 
373
timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time */
374

    
375
/* (password is in Base64 and only saved when reauthentication is enabled) */
376
function captiveportal_prune_old() {
377
	
378
	global $g, $config;
379

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

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

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

    
511
/* remove a single client according to the DB entry */
512
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
513
	
514
	global $g, $config;
515

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

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

    
572
/* send RADIUS acct stop for all current clients */
573
function captiveportal_radius_stop_all() {
574
	global $g, $config;
575
	
576
	if (!isset($config['captiveportal']['radacct_enable']))
577
		return;
578

    
579
	captiveportal_lock();
580
	$cpdb = captiveportal_read_db();
581
	
582
	$radiusservers = captiveportal_get_radius_servers();
583
	
584
	if (isset($radiusservers[0])) {
585
		for ($i = 0; $i < count($cpdb); $i++) {
586
			RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
587
								   $cpdb[$i][4], // username
588
								   $cpdb[$i][5], // sessionid
589
								   $cpdb[$i][0], // start time
590
								   $radiusservers[0]['ipaddr'],
591
								   $radiusservers[0]['acctport'],
592
								   $radiusservers[0]['key'],
593
								   $cpdb[$i][2], // clientip
594
								   $cpdb[$i][3], // clientmac
595
								   7); // Admin Reboot
596
		}
597
	}
598
	captiveportal_unlock();
599
}
600

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

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

    
643
	captiveportal_unlock();
644
	
645
	return 0;
646
}
647

    
648
function captiveportal_allowedip_configure() {
649
	global $config, $g;
650
	
651
	captiveportal_lock();
652

    
653
	/* clear out existing allowed ips, if necessary */
654
	if (file_exists("{$g['vardb_path']}/captiveportal_ip.db")) {
655
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "r");
656
		if ($fd) {
657
			while (!feof($fd)) {
658
				$line = trim(fgets($fd));
659
				if ($line) {
660
					list($ip,$rule) = explode(",",$line);
661
					mwexec("/sbin/ipfw delete $rule");
662
				}	
663
			}
664
		}
665
		fclose($fd);
666
		unlink("{$g['vardb_path']}/captiveportal_ip.db");
667
	}
668

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

    
705
		/* write next rule number */
706
		$fd = @fopen("{$g['vardb_path']}/captiveportal.nextrule", "w");
707
		if ($fd) {
708
			fwrite($fd, $ruleno);
709
			fclose($fd);
710
		}
711
	}
712
	
713
	captiveportal_unlock();
714
	return 0;
715
}
716

    
717
/* get last activity timestamp given ipfw rule number */
718
function captiveportal_get_last_activity($ruleno) {
719
	
720
	$ipfwoutput = "";
721
	exec("/sbin/ipfw -T list {$ruleno} 2>/dev/null", $ipfwoutput);
722
	
723
	/* in */
724
	if ($ipfwoutput[0]) {
725
		$ri = explode(" ", $ipfwoutput[0]);
726
		if ($ri[1])
727
			return $ri[1];
728
	}
729
	
730
	return 0;
731
}
732

    
733
/* read RADIUS servers into array */
734
function captiveportal_get_radius_servers() {
735

    
736
        global $g;
737

    
738
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
739
                $fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db","r");
740
                if ($fd) {
741
                        $radiusservers = array();
742
                        while (!feof($fd)) {
743
                                $line = trim(fgets($fd));
744
                                if ($line) {
745
                                        $radsrv = array();
746
                                        list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
747
                                        $radiusservers[] = $radsrv;
748
                                }
749
                        }
750
                        fclose($fd);
751

    
752
                        return $radiusservers;
753
                }
754
        }
755

    
756
        return false;
757
}
758

    
759
/* lock captive portal information, decide that the lock file is stale after
760
   10 seconds */
761
function captiveportal_lock() {
762

    
763
        global $lockfile;
764

    
765
        $n = 0;
766
        while ($n < 10) {
767
                /* open the lock file in append mode to avoid race condition */
768
                if ($fd = @fopen($lockfile, "x")) {
769
                        /* succeeded */
770
                        fclose($fd);
771
                        return;
772
                } else {
773
                        /* file locked, wait and try again */
774
                        sleep(1);
775
                        $n++;
776
                }
777
        }
778
}
779

    
780
/* unlock captive portal information file */
781
function captiveportal_unlock() {
782

    
783
        global $lockfile;
784

    
785
        if (file_exists($lockfile))
786
                unlink($lockfile);
787
}
788

    
789
/* log successful captive portal authentication to syslog */
790
/* part of this code from php.net */
791
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
792
	define_syslog_variables();
793
	$message = trim($message);
794
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
795
	// Log it
796
	if (!$message)
797
	syslog(LOG_INFO, "$status: $user, $mac, $ip");
798
	else
799
	syslog(LOG_INFO, "$status: $user, $mac, $ip, $message");
800
	closelog();
801
}
802

    
803
function radius($username,$password,$clientip,$clientmac,$type) {
804
	global $g, $config;
805

    
806
	$next_ruleno = get_next_ipfw_ruleno();
807
	$radiusservers = captiveportal_get_radius_servers();
808
	$radacct_enable = isset($config['captiveportal']['radacct_enable']);
809

    
810
	$auth_list = RADIUS_AUTHENTICATION($username,
811
					$password,
812
					$radiusservers,
813
					$clientip,
814
					$clientmac,
815
					$next_ruleno);
816

    
817
	if ($auth_list['auth_val'] == 2) {
818
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
819
		$sessionid = portal_allow($clientip,
820
					$clientmac,
821
					$username,
822
					$password,
823
					$auth_list['session_timeout'],
824
					$auth_list['idle_timeout'],
825
					$auth_list['url_redirection'],
826
					$auth_list['session_terminate_time']);
827

    
828
		if ($radacct_enable) {
829
			$auth_list['acct_val'] = RADIUS_ACCOUNTING_START($next_ruleno,
830
									$username,
831
									$sessionid,
832
									$radiusservers[0]['ipaddr'],
833
									$radiusservers[0]['acctport'],
834
									$radiusservers[0]['key'],
835
									$clientip,
836
									$clientmac);
837
			if ($auth_list['acct_val'] == 1) 
838
				captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
839
		}
840
	}
841

    
842
	return $auth_list;
843

    
844
}
845

    
846
/* read captive portal DB into array */
847
function captiveportal_read_db() {
848

    
849
        global $g;
850

    
851
        $cpdb = array();
852
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
853
        if ($fd) {
854
                while (!feof($fd)) {
855
                        $line = trim(fgets($fd));
856
                        if ($line) {
857
                                $cpdb[] = explode(",", $line);
858
                        }
859
                }
860
                fclose($fd);
861
        }
862
        return $cpdb;
863
}
864

    
865
/* write captive portal DB */
866
function captiveportal_write_db($cpdb) {
867
                 
868
        global $g;
869
                
870
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
871
        if ($fd) { 
872
                foreach ($cpdb as $cpent) {
873
                        fwrite($fd, join(",", $cpent) . "\n");
874
                }       
875
                fclose($fd);
876
        }       
877
}
878

    
879
function captiveportal_write_elements() {
880
	global $g, $config;
881
	
882
	/* delete any existing elements */
883
	if (is_dir($g['captiveportal_element_path'])) {
884
		$dh = opendir($g['captiveportal_element_path']);
885
		while (($file = readdir($dh)) !== false) {
886
			if ($file != "." && $file != "..")
887
				unlink($g['captiveportal_element_path'] . "/" . $file);
888
		}
889
		closedir($dh);
890
	} else {
891
		mkdir($g['captiveportal_element_path']);
892
	}
893
	
894
	if (is_array($config['captiveportal']['element'])) {
895
	
896
		foreach ($config['captiveportal']['element'] as $data) {
897
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
898
			if (!$fd) {
899
				printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
900
				return 1;
901
			}
902
			$decoded = base64_decode($data['content']);
903
			fwrite($fd,$decoded);
904
			fclose($fd);
905
		}
906
	}
907
	
908
	return 0;
909
}
910

    
911
?>
(4-4/27)