Project

General

Profile

Download (35.5 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
        /* hard timeout? */
465
        if ($timeout) {
466
            if ((time() - $cpdb[$i][0]) >= $timeout) {
467
                $timedout = true;
468
                $term_cause = 5; // Session-Timeout
469
            }
470
        }
471

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

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

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

    
504
        if ($timedout) {
505
            captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
506
            captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
507
            unset($cpdb[$i]);
508
        }
509

    
510
        /* do periodic RADIUS reauthentication? */
511
        if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
512
            ($radiusservers !== false)) {
513

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

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

    
559
            if ($auth_list['auth_val'] == 3) {
560
                captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
561
                captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
562
                unset($cpdb[$i]);
563
            }
564
        }
565
    }
566

    
567
    /* write database */
568
    captiveportal_write_db($cpdb);
569

    
570
    captiveportal_unlock();
571
}
572

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

    
576
	global $g, $config;
577

    
578
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
579

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

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

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

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

    
610
}
611

    
612
/* remove a single client by ipfw rule number */
613
function captiveportal_disconnect_client($id,$term_cause = 1) {
614

    
615
	global $g, $config;
616

    
617
	captiveportal_lock();
618

    
619
	/* read database */
620
	$cpdb = captiveportal_read_db();
621
	$radiusservers = captiveportal_get_radius_servers();
622

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

    
633
	/* write database */
634
	captiveportal_write_db($cpdb);
635

    
636
	captiveportal_unlock();
637
}
638

    
639
/* send RADIUS acct stop for all current clients */
640
function captiveportal_radius_stop_all() {
641
	global $g, $config;
642

    
643
	if (!isset($config['captiveportal']['radacct_enable']))
644
		return;
645

    
646
	captiveportal_lock();
647
	$cpdb = captiveportal_read_db();
648

    
649
	$radiusservers = captiveportal_get_radius_servers();
650

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

    
668
function captiveportal_passthrumac_configure() {
669
	global $config, $g;
670

    
671
	captiveportal_lock();
672

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

    
676
	if (is_array($config['captiveportal']['passthrumac'])) {
677

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

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

    
690
		fclose($fd);
691
	}
692

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

    
711
	captiveportal_unlock();
712

    
713
	return 0;
714
}
715

    
716
function captiveportal_allowedip_configure() {
717
	global $config, $g;
718

    
719
	captiveportal_lock();
720

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

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

    
743
	if (is_array($config['captiveportal']['allowedip'])) {
744

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

    
752
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
753
            /* get next ipfw rule number */
754
            $ruleno = captiveportal_get_next_ipfw_ruleno();
755

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

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

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

    
776
        }
777

    
778
        fclose($fd);
779
    }
780

    
781
    captiveportal_unlock();
782
    return 0;
783
}
784

    
785
/* get last activity timestamp given ipfw rule number */
786
function captiveportal_get_last_activity($ruleno) {
787

    
788
	$ipfwoutput = "";
789

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

    
792
	/* in */
793
	if ($ipfwoutput[0]) {
794
		$ri = explode(" ", $ipfwoutput[0]);
795
		if ($ri[1])
796
			return $ri[1];
797
	}
798

    
799
	return 0;
800
}
801

    
802
/* read RADIUS servers into array */
803
function captiveportal_get_radius_servers() {
804

    
805
        global $g;
806

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

    
821
                        return $radiusservers;
822
                }
823
        }
824

    
825
        return false;
826
}
827

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

    
832
        global $lockfile;
833

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

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

    
861
/* unlock captive portal information file */
862
function captiveportal_unlock() {
863

    
864
        global $lockfile;
865

    
866
        if (file_exists($lockfile))
867
                unlink($lockfile);
868
}
869

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

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

    
893
function radius($username,$password,$clientip,$clientmac,$type) {
894
    global $g, $config;
895

    
896
    /* Start locking from the beginning of an authentication session */
897
    captiveportal_lock();
898

    
899
    $ruleno = captiveportal_get_next_ipfw_ruleno();
900

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

    
910
    $radiusservers = captiveportal_get_radius_servers();
911

    
912
    $auth_list = RADIUS_AUTHENTICATION($username,
913
                    $password,
914
                    $radiusservers,
915
                    $clientip,
916
                    $clientmac,
917
                    $ruleno);
918

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

    
932
    return $auth_list;
933

    
934
}
935

    
936
/* read captive portal DB into array */
937
function captiveportal_read_db() {
938

    
939
        global $g;
940

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

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

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

    
1005
/*
1006
 * This function will calculate the lowest free firewall ruleno
1007
 * within the range specified based on the actual installed rules
1008
 *
1009
 */
1010

    
1011
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 10000, $rulenos_range_max = 9899) {
1012

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

    
1029
	return $ruleno;
1030
}
1031

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

    
1043
function getVolume($ruleno) {
1044

    
1045
    $volume = array();
1046

    
1047
    // Initialize vars properly, since we don't want NULL vars
1048
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1049

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

    
1058
    // Flush internal buffer
1059
    unset($matches);
1060

    
1061
    // Outgress
1062
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[1], $matches);
1063
    $volume['output_pkts'] = $matches[2];
1064
    $volume['output_bytes'] = $matches[3];
1065

    
1066
    return $volume;
1067
}
1068

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

    
1083
/**
1084
 * Get the NAS-IP-Address based on the current wan address
1085
 *
1086
 * Use functions in interfaces.inc to find this out
1087
 *
1088
 */
1089

    
1090
function getNasIP()
1091
{
1092
    $nasIp = get_current_wan_address();
1093
    if(!$nasIp)
1094
        $nasIp = "0.0.0.0";
1095
    return $nasIp;
1096
}
1097

    
1098
?>
(5-5/29)