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("globals.inc");
39
require_once("util.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
	$captiveportallck = lock('captiveportal');
50
	
51
	$cpactive = false;
52
	if (isset($config['captiveportal']['enable'])) {
53
		$cpips = array();
54
		$ifaces = get_configured_interface_list();
55
		$cpinterfaces = explode(",", $config['captiveportal']['interface']);
56
		$firsttime = 0;
57
		foreach ($cpinterfaces as $cpifgrp) {
58
			if (!isset($ifaces[$cpifgrp]))
59
				continue;
60
			$tmpif = get_real_interface($cpifgrp);
61
			if (!empty($tmpif)) {
62
				if ($firsttime > 0)
63
					$cpinterface .= " or ";
64
				$cpinterface .= "via {$tmpif}"; 
65
				$firsttime = 1;
66
				$cpipm = get_interface_ip($cpifgrp);
67
				if (is_ipaddr($cpipm))
68
					$cpips[] = $cpipm;
69
			}
70
		}
71
		if (count($cpips) > 0) {
72
			$cpactive = true;
73
			$cpinterface = "{ {$cpinterface} } ";
74
		}
75
	}
76

    
77
	if ($cpactive == true) {
78

    
79
		if ($g['booting'])
80
			echo "Starting captive portal... ";
81

    
82
		/* kill any running mini_httpd */
83
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
84
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal-SSL.pid");
85

    
86
		/* kill any running minicron */
87
		killbypid("{$g['varrun_path']}/minicron.pid");
88

    
89
		/* make sure ipfw is loaded */
90
		if (!is_module_loaded("ipfw.ko"))
91
			filter_load_ipfw();
92
		if (isset($config['captiveportal']['peruserbw']) && !is_module_loaded("dummynet.ko"))
93
                        mwexec("/sbin/kldload dummynet");
94

    
95
		/* generate ipfw rules */
96
		$cprules = captiveportal_rules_generate($cpinterface, $cpips);
97

    
98
		/* stop accounting on all clients */
99
		captiveportal_radius_stop_all(true);
100

    
101
		/* initialize minicron interval value */
102
		$croninterval = $config['captiveportal']['croninterval'] ? $config['captiveportal']['croninterval'] : 60;
103

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

    
107
		/* remove old information */
108
		unlink_if_exists("{$g['vardb_path']}/captiveportal.nextrule");
109
		unlink_if_exists("{$g['vardb_path']}/captiveportal.db");
110
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
111
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip.db");
112
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius.db");
113

    
114
		/* write portal page */
115
		if ($config['captiveportal']['page']['htmltext'])
116
			$htmltext = base64_decode($config['captiveportal']['page']['htmltext']);
117
		else {
118
			/* example/template page */
119
			$htmltext = <<<EOD
120
<html>
121
<head>
122
<title>{$g['product_name']} captive portal</title>
123
</head>
124
<body>
125
<center>
126
<h2>{$g['product_name']} captive portal</h2>
127
Welcome to the {$g['product_name']} Captive Portal!  This is the default page since a custom page has not been defined.
128
<p>
129
<form method="post" action="\$PORTAL_ACTION\$">
130
<input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$">
131
<table>
132
   <tr><td>Username:</td><td><input name="auth_user" type="text"></td></tr>
133
   <tr><td>Password:</td><td><input name="auth_pass" type="password"></td></tr>
134
   <tr><td>&nbsp;</td></tr>
135
   <tr>
136
     <td colspan="2">
137
	<center><input name="accept" type="submit" value="Continue"></center>
138
     </td>
139
   </tr>
140
</table>
141
</center>
142
</form>
143
</body>
144
</html>
145

    
146

    
147

    
148
EOD;
149
		}
150

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

    
157
		/* write error page */
158
		if ($config['captiveportal']['page']['errtext'])
159
			$errtext = base64_decode($config['captiveportal']['page']['errtext']);
160
		else {
161
			/* example page */
162
			$errtext = <<<EOD
163
<html>
164
<head>
165
<title>Authentication error</title>
166
</head>
167
<body>
168
<font color="#cc0000"><h2>Authentication error</h2></font>
169
<b>
170
Username and/or password invalid.
171
<br><br>
172
<a href="javascript:history.back()">Go back</a>
173
</b>
174
</body>
175
</html>
176

    
177
EOD;
178
		}
179

    
180
		$fd = @fopen("{$g['varetc_path']}/captiveportal-error.html", "w");
181
		if ($fd) {
182
			fwrite($fd, $errtext);
183
			fclose($fd);
184
		}
185

    
186
		/* write elements */
187
		captiveportal_write_elements();
188

    
189
		/* load rules */
190
		mwexec("/sbin/ipfw -f delete set 1");
191
		mwexec("/sbin/ipfw -f delete set 2");
192
		mwexec("/sbin/ipfw -f delete set 3");
193

    
194
		/* ipfw cannot accept rules directly on stdin,
195
		   so we have to write them to a temporary file first */
196
		$fd = @fopen("{$g['tmp_path']}/ipfw.cp.rules", "w");
197
		if (!$fd) {
198
			printf("Cannot open ipfw.cp.rules in captiveportal_configure()\n");
199
			return 1;
200
		}
201

    
202
		fwrite($fd, $cprules);
203
		fclose($fd);
204

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

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

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

    
212
		chdir($g['captiveportal_path']);
213

    
214
		if ($config['captiveportal']['maxproc'])
215
			$maxproc = $config['captiveportal']['maxproc'];
216
		else
217
			$maxproc = 16;
218

    
219
		$use_fastcgi = true;
220

    
221
		if(isset($config['captiveportal']['httpslogin'])) {
222
			$cert = base64_decode($config['captiveportal']['certificate']);
223
			$key = base64_decode($config['captiveportal']['private-key']);
224
			/* generate lighttpd configuration */
225
			system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal-SSL.conf",
226
				$cert, $key, "", "lighty-CaptivePortal-ssl.pid", "8001", "/usr/local/captiveportal/",
227
					"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
228
		}
229

    
230
		/* generate lighttpd configuration */
231
		system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal.conf",
232
			"", "", "", "lighty-CaptivePortal.pid", "8000", "/usr/local/captiveportal/",
233
				"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
234

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

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

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

    
246
		/* generate passthru mac database */
247
		captiveportal_passthrumac_configure(true);
248
		/* create allowed ip database and insert ipfw rules to make it so */
249
		captiveportal_allowedip_configure(true);
250

    
251
		/* generate radius server database */
252
		if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
253
				($config['captiveportal']['auth_method'] == "radius"))) {
254
			$radiusip = $config['captiveportal']['radiusip'];
255
			$radiusip2 = ($config['captiveportal']['radiusip2']) ? $config['captiveportal']['radiusip2'] : null;
256

    
257
			if ($config['captiveportal']['radiusport'])
258
				$radiusport = $config['captiveportal']['radiusport'];
259
			else
260
				$radiusport = 1812;
261

    
262
			if ($config['captiveportal']['radiusacctport'])
263
				$radiusacctport = $config['captiveportal']['radiusacctport'];
264
			else
265
				$radiusacctport = 1813;
266

    
267
			if ($config['captiveportal']['radiusport2'])
268
				$radiusport2 = $config['captiveportal']['radiusport2'];
269
			else
270
				$radiusport2 = 1812;
271

    
272
			$radiuskey = $config['captiveportal']['radiuskey'];
273
			$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
274

    
275
			$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db", "w");
276
			if (!$fd) {
277
				printf("Error: cannot open radius DB file in captiveportal_configure().\n");
278
				return 1;
279
			} else if (isset($radiusip2, $radiuskey2)) {
280
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . "\n"
281
					 . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2);
282
			} else {
283
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey);
284
			}
285
			fclose($fd);
286
		}
287

    
288
		if ($g['booting'])
289
			echo "done\n";
290

    
291
	} else {
292
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
293
		killbypid("{$g['varrun_path']}/minicron.pid");
294

    
295
		captiveportal_radius_stop_all(true);
296

    
297
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
298

    
299
		/* unload ipfw */
300
		mwexec("/sbin/kldunload ipfw.ko");
301
	}
302

    
303
	unlock($captiveportallck);
304
	
305
	return 0;
306
}
307

    
308
function captiveportal_rules_generate($cpif, &$cpiparray) {
309
	global $config, $g;
310

    
311
	$cpifn = $config['captiveportal']['interface'];
312
	$lanip = get_interface_ip("lan");
313
	
314
	/* note: the captive portal daemon inserts all pass rules for authenticated
315
	   clients as skipto 50000 rules to make traffic shaping work */
316

    
317
	$cprules =  "add 500 set 1 allow pfsync from any to any\n";
318
	$cprules .= "add 500 set 1 allow carp from any to any\n";
319

    
320
	/*   allow nat redirects to work  see
321
	     http://cvstrac.pfsense.com/tktview?tn=651
322
	 */
323
        /* if list */
324
        $iflist = get_configured_interface_list();
325
	foreach ($iflist as $ifent => $ifname) {
326
		if(stristr($cpifn, $ifname))
327
			continue;
328
		$int = get_real_interface($ifname);
329
		$cprules .= "add 30 set 1 skipto 50000 all from any to any in via {$int} keep-state\n";
330
	}
331

    
332
	/* captive portal on LAN interface? */
333
	if (stristr($cpifn,  "lan")) {
334
		/* add anti-lockout rules */
335
		$cprules .= <<<EOD
336
add 500 set 1 pass all from $lanip to any out $cpif
337
add 501 set 1 pass all from any to $lanip in $cpif
338

    
339
EOD;
340
	}
341

    
342
	$cprules .= <<<EOD
343
add 1000 set 1 skipto 1200 all from any to any not layer2 $cpif
344
# skip to traffic shaper if not on captive portal interface
345
add 1001 set 1 skipto 50000 all from any to any not layer2
346
add 1003 set 1 skipto 1100 layer2 $cpif
347
# pass all layer2 traffic on other interfaces
348
add 1004 set 1 pass layer2 
349

    
350
# layer 2: pass ARP
351
add 1100 set 1 pass layer2 mac-type arp
352
# pfsense requires for WPA
353
add 1100 set 1 pass layer2 mac-type 0x888e
354
add 1100 set 1 pass layer2 mac-type 0x88c7
355

    
356
# PPP Over Ethernet Discovery Stage
357
add 1100 set 1 pass layer2 mac-type 0x8863
358
# PPP Over Ethernet Session Stage
359
add 1100 set 1 pass layer2 mac-type 0x8864
360
# Allow WPA
361
add 1100 set 1 pass layer2 mac-type 0x888e
362

    
363
# layer 2: block anything else non-IP
364
add 1101 set 1 deny layer2 not mac-type ip
365
# layer 2: check if MAC addresses of authenticated clients are correct
366
add 1102 set 1 skipto 20000 layer2
367

    
368
EOD;
369

    
370
	$rulenum = 1200;
371
	foreach ($cpiparray as $cpip) {
372
		//# allow access to our DHCP server (which needs to be able to ping clients as well)
373
		$cprules .= "add {$rulenum} set 1 pass udp from any 68 to 255.255.255.255 67 in \n";
374
		$rulenum++;
375
		$cprules .= "add {$rulenum} set 1 pass udp from any 68 to {$cpip} 67 in \n";
376
		$rulenum++;
377
		$cprules .= "add {$rulenum} set 1 pass udp from {$cpip} 67 to any 68 out \n";
378
		$rulenum++;
379
		$cprules .= "add {$rulenum} set 1 pass icmp from {$cpip} to any out icmptype 8\n";
380
		$rulenum++;
381
		$cprules .= "add {$rulenum} set 1 pass icmp from any to {$cpip} in icmptype 0 \n";
382
		$rulenum++;
383
		//# allow access to our DNS forwarder
384
		$cprules .= "add {$rulenum} set 1 pass udp from any to {$cpip} 53 in \n";
385
		$rulenum++;
386
		$cprules .= "add {$rulenum} set 1 pass udp from {$cpip} 53 to any out \n";
387
		$rulenum++;
388
		# allow access to our web server
389
		$cprules .= "add {$rulenum} set 1 pass tcp from any to {$cpip} 8000 in \n";
390
		$rulenum++;
391
		$cprules .= "add {$rulenum} set 1 pass tcp from {$cpip} 8000 to any out \n";
392

    
393
		if (isset($config['captiveportal']['httpslogin'])) {
394
			$rulenum++;
395
			$cprules .= "add {$rulenum} set 1 pass tcp from any to {$cpip} 8001 in \n";
396
			$rulenum++;
397
			$cprules .= "add {$rulenum} set 1 pass tcp from {$cpip} 8001 to any out \n";
398
		}
399
	}
400

    
401
	$rulenum++;
402
        //# allow access to our DNS forwarder if it incorrectly resolves the hostname to $lanip
403
        $cprules .= "add {$rulenum} set 1 pass udp from any to {$lanip} 53 in \n";
404
        $rulenum++;
405
        $cprules .= "add {$rulenum} set 1 pass udp from {$lanip} 53 to any out \n";
406
	//# allow access to lan web server incase the dns name resolves incorrectly to $lanip
407
	$rulenum++;
408
        $cprules .= "add {$rulenum} set 1 pass tcp from any to {$lanip} 8000 in \n";
409
        $rulenum++;
410
        $cprules .= "add {$rulenum} set 1 pass tcp from {$lanip} 8000 to any out \n";
411

    
412
        $cprules .= <<<EOD
413

    
414
# ... 10000-19899: rules per authenticated client go here...
415

    
416
# redirect non-authenticated clients to captive portal
417
add 19902 set 1 fwd 127.0.0.1,8000 tcp from any to any 80 in
418
# let the responses from the captive portal web server back out
419
add 19903 set 1 pass tcp from any 80 to any out
420
# block everything else
421
add 19904 set 1 deny all from any to any
422

    
423
# ... 20000-29899: layer2 block rules per authenticated client go here...
424

    
425
# pass everything else on layer2
426
add 29900 set 1 pass all from any to any layer2
427

    
428
EOD;
429

    
430
    return $cprules;
431
}
432

    
433
/* remove clients that have been around for longer than the specified amount of time */
434
/* db file structure:
435
timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time */
436

    
437
/* (password is in Base64 and only saved when reauthentication is enabled) */
438
function captiveportal_prune_old() {
439

    
440
    global $g, $config;
441

    
442
    /* check for expired entries */
443
    if ($config['captiveportal']['timeout'])
444
        $timeout = $config['captiveportal']['timeout'] * 60;
445
    else
446
        $timeout = 0;
447

    
448
    if ($config['captiveportal']['idletimeout'])
449
        $idletimeout = $config['captiveportal']['idletimeout'] * 60;
450
    else
451
        $idletimeout = 0;
452

    
453
    if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && !isset($config['captiveportal']['radiussession_timeout']))
454
        return;
455

    
456
    $captiveportallck = lock('captiveportal');
457

    
458
    /* read database */
459
    $cpdb = captiveportal_read_db();
460

    
461
    $radiusservers = captiveportal_get_radius_servers();
462

    
463
 	/*  To make sure we iterate over ALL accounts on every run the count($cpdb) is moved outside of the loop. Otherwise
464
     *  the loop would evalate count() on every iteration and since $i would increase and count() would decrement they
465
     *  would meet before we had a chance to iterate over all accounts.
466
     */
467
    $no_users = count($cpdb);
468
    for ($i = 0; $i < $no_users; $i++) {
469

    
470
        $timedout = false;
471
        $term_cause = 1;
472

    
473
		/* no pruning for fixed mac address entry */
474
		if (portal_mac_fixed($cpdb[$i][3])) {
475
			continue; // check next value
476
		}
477
        /* hard timeout? */
478
        if ($timeout) {
479
            if ((time() - $cpdb[$i][0]) >= $timeout) {
480
                $timedout = true;
481
                $term_cause = 5; // Session-Timeout
482
            }
483
        }
484

    
485
        /* Session-Terminate-Time */
486
        if (!$timedout && !empty($cpdb[$i][9])) {
487
            if (time() >= $cpdb[$i][9]) {
488
                $timedout = true;
489
                $term_cause = 5; // Session-Timeout
490
            }
491
        }
492

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

    
509
        /* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
510
        if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpdb[$i][7])) {
511
            if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
512
                $timedout = true;
513
                $term_cause = 5; // Session-Timeout
514
            }
515
        }
516

    
517
        if ($timedout) {
518
            captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
519
            captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
520
            unset($cpdb[$i]);
521
        }
522

    
523
        /* do periodic RADIUS reauthentication? */
524
        if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
525
            ($radiusservers !== false)) {
526

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

    
564
            /* check this user against RADIUS again */
565
            $auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
566
                                          base64_decode($cpdb[$i][6]), // password
567
                                            $radiusservers,
568
                                          $cpdb[$i][2], // clientip
569
                                          $cpdb[$i][3], // clientmac
570
                                          $cpdb[$i][1]); // ruleno
571

    
572
            if ($auth_list['auth_val'] == 3) {
573
                captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
574
                captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
575
                unset($cpdb[$i]);
576
            }
577
        }
578
    }
579

    
580
    /* write database */
581
    captiveportal_write_db($cpdb);
582

    
583
    unlock($captiveportallck);
584
}
585

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

    
589
	global $g, $config;
590

    
591
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
592

    
593
	/* this client needs to be deleted - remove ipfw rules */
594
	if (isset($config['captiveportal']['radacct_enable']) && isset($radiusservers[0])) {
595
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
596
							   $dbent[4], // username
597
							   $dbent[5], // sessionid
598
							   $dbent[0], // start time
599
							   $radiusservers[0]['ipaddr'],
600
							   $radiusservers[0]['acctport'],
601
							   $radiusservers[0]['key'],
602
							   $dbent[2], // clientip
603
							   $dbent[3], // clientmac
604
							   $term_cause, // Acct-Terminate-Cause
605
							   false,
606
							   $stop_time);
607
	}
608

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

    
611
    /* We need to delete +40500 and +45500 as well...
612
     * these are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
613
     * We could get an error if the pipe doesn't exist but everything should still be fine
614
     */
615
    if (isset($config['captiveportal']['peruserbw'])) {
616
        mwexec("/sbin/ipfw pipe " . ($dbent[1]+40500) . " delete");
617
        mwexec("/sbin/ipfw pipe " . ($dbent[1]+45500) . " delete");
618
    }
619

    
620
	/* pfSense: ensure all pf states are killed (pfSense) */
621
	mwexec("pfctl -k {$dbent[2]}");
622
	mwexec("pfctl -K {$dbent[2]}");
623

    
624
}
625

    
626
/* remove a single client by ipfw rule number */
627
function captiveportal_disconnect_client($id,$term_cause = 1) {
628

    
629
	global $g, $config;
630

    
631
	$captiveportallck = lock('captiveportal');
632

    
633
	/* read database */
634
	$cpdb = captiveportal_read_db();
635
	$radiusservers = captiveportal_get_radius_servers();
636

    
637
	/* find entry */
638
	for ($i = 0; $i < count($cpdb); $i++) {
639
		if ($cpdb[$i][1] == $id) {
640
			captiveportal_disconnect($cpdb[$i], $radiusservers, $term_cause);
641
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "DISCONNECT");
642
			unset($cpdb[$i]);
643
			break;
644
		}
645
	}
646

    
647
	/* write database */
648
	captiveportal_write_db($cpdb);
649

    
650
	unlock($captiveportallck);
651
}
652

    
653
/* send RADIUS acct stop for all current clients */
654
function captiveportal_radius_stop_all($lock = false) {
655
	global $g, $config;
656

    
657
	if (!isset($config['captiveportal']['radacct_enable']))
658
		return;
659

    
660
	if (!$lock)
661
		$captiveportallck = lock('captiveportal');
662

    
663
	$cpdb = captiveportal_read_db();
664

    
665
	$radiusservers = captiveportal_get_radius_servers();
666
	if (isset($radiusservers[0])) {
667
		for ($i = 0; $i < count($cpdb); $i++) {
668
			RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
669
								   $cpdb[$i][4], // username
670
								   $cpdb[$i][5], // sessionid
671
								   $cpdb[$i][0], // start time
672
								   $radiusservers[0]['ipaddr'],
673
								   $radiusservers[0]['acctport'],
674
								   $radiusservers[0]['key'],
675
								   $cpdb[$i][2], // clientip
676
								   $cpdb[$i][3], // clientmac
677
								   7); // Admin Reboot
678
		}
679
	}
680
	if (!$lock)
681
		unlock($captiveportallck);
682
}
683

    
684
function captiveportal_passthrumac_configure($lock = false) {
685
	global $config, $g;
686

    
687
	if (!$lock)
688
		$captiveportallck = lock('captiveportal');
689

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

    
693
	if (is_array($config['captiveportal']['passthrumac'])) {
694

    
695
		$fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db", "w");
696
		if (!$fd) {
697
			printf("Error: cannot open passthru mac DB file in captiveportal_passthrumac_configure().\n");
698
			unlock($captiveportallck);
699
			return 1;
700
		}
701

    
702
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
703
			/* record passthru mac so it can be recognized and let thru */
704
			fwrite($fd, $macent['mac'] . "\n");
705
		}
706

    
707
		fclose($fd);
708
	}
709

    
710
	/*    pfSense:
711
	 * 	  pass through mac entries should always exist.  the reason
712
	 *    for this is because we do not have native mac address filtering
713
	 *    mechanisms.  this allows us to filter by mac address easily
714
	 *    and get around this limitation.   I consider this a bug in
715
	 *    m0n0wall and pfSense as m0n0wall does not have native mac
716
	 *    filtering mechanisms as well. -Scott Ullrich
717
	 */
718
	if (is_array($config['captiveportal']['passthrumac'])) {
719
		mwexec("/sbin/ipfw delete 50");
720
		foreach($config['captiveportal']['passthrumac'] as $ptm) {
721
			/* create the pass through mac entry */
722
			//system("echo /sbin/ipfw add 50 skipto 65535 ip from any to any MAC {$ptm['mac']} any > /tmp/cp");
723
			mwexec("/sbin/ipfw add 50 skipto 29900 ip from any to any MAC {$ptm['mac']} any keep-state");
724
			mwexec("/sbin/ipfw add 50 skipto 29900 ip from any to any MAC any {$ptm['mac']} keep-state");
725
		}
726
	}
727

    
728
	if (!$lock)
729
		unlock($captiveportallck);
730

    
731
	return 0;
732
}
733

    
734
function captiveportal_allowedip_configure($lock = false) {
735
	global $config, $g;
736

    
737
	if (!$lock)
738
		$captiveportallck = lock('captiveportal');
739

    
740
	/* clear out existing allowed ips, if necessary */
741
	if (file_exists("{$g['vardb_path']}/captiveportal_ip.db")) {
742
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "r");
743
		if ($fd) {
744
			while (!feof($fd)) {
745
				$line = trim(fgets($fd));
746
				if ($line) {
747
					list($ip,$rule) = explode(",",$line);
748
					mwexec("/sbin/ipfw delete $rule");
749
				}
750
			}
751
		}
752
		fclose($fd);
753
		unlink("{$g['vardb_path']}/captiveportal_ip.db");
754
	}
755

    
756
	/* get next ipfw rule number */
757
	if (file_exists("{$g['vardb_path']}/captiveportal.nextrule"))
758
		$ruleno = trim(file_get_contents("{$g['vardb_path']}/captiveportal.nextrule"));
759
	if (!$ruleno)
760
		$ruleno = 10000;	/* first rule number */
761

    
762
	if (is_array($config['captiveportal']['allowedip'])) {
763

    
764
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "w");
765
		if (!$fd) {
766
			printf("Error: cannot open allowed ip DB file in captiveportal_allowedip_configure().\n");
767
			unlock($captiveportallck);
768
			return 1;
769
		}
770

    
771
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
772
            /* get next ipfw rule number */
773
            $ruleno = captiveportal_get_next_ipfw_ruleno();
774

    
775
            /* if the pool is empty, return apprioriate message and fail */
776
            if (is_null($ruleno)) {
777
                printf("Error: system reached maximum login capacity, no free FW rulenos in captiveportal_allowedip_configure().\n");
778
                fclose($fd);
779
                unlock($captiveportallck);
780
                return 1;
781
            }
782

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

    
786
            /* insert ipfw rule to allow ip thru */
787
            if ($ipent['dir'] == "from") {
788
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any in");
789
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " out");
790
            } else {
791
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " in");
792
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any out");
793
            }
794

    
795
        }
796

    
797
        fclose($fd);
798
    }
799

    
800
	if (!$lock)
801
    		unlock($captiveportallck);
802
    return 0;
803
}
804

    
805
/* get last activity timestamp given ipfw rule number */
806
function captiveportal_get_last_activity($ruleno) {
807

    
808
	$ipfwoutput = "";
809

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

    
812
	/* in */
813
	if ($ipfwoutput[0]) {
814
		$ri = explode(" ", $ipfwoutput[0]);
815
		if ($ri[1])
816
			return $ri[1];
817
	}
818

    
819
	return 0;
820
}
821

    
822
/* read RADIUS servers into array */
823
function captiveportal_get_radius_servers() {
824

    
825
        global $g;
826

    
827
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
828
                $radiusservers = array();
829
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius.db", 
830
			FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
831
		if ($cpradiusdb)
832
		foreach($cpradiusdb as $cpradiusentry) {
833
                	$line = trim($cpradiusentry);
834
                        if ($line) {
835
                        	$radsrv = array();
836
                                list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
837
                        	$radiusservers[] = $radsrv;
838
                        }
839
		}
840

    
841
		return $radiusservers;
842
        }
843

    
844
        return false;
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
    $captiveportallck = lock('captiveportal');
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
        unlock($captiveportallck);
884
        return $auth_list;
885
    }
886

    
887
    /*
888
     * Drop the lock since radius takes some time to finish.
889
     * The implementation is reentrant so we gain speed with this.
890
     */
891
    unlock($captiveportallck);
892

    
893
    $radiusservers = captiveportal_get_radius_servers();
894

    
895
    $auth_list = RADIUS_AUTHENTICATION($username,
896
                    $password,
897
                    $radiusservers,
898
                    $clientip,
899
                    $clientmac,
900
                    $ruleno);
901

    
902
    $captiveportallck = lock('captiveportal');
903

    
904
    if ($auth_list['auth_val'] == 2) {
905
        captiveportal_logportalauth($username,$clientmac,$clientip,$type);
906
        $sessionid = portal_allow($clientip,
907
                    $clientmac,
908
                    $username,
909
                    $password,
910
                    $auth_list,
911
                    $ruleno);
912
    }
913

    
914
    unlock($captiveportallck);
915

    
916
    return $auth_list;
917

    
918
}
919

    
920
/* read captive portal DB into array */
921
function captiveportal_read_db() {
922

    
923
        global $g;
924

    
925
        $cpdb = array();
926
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
927
        if ($fd) {
928
                while (!feof($fd)) {
929
                        $line = trim(fgets($fd));
930
                        if ($line) {
931
                                $cpdb[] = explode(",", $line);
932
                        }
933
                }
934
                fclose($fd);
935
        }
936
        return $cpdb;
937
}
938

    
939
/* write captive portal DB */
940
function captiveportal_write_db($cpdb) {
941
                 
942
        global $g;
943
                
944
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
945
        if ($fd) { 
946
                foreach ($cpdb as $cpent) {
947
                        fwrite($fd, join(",", $cpent) . "\n");
948
                }       
949
                fclose($fd);
950
        }       
951
}
952

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

    
989
/*
990
 * This function will calculate the lowest free firewall ruleno
991
 * within the range specified based on the actual installed rules
992
 *
993
 */
994
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 10000, $rulenos_range_max = 9899) {
995

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

    
1012
	return $ruleno;
1013
}
1014

    
1015
/**
1016
 * This function will calculate the traffic produced by a client
1017
 * based on its firewall rule
1018
 *
1019
 * Point of view: NAS
1020
 *
1021
 * Input means: from the client
1022
 * Output means: to the client
1023
 *
1024
 */
1025

    
1026
function getVolume($ruleno) {
1027

    
1028
    $volume = array();
1029

    
1030
    // Initialize vars properly, since we don't want NULL vars
1031
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1032

    
1033
    // Ingress
1034
    $ipfw = "";
1035
    $matches = "";
1036
    exec("/sbin/ipfw show {$ruleno}", $ipfw);
1037
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[0], $matches);
1038
    $volume['input_pkts'] = $matches[2];
1039
    $volume['input_bytes'] = $matches[3];
1040

    
1041
    // Flush internal buffer
1042
    unset($matches);
1043

    
1044
    // Outgress
1045
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[1], $matches);
1046
    $volume['output_pkts'] = $matches[2];
1047
    $volume['output_bytes'] = $matches[3];
1048

    
1049
    return $volume;
1050
}
1051

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

    
1066
/**
1067
 * Get the NAS-IP-Address based on the current wan address
1068
 *
1069
 * Use functions in interfaces.inc to find this out
1070
 *
1071
 */
1072

    
1073
function getNasIP()
1074
{
1075
    $nasIp = get_interface_ip();
1076
    if(!$nasIp)
1077
        $nasIp = "0.0.0.0";
1078
    return $nasIp;
1079
}
1080

    
1081
function portal_mac_fixed($clientmac) {
1082
    global $g ;
1083

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

    
1102
function portal_ip_from_client_ip($cliip) {
1103
	global $config;
1104

    
1105
	$interfaces = explode(",", $config['captiveportal']['interface']);
1106
	foreach ($interfaces as $cpif) {
1107
		$ip = get_interface_ip($cpif);
1108
		$sn = get_interface_subnet($cpif);
1109
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1110
			return $ip;
1111
	}
1112

    
1113
	return false;
1114
}
1115

    
1116
?>
(6-6/40)