Project

General

Profile

Download (32 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
require_once("radius.inc");
42

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

    
45
function captiveportal_configure() {
46
	global $config, $g;
47

    
48
	if (isset($config['captiveportal']['enable']) &&
49
		(($config['captiveportal']['interface'] == "lan") ||
50
			isset($config['interfaces'][$config['captiveportal']['interface']]['enable']))) {
51

    
52
		if ($g['booting'])
53
			echo "Starting captive portal... ";
54

    
55
		/* kill any running mini_httpd */
56
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
57
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal-SSL.pid");
58

    
59
		/* kill any running minicron */
60
		killbypid("{$g['varrun_path']}/minicron.pid");
61

    
62
		/* generate ipfw rules */
63
		$cprules = captiveportal_rules_generate();
64

    
65
		/* make sure ipfw is loaded */
66
		mwexec("/sbin/kldload ipfw");
67

    
68
		/* stop accounting on all clients */
69
		captiveportal_radius_stop_all();
70

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

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

    
77
		/* remove old information */
78
		unlink_if_exists("{$g['vardb_path']}/captiveportal.nextrule");
79
		unlink_if_exists("{$g['vardb_path']}/captiveportal.db");
80
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
81
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip.db");
82
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius.db");
83

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

    
116

    
117

    
118
EOD;
119
		}
120

    
121
		$fd = @fopen("{$g['varetc_path']}/captiveportal.html", "w");
122
		if ($fd) {
123
			fwrite($fd, $htmltext);
124
			fclose($fd);
125
		}
126

    
127
		/* write error page */
128
		if ($config['captiveportal']['page']['errtext'])
129
			$errtext = base64_decode($config['captiveportal']['page']['errtext']);
130
		else {
131
			/* example page */
132
			$errtext = <<<EOD
133
<html>
134
<head>
135
<title>Authentication error</title>
136
</head>
137
<body>
138
<font color="#cc0000"><h2>Authentication error</h2></font>
139
<b>
140
Username and/or password invalid.
141
<br><br>
142
<a href="javascript:history.back()">Go back</a>
143
</b>
144
</body>
145
</html>
146

    
147
EOD;
148
		}
149

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

    
156
		/* write elements */
157
		captiveportal_write_elements();
158

    
159
		/* load rules */
160
		mwexec("/sbin/ipfw -f delete set 1");
161
		mwexec("/sbin/ipfw -f delete set 2");
162
		mwexec("/sbin/ipfw -f delete set 3");
163

    
164
		/* ipfw cannot accept rules directly on stdin,
165
		   so we have to write them to a temporary file first */
166
		$fd = @fopen("{$g['tmp_path']}/ipfw.cp.rules", "w");
167
		if (!$fd) {
168
			printf("Cannot open ipfw.cp.rules in captiveportal_configure()\n");
169
			return 1;
170
		}
171

    
172
		fwrite($fd, $cprules);
173
		fclose($fd);
174

    
175
		mwexec("/sbin/ipfw {$g['tmp_path']}/ipfw.cp.rules");
176

    
177
		unlink("{$g['tmp_path']}/ipfw.cp.rules");
178

    
179
		/* filter on layer2 as well so we can check MAC addresses */
180
		mwexec("/sbin/sysctl net.link.ether.ipfw=1");
181

    
182
		chdir($g['captiveportal_path']);
183

    
184
		/* TEMPORARY!  FAST_CGI reports _FALSE_ client ip
185
		 * 	       addresses.
186
		 */
187
		$use_fastcgi = false;
188

    
189
		if ($config['captiveportal']['maxproc'])
190
			$maxproc = $config['captiveportal']['maxproc'];
191
		else
192
			$maxproc = 16;
193

    
194
		if(isset($config['captiveportal']['httpslogin'])) {
195
			$cert = base64_decode($config['captiveportal']['certificate']);
196
			$key = base64_decode($config['captiveportal']['private-key']);
197
			/* generate lighttpd configuration */
198
			system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal-SSL.conf",
199
				$cert, $key, "lighty-CaptivePortal-ssl.pid", "8001", "/usr/local/captiveportal/",
200
					"cert-portal.pem", "1", $maxproc, $use_fastcgi, true);
201
		}
202

    
203
		/* generate lighttpd configuration */
204
		system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal.conf",
205
			"", "", "lighty-CaptivePortal.pid", "8000", "/usr/local/captiveportal/",
206
				"cert-portal.pem", "1", $maxproc, $use_fastcgi, true);
207

    
208
		/* attempt to start lighttpd */
209
		$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-CaptivePortal.conf");
210

    
211
		/* fire up https instance */
212
		if(isset($config['captiveportal']['httpslogin']))
213
			$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-CaptivePortal-SSL.conf");
214

    
215
		/* start pruning process (interval defaults to 60 seconds) */
216
		mwexec("/usr/local/bin/minicron $croninterval {$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
			$radiusip2 = ($config['captiveportal']['radiusip2']) ? $config['captiveportal']['radiusip2'] : null;
229

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

    
235
			if ($config['captiveportal']['radiusacctport'])
236
				$radiusacctport = $config['captiveportal']['radiusacctport'];
237
			else
238
				$radiusacctport = 1813;
239

    
240
			if ($config['captiveportal']['radiusport2'])
241
				$radiusport2 = $config['captiveportal']['radiusport2'];
242
			else
243
				$radiusport2 = 1812;
244

    
245
			$radiuskey = $config['captiveportal']['radiuskey'];
246
			$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
247

    
248
			$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db", "w");
249
			if (!$fd) {
250
				printf("Error: cannot open radius DB file in captiveportal_configure().\n");
251
				return 1;
252
			} else if (isset($radiusip2, $radiuskey2)) {
253
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . "\n"
254
					 . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2);
255
			} else {
256
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey);
257
			}
258
			fclose($fd);
259
		}
260

    
261
		if ($g['booting'])
262
			echo "done\n";
263

    
264
	} else {
265
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
266
		killbypid("{$g['varrun_path']}/minicron.pid");
267

    
268
		captiveportal_radius_stop_all();
269

    
270
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
271

    
272
		if (!isset($config['shaper']['enable'])) {
273
			/* unload ipfw */
274
			mwexec("/sbin/kldunload ipfw");
275
		} else {
276
			/* shaper is on - just remove our rules */
277
			mwexec("/sbin/ipfw -f delete set 1");
278
			mwexec("/sbin/ipfw -f delete set 2");
279
			mwexec("/sbin/ipfw -f delete set 3");
280
		}
281
	}
282

    
283
	return 0;
284
}
285

    
286
function captiveportal_rules_generate() {
287
	global $config, $g;
288

    
289
	$cpifn = $config['captiveportal']['interface'];
290
	$cpif = $config['interfaces'][$cpifn]['if'];
291
	$cpip = $config['interfaces'][$cpifn]['ipaddr'];
292

    
293
	/* note: the captive portal daemon inserts all pass rules for authenticated
294
	   clients as skipto 50000 rules to make traffic shaping work */
295

    
296
	$cprules = "";
297

    
298
	/*   allow nat redirects to work  see
299
	     http://cvstrac.pfsense.com/tktview?tn=651
300
	 */
301
	$iflist = array("lan" => "LAN", "wan" => "WAN");
302
	$captive_portal_interface = strtoupper($cpifn);
303
	for ($i = 1; isset($config['interfaces']['opt' . $i]); $i++)
304
		$iflist['opt' . $i] = "OPT{$i}";
305
	foreach ($iflist as $ifent => $ifname) {
306
		if($captive_portal_interface == strtoupper($ifname))
307
			continue;
308
		$int = convert_friendly_interface_to_real_interface_name($ifname);
309
		$cprules .= "add 30 set 1 skipto 50000 all from any to any in via {$int} keep-state\n";
310
	}
311

    
312
	/* captive portal on LAN interface? */
313
	if ($cpifn == "lan") {
314
		/* add anti-lockout rules */
315
		$cprules .= <<<EOD
316
add 500 set 1 pass all from $cpip to any out via $cpif
317
add 501 set 1 pass all from any to $cpip in via $cpif
318

    
319
EOD;
320
	}
321

    
322
	$cprules .= <<<EOD
323
# skip to traffic shaper if not on captive portal interface
324
add 1000 set 1 skipto 50000 all from any to any not layer2 not via $cpif
325
# pass all layer2 traffic on other interfaces
326
add 1001 set 1 pass layer2 not via $cpif
327

    
328
# layer 2: pass ARP
329
add 1100 set 1 pass layer2 mac-type arp
330
# pfsense requires for WPA
331
add 1100 set 1 pass layer2 mac-type 0x888e
332

    
333
# PPP Over Ethernet Discovery Stage
334
add 1100 set 1 pass layer2 mac-type 0x8863
335
# PPP Over Ethernet Session Stage
336
add 1100 set 1 pass layer2 mac-type 0x8864
337

    
338
# layer 2: block anything else non-IP
339
add 1101 set 1 deny layer2 not mac-type ip
340
# layer 2: check if MAC addresses of authenticated clients are correct
341
add 1102 set 1 skipto 20000 layer2
342

    
343
# allow access to our DHCP server (which needs to be able to ping clients as well)
344
add 1200 set 1 pass udp from any 68 to 255.255.255.255 67 in
345
add 1201 set 1 pass udp from any 68 to $cpip 67 in
346
add 1202 set 1 pass udp from $cpip 67 to any 68 out
347
add 1203 set 1 pass icmp from $cpip to any out icmptype 8
348
add 1204 set 1 pass icmp from any to $cpip in icmptype 0
349

    
350
# allow access to our DNS forwarder
351
add 1300 set 1 pass udp from any to $cpip 53 in
352
add 1301 set 1 pass udp from $cpip 53 to any out
353

    
354
# allow access to our web server
355
add 1302 set 1 pass tcp from any to $cpip 8000 in
356
add 1303 set 1 pass tcp from $cpip 8000 to any out
357

    
358
EOD;
359

    
360
	if (isset($config['captiveportal']['httpslogin'])) {
361
		$cprules .= <<<EOD
362
add 1304 set 1 pass tcp from any to $cpip 8001 in
363
add 1305 set 1 pass tcp from $cpip 8001 to any out
364

    
365
EOD;
366
	}
367

    
368
	$cprules .= <<<EOD
369

    
370
# ... 10000-19899: rules per authenticated client go here...
371

    
372
# redirect non-authenticated clients to captive portal
373
add 19900 set 1 fwd 127.0.0.1,8000 tcp from any to any 80 in
374

    
375
# --- for redir ssl
376
# redirect non-authenticated clients to captive portal on ssl
377
add 19901 set 1 fwd 127.0.0.1,8001 tcp from any to any 443 in
378

    
379
# let the responses from the captive portal web server back out
380
add 19902 set 1 pass tcp from any 443 to any out
381

    
382
# --- End redir ssl
383

    
384
# let the responses from the captive portal web server back out
385
add 19903 set 1 pass tcp from any 80 to any out
386
# block everything else
387
add 19904 set 1 deny all from any to any
388

    
389
# ... 20000-29899: layer2 block rules per authenticated client go here...
390

    
391
# pass everything else on layer2
392
add 29900 set 1 pass all from any to any layer2
393

    
394
EOD;
395

    
396
	return $cprules;
397
}
398

    
399
/* remove clients that have been around for longer than the specified amount of time */
400
/* db file structure:
401
timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time */
402

    
403
/* (password is in Base64 and only saved when reauthentication is enabled) */
404
function captiveportal_prune_old() {
405

    
406
	global $g, $config;
407

    
408
	/* check for expired entries */
409
	if ($config['captiveportal']['timeout'])
410
		$timeout = $config['captiveportal']['timeout'] * 60;
411
	else
412
		$timeout = 0;
413

    
414
	if ($config['captiveportal']['idletimeout'])
415
		$idletimeout = $config['captiveportal']['idletimeout'] * 60;
416
	else
417
		$idletimeout = 0;
418

    
419
	if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']))
420
		return;
421

    
422
	captiveportal_lock();
423

    
424
	/* read database */
425
	$cpdb = captiveportal_read_db();
426

    
427
	$radiusservers = captiveportal_get_radius_servers();
428

    
429
	for ($i = 0; $i < count($cpdb); $i++) {
430

    
431
		$timedout = false;
432
		$term_cause = 1;
433

    
434
		/* hard timeout? */
435
		if ($timeout) {
436
			if ((time() - $cpdb[$i][0]) >= $timeout) {
437
				$timedout = true;
438
				$term_cause = 5; // Session-Timeout
439
			}
440
		}
441

    
442
		/* Session-Terminate-Time */
443
		if (!$timedout && !empty($cpdb[$i][9])) {
444
			if (time() >= $cpdb[$i][9]) {
445
				$timedout = true;
446
				$term_cause = 5; // Session-Timeout
447
			}
448
		}
449

    
450
		/* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
451
		$idletimeout = (is_numeric($cpdb[$i][8])) ? $cpdb[$i][8] : $idletimeout;
452
		/* if an idle timeout is specified, get last activity timestamp from ipfw */
453
		if (!$timedout && $idletimeout) {
454
			$lastact = captiveportal_get_last_activity($cpdb[$i][1]);
455
			if ($lastact && ((time() - $lastact) >= $idletimeout)) {
456
				$timedout = true;
457
				$term_cause = 4; // Idle-Timeout
458
				$stop_time = $lastact; // Entry added to comply with WISPr
459
			}
460
		}
461

    
462
		/* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
463
		if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpdb[$i][7])) {
464
			if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
465
				$timedout = true;
466
				$term_cause = 5; // Session-Timeout
467
			}
468
		}
469

    
470
		if ($timedout) {
471
			captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
472
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
473
			unset($cpdb[$i]);
474
		}
475

    
476
		/* do periodic RADIUS reauthentication? */
477
		if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
478
			($radiusservers !== false)) {
479

    
480
			if (isset($config['captiveportal']['radacct_enable'])) {
481
				if ($config['captiveportal']['reauthenticateacct'] == "stopstart") {
482
					/* stop and restart accounting */
483
					RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
484
										   $cpdb[$i][4], // username
485
										   $cpdb[$i][5], // sessionid
486
										   $cpdb[$i][0], // start time
487
										   $radiusservers[0]['ipaddr'],
488
										   $radiusservers[0]['acctport'],
489
										   $radiusservers[0]['key'],
490
										   $cpdb[$i][2], // clientip
491
										   $cpdb[$i][3], // clientmac
492
										   10); // NAS Request
493
					exec("/sbin/ipfw zero {$cpdb[$i][1]}");
494
					RADIUS_ACCOUNTING_START($cpdb[$i][1], // ruleno
495
											$cpdb[$i][4], // username
496
											$cpdb[$i][5], // sessionid
497
											$radiusservers[0]['ipaddr'],
498
											$radiusservers[0]['acctport'],
499
											$radiusservers[0]['key'],
500
											$cpdb[$i][2], // clientip
501
											$cpdb[$i][3]); // clientmac
502
				} else if ($config['captiveportal']['reauthenticateacct'] == "interimupdate") {
503
					RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
504
										   $cpdb[$i][4], // username
505
										   $cpdb[$i][5], // sessionid
506
										   $cpdb[$i][0], // start time
507
										   $radiusservers[0]['ipaddr'],
508
										   $radiusservers[0]['acctport'],
509
										   $radiusservers[0]['key'],
510
										   $cpdb[$i][2], // clientip
511
										   $cpdb[$i][3], // clientmac
512
										   10, // NAS Request
513
										   true); // Interim Updates
514
				}
515
			}
516

    
517
			/* check this user against RADIUS again */
518
			$auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
519
										  base64_decode($cpdb[$i][6]), // password
520
							  			  $radiusservers,
521
										  $cpdb[$i][2], // clientip
522
										  $cpdb[$i][3], // clientmac
523
										  $cpdb[$i][1]); // ruleno
524

    
525
			if ($auth_list['auth_val'] == 3) {
526
				captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
527
				captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
528
				unset($cpdb[$i]);
529
			}
530
		}
531
	}
532

    
533
	/* write database */
534
	captiveportal_write_db($cpdb);
535

    
536
	captiveportal_unlock();
537
}
538

    
539
/* remove a single client according to the DB entry */
540
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
541

    
542
	global $g, $config;
543

    
544
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
545

    
546
	/* this client needs to be deleted - remove ipfw rules */
547
	if (isset($config['captiveportal']['radacct_enable']) && isset($radiusservers[0])) {
548
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
549
							   $dbent[4], // username
550
							   $dbent[5], // sessionid
551
							   $dbent[0], // start time
552
							   $radiusservers[0]['ipaddr'],
553
							   $radiusservers[0]['acctport'],
554
							   $radiusservers[0]['key'],
555
							   $dbent[2], // clientip
556
							   $dbent[3], // clientmac
557
							   $term_cause, // Acct-Terminate-Cause
558
							   false,
559
							   $stop_time);
560
	}
561

    
562
	mwexec("/sbin/ipfw delete " . $dbent[1] . " " . ($dbent[1]+10000));
563

    
564
	//KEYCOM: we need to delete +40500 and +45500 as well...
565
	//these are the rule numbers we use to control traffic shaping for each logged in user via captive portal
566
	//we only need to remove our rules if peruserbw is turned on.
567
	if (isset($config['captiveportal']['peruserbw'])) {
568
		mwexec("/sbin/ipfw delete " . ($dbent[1]+40500));
569
		mwexec("/sbin/ipfw delete " . ($dbent[1]+45500));
570
	}
571

    
572
	/* ensure all pf states are killed (pfSense) */
573
	mwexec("pfctl -k {$dbent[2]}");
574

    
575
}
576

    
577
/* remove a single client by ipfw rule number */
578
function captiveportal_disconnect_client($id,$term_cause = 1) {
579

    
580
	global $g, $config;
581

    
582
	captiveportal_lock();
583

    
584
	/* read database */
585
	$cpdb = captiveportal_read_db();
586
	$radiusservers = captiveportal_get_radius_servers();
587

    
588
	/* find entry */
589
	for ($i = 0; $i < count($cpdb); $i++) {
590
		if ($cpdb[$i][1] == $id) {
591
			captiveportal_disconnect($cpdb[$i], $radiusservers, $term_cause);
592
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "DISCONNECT");
593
			unset($cpdb[$i]);
594
			break;
595
		}
596
	}
597

    
598
	/* write database */
599
	captiveportal_write_db($cpdb);
600

    
601
	captiveportal_unlock();
602
}
603

    
604
/* send RADIUS acct stop for all current clients */
605
function captiveportal_radius_stop_all() {
606
	global $g, $config;
607

    
608
	if (!isset($config['captiveportal']['radacct_enable']))
609
		return;
610

    
611
	captiveportal_lock();
612
	$cpdb = captiveportal_read_db();
613

    
614
	$radiusservers = captiveportal_get_radius_servers();
615

    
616
	if (isset($radiusservers[0])) {
617
		for ($i = 0; $i < count($cpdb); $i++) {
618
			RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
619
								   $cpdb[$i][4], // username
620
								   $cpdb[$i][5], // sessionid
621
								   $cpdb[$i][0], // start time
622
								   $radiusservers[0]['ipaddr'],
623
								   $radiusservers[0]['acctport'],
624
								   $radiusservers[0]['key'],
625
								   $cpdb[$i][2], // clientip
626
								   $cpdb[$i][3], // clientmac
627
								   7); // Admin Reboot
628
		}
629
	}
630
	captiveportal_unlock();
631
}
632

    
633
function captiveportal_passthrumac_configure() {
634
	global $config, $g;
635

    
636
	captiveportal_lock();
637

    
638
	/* clear out passthru macs, if necessary */
639
	unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
640

    
641
	if (is_array($config['captiveportal']['passthrumac'])) {
642

    
643
		$fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db", "w");
644
		if (!$fd) {
645
			printf("Error: cannot open passthru mac DB file in captiveportal_passthrumac_configure().\n");
646
			captiveportal_unlock();
647
			return 1;
648
		}
649

    
650
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
651
			/* record passthru mac so it can be recognized and let thru */
652
			fwrite($fd, $macent['mac'] . "\n");
653
		}
654

    
655
		fclose($fd);
656
	}
657

    
658
	/*    pass through mac entries should always exist.  the reason
659
	 *    for this is because we do not have native mac address filtering
660
         *    mechanisms.  this allows us to filter by mac address easily
661
	 *    and get around this limitation.   I consider this a bug in
662
         *    m0n0wall and pfSense as m0n0wall does not have native mac
663
         *    filtering mechanisms as well. -Scott Ullrich
664
         */
665
	if (is_array($config['captiveportal']['passthrumac'])) {
666
		mwexec("/sbin/ipfw delete 50");
667
		foreach($config['captiveportal']['passthrumac'] as $ptm) {
668
			/* create the pass through mac entry */
669
			//system("echo /sbin/ipfw add 50 skipto 65535 ip from any to any MAC {$ptm['mac']} any > /tmp/cp");
670
			mwexec("/sbin/ipfw add 50 skipto 29900 ip from any to any MAC {$ptm['mac']} any keep-state");
671
			mwexec("/sbin/ipfw add 50 skipto 29900 ip from any to any MAC any {$ptm['mac']} keep-state");
672
		}
673
	}
674

    
675
	captiveportal_unlock();
676

    
677
	return 0;
678
}
679

    
680
function captiveportal_allowedip_configure() {
681
	global $config, $g;
682

    
683
	captiveportal_lock();
684

    
685
	/* clear out existing allowed ips, if necessary */
686
	if (file_exists("{$g['vardb_path']}/captiveportal_ip.db")) {
687
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "r");
688
		if ($fd) {
689
			while (!feof($fd)) {
690
				$line = trim(fgets($fd));
691
				if ($line) {
692
					list($ip,$rule) = explode(",",$line);
693
					mwexec("/sbin/ipfw delete $rule");
694
				}
695
			}
696
		}
697
		fclose($fd);
698
		unlink("{$g['vardb_path']}/captiveportal_ip.db");
699
	}
700

    
701
	/* get next ipfw rule number */
702
	if (file_exists("{$g['vardb_path']}/captiveportal.nextrule"))
703
		$ruleno = trim(file_get_contents("{$g['vardb_path']}/captiveportal.nextrule"));
704
	if (!$ruleno)
705
		$ruleno = 10000;	/* first rule number */
706

    
707
	if (is_array($config['captiveportal']['allowedip'])) {
708

    
709
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "w");
710
		if (!$fd) {
711
			printf("Error: cannot open allowed ip DB file in captiveportal_allowedip_configure().\n");
712
			captiveportal_unlock();
713
			return 1;
714
		}
715

    
716
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
717

    
718
			/* record allowed ip so it can be recognized and removed later */
719
			fwrite($fd, $ipent['ip'] . "," . $ruleno ."\n");
720

    
721
			/* insert ipfw rule to allow ip thru */
722
			if ($ipent['dir'] == "from") {
723
				mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any in");
724
				mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " out");
725
			} else {
726
				mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " in");
727
				mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any out");
728
			}
729

    
730
			$ruleno++;
731
			if ($ruleno > 19899)
732
				$ruleno = 10000;
733
		}
734

    
735
		fclose($fd);
736

    
737
		/* write next rule number */
738
		$fd = @fopen("{$g['vardb_path']}/captiveportal.nextrule", "w");
739
		if ($fd) {
740
			fwrite($fd, $ruleno);
741
			fclose($fd);
742
		}
743
	}
744

    
745
	captiveportal_unlock();
746
	return 0;
747
}
748

    
749
/* get last activity timestamp given ipfw rule number */
750
function captiveportal_get_last_activity($ruleno) {
751

    
752
	$ipfwoutput = "";
753

    
754
	exec("/sbin/ipfw -T list {$ruleno} 2>/dev/null", $ipfwoutput);
755

    
756
	/* in */
757
	if ($ipfwoutput[0]) {
758
		$ri = explode(" ", $ipfwoutput[0]);
759
		if ($ri[1])
760
			return $ri[1];
761
	}
762

    
763
	return 0;
764
}
765

    
766
/* read RADIUS servers into array */
767
function captiveportal_get_radius_servers() {
768

    
769
        global $g;
770

    
771
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
772
                $fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db","r");
773
                if ($fd) {
774
                        $radiusservers = array();
775
                        while (!feof($fd)) {
776
                                $line = trim(fgets($fd));
777
                                if ($line) {
778
                                        $radsrv = array();
779
                                        list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
780
                                        $radiusservers[] = $radsrv;
781
                                }
782
                        }
783
                        fclose($fd);
784

    
785
                        return $radiusservers;
786
                }
787
        }
788

    
789
        return false;
790
}
791

    
792
/* lock captive portal information, decide that the lock file is stale after
793
   10 seconds */
794
function captiveportal_lock() {
795

    
796
        global $lockfile;
797

    
798
        $n = 0;
799
        while ($n < 10) {
800
                /* open the lock file in append mode to avoid race condition */
801
                if ($fd = @fopen($lockfile, "x")) {
802
                        /* succeeded */
803
                        fclose($fd);
804
                        return;
805
                } else {
806
                        /* file locked, wait and try again */
807
                        sleep(1);
808
                        $n++;
809
                }
810
        }
811
}
812

    
813
/* unlock captive portal information file */
814
function captiveportal_unlock() {
815

    
816
        global $lockfile;
817

    
818
        if (file_exists($lockfile))
819
                unlink($lockfile);
820
}
821

    
822
/* log successful captive portal authentication to syslog */
823
/* part of this code from php.net */
824
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
825
	define_syslog_variables();
826
	$message = trim($message);
827
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
828
	// Log it
829
	if (!$message)
830
	syslog(LOG_INFO, "$status: $user, $mac, $ip");
831
	else
832
	syslog(LOG_INFO, "$status: $user, $mac, $ip, $message");
833
	closelog();
834
}
835

    
836
function radius($username,$password,$clientip,$clientmac,$type) {
837
	global $g, $config;
838

    
839
	$next_ruleno = get_next_ipfw_ruleno();
840
	$radiusservers = captiveportal_get_radius_servers();
841
	$radacct_enable = isset($config['captiveportal']['radacct_enable']);
842

    
843
	$auth_list = RADIUS_AUTHENTICATION($username,
844
					$password,
845
					$radiusservers,
846
					$clientip,
847
					$clientmac,
848
					$next_ruleno);
849

    
850
	if ($auth_list['auth_val'] == 2) {
851
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
852
		$sessionid = portal_allow($clientip,
853
					$clientmac,
854
					$username,
855
					$password,
856
					$auth_list['session_timeout'],
857
					$auth_list['idle_timeout'],
858
					$auth_list['url_redirection'],
859
					$auth_list['session_terminate_time']);
860

    
861
		if ($radacct_enable) {
862
			$auth_list['acct_val'] = RADIUS_ACCOUNTING_START($next_ruleno,
863
									$username,
864
									$sessionid,
865
									$radiusservers[0]['ipaddr'],
866
									$radiusservers[0]['acctport'],
867
									$radiusservers[0]['key'],
868
									$clientip,
869
									$clientmac);
870
			if ($auth_list['acct_val'] == 1)
871
				captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
872
		}
873
	}
874

    
875
	return $auth_list;
876

    
877
}
878

    
879
/* read captive portal DB into array */
880
function captiveportal_read_db() {
881

    
882
        global $g;
883

    
884
        $cpdb = array();
885
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
886
        if ($fd) {
887
                while (!feof($fd)) {
888
                        $line = trim(fgets($fd));
889
                        if ($line) {
890
                                $cpdb[] = explode(",", $line);
891
                        }
892
                }
893
                fclose($fd);
894
        }
895
        return $cpdb;
896
}
897

    
898
/* write captive portal DB */
899
function captiveportal_write_db($cpdb) {
900

    
901
        global $g;
902

    
903
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
904
        if ($fd) {
905
                foreach ($cpdb as $cpent) {
906
                        fwrite($fd, join(",", $cpent) . "\n");
907
                }
908
                fclose($fd);
909
        }
910
}
911

    
912
function captiveportal_write_elements() {
913
	global $g, $config;
914

    
915
	/* delete any existing elements */
916
	if (is_dir($g['captiveportal_element_path'])) {
917
		$dh = opendir($g['captiveportal_element_path']);
918
		while (($file = readdir($dh)) !== false) {
919
			if ($file != "." && $file != "..")
920
				unlink($g['captiveportal_element_path'] . "/" . $file);
921
		}
922
		closedir($dh);
923
	} else {
924
		mkdir($g['captiveportal_element_path']);
925
	}
926

    
927
	if (is_array($config['captiveportal']['element'])) {
928
		conf_mount_rw();
929
		foreach ($config['captiveportal']['element'] as $data) {
930
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
931
			if (!$fd) {
932
				printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
933
				return 1;
934
			}
935
			$decoded = base64_decode($data['content']);
936
			fwrite($fd,$decoded);
937
			fclose($fd);
938
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
939
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
940
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
941
		}
942
		conf_mount_ro();
943
	}
944

    
945
	return 0;
946
}
947

    
948
/*
949
 * This function will calculate the lowest free firewall ruleno
950
 * within the range specified based on the actual installed rules
951
 *
952
 */
953

    
954
function get_next_ipfw_ruleno($rulenos_start = 10000, $rulenos_range_max = 9899) {
955

    
956
	$fwrules = "";
957
	$matches = "";
958
	exec("/sbin/ipfw show", $fwrules);
959
	foreach ($fwrules as $fwrule) {
960
		preg_match("/^(\d+)\s+/", $fwrule, $matches);
961
		$rulenos_used[] = $matches[1];
962
	}
963
	$rulenos_used = array_unique($rulenos_used);
964
	$rulenos_range = count($rulenos_used);
965
	if ($rulenos_range > $rulenos_range_max) {
966
		return NULL;
967
	}
968
	$rulenos_pool = range($rulenos_start, ($rulenos_start + $rulenos_range));
969
	$rulenos_free = array_diff($rulenos_pool, $rulenos_used);
970
	$ruleno = array_shift($rulenos_free);
971

    
972
	return $ruleno;
973
}
974

    
975
/*
976
 * This function will calculate the lowest free firewall ruleno
977
 * within the range specified based on the actual installed rules
978
 *
979
 */
980

    
981
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 10000, $rulenos_range_max = 9899) {
982

    
983
	$fwrules = "";
984
	$matches = "";
985
	exec("/sbin/ipfw show", $fwrules);
986
	foreach ($fwrules as $fwrule) {
987
		preg_match("/^(\d+)\s+/", $fwrule, $matches);
988
		$rulenos_used[] = $matches[1];
989
	}
990
	$rulenos_used = array_unique($rulenos_used);
991
	$rulenos_range = count($rulenos_used);
992
	if ($rulenos_range > $rulenos_range_max) {
993
		return NULL;
994
	}
995
	$rulenos_pool = range($rulenos_start, ($rulenos_start + $rulenos_range));
996
	$rulenos_free = array_diff($rulenos_pool, $rulenos_used);
997
	$ruleno = array_shift($rulenos_free);
998

    
999
	return $ruleno;
1000
}
1001

    
1002
/**
1003
 * This function will calculate the traffic produced by a client
1004
 * based on its firewall rule
1005
 *
1006
 * Point of view: NAS
1007
 *
1008
 * Input means: from the client
1009
 * Output means: to the client
1010
 *
1011
 */
1012

    
1013
function getVolume($ruleno) {
1014

    
1015
    $volume = array();
1016

    
1017
    // Initialize vars properly, since we don't want NULL vars
1018
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1019

    
1020
    // Ingress
1021
    $ipfw = "";
1022
    $matches = "";
1023
    exec("/sbin/ipfw show {$ruleno}", $ipfw);
1024
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[0], $matches);
1025
    $volume['input_pkts'] = $matches[2];
1026
    $volume['input_bytes'] = $matches[3];
1027

    
1028
    // Flush internal buffer
1029
    unset($matches);
1030

    
1031
    // Outgress
1032
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[1], $matches);
1033
    $volume['output_pkts'] = $matches[2];
1034
    $volume['output_bytes'] = $matches[3];
1035

    
1036
    return $volume;
1037
}
1038

    
1039
/**
1040
 * Get the NAS-Identifier
1041
 *
1042
 * We will use our local hostname to make up the nas_id
1043
 */
1044
function getNasID()
1045
{
1046
    $nasId = "";
1047
    exec("/bin/hostname", $nasId);
1048
    if(!$nasId[0])
1049
        $nasId[0] = "pfSense";
1050
    return $nasId[0];
1051
}
1052

    
1053
/**
1054
 * Get the NAS-IP-Address based on the current wan address
1055
 *
1056
 * Use functions in interfaces.inc to find this out
1057
 *
1058
 */
1059

    
1060
function getNasIP()
1061
{
1062
    $nasIp = get_current_wan_address();
1063
    if(!$nasIp)
1064
        $nasIp = "0.0.0.0";
1065
    return $nasIp;
1066
}
1067

    
1068
?>
(4-4/27)