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

    
38
/* include all configuration functions */
39
require_once("globals.inc");
40
require_once("util.inc");
41
require_once("radius_authentication.inc");
42
require_once("radius_accounting.inc");
43
require_once("radius.inc");
44
require_once("voucher.inc");
45

    
46
function captiveportal_configure() {
47
	global $config, $g;
48

    
49
	$captiveportallck = lock('captiveportal');
50
	
51
	$cpactive = false;
52
	if (isset($config['captiveportal']['enable'])) {
53
		$cpips = array();
54
		$ifaces = get_configured_interface_list();
55
		$cpinterfaces = explode(",", $config['captiveportal']['interface']);
56
		$firsttime = 0;
57
		foreach ($cpinterfaces as $cpifgrp) {
58
			if (!isset($ifaces[$cpifgrp]))
59
				continue;
60
			$tmpif = get_real_interface($cpifgrp);
61
			if (!empty($tmpif)) {
62
				mwexec("/sbin/ifconfig {$tmpif} -ipfwfilter");
63
				if ($firsttime > 0)
64
					$cpinterface .= " or ";
65
				$cpinterface .= "via {$tmpif}"; 
66
				$firsttime = 1;
67
				$cpipm = get_interface_ip($cpifgrp);
68
				if (is_ipaddr($cpipm)) {
69
					$cpips[] = $cpipm;
70
					mwexec("/sbin/ifconfig {$tmpif} ipfwfilter");
71
				}
72
			}
73
		}
74
		if (count($cpips) > 0) {
75
			$cpactive = true;
76
			$cpinterface = "{ {$cpinterface} } ";
77
		}
78
	}
79

    
80
	if ($cpactive == true) {
81

    
82
		if ($g['booting'])
83
			echo "Starting captive portal... ";
84

    
85
		/* kill any running mini_httpd */
86
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
87
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal-SSL.pid");
88

    
89
		/* kill any running minicron */
90
		killbypid("{$g['varrun_path']}/minicron.pid");
91

    
92
		/* make sure ipfw is loaded */
93
		if (!is_module_loaded("ipfw.ko"))
94
			filter_load_ipfw();
95
		if (isset($config['captiveportal']['peruserbw']) && !is_module_loaded("dummynet.ko"))
96
                        mwexec("/sbin/kldload dummynet");
97

    
98
		/* generate ipfw rules */
99
		$cprules = captiveportal_rules_generate($cpinterface, $cpips);
100

    
101
		/* stop accounting on all clients */
102
		captiveportal_radius_stop_all(true);
103

    
104
		/* initialize minicron interval value */
105
		$croninterval = $config['captiveportal']['croninterval'] ? $config['captiveportal']['croninterval'] : 60;
106

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

    
110
		/* remove old information */
111
		unlink_if_exists("{$g['vardb_path']}/captiveportal.nextrule");
112
		unlink_if_exists("{$g['vardb_path']}/captiveportal.db");
113
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
114
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip.db");
115
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius.db");
116

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

    
149

    
150

    
151
EOD;
152
		}
153

    
154
		$fd = @fopen("{$g['varetc_path']}/captiveportal.html", "w");
155
		if ($fd) {
156
			fwrite($fd, $htmltext);
157
			fclose($fd);
158
		}
159

    
160
		/* write error page */
161
		if ($config['captiveportal']['page']['errtext'])
162
			$errtext = base64_decode($config['captiveportal']['page']['errtext']);
163
		else {
164
			/* example page */
165
			$errtext = <<<EOD
166
<html>
167
<head>
168
<title>Authentication error</title>
169
</head>
170
<body>
171
<font color="#cc0000"><h2>Authentication error</h2></font>
172
<b>
173
Username and/or password invalid.
174
<br><br>
175
<a href="javascript:history.back(); ">Go back</a>
176
</b>
177
</body>
178
</html>
179

    
180
EOD;
181
		}
182

    
183
		$fd = @fopen("{$g['varetc_path']}/captiveportal-error.html", "w");
184
		if ($fd) {
185
			fwrite($fd, $errtext);
186
			fclose($fd);
187
		}
188

    
189
		/* write elements */
190
		captiveportal_write_elements();
191

    
192
		/* load rules */
193
		mwexec("/sbin/ipfw -f delete set 1");
194
		mwexec("/sbin/ipfw -f delete set 2");
195
		mwexec("/sbin/ipfw -f delete set 3");
196

    
197
		/* ipfw cannot accept rules directly on stdin,
198
		   so we have to write them to a temporary file first */
199
		$fd = @fopen("{$g['tmp_path']}/ipfw.cp.rules", "w");
200
		if (!$fd) {
201
			printf("Cannot open ipfw.cp.rules in captiveportal_configure()\n");
202
			return 1;
203
		}
204

    
205
		fwrite($fd, $cprules);
206
		fclose($fd);
207

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

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

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

    
215
		chdir($g['captiveportal_path']);
216

    
217
		if ($config['captiveportal']['maxproc'])
218
			$maxproc = $config['captiveportal']['maxproc'];
219
		else
220
			$maxproc = 16;
221

    
222
		$use_fastcgi = true;
223

    
224
		if(isset($config['captiveportal']['httpslogin'])) {
225
			$cert = base64_decode($config['captiveportal']['certificate']);
226
			$key = base64_decode($config['captiveportal']['private-key']);
227
			/* generate lighttpd configuration */
228
			system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal-SSL.conf",
229
				$cert, $key, "", "lighty-CaptivePortal-ssl.pid", "8001", "/usr/local/captiveportal/",
230
					"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
231
		}
232

    
233
		/* generate lighttpd configuration */
234
		system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal.conf",
235
			"", "", "", "lighty-CaptivePortal.pid", "8000", "/usr/local/captiveportal/",
236
				"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
237

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

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

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

    
249
		/* generate passthru mac database */
250
		captiveportal_passthrumac_configure(true);
251
		/* allowed ipfw rules to make allowed ip work */
252
		captiveportal_allowedip_configure();
253

    
254
		/* generate radius server database */
255
		if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
256
				($config['captiveportal']['auth_method'] == "radius"))) {
257
			$radiusip = $config['captiveportal']['radiusip'];
258
			$radiusip2 = ($config['captiveportal']['radiusip2']) ? $config['captiveportal']['radiusip2'] : null;
259

    
260
			if ($config['captiveportal']['radiusport'])
261
				$radiusport = $config['captiveportal']['radiusport'];
262
			else
263
				$radiusport = 1812;
264

    
265
			if ($config['captiveportal']['radiusacctport'])
266
				$radiusacctport = $config['captiveportal']['radiusacctport'];
267
			else
268
				$radiusacctport = 1813;
269

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

    
275
			$radiuskey = $config['captiveportal']['radiuskey'];
276
			$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
277

    
278
			$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db", "w");
279
			if (!$fd) {
280
				printf("Error: cannot open radius DB file in captiveportal_configure().\n");
281
				return 1;
282
			} else if (isset($radiusip2, $radiuskey2)) {
283
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . "\n"
284
					 . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2);
285
			} else {
286
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey);
287
			}
288
			fclose($fd);
289
		}
290

    
291
		if ($g['booting'])
292
			echo "done\n";
293

    
294
	} else {
295
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
296
		killbypid("{$g['varrun_path']}/minicron.pid");
297

    
298
		captiveportal_radius_stop_all(true);
299

    
300
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
301

    
302
		/* unload ipfw */
303
		mwexec("/sbin/kldunload ipfw.ko");
304
		$listifs = get_configured_interface_list_by_realif();
305
		foreach ($listifs as $listrealif => $listif)
306
			mwexec("/sbin/ifconfig {$listrealif} -ipfwfilter");
307
	}
308

    
309
	unlock($captiveportallck);
310
	
311
	return 0;
312
}
313

    
314
function captiveportal_rules_generate($cpif, &$cpiparray) {
315
	global $config, $g;
316

    
317
	$cpifn = $config['captiveportal']['interface'];
318
	$lanip = get_interface_ip("lan");
319
	
320
	/* note: the captive portal daemon inserts all pass rules for authenticated
321
	   clients as skipto 50000 rules to make traffic shaping work */
322

    
323
	$cprules =  "add 500 set 1 allow pfsync from any to any\n";
324
	$cprules .= "add 500 set 1 allow carp from any to any\n";
325

    
326
	/* captive portal on LAN interface? */
327
	if (stristr($cpifn,  "lan")) {
328
		/* add anti-lockout rules */
329
		$cprules .= <<<EOD
330
add 500 set 1 pass all from $lanip to any out $cpif
331
add 501 set 1 pass all from any to $lanip in $cpif
332

    
333
EOD;
334
	}
335

    
336
	$cprules .= <<<EOD
337
add 1000 set 1 skipto 1150 all from any to any not layer2
338
# layer 2: pass ARP
339
add 1100 set 1 pass layer2 mac-type arp
340
# pfsense requires for WPA
341
add 1100 set 1 pass layer2 mac-type 0x888e
342
add 1100 set 1 pass layer2 mac-type 0x88c7
343

    
344
# PPP Over Ethernet Discovery Stage
345
add 1100 set 1 pass layer2 mac-type 0x8863
346
# PPP Over Ethernet Session Stage
347
add 1100 set 1 pass layer2 mac-type 0x8864
348
# Allow WPA
349
add 1100 set 1 pass layer2 mac-type 0x888e
350

    
351
# layer 2: block anything else non-IP
352
add 1101 set 1 deny layer2 not mac-type ip
353
# layer 2: check if MAC addresses of authenticated clients are correct
354
add 1102 set 1 skipto 2000 layer2
355

    
356
EOD;
357

    
358
	$rulenum = 1150;
359
	foreach ($cpiparray as $cpip) {
360
		//# allow access to our DHCP server (which needs to be able to ping clients as well)
361
		$cprules .= "add {$rulenum} set 1 pass udp from any 68 to 255.255.255.255 67 in \n";
362
		$rulenum++;
363
		$cprules .= "add {$rulenum} set 1 pass udp from any 68 to {$cpip} 67 in \n";
364
		$rulenum++;
365
		$cprules .= "add {$rulenum} set 1 pass udp from {$cpip} 67 to any 68 out \n";
366
		$rulenum++;
367
		$cprules .= "add {$rulenum} set 1 pass icmp from {$cpip} to any out icmptype 8\n";
368
		$rulenum++;
369
		$cprules .= "add {$rulenum} set 1 pass icmp from any to {$cpip} in icmptype 0 \n";
370
		$rulenum++;
371
		//# allow access to our DNS forwarder
372
		$cprules .= "add {$rulenum} set 1 pass udp from any to {$cpip} 53 in \n";
373
		$rulenum++;
374
		$cprules .= "add {$rulenum} set 1 pass udp from {$cpip} 53 to any out \n";
375
		$rulenum++;
376
		# allow access to our web server
377
		$cprules .= "add {$rulenum} set 1 pass tcp from any to {$cpip} 8000 in \n";
378
		$rulenum++;
379
		$cprules .= "add {$rulenum} set 1 pass tcp from {$cpip} 8000 to any out \n";
380

    
381
		if (isset($config['captiveportal']['httpslogin'])) {
382
			$rulenum++;
383
			$cprules .= "add {$rulenum} set 1 pass tcp from any to {$cpip} 8001 in \n";
384
			$rulenum++;
385
			$cprules .= "add {$rulenum} set 1 pass tcp from {$cpip} 8001 to any out \n";
386
		}
387
	}
388
	$rulenum++;
389

    
390
	if (isset($config['captiveportal']['peruserbw'])) {
391
		$cprules .= "add {$rulenum} set 2 pipe tablearg ip from table(3) to any in\n";
392
		$rulenum++;
393
		$cprules .= "add {$rulenum} set 2 pipe tablearg ip from any to table(4) out\n";
394
		$rulenum++;
395
	} else {
396
		$cprules .= "add {$rulenum} set 2 skipto 50000 ip from table(3) to any in\n";
397
                $rulenum++;
398
                $cprules .= "add {$rulenum} set 2 skipto 50000 ip from any to table(4) out\n";
399
                $rulenum++;
400
	}
401
	
402
       $cprules .= <<<EOD
403

    
404
# redirect non-authenticated clients to captive portal
405
add 1990 set 1 fwd 127.0.0.1,8000 tcp from any to any 80 in
406
# let the responses from the captive portal web server back out
407
add 1991 set 1 pass tcp from any 80 to any out
408
# block everything else
409
add 1992 set 1 deny all from any to any
410

    
411
# ... 2000-49899: layer2 block rules per authenticated client go here...
412

    
413
# pass everything else on layer2
414
add 49900 set 1 pass all from any to any layer2
415

    
416
EOD;
417

    
418
    return $cprules;
419
}
420

    
421
/* remove clients that have been around for longer than the specified amount of time */
422
/* db file structure:
423
timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time */
424

    
425
/* (password is in Base64 and only saved when reauthentication is enabled) */
426
function captiveportal_prune_old() {
427

    
428
    global $g, $config;
429

    
430
    /* check for expired entries */
431
    if ($config['captiveportal']['timeout'])
432
        $timeout = $config['captiveportal']['timeout'] * 60;
433
    else
434
        $timeout = 0;
435

    
436
    if ($config['captiveportal']['idletimeout'])
437
        $idletimeout = $config['captiveportal']['idletimeout'] * 60;
438
    else
439
        $idletimeout = 0;
440

    
441
    if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && 
442
		!isset($config['captiveportal']['radiussession_timeout']) && !isset($config['voucher']['enable']))
443
        return;
444

    
445
    $captiveportallck = lock('captiveportal');
446

    
447
    /* read database */
448
    $cpdb = captiveportal_read_db();
449

    
450
    $radiusservers = captiveportal_get_radius_servers();
451

    
452
 	/*  To make sure we iterate over ALL accounts on every run the count($cpdb) is moved outside of the loop. Otherwise
453
     *  the loop would evalate count() on every iteration and since $i would increase and count() would decrement they
454
     *  would meet before we had a chance to iterate over all accounts.
455
     */
456
    $unsetindexes = array();
457
    $no_users = count($cpdb);
458
    for ($i = 0; $i < $no_users; $i++) {
459

    
460
        $timedout = false;
461
        $term_cause = 1;
462

    
463
		/* no pruning for fixed mac address entry */
464
		if (portal_mac_fixed($cpdb[$i][3])) {
465
			continue; // check next value
466
		}
467
        /* hard timeout? */
468
        if ($timeout) {
469
            if ((time() - $cpdb[$i][0]) >= $timeout) {
470
                $timedout = true;
471
                $term_cause = 5; // Session-Timeout
472
            }
473
        }
474

    
475
        /* Session-Terminate-Time */
476
        if (!$timedout && !empty($cpdb[$i][9])) {
477
            if (time() >= $cpdb[$i][9]) {
478
                $timedout = true;
479
                $term_cause = 5; // Session-Timeout
480
            }
481
        }
482

    
483
        /* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
484
        $idletimeout = (is_numeric($cpdb[$i][8])) ? $cpdb[$i][8] : $idletimeout;
485
        /* if an idle timeout is specified, get last activity timestamp from ipfw */
486
        if (!$timedout && $idletimeout) {
487
            $lastact = captiveportal_get_last_activity($cpdb[$i][2]);
488
			/*  if the user has logged on but not sent any trafic they will never be logged out.
489
			 *  We "fix" this by setting lastact to the login timestamp 
490
			 */
491
			$lastact = $lastact ? $lastact : $cpdb[$i][0];
492
            if ($lastact && ((time() - $lastact) >= $idletimeout)) {
493
                $timedout = true;
494
                $term_cause = 4; // Idle-Timeout
495
                $stop_time = $lastact; // Entry added to comply with WISPr
496
            }
497
        }
498

    
499
	/* if vouchers are configured, activate session timeouts */
500
	if (!$timedout && isset($config['voucher']['enable']) && !empty($cpdb[$i][7])) {
501
		if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
502
			$timedout = true;
503
			$term_cause = 5; // Session-Timeout
504
		}
505
	}
506

    
507
        /* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
508
        if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpdb[$i][7])) {
509
            if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
510
                $timedout = true;
511
                $term_cause = 5; // Session-Timeout
512
            }
513
        }
514

    
515
        if ($timedout) {
516
            captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
517
            captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
518
	    $unsetindexes[$i] = $i;
519
        }
520

    
521
        /* do periodic RADIUS reauthentication? */
522
        if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
523
            ($radiusservers !== false)) {
524

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

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

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

    
582
    /* write database */
583
    captiveportal_write_db($cpdb);
584

    
585
    unlock($captiveportallck);
586
}
587

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

    
591
	global $g, $config;
592

    
593
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
594

    
595
	/* this client needs to be deleted - remove ipfw rules */
596
	if (isset($config['captiveportal']['radacct_enable']) && isset($radiusservers[0])) {
597
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
598
							   $dbent[4], // username
599
							   $dbent[5], // sessionid
600
							   $dbent[0], // start time
601
							   $radiusservers[0]['ipaddr'],
602
							   $radiusservers[0]['acctport'],
603
							   $radiusservers[0]['key'],
604
							   $dbent[2], // clientip
605
							   $dbent[3], // clientmac
606
							   $term_cause, // Acct-Terminate-Cause
607
							   false,
608
							   $stop_time);
609
	}
610

    
611
	mwexec("/sbin/ipfw table 3 delete {$dbent[2]}");
612
	mwexec("/sbin/ipfw table 4 delete {$dbent[2]}");
613
	mwexec("/sbin/ipfw delete {$dbent[1]}");
614

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

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

    
628
}
629

    
630
/* remove a single client by ipfw rule number */
631
function captiveportal_disconnect_client($id,$term_cause = 1) {
632

    
633
	global $g, $config;
634

    
635
	$captiveportallck = lock('captiveportal');
636

    
637
	/* read database */
638
	$cpdb = captiveportal_read_db();
639
	$radiusservers = captiveportal_get_radius_servers();
640

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

    
654
	/* write database */
655
	captiveportal_write_db($cpdb);
656

    
657
	unlock($captiveportallck);
658
}
659

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

    
664
	if (!isset($config['captiveportal']['radacct_enable']))
665
		return;
666

    
667
	if (!$lock)
668
		$captiveportallck = lock('captiveportal');
669

    
670
	$cpdb = captiveportal_read_db();
671

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

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

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

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

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

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

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

    
714
		fclose($fd);
715
	}
716

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

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

    
738
	return 0;
739
}
740

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

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

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

    
771
    return 0;
772
}
773

    
774
/* get last activity timestamp given ipfw rule number */
775
function captiveportal_get_last_activity($ip) {
776

    
777
	$ipfwoutput = "";
778

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

    
787
	return 0;
788
}
789

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

    
793
        global $g;
794

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

    
809
		return $radiusservers;
810
        }
811

    
812
        return false;
813
}
814

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

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

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

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

    
844
    $ruleno = captiveportal_get_next_ipfw_ruleno();
845

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

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

    
861
    $radiusservers = captiveportal_get_radius_servers();
862

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

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

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

    
882
    unlock($captiveportallck);
883

    
884
    return $auth_list;
885

    
886
}
887

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

    
891
        global $g;
892

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

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

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

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

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

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

    
997
function getVolume($ip) {
998

    
999
    $volume = array();
1000

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

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

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

    
1023
    return $volume;
1024
}
1025

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

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

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

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

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

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

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

    
1087
	return false;
1088
}
1089

    
1090
?>
(6-6/44)