Project

General

Profile

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

    
6
	Copyright (C) 2009 Ermal Lu?i
7
	Copyright (C) 2003-2006 Manuel Kasper <mk@neon1.net>.
8
	All rights reserved.
9

    
10
	Redistribution and use in source and binary forms, with or without
11
	modification, are permitted provided that the following conditions are met:
12

    
13
	1. Redistributions of source code must retain the above copyright notice,
14
	   this list of conditions and the following disclaimer.
15

    
16
	2. Redistributions in binary form must reproduce the above copyright
17
	   notice, this list of conditions and the following disclaimer in the
18
	   documentation and/or other materials provided with the distribution.
19

    
20
	THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
21
	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
22
	AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23
	AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
24
	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
	POSSIBILITY OF SUCH DAMAGE.
30

    
31
	This version of captiveportal.inc has been modified by Rob Parker
32
	<rob.parker@keycom.co.uk> to include changes for per-user bandwidth management
33
	via returned RADIUS attributes. This page has been modified to delete any
34
	added rules which may have been created by other per-user code (index.php, etc).
35
	These changes are (c) 2004 Keycom PLC.
36
	
37
	pfSense_BUILDER_BINARIES:	/sbin/ifconfig	/sbin/ipfw	/sbin/sysctl	/sbin/kldunload
38
	pfSense_BUILDER_BINARIES:	/usr/local/sbin/lighttpd	/usr/local/bin/minicron	/sbin/pfctl
39
	pfSense_BUILDER_BINARIES:	/bin/hostname	/bin/cp	
40
	pfSense_MODULE:	captiveportal
41
*/
42

    
43
/* include all configuration functions */
44
require_once("config.inc");
45
require_once("functions.inc");
46
require_once("radius_authentication.inc");
47
require_once("radius_accounting.inc");
48
require_once("radius.inc");
49
require_once("voucher.inc");
50

    
51
function captiveportal_configure() {
52
	global $config, $g;
53

    
54
	$captiveportallck = lock('captiveportal');
55
	
56
	$cpactive = false;
57
	if (isset($config['captiveportal']['enable'])) {
58
		$cpips = array();
59
		$ifaces = get_configured_interface_list();
60
		foreach ($ifaces as $kiface => $kiface2) {
61
			$tmpif = get_real_interface($kiface);
62
			mwexec("/sbin/ifconfig {$tmpif} -ipfwfilter");
63
		}
64
		$cpinterfaces = explode(",", $config['captiveportal']['interface']);
65
		$firsttime = 0;
66
		foreach ($cpinterfaces as $cpifgrp) {
67
			if (!isset($ifaces[$cpifgrp]))
68
				continue;
69
			$tmpif = get_real_interface($cpifgrp);
70
			if (!empty($tmpif)) {
71
				if ($firsttime > 0)
72
					$cpinterface .= " or ";
73
				$cpinterface .= "via {$tmpif}"; 
74
				$firsttime = 1;
75
				$cpipm = get_interface_ip($cpifgrp);
76
				if (is_ipaddr($cpipm)) {
77
					$carpif = link_ip_to_carp_interface($cpipm);
78
					if (!empty($carpif)) {
79
						$carpsif = explode(" ", $carpif);
80
						foreach ($carpsif as $cpcarp) {
81
							mwexec("/sbin/ifconfig {$cpcarp} ipfwfilter");
82
							$carpip = find_interface_ip($cpcarp);
83
							if (is_ipaddr($carpip))
84
								$cpips[] = $carpip;
85
						}
86
					}
87
					$cpips[] = $cpipm;
88
					mwexec("/sbin/ifconfig {$tmpif} ipfwfilter");
89
				}
90
			}
91
		}
92
		if (count($cpips) > 0) {
93
			$cpactive = true;
94
			$cpinterface = "{ {$cpinterface} } ";
95
		}
96
	}
97

    
98
	if ($cpactive == true) {
99

    
100
		if ($g['booting'])
101
			echo "Starting captive portal... ";
102

    
103
		/* kill any running mini_httpd */
104
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
105
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal-SSL.pid");
106

    
107
		/* kill any running minicron */
108
		killbypid("{$g['varrun_path']}/minicron.pid");
109

    
110
		/* make sure ipfw is loaded */
111
		if (!is_module_loaded("ipfw.ko"))
112
			filter_load_ipfw();
113
		if (isset($config['captiveportal']['peruserbw']) && !is_module_loaded("dummynet.ko"))
114
                        mwexec("/sbin/kldload dummynet");
115

    
116
		/* generate ipfw rules */
117
		$cprules = captiveportal_rules_generate($cpinterface, $cpips);
118

    
119
		/* stop accounting on all clients */
120
		captiveportal_radius_stop_all(true);
121

    
122
		/* initialize minicron interval value */
123
		$croninterval = $config['captiveportal']['croninterval'] ? $config['captiveportal']['croninterval'] : 60;
124

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

    
128
		/* remove old information */
129
		unlink_if_exists("{$g['vardb_path']}/captiveportal.nextrule");
130
		unlink_if_exists("{$g['vardb_path']}/captiveportal.db");
131
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
132
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip.db");
133
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius.db");
134
		mwexec("/sbin/ipfw table all flush");
135

    
136
		/* setup new database in case someone tries to access the status -> captive portal page */
137
		touch("{$g['vardb_path']}/captiveportal.db");
138

    
139
		/* write portal page */
140
		if ($config['captiveportal']['page']['htmltext'])
141
			$htmltext = base64_decode($config['captiveportal']['page']['htmltext']);
142
		else {
143
			/* example/template page */
144
			$htmltext = <<<EOD
145
<html>
146
<head>
147
<title>{$g['product_name']} captive portal</title>
148
</head>
149
<body>
150
<center>
151
<h2>{$g['product_name']} captive portal</h2>
152
Welcome to the {$g['product_name']} Captive Portal!  This is the default page since a custom page has not been defined.
153
<p>
154
<form method="post" action="\$PORTAL_ACTION\$">
155
<input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$">
156
<table>
157
   <tr><td>Username:</td><td><input name="auth_user" type="text"></td></tr>
158
   <tr><td>Password:</td><td><input name="auth_pass" type="password"></td></tr>
159
   <tr><td>&nbsp;</td></tr>
160
   <tr>
161
     <td colspan="2">
162
	<center><input name="accept" type="submit" value="Continue"></center>
163
     </td>
164
   </tr>
165
</table>
166
</center>
167
</form>
168
</body>
169
</html>
170

    
171

    
172

    
173
EOD;
174
		}
175

    
176
		$fd = @fopen("{$g['varetc_path']}/captiveportal.html", "w");
177
		if ($fd) {
178
			fwrite($fd, $htmltext);
179
			fclose($fd);
180
		}
181

    
182
		/* write error page */
183
		if ($config['captiveportal']['page']['errtext'])
184
			$errtext = base64_decode($config['captiveportal']['page']['errtext']);
185
		else {
186
			/* example page */
187
			$errtext = <<<EOD
188
<html>
189
<head>
190
<title>Authentication error</title>
191
</head>
192
<body>
193
<font color="#cc0000"><h2>Authentication error</h2></font>
194
<b>
195
Username and/or password invalid.
196
<br><br>
197
<a href="javascript:history.back(); ">Go back</a>
198
</b>
199
</body>
200
</html>
201

    
202
EOD;
203
		}
204

    
205
		$fd = @fopen("{$g['varetc_path']}/captiveportal-error.html", "w");
206
		if ($fd) {
207
			fwrite($fd, $errtext);
208
			fclose($fd);
209
		}
210

    
211
		/* write elements */
212
		captiveportal_write_elements();
213

    
214
		/* load rules */
215
		mwexec("/sbin/ipfw -f delete set 1");
216

    
217
		/* ipfw cannot accept rules directly on stdin,
218
		   so we have to write them to a temporary file first */
219
		$fd = @fopen("{$g['tmp_path']}/ipfw.cp.rules", "w");
220
		if (!$fd) {
221
			printf("Cannot open ipfw.cp.rules in captiveportal_configure()\n");
222
			return 1;
223
		}
224

    
225
		fwrite($fd, $cprules);
226
		fclose($fd);
227

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

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

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

    
235
		chdir($g['captiveportal_path']);
236

    
237
		if ($config['captiveportal']['maxproc'])
238
			$maxproc = $config['captiveportal']['maxproc'];
239
		else
240
			$maxproc = 16;
241

    
242
		$use_fastcgi = true;
243

    
244
		if(isset($config['captiveportal']['httpslogin'])) {
245
			$cert = base64_decode($config['captiveportal']['certificate']);
246
			if (isset($config['captiveportal']['cacertificate']))
247
				$cacert = base64_decode($config['captiveportal']['cacertificate']);
248
			else
249
				$cacert = "";
250
			$key = base64_decode($config['captiveportal']['private-key']);
251
			/* generate lighttpd configuration */
252
			system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal-SSL.conf",
253
				$cert, $key, $cacert, "lighty-CaptivePortal-ssl.pid", "8001", "/usr/local/captiveportal/",
254
					"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
255
		}
256

    
257
		/* generate lighttpd configuration */
258
		system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal.conf",
259
			"", "", "", "lighty-CaptivePortal.pid", "8000", "/usr/local/captiveportal/",
260
				"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
261

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

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

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

    
273
		/* generate passthru mac database */
274
		captiveportal_passthrumac_configure(true);
275
		/* allowed ipfw rules to make allowed ip work */
276
		captiveportal_allowedip_configure();
277

    
278
		/* generate radius server database */
279
		if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
280
				($config['captiveportal']['auth_method'] == "radius"))) {
281
			$radiusip = $config['captiveportal']['radiusip'];
282
			$radiusip2 = ($config['captiveportal']['radiusip2']) ? $config['captiveportal']['radiusip2'] : null;
283

    
284
			if ($config['captiveportal']['radiusport'])
285
				$radiusport = $config['captiveportal']['radiusport'];
286
			else
287
				$radiusport = 1812;
288

    
289
			if ($config['captiveportal']['radiusacctport'])
290
				$radiusacctport = $config['captiveportal']['radiusacctport'];
291
			else
292
				$radiusacctport = 1813;
293

    
294
			if ($config['captiveportal']['radiusport2'])
295
				$radiusport2 = $config['captiveportal']['radiusport2'];
296
			else
297
				$radiusport2 = 1812;
298

    
299
			$radiuskey = $config['captiveportal']['radiuskey'];
300
			$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
301

    
302
			$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db", "w");
303
			if (!$fd) {
304
				printf("Error: cannot open radius DB file in captiveportal_configure().\n");
305
				return 1;
306
			} else if (isset($radiusip2, $radiuskey2)) {
307
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . "\n"
308
					 . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2);
309
			} else {
310
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey);
311
			}
312
			fclose($fd);
313
		}
314

    
315
		if ($g['booting'])
316
			echo "done\n";
317

    
318
	} else {
319
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
320
		killbypid("{$g['varrun_path']}/minicron.pid");
321

    
322
		captiveportal_radius_stop_all(true);
323

    
324
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
325

    
326
		/* unload ipfw */
327
		if (is_module_loaded("ipfw.ko"))		
328
			mwexec("/sbin/kldunload ipfw.ko");
329
		$listifs = get_configured_interface_list_by_realif();
330
		foreach ($listifs as $listrealif => $listif) {
331
			if (!empty($listrealif)) {
332
				mwexec("/sbin/ifconfig {$listrealif} -ipfwfilter");
333
				$carpif = link_ip_to_carp_interface(find_interface_ip($listrealif));
334
                        	if (!empty($carpif)) {
335
					$carpsif = explode(" ", $carpif);
336
					foreach ($carpsif as $cpcarp)
337
						mwexec("/sbin/ifconfig {$cpcarp} -ipfwfilter");
338
				}
339
			}
340
		}
341
	}
342

    
343
	unlock($captiveportallck);
344
	
345
	return 0;
346
}
347

    
348
function captiveportal_rules_generate($cpif, &$cpiparray) {
349
	global $config, $g;
350

    
351
	$cpifn = $config['captiveportal']['interface'];
352
	$lanip = get_interface_ip("lan");
353
	
354
	/* note: the captive portal daemon inserts all pass rules for authenticated
355
	   clients as skipto 50000 rules to make traffic shaping work */
356

    
357
	$cprules =  "add 500 set 1 allow pfsync from any to any\n";
358
	$cprules .= "add 500 set 1 allow carp from any to any\n";
359

    
360
	$cprules .= <<<EOD
361
add 1000 set 1 skipto 1150 all from any to any not layer2
362
# layer 2: pass ARP
363
add 1100 set 1 pass layer2 mac-type arp
364
# pfsense requires for WPA
365
add 1100 set 1 pass layer2 mac-type 0x888e
366
add 1100 set 1 pass layer2 mac-type 0x88c7
367

    
368
# PPP Over Ethernet Discovery Stage
369
add 1100 set 1 pass layer2 mac-type 0x8863
370
# PPP Over Ethernet Session Stage
371
add 1100 set 1 pass layer2 mac-type 0x8864
372
# Allow WPA
373
add 1100 set 1 pass layer2 mac-type 0x888e
374

    
375
# layer 2: block anything else non-IP
376
add 1101 set 1 deny layer2 not mac-type ip
377

    
378
EOD;
379

    
380
	$rulenum = 1150;
381
	$ips = "255.255.255.255 ";
382
	foreach ($cpiparray as $cpip)
383
		$ips .= "or {$cpip} ";
384
	$ips = "{ {$ips} }";
385
	//# allow access to our DHCP server (which needs to be able to ping clients as well)
386
	$cprules .= "add {$rulenum} set 1 pass udp from any 68 to {$ips} 67 in \n";
387
	$rulenum++;
388
	$cprules .= "add {$rulenum} set 1 pass udp from any 68 to {$ips} 67 in \n";
389
	$rulenum++;
390
	$cprules .= "add {$rulenum} set 1 pass udp from {$ips} 67 to any 68 out \n";
391
	$rulenum++;
392
	$cprules .= "add {$rulenum} set 1 pass icmp from {$ips} to any out icmptype 0\n";
393
	$rulenum++;
394
	$cprules .= "add {$rulenum} set 1 pass icmp from any to {$ips} in icmptype 8 \n";
395
	$rulenum++;
396
	//# allow access to our DNS forwarder
397
	$cprules .= "add {$rulenum} set 1 pass udp from any to {$ips} 53 in \n";
398
	$rulenum++;
399
	$cprules .= "add {$rulenum} set 1 pass udp from {$ips} 53 to any out \n";
400
	$rulenum++;
401
	# allow access to our web server
402
	$cprules .= "add {$rulenum} set 1 pass tcp from any to {$ips} 8000 in \n";
403
	$rulenum++;
404
	$cprules .= "add {$rulenum} set 1 pass tcp from {$ips} 8000 to any out \n";
405

    
406
	if (isset($config['captiveportal']['httpslogin'])) {
407
		$rulenum++;
408
		$cprules .= "add {$rulenum} set 1 pass tcp from any to {$ips} 8001 in \n";
409
		$rulenum++;
410
		$cprules .= "add {$rulenum} set 1 pass tcp from {$ips} 8001 to any out \n";
411
	}
412
	if (!empty($config['system']['webgui']['port']))
413
		$port = $config['system']['webgui']['port'];
414
	else if ($config['system']['webgui']['proto'] == "https")
415
		$port = 443;
416
	else
417
		$port = 80;
418
	$rulenum++;
419
	$cprules .= "add {$rulenum} set 1 pass tcp from any to {$ips} {$port} in \n";
420
	$rulenum++;
421
	$cprules .= "add {$rulenum} set 1 pass tcp from {$ips} {$port} to any out \n";
422
	$rulenum++;
423

    
424
	if (isset($config['captiveportal']['peruserbw'])) {
425
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from table(3) to any in\n";
426
		$rulenum++;
427
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from any to table(4) out\n";
428
		$rulenum++;
429
	} else {
430
		$cprules .= "add {$rulenum} set 1 skipto 50000 ip from table(3) to any in\n";
431
                $rulenum++;
432
                $cprules .= "add {$rulenum} set 1 skipto 50000 ip from any to table(4) out\n";
433
                $rulenum++;
434
	}
435
	
436
       $cprules .= <<<EOD
437

    
438
# redirect non-authenticated clients to captive portal
439
add 1990 set 1 fwd 127.0.0.1,8000 tcp from any to any in
440
# let the responses from the captive portal web server back out
441
add 1991 set 1 pass tcp from any to any out
442
# block everything else
443
add 1992 set 1 deny all from any to any
444

    
445
# ... 2000-49899: layer2 block rules per authenticated client go here...
446

    
447
# pass everything else on layer2
448
add 49900 set 1 pass all from any to any layer2
449

    
450
EOD;
451

    
452
    return $cprules;
453
}
454

    
455
/* remove clients that have been around for longer than the specified amount of time */
456
/* db file structure:
457
timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time */
458

    
459
/* (password is in Base64 and only saved when reauthentication is enabled) */
460
function captiveportal_prune_old() {
461

    
462
    global $g, $config;
463

    
464
    /* check for expired entries */
465
    if ($config['captiveportal']['timeout'])
466
        $timeout = $config['captiveportal']['timeout'] * 60;
467
    else
468
        $timeout = 0;
469

    
470
    if ($config['captiveportal']['idletimeout'])
471
        $idletimeout = $config['captiveportal']['idletimeout'] * 60;
472
    else
473
        $idletimeout = 0;
474

    
475
    if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && 
476
		!isset($config['captiveportal']['radiussession_timeout']) && !isset($config['voucher']['enable']))
477
        return;
478

    
479
    $captiveportallck = lock('captiveportal');
480

    
481
    /* read database */
482
    $cpdb = captiveportal_read_db();
483

    
484
    $radiusservers = captiveportal_get_radius_servers();
485

    
486
    /*  To make sure we iterate over ALL accounts on every run the count($cpdb) is moved
487
     *  outside of the loop. Otherwise the loop would evaluate count() on every iteration
488
     *  and since $i would increase and count() would decrement they would meet before we
489
     *  had a chance to iterate over all accounts.
490
     */
491
    $unsetindexes = array();
492
    $no_users = count($cpdb);
493
    for ($i = 0; $i < $no_users; $i++) {
494

    
495
        $timedout = false;
496
        $term_cause = 1;
497

    
498
		/* no pruning for fixed mac address entry */
499
		if (portal_mac_fixed($cpdb[$i][3])) {
500
			continue; // check next value
501
		}
502
        /* hard timeout? */
503
        if ($timeout) {
504
            if ((time() - $cpdb[$i][0]) >= $timeout) {
505
                $timedout = true;
506
                $term_cause = 5; // Session-Timeout
507
            }
508
        }
509

    
510
        /* Session-Terminate-Time */
511
        if (!$timedout && !empty($cpdb[$i][9])) {
512
            if (time() >= $cpdb[$i][9]) {
513
                $timedout = true;
514
                $term_cause = 5; // Session-Timeout
515
            }
516
        }
517

    
518
        /* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
519
        $idletimeout = (is_numeric($cpdb[$i][8])) ? $cpdb[$i][8] : $idletimeout;
520
        /* if an idle timeout is specified, get last activity timestamp from ipfw */
521
        if (!$timedout && $idletimeout) {
522
            $lastact = captiveportal_get_last_activity($cpdb[$i][2]);
523
			/*  If the user has logged on but not sent any traffic they will never be logged out.
524
			 *  We "fix" this by setting lastact to the login timestamp. 
525
			 */
526
			$lastact = $lastact ? $lastact : $cpdb[$i][0];
527
            if ($lastact && ((time() - $lastact) >= $idletimeout)) {
528
                $timedout = true;
529
                $term_cause = 4; // Idle-Timeout
530
                $stop_time = $lastact; // Entry added to comply with WISPr
531
            }
532
        }
533

    
534
	/* if vouchers are configured, activate session timeouts */
535
	if (!$timedout && isset($config['voucher']['enable']) && !empty($cpdb[$i][7])) {
536
		if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
537
			$timedout = true;
538
			$term_cause = 5; // Session-Timeout
539
		}
540
	}
541

    
542
        /* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
543
        if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpdb[$i][7])) {
544
            if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
545
                $timedout = true;
546
                $term_cause = 5; // Session-Timeout
547
            }
548
        }
549

    
550
        if ($timedout) {
551
            captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
552
            captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
553
	    $unsetindexes[$i] = $i;
554
        }
555

    
556
        /* do periodic RADIUS reauthentication? */
557
        if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
558
            !empty($radiusservers)) {
559

    
560
            if (isset($config['captiveportal']['radacct_enable'])) {
561
                if ($config['captiveportal']['reauthenticateacct'] == "stopstart") {
562
                    /* stop and restart accounting */
563
                    RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
564
                                           $cpdb[$i][4], // username
565
                                           $cpdb[$i][5], // sessionid
566
                                           $cpdb[$i][0], // start time
567
                                           $radiusservers,
568
                                           $cpdb[$i][2], // clientip
569
                                           $cpdb[$i][3], // clientmac
570
                                           10); // NAS Request
571
                    exec("/sbin/ipfw table 3 entryzerostats {$cpdb[$i][2]}");
572
                    exec("/sbin/ipfw table 4 entryzerostats {$cpdb[$i][2]}");
573
                    RADIUS_ACCOUNTING_START($cpdb[$i][1], // ruleno
574
                                            $cpdb[$i][4], // username
575
                                            $cpdb[$i][5], // sessionid
576
                                            $radiusservers,
577
                                            $cpdb[$i][2], // clientip
578
                                            $cpdb[$i][3]); // clientmac
579
                } else if ($config['captiveportal']['reauthenticateacct'] == "interimupdate") {
580
                    RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
581
                                           $cpdb[$i][4], // username
582
                                           $cpdb[$i][5], // sessionid
583
                                           $cpdb[$i][0], // start time
584
                                           $radiusservers,
585
                                           $cpdb[$i][2], // clientip
586
                                           $cpdb[$i][3], // clientmac
587
                                           10, // NAS Request
588
                                           true); // Interim Updates
589
                }
590
            }
591

    
592
            /* check this user against RADIUS again */
593
            $auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
594
                                          base64_decode($cpdb[$i][6]), // password
595
                                            $radiusservers,
596
                                          $cpdb[$i][2], // clientip
597
                                          $cpdb[$i][3], // clientmac
598
                                          $cpdb[$i][1]); // ruleno
599

    
600
            if ($auth_list['auth_val'] == 3) {
601
                captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
602
                captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
603
	        $unsetindexes[$i] = $i;
604
            }
605
        }
606
    }
607
    /* This is a kludge to overcome some php weirdness */
608
    foreach($unsetindexes as $unsetindex)
609
	unset($cpdb[$unsetindex]);
610

    
611
    /* write database */
612
    captiveportal_write_db($cpdb);
613

    
614
    unlock($captiveportallck);
615
}
616

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

    
620
	global $g, $config;
621

    
622
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
623

    
624
	/* this client needs to be deleted - remove ipfw rules */
625
	if (isset($config['captiveportal']['radacct_enable']) && !empty($radiusservers)) {
626
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
627
							   $dbent[4], // username
628
							   $dbent[5], // sessionid
629
							   $dbent[0], // start time
630
							   $radiusservers,
631
							   $dbent[2], // clientip
632
							   $dbent[3], // clientmac
633
							   $term_cause, // Acct-Terminate-Cause
634
							   false,
635
							   $stop_time);
636
	}
637
	/* Delete client's ip entry from tables 3 and 4. */
638
	mwexec("/sbin/ipfw table 3 delete {$dbent[2]}");
639
	mwexec("/sbin/ipfw table 4 delete {$dbent[2]}");
640

    
641
	/* 
642
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
643
	* We could get an error if the pipe doesn't exist but everything should still be fine
644
	*/
645
	if (isset($config['captiveportal']['peruserbw'])) {
646
		mwexec("/sbin/ipfw pipe " . ($dbent[1]+20000) . " delete");
647
		mwexec("/sbin/ipfw pipe " . ($dbent[1]+20001) . " delete");
648
	}
649

    
650
	/* Ensure all pf(4) states are killed. */
651
	mwexec("pfctl -k {$dbent[2]}");
652
	mwexec("pfctl -K {$dbent[2]}");
653

    
654
}
655

    
656
/* remove a single client by ipfw rule number */
657
function captiveportal_disconnect_client($id,$term_cause = 1) {
658

    
659
	global $g, $config;
660

    
661
	$captiveportallck = lock('captiveportal');
662

    
663
	/* read database */
664
	$cpdb = captiveportal_read_db();
665
	$radiusservers = captiveportal_get_radius_servers();
666

    
667
	/* find entry */
668
	$tmpindex = 0;
669
	for ($i = 0; $i < count($cpdb); $i++) {
670
		if ($cpdb[$i][1] == $id) {
671
			captiveportal_disconnect($cpdb[$i], $radiusservers, $term_cause);
672
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "DISCONNECT");
673
			unset($cpdb[$i]);
674
			break;
675
		}
676
	}		
677

    
678
	/* write database */
679
	captiveportal_write_db($cpdb);
680

    
681
	unlock($captiveportallck);
682
}
683

    
684
/* send RADIUS acct stop for all current clients */
685
function captiveportal_radius_stop_all($lock = false) {
686
	global $g, $config;
687

    
688
	if (!isset($config['captiveportal']['radacct_enable']))
689
		return;
690

    
691
	if (!$lock)
692
		$captiveportallck = lock('captiveportal');
693

    
694
	$cpdb = captiveportal_read_db();
695

    
696
	$radiusservers = captiveportal_get_radius_servers();
697
	if (!empty($radiusservers)) {
698
		for ($i = 0; $i < count($cpdb); $i++) {
699
			RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
700
								   $cpdb[$i][4], // username
701
								   $cpdb[$i][5], // sessionid
702
								   $cpdb[$i][0], // start time
703
								   $radiusservers,
704
								   $cpdb[$i][2], // clientip
705
								   $cpdb[$i][3], // clientmac
706
								   7); // Admin Reboot
707
		}
708
	}
709
	if (!$lock)
710
		unlock($captiveportallck);
711
}
712

    
713
function captiveportal_passthrumac_configure($lock = false) {
714
	global $config, $g;
715

    
716
	if (!$lock)
717
		$captiveportallck = lock('captiveportal');
718

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

    
722
	if (is_array($config['captiveportal']['passthrumac'])) {
723

    
724
		$fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db", "w");
725
		if (!$fd) {
726
			printf("Error: cannot open passthru mac DB file in captiveportal_passthrumac_configure().\n");
727
			unlock($captiveportallck);
728
			return 1;
729
		}
730

    
731
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
732
			/* record passthru mac so it can be recognized and let thru */
733
			fwrite($fd, $macent['mac'] . "\n");
734
		}
735

    
736
		fclose($fd);
737
	}
738

    
739
	/*    pfSense:
740
	 * 	  pass through mac entries should always exist.  the reason
741
	 *    for this is because we do not have native mac address filtering
742
	 *    mechanisms.  this allows us to filter by mac address easily
743
	 *    and get around this limitation.   I consider this a bug in
744
	 *    m0n0wall and pfSense as m0n0wall does not have native mac
745
	 *    filtering mechanisms as well. -Scott Ullrich
746
	 */
747
	if (is_array($config['captiveportal']['passthrumac'])) {
748
		mwexec("/sbin/ipfw delete 50");
749
		foreach($config['captiveportal']['passthrumac'] as $ptm) {
750
			/* create the pass through mac entry */
751
			//system("echo /sbin/ipfw add 50 skipto 65535 ip from any to any MAC {$ptm['mac']} any > /tmp/cp");
752
			mwexec("/sbin/ipfw add 50 skipto 49900 ip from any to any MAC {$ptm['mac']} any keep-state");
753
			mwexec("/sbin/ipfw add 50 skipto 49900 ip from any to any MAC any {$ptm['mac']} keep-state");
754
		}
755
	}
756

    
757
	if (!$lock)
758
		unlock($captiveportallck);
759

    
760
	return 0;
761
}
762

    
763
function captiveportal_allowedip_configure() {
764
	global $config, $g;
765

    
766
	/* clear out existing allowed ips, if necessary */
767
	mwexec("/sbin/ipfw table 1 flush");
768
	mwexec("/sbin/ipfw table 2 flush");
769

    
770
	if (is_array($config['captiveportal']['allowedip'])) {
771
		$tableone = false;
772
		$tabletwo = false;
773
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
774
			/* insert address in ipfw table */
775
			if ($ipent['dir'] == "from") {
776
				mwexec("/sbin/ipfw table 1 add {$ipent['ip']}");
777
				$tableone = true;
778
			} else { 
779
				mwexec("/sbin/ipfw table 2 add {$ipent['ip']}");
780
				$tabletwo = true;
781
			}
782
		}
783
		if ($tableone == true) {
784
			mwexec("/sbin/ipfw add 1890 set 1 skipto 50000 ip from table\(1\) to any in");
785
			mwexec("/sbin/ipfw add 1891 set 1 skipto 50000 ip from any to table\(1\) out");
786
		}
787
		if ($tabletwo == true) {
788
			mwexec("/sbin/ipfw add 1892 set 1 skipto 50000 ip from any to table\(2\) in");
789
			mwexec("/sbin/ipfw add 1893 set 1 skipto 50000 ip from table\(2\) to any out");
790
		}
791
	}
792

    
793
    return 0;
794
}
795

    
796
/* get last activity timestamp given client IP address */
797
function captiveportal_get_last_activity($ip) {
798

    
799
	$ipfwoutput = "";
800

    
801
	exec("/sbin/ipfw table 3 entrystats {$ip} 2>/dev/null", $ipfwoutput);
802
	/* Reading only from one of the tables is enough of approximation. */
803
	if ($ipfwoutput[0]) {
804
		$ri = explode(" ", $ipfwoutput[0]);
805
		if ($ri[4])
806
			return $ri[4];
807
	}
808

    
809
	return 0;
810
}
811

    
812
/* read RADIUS servers into array */
813
function captiveportal_get_radius_servers() {
814

    
815
        global $g;
816

    
817
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
818
                $radiusservers = array();
819
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius.db", 
820
			FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
821
		if ($cpradiusdb)
822
		foreach($cpradiusdb as $cpradiusentry) {
823
                	$line = trim($cpradiusentry);
824
                        if ($line) {
825
                        	$radsrv = array();
826
                                list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
827
                        	$radiusservers[] = $radsrv;
828
                        }
829
		}
830

    
831
		return $radiusservers;
832
        }
833

    
834
        return false;
835
}
836

    
837
/* log successful captive portal authentication to syslog */
838
/* part of this code from php.net */
839
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
840
	$message = trim($message);
841
	// Log it
842
	if (!$message)
843
		$message = "$status: $user, $mac, $ip";
844
	else
845
		$message = "$status: $user, $mac, $ip, $message";
846
	captiveportal_syslog($message);
847
	closelog();
848
}
849

    
850
/* log simple messages to syslog */
851
function captiveportal_syslog($message) {
852
	define_syslog_variables();
853
	$message = trim($message);
854
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
855
	// Log it
856
	syslog(LOG_INFO, $message);
857
	closelog();
858
}
859

    
860
function radius($username,$password,$clientip,$clientmac,$type) {
861
    global $g, $config;
862

    
863
    /* Start locking from the beginning of an authentication session */
864
    $captiveportallck = lock('captiveportal');
865

    
866
    $ruleno = captiveportal_get_next_ipfw_ruleno();
867

    
868
    /* If the pool is empty, return appropriate message and fail authentication */
869
    if (is_null($ruleno)) {
870
        $auth_list = array();
871
        $auth_list['auth_val'] = 1;
872
        $auth_list['error'] = "System reached maximum login capacity";
873
        unlock($captiveportallck);
874
        return $auth_list;
875
    }
876

    
877
    /*
878
     * Drop the lock since radius takes some time to finish.
879
     * The implementation is reentrant so we gain speed with this.
880
     */
881
    unlock($captiveportallck);
882

    
883
    $radiusservers = captiveportal_get_radius_servers();
884

    
885
    $auth_list = RADIUS_AUTHENTICATION($username,
886
                    $password,
887
                    $radiusservers,
888
                    $clientip,
889
                    $clientmac,
890
                    $ruleno);
891

    
892
    $captiveportallck = lock('captiveportal');
893

    
894
    if ($auth_list['auth_val'] == 2) {
895
        captiveportal_logportalauth($username,$clientmac,$clientip,$type);
896
        $sessionid = portal_allow($clientip,
897
                    $clientmac,
898
                    $username,
899
                    $password,
900
                    $auth_list,
901
                    $ruleno);
902
    }
903

    
904
    unlock($captiveportallck);
905

    
906
    return $auth_list;
907

    
908
}
909

    
910
/* read captive portal DB into array */
911
function captiveportal_read_db() {
912

    
913
        global $g;
914

    
915
        $cpdb = array();
916
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
917
        if ($fd) {
918
                while (!feof($fd)) {
919
                        $line = trim(fgets($fd));
920
                        if ($line) {
921
                                $cpdb[] = explode(",", $line);
922
                        }
923
                }
924
                fclose($fd);
925
        }
926
        return $cpdb;
927
}
928

    
929
/* write captive portal DB */
930
function captiveportal_write_db($cpdb) {
931
                 
932
        global $g;
933
                
934
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
935
        if ($fd) { 
936
                foreach ($cpdb as $cpent) {
937
                        fwrite($fd, join(",", $cpent) . "\n");
938
                }       
939
                fclose($fd);
940
        }       
941
}
942

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

    
979
/*
980
 * This function will calculate the lowest free firewall ruleno
981
 * within the range specified based on the actual logged on users
982
 *
983
 */
984
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
985
	global $config, $g;
986
	if(!isset($config['captiveportal']['enable']))
987
		return NULL;
988
	$ruleno = 0;
989
	if (file_exists("{$g['vardb_path']}/captiveportal.nextrule"))
990
		$ruleno = intval(file_get_contents("{$g['vardb_path']}/captiveportal.nextrule"));
991
	else
992
		$ruleno = 1;
993
	if ($ruleno > 0 && (($rulenos_start + $ruleno) < $rulenos_range_max)) {
994
		/* 
995
		 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
996
		 * and the out pipe ruleno + 1. This removes limitation that where present in 
997
		 * previous version of the peruserbw.
998
		 */
999
		if (isset($config['captiveportal']['peruserbw']))
1000
			$ruleno += 2;
1001
		else
1002
			$ruleno++;
1003
		file_put_contents("{$g['vardb_path']}/captiveportal.nextrule", $ruleno);
1004
		return $rulenos_start + $ruleno;
1005
	}
1006
	return NULL;
1007
}
1008

    
1009
/**
1010
 * This function will calculate the traffic produced by a client
1011
 * based on its firewall rule
1012
 *
1013
 * Point of view: NAS
1014
 *
1015
 * Input means: from the client
1016
 * Output means: to the client
1017
 *
1018
 */
1019

    
1020
function getVolume($ip) {
1021

    
1022
    $volume = array();
1023

    
1024
    // Initialize vars properly, since we don't want NULL vars
1025
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1026

    
1027
    // Ingress
1028
    $ipfwin = "";
1029
    $ipfwout = "";
1030
    $matchesin = "";
1031
    $matchesout = "";
1032
    exec("/sbin/ipfw table 3 entrystats {$ip}", $ipfwin);
1033
    if ($ipfwin[0]) {
1034
		$ipfwin = split(" ", $ipfwin[0]);
1035
		$volume['input_pkts'] = $ipfwin[2];
1036
		$volume['input_bytes'] = $ipfwin[3];
1037
    }
1038

    
1039
    exec("/sbin/ipfw table 4 entrystats {$ip}", $ipfwout);
1040
    if ($ipfwout[0]) {
1041
        $ipfwout = split(" ", $ipfwout[0]);
1042
        $volume['output_pkts'] = $ipfwout[2];
1043
        $volume['output_bytes'] = $ipfwout[3];
1044
    }
1045

    
1046
    return $volume;
1047
}
1048

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

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

    
1070
function getNasIP()
1071
{
1072
    $nasIp = get_interface_ip();
1073
    if(!$nasIp)
1074
        $nasIp = "0.0.0.0";
1075
    return $nasIp;
1076
}
1077

    
1078
function portal_mac_fixed($clientmac) {
1079
    global $g ;
1080

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

    
1099
function portal_ip_from_client_ip($cliip) {
1100
	global $config;
1101

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

    
1110
	// doesn't match up to any particular interface
1111
	// so let's set the portal IP to what PHP says 
1112
	// the server IP issuing the request is. 
1113
	// allows same behavior as 1.2.x where IP isn't 
1114
	// in the subnet of any CP interface (static routes, etc.)
1115
	// rather than forcing to DNS hostname resolution
1116
	$ip = $_SERVER['SERVER_ADDR'];
1117
	if (is_ipaddr($ip))
1118
		return $ip;
1119

    
1120
	return false;
1121
}
1122

    
1123
?>
(6-6/50)