Project

General

Profile

Download (33.4 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
		if ($config['captiveportal']['maxproc'])
185
			$maxproc = $config['captiveportal']['maxproc'];
186
		else
187
			$maxproc = 16;
188

    
189
		$use_fastcgi = true;
190

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

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

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

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

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

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

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

    
237
			if ($config['captiveportal']['radiusport2'])
238
				$radiusport2 = $config['captiveportal']['radiusport2'];
239
			else
240
				$radiusport2 = 1812;
241

    
242
			$radiuskey = $config['captiveportal']['radiuskey'];
243
			$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
244

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

    
258
		if ($g['booting'])
259
			echo "done\n";
260

    
261
	} else {
262
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
263
		killbypid("{$g['varrun_path']}/minicron.pid");
264

    
265
		captiveportal_radius_stop_all();
266

    
267
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
268

    
269
		if (!isset($config['shaper']['enable'])) {
270
			/* unload ipfw */
271
			$installed_time_based_rules = false;
272
			if($config['schedules']) {
273
				foreach($config['schedules']['schedule'] as $sched) {
274
					$installed_time_based_rules = true;
275
				}
276
			}
277
			if($installed_time_based_rules == false)
278
				mwexec("/sbin/kldunload ipfw");
279
		} else {
280
			/* shaper is on - just remove our rules */
281
			mwexec("/sbin/ipfw -f delete set 1");
282
			mwexec("/sbin/ipfw -f delete set 2");
283
			mwexec("/sbin/ipfw -f delete set 3");
284
		}
285
	}
286

    
287
	return 0;
288
}
289

    
290
function captiveportal_rules_generate() {
291
	global $config, $g;
292

    
293
	$cpifn = $config['captiveportal']['interface'];
294
	$cpif = $config['interfaces'][$cpifn]['if'];
295
	$cpip = $config['interfaces'][$cpifn]['ipaddr'];
296

    
297
	/* note: the captive portal daemon inserts all pass rules for authenticated
298
	   clients as skipto 50000 rules to make traffic shaping work */
299

    
300
	$cprules = "";
301

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

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

    
323
EOD;
324
	}
325

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

    
332
# layer 2: pass ARP
333
add 1100 set 1 pass layer2 mac-type arp
334
# pfsense requires for WPA
335
add 1100 set 1 pass layer2 mac-type 0x888e
336
add 1100 set 1 pass layer2 mac-type 0x88c7
337

    
338
# PPP Over Ethernet Discovery Stage
339
add 1100 set 1 pass layer2 mac-type 0x8863
340
# PPP Over Ethernet Session Stage
341
add 1100 set 1 pass layer2 mac-type 0x8864
342

    
343
# layer 2: block anything else non-IP
344
add 1101 set 1 deny layer2 not mac-type ip
345
# layer 2: check if MAC addresses of authenticated clients are correct
346
add 1102 set 1 skipto 20000 layer2
347

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

    
355
# allow access to our DNS forwarder
356
add 1300 set 1 pass udp from any to $cpip 53 in
357
add 1301 set 1 pass udp from $cpip 53 to any out
358

    
359
# allow access to our web server
360
add 1302 set 1 pass tcp from any to $cpip 8000 in
361
add 1303 set 1 pass tcp from $cpip 8000 to any out
362

    
363
EOD;
364

    
365
	if (isset($config['captiveportal']['httpslogin'])) {
366
		$cprules .= <<<EOD
367
add 1304 set 1 pass tcp from any to $cpip 8001 in
368
add 1305 set 1 pass tcp from $cpip 8001 to any out
369

    
370
EOD;
371
	}
372

    
373
        $cprules .= <<<EOD
374
#PPPoE Discovery Stage
375
add 1100 set 1 pass layer2 mac-type 0x8863
376
#PPPoE Session Stage
377
add 1100 set 1 pass layer2 mac-type 0x8864
378

    
379
EOD;
380

    
381
        $cprules .= <<<EOD
382
# Allow WPA
383
add 1100 set 1 pass layer2 mac-type 0x888e
384

    
385
EOD;
386

    
387

    
388
    $cprules .= <<<EOD
389

    
390
# ... 10000-19899: rules per authenticated client go here...
391

    
392
# redirect non-authenticated clients to captive portal
393
add 19902 set 1 fwd 127.0.0.1,8000 tcp from any to any 80 in
394
# let the responses from the captive portal web server back out
395
add 19903 set 1 pass tcp from any 80 to any out
396
# block everything else
397
add 19904 set 1 deny all from any to any
398

    
399
# ... 20000-29899: layer2 block rules per authenticated client go here...
400

    
401
# pass everything else on layer2
402
add 29900 set 1 pass all from any to any layer2
403

    
404
EOD;
405

    
406
    return $cprules;
407
}
408

    
409
/* remove clients that have been around for longer than the specified amount of time */
410
/* db file structure:
411
timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time */
412

    
413
/* (password is in Base64 and only saved when reauthentication is enabled) */
414
function captiveportal_prune_old() {
415

    
416
    global $g, $config;
417

    
418
    /* check for expired entries */
419
    if ($config['captiveportal']['timeout'])
420
        $timeout = $config['captiveportal']['timeout'] * 60;
421
    else
422
        $timeout = 0;
423

    
424
    if ($config['captiveportal']['idletimeout'])
425
        $idletimeout = $config['captiveportal']['idletimeout'] * 60;
426
    else
427
        $idletimeout = 0;
428

    
429
    if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && !isset($config['captiveportal']['radiussession_timeout']))
430
        return;
431

    
432
    captiveportal_lock();
433

    
434
    /* read database */
435
    $cpdb = captiveportal_read_db();
436

    
437
    $radiusservers = captiveportal_get_radius_servers();
438

    
439
    for ($i = 0; $i < count($cpdb); $i++) {
440

    
441
        $timedout = false;
442
        $term_cause = 1;
443

    
444
        /* hard timeout? */
445
        if ($timeout) {
446
            if ((time() - $cpdb[$i][0]) >= $timeout) {
447
                $timedout = true;
448
                $term_cause = 5; // Session-Timeout
449
            }
450
        }
451

    
452
        /* Session-Terminate-Time */
453
        if (!$timedout && !empty($cpdb[$i][9])) {
454
            if (time() >= $cpdb[$i][9]) {
455
                $timedout = true;
456
                $term_cause = 5; // Session-Timeout
457
            }
458
        }
459

    
460
        /* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
461
        $idletimeout = (is_numeric($cpdb[$i][8])) ? $cpdb[$i][8] : $idletimeout;
462
        /* if an idle timeout is specified, get last activity timestamp from ipfw */
463
        if (!$timedout && $idletimeout) {
464
            $lastact = captiveportal_get_last_activity($cpdb[$i][1]);
465
            if ($lastact && ((time() - $lastact) >= $idletimeout)) {
466
                $timedout = true;
467
                $term_cause = 4; // Idle-Timeout
468
                $stop_time = $lastact; // Entry added to comply with WISPr
469
            }
470
        }
471

    
472
        /* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
473
        if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpdb[$i][7])) {
474
            if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
475
                $timedout = true;
476
                $term_cause = 5; // Session-Timeout
477
            }
478
        }
479

    
480
        if ($timedout) {
481
            captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
482
            captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
483
            unset($cpdb[$i]);
484
        }
485

    
486
        /* do periodic RADIUS reauthentication? */
487
        if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
488
            ($radiusservers !== false)) {
489

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

    
527
            /* check this user against RADIUS again */
528
            $auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
529
                                          base64_decode($cpdb[$i][6]), // password
530
                                            $radiusservers,
531
                                          $cpdb[$i][2], // clientip
532
                                          $cpdb[$i][3], // clientmac
533
                                          $cpdb[$i][1]); // ruleno
534

    
535
            if ($auth_list['auth_val'] == 3) {
536
                captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
537
                captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
538
                unset($cpdb[$i]);
539
            }
540
        }
541
    }
542

    
543
    /* write database */
544
    captiveportal_write_db($cpdb);
545

    
546
    captiveportal_unlock();
547
}
548

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

    
552
	global $g, $config;
553

    
554
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
555

    
556
	/* this client needs to be deleted - remove ipfw rules */
557
	if (isset($config['captiveportal']['radacct_enable']) && isset($radiusservers[0])) {
558
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
559
							   $dbent[4], // username
560
							   $dbent[5], // sessionid
561
							   $dbent[0], // start time
562
							   $radiusservers[0]['ipaddr'],
563
							   $radiusservers[0]['acctport'],
564
							   $radiusservers[0]['key'],
565
							   $dbent[2], // clientip
566
							   $dbent[3], // clientmac
567
							   $term_cause, // Acct-Terminate-Cause
568
							   false,
569
							   $stop_time);
570
	}
571

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

    
574
    /* We need to delete +40500 and +45500 as well...
575
     * these are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
576
     * We could get an error if the pipe doesn't exist but everything should still be fine
577
     */
578
    if (isset($config['captiveportal']['peruserbw'])) {
579
        mwexec("/sbin/ipfw pipe " . ($dbent[1]+40500) . " delete");
580
        mwexec("/sbin/ipfw pipe " . ($dbent[1]+45500) . " delete");
581
    }
582

    
583
	/* pfSense: ensure all pf states are killed (pfSense) */
584
	mwexec("pfctl -k {$dbent[2]}");
585

    
586
}
587

    
588
/* remove a single client by ipfw rule number */
589
function captiveportal_disconnect_client($id,$term_cause = 1) {
590

    
591
	global $g, $config;
592

    
593
	captiveportal_lock();
594

    
595
	/* read database */
596
	$cpdb = captiveportal_read_db();
597
	$radiusservers = captiveportal_get_radius_servers();
598

    
599
	/* find entry */
600
	for ($i = 0; $i < count($cpdb); $i++) {
601
		if ($cpdb[$i][1] == $id) {
602
			captiveportal_disconnect($cpdb[$i], $radiusservers, $term_cause);
603
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "DISCONNECT");
604
			unset($cpdb[$i]);
605
			break;
606
		}
607
	}
608

    
609
	/* write database */
610
	captiveportal_write_db($cpdb);
611

    
612
	captiveportal_unlock();
613
}
614

    
615
/* send RADIUS acct stop for all current clients */
616
function captiveportal_radius_stop_all() {
617
	global $g, $config;
618

    
619
	if (!isset($config['captiveportal']['radacct_enable']))
620
		return;
621

    
622
	captiveportal_lock();
623
	$cpdb = captiveportal_read_db();
624

    
625
	$radiusservers = captiveportal_get_radius_servers();
626

    
627
	if (isset($radiusservers[0])) {
628
		for ($i = 0; $i < count($cpdb); $i++) {
629
			RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
630
								   $cpdb[$i][4], // username
631
								   $cpdb[$i][5], // sessionid
632
								   $cpdb[$i][0], // start time
633
								   $radiusservers[0]['ipaddr'],
634
								   $radiusservers[0]['acctport'],
635
								   $radiusservers[0]['key'],
636
								   $cpdb[$i][2], // clientip
637
								   $cpdb[$i][3], // clientmac
638
								   7); // Admin Reboot
639
		}
640
	}
641
	captiveportal_unlock();
642
}
643

    
644
function captiveportal_passthrumac_configure() {
645
	global $config, $g;
646

    
647
	captiveportal_lock();
648

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

    
652
	if (is_array($config['captiveportal']['passthrumac'])) {
653

    
654
		$fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db", "w");
655
		if (!$fd) {
656
			printf("Error: cannot open passthru mac DB file in captiveportal_passthrumac_configure().\n");
657
			captiveportal_unlock();
658
			return 1;
659
		}
660

    
661
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
662
			/* record passthru mac so it can be recognized and let thru */
663
			fwrite($fd, $macent['mac'] . "\n");
664
		}
665

    
666
		fclose($fd);
667
	}
668

    
669
	/*    pfSense:
670
	 * 	  pass through mac entries should always exist.  the reason
671
	 *    for this is because we do not have native mac address filtering
672
	 *    mechanisms.  this allows us to filter by mac address easily
673
	 *    and get around this limitation.   I consider this a bug in
674
	 *    m0n0wall and pfSense as m0n0wall does not have native mac
675
	 *    filtering mechanisms as well. -Scott Ullrich
676
	 */
677
	if (is_array($config['captiveportal']['passthrumac'])) {
678
		mwexec("/sbin/ipfw delete 50");
679
		foreach($config['captiveportal']['passthrumac'] as $ptm) {
680
			/* create the pass through mac entry */
681
			//system("echo /sbin/ipfw add 50 skipto 65535 ip from any to any MAC {$ptm['mac']} any > /tmp/cp");
682
			mwexec("/sbin/ipfw add 50 skipto 29900 ip from any to any MAC {$ptm['mac']} any keep-state");
683
			mwexec("/sbin/ipfw add 50 skipto 29900 ip from any to any MAC any {$ptm['mac']} keep-state");
684
		}
685
	}
686

    
687
	captiveportal_unlock();
688

    
689
	return 0;
690
}
691

    
692
function captiveportal_allowedip_configure() {
693
	global $config, $g;
694

    
695
	captiveportal_lock();
696

    
697
	/* clear out existing allowed ips, if necessary */
698
	if (file_exists("{$g['vardb_path']}/captiveportal_ip.db")) {
699
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "r");
700
		if ($fd) {
701
			while (!feof($fd)) {
702
				$line = trim(fgets($fd));
703
				if ($line) {
704
					list($ip,$rule) = explode(",",$line);
705
					mwexec("/sbin/ipfw delete $rule");
706
				}
707
			}
708
		}
709
		fclose($fd);
710
		unlink("{$g['vardb_path']}/captiveportal_ip.db");
711
	}
712

    
713
	/* get next ipfw rule number */
714
	if (file_exists("{$g['vardb_path']}/captiveportal.nextrule"))
715
		$ruleno = trim(file_get_contents("{$g['vardb_path']}/captiveportal.nextrule"));
716
	if (!$ruleno)
717
		$ruleno = 10000;	/* first rule number */
718

    
719
	if (is_array($config['captiveportal']['allowedip'])) {
720

    
721
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "w");
722
		if (!$fd) {
723
			printf("Error: cannot open allowed ip DB file in captiveportal_allowedip_configure().\n");
724
			captiveportal_unlock();
725
			return 1;
726
		}
727

    
728
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
729
            /* get next ipfw rule number */
730
            $ruleno = captiveportal_get_next_ipfw_ruleno();
731

    
732
            /* if the pool is empty, return apprioriate message and fail */
733
            if (is_null($ruleno)) {
734
                printf("Error: system reached maximum login capacity, no free FW rulenos in captiveportal_allowedip_configure().\n");
735
                fclose($fd);
736
                captiveportal_unlock();
737
                return 1;
738
            }
739

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

    
743
            /* insert ipfw rule to allow ip thru */
744
            if ($ipent['dir'] == "from") {
745
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any in");
746
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " out");
747
            } else {
748
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " in");
749
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any out");
750
            }
751

    
752
        }
753

    
754
        fclose($fd);
755
    }
756

    
757
    captiveportal_unlock();
758
    return 0;
759
}
760

    
761
/* get last activity timestamp given ipfw rule number */
762
function captiveportal_get_last_activity($ruleno) {
763

    
764
	$ipfwoutput = "";
765

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

    
768
	/* in */
769
	if ($ipfwoutput[0]) {
770
		$ri = explode(" ", $ipfwoutput[0]);
771
		if ($ri[1])
772
			return $ri[1];
773
	}
774

    
775
	return 0;
776
}
777

    
778
/* read RADIUS servers into array */
779
function captiveportal_get_radius_servers() {
780

    
781
        global $g;
782

    
783
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
784
                $fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db","r");
785
                if ($fd) {
786
                        $radiusservers = array();
787
                        while (!feof($fd)) {
788
                                $line = trim(fgets($fd));
789
                                if ($line) {
790
                                        $radsrv = array();
791
                                        list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
792
                                        $radiusservers[] = $radsrv;
793
                                }
794
                        }
795
                        fclose($fd);
796

    
797
                        return $radiusservers;
798
                }
799
        }
800

    
801
        return false;
802
}
803

    
804
/* lock captive portal information, decide that the lock file is stale after
805
   10 seconds */
806
function captiveportal_lock() {
807

    
808
        global $lockfile;
809

    
810
        $n = 0;
811
        while ($n < 10) {
812
                /* open the lock file in append mode to avoid race condition */
813
                if ($fd = @fopen($lockfile, "x")) {
814
                        /* succeeded */
815
                        fclose($fd);
816
                        return;
817
                } else {
818
                        /* file locked, wait and try again */
819
                        sleep(1);
820
                        $n++;
821
                }
822
        }
823
}
824

    
825
/* unlock captive portal information file */
826
function captiveportal_unlock() {
827

    
828
        global $lockfile;
829

    
830
        if (file_exists($lockfile))
831
                unlink($lockfile);
832
}
833

    
834
/* log successful captive portal authentication to syslog */
835
/* part of this code from php.net */
836
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
837
	define_syslog_variables();
838
	$message = trim($message);
839
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
840
	// Log it
841
	if (!$message)
842
	syslog(LOG_INFO, "$status: $user, $mac, $ip");
843
	else
844
	syslog(LOG_INFO, "$status: $user, $mac, $ip, $message");
845
	closelog();
846
}
847

    
848
function radius($username,$password,$clientip,$clientmac,$type) {
849
    global $g, $config;
850

    
851
    /* Start locking from the beginning of an authentication session */
852
    captiveportal_lock();
853

    
854
    $ruleno = captiveportal_get_next_ipfw_ruleno();
855

    
856
    /* if the pool is empty, return apprioriate message and fail authentication */
857
    if (is_null($ruleno)) {
858
        $auth_list = array();
859
        $auth_list['auth_val'] = 1;
860
        $auth_list['error'] = "System reached maximum login capacity";
861
        captiveportal_unlock();
862
        return $auth_list;
863
    }
864

    
865
    $radiusservers = captiveportal_get_radius_servers();
866

    
867
    $auth_list = RADIUS_AUTHENTICATION($username,
868
                    $password,
869
                    $radiusservers,
870
                    $clientip,
871
                    $clientmac,
872
                    $ruleno);
873

    
874
    if ($auth_list['auth_val'] == 2) {
875
        captiveportal_logportalauth($username,$clientmac,$clientip,$type);
876
        $sessionid = portal_allow($clientip,
877
                    $clientmac,
878
                    $username,
879
                    $password,
880
                    $auth_list,
881
                    $ruleno);
882
    }
883
    else {
884
        captiveportal_unlock();
885
    }
886

    
887
    return $auth_list;
888

    
889
}
890

    
891
/* read captive portal DB into array */
892
function captiveportal_read_db() {
893

    
894
        global $g;
895

    
896
        $cpdb = array();
897
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
898
        if ($fd) {
899
                while (!feof($fd)) {
900
                        $line = trim(fgets($fd));
901
                        if ($line) {
902
                                $cpdb[] = explode(",", $line);
903
                        }
904
                }
905
                fclose($fd);
906
        }
907
        return $cpdb;
908
}
909

    
910
/* write captive portal DB */
911
function captiveportal_write_db($cpdb) {
912
                 
913
        global $g;
914
                
915
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
916
        if ($fd) { 
917
                foreach ($cpdb as $cpent) {
918
                        fwrite($fd, join(",", $cpent) . "\n");
919
                }       
920
                fclose($fd);
921
        }       
922
}
923

    
924
function captiveportal_write_elements() {
925
    global $g, $config;
926
    
927
    /* delete any existing elements */
928
    if (is_dir($g['captiveportal_element_path'])) {
929
        $dh = opendir($g['captiveportal_element_path']);
930
        while (($file = readdir($dh)) !== false) {
931
            if ($file != "." && $file != "..")
932
                unlink($g['captiveportal_element_path'] . "/" . $file);
933
        }
934
        closedir($dh);
935
    } else {
936
        mkdir($g['captiveportal_element_path']);
937
    }
938
    
939
    if (is_array($config['captiveportal']['element'])) {
940
    
941
        foreach ($config['captiveportal']['element'] as $data) {
942
            $fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
943
            if (!$fd) {
944
                printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
945
                return 1;
946
            }
947
            $decoded = base64_decode($data['content']);
948
            fwrite($fd,$decoded);
949
            fclose($fd);
950
        }
951
    }
952
    
953
    return 0;
954
}
955

    
956
/*
957
 * This function will calculate the lowest free firewall ruleno
958
 * within the range specified based on the actual installed rules
959
 *
960
 */
961

    
962
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 10000, $rulenos_range_max = 9899) {
963

    
964
	$fwrules = "";
965
	$matches = "";
966
	exec("/sbin/ipfw show", $fwrules);
967
	foreach ($fwrules as $fwrule) {
968
		preg_match("/^(\d+)\s+/", $fwrule, $matches);
969
		$rulenos_used[] = $matches[1];
970
	}
971
	$rulenos_used = array_unique($rulenos_used);
972
	$rulenos_range = count($rulenos_used);
973
	if ($rulenos_range > $rulenos_range_max) {
974
		return NULL;
975
	}
976
	$rulenos_pool = range($rulenos_start, ($rulenos_start + $rulenos_range));
977
	$rulenos_free = array_diff($rulenos_pool, $rulenos_used);
978
	$ruleno = array_shift($rulenos_free);
979

    
980
	return $ruleno;
981
}
982

    
983
/**
984
 * This function will calculate the traffic produced by a client
985
 * based on its firewall rule
986
 *
987
 * Point of view: NAS
988
 *
989
 * Input means: from the client
990
 * Output means: to the client
991
 *
992
 */
993

    
994
function getVolume($ruleno) {
995

    
996
    $volume = array();
997

    
998
    // Initialize vars properly, since we don't want NULL vars
999
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1000

    
1001
    // Ingress
1002
    $ipfw = "";
1003
    $matches = "";
1004
    exec("/sbin/ipfw show {$ruleno}", $ipfw);
1005
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[0], $matches);
1006
    $volume['input_pkts'] = $matches[2];
1007
    $volume['input_bytes'] = $matches[3];
1008

    
1009
    // Flush internal buffer
1010
    unset($matches);
1011

    
1012
    // Outgress
1013
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[1], $matches);
1014
    $volume['output_pkts'] = $matches[2];
1015
    $volume['output_bytes'] = $matches[3];
1016

    
1017
    return $volume;
1018
}
1019

    
1020
/**
1021
 * Get the NAS-Identifier
1022
 *
1023
 * We will use our local hostname to make up the nas_id
1024
 */
1025
function getNasID()
1026
{
1027
    $nasId = "";
1028
    exec("/bin/hostname", $nasId);
1029
    if(!$nasId[0])
1030
        $nasId[0] = "pfSense";
1031
    return $nasId[0];
1032
}
1033

    
1034
/**
1035
 * Get the NAS-IP-Address based on the current wan address
1036
 *
1037
 * Use functions in interfaces.inc to find this out
1038
 *
1039
 */
1040

    
1041
function getNasIP()
1042
{
1043
    $nasIp = get_current_wan_address();
1044
    if(!$nasIp)
1045
        $nasIp = "0.0.0.0";
1046
    return $nasIp;
1047
}
1048

    
1049
?>
(5-5/29)