Project

General

Profile

Download (35.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("globals.inc");
40
require_once("radius_authentication.inc");
41
require_once("radius_accounting.inc");
42
require_once("radius.inc");
43

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

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

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

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

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

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

    
63
		/* make sure ipfw is loaded */
64
		if (!is_module_loaded("ipfw.ko"))
65
			filter_load_ipfw();
66
		if (isset($config['captiveportal']['peruserbw']) && !is_module_loaded("dummynet.ko"))
67
                        mwexec("/sbin/kldload dummynet");
68

    
69
		/* generate ipfw rules */
70
		$cprules = captiveportal_rules_generate();
71

    
72
		/* stop accounting on all clients */
73
		captiveportal_radius_stop_all();
74

    
75
		/* initialize minicron interval value */
76
		$croninterval = $config['captiveportal']['croninterval'] ? $config['captiveportal']['croninterval'] : 60;
77

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

    
81
		/* remove old information */
82
		unlink_if_exists("{$g['vardb_path']}/captiveportal.nextrule");
83
		unlink_if_exists("{$g['vardb_path']}/captiveportal.db");
84
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
85
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip.db");
86
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius.db");
87

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

    
120

    
121

    
122
EOD;
123
		}
124

    
125
		$fd = @fopen("{$g['varetc_path']}/captiveportal.html", "w");
126
		if ($fd) {
127
			fwrite($fd, $htmltext);
128
			fclose($fd);
129
		}
130

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

    
151
EOD;
152
		}
153

    
154
		$fd = @fopen("{$g['varetc_path']}/captiveportal-error.html", "w");
155
		if ($fd) {
156
			fwrite($fd, $errtext);
157
			fclose($fd);
158
		}
159

    
160
		/* write elements */
161
		captiveportal_write_elements();
162

    
163
		/* load rules */
164
		mwexec("/sbin/ipfw -f delete set 1");
165
		mwexec("/sbin/ipfw -f delete set 2");
166
		mwexec("/sbin/ipfw -f delete set 3");
167

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

    
176
		fwrite($fd, $cprules);
177
		fclose($fd);
178

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

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

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

    
186
		chdir($g['captiveportal_path']);
187

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

    
193
		$use_fastcgi = true;
194

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

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

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

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

    
216
		/* start pruning process (interval defaults to 60 seconds) */
217
		mwexec("/usr/local/bin/minicron $croninterval {$g['varrun_path']}/minicron.pid " .
218
			"/etc/rc.prunecaptiveportal");
219

    
220
		/* generate passthru mac database */
221
		captiveportal_passthrumac_configure();
222
		/* create allowed ip database and insert ipfw rules to make it so */
223
		captiveportal_allowedip_configure();
224

    
225
		/* generate radius server database */
226
		if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
227
				($config['captiveportal']['auth_method'] == "radius"))) {
228
			$radiusip = $config['captiveportal']['radiusip'];
229
			$radiusip2 = ($config['captiveportal']['radiusip2']) ? $config['captiveportal']['radiusip2'] : null;
230

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

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

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

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

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

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

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

    
269
		captiveportal_radius_stop_all();
270

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

    
273
		/* unload ipfw */
274
		mwexec("/sbin/kldunload ipfw.ko");
275
	}
276

    
277
    captiveportal_unlock();
278
	
279
	return 0;
280
}
281

    
282
function captiveportal_rules_generate() {
283
	global $config, $g;
284

    
285
	$cpifn = $config['captiveportal']['interface'];
286
	$cpif = get_real_interface($cpifn);
287
	$cpip = get_interface_ip($cpifn);
288
	$lanip = get_interface_ip("lan");
289
	
290
	/* note: the captive portal daemon inserts all pass rules for authenticated
291
	   clients as skipto 50000 rules to make traffic shaping work */
292

    
293
	$cprules =  "add 500 set 1 allow pfsync from any to any\n";
294
	$cprules .= "add 500 set 1 allow carp from any to any\n";
295

    
296
	/*   allow nat redirects to work  see
297
	     http://cvstrac.pfsense.com/tktview?tn=651
298
	 */
299
        /* if list */
300
        $iflist = get_configured_interface_list();
301
	foreach ($iflist as $ifent => $ifname) {
302
		if($cpifn == $ifname)
303
			continue;
304
		$int = get_real_interface($ifname);
305
		$cprules .= "add 30 set 1 skipto 50000 all from any to any in via {$int} keep-state\n";
306
	}
307

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

    
315
EOD;
316
	}
317

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

    
324
# layer 2: pass ARP
325
add 1100 set 1 pass layer2 mac-type arp
326
# pfsense requires for WPA
327
add 1100 set 1 pass layer2 mac-type 0x888e
328
add 1100 set 1 pass layer2 mac-type 0x88c7
329

    
330
# PPP Over Ethernet Discovery Stage
331
add 1100 set 1 pass layer2 mac-type 0x8863
332
# PPP Over Ethernet Session Stage
333
add 1100 set 1 pass layer2 mac-type 0x8864
334
# Allow WPA
335
add 1100 set 1 pass layer2 mac-type 0x888e
336

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

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

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

    
353
# allow access to our DNS forwarder if it incorrectly resolves the hostname to $lanip
354
add 1300 set 1 pass udp from any to $lanip 53 in
355
add 1301 set 1 pass udp from $lanip 53 to any out
356

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

    
361
# allow access to lan web server incase the dns name resolves incorrectly to $lanip
362
add 1302 set 1 pass tcp from any to $lanip 8000 in
363
add 1303 set 1 pass tcp from $lanip 8000 to any out
364

    
365
EOD;
366

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

    
372
EOD;
373
	}
374

    
375
        $cprules .= <<<EOD
376

    
377
# ... 10000-19899: rules per authenticated client go here...
378

    
379
# redirect non-authenticated clients to captive portal
380
add 19902 set 1 fwd 127.0.0.1,8000 tcp from any to any 80 in
381
# let the responses from the captive portal web server back out
382
add 19903 set 1 pass tcp from any 80 to any out
383
# block everything else
384
add 19904 set 1 deny all from any to any
385

    
386
# ... 20000-29899: layer2 block rules per authenticated client go here...
387

    
388
# pass everything else on layer2
389
add 29900 set 1 pass all from any to any layer2
390

    
391
EOD;
392

    
393
    return $cprules;
394
}
395

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

    
400
/* (password is in Base64 and only saved when reauthentication is enabled) */
401
function captiveportal_prune_old() {
402

    
403
    global $g, $config;
404

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

    
411
    if ($config['captiveportal']['idletimeout'])
412
        $idletimeout = $config['captiveportal']['idletimeout'] * 60;
413
    else
414
        $idletimeout = 0;
415

    
416
    if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && !isset($config['captiveportal']['radiussession_timeout']))
417
        return;
418

    
419
    captiveportal_lock();
420

    
421
    /* read database */
422
    $cpdb = captiveportal_read_db();
423

    
424
    $radiusservers = captiveportal_get_radius_servers();
425

    
426
 	/*  To make sure we iterate over ALL accounts on every run the count($cpdb) is moved outside of the loop. Otherwise
427
     *  the loop would evalate count() on every iteration and since $i would increase and count() would decrement they
428
     *  would meet before we had a chance to iterate over all accounts.
429
     */
430
    $no_users = count($cpdb);
431
    for ($i = 0; $i < $no_users; $i++) {
432

    
433
        $timedout = false;
434
        $term_cause = 1;
435

    
436
		/* no pruning for fixed mac address entry */
437
		if (portal_mac_fixed($cpdb[$i][3])) {
438
			continue; // check next value
439
		}
440
        /* hard timeout? */
441
        if ($timeout) {
442
            if ((time() - $cpdb[$i][0]) >= $timeout) {
443
                $timedout = true;
444
                $term_cause = 5; // Session-Timeout
445
            }
446
        }
447

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

    
456
        /* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
457
        $idletimeout = (is_numeric($cpdb[$i][8])) ? $cpdb[$i][8] : $idletimeout;
458
        /* if an idle timeout is specified, get last activity timestamp from ipfw */
459
        if (!$timedout && $idletimeout) {
460
            $lastact = captiveportal_get_last_activity($cpdb[$i][1]);
461
			/*  if the user has logged on but not sent any trafic they will never be logged out.
462
			 *  We "fix" this by setting lastact to the login timestamp 
463
			 */
464
			$lastact = $lastact ? $lastact : $cpdb[$i][0];
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
	mwexec("pfctl -K {$dbent[2]}");
586

    
587
}
588

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

    
592
	global $g, $config;
593

    
594
	captiveportal_lock();
595

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

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

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

    
613
	captiveportal_unlock();
614
}
615

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

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

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

    
626
	$radiusservers = captiveportal_get_radius_servers();
627

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

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

    
648
	captiveportal_lock();
649

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

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

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

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

    
667
		fclose($fd);
668
	}
669

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

    
688
	captiveportal_unlock();
689

    
690
	return 0;
691
}
692

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

    
696
	captiveportal_lock();
697

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

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

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

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

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

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

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

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

    
753
        }
754

    
755
        fclose($fd);
756
    }
757

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

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

    
765
	$ipfwoutput = "";
766

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

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

    
776
	return 0;
777
}
778

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

    
782
        global $g;
783

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

    
798
                        return $radiusservers;
799
                }
800
        }
801

    
802
        return false;
803
}
804

    
805
/* lock captive portal information, decide that the lock file is stale after 
806
   10 minutes and EXIT the process to not risk dataloss, issue warning in syslog every 1 minutes */
807
function captiveportal_lock() {
808

    
809
        global $lockfile;
810

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

    
825
						if(($n % 60) == 0) {
826
						    captiveportal_syslog("LOCKWARNING: waiting for lock for " . $n/60 . " minute/s!");
827
						    if(($n % 600) == 0) {
828
						        captiveportal_syslog("LOCKERROR: waiting for lock for 10 minute/s - EXITING PROCESS!");
829
						        die("Can't get a lock");
830
						    }
831
					    }
832
                }
833
                $n++;
834
        }
835
		/* we never get here */
836
}
837

    
838
/* unlock captive portal information file */
839
function captiveportal_unlock() {
840

    
841
        global $lockfile;
842

    
843
        if (file_exists($lockfile))
844
                unlink($lockfile);
845
}
846

    
847
/* log successful captive portal authentication to syslog */
848
/* part of this code from php.net */
849
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
850
	$message = trim($message);
851
	// Log it
852
	if (!$message)
853
		$message = "$status: $user, $mac, $ip";
854
	else
855
		$message = "$status: $user, $mac, $ip, $message";
856
	captiveportal_syslog($message);
857
	closelog();
858
}
859

    
860
/* log simple messages to syslog */
861
function captiveportal_syslog($message) {
862
	define_syslog_variables();
863
	$message = trim($message);
864
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
865
	// Log it
866
	syslog(LOG_INFO, $message);
867
	closelog();
868
}
869

    
870
function radius($username,$password,$clientip,$clientmac,$type) {
871
    global $g, $config;
872

    
873
    /* Start locking from the beginning of an authentication session */
874
    captiveportal_lock();
875

    
876
    $ruleno = captiveportal_get_next_ipfw_ruleno();
877

    
878
    /* if the pool is empty, return apprioriate message and fail authentication */
879
    if (is_null($ruleno)) {
880
        $auth_list = array();
881
        $auth_list['auth_val'] = 1;
882
        $auth_list['error'] = "System reached maximum login capacity";
883
        captiveportal_unlock();
884
        return $auth_list;
885
    }
886

    
887
    $radiusservers = captiveportal_get_radius_servers();
888

    
889
    $auth_list = RADIUS_AUTHENTICATION($username,
890
                    $password,
891
                    $radiusservers,
892
                    $clientip,
893
                    $clientmac,
894
                    $ruleno);
895

    
896
    if ($auth_list['auth_val'] == 2) {
897
        captiveportal_logportalauth($username,$clientmac,$clientip,$type);
898
        $sessionid = portal_allow($clientip,
899
                    $clientmac,
900
                    $username,
901
                    $password,
902
                    $auth_list,
903
                    $ruleno);
904
    }
905
    else {
906
        captiveportal_unlock();
907
    }
908

    
909
    return $auth_list;
910

    
911
}
912

    
913
/* read captive portal DB into array */
914
function captiveportal_read_db() {
915

    
916
        global $g;
917

    
918
        $cpdb = array();
919
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
920
        if ($fd) {
921
                while (!feof($fd)) {
922
                        $line = trim(fgets($fd));
923
                        if ($line) {
924
                                $cpdb[] = explode(",", $line);
925
                        }
926
                }
927
                fclose($fd);
928
        }
929
        return $cpdb;
930
}
931

    
932
/* write captive portal DB */
933
function captiveportal_write_db($cpdb) {
934
                 
935
        global $g;
936
                
937
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
938
        if ($fd) { 
939
                foreach ($cpdb as $cpent) {
940
                        fwrite($fd, join(",", $cpent) . "\n");
941
                }       
942
                fclose($fd);
943
        }       
944
}
945

    
946
function captiveportal_write_elements() {
947
    global $g, $config;
948
    
949
    /* delete any existing elements */
950
    if (is_dir($g['captiveportal_element_path'])) {
951
        $dh = opendir($g['captiveportal_element_path']);
952
        while (($file = readdir($dh)) !== false) {
953
            if ($file != "." && $file != "..")
954
                unlink($g['captiveportal_element_path'] . "/" . $file);
955
        }
956
        closedir($dh);
957
    } else {
958
        @mkdir($g['captiveportal_element_path']);
959
    }
960
    
961
	if (is_array($config['captiveportal']['element'])) {
962
		conf_mount_rw();
963
		foreach ($config['captiveportal']['element'] as $data) {
964
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
965
			if (!$fd) {
966
				printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
967
				return 1;
968
			}
969
			$decoded = base64_decode($data['content']);
970
			fwrite($fd,$decoded);
971
			fclose($fd);
972
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
973
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
974
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
975
		}
976
		conf_mount_ro();
977
	}
978
    
979
    return 0;
980
}
981

    
982
/*
983
 * This function will calculate the lowest free firewall ruleno
984
 * within the range specified based on the actual installed rules
985
 *
986
 */
987

    
988
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 10000, $rulenos_range_max = 9899) {
989

    
990
	$fwrules = "";
991
	$matches = "";
992
	exec("/sbin/ipfw show", $fwrules);
993
	foreach ($fwrules as $fwrule) {
994
		preg_match("/^(\d+)\s+/", $fwrule, $matches);
995
		$rulenos_used[] = $matches[1];
996
	}
997
	$rulenos_used = array_unique($rulenos_used);
998
	$rulenos_range = count($rulenos_used);
999
	if ($rulenos_range > $rulenos_range_max) {
1000
		return NULL;
1001
	}
1002
	$rulenos_pool = range($rulenos_start, ($rulenos_start + $rulenos_range));
1003
	$rulenos_free = array_diff($rulenos_pool, $rulenos_used);
1004
	$ruleno = array_shift($rulenos_free);
1005

    
1006
	return $ruleno;
1007
}
1008

    
1009
/**
1010
 * This function will calculate the traffic produced by a client
1011
 * based on its firewall rule
1012
 *
1013
 * Point of view: NAS
1014
 *
1015
 * Input means: from the client
1016
 * Output means: to the client
1017
 *
1018
 */
1019

    
1020
function getVolume($ruleno) {
1021

    
1022
    $volume = array();
1023

    
1024
    // Initialize vars properly, since we don't want NULL vars
1025
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1026

    
1027
    // Ingress
1028
    $ipfw = "";
1029
    $matches = "";
1030
    exec("/sbin/ipfw show {$ruleno}", $ipfw);
1031
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[0], $matches);
1032
    $volume['input_pkts'] = $matches[2];
1033
    $volume['input_bytes'] = $matches[3];
1034

    
1035
    // Flush internal buffer
1036
    unset($matches);
1037

    
1038
    // Outgress
1039
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[1], $matches);
1040
    $volume['output_pkts'] = $matches[2];
1041
    $volume['output_bytes'] = $matches[3];
1042

    
1043
    return $volume;
1044
}
1045

    
1046
/**
1047
 * Get the NAS-Identifier
1048
 *
1049
 * We will use our local hostname to make up the nas_id
1050
 */
1051
function getNasID()
1052
{
1053
    $nasId = "";
1054
    exec("/bin/hostname", $nasId);
1055
    if(!$nasId[0])
1056
        $nasId[0] = "{$g['product_name']}";
1057
    return $nasId[0];
1058
}
1059

    
1060
/**
1061
 * Get the NAS-IP-Address based on the current wan address
1062
 *
1063
 * Use functions in interfaces.inc to find this out
1064
 *
1065
 */
1066

    
1067
function getNasIP()
1068
{
1069
    $nasIp = get_interface_ip();
1070
    if(!$nasIp)
1071
        $nasIp = "0.0.0.0";
1072
    return $nasIp;
1073
}
1074

    
1075
function portal_mac_fixed($clientmac) {
1076
    global $g ;
1077

    
1078
    /* open captive portal mac db */
1079
    if (file_exists("{$g['vardb_path']}/captiveportal_mac.db")) {
1080
        $fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db","r") ;
1081
        if (!$fd) {
1082
            return FALSE;
1083
        }
1084
        while (!feof($fd)) {
1085
            $mac = trim(fgets($fd)) ;
1086
            if(strcasecmp($clientmac, $mac) == 0) {
1087
                fclose($fd) ;
1088
                return TRUE ;
1089
            }
1090
        }
1091
        fclose($fd) ;
1092
    }
1093
    return FALSE ;
1094
}
1095

    
1096
?>
(6-6/40)