Project

General

Profile

Download (36.3 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
require_once("voucher.inc");
44

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

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

    
76
	if ($cpactive == true) {
77

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

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

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

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

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

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

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

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

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

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

    
145

    
146

    
147
EOD;
148
		}
149

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

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

    
176
EOD;
177
		}
178

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

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

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

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

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

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

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

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

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

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

    
218
		$use_fastcgi = true;
219

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
294
		captiveportal_radius_stop_all(true);
295

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

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

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

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

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

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

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

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

    
338
EOD;
339
	}
340

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

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

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

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

    
367
EOD;
368

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

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

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

    
411
        $cprules .= <<<EOD
412

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

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

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

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

    
427
EOD;
428

    
429
    return $cprules;
430
}
431

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

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

    
439
    global $g, $config;
440

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

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

    
452
    if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && 
453
		!isset($config['captiveportal']['radiussession_timeout']) && !isset($config['voucher']['enable']))
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 vouchers are configured, activate session timeouts */
510
	if (!$timedout && isset($config['voucher']['enable']) && !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 radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
518
        if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpdb[$i][7])) {
519
            if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
520
                $timedout = true;
521
                $term_cause = 5; // Session-Timeout
522
            }
523
        }
524

    
525
        if ($timedout) {
526
            captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
527
            captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
528
            unset($cpdb[$i]);
529
        }
530

    
531
        /* do periodic RADIUS reauthentication? */
532
        if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
533
            ($radiusservers !== false)) {
534

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

    
572
            /* check this user against RADIUS again */
573
            $auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
574
                                          base64_decode($cpdb[$i][6]), // password
575
                                            $radiusservers,
576
                                          $cpdb[$i][2], // clientip
577
                                          $cpdb[$i][3], // clientmac
578
                                          $cpdb[$i][1]); // ruleno
579

    
580
            if ($auth_list['auth_val'] == 3) {
581
                captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
582
                captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
583
                unset($cpdb[$i]);
584
            }
585
        }
586
    }
587

    
588
    /* write database */
589
    captiveportal_write_db($cpdb);
590

    
591
    unlock($captiveportallck);
592
}
593

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

    
597
	global $g, $config;
598

    
599
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
600

    
601
	/* this client needs to be deleted - remove ipfw rules */
602
	if (isset($config['captiveportal']['radacct_enable']) && isset($radiusservers[0])) {
603
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
604
							   $dbent[4], // username
605
							   $dbent[5], // sessionid
606
							   $dbent[0], // start time
607
							   $radiusservers[0]['ipaddr'],
608
							   $radiusservers[0]['acctport'],
609
							   $radiusservers[0]['key'],
610
							   $dbent[2], // clientip
611
							   $dbent[3], // clientmac
612
							   $term_cause, // Acct-Terminate-Cause
613
							   false,
614
							   $stop_time);
615
	}
616

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

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

    
628
	/* pfSense: ensure all pf states are killed (pfSense) */
629
	mwexec("pfctl -k {$dbent[2]}");
630
	mwexec("pfctl -K {$dbent[2]}");
631

    
632
}
633

    
634
/* remove a single client by ipfw rule number */
635
function captiveportal_disconnect_client($id,$term_cause = 1) {
636

    
637
	global $g, $config;
638

    
639
	$captiveportallck = lock('captiveportal');
640

    
641
	/* read database */
642
	$cpdb = captiveportal_read_db();
643
	$radiusservers = captiveportal_get_radius_servers();
644

    
645
	/* find entry */
646
	for ($i = 0; $i < count($cpdb); $i++) {
647
		if ($cpdb[$i][1] == $id) {
648
			captiveportal_disconnect($cpdb[$i], $radiusservers, $term_cause);
649
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "DISCONNECT");
650
			unset($cpdb[$i]);
651
			break;
652
		}
653
	}
654

    
655
	/* write database */
656
	captiveportal_write_db($cpdb);
657

    
658
	unlock($captiveportallck);
659
}
660

    
661
/* send RADIUS acct stop for all current clients */
662
function captiveportal_radius_stop_all($lock = false) {
663
	global $g, $config;
664

    
665
	if (!isset($config['captiveportal']['radacct_enable']))
666
		return;
667

    
668
	if (!$lock)
669
		$captiveportallck = lock('captiveportal');
670

    
671
	$cpdb = captiveportal_read_db();
672

    
673
	$radiusservers = captiveportal_get_radius_servers();
674
	if (isset($radiusservers[0])) {
675
		for ($i = 0; $i < count($cpdb); $i++) {
676
			RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
677
								   $cpdb[$i][4], // username
678
								   $cpdb[$i][5], // sessionid
679
								   $cpdb[$i][0], // start time
680
								   $radiusservers[0]['ipaddr'],
681
								   $radiusservers[0]['acctport'],
682
								   $radiusservers[0]['key'],
683
								   $cpdb[$i][2], // clientip
684
								   $cpdb[$i][3], // clientmac
685
								   7); // Admin Reboot
686
		}
687
	}
688
	if (!$lock)
689
		unlock($captiveportallck);
690
}
691

    
692
function captiveportal_passthrumac_configure($lock = false) {
693
	global $config, $g;
694

    
695
	if (!$lock)
696
		$captiveportallck = lock('captiveportal');
697

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

    
701
	if (is_array($config['captiveportal']['passthrumac'])) {
702

    
703
		$fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db", "w");
704
		if (!$fd) {
705
			printf("Error: cannot open passthru mac DB file in captiveportal_passthrumac_configure().\n");
706
			unlock($captiveportallck);
707
			return 1;
708
		}
709

    
710
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
711
			/* record passthru mac so it can be recognized and let thru */
712
			fwrite($fd, $macent['mac'] . "\n");
713
		}
714

    
715
		fclose($fd);
716
	}
717

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

    
736
	if (!$lock)
737
		unlock($captiveportallck);
738

    
739
	return 0;
740
}
741

    
742
function captiveportal_allowedip_configure($lock = false) {
743
	global $config, $g;
744

    
745
	if (!$lock)
746
		$captiveportallck = lock('captiveportal');
747

    
748
	/* clear out existing allowed ips, if necessary */
749
	if (file_exists("{$g['vardb_path']}/captiveportal_ip.db")) {
750
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "r");
751
		if ($fd) {
752
			while (!feof($fd)) {
753
				$line = trim(fgets($fd));
754
				if ($line) {
755
					list($ip,$rule) = explode(",",$line);
756
					mwexec("/sbin/ipfw delete $rule");
757
				}
758
			}
759
		}
760
		fclose($fd);
761
		unlink("{$g['vardb_path']}/captiveportal_ip.db");
762
	}
763

    
764
	/* get next ipfw rule number */
765
	if (file_exists("{$g['vardb_path']}/captiveportal.nextrule"))
766
		$ruleno = trim(file_get_contents("{$g['vardb_path']}/captiveportal.nextrule"));
767
	if (!$ruleno)
768
		$ruleno = 10000;	/* first rule number */
769

    
770
	if (is_array($config['captiveportal']['allowedip'])) {
771

    
772
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "w");
773
		if (!$fd) {
774
			printf("Error: cannot open allowed ip DB file in captiveportal_allowedip_configure().\n");
775
			unlock($captiveportallck);
776
			return 1;
777
		}
778

    
779
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
780
            /* get next ipfw rule number */
781
            $ruleno = captiveportal_get_next_ipfw_ruleno();
782

    
783
            /* if the pool is empty, return apprioriate message and fail */
784
            if (is_null($ruleno)) {
785
                printf("Error: system reached maximum login capacity, no free FW rulenos in captiveportal_allowedip_configure().\n");
786
                fclose($fd);
787
                unlock($captiveportallck);
788
                return 1;
789
            }
790

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

    
794
            /* insert ipfw rule to allow ip thru */
795
            if ($ipent['dir'] == "from") {
796
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any in");
797
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " out");
798
            } else {
799
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " in");
800
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any out");
801
            }
802

    
803
        }
804

    
805
        fclose($fd);
806
    }
807

    
808
	if (!$lock)
809
    		unlock($captiveportallck);
810
    return 0;
811
}
812

    
813
/* get last activity timestamp given ipfw rule number */
814
function captiveportal_get_last_activity($ruleno) {
815

    
816
	$ipfwoutput = "";
817

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

    
820
	/* in */
821
	if ($ipfwoutput[0]) {
822
		$ri = explode(" ", $ipfwoutput[0]);
823
		if ($ri[1])
824
			return $ri[1];
825
	}
826

    
827
	return 0;
828
}
829

    
830
/* read RADIUS servers into array */
831
function captiveportal_get_radius_servers() {
832

    
833
        global $g;
834

    
835
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
836
                $radiusservers = array();
837
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius.db", 
838
			FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
839
		if ($cpradiusdb)
840
		foreach($cpradiusdb as $cpradiusentry) {
841
                	$line = trim($cpradiusentry);
842
                        if ($line) {
843
                        	$radsrv = array();
844
                                list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
845
                        	$radiusservers[] = $radsrv;
846
                        }
847
		}
848

    
849
		return $radiusservers;
850
        }
851

    
852
        return false;
853
}
854

    
855
/* log successful captive portal authentication to syslog */
856
/* part of this code from php.net */
857
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
858
	$message = trim($message);
859
	// Log it
860
	if (!$message)
861
		$message = "$status: $user, $mac, $ip";
862
	else
863
		$message = "$status: $user, $mac, $ip, $message";
864
	captiveportal_syslog($message);
865
	closelog();
866
}
867

    
868
/* log simple messages to syslog */
869
function captiveportal_syslog($message) {
870
	define_syslog_variables();
871
	$message = trim($message);
872
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
873
	// Log it
874
	syslog(LOG_INFO, $message);
875
	closelog();
876
}
877

    
878
function radius($username,$password,$clientip,$clientmac,$type) {
879
    global $g, $config;
880

    
881
    /* Start locking from the beginning of an authentication session */
882
    $captiveportallck = lock('captiveportal');
883

    
884
    $ruleno = captiveportal_get_next_ipfw_ruleno();
885

    
886
    /* if the pool is empty, return apprioriate message and fail authentication */
887
    if (is_null($ruleno)) {
888
        $auth_list = array();
889
        $auth_list['auth_val'] = 1;
890
        $auth_list['error'] = "System reached maximum login capacity";
891
        unlock($captiveportallck);
892
        return $auth_list;
893
    }
894

    
895
    /*
896
     * Drop the lock since radius takes some time to finish.
897
     * The implementation is reentrant so we gain speed with this.
898
     */
899
    unlock($captiveportallck);
900

    
901
    $radiusservers = captiveportal_get_radius_servers();
902

    
903
    $auth_list = RADIUS_AUTHENTICATION($username,
904
                    $password,
905
                    $radiusservers,
906
                    $clientip,
907
                    $clientmac,
908
                    $ruleno);
909

    
910
    $captiveportallck = lock('captiveportal');
911

    
912
    if ($auth_list['auth_val'] == 2) {
913
        captiveportal_logportalauth($username,$clientmac,$clientip,$type);
914
        $sessionid = portal_allow($clientip,
915
                    $clientmac,
916
                    $username,
917
                    $password,
918
                    $auth_list,
919
                    $ruleno);
920
    }
921

    
922
    unlock($captiveportallck);
923

    
924
    return $auth_list;
925

    
926
}
927

    
928
/* read captive portal DB into array */
929
function captiveportal_read_db() {
930

    
931
        global $g;
932

    
933
        $cpdb = array();
934
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
935
        if ($fd) {
936
                while (!feof($fd)) {
937
                        $line = trim(fgets($fd));
938
                        if ($line) {
939
                                $cpdb[] = explode(",", $line);
940
                        }
941
                }
942
                fclose($fd);
943
        }
944
        return $cpdb;
945
}
946

    
947
/* write captive portal DB */
948
function captiveportal_write_db($cpdb) {
949
                 
950
        global $g;
951
                
952
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
953
        if ($fd) { 
954
                foreach ($cpdb as $cpent) {
955
                        fwrite($fd, join(",", $cpent) . "\n");
956
                }       
957
                fclose($fd);
958
        }       
959
}
960

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

    
997
/*
998
 * This function will calculate the lowest free firewall ruleno
999
 * within the range specified based on the actual installed rules
1000
 *
1001
 */
1002
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 10000, $rulenos_range_max = 9899) {
1003

    
1004
	$fwrules = "";
1005
	$matches = "";
1006
	exec("/sbin/ipfw show", $fwrules);
1007
	foreach ($fwrules as $fwrule) {
1008
		preg_match("/^(\d+)\s+/", $fwrule, $matches);
1009
		$rulenos_used[] = $matches[1];
1010
	}
1011
	$rulenos_used = array_unique($rulenos_used);
1012
	$rulenos_range = count($rulenos_used);
1013
	if ($rulenos_range > $rulenos_range_max) {
1014
		return NULL;
1015
	}
1016
	$rulenos_pool = range($rulenos_start, ($rulenos_start + $rulenos_range));
1017
	$rulenos_free = array_diff($rulenos_pool, $rulenos_used);
1018
	$ruleno = array_shift($rulenos_free);
1019

    
1020
	return $ruleno;
1021
}
1022

    
1023
/**
1024
 * This function will calculate the traffic produced by a client
1025
 * based on its firewall rule
1026
 *
1027
 * Point of view: NAS
1028
 *
1029
 * Input means: from the client
1030
 * Output means: to the client
1031
 *
1032
 */
1033

    
1034
function getVolume($ruleno) {
1035

    
1036
    $volume = array();
1037

    
1038
    // Initialize vars properly, since we don't want NULL vars
1039
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1040

    
1041
    // Ingress
1042
    $ipfw = "";
1043
    $matches = "";
1044
    exec("/sbin/ipfw show {$ruleno}", $ipfw);
1045
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[0], $matches);
1046
    $volume['input_pkts'] = $matches[2];
1047
    $volume['input_bytes'] = $matches[3];
1048

    
1049
    // Flush internal buffer
1050
    unset($matches);
1051

    
1052
    // Outgress
1053
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[1], $matches);
1054
    $volume['output_pkts'] = $matches[2];
1055
    $volume['output_bytes'] = $matches[3];
1056

    
1057
    return $volume;
1058
}
1059

    
1060
/**
1061
 * Get the NAS-Identifier
1062
 *
1063
 * We will use our local hostname to make up the nas_id
1064
 */
1065
function getNasID()
1066
{
1067
    $nasId = "";
1068
    exec("/bin/hostname", $nasId);
1069
    if(!$nasId[0])
1070
        $nasId[0] = "{$g['product_name']}";
1071
    return $nasId[0];
1072
}
1073

    
1074
/**
1075
 * Get the NAS-IP-Address based on the current wan address
1076
 *
1077
 * Use functions in interfaces.inc to find this out
1078
 *
1079
 */
1080

    
1081
function getNasIP()
1082
{
1083
    $nasIp = get_interface_ip();
1084
    if(!$nasIp)
1085
        $nasIp = "0.0.0.0";
1086
    return $nasIp;
1087
}
1088

    
1089
function portal_mac_fixed($clientmac) {
1090
    global $g ;
1091

    
1092
    /* open captive portal mac db */
1093
    if (file_exists("{$g['vardb_path']}/captiveportal_mac.db")) {
1094
        $fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db","r") ;
1095
        if (!$fd) {
1096
            return FALSE;
1097
        }
1098
        while (!feof($fd)) {
1099
            $mac = trim(fgets($fd)) ;
1100
            if(strcasecmp($clientmac, $mac) == 0) {
1101
                fclose($fd) ;
1102
                return TRUE ;
1103
            }
1104
        }
1105
        fclose($fd) ;
1106
    }
1107
    return FALSE ;
1108
}
1109

    
1110
function portal_ip_from_client_ip($cliip) {
1111
	global $config;
1112

    
1113
	$interfaces = explode(",", $config['captiveportal']['interface']);
1114
	foreach ($interfaces as $cpif) {
1115
		$ip = get_interface_ip($cpif);
1116
		$sn = get_interface_subnet($cpif);
1117
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1118
			return $ip;
1119
	}
1120

    
1121
	return false;
1122
}
1123

    
1124
?>
(6-6/43)