Project

General

Profile

Download (36.1 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	captiveportal.inc
4
	part of m0n0wall (http://m0n0.ch/wall)
5

    
6
	Copyright (C) 2003-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
		/* generate ipfw rules */
64
		$cprules = captiveportal_rules_generate();
65

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

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

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

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

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

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

    
117

    
118

    
119
EOD;
120
		}
121

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

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

    
148
EOD;
149
		}
150

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

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

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

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

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

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

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

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

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

    
185
		if ($config['captiveportal']['maxproc'])
186
			$maxproc = $config['captiveportal']['maxproc'];
187
		else
188
			$maxproc = 16;
189

    
190
		$use_fastcgi = true;
191

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
266
		captiveportal_radius_stop_all();
267

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

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

    
288
    captiveportal_unlock();
289
	
290
	return 0;
291
}
292

    
293
function captiveportal_rules_generate() {
294
	global $config, $g;
295

    
296
	$cpifn = $config['captiveportal']['interface'];
297
	$cpif = $config['interfaces'][$cpifn]['if'];
298
	$cpip = $config['interfaces'][$cpifn]['ipaddr'];
299
	$lanip = $config['interfaces']['lan']['ipaddr'];
300
	
301
	/* note: the captive portal daemon inserts all pass rules for authenticated
302
	   clients as skipto 50000 rules to make traffic shaping work */
303

    
304
	$cprules =  "add 500 set 1 allow pfsync from any to any\n";
305
	$cprules .= "add 500 set 1 allow carp from any to any\n";
306

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

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

    
328
EOD;
329
	}
330

    
331
	$cprules .= <<<EOD
332
# skip to traffic shaper if not on captive portal interface
333
add 1000 set 1 skipto 50000 all from any to any not layer2 not via $cpif
334
# pass all layer2 traffic on other interfaces
335
add 1001 set 1 pass layer2 not via $cpif
336

    
337
# layer 2: pass ARP
338
add 1100 set 1 pass layer2 mac-type arp
339
# pfsense requires for WPA
340
add 1100 set 1 pass layer2 mac-type 0x888e
341
add 1100 set 1 pass layer2 mac-type 0x88c7
342

    
343
# PPP Over Ethernet Discovery Stage
344
add 1100 set 1 pass layer2 mac-type 0x8863
345
# PPP Over Ethernet Session Stage
346
add 1100 set 1 pass layer2 mac-type 0x8864
347

    
348
# layer 2: block anything else non-IP
349
add 1101 set 1 deny layer2 not mac-type ip
350
# layer 2: check if MAC addresses of authenticated clients are correct
351
add 1102 set 1 skipto 20000 layer2
352

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

    
360
# allow access to our DNS forwarder
361
add 1300 set 1 pass udp from any to $cpip 53 in
362
add 1301 set 1 pass udp from $cpip 53 to any out
363

    
364
# allow access to our DNS forwarder if it incorrectly resolves the hostname to $lanip
365
add 1300 set 1 pass udp from any to $lanip 53 in
366
add 1301 set 1 pass udp from $lanip 53 to any out
367

    
368
# allow access to our web server
369
add 1302 set 1 pass tcp from any to $cpip 8000 in
370
add 1303 set 1 pass tcp from $cpip 8000 to any out
371

    
372
# allow access to lan web server incase the dns name resolves incorrectly to $lanip
373
add 1302 set 1 pass tcp from any to $lanip 8000 in
374
add 1303 set 1 pass tcp from $lanip 8000 to any out
375

    
376
EOD;
377

    
378
	if (isset($config['captiveportal']['httpslogin'])) {
379
		$cprules .= <<<EOD
380
add 1304 set 1 pass tcp from any to $cpip 8001 in
381
add 1305 set 1 pass tcp from $cpip 8001 to any out
382
add 1306 set 1 pass tcp from any to $lanip 8001 in
383
add 1307 set 1 pass tcp from $lanip 8001 to any out
384

    
385
EOD;
386
	}
387

    
388
        $cprules .= <<<EOD
389
#PPPoE Discovery Stage
390
add 1100 set 1 pass layer2 mac-type 0x8863
391
#PPPoE Session Stage
392
add 1100 set 1 pass layer2 mac-type 0x8864
393

    
394
EOD;
395

    
396
        $cprules .= <<<EOD
397
# Allow WPA
398
add 1100 set 1 pass layer2 mac-type 0x888e
399

    
400
EOD;
401

    
402

    
403
    $cprules .= <<<EOD
404

    
405
# ... 10000-19899: rules per authenticated client go here...
406

    
407
# redirect non-authenticated clients to captive portal
408
add 19902 set 1 fwd 127.0.0.1,8000 tcp from any to any 80 in
409
# let the responses from the captive portal web server back out
410
add 19903 set 1 pass tcp from any 80 to any out
411
# block everything else
412
add 19904 set 1 deny all from any to any
413

    
414
# ... 20000-29899: layer2 block rules per authenticated client go here...
415

    
416
# pass everything else on layer2
417
add 29900 set 1 pass all from any to any layer2
418

    
419
EOD;
420

    
421
    return $cprules;
422
}
423

    
424
/* remove clients that have been around for longer than the specified amount of time */
425
/* db file structure:
426
timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time */
427

    
428
/* (password is in Base64 and only saved when reauthentication is enabled) */
429
function captiveportal_prune_old() {
430

    
431
    global $g, $config;
432

    
433
    /* check for expired entries */
434
    if ($config['captiveportal']['timeout'])
435
        $timeout = $config['captiveportal']['timeout'] * 60;
436
    else
437
        $timeout = 0;
438

    
439
    if ($config['captiveportal']['idletimeout'])
440
        $idletimeout = $config['captiveportal']['idletimeout'] * 60;
441
    else
442
        $idletimeout = 0;
443

    
444
    if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && !isset($config['captiveportal']['radiussession_timeout']))
445
        return;
446

    
447
    captiveportal_lock();
448

    
449
    /* read database */
450
    $cpdb = captiveportal_read_db();
451

    
452
    $radiusservers = captiveportal_get_radius_servers();
453

    
454
 	/*  To make sure we iterate over ALL accounts on every run the count($cpdb) is moved outside of the loop. Otherwise
455
     *  the loop would evalate count() on every iteration and since $i would increase and count() would decrement they
456
     *  would meet before we had a chance to iterate over all accounts.
457
     */
458
    $no_users = count($cpdb);
459
    for ($i = 0; $i < $no_users; $i++) {
460

    
461
        $timedout = false;
462
        $term_cause = 1;
463

    
464
		/* no pruning for fixed mac address entry */
465
		if (portal_mac_fixed($cpdb[$i][3])) {
466
			continue; // check next value
467
		}
468
        /* hard timeout? */
469
        if ($timeout) {
470
            if ((time() - $cpdb[$i][0]) >= $timeout) {
471
                $timedout = true;
472
                $term_cause = 5; // Session-Timeout
473
            }
474
        }
475

    
476
        /* Session-Terminate-Time */
477
        if (!$timedout && !empty($cpdb[$i][9])) {
478
            if (time() >= $cpdb[$i][9]) {
479
                $timedout = true;
480
                $term_cause = 5; // Session-Timeout
481
            }
482
        }
483

    
484
        /* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
485
        $idletimeout = (is_numeric($cpdb[$i][8])) ? $cpdb[$i][8] : $idletimeout;
486
        /* if an idle timeout is specified, get last activity timestamp from ipfw */
487
        if (!$timedout && $idletimeout) {
488
            $lastact = captiveportal_get_last_activity($cpdb[$i][1]);
489
			/*  if the user has logged on but not sent any trafic they will never be logged out.
490
			 *  We "fix" this by setting lastact to the login timestamp 
491
			 */
492
			$lastact = $lastact ? $lastact : $cpdb[$i][0];
493
            if ($lastact && ((time() - $lastact) >= $idletimeout)) {
494
                $timedout = true;
495
                $term_cause = 4; // Idle-Timeout
496
                $stop_time = $lastact; // Entry added to comply with WISPr
497
            }
498
        }
499

    
500
        /* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
501
        if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpdb[$i][7])) {
502
            if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
503
                $timedout = true;
504
                $term_cause = 5; // Session-Timeout
505
            }
506
        }
507

    
508
        if ($timedout) {
509
            captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
510
            captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
511
            unset($cpdb[$i]);
512
        }
513

    
514
        /* do periodic RADIUS reauthentication? */
515
        if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
516
            ($radiusservers !== false)) {
517

    
518
            if (isset($config['captiveportal']['radacct_enable'])) {
519
                if ($config['captiveportal']['reauthenticateacct'] == "stopstart") {
520
                    /* stop and restart accounting */
521
                    RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
522
                                           $cpdb[$i][4], // username
523
                                           $cpdb[$i][5], // sessionid
524
                                           $cpdb[$i][0], // start time
525
                                           $radiusservers[0]['ipaddr'],
526
                                           $radiusservers[0]['acctport'],
527
                                           $radiusservers[0]['key'],
528
                                           $cpdb[$i][2], // clientip
529
                                           $cpdb[$i][3], // clientmac
530
                                           10); // NAS Request
531
                    exec("/sbin/ipfw zero {$cpdb[$i][1]}");
532
                    RADIUS_ACCOUNTING_START($cpdb[$i][1], // ruleno
533
                                            $cpdb[$i][4], // username
534
                                            $cpdb[$i][5], // sessionid
535
                                            $radiusservers[0]['ipaddr'],
536
                                            $radiusservers[0]['acctport'],
537
                                            $radiusservers[0]['key'],
538
                                            $cpdb[$i][2], // clientip
539
                                            $cpdb[$i][3]); // clientmac
540
                } else if ($config['captiveportal']['reauthenticateacct'] == "interimupdate") {
541
                    RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
542
                                           $cpdb[$i][4], // username
543
                                           $cpdb[$i][5], // sessionid
544
                                           $cpdb[$i][0], // start time
545
                                           $radiusservers[0]['ipaddr'],
546
                                           $radiusservers[0]['acctport'],
547
                                           $radiusservers[0]['key'],
548
                                           $cpdb[$i][2], // clientip
549
                                           $cpdb[$i][3], // clientmac
550
                                           10, // NAS Request
551
                                           true); // Interim Updates
552
                }
553
            }
554

    
555
            /* check this user against RADIUS again */
556
            $auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
557
                                          base64_decode($cpdb[$i][6]), // password
558
                                            $radiusservers,
559
                                          $cpdb[$i][2], // clientip
560
                                          $cpdb[$i][3], // clientmac
561
                                          $cpdb[$i][1]); // ruleno
562

    
563
            if ($auth_list['auth_val'] == 3) {
564
                captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
565
                captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
566
                unset($cpdb[$i]);
567
            }
568
        }
569
    }
570

    
571
    /* write database */
572
    captiveportal_write_db($cpdb);
573

    
574
    captiveportal_unlock();
575
}
576

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

    
580
	global $g, $config;
581

    
582
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
583

    
584
	/* this client needs to be deleted - remove ipfw rules */
585
	if (isset($config['captiveportal']['radacct_enable']) && isset($radiusservers[0])) {
586
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
587
							   $dbent[4], // username
588
							   $dbent[5], // sessionid
589
							   $dbent[0], // start time
590
							   $radiusservers[0]['ipaddr'],
591
							   $radiusservers[0]['acctport'],
592
							   $radiusservers[0]['key'],
593
							   $dbent[2], // clientip
594
							   $dbent[3], // clientmac
595
							   $term_cause, // Acct-Terminate-Cause
596
							   false,
597
							   $stop_time);
598
	}
599

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

    
602
    /* We need to delete +40500 and +45500 as well...
603
     * these are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
604
     * We could get an error if the pipe doesn't exist but everything should still be fine
605
     */
606
    if (isset($config['captiveportal']['peruserbw'])) {
607
        mwexec("/sbin/ipfw pipe " . ($dbent[1]+40500) . " delete");
608
        mwexec("/sbin/ipfw pipe " . ($dbent[1]+45500) . " delete");
609
    }
610

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

    
614
}
615

    
616
/* remove a single client by ipfw rule number */
617
function captiveportal_disconnect_client($id,$term_cause = 1) {
618

    
619
	global $g, $config;
620

    
621
	captiveportal_lock();
622

    
623
	/* read database */
624
	$cpdb = captiveportal_read_db();
625
	$radiusservers = captiveportal_get_radius_servers();
626

    
627
	/* find entry */
628
	for ($i = 0; $i < count($cpdb); $i++) {
629
		if ($cpdb[$i][1] == $id) {
630
			captiveportal_disconnect($cpdb[$i], $radiusservers, $term_cause);
631
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "DISCONNECT");
632
			unset($cpdb[$i]);
633
			break;
634
		}
635
	}
636

    
637
	/* write database */
638
	captiveportal_write_db($cpdb);
639

    
640
	captiveportal_unlock();
641
}
642

    
643
/* send RADIUS acct stop for all current clients */
644
function captiveportal_radius_stop_all() {
645
	global $g, $config;
646

    
647
	if (!isset($config['captiveportal']['radacct_enable']))
648
		return;
649

    
650
	captiveportal_lock();
651
	$cpdb = captiveportal_read_db();
652

    
653
	$radiusservers = captiveportal_get_radius_servers();
654

    
655
	if (isset($radiusservers[0])) {
656
		for ($i = 0; $i < count($cpdb); $i++) {
657
			RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
658
								   $cpdb[$i][4], // username
659
								   $cpdb[$i][5], // sessionid
660
								   $cpdb[$i][0], // start time
661
								   $radiusservers[0]['ipaddr'],
662
								   $radiusservers[0]['acctport'],
663
								   $radiusservers[0]['key'],
664
								   $cpdb[$i][2], // clientip
665
								   $cpdb[$i][3], // clientmac
666
								   7); // Admin Reboot
667
		}
668
	}
669
	captiveportal_unlock();
670
}
671

    
672
function captiveportal_passthrumac_configure() {
673
	global $config, $g;
674

    
675
	captiveportal_lock();
676

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

    
680
	if (is_array($config['captiveportal']['passthrumac'])) {
681

    
682
		$fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db", "w");
683
		if (!$fd) {
684
			printf("Error: cannot open passthru mac DB file in captiveportal_passthrumac_configure().\n");
685
			captiveportal_unlock();
686
			return 1;
687
		}
688

    
689
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
690
			/* record passthru mac so it can be recognized and let thru */
691
			fwrite($fd, $macent['mac'] . "\n");
692
		}
693

    
694
		fclose($fd);
695
	}
696

    
697
	/*    pfSense:
698
	 * 	  pass through mac entries should always exist.  the reason
699
	 *    for this is because we do not have native mac address filtering
700
	 *    mechanisms.  this allows us to filter by mac address easily
701
	 *    and get around this limitation.   I consider this a bug in
702
	 *    m0n0wall and pfSense as m0n0wall does not have native mac
703
	 *    filtering mechanisms as well. -Scott Ullrich
704
	 */
705
	if (is_array($config['captiveportal']['passthrumac'])) {
706
		mwexec("/sbin/ipfw delete 50");
707
		foreach($config['captiveportal']['passthrumac'] as $ptm) {
708
			/* create the pass through mac entry */
709
			//system("echo /sbin/ipfw add 50 skipto 65535 ip from any to any MAC {$ptm['mac']} any > /tmp/cp");
710
			mwexec("/sbin/ipfw add 50 skipto 29900 ip from any to any MAC {$ptm['mac']} any keep-state");
711
			mwexec("/sbin/ipfw add 50 skipto 29900 ip from any to any MAC any {$ptm['mac']} keep-state");
712
		}
713
	}
714

    
715
	captiveportal_unlock();
716

    
717
	return 0;
718
}
719

    
720
function captiveportal_allowedip_configure() {
721
	global $config, $g;
722

    
723
	captiveportal_lock();
724

    
725
	/* clear out existing allowed ips, if necessary */
726
	if (file_exists("{$g['vardb_path']}/captiveportal_ip.db")) {
727
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "r");
728
		if ($fd) {
729
			while (!feof($fd)) {
730
				$line = trim(fgets($fd));
731
				if ($line) {
732
					list($ip,$rule) = explode(",",$line);
733
					mwexec("/sbin/ipfw delete $rule");
734
				}
735
			}
736
		}
737
		fclose($fd);
738
		unlink("{$g['vardb_path']}/captiveportal_ip.db");
739
	}
740

    
741
	/* get next ipfw rule number */
742
	if (file_exists("{$g['vardb_path']}/captiveportal.nextrule"))
743
		$ruleno = trim(file_get_contents("{$g['vardb_path']}/captiveportal.nextrule"));
744
	if (!$ruleno)
745
		$ruleno = 10000;	/* first rule number */
746

    
747
	if (is_array($config['captiveportal']['allowedip'])) {
748

    
749
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "w");
750
		if (!$fd) {
751
			printf("Error: cannot open allowed ip DB file in captiveportal_allowedip_configure().\n");
752
			captiveportal_unlock();
753
			return 1;
754
		}
755

    
756
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
757
            /* get next ipfw rule number */
758
            $ruleno = captiveportal_get_next_ipfw_ruleno();
759

    
760
            /* if the pool is empty, return apprioriate message and fail */
761
            if (is_null($ruleno)) {
762
                printf("Error: system reached maximum login capacity, no free FW rulenos in captiveportal_allowedip_configure().\n");
763
                fclose($fd);
764
                captiveportal_unlock();
765
                return 1;
766
            }
767

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

    
771
            /* insert ipfw rule to allow ip thru */
772
            if ($ipent['dir'] == "from") {
773
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any in");
774
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " out");
775
            } else {
776
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " in");
777
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any out");
778
            }
779

    
780
        }
781

    
782
        fclose($fd);
783
    }
784

    
785
    captiveportal_unlock();
786
    return 0;
787
}
788

    
789
/* get last activity timestamp given ipfw rule number */
790
function captiveportal_get_last_activity($ruleno) {
791

    
792
	$ipfwoutput = "";
793

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

    
796
	/* in */
797
	if ($ipfwoutput[0]) {
798
		$ri = explode(" ", $ipfwoutput[0]);
799
		if ($ri[1])
800
			return $ri[1];
801
	}
802

    
803
	return 0;
804
}
805

    
806
/* read RADIUS servers into array */
807
function captiveportal_get_radius_servers() {
808

    
809
        global $g;
810

    
811
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
812
                $fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db","r");
813
                if ($fd) {
814
                        $radiusservers = array();
815
                        while (!feof($fd)) {
816
                                $line = trim(fgets($fd));
817
                                if ($line) {
818
                                        $radsrv = array();
819
                                        list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
820
                                        $radiusservers[] = $radsrv;
821
                                }
822
                        }
823
                        fclose($fd);
824

    
825
                        return $radiusservers;
826
                }
827
        }
828

    
829
        return false;
830
}
831

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

    
836
        global $lockfile;
837

    
838
        $n = 1;
839
        while ($n) {
840
                /* open the lock file in append mode to avoid race condition */
841
                if ($fd = @fopen($lockfile, "x")) {
842
                        /* succeeded */
843
                        fclose($fd);
844
						if($n > 10) {
845
						    captiveportal_syslog("LOCKINFO: Waiting for lock for $n seconds/s!");
846
						}
847
                        return;
848
                } else {
849
                        /* file locked, wait and try again */
850
                        sleep(1);
851

    
852
						if(($n % 60) == 0) {
853
						    captiveportal_syslog("LOCKWARNING: waiting for lock for " . $n/60 . " minute/s!");
854
						    if(($n % 600) == 0) {
855
						        captiveportal_syslog("LOCKERROR: waiting for lock for 10 minute/s - EXITING PROCESS!");
856
						        die("Can't get a lock");
857
						    }
858
					    }
859
                }
860
                $n++;
861
        }
862
		/* we never get here */
863
}
864

    
865
/* unlock captive portal information file */
866
function captiveportal_unlock() {
867

    
868
        global $lockfile;
869

    
870
        if (file_exists($lockfile))
871
                unlink($lockfile);
872
}
873

    
874
/* log successful captive portal authentication to syslog */
875
/* part of this code from php.net */
876
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
877
	$message = trim($message);
878
	// Log it
879
	if (!$message)
880
		$message = "$status: $user, $mac, $ip";
881
	else
882
		$message = "$status: $user, $mac, $ip, $message";
883
	captiveportal_syslog($message);
884
	closelog();
885
}
886

    
887
/* log simple messages to syslog */
888
function captiveportal_syslog($message) {
889
	define_syslog_variables();
890
	$message = trim($message);
891
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
892
	// Log it
893
	syslog(LOG_INFO, $message);
894
	closelog();
895
}
896

    
897
function radius($username,$password,$clientip,$clientmac,$type) {
898
    global $g, $config;
899

    
900
    /* Start locking from the beginning of an authentication session */
901
    captiveportal_lock();
902

    
903
    $ruleno = captiveportal_get_next_ipfw_ruleno();
904

    
905
    /* if the pool is empty, return apprioriate message and fail authentication */
906
    if (is_null($ruleno)) {
907
        $auth_list = array();
908
        $auth_list['auth_val'] = 1;
909
        $auth_list['error'] = "System reached maximum login capacity";
910
        captiveportal_unlock();
911
        return $auth_list;
912
    }
913

    
914
    $radiusservers = captiveportal_get_radius_servers();
915

    
916
    $auth_list = RADIUS_AUTHENTICATION($username,
917
                    $password,
918
                    $radiusservers,
919
                    $clientip,
920
                    $clientmac,
921
                    $ruleno);
922

    
923
    if ($auth_list['auth_val'] == 2) {
924
        captiveportal_logportalauth($username,$clientmac,$clientip,$type);
925
        $sessionid = portal_allow($clientip,
926
                    $clientmac,
927
                    $username,
928
                    $password,
929
                    $auth_list,
930
                    $ruleno);
931
    }
932
    else {
933
        captiveportal_unlock();
934
    }
935

    
936
    return $auth_list;
937

    
938
}
939

    
940
/* read captive portal DB into array */
941
function captiveportal_read_db() {
942

    
943
        global $g;
944

    
945
        $cpdb = array();
946
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
947
        if ($fd) {
948
                while (!feof($fd)) {
949
                        $line = trim(fgets($fd));
950
                        if ($line) {
951
                                $cpdb[] = explode(",", $line);
952
                        }
953
                }
954
                fclose($fd);
955
        }
956
        return $cpdb;
957
}
958

    
959
/* write captive portal DB */
960
function captiveportal_write_db($cpdb) {
961
                 
962
        global $g;
963
                
964
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
965
        if ($fd) { 
966
                foreach ($cpdb as $cpent) {
967
                        fwrite($fd, join(",", $cpent) . "\n");
968
                }       
969
                fclose($fd);
970
        }       
971
}
972

    
973
function captiveportal_write_elements() {
974
    global $g, $config;
975
    
976
    /* delete any existing elements */
977
    if (is_dir($g['captiveportal_element_path'])) {
978
        $dh = opendir($g['captiveportal_element_path']);
979
        while (($file = readdir($dh)) !== false) {
980
            if ($file != "." && $file != "..")
981
                unlink($g['captiveportal_element_path'] . "/" . $file);
982
        }
983
        closedir($dh);
984
    } else {
985
        mkdir($g['captiveportal_element_path']);
986
    }
987
    
988
	if (is_array($config['captiveportal']['element'])) {
989
		conf_mount_rw();
990
		foreach ($config['captiveportal']['element'] as $data) {
991
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
992
			if (!$fd) {
993
				printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
994
				return 1;
995
			}
996
			$decoded = base64_decode($data['content']);
997
			fwrite($fd,$decoded);
998
			fclose($fd);
999
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1000
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1001
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
1002
		}
1003
		conf_mount_ro();
1004
	}
1005
    
1006
    return 0;
1007
}
1008

    
1009
/*
1010
 * This function will calculate the lowest free firewall ruleno
1011
 * within the range specified based on the actual installed rules
1012
 *
1013
 */
1014

    
1015
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 10000, $rulenos_range_max = 9899) {
1016

    
1017
	$fwrules = "";
1018
	$matches = "";
1019
	exec("/sbin/ipfw show", $fwrules);
1020
	foreach ($fwrules as $fwrule) {
1021
		preg_match("/^(\d+)\s+/", $fwrule, $matches);
1022
		$rulenos_used[] = $matches[1];
1023
	}
1024
	$rulenos_used = array_unique($rulenos_used);
1025
	$rulenos_range = count($rulenos_used);
1026
	if ($rulenos_range > $rulenos_range_max) {
1027
		return NULL;
1028
	}
1029
	$rulenos_pool = range($rulenos_start, ($rulenos_start + $rulenos_range));
1030
	$rulenos_free = array_diff($rulenos_pool, $rulenos_used);
1031
	$ruleno = array_shift($rulenos_free);
1032

    
1033
	return $ruleno;
1034
}
1035

    
1036
/**
1037
 * This function will calculate the traffic produced by a client
1038
 * based on its firewall rule
1039
 *
1040
 * Point of view: NAS
1041
 *
1042
 * Input means: from the client
1043
 * Output means: to the client
1044
 *
1045
 */
1046

    
1047
function getVolume($ruleno) {
1048

    
1049
    $volume = array();
1050

    
1051
    // Initialize vars properly, since we don't want NULL vars
1052
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1053

    
1054
    // Ingress
1055
    $ipfw = "";
1056
    $matches = "";
1057
    exec("/sbin/ipfw show {$ruleno}", $ipfw);
1058
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[0], $matches);
1059
    $volume['input_pkts'] = $matches[2];
1060
    $volume['input_bytes'] = $matches[3];
1061

    
1062
    // Flush internal buffer
1063
    unset($matches);
1064

    
1065
    // Outgress
1066
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[1], $matches);
1067
    $volume['output_pkts'] = $matches[2];
1068
    $volume['output_bytes'] = $matches[3];
1069

    
1070
    return $volume;
1071
}
1072

    
1073
/**
1074
 * Get the NAS-Identifier
1075
 *
1076
 * We will use our local hostname to make up the nas_id
1077
 */
1078
function getNasID()
1079
{
1080
    $nasId = "";
1081
    exec("/bin/hostname", $nasId);
1082
    if(!$nasId[0])
1083
        $nasId[0] = "{$g['product_name']}";
1084
    return $nasId[0];
1085
}
1086

    
1087
/**
1088
 * Get the NAS-IP-Address based on the current wan address
1089
 *
1090
 * Use functions in interfaces.inc to find this out
1091
 *
1092
 */
1093

    
1094
function getNasIP()
1095
{
1096
    $nasIp = get_current_wan_address();
1097
    if(!$nasIp)
1098
        $nasIp = "0.0.0.0";
1099
    return $nasIp;
1100
}
1101

    
1102
function portal_mac_fixed($clientmac) {
1103
	global $g ;
1104

    
1105
	/* open captive portal mac db */
1106
	if (file_exists("{$g['vardb_path']}/captiveportal_mac.db")) {
1107
		$fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db","r") ;
1108
		if (!$fd) {
1109
			return FALSE;
1110
		}
1111
		while (!feof($fd)) {
1112
			$mac = trim(fgets($fd)) ;
1113
			if(strcasecmp($clientmac, $mac) == 0) {
1114
				fclose($fd) ;
1115
				return TRUE ;
1116
			}
1117
		}
1118
		fclose($fd) ;
1119
	}
1120
	return FALSE ;
1121
}
1122

    
1123
?>
(5-5/29)