Project

General

Profile

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

    
405
		if (isset($config['captiveportal']['httpslogin'])) {
406
			$rulenum++;
407
			$cprules .= "add {$rulenum} set 1 pass tcp from any to {$cpip} 8001 in \n";
408
			$rulenum++;
409
			$cprules .= "add {$rulenum} set 1 pass tcp from {$cpip} 8001 to any out \n";
410
		}
411
	}
412
	$rulenum++;
413

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

    
428
# redirect non-authenticated clients to captive portal
429
add 1990 set 1 fwd 127.0.0.1,8000 tcp from any to any in
430
# let the responses from the captive portal web server back out
431
add 1991 set 1 pass tcp from any to any out
432
# block everything else
433
add 1992 set 1 deny all from any to any
434

    
435
# ... 2000-49899: layer2 block rules per authenticated client go here...
436

    
437
# pass everything else on layer2
438
add 49900 set 1 pass all from any to any layer2
439

    
440
EOD;
441

    
442
    return $cprules;
443
}
444

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

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

    
452
    global $g, $config;
453

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

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

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

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

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

    
474
    $radiusservers = captiveportal_get_radius_servers();
475

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

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

    
488
		/* no pruning for fixed mac address entry */
489
		if (portal_mac_fixed($cpdb[$i][3])) {
490
			continue; // check next value
491
		}
492
        /* hard timeout? */
493
        if ($timeout) {
494
            if ((time() - $cpdb[$i][0]) >= $timeout) {
495
                $timedout = true;
496
                $term_cause = 5; // Session-Timeout
497
            }
498
        }
499

    
500
        /* Session-Terminate-Time */
501
        if (!$timedout && !empty($cpdb[$i][9])) {
502
            if (time() >= $cpdb[$i][9]) {
503
                $timedout = true;
504
                $term_cause = 5; // Session-Timeout
505
            }
506
        }
507

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

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

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

    
540
        if ($timedout) {
541
            captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
542
            captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
543
	    $unsetindexes[$i] = $i;
544
        }
545

    
546
        /* do periodic RADIUS reauthentication? */
547
        if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
548
            !empty($radiusservers)) {
549

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

    
582
            /* check this user against RADIUS again */
583
            $auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
584
                                          base64_decode($cpdb[$i][6]), // password
585
                                            $radiusservers,
586
                                          $cpdb[$i][2], // clientip
587
                                          $cpdb[$i][3], // clientmac
588
                                          $cpdb[$i][1]); // ruleno
589

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

    
601
    /* write database */
602
    captiveportal_write_db($cpdb);
603

    
604
    unlock($captiveportallck);
605
}
606

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

    
610
	global $g, $config;
611

    
612
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
613

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

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

    
640
	/* Ensure all pf(4) states are killed. */
641
	mwexec("pfctl -k {$dbent[2]}");
642
	mwexec("pfctl -K {$dbent[2]}");
643

    
644
}
645

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

    
649
	global $g, $config;
650

    
651
	$captiveportallck = lock('captiveportal');
652

    
653
	/* read database */
654
	$cpdb = captiveportal_read_db();
655
	$radiusservers = captiveportal_get_radius_servers();
656

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

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

    
671
	unlock($captiveportallck);
672
}
673

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

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

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

    
684
	$cpdb = captiveportal_read_db();
685

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

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

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

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

    
712
	if (is_array($config['captiveportal']['passthrumac'])) {
713

    
714
		$fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db", "w");
715
		if (!$fd) {
716
			printf("Error: cannot open passthru mac DB file in captiveportal_passthrumac_configure().\n");
717
			unlock($captiveportallck);
718
			return 1;
719
		}
720

    
721
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
722
			/* record passthru mac so it can be recognized and let thru */
723
			fwrite($fd, $macent['mac'] . "\n");
724
		}
725

    
726
		fclose($fd);
727
	}
728

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

    
747
	if (!$lock)
748
		unlock($captiveportallck);
749

    
750
	return 0;
751
}
752

    
753
function captiveportal_allowedip_configure() {
754
	global $config, $g;
755

    
756
	/* clear out existing allowed ips, if necessary */
757
	mwexec("/sbin/ipfw table 1 flush");
758
	mwexec("/sbin/ipfw table 2 flush");
759

    
760
	if (is_array($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 1890 set 1 skipto 50000 ip from table\(1\) to any in");
775
			mwexec("/sbin/ipfw add 1891 set 1 skipto 50000 ip from any to table\(1\) out");
776
		}
777
		if ($tabletwo == true) {
778
			mwexec("/sbin/ipfw add 1892 set 1 skipto 50000 ip from any to table\(2\) in");
779
			mwexec("/sbin/ipfw add 1893 set 1 skipto 50000 ip from table\(2\) to any out");
780
		}
781
	}
782

    
783
    return 0;
784
}
785

    
786
/* get last activity timestamp given client IP address */
787
function captiveportal_get_last_activity($ip) {
788

    
789
	$ipfwoutput = "";
790

    
791
	exec("/sbin/ipfw table 3 entrystats {$ip} 2>/dev/null", $ipfwoutput);
792
	/* Reading only from one of the tables is enough of approximation. */
793
	if ($ipfwoutput[0]) {
794
		$ri = explode(" ", $ipfwoutput[0]);
795
		if ($ri[4])
796
			return $ri[4];
797
	}
798

    
799
	return 0;
800
}
801

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

    
805
        global $g;
806

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

    
821
		return $radiusservers;
822
        }
823

    
824
        return false;
825
}
826

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

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

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

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

    
856
    $ruleno = captiveportal_get_next_ipfw_ruleno();
857

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

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

    
873
    $radiusservers = captiveportal_get_radius_servers();
874

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

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

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

    
894
    unlock($captiveportallck);
895

    
896
    return $auth_list;
897

    
898
}
899

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

    
903
        global $g;
904

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

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

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

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

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

    
1010
function getVolume($ip) {
1011

    
1012
    $volume = array();
1013

    
1014
    // Initialize vars properly, since we don't want NULL vars
1015
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1016

    
1017
    // Ingress
1018
    $ipfwin = "";
1019
    $ipfwout = "";
1020
    $matchesin = "";
1021
    $matchesout = "";
1022
    exec("/sbin/ipfw table 3 entrystats {$ip}", $ipfwin);
1023
    if ($ipfwin[0]) {
1024
		$ipfwin = split(" ", $ipfwin[0]);
1025
		$volume['input_pkts'] = $ipfwin[2];
1026
		$volume['input_bytes'] = $ipfwin[3];
1027
    }
1028

    
1029
    exec("/sbin/ipfw table 4 entrystats {$ip}", $ipfwout);
1030
    if ($ipfwout[0]) {
1031
        $ipfwout = split(" ", $ipfwout[0]);
1032
        $volume['output_pkts'] = $ipfwout[2];
1033
        $volume['output_bytes'] = $ipfwout[3];
1034
    }
1035

    
1036
    return $volume;
1037
}
1038

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

    
1053
/**
1054
 * Get the NAS-IP-Address based on the current wan address
1055
 *
1056
 * Use functions in interfaces.inc to find this out
1057
 *
1058
 */
1059

    
1060
function getNasIP()
1061
{
1062
    $nasIp = get_interface_ip();
1063
    if(!$nasIp)
1064
        $nasIp = "0.0.0.0";
1065
    return $nasIp;
1066
}
1067

    
1068
function portal_mac_fixed($clientmac) {
1069
    global $g ;
1070

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

    
1089
function portal_ip_from_client_ip($cliip) {
1090
	global $config;
1091

    
1092
	$interfaces = explode(",", $config['captiveportal']['interface']);
1093
	foreach ($interfaces as $cpif) {
1094
		$ip = get_interface_ip($cpif);
1095
		$sn = get_interface_subnet($cpif);
1096
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1097
			return $ip;
1098
	}
1099

    
1100
	// doesn't match up to any particular interface
1101
	// so let's set the portal IP to what PHP says 
1102
	// the server IP issuing the request is. 
1103
	// allows same behavior as 1.2.x where IP isn't 
1104
	// in the subnet of any CP interface (static routes, etc.)
1105
	// rather than forcing to DNS hostname resolution
1106
	$ip = $_SERVER['SERVER_ADDR'];
1107
	if (is_ipaddr($ip))
1108
		return $ip;
1109

    
1110
	return false;
1111
}
1112

    
1113
?>
(6-6/50)