Project

General

Profile

Download (35.1 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
		/* setup new database in case someone tries to access the status -> captive portal page */
127
		touch("{$g['vardb_path']}/captiveportal.db");
128

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

    
161

    
162

    
163
EOD;
164
		}
165

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

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

    
192
EOD;
193
		}
194

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

    
201
		/* write elements */
202
		captiveportal_write_elements();
203

    
204
		/* load rules */
205
		mwexec("/sbin/ipfw -f delete set 1");
206
		mwexec("/sbin/ipfw -f delete set 2");
207
		mwexec("/sbin/ipfw -f delete set 3");
208

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

    
217
		fwrite($fd, $cprules);
218
		fclose($fd);
219

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

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

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

    
227
		chdir($g['captiveportal_path']);
228

    
229
		if ($config['captiveportal']['maxproc'])
230
			$maxproc = $config['captiveportal']['maxproc'];
231
		else
232
			$maxproc = 16;
233

    
234
		$use_fastcgi = true;
235

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

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

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

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

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

    
261
		/* generate passthru mac database */
262
		captiveportal_passthrumac_configure(true);
263
		/* allowed ipfw rules to make allowed ip work */
264
		captiveportal_allowedip_configure();
265

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

    
272
			if ($config['captiveportal']['radiusport'])
273
				$radiusport = $config['captiveportal']['radiusport'];
274
			else
275
				$radiusport = 1812;
276

    
277
			if ($config['captiveportal']['radiusacctport'])
278
				$radiusacctport = $config['captiveportal']['radiusacctport'];
279
			else
280
				$radiusacctport = 1813;
281

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

    
287
			$radiuskey = $config['captiveportal']['radiuskey'];
288
			$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
289

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

    
303
		if ($g['booting'])
304
			echo "done\n";
305

    
306
	} else {
307
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
308
		killbypid("{$g['varrun_path']}/minicron.pid");
309

    
310
		captiveportal_radius_stop_all(true);
311

    
312
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
313

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

    
321
	unlock($captiveportallck);
322
	
323
	return 0;
324
}
325

    
326
function captiveportal_rules_generate($cpif, &$cpiparray) {
327
	global $config, $g;
328

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

    
335
	$cprules =  "add 500 set 1 allow pfsync from any to any\n";
336
	$cprules .= "add 500 set 1 allow carp from any to any\n";
337

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

    
345
EOD;
346
	}
347

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

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

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

    
368
EOD;
369

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

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

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

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

    
423
# ... 2000-49899: layer2 block rules per authenticated client go here...
424

    
425
# pass everything else on layer2
426
add 49900 set 1 pass all from any to any layer2
427

    
428
EOD;
429

    
430
    return $cprules;
431
}
432

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

    
437
/* (password is in Base64 and only saved when reauthentication is enabled) */
438
function captiveportal_prune_old() {
439

    
440
    global $g, $config;
441

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

    
448
    if ($config['captiveportal']['idletimeout'])
449
        $idletimeout = $config['captiveportal']['idletimeout'] * 60;
450
    else
451
        $idletimeout = 0;
452

    
453
    if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && 
454
		!isset($config['captiveportal']['radiussession_timeout']) && !isset($config['voucher']['enable']))
455
        return;
456

    
457
    $captiveportallck = lock('captiveportal');
458

    
459
    /* read database */
460
    $cpdb = captiveportal_read_db();
461

    
462
    $radiusservers = captiveportal_get_radius_servers();
463

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

    
473
        $timedout = false;
474
        $term_cause = 1;
475

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

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

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

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

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

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

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

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

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

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

    
589
    /* write database */
590
    captiveportal_write_db($cpdb);
591

    
592
    unlock($captiveportallck);
593
}
594

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

    
598
	global $g, $config;
599

    
600
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
601

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

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

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

    
634
}
635

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

    
639
	global $g, $config;
640

    
641
	$captiveportallck = lock('captiveportal');
642

    
643
	/* read database */
644
	$cpdb = captiveportal_read_db();
645
	$radiusservers = captiveportal_get_radius_servers();
646

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

    
660
	/* write database */
661
	captiveportal_write_db($cpdb);
662

    
663
	unlock($captiveportallck);
664
}
665

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

    
670
	if (!isset($config['captiveportal']['radacct_enable']))
671
		return;
672

    
673
	if (!$lock)
674
		$captiveportallck = lock('captiveportal');
675

    
676
	$cpdb = captiveportal_read_db();
677

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

    
695
function captiveportal_passthrumac_configure($lock = false) {
696
	global $config, $g;
697

    
698
	if (!$lock)
699
		$captiveportallck = lock('captiveportal');
700

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

    
704
	if (is_array($config['captiveportal']['passthrumac'])) {
705

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

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

    
718
		fclose($fd);
719
	}
720

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

    
739
	if (!$lock)
740
		unlock($captiveportallck);
741

    
742
	return 0;
743
}
744

    
745
function captiveportal_allowedip_configure() {
746
	global $config, $g;
747

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

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

    
775
    return 0;
776
}
777

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

    
781
	$ipfwoutput = "";
782

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

    
791
	return 0;
792
}
793

    
794
/* read RADIUS servers into array */
795
function captiveportal_get_radius_servers() {
796

    
797
        global $g;
798

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

    
813
		return $radiusservers;
814
        }
815

    
816
        return false;
817
}
818

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

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

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

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

    
848
    $ruleno = captiveportal_get_next_ipfw_ruleno();
849

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

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

    
865
    $radiusservers = captiveportal_get_radius_servers();
866

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

    
874
    $captiveportallck = lock('captiveportal');
875

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

    
886
    unlock($captiveportallck);
887

    
888
    return $auth_list;
889

    
890
}
891

    
892
/* read captive portal DB into array */
893
function captiveportal_read_db() {
894

    
895
        global $g;
896

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

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

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

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

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

    
1002
function getVolume($ip) {
1003

    
1004
    $volume = array();
1005

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

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

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

    
1028
    return $volume;
1029
}
1030

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

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

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

    
1060
function portal_mac_fixed($clientmac) {
1061
    global $g ;
1062

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

    
1081
function portal_ip_from_client_ip($cliip) {
1082
	global $config;
1083

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

    
1092
	return false;
1093
}
1094

    
1095
?>
(6-6/44)