Project

General

Profile

Download (35.2 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
				mwexec("/sbin/ifconfig {$tmpif} -ipfwfilter");
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
					mwexec("/sbin/ifconfig {$tmpif} ipfwfilter");
70
				}
71
			}
72
		}
73
		if (count($cpips) > 0) {
74
			$cpactive = true;
75
			$cpinterface = "{ {$cpinterface} } ";
76
		}
77
	}
78

    
79
	if ($cpactive == true) {
80

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

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

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

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

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

    
100
		/* stop accounting on all clients */
101
		captiveportal_radius_stop_all(true);
102

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

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

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

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

    
148

    
149

    
150
EOD;
151
		}
152

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

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

    
179
EOD;
180
		}
181

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

    
188
		/* write elements */
189
		captiveportal_write_elements();
190

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

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

    
204
		fwrite($fd, $cprules);
205
		fclose($fd);
206

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

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

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

    
214
		chdir($g['captiveportal_path']);
215

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

    
221
		$use_fastcgi = true;
222

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

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

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

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

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

    
248
		/* generate passthru mac database */
249
		captiveportal_passthrumac_configure(true);
250
		/* allowed ipfw rules to make allowed ip work */
251
		captiveportal_allowedip_configure();
252

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

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

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

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

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

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

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

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

    
297
		captiveportal_radius_stop_all(true);
298

    
299
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
300

    
301
		/* unload ipfw */
302
		mwexec("/sbin/kldunload ipfw.ko");
303
		$listifs = get_configured_interface_list_by_realif();
304
		foreach ($listifs as $listrealif => $listif)
305
			mwexec("/sbin/ifconfig {$listrealif} -ipfwfilter");
306
	}
307

    
308
	unlock($captiveportallck);
309
	
310
	return 0;
311
}
312

    
313
function captiveportal_rules_generate($cpif, &$cpiparray) {
314
	global $config, $g;
315

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

    
322
	$cprules =  "add 500 set 1 allow pfsync from any to any\n";
323
	$cprules .= "add 500 set 1 allow carp from any to any\n";
324

    
325
	/* captive portal on LAN interface? */
326
	if (stristr($cpifn,  "lan")) {
327
		/* add anti-lockout rules */
328
		$cprules .= <<<EOD
329
add 500 set 1 pass all from $lanip to any out $cpif
330
add 501 set 1 pass all from any to $lanip in $cpif
331

    
332
EOD;
333
	}
334

    
335
	$cprules .= <<<EOD
336
add 1000 set 1 skipto 1200 all from any to any not layer2
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
# Allow WPA
348
add 1100 set 1 pass layer2 mac-type 0x888e
349

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

    
355
EOD;
356

    
357
	$rulenum = 1200;
358
	foreach ($cpiparray as $cpip) {
359
		//# allow access to our DHCP server (which needs to be able to ping clients as well)
360
		$cprules .= "add {$rulenum} set 1 pass udp from any 68 to 255.255.255.255 67 in \n";
361
		$rulenum++;
362
		$cprules .= "add {$rulenum} set 1 pass udp from any 68 to {$cpip} 67 in \n";
363
		$rulenum++;
364
		$cprules .= "add {$rulenum} set 1 pass udp from {$cpip} 67 to any 68 out \n";
365
		$rulenum++;
366
		$cprules .= "add {$rulenum} set 1 pass icmp from {$cpip} to any out icmptype 8\n";
367
		$rulenum++;
368
		$cprules .= "add {$rulenum} set 1 pass icmp from any to {$cpip} in icmptype 0 \n";
369
		$rulenum++;
370
		//# allow access to our DNS forwarder
371
		$cprules .= "add {$rulenum} set 1 pass udp from any to {$cpip} 53 in \n";
372
		$rulenum++;
373
		$cprules .= "add {$rulenum} set 1 pass udp from {$cpip} 53 to any out \n";
374
		$rulenum++;
375
		# allow access to our web server
376
		$cprules .= "add {$rulenum} set 1 pass tcp from any to {$cpip} 8000 in \n";
377
		$rulenum++;
378
		$cprules .= "add {$rulenum} set 1 pass tcp from {$cpip} 8000 to any out \n";
379

    
380
		if (isset($config['captiveportal']['httpslogin'])) {
381
			$rulenum++;
382
			$cprules .= "add {$rulenum} set 1 pass tcp from any to {$cpip} 8001 in \n";
383
			$rulenum++;
384
			$cprules .= "add {$rulenum} set 1 pass tcp from {$cpip} 8001 to any out \n";
385
		}
386
	}
387

    
388
	$rulenum++;
389
        $cprules .= <<<EOD
390

    
391
# ... 10000-19899: rules per authenticated client go here...
392

    
393
# redirect non-authenticated clients to captive portal
394
add 19902 set 1 fwd 127.0.0.1,8000 tcp from any to any 80 in
395
# let the responses from the captive portal web server back out
396
add 19903 set 1 pass tcp from any 80 to any out
397
# block everything else
398
add 19904 set 1 deny all from any to any
399

    
400
# ... 20000-29899: layer2 block rules per authenticated client go here...
401

    
402
# pass everything else on layer2
403
add 29900 set 1 pass all from any to any layer2
404

    
405
EOD;
406

    
407
    return $cprules;
408
}
409

    
410
/* remove clients that have been around for longer than the specified amount of time */
411
/* db file structure:
412
timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time */
413

    
414
/* (password is in Base64 and only saved when reauthentication is enabled) */
415
function captiveportal_prune_old() {
416

    
417
    global $g, $config;
418

    
419
    /* check for expired entries */
420
    if ($config['captiveportal']['timeout'])
421
        $timeout = $config['captiveportal']['timeout'] * 60;
422
    else
423
        $timeout = 0;
424

    
425
    if ($config['captiveportal']['idletimeout'])
426
        $idletimeout = $config['captiveportal']['idletimeout'] * 60;
427
    else
428
        $idletimeout = 0;
429

    
430
    if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && 
431
		!isset($config['captiveportal']['radiussession_timeout']) && !isset($config['voucher']['enable']))
432
        return;
433

    
434
    $captiveportallck = lock('captiveportal');
435

    
436
    /* read database */
437
    $cpdb = captiveportal_read_db();
438

    
439
    $radiusservers = captiveportal_get_radius_servers();
440

    
441
 	/*  To make sure we iterate over ALL accounts on every run the count($cpdb) is moved outside of the loop. Otherwise
442
     *  the loop would evalate count() on every iteration and since $i would increase and count() would decrement they
443
     *  would meet before we had a chance to iterate over all accounts.
444
     */
445
    $unsetindexes = array();
446
    $no_users = count($cpdb);
447
    for ($i = 0; $i < $no_users; $i++) {
448

    
449
        $timedout = false;
450
        $term_cause = 1;
451

    
452
		/* no pruning for fixed mac address entry */
453
		if (portal_mac_fixed($cpdb[$i][3])) {
454
			continue; // check next value
455
		}
456
        /* hard timeout? */
457
        if ($timeout) {
458
            if ((time() - $cpdb[$i][0]) >= $timeout) {
459
                $timedout = true;
460
                $term_cause = 5; // Session-Timeout
461
            }
462
        }
463

    
464
        /* Session-Terminate-Time */
465
        if (!$timedout && !empty($cpdb[$i][9])) {
466
            if (time() >= $cpdb[$i][9]) {
467
                $timedout = true;
468
                $term_cause = 5; // Session-Timeout
469
            }
470
        }
471

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

    
488
	/* if vouchers are configured, activate session timeouts */
489
	if (!$timedout && isset($config['voucher']['enable']) && !empty($cpdb[$i][7])) {
490
		if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
491
			$timedout = true;
492
			$term_cause = 5; // Session-Timeout
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
	    $unsetindexes[$i] = $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
	        $unsetindexes[$i] = $i;
563
            }
564
        }
565
    }
566
    /* This is a kludge to overcome some php weirdness */
567
    foreach($unsetindexes as $unsetindex)
568
	unset($cpdb[$unsetindex]);
569

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

    
573
    unlock($captiveportallck);
574
}
575

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

    
579
	global $g, $config;
580

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

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

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

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

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

    
614
}
615

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

    
619
	global $g, $config;
620

    
621
	$captiveportallck = lock('captiveportal');
622

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

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

    
640
	/* write database */
641
	captiveportal_write_db($cpdb);
642

    
643
	unlock($captiveportallck);
644
}
645

    
646
/* send RADIUS acct stop for all current clients */
647
function captiveportal_radius_stop_all($lock = false) {
648
	global $g, $config;
649

    
650
	if (!isset($config['captiveportal']['radacct_enable']))
651
		return;
652

    
653
	if (!$lock)
654
		$captiveportallck = lock('captiveportal');
655

    
656
	$cpdb = captiveportal_read_db();
657

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

    
677
function captiveportal_passthrumac_configure($lock = false) {
678
	global $config, $g;
679

    
680
	if (!$lock)
681
		$captiveportallck = lock('captiveportal');
682

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

    
686
	if (is_array($config['captiveportal']['passthrumac'])) {
687

    
688
		$fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db", "w");
689
		if (!$fd) {
690
			printf("Error: cannot open passthru mac DB file in captiveportal_passthrumac_configure().\n");
691
			unlock($captiveportallck);
692
			return 1;
693
		}
694

    
695
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
696
			/* record passthru mac so it can be recognized and let thru */
697
			fwrite($fd, $macent['mac'] . "\n");
698
		}
699

    
700
		fclose($fd);
701
	}
702

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

    
721
	if (!$lock)
722
		unlock($captiveportallck);
723

    
724
	return 0;
725
}
726

    
727
function captiveportal_allowedip_configure() {
728
	global $config, $g;
729

    
730
	/* clear out existing allowed ips, if necessary */
731
	mwexec("/sbin/ipfw table 1 flush");
732
	mwexec("/sbin/ipfw table 2 flush");
733
	if (file_exists("{$g['vardb_path']}/captiveportal_ip.db")) {
734
		$ruleno = intval(file_get_contents("{$g['vardb_path']}/captiveportal_ip.db"));
735
		mwexec("/sbin/ipfw delete {$ruleno}");
736
	}
737

    
738
	if (is_array($config['captiveportal']['allowedip'])) {
739

    
740
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "w");
741
		if (!$fd) {
742
			printf("Error: cannot open allowed ip DB file in captiveportal_allowedip_configure().\n");
743
			unlock($captiveportallck);
744
			return 1;
745
		}
746
		/* get next ipfw rule number */
747
		$ruleno = captiveportal_get_next_ipfw_ruleno();
748

    
749
		/* if the pool is empty, return apprioriate message and fail */
750
		if (is_null($ruleno)) {
751
			printf("Error: system reached maximum login capacity, no free FW rulenos in captiveportal_allowedip_configure().\n");
752
			fclose($fd);
753
			unlock($captiveportallck);
754
			return 1;
755
		}
756
		/* Keep the rule number where this will be stored */
757
		fwrite($fd, $ruleno);
758
		fclose($fd);
759

    
760
		$numberofallowedip = count($config['captiveportal']['allowedip']);
761
		$tableone = false;
762
		$tabletwo = false;
763
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
764
			/* insert address in ipfw table */
765
			if ($ipent['dir'] == "from") {
766
				mwexec("/sbin/ipfw table 1 add {$ipent['ip']}");
767
				$tableone = true;
768
			} else { 
769
				mwexec("/sbin/ipfw table 2 add {$ipent['ip']}");
770
				$tabletwo = true;
771
			}
772
		}
773
		if ($tableone == true) {
774
			mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from table\(1\) to any in");
775
			mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to table\(1\) out");
776
		}
777
		if ($tabletwo == true) {
778
			mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to table\(2\) in");
779
			mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from table\(2\) to any out");
780
		}
781
	}
782

    
783
    return 0;
784
}
785

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

    
789
	$ipfwoutput = "";
790

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

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

    
800
	return 0;
801
}
802

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

    
806
        global $g;
807

    
808
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
809
                $radiusservers = array();
810
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius.db", 
811
			FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
812
		if ($cpradiusdb)
813
		foreach($cpradiusdb as $cpradiusentry) {
814
                	$line = trim($cpradiusentry);
815
                        if ($line) {
816
                        	$radsrv = array();
817
                                list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
818
                        	$radiusservers[] = $radsrv;
819
                        }
820
		}
821

    
822
		return $radiusservers;
823
        }
824

    
825
        return false;
826
}
827

    
828
/* log successful captive portal authentication to syslog */
829
/* part of this code from php.net */
830
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
831
	$message = trim($message);
832
	// Log it
833
	if (!$message)
834
		$message = "$status: $user, $mac, $ip";
835
	else
836
		$message = "$status: $user, $mac, $ip, $message";
837
	captiveportal_syslog($message);
838
	closelog();
839
}
840

    
841
/* log simple messages to syslog */
842
function captiveportal_syslog($message) {
843
	define_syslog_variables();
844
	$message = trim($message);
845
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
846
	// Log it
847
	syslog(LOG_INFO, $message);
848
	closelog();
849
}
850

    
851
function radius($username,$password,$clientip,$clientmac,$type) {
852
    global $g, $config;
853

    
854
    /* Start locking from the beginning of an authentication session */
855
    $captiveportallck = lock('captiveportal');
856

    
857
    $ruleno = captiveportal_get_next_ipfw_ruleno();
858

    
859
    /* if the pool is empty, return apprioriate message and fail authentication */
860
    if (is_null($ruleno)) {
861
        $auth_list = array();
862
        $auth_list['auth_val'] = 1;
863
        $auth_list['error'] = "System reached maximum login capacity";
864
        unlock($captiveportallck);
865
        return $auth_list;
866
    }
867

    
868
    /*
869
     * Drop the lock since radius takes some time to finish.
870
     * The implementation is reentrant so we gain speed with this.
871
     */
872
    unlock($captiveportallck);
873

    
874
    $radiusservers = captiveportal_get_radius_servers();
875

    
876
    $auth_list = RADIUS_AUTHENTICATION($username,
877
                    $password,
878
                    $radiusservers,
879
                    $clientip,
880
                    $clientmac,
881
                    $ruleno);
882

    
883
    $captiveportallck = lock('captiveportal');
884

    
885
    if ($auth_list['auth_val'] == 2) {
886
        captiveportal_logportalauth($username,$clientmac,$clientip,$type);
887
        $sessionid = portal_allow($clientip,
888
                    $clientmac,
889
                    $username,
890
                    $password,
891
                    $auth_list,
892
                    $ruleno);
893
    }
894

    
895
    unlock($captiveportallck);
896

    
897
    return $auth_list;
898

    
899
}
900

    
901
/* read captive portal DB into array */
902
function captiveportal_read_db() {
903

    
904
        global $g;
905

    
906
        $cpdb = array();
907
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
908
        if ($fd) {
909
                while (!feof($fd)) {
910
                        $line = trim(fgets($fd));
911
                        if ($line) {
912
                                $cpdb[] = explode(",", $line);
913
                        }
914
                }
915
                fclose($fd);
916
        }
917
        return $cpdb;
918
}
919

    
920
/* write captive portal DB */
921
function captiveportal_write_db($cpdb) {
922
                 
923
        global $g;
924
                
925
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
926
        if ($fd) { 
927
                foreach ($cpdb as $cpent) {
928
                        fwrite($fd, join(",", $cpent) . "\n");
929
                }       
930
                fclose($fd);
931
        }       
932
}
933

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

    
970
/*
971
 * This function will calculate the lowest free firewall ruleno
972
 * within the range specified based on the actual installed rules
973
 *
974
 */
975
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 10000, $rulenos_range_max = 9899) {
976

    
977
	$fwrules = "";
978
	$matches = "";
979
	exec("/sbin/ipfw show", $fwrules);
980
	foreach ($fwrules as $fwrule) {
981
		preg_match("/^(\d+)\s+/", $fwrule, $matches);
982
		$rulenos_used[] = $matches[1];
983
	}
984
	$rulenos_used = array_unique($rulenos_used);
985
	$rulenos_range = count($rulenos_used);
986
	if ($rulenos_range > $rulenos_range_max) {
987
		return NULL;
988
	}
989
	$rulenos_pool = range($rulenos_start, ($rulenos_start + $rulenos_range));
990
	$rulenos_free = array_diff($rulenos_pool, $rulenos_used);
991
	$ruleno = array_shift($rulenos_free);
992

    
993
	return $ruleno;
994
}
995

    
996
/**
997
 * This function will calculate the traffic produced by a client
998
 * based on its firewall rule
999
 *
1000
 * Point of view: NAS
1001
 *
1002
 * Input means: from the client
1003
 * Output means: to the client
1004
 *
1005
 */
1006

    
1007
function getVolume($ruleno) {
1008

    
1009
    $volume = array();
1010

    
1011
    // Initialize vars properly, since we don't want NULL vars
1012
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1013

    
1014
    // Ingress
1015
    $ipfw = "";
1016
    $matches = "";
1017
    exec("/sbin/ipfw show {$ruleno}", $ipfw);
1018
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[0], $matches);
1019
    $volume['input_pkts'] = $matches[2];
1020
    $volume['input_bytes'] = $matches[3];
1021

    
1022
    // Flush internal buffer
1023
    unset($matches);
1024

    
1025
    // Outgress
1026
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[1], $matches);
1027
    $volume['output_pkts'] = $matches[2];
1028
    $volume['output_bytes'] = $matches[3];
1029

    
1030
    return $volume;
1031
}
1032

    
1033
/**
1034
 * Get the NAS-Identifier
1035
 *
1036
 * We will use our local hostname to make up the nas_id
1037
 */
1038
function getNasID()
1039
{
1040
    $nasId = "";
1041
    exec("/bin/hostname", $nasId);
1042
    if(!$nasId[0])
1043
        $nasId[0] = "{$g['product_name']}";
1044
    return $nasId[0];
1045
}
1046

    
1047
/**
1048
 * Get the NAS-IP-Address based on the current wan address
1049
 *
1050
 * Use functions in interfaces.inc to find this out
1051
 *
1052
 */
1053

    
1054
function getNasIP()
1055
{
1056
    $nasIp = get_interface_ip();
1057
    if(!$nasIp)
1058
        $nasIp = "0.0.0.0";
1059
    return $nasIp;
1060
}
1061

    
1062
function portal_mac_fixed($clientmac) {
1063
    global $g ;
1064

    
1065
    /* open captive portal mac db */
1066
    if (file_exists("{$g['vardb_path']}/captiveportal_mac.db")) {
1067
        $fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db","r") ;
1068
        if (!$fd) {
1069
            return FALSE;
1070
        }
1071
        while (!feof($fd)) {
1072
            $mac = trim(fgets($fd)) ;
1073
            if(strcasecmp($clientmac, $mac) == 0) {
1074
                fclose($fd) ;
1075
                return TRUE ;
1076
            }
1077
        }
1078
        fclose($fd) ;
1079
    }
1080
    return FALSE ;
1081
}
1082

    
1083
function portal_ip_from_client_ip($cliip) {
1084
	global $config;
1085

    
1086
	$interfaces = explode(",", $config['captiveportal']['interface']);
1087
	foreach ($interfaces as $cpif) {
1088
		$ip = get_interface_ip($cpif);
1089
		$sn = get_interface_subnet($cpif);
1090
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1091
			return $ip;
1092
	}
1093

    
1094
	return false;
1095
}
1096

    
1097
?>
(6-6/43)