Project

General

Profile

Download (34.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("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
					$cpips[] = $cpipm;
78
					mwexec("/sbin/ifconfig {$tmpif} ipfwfilter");
79
				}
80
			}
81
		}
82
		if (count($cpips) > 0) {
83
			$cpactive = true;
84
			$cpinterface = "{ {$cpinterface} } ";
85
		}
86
	}
87

    
88
	if ($cpactive == true) {
89

    
90
		if ($g['booting'])
91
			echo "Starting captive portal... ";
92

    
93
		/* kill any running mini_httpd */
94
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
95
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal-SSL.pid");
96

    
97
		/* kill any running minicron */
98
		killbypid("{$g['varrun_path']}/minicron.pid");
99

    
100
		/* make sure ipfw is loaded */
101
		if (!is_module_loaded("ipfw.ko"))
102
			filter_load_ipfw();
103
		if (isset($config['captiveportal']['peruserbw']) && !is_module_loaded("dummynet.ko"))
104
                        mwexec("/sbin/kldload dummynet");
105

    
106
		/* generate ipfw rules */
107
		$cprules = captiveportal_rules_generate($cpinterface, $cpips);
108

    
109
		/* stop accounting on all clients */
110
		captiveportal_radius_stop_all(true);
111

    
112
		/* initialize minicron interval value */
113
		$croninterval = $config['captiveportal']['croninterval'] ? $config['captiveportal']['croninterval'] : 60;
114

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

    
118
		/* remove old information */
119
		unlink_if_exists("{$g['vardb_path']}/captiveportal.nextrule");
120
		unlink_if_exists("{$g['vardb_path']}/captiveportal.db");
121
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
122
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip.db");
123
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius.db");
124
		mwexec("/sbin/ipfw table all flush");
125

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

    
158

    
159

    
160
EOD;
161
		}
162

    
163
		$fd = @fopen("{$g['varetc_path']}/captiveportal.html", "w");
164
		if ($fd) {
165
			fwrite($fd, $htmltext);
166
			fclose($fd);
167
		}
168

    
169
		/* write error page */
170
		if ($config['captiveportal']['page']['errtext'])
171
			$errtext = base64_decode($config['captiveportal']['page']['errtext']);
172
		else {
173
			/* example page */
174
			$errtext = <<<EOD
175
<html>
176
<head>
177
<title>Authentication error</title>
178
</head>
179
<body>
180
<font color="#cc0000"><h2>Authentication error</h2></font>
181
<b>
182
Username and/or password invalid.
183
<br><br>
184
<a href="javascript:history.back(); ">Go back</a>
185
</b>
186
</body>
187
</html>
188

    
189
EOD;
190
		}
191

    
192
		$fd = @fopen("{$g['varetc_path']}/captiveportal-error.html", "w");
193
		if ($fd) {
194
			fwrite($fd, $errtext);
195
			fclose($fd);
196
		}
197

    
198
		/* write elements */
199
		captiveportal_write_elements();
200

    
201
		/* load rules */
202
		mwexec("/sbin/ipfw -f delete set 1");
203
		mwexec("/sbin/ipfw -f delete set 2");
204
		mwexec("/sbin/ipfw -f delete set 3");
205

    
206
		/* ipfw cannot accept rules directly on stdin,
207
		   so we have to write them to a temporary file first */
208
		$fd = @fopen("{$g['tmp_path']}/ipfw.cp.rules", "w");
209
		if (!$fd) {
210
			printf("Cannot open ipfw.cp.rules in captiveportal_configure()\n");
211
			return 1;
212
		}
213

    
214
		fwrite($fd, $cprules);
215
		fclose($fd);
216

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

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

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

    
224
		chdir($g['captiveportal_path']);
225

    
226
		if ($config['captiveportal']['maxproc'])
227
			$maxproc = $config['captiveportal']['maxproc'];
228
		else
229
			$maxproc = 16;
230

    
231
		$use_fastcgi = true;
232

    
233
		if(isset($config['captiveportal']['httpslogin'])) {
234
			$cert = base64_decode($config['captiveportal']['certificate']);
235
			$key = base64_decode($config['captiveportal']['private-key']);
236
			/* generate lighttpd configuration */
237
			system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal-SSL.conf",
238
				$cert, $key, "", "lighty-CaptivePortal-ssl.pid", "8001", "/usr/local/captiveportal/",
239
					"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
240
		}
241

    
242
		/* generate lighttpd configuration */
243
		system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal.conf",
244
			"", "", "", "lighty-CaptivePortal.pid", "8000", "/usr/local/captiveportal/",
245
				"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
246

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

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

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

    
258
		/* generate passthru mac database */
259
		captiveportal_passthrumac_configure(true);
260
		/* allowed ipfw rules to make allowed ip work */
261
		captiveportal_allowedip_configure();
262

    
263
		/* generate radius server database */
264
		if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
265
				($config['captiveportal']['auth_method'] == "radius"))) {
266
			$radiusip = $config['captiveportal']['radiusip'];
267
			$radiusip2 = ($config['captiveportal']['radiusip2']) ? $config['captiveportal']['radiusip2'] : null;
268

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

    
274
			if ($config['captiveportal']['radiusacctport'])
275
				$radiusacctport = $config['captiveportal']['radiusacctport'];
276
			else
277
				$radiusacctport = 1813;
278

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

    
284
			$radiuskey = $config['captiveportal']['radiuskey'];
285
			$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
286

    
287
			$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db", "w");
288
			if (!$fd) {
289
				printf("Error: cannot open radius DB file in captiveportal_configure().\n");
290
				return 1;
291
			} else if (isset($radiusip2, $radiuskey2)) {
292
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . "\n"
293
					 . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2);
294
			} else {
295
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey);
296
			}
297
			fclose($fd);
298
		}
299

    
300
		if ($g['booting'])
301
			echo "done\n";
302

    
303
	} else {
304
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
305
		killbypid("{$g['varrun_path']}/minicron.pid");
306

    
307
		captiveportal_radius_stop_all(true);
308

    
309
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
310

    
311
		/* unload ipfw */
312
		mwexec("/sbin/kldunload ipfw.ko");
313
		$listifs = get_configured_interface_list_by_realif();
314
		foreach ($listifs as $listrealif => $listif)
315
			mwexec("/sbin/ifconfig {$listrealif} -ipfwfilter");
316
	}
317

    
318
	unlock($captiveportallck);
319
	
320
	return 0;
321
}
322

    
323
function captiveportal_rules_generate($cpif, &$cpiparray) {
324
	global $config, $g;
325

    
326
	$cpifn = $config['captiveportal']['interface'];
327
	$lanip = get_interface_ip("lan");
328
	
329
	/* note: the captive portal daemon inserts all pass rules for authenticated
330
	   clients as skipto 50000 rules to make traffic shaping work */
331

    
332
	$cprules =  "add 500 set 1 allow pfsync from any to any\n";
333
	$cprules .= "add 500 set 1 allow carp from any to any\n";
334

    
335
	/* captive portal on LAN interface? */
336
	if (stristr($cpifn,  "lan")) {
337
		/* add anti-lockout rules */
338
		$cprules .= <<<EOD
339
add 500 set 1 pass all from $lanip to any out $cpif
340
add 501 set 1 pass all from any to $lanip in $cpif
341

    
342
EOD;
343
	}
344

    
345
	$cprules .= <<<EOD
346
add 1000 set 1 skipto 1150 all from any to any not layer2
347
# layer 2: pass ARP
348
add 1100 set 1 pass layer2 mac-type arp
349
# pfsense requires for WPA
350
add 1100 set 1 pass layer2 mac-type 0x888e
351
add 1100 set 1 pass layer2 mac-type 0x88c7
352

    
353
# PPP Over Ethernet Discovery Stage
354
add 1100 set 1 pass layer2 mac-type 0x8863
355
# PPP Over Ethernet Session Stage
356
add 1100 set 1 pass layer2 mac-type 0x8864
357
# Allow WPA
358
add 1100 set 1 pass layer2 mac-type 0x888e
359

    
360
# layer 2: block anything else non-IP
361
add 1101 set 1 deny layer2 not mac-type ip
362
# layer 2: check if MAC addresses of authenticated clients are correct
363
add 1102 set 1 skipto 2000 layer2
364

    
365
EOD;
366

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

    
390
		if (isset($config['captiveportal']['httpslogin'])) {
391
			$rulenum++;
392
			$cprules .= "add {$rulenum} set 1 pass tcp from any to {$cpip} 8001 in \n";
393
			$rulenum++;
394
			$cprules .= "add {$rulenum} set 1 pass tcp from {$cpip} 8001 to any out \n";
395
		}
396
	}
397
	$rulenum++;
398

    
399
	if (isset($config['captiveportal']['peruserbw'])) {
400
		$cprules .= "add {$rulenum} set 2 pipe tablearg ip from table(3) to any in\n";
401
		$rulenum++;
402
		$cprules .= "add {$rulenum} set 2 pipe tablearg ip from any to table(4) out\n";
403
		$rulenum++;
404
	} else {
405
		$cprules .= "add {$rulenum} set 2 skipto 50000 ip from table(3) to any in\n";
406
                $rulenum++;
407
                $cprules .= "add {$rulenum} set 2 skipto 50000 ip from any to table(4) out\n";
408
                $rulenum++;
409
	}
410
	
411
       $cprules .= <<<EOD
412

    
413
# redirect non-authenticated clients to captive portal
414
add 1990 set 1 fwd 127.0.0.1,8000 tcp from any to any 80 in
415
# let the responses from the captive portal web server back out
416
add 1991 set 1 pass tcp from any 80 to any out
417
# block everything else
418
add 1992 set 1 deny all from any to any
419

    
420
# ... 2000-49899: layer2 block rules per authenticated client go here...
421

    
422
# pass everything else on layer2
423
add 49900 set 1 pass all from any to any layer2
424

    
425
EOD;
426

    
427
    return $cprules;
428
}
429

    
430
/* remove clients that have been around for longer than the specified amount of time */
431
/* db file structure:
432
timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time */
433

    
434
/* (password is in Base64 and only saved when reauthentication is enabled) */
435
function captiveportal_prune_old() {
436

    
437
    global $g, $config;
438

    
439
    /* check for expired entries */
440
    if ($config['captiveportal']['timeout'])
441
        $timeout = $config['captiveportal']['timeout'] * 60;
442
    else
443
        $timeout = 0;
444

    
445
    if ($config['captiveportal']['idletimeout'])
446
        $idletimeout = $config['captiveportal']['idletimeout'] * 60;
447
    else
448
        $idletimeout = 0;
449

    
450
    if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && 
451
		!isset($config['captiveportal']['radiussession_timeout']) && !isset($config['voucher']['enable']))
452
        return;
453

    
454
    $captiveportallck = lock('captiveportal');
455

    
456
    /* read database */
457
    $cpdb = captiveportal_read_db();
458

    
459
    $radiusservers = captiveportal_get_radius_servers();
460

    
461
    /*  To make sure we iterate over ALL accounts on every run the count($cpdb) is moved
462
     *  outside of the loop. Otherwise the loop would evaluate count() on every iteration
463
     *  and since $i would increase and count() would decrement they would meet before we
464
     *  had a chance to iterate over all accounts.
465
     */
466
    $unsetindexes = array();
467
    $no_users = count($cpdb);
468
    for ($i = 0; $i < $no_users; $i++) {
469

    
470
        $timedout = false;
471
        $term_cause = 1;
472

    
473
		/* no pruning for fixed mac address entry */
474
		if (portal_mac_fixed($cpdb[$i][3])) {
475
			continue; // check next value
476
		}
477
        /* hard timeout? */
478
        if ($timeout) {
479
            if ((time() - $cpdb[$i][0]) >= $timeout) {
480
                $timedout = true;
481
                $term_cause = 5; // Session-Timeout
482
            }
483
        }
484

    
485
        /* Session-Terminate-Time */
486
        if (!$timedout && !empty($cpdb[$i][9])) {
487
            if (time() >= $cpdb[$i][9]) {
488
                $timedout = true;
489
                $term_cause = 5; // Session-Timeout
490
            }
491
        }
492

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

    
509
	/* if vouchers are configured, activate session timeouts */
510
	if (!$timedout && isset($config['voucher']['enable']) && !empty($cpdb[$i][7])) {
511
		if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
512
			$timedout = true;
513
			$term_cause = 5; // Session-Timeout
514
		}
515
	}
516

    
517
        /* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
518
        if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpdb[$i][7])) {
519
            if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
520
                $timedout = true;
521
                $term_cause = 5; // Session-Timeout
522
            }
523
        }
524

    
525
        if ($timedout) {
526
            captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
527
            captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
528
	    $unsetindexes[$i] = $i;
529
        }
530

    
531
        /* do periodic RADIUS reauthentication? */
532
        if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
533
            !empty($radiusservers)) {
534

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

    
567
            /* check this user against RADIUS again */
568
            $auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
569
                                          base64_decode($cpdb[$i][6]), // password
570
                                            $radiusservers,
571
                                          $cpdb[$i][2], // clientip
572
                                          $cpdb[$i][3], // clientmac
573
                                          $cpdb[$i][1]); // ruleno
574

    
575
            if ($auth_list['auth_val'] == 3) {
576
                captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
577
                captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
578
	        $unsetindexes[$i] = $i;
579
            }
580
        }
581
    }
582
    /* This is a kludge to overcome some php weirdness */
583
    foreach($unsetindexes as $unsetindex)
584
	unset($cpdb[$unsetindex]);
585

    
586
    /* write database */
587
    captiveportal_write_db($cpdb);
588

    
589
    unlock($captiveportallck);
590
}
591

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

    
595
	global $g, $config;
596

    
597
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
598

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

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

    
627
	/* Ensure all pf(4) states are killed. */
628
	mwexec("pfctl -k {$dbent[2]}");
629
	mwexec("pfctl -K {$dbent[2]}");
630

    
631
}
632

    
633
/* remove a single client by ipfw rule number */
634
function captiveportal_disconnect_client($id,$term_cause = 1) {
635

    
636
	global $g, $config;
637

    
638
	$captiveportallck = lock('captiveportal');
639

    
640
	/* read database */
641
	$cpdb = captiveportal_read_db();
642
	$radiusservers = captiveportal_get_radius_servers();
643

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

    
657
	/* write database */
658
	captiveportal_write_db($cpdb);
659

    
660
	unlock($captiveportallck);
661
}
662

    
663
/* send RADIUS acct stop for all current clients */
664
function captiveportal_radius_stop_all($lock = false) {
665
	global $g, $config;
666

    
667
	if (!isset($config['captiveportal']['radacct_enable']))
668
		return;
669

    
670
	if (!$lock)
671
		$captiveportallck = lock('captiveportal');
672

    
673
	$cpdb = captiveportal_read_db();
674

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

    
692
function captiveportal_passthrumac_configure($lock = false) {
693
	global $config, $g;
694

    
695
	if (!$lock)
696
		$captiveportallck = lock('captiveportal');
697

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

    
701
	if (is_array($config['captiveportal']['passthrumac'])) {
702

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

    
710
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
711
			/* record passthru mac so it can be recognized and let thru */
712
			fwrite($fd, $macent['mac'] . "\n");
713
		}
714

    
715
		fclose($fd);
716
	}
717

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

    
736
	if (!$lock)
737
		unlock($captiveportallck);
738

    
739
	return 0;
740
}
741

    
742
function captiveportal_allowedip_configure() {
743
	global $config, $g;
744

    
745
	/* clear out existing allowed ips, if necessary */
746
	mwexec("/sbin/ipfw table 1 flush");
747
	mwexec("/sbin/ipfw table 2 flush");
748

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

    
772
    return 0;
773
}
774

    
775
/* get last activity timestamp given client IP address */
776
function captiveportal_get_last_activity($ip) {
777

    
778
	$ipfwoutput = "";
779

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

    
788
	return 0;
789
}
790

    
791
/* read RADIUS servers into array */
792
function captiveportal_get_radius_servers() {
793

    
794
        global $g;
795

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

    
810
		return $radiusservers;
811
        }
812

    
813
        return false;
814
}
815

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

    
829
/* log simple messages to syslog */
830
function captiveportal_syslog($message) {
831
	define_syslog_variables();
832
	$message = trim($message);
833
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
834
	// Log it
835
	syslog(LOG_INFO, $message);
836
	closelog();
837
}
838

    
839
function radius($username,$password,$clientip,$clientmac,$type) {
840
    global $g, $config;
841

    
842
    /* Start locking from the beginning of an authentication session */
843
    $captiveportallck = lock('captiveportal');
844

    
845
    $ruleno = captiveportal_get_next_ipfw_ruleno();
846

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

    
856
    /*
857
     * Drop the lock since radius takes some time to finish.
858
     * The implementation is reentrant so we gain speed with this.
859
     */
860
    unlock($captiveportallck);
861

    
862
    $radiusservers = captiveportal_get_radius_servers();
863

    
864
    $auth_list = RADIUS_AUTHENTICATION($username,
865
                    $password,
866
                    $radiusservers,
867
                    $clientip,
868
                    $clientmac,
869
                    $ruleno);
870

    
871
    $captiveportallck = lock('captiveportal');
872

    
873
    if ($auth_list['auth_val'] == 2) {
874
        captiveportal_logportalauth($username,$clientmac,$clientip,$type);
875
        $sessionid = portal_allow($clientip,
876
                    $clientmac,
877
                    $username,
878
                    $password,
879
                    $auth_list,
880
                    $ruleno);
881
    }
882

    
883
    unlock($captiveportallck);
884

    
885
    return $auth_list;
886

    
887
}
888

    
889
/* read captive portal DB into array */
890
function captiveportal_read_db() {
891

    
892
        global $g;
893

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

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

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

    
958
/*
959
 * This function will calculate the lowest free firewall ruleno
960
 * within the range specified based on the actual logged on users
961
 *
962
 */
963
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
964
	global $config, $g;
965

    
966
	$ruleno = 0;
967
	if (file_exists("{$g['vardb_path']}/captiveportal.nextrule"))
968
		$ruleno = intval(file_get_contents("{$g['vardb_path']}/captiveportal.nextrule"));
969
	else
970
		$ruleno = 1;
971
	if ($ruleno > 0 && (($rulenos_start + $ruleno) < $rulenos_range_max)) {
972
		/* 
973
		 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
974
		 * and the out pipe ruleno + 1. This removes limitation that where present in 
975
		 * previous version of the peruserbw.
976
		 */
977
		if (isset($config['captiveportal']['peruserbw']))
978
			$ruleno += 2;
979
		else
980
			$ruleno++;
981
		file_put_contents("{$g['vardb_path']}/captiveportal.nextrule", $ruleno);
982
		return $rulenos_start + $ruleno;
983
	}
984
	return NULL;
985
}
986

    
987
/**
988
 * This function will calculate the traffic produced by a client
989
 * based on its firewall rule
990
 *
991
 * Point of view: NAS
992
 *
993
 * Input means: from the client
994
 * Output means: to the client
995
 *
996
 */
997

    
998
function getVolume($ip) {
999

    
1000
    $volume = array();
1001

    
1002
    // Initialize vars properly, since we don't want NULL vars
1003
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1004

    
1005
    // Ingress
1006
    $ipfwin = "";
1007
    $ipfwout = "";
1008
    $matchesin = "";
1009
    $matchesout = "";
1010
    exec("/sbin/ipfw table 3 entrystats {$ip}", $ipfwin);
1011
    if ($ipfwin[0]) {
1012
		$ipfwin = split(" ", $ipfwin[0]);
1013
		$volume['input_pkts'] = $ipfwin[2];
1014
		$volume['input_bytes'] = $ipfwin[3];
1015
    }
1016

    
1017
    exec("/sbin/ipfw table 4 entrystats {$ip}", $ipfwout);
1018
    if ($ipfwout[0]) {
1019
        $ipfwout = split(" ", $ipfwout[0]);
1020
        $volume['output_pkts'] = $ipfwout[2];
1021
        $volume['output_bytes'] = $ipfwout[3];
1022
    }
1023

    
1024
    return $volume;
1025
}
1026

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

    
1041
/**
1042
 * Get the NAS-IP-Address based on the current wan address
1043
 *
1044
 * Use functions in interfaces.inc to find this out
1045
 *
1046
 */
1047

    
1048
function getNasIP()
1049
{
1050
    $nasIp = get_interface_ip();
1051
    if(!$nasIp)
1052
        $nasIp = "0.0.0.0";
1053
    return $nasIp;
1054
}
1055

    
1056
function portal_mac_fixed($clientmac) {
1057
    global $g ;
1058

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

    
1077
function portal_ip_from_client_ip($cliip) {
1078
	global $config;
1079

    
1080
	$interfaces = explode(",", $config['captiveportal']['interface']);
1081
	foreach ($interfaces as $cpif) {
1082
		$ip = get_interface_ip($cpif);
1083
		$sn = get_interface_subnet($cpif);
1084
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1085
			return $ip;
1086
	}
1087

    
1088
	return false;
1089
}
1090

    
1091
?>
(6-6/44)