Project

General

Profile

Download (36.7 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.inc");
47
require_once("voucher.inc");
48

    
49
function captiveportal_configure() {
50
	global $config, $g;
51

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

    
96
	if ($cpactive == true) {
97

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

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

    
105
		/* remove old information */
106
		unlink_if_exists("{$g['vardb_path']}/captiveportal.db");
107
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
108
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip.db");
109
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius.db");
110
		mwexec("/sbin/ipfw table all flush");
111

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

    
115
		/* kill any running minicron */
116
		killbypid("{$g['varrun_path']}/minicron.pid");
117

    
118
		/* make sure ipfw is loaded */
119
		if (!is_module_loaded("ipfw.ko"))
120
			filter_load_ipfw();
121
		if (isset($config['captiveportal']['peruserbw']) && !is_module_loaded("dummynet.ko"))
122
                        mwexec("/sbin/kldload dummynet");
123

    
124
		/* generate ipfw rules */
125
		captiveportal_init_ipfw_ruleno();
126
		$cprules = captiveportal_rules_generate($cpinterface, $cpips);
127
		$cprules .= "\n";
128
		/* generate passthru mac database */
129
		captiveportal_passthrumac_configure(true);
130
		/* allowed ipfw rules to make allowed ip work */
131
		$cprules .= captiveportal_allowedip_configure();
132

    
133
		/* stop accounting on all clients */
134
		captiveportal_radius_stop_all(true);
135

    
136
		/* initialize minicron interval value */
137
		$croninterval = $config['captiveportal']['croninterval'] ? $config['captiveportal']['croninterval'] : 60;
138

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

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

    
174

    
175

    
176
EOD;
177
		}
178

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

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

    
205
EOD;
206
		}
207

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

    
214
		/* write elements */
215
		captiveportal_write_elements();
216

    
217
		/* load rules */
218
		mwexec("/sbin/ipfw -q flush");
219

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

    
228
		fwrite($fd, $cprules);
229
		fclose($fd);
230

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

    
233
		@unlink("{$g['tmp_path']}/ipfw.cp.rules");
234

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

    
238
		chdir($g['captiveportal_path']);
239

    
240
		if ($config['captiveportal']['maxproc'])
241
			$maxproc = $config['captiveportal']['maxproc'];
242
		else
243
			$maxproc = 16;
244

    
245
		$use_fastcgi = true;
246

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

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

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

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

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

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

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

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

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

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

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

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

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

    
320
		captiveportal_radius_stop_all(true);
321

    
322
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
323

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

    
341
	unlock($captiveportallck);
342
	
343
	return 0;
344
}
345

    
346
function captiveportal_rules_generate($cpif, &$cpiparray) {
347
	global $config, $g;
348

    
349
	$cprules =  "add 65301 set 1 allow pfsync from any to any\n";
350
	$cprules .= "add 65302 set 1 allow carp from any to any\n";
351

    
352
	$cprules .= <<<EOD
353
# add 65305 set 1 skipto 65534 all from any to any not layer2
354
# layer 2: pass ARP
355
add 65310 set 1 pass layer2 mac-type arp
356
# pfsense requires for WPA
357
add 65311 set 1 pass layer2 mac-type 0x888e
358
add 65312 set 1 pass layer2 mac-type 0x88c7
359

    
360
# PPP Over Ethernet Discovery Stage
361
add 65313 set 1 pass layer2 mac-type 0x8863
362
# PPP Over Ethernet Session Stage
363
add 65314 set 1 pass layer2 mac-type 0x8864
364
# Allow WPA
365
add 65315 set 1 pass layer2 mac-type 0x888e
366

    
367
# layer 2: block anything else non-IP
368
add 65316 set 1 deny layer2 not mac-type ip
369

    
370
EOD;
371

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

    
398
	if (isset($config['captiveportal']['httpslogin'])) {
399
		$rulenum++;
400
		$cprules .= "add {$rulenum} set 1 pass tcp from any to {$ips} 8001 in \n";
401
		$rulenum++;
402
		$cprules .= "add {$rulenum} set 1 pass tcp from {$ips} 8001 to any out \n";
403
	}
404
	if (!empty($config['system']['webgui']['port']))
405
		$port = $config['system']['webgui']['port'];
406
	else if ($config['system']['webgui']['proto'] == "https")
407
		$port = 443;
408
	else
409
		$port = 80;
410
	$rulenum++;
411
	$cprules .= "add {$rulenum} set 1 pass tcp from any to {$ips} {$port} in \n";
412
	$rulenum++;
413
	$cprules .= "add {$rulenum} set 1 pass tcp from {$ips} {$port} to any out \n";
414
	$rulenum++;
415

    
416
	if (isset($config['captiveportal']['peruserbw'])) {
417
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from table(1) to any in\n";
418
		$rulenum++;
419
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from any to table(2) out\n";
420
		$rulenum++;
421
	} else {
422
		$cprules .= "add {$rulenum} set 1 allow ip from table(1) to any in\n";
423
                $rulenum++;
424
                $cprules .= "add {$rulenum} set 1 allow ip from any to table(2) out\n";
425
                $rulenum++;
426
	}
427
	
428
       $cprules .= <<<EOD
429

    
430
# redirect non-authenticated clients to captive portal
431
add 65531 set 1 fwd 127.0.0.1,8000 tcp from any to any in
432
# let the responses from the captive portal web server back out
433
add 65532 set 1 pass tcp from any to any out
434
# block everything else
435
add 65533 set 1 deny all from any to any
436
# pass everything else on layer2
437
add 65534 set 1 pass all from any to any layer2
438

    
439
EOD;
440

    
441
    return $cprules;
442
}
443

    
444
/* remove clients that have been around for longer than the specified amount of time */
445
/* db file structure:
446
timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time */
447

    
448
/* (password is in Base64 and only saved when reauthentication is enabled) */
449
function captiveportal_prune_old() {
450

    
451
    global $g, $config;
452

    
453
    /* check for expired entries */
454
    if ($config['captiveportal']['timeout'])
455
        $timeout = $config['captiveportal']['timeout'] * 60;
456
    else
457
        $timeout = 0;
458

    
459
    if ($config['captiveportal']['idletimeout'])
460
        $idletimeout = $config['captiveportal']['idletimeout'] * 60;
461
    else
462
        $idletimeout = 0;
463

    
464
    if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && 
465
		!isset($config['captiveportal']['radiussession_timeout']) && !isset($config['voucher']['enable']))
466
        return;
467

    
468
    $captiveportallck = lock('captiveportal');
469

    
470
    /* read database */
471
    $cpdb = captiveportal_read_db();
472

    
473
    $radiusservers = captiveportal_get_radius_servers();
474

    
475
    /*  To make sure we iterate over ALL accounts on every run the count($cpdb) is moved
476
     *  outside of the loop. Otherwise the loop would evaluate count() on every iteration
477
     *  and since $i would increase and count() would decrement they would meet before we
478
     *  had a chance to iterate over all accounts.
479
     */
480
    $unsetindexes = array();
481
    $no_users = count($cpdb);
482
    for ($i = 0; $i < $no_users; $i++) {
483

    
484
        $timedout = false;
485
        $term_cause = 1;
486

    
487
        /* hard timeout? */
488
        if ($timeout) {
489
            if ((time() - $cpdb[$i][0]) >= $timeout) {
490
                $timedout = true;
491
                $term_cause = 5; // Session-Timeout
492
            }
493
        }
494

    
495
        /* Session-Terminate-Time */
496
        if (!$timedout && !empty($cpdb[$i][9])) {
497
            if (time() >= $cpdb[$i][9]) {
498
                $timedout = true;
499
                $term_cause = 5; // Session-Timeout
500
            }
501
        }
502

    
503
        /* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
504
        $idletimeout = (is_numeric($cpdb[$i][8])) ? $cpdb[$i][8] : $idletimeout;
505
        /* if an idle timeout is specified, get last activity timestamp from ipfw */
506
        if (!$timedout && $idletimeout) {
507
            $lastact = captiveportal_get_last_activity($cpdb[$i][2]);
508
			/*  If the user has logged on but not sent any traffic they will never be logged out.
509
			 *  We "fix" this by setting lastact to the login timestamp. 
510
			 */
511
			$lastact = $lastact ? $lastact : $cpdb[$i][0];
512
            if ($lastact && ((time() - $lastact) >= $idletimeout)) {
513
                $timedout = true;
514
                $term_cause = 4; // Idle-Timeout
515
                $stop_time = $lastact; // Entry added to comply with WISPr
516
            }
517
        }
518

    
519
	/* if vouchers are configured, activate session timeouts */
520
	if (!$timedout && isset($config['voucher']['enable']) && !empty($cpdb[$i][7])) {
521
		if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
522
			$timedout = true;
523
			$term_cause = 5; // Session-Timeout
524
		}
525
	}
526

    
527
        /* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
528
        if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpdb[$i][7])) {
529
            if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
530
                $timedout = true;
531
                $term_cause = 5; // Session-Timeout
532
            }
533
        }
534

    
535
        if ($timedout) {
536
            captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
537
            captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
538
	    $unsetindexes[$i] = $i;
539
        }
540

    
541
        /* do periodic RADIUS reauthentication? */
542
        if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
543
            !empty($radiusservers)) {
544

    
545
            if (isset($config['captiveportal']['radacct_enable'])) {
546
                if ($config['captiveportal']['reauthenticateacct'] == "stopstart") {
547
                    /* stop and restart accounting */
548
                    RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
549
                                           $cpdb[$i][4], // username
550
                                           $cpdb[$i][5], // sessionid
551
                                           $cpdb[$i][0], // start time
552
                                           $radiusservers,
553
                                           $cpdb[$i][2], // clientip
554
                                           $cpdb[$i][3], // clientmac
555
                                           10); // NAS Request
556
                    exec("/sbin/ipfw table 1 entryzerostats {$cpdb[$i][2]}");
557
                    exec("/sbin/ipfw table 2 entryzerostats {$cpdb[$i][2]}");
558
                    RADIUS_ACCOUNTING_START($cpdb[$i][1], // ruleno
559
                                            $cpdb[$i][4], // username
560
                                            $cpdb[$i][5], // sessionid
561
                                            $radiusservers,
562
                                            $cpdb[$i][2], // clientip
563
                                            $cpdb[$i][3]); // clientmac
564
                } else if ($config['captiveportal']['reauthenticateacct'] == "interimupdate") {
565
                    RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
566
                                           $cpdb[$i][4], // username
567
                                           $cpdb[$i][5], // sessionid
568
                                           $cpdb[$i][0], // start time
569
                                           $radiusservers,
570
                                           $cpdb[$i][2], // clientip
571
                                           $cpdb[$i][3], // clientmac
572
                                           10, // NAS Request
573
                                           true); // Interim Updates
574
                }
575
            }
576

    
577
            /* check this user against RADIUS again */
578
            $auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
579
                                          base64_decode($cpdb[$i][6]), // password
580
                                            $radiusservers,
581
                                          $cpdb[$i][2], // clientip
582
                                          $cpdb[$i][3], // clientmac
583
                                          $cpdb[$i][1]); // ruleno
584

    
585
            if ($auth_list['auth_val'] == 3) {
586
                captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
587
                captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
588
	        $unsetindexes[$i] = $i;
589
            }
590
        }
591
    }
592
    /* This is a kludge to overcome some php weirdness */
593
    foreach($unsetindexes as $unsetindex)
594
	unset($cpdb[$unsetindex]);
595

    
596
    /* write database */
597
    captiveportal_write_db($cpdb);
598

    
599
    unlock($captiveportallck);
600
}
601

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

    
605
	global $g, $config;
606

    
607
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
608

    
609
	/* this client needs to be deleted - remove ipfw rules */
610
	if (isset($config['captiveportal']['radacct_enable']) && !empty($radiusservers)) {
611
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
612
							   $dbent[4], // username
613
							   $dbent[5], // sessionid
614
							   $dbent[0], // start time
615
							   $radiusservers,
616
							   $dbent[2], // clientip
617
							   $dbent[3], // clientmac
618
							   $term_cause, // Acct-Terminate-Cause
619
							   false,
620
							   $stop_time);
621
	}
622
	/* Delete client's ip entry from tables 3 and 4. */
623
	mwexec("/sbin/ipfw table 1 delete {$dbent[2]}");
624
	mwexec("/sbin/ipfw table 2 delete {$dbent[2]}");
625

    
626
	/* Release the ruleno so it can be reallocated to new clients. */
627
	captiveportal_free_ipfw_ruleno($dbent[1]);
628

    
629
	/* 
630
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
631
	* We could get an error if the pipe doesn't exist but everything should still be fine
632
	*/
633
	if (isset($config['captiveportal']['peruserbw'])) {
634
		mwexec("/sbin/ipfw pipe " . ($dbent[1]+20000) . " delete");
635
		mwexec("/sbin/ipfw pipe " . ($dbent[1]+20001) . " delete");
636
	}
637

    
638
	/* XXX: Redundant?! Ensure all pf(4) states are killed. */
639
	mwexec("pfctl -k {$dbent[2]}");
640
	mwexec("pfctl -K {$dbent[2]}");
641

    
642
}
643

    
644
/* remove a single client by ipfw rule number */
645
function captiveportal_disconnect_client($id,$term_cause = 1) {
646

    
647
	global $g, $config;
648

    
649
	$captiveportallck = lock('captiveportal');
650

    
651
	/* read database */
652
	$cpdb = captiveportal_read_db();
653
	$radiusservers = captiveportal_get_radius_servers();
654

    
655
	/* find entry */
656
	$tmpindex = 0;
657
	$cpdbcount = count($cpdb);
658
	for ($i = 0; $i < $cpdbcount; $i++) {
659
		if ($cpdb[$i][1] == $id) {
660
			captiveportal_disconnect($cpdb[$i], $radiusservers, $term_cause);
661
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "DISCONNECT");
662
			unset($cpdb[$i]);
663
			break;
664
		}
665
	}		
666

    
667
	/* write database */
668
	captiveportal_write_db($cpdb);
669

    
670
	unlock($captiveportallck);
671
}
672

    
673
/* send RADIUS acct stop for all current clients */
674
function captiveportal_radius_stop_all($lock = false) {
675
	global $g, $config;
676

    
677
	if (!isset($config['captiveportal']['radacct_enable']))
678
		return;
679

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

    
683
	$cpdb = captiveportal_read_db();
684

    
685
	$radiusservers = captiveportal_get_radius_servers();
686
	if (!empty($radiusservers)) {
687
		for ($i = 0; $i < count($cpdb); $i++) {
688
			RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
689
								   $cpdb[$i][4], // username
690
								   $cpdb[$i][5], // sessionid
691
								   $cpdb[$i][0], // start time
692
								   $radiusservers,
693
								   $cpdb[$i][2], // clientip
694
								   $cpdb[$i][3], // clientmac
695
								   7); // Admin Reboot
696
		}
697
	}
698
	if (!$lock)
699
		unlock($captiveportallck);
700
}
701

    
702
function captiveportal_passthrumac_configure($lock = false) {
703
	global $config, $g;
704

    
705
	if (!$lock)
706
		$captiveportallck = lock('captiveportal');
707

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

    
711
	if (is_array($config['captiveportal']['passthrumac'])) {
712
		$macdb = array();
713
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
714
			$macdb[$macent['mac']]['active']  = true;
715
			if (isset($macent['bw_up']))
716
				$macdb[$macent['mac']]['bw_up']  = $macent['bw_up'];
717
			if (isset($macent['bw_down']))
718
				$macdb[$macent['mac']]['bw_down']  = $macent['bw_down'];
719

    
720
		}
721
		/* record passthru MACs so can be recognized and let thru */
722
		file_put_contents("{$g['vardb_path']}/captiveportal_mac.db", serialize($macdb));
723
	}
724

    
725
	if (!$lock)
726
		unlock($captiveportallck);
727
}
728

    
729
function captiveportal_allowedip_configure() {
730
	global $config, $g;
731

    
732
	$rules = "";
733
	if (is_array($config['captiveportal']['allowedip'])) {
734
		$peruserbw = isset($config['captiveportal']['peruserbw']);
735
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
736
                	$ruleno = captiveportal_get_next_ipfw_ruleno();
737
			$bw_up = "";
738
			$bw_down = "";
739
                        if ($peruserbw) {
740
                                $bwup = isset($ipent['bw_up']) ? trim($ipent['bw_up']) : $config['captiveportal']['bwdefaultup'];
741
                                $bwdown = isset($ipent['bw_down']) ? trim($ipent['bw_down']) : $config['captiveportal']['bwdefaultdn'];
742
                                if (!empty($bwup) && is_numeric($bwup)) {
743
                                        $bw_up = $ruleno + 20000;
744
                                        $rules .= "pipe {$bw_up} config bw {$bw_up}Kbit/s queue 100\n";
745
                                }
746
                                if (!empty($bwdown) && is_numeric($bwdown)) {
747
                                        $bw_down = $ruleno + 20001;
748
                                        $rules .= "pipe {$bw_down} config bw {$bw_down}Kbit/s queue 100\n";
749
                                }
750
                        }
751
			/* insert address in ipfw table */
752
			$rules .= "table 1 add {$ipent['ip']} ${bw_up}\n";
753
			$rules .= "table 2 add {$ipent['ip']} ${bw_down}\n";
754
		}
755
	}
756

    
757
	return $rules;
758
}
759

    
760
/* get last activity timestamp given client IP address */
761
function captiveportal_get_last_activity($ip) {
762

    
763
	$ipfwoutput = "";
764

    
765
	exec("/sbin/ipfw table 1 entrystats {$ip} 2>/dev/null", $ipfwoutput);
766
	/* Reading only from one of the tables is enough of approximation. */
767
	if ($ipfwoutput[0]) {
768
		$ri = explode(" ", $ipfwoutput[0]);
769
		if ($ri[4])
770
			return $ri[4];
771
	}
772

    
773
	return 0;
774
}
775

    
776
/* read RADIUS servers into array */
777
function captiveportal_get_radius_servers() {
778

    
779
        global $g;
780

    
781
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
782
                $radiusservers = array();
783
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius.db", 
784
			FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
785
		if ($cpradiusdb)
786
		foreach($cpradiusdb as $cpradiusentry) {
787
                	$line = trim($cpradiusentry);
788
                        if ($line) {
789
                        	$radsrv = array();
790
                                list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
791
                        	$radiusservers[] = $radsrv;
792
                        }
793
		}
794

    
795
		return $radiusservers;
796
        }
797

    
798
        return false;
799
}
800

    
801
/* log successful captive portal authentication to syslog */
802
/* part of this code from php.net */
803
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
804
	$message = trim($message);
805
	// Log it
806
	if (!$message)
807
		$message = "$status: $user, $mac, $ip";
808
	else
809
		$message = "$status: $user, $mac, $ip, $message";
810
	captiveportal_syslog($message);
811
	closelog();
812
}
813

    
814
/* log simple messages to syslog */
815
function captiveportal_syslog($message) {
816
	define_syslog_variables();
817
	$message = trim($message);
818
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
819
	// Log it
820
	syslog(LOG_INFO, $message);
821
	closelog();
822
}
823

    
824
function radius($username,$password,$clientip,$clientmac,$type) {
825
    global $g, $config;
826

    
827
    /* Start locking from the beginning of an authentication session */
828
    $captiveportallck = lock('captiveportal');
829

    
830
    $ruleno = captiveportal_get_next_ipfw_ruleno();
831

    
832
    /* If the pool is empty, return appropriate message and fail authentication */
833
    if (is_null($ruleno)) {
834
        $auth_list = array();
835
        $auth_list['auth_val'] = 1;
836
        $auth_list['error'] = "System reached maximum login capacity";
837
        unlock($captiveportallck);
838
        return $auth_list;
839
    }
840

    
841
    /*
842
     * Drop the lock since radius takes some time to finish.
843
     * The implementation is reentrant so we gain speed with this.
844
     */
845
    unlock($captiveportallck);
846

    
847
    $radiusservers = captiveportal_get_radius_servers();
848

    
849
    $auth_list = RADIUS_AUTHENTICATION($username,
850
                    $password,
851
                    $radiusservers,
852
                    $clientip,
853
                    $clientmac,
854
                    $ruleno);
855

    
856
    $captiveportallck = lock('captiveportal');
857

    
858
    if ($auth_list['auth_val'] == 2) {
859
        captiveportal_logportalauth($username,$clientmac,$clientip,$type);
860
        $sessionid = portal_allow($clientip,
861
                    $clientmac,
862
                    $username,
863
                    $password,
864
                    $auth_list,
865
                    $ruleno);
866
    }
867

    
868
    unlock($captiveportallck);
869

    
870
    return $auth_list;
871

    
872
}
873

    
874
/* read captive portal DB into array */
875
function captiveportal_read_db() {
876

    
877
        global $g;
878

    
879
        $cpdb = array();
880
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
881
        if ($fd) {
882
                while (!feof($fd)) {
883
                        $line = trim(fgets($fd));
884
                        if ($line) {
885
                                $cpdb[] = explode(",", $line);
886
                        }
887
                }
888
                fclose($fd);
889
        }
890
        return $cpdb;
891
}
892

    
893
/* write captive portal DB */
894
function captiveportal_write_db($cpdb) {
895
                 
896
        global $g;
897
                
898
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
899
        if ($fd) { 
900
                foreach ($cpdb as $cpent) {
901
                        fwrite($fd, join(",", $cpent) . "\n");
902
                }       
903
                fclose($fd);
904
        }       
905
}
906

    
907
function captiveportal_write_elements() {
908
    global $g, $config;
909
    
910
    /* delete any existing elements */
911
    if (is_dir($g['captiveportal_element_path'])) {
912
        $dh = opendir($g['captiveportal_element_path']);
913
        while (($file = readdir($dh)) !== false) {
914
            if ($file != "." && $file != "..")
915
                unlink($g['captiveportal_element_path'] . "/" . $file);
916
        }
917
        closedir($dh);
918
    } else {
919
        @mkdir($g['captiveportal_element_path']);
920
    }
921
    
922
	if (is_array($config['captiveportal']['element'])) {
923
		conf_mount_rw();
924
		foreach ($config['captiveportal']['element'] as $data) {
925
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
926
			if (!$fd) {
927
				printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
928
				return 1;
929
			}
930
			$decoded = base64_decode($data['content']);
931
			fwrite($fd,$decoded);
932
			fclose($fd);
933
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
934
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
935
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
936
		}
937
		conf_mount_ro();
938
	}
939
    
940
    return 0;
941
}
942

    
943
function captiveportal_init_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
944
	global $g;
945

    
946
	@unlink("{$g['vardb_path']}/captiveportal.rules");
947
	$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
948
	file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
949
}
950

    
951
/*
952
 * This function will calculate the lowest free firewall ruleno
953
 * within the range specified based on the actual logged on users
954
 *
955
 */
956
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
957
	global $config, $g;
958

    
959
	if(!isset($config['captiveportal']['enable']))
960
		return NULL;
961

    
962
	$ruleno = 0;
963
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
964
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
965
		for ($ridx = 2; $ridx < ($rulenos_range_max - $rulenos_start); $ridx++) {
966
			if ($rules[$ridx]) {
967
				/* 
968
	 			 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
969
	 			 * and the out pipe ruleno + 1. This removes limitation that where present in 
970
	 			 * previous version of the peruserbw.
971
	 			 */
972
				if (isset($config['captiveportal']['peruserbw']))
973
					$ridx++;
974
				continue;
975
			}
976
			$ruleno = $ridx;
977
			$rules[$ridx] = "used";
978
			if (isset($config['captiveportal']['peruserbw']))
979
				$rules[++$ridx] = "used";
980
			break;
981
		}
982
	} else {
983
		$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
984
		$rules[2] = "used";
985
		$ruleno = 2;
986
	}
987
	file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
988
	return $ruleno;
989
}
990

    
991
function captiveportal_free_ipfw_ruleno($ruleno) {
992
	global $config, $g;
993

    
994
	if(!isset($config['captiveportal']['enable']))
995
		return NULL;
996

    
997
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
998
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
999
		$rules[$ruleno] = false;
1000
		if (isset($config['captiveportal']['peruserbw']))
1001
			$rules[++$ruleno] = false;
1002
		file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1003
	}
1004
}
1005

    
1006
function captiveportal_get_ipfw_ruleno_byvalue($value) {
1007
	global $config, $g;
1008

    
1009
	if(!isset($config['captiveportal']['enable']))
1010
                return NULL;
1011

    
1012
        if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1013
                $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1014
		$ruleno = intval(`/sbin/ipfw table 1 list | /usr/bin/grep {$value} |  /usr/bin/grep -v grep | /usr/bin/cut -d " " -f 1 | /usr/bin/head -n 1`);
1015
		if ($rules[$ruleno])
1016
			return $ruleno;
1017
        }
1018

    
1019
	return NULL;
1020
}
1021

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

    
1033
function getVolume($ip) {
1034

    
1035
    $volume = array();
1036

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

    
1040
    // Ingress
1041
    $ipfwin = "";
1042
    $ipfwout = "";
1043
    $matchesin = "";
1044
    $matchesout = "";
1045
    exec("/sbin/ipfw table 1 entrystats {$ip}", $ipfwin);
1046
    if ($ipfwin[0]) {
1047
		$ipfwin = split(" ", $ipfwin[0]);
1048
		$volume['input_pkts'] = $ipfwin[2];
1049
		$volume['input_bytes'] = $ipfwin[3];
1050
    }
1051

    
1052
    exec("/sbin/ipfw table 2 entrystats {$ip}", $ipfwout);
1053
    if ($ipfwout[0]) {
1054
        $ipfwout = split(" ", $ipfwout[0]);
1055
        $volume['output_pkts'] = $ipfwout[2];
1056
        $volume['output_bytes'] = $ipfwout[3];
1057
    }
1058

    
1059
    return $volume;
1060
}
1061

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

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

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

    
1091
function portal_mac_fixed($clientmac) {
1092
    global $g ;
1093

    
1094
    /* open captive portal mac db */
1095
    if (file_exists("{$g['vardb_path']}/captiveportal_mac.db")) {
1096
	$macdb = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_mac.db"));
1097
	if (isset($macdb[$clientmac]))
1098
		return $macdb[$clientmac];
1099
    }
1100
    return FALSE ;
1101
}
1102

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

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

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

    
1124
	return false;
1125
}
1126

    
1127
?>
(6-6/50)