Project

General

Profile

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

    
6
	Copyright (C) 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("globals.inc");
45
require_once("util.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
			$key = base64_decode($config['captiveportal']['private-key']);
247
			/* generate lighttpd configuration */
248
			system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal-SSL.conf",
249
				$cert, $key, "", "lighty-CaptivePortal-ssl.pid", "8001", "/usr/local/captiveportal/",
250
					"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
251
		}
252

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

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

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

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

    
269
		/* generate passthru mac database */
270
		captiveportal_passthrumac_configure(true);
271
		/* allowed ipfw rules to make allowed ip work */
272
		captiveportal_allowedip_configure();
273

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

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

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

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

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

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

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

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

    
318
		captiveportal_radius_stop_all(true);
319

    
320
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
321

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

    
337
	unlock($captiveportallck);
338
	
339
	return 0;
340
}
341

    
342
function captiveportal_rules_generate($cpif, &$cpiparray) {
343
	global $config, $g;
344

    
345
	$cpifn = $config['captiveportal']['interface'];
346
	$lanip = get_interface_ip("lan");
347
	
348
	/* note: the captive portal daemon inserts all pass rules for authenticated
349
	   clients as skipto 50000 rules to make traffic shaping work */
350

    
351
	$cprules =  "add 500 set 1 allow pfsync from any to any\n";
352
	$cprules .= "add 500 set 1 allow carp from any to any\n";
353

    
354
	$cprules .= <<<EOD
355
add 1000 set 1 skipto 1150 all from any to any not layer2
356
# layer 2: pass ARP
357
add 1100 set 1 pass layer2 mac-type arp
358
# pfsense requires for WPA
359
add 1100 set 1 pass layer2 mac-type 0x888e
360
add 1100 set 1 pass layer2 mac-type 0x88c7
361

    
362
# PPP Over Ethernet Discovery Stage
363
add 1100 set 1 pass layer2 mac-type 0x8863
364
# PPP Over Ethernet Session Stage
365
add 1100 set 1 pass layer2 mac-type 0x8864
366
# Allow WPA
367
add 1100 set 1 pass layer2 mac-type 0x888e
368

    
369
# layer 2: block anything else non-IP
370
add 1101 set 1 deny layer2 not mac-type ip
371

    
372
EOD;
373

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

    
397
		if (isset($config['captiveportal']['httpslogin'])) {
398
			$rulenum++;
399
			$cprules .= "add {$rulenum} set 1 pass tcp from any to {$cpip} 8001 in \n";
400
			$rulenum++;
401
			$cprules .= "add {$rulenum} set 1 pass tcp from {$cpip} 8001 to any out \n";
402
		}
403
	}
404
	$rulenum++;
405

    
406
	if (isset($config['captiveportal']['peruserbw'])) {
407
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from table(3) to any in\n";
408
		$rulenum++;
409
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from any to table(4) out\n";
410
		$rulenum++;
411
	} else {
412
		$cprules .= "add {$rulenum} set 1 skipto 50000 ip from table(3) to any in\n";
413
                $rulenum++;
414
                $cprules .= "add {$rulenum} set 1 skipto 50000 ip from any to table(4) out\n";
415
                $rulenum++;
416
	}
417
	
418
       $cprules .= <<<EOD
419

    
420
# redirect non-authenticated clients to captive portal
421
add 1990 set 1 fwd 127.0.0.1,8000 tcp from any to any in
422
# let the responses from the captive portal web server back out
423
add 1991 set 1 pass tcp from any 80 to any out
424
# block everything else
425
add 1992 set 1 deny all from any to any
426

    
427
# ... 2000-49899: layer2 block rules per authenticated client go here...
428

    
429
# pass everything else on layer2
430
add 49900 set 1 pass all from any to any layer2
431

    
432
EOD;
433

    
434
    return $cprules;
435
}
436

    
437
/* remove clients that have been around for longer than the specified amount of time */
438
/* db file structure:
439
timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time */
440

    
441
/* (password is in Base64 and only saved when reauthentication is enabled) */
442
function captiveportal_prune_old() {
443

    
444
    global $g, $config;
445

    
446
    /* check for expired entries */
447
    if ($config['captiveportal']['timeout'])
448
        $timeout = $config['captiveportal']['timeout'] * 60;
449
    else
450
        $timeout = 0;
451

    
452
    if ($config['captiveportal']['idletimeout'])
453
        $idletimeout = $config['captiveportal']['idletimeout'] * 60;
454
    else
455
        $idletimeout = 0;
456

    
457
    if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && 
458
		!isset($config['captiveportal']['radiussession_timeout']) && !isset($config['voucher']['enable']))
459
        return;
460

    
461
    $captiveportallck = lock('captiveportal');
462

    
463
    /* read database */
464
    $cpdb = captiveportal_read_db();
465

    
466
    $radiusservers = captiveportal_get_radius_servers();
467

    
468
    /*  To make sure we iterate over ALL accounts on every run the count($cpdb) is moved
469
     *  outside of the loop. Otherwise the loop would evaluate count() on every iteration
470
     *  and since $i would increase and count() would decrement they would meet before we
471
     *  had a chance to iterate over all accounts.
472
     */
473
    $unsetindexes = array();
474
    $no_users = count($cpdb);
475
    for ($i = 0; $i < $no_users; $i++) {
476

    
477
        $timedout = false;
478
        $term_cause = 1;
479

    
480
		/* no pruning for fixed mac address entry */
481
		if (portal_mac_fixed($cpdb[$i][3])) {
482
			continue; // check next value
483
		}
484
        /* hard timeout? */
485
        if ($timeout) {
486
            if ((time() - $cpdb[$i][0]) >= $timeout) {
487
                $timedout = true;
488
                $term_cause = 5; // Session-Timeout
489
            }
490
        }
491

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

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

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

    
524
        /* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
525
        if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !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 ($timedout) {
533
            captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
534
            captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
535
	    $unsetindexes[$i] = $i;
536
        }
537

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

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

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

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

    
593
    /* write database */
594
    captiveportal_write_db($cpdb);
595

    
596
    unlock($captiveportallck);
597
}
598

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

    
602
	global $g, $config;
603

    
604
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
605

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

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

    
632
	/* Ensure all pf(4) states are killed. */
633
	mwexec("pfctl -k {$dbent[2]}");
634
	mwexec("pfctl -K {$dbent[2]}");
635

    
636
}
637

    
638
/* remove a single client by ipfw rule number */
639
function captiveportal_disconnect_client($id,$term_cause = 1) {
640

    
641
	global $g, $config;
642

    
643
	$captiveportallck = lock('captiveportal');
644

    
645
	/* read database */
646
	$cpdb = captiveportal_read_db();
647
	$radiusservers = captiveportal_get_radius_servers();
648

    
649
	/* find entry */
650
	$tmpindex = 0;
651
	for ($i = 0; $i < count($cpdb); $i++) {
652
		if ($cpdb[$i][1] == $id) {
653
			captiveportal_disconnect($cpdb[$i], $radiusservers, $term_cause);
654
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "DISCONNECT");
655
			$tmpindex = $i;
656
			break;
657
		}
658
	}
659
	if ($tmpindex > 0)
660
		unset($cpdb[$tmpindex]);
661

    
662
	/* write database */
663
	captiveportal_write_db($cpdb);
664

    
665
	unlock($captiveportallck);
666
}
667

    
668
/* send RADIUS acct stop for all current clients */
669
function captiveportal_radius_stop_all($lock = false) {
670
	global $g, $config;
671

    
672
	if (!isset($config['captiveportal']['radacct_enable']))
673
		return;
674

    
675
	if (!$lock)
676
		$captiveportallck = lock('captiveportal');
677

    
678
	$cpdb = captiveportal_read_db();
679

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

    
697
function captiveportal_passthrumac_configure($lock = false) {
698
	global $config, $g;
699

    
700
	if (!$lock)
701
		$captiveportallck = lock('captiveportal');
702

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

    
706
	if (is_array($config['captiveportal']['passthrumac'])) {
707

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

    
715
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
716
			/* record passthru mac so it can be recognized and let thru */
717
			fwrite($fd, $macent['mac'] . "\n");
718
		}
719

    
720
		fclose($fd);
721
	}
722

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

    
741
	if (!$lock)
742
		unlock($captiveportallck);
743

    
744
	return 0;
745
}
746

    
747
function captiveportal_allowedip_configure() {
748
	global $config, $g;
749

    
750
	/* clear out existing allowed ips, if necessary */
751
	mwexec("/sbin/ipfw table 1 flush");
752
	mwexec("/sbin/ipfw table 2 flush");
753

    
754
	if (is_array($config['captiveportal']['allowedip'])) {
755
		$tableone = false;
756
		$tabletwo = false;
757
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
758
			/* insert address in ipfw table */
759
			if ($ipent['dir'] == "from") {
760
				mwexec("/sbin/ipfw table 1 add {$ipent['ip']}");
761
				$tableone = true;
762
			} else { 
763
				mwexec("/sbin/ipfw table 2 add {$ipent['ip']}");
764
				$tabletwo = true;
765
			}
766
		}
767
		if ($tableone == true) {
768
			mwexec("/sbin/ipfw add 1890 set 1 skipto 50000 ip from table\(1\) to any in");
769
			mwexec("/sbin/ipfw add 1891 set 1 skipto 50000 ip from any to table\(1\) out");
770
		}
771
		if ($tabletwo == true) {
772
			mwexec("/sbin/ipfw add 1892 set 1 skipto 50000 ip from any to table\(2\) in");
773
			mwexec("/sbin/ipfw add 1893 set 1 skipto 50000 ip from table\(2\) to any out");
774
		}
775
	}
776

    
777
    return 0;
778
}
779

    
780
/* get last activity timestamp given client IP address */
781
function captiveportal_get_last_activity($ip) {
782

    
783
	$ipfwoutput = "";
784

    
785
	exec("/sbin/ipfw table 3 entrystats {$ip} 2>/dev/null", $ipfwoutput);
786
	/* Reading only from one of the tables is enough of approximation. */
787
	if ($ipfwoutput[0]) {
788
		$ri = explode(" ", $ipfwoutput[0]);
789
		if ($ri[4])
790
			return $ri[4];
791
	}
792

    
793
	return 0;
794
}
795

    
796
/* read RADIUS servers into array */
797
function captiveportal_get_radius_servers() {
798

    
799
        global $g;
800

    
801
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
802
                $radiusservers = array();
803
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius.db", 
804
			FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
805
		if ($cpradiusdb)
806
		foreach($cpradiusdb as $cpradiusentry) {
807
                	$line = trim($cpradiusentry);
808
                        if ($line) {
809
                        	$radsrv = array();
810
                                list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
811
                        	$radiusservers[] = $radsrv;
812
                        }
813
		}
814

    
815
		return $radiusservers;
816
        }
817

    
818
        return false;
819
}
820

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

    
834
/* log simple messages to syslog */
835
function captiveportal_syslog($message) {
836
	define_syslog_variables();
837
	$message = trim($message);
838
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
839
	// Log it
840
	syslog(LOG_INFO, $message);
841
	closelog();
842
}
843

    
844
function radius($username,$password,$clientip,$clientmac,$type) {
845
    global $g, $config;
846

    
847
    /* Start locking from the beginning of an authentication session */
848
    $captiveportallck = lock('captiveportal');
849

    
850
    $ruleno = captiveportal_get_next_ipfw_ruleno();
851

    
852
    /* If the pool is empty, return appropriate message and fail authentication */
853
    if (is_null($ruleno)) {
854
        $auth_list = array();
855
        $auth_list['auth_val'] = 1;
856
        $auth_list['error'] = "System reached maximum login capacity";
857
        unlock($captiveportallck);
858
        return $auth_list;
859
    }
860

    
861
    /*
862
     * Drop the lock since radius takes some time to finish.
863
     * The implementation is reentrant so we gain speed with this.
864
     */
865
    unlock($captiveportallck);
866

    
867
    $radiusservers = captiveportal_get_radius_servers();
868

    
869
    $auth_list = RADIUS_AUTHENTICATION($username,
870
                    $password,
871
                    $radiusservers,
872
                    $clientip,
873
                    $clientmac,
874
                    $ruleno);
875

    
876
    $captiveportallck = lock('captiveportal');
877

    
878
    if ($auth_list['auth_val'] == 2) {
879
        captiveportal_logportalauth($username,$clientmac,$clientip,$type);
880
        $sessionid = portal_allow($clientip,
881
                    $clientmac,
882
                    $username,
883
                    $password,
884
                    $auth_list,
885
                    $ruleno);
886
    }
887

    
888
    unlock($captiveportallck);
889

    
890
    return $auth_list;
891

    
892
}
893

    
894
/* read captive portal DB into array */
895
function captiveportal_read_db() {
896

    
897
        global $g;
898

    
899
        $cpdb = array();
900
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
901
        if ($fd) {
902
                while (!feof($fd)) {
903
                        $line = trim(fgets($fd));
904
                        if ($line) {
905
                                $cpdb[] = explode(",", $line);
906
                        }
907
                }
908
                fclose($fd);
909
        }
910
        return $cpdb;
911
}
912

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

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

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

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

    
1004
function getVolume($ip) {
1005

    
1006
    $volume = array();
1007

    
1008
    // Initialize vars properly, since we don't want NULL vars
1009
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1010

    
1011
    // Ingress
1012
    $ipfwin = "";
1013
    $ipfwout = "";
1014
    $matchesin = "";
1015
    $matchesout = "";
1016
    exec("/sbin/ipfw table 3 entrystats {$ip}", $ipfwin);
1017
    if ($ipfwin[0]) {
1018
		$ipfwin = split(" ", $ipfwin[0]);
1019
		$volume['input_pkts'] = $ipfwin[2];
1020
		$volume['input_bytes'] = $ipfwin[3];
1021
    }
1022

    
1023
    exec("/sbin/ipfw table 4 entrystats {$ip}", $ipfwout);
1024
    if ($ipfwout[0]) {
1025
        $ipfwout = split(" ", $ipfwout[0]);
1026
        $volume['output_pkts'] = $ipfwout[2];
1027
        $volume['output_bytes'] = $ipfwout[3];
1028
    }
1029

    
1030
    return $volume;
1031
}
1032

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

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

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

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

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

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

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

    
1094
	return false;
1095
}
1096

    
1097
?>
(6-6/48)