Project

General

Profile

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

    
6
	Copyright (C) 2003-2006 Manuel Kasper <mk@neon1.net>.
7
	All rights reserved.
8

    
9
	Redistribution and use in source and binary forms, with or without
10
	modification, are permitted provided that the following conditions are met:
11

    
12
	1. Redistributions of source code must retain the above copyright notice,
13
	   this list of conditions and the following disclaimer.
14

    
15
	2. Redistributions in binary form must reproduce the above copyright
16
	   notice, this list of conditions and the following disclaimer in the
17
	   documentation and/or other materials provided with the distribution.
18

    
19
	THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
20
	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
21
	AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22
	AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
23
	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
	POSSIBILITY OF SUCH DAMAGE.
29

    
30
	This version of captiveportal.inc has been modified by Rob Parker
31
	<rob.parker@keycom.co.uk> to include changes for per-user bandwidth management
32
	via returned RADIUS attributes. This page has been modified to delete any
33
	added rules which may have been created by other per-user code (index.php, etc).
34
	These changes are (c) 2004 Keycom PLC.
35
*/
36

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

    
44
$lockfile = "{$g['varrun_path']}/captiveportal.lock";
45

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

    
49
	if (isset($config['captiveportal']['enable']) &&
50
		(($config['captiveportal']['interface'] == "lan") ||
51
			isset($config['interfaces'][$config['captiveportal']['interface']]['enable']))) {
52

    
53
		if ($g['booting'])
54
			echo "Starting captive portal... ";
55

    
56
		/* kill any running mini_httpd */
57
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
58
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal-SSL.pid");
59

    
60
		/* kill any running minicron */
61
		killbypid("{$g['varrun_path']}/minicron.pid");
62

    
63
		/* generate ipfw rules */
64
		$cprules = captiveportal_rules_generate();
65

    
66
		/* make sure ipfw is loaded */
67
		mwexec("/sbin/kldload ipfw");
68
		
69
		/* 
70
		 * make sure ipfw is the first hook to make CP work correctly on
71
		 * Multi-WAN.
72
		 * Disable the ipfw outer hook it has not value to us.
73
		 */
74
		mwexec("/sbin/sysctl net.inet.ip.pfil.inbound=\"ipfw,pf\"");
75

    
76
		/* 
77
		 * TODO: Check if disabling ipfw hook 
78
		 * does not break accounting. 
79
		 */
80
		mwexec("/sbin/sysctl net.inet.ip.pfil.outbound=\"ipfw,pf\"");    
81

    
82
		/* stop accounting on all clients */
83
		captiveportal_radius_stop_all();
84

    
85
		/* initialize minicron interval value */
86
		$croninterval = $config['captiveportal']['croninterval'] ? $config['captiveportal']['croninterval'] : 60;
87

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

    
91
		/* remove old information */
92
		unlink_if_exists("{$g['vardb_path']}/captiveportal.nextrule");
93
		unlink_if_exists("{$g['vardb_path']}/captiveportal.db");
94
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
95
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip.db");
96
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius.db");
97

    
98
		/* write portal page */
99
		if ($config['captiveportal']['page']['htmltext'])
100
			$htmltext = base64_decode($config['captiveportal']['page']['htmltext']);
101
		else {
102
			/* example/template page */
103
			$htmltext = <<<EOD
104
<html>
105
<head>
106
<title>{$g['product_name']} captive portal</title>
107
</head>
108
<body>
109
<center>
110
<h2>{$g['product_name']} captive portal</h2>
111
Welcome to the {$g['product_name']} Captive Portal!  This is the default page since a custom page has not been defined.
112
<p>
113
<form method="post" action="\$PORTAL_ACTION\$">
114
<input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$">
115
<table>
116
   <tr><td>Username:</td><td><input name="auth_user" type="text"></td></tr>
117
   <tr><td>Password:</td><td><input name="auth_pass" type="password"></td></tr>
118
   <tr><td>&nbsp;</td></tr>
119
   <tr>
120
     <td colspan="2">
121
	<center><input name="accept" type="submit" value="Continue"></center>
122
     </td>
123
   </tr>
124
</table>
125
</center>
126
</form>
127
</body>
128
</html>
129

    
130

    
131

    
132
EOD;
133
		}
134

    
135
		$fd = @fopen("{$g['varetc_path']}/captiveportal.html", "w");
136
		if ($fd) {
137
			fwrite($fd, $htmltext);
138
			fclose($fd);
139
		}
140

    
141
		/* write error page */
142
		if ($config['captiveportal']['page']['errtext'])
143
			$errtext = base64_decode($config['captiveportal']['page']['errtext']);
144
		else {
145
			/* example page */
146
			$errtext = <<<EOD
147
<html>
148
<head>
149
<title>Authentication error</title>
150
</head>
151
<body>
152
<font color="#cc0000"><h2>Authentication error</h2></font>
153
<b>
154
Username and/or password invalid.
155
<br><br>
156
<a href="javascript:history.back()">Go back</a>
157
</b>
158
</body>
159
</html>
160

    
161
EOD;
162
		}
163

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

    
170
		/* write elements */
171
		captiveportal_write_elements();
172

    
173
		/* load rules */
174
		mwexec("/sbin/ipfw -f delete set 1");
175
		mwexec("/sbin/ipfw -f delete set 2");
176
		mwexec("/sbin/ipfw -f delete set 3");
177

    
178
		/* ipfw cannot accept rules directly on stdin,
179
		   so we have to write them to a temporary file first */
180
		$fd = @fopen("{$g['tmp_path']}/ipfw.cp.rules", "w");
181
		if (!$fd) {
182
			printf("Cannot open ipfw.cp.rules in captiveportal_configure()\n");
183
			return 1;
184
		}
185

    
186
		fwrite($fd, $cprules);
187
		fclose($fd);
188

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

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

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

    
196
		chdir($g['captiveportal_path']);
197

    
198
		if ($config['captiveportal']['maxproc'])
199
			$maxproc = $config['captiveportal']['maxproc'];
200
		else
201
			$maxproc = 16;
202

    
203
		$use_fastcgi = true;
204

    
205
		if(isset($config['captiveportal']['httpslogin'])) {
206
			$cert = base64_decode($config['captiveportal']['certificate']);
207
			$key = base64_decode($config['captiveportal']['private-key']);
208
			/* generate lighttpd configuration */
209
			system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal-SSL.conf",
210
				$cert, $key, "lighty-CaptivePortal-ssl.pid", "8001", "/usr/local/captiveportal/",
211
					"cert-portal.pem", "1", $maxproc, $use_fastcgi, true);
212
		}
213

    
214
		/* generate lighttpd configuration */
215
		system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal.conf",
216
			"", "", "lighty-CaptivePortal.pid", "8000", "/usr/local/captiveportal/",
217
				"cert-portal.pem", "1", $maxproc, $use_fastcgi, true);
218

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

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

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

    
230
		/* generate passthru mac database */
231
		captiveportal_passthrumac_configure();
232
		/* create allowed ip database and insert ipfw rules to make it so */
233
		captiveportal_allowedip_configure();
234

    
235
		/* generate radius server database */
236
		if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
237
				($config['captiveportal']['auth_method'] == "radius"))) {
238
			$radiusip = $config['captiveportal']['radiusip'];
239
			$radiusip2 = ($config['captiveportal']['radiusip2']) ? $config['captiveportal']['radiusip2'] : null;
240

    
241
			if ($config['captiveportal']['radiusport'])
242
				$radiusport = $config['captiveportal']['radiusport'];
243
			else
244
				$radiusport = 1812;
245

    
246
			if ($config['captiveportal']['radiusacctport'])
247
				$radiusacctport = $config['captiveportal']['radiusacctport'];
248
			else
249
				$radiusacctport = 1813;
250

    
251
			if ($config['captiveportal']['radiusport2'])
252
				$radiusport2 = $config['captiveportal']['radiusport2'];
253
			else
254
				$radiusport2 = 1812;
255

    
256
			$radiuskey = $config['captiveportal']['radiuskey'];
257
			$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
258

    
259
			$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db", "w");
260
			if (!$fd) {
261
				printf("Error: cannot open radius DB file in captiveportal_configure().\n");
262
				return 1;
263
			} else if (isset($radiusip2, $radiuskey2)) {
264
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . "\n"
265
					 . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2);
266
			} else {
267
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey);
268
			}
269
			fclose($fd);
270
		}
271

    
272
		if ($g['booting'])
273
			echo "done\n";
274

    
275
	} else {
276
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
277
		killbypid("{$g['varrun_path']}/minicron.pid");
278

    
279
		captiveportal_radius_stop_all();
280

    
281
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
282

    
283
		if (!isset($config['shaper']['enable'])) {
284
			/* unload ipfw */
285
			$installed_time_based_rules = false;
286
			if($config['schedules']) {
287
				foreach($config['schedules']['schedule'] as $sched) {
288
					$installed_time_based_rules = true;
289
				}
290
			}
291
			if($installed_time_based_rules == false)
292
				mwexec("/sbin/kldunload ipfw");
293
		} else {
294
			/* shaper is on - just remove our rules */
295
			mwexec("/sbin/ipfw -f delete set 1");
296
			mwexec("/sbin/ipfw -f delete set 2");
297
			mwexec("/sbin/ipfw -f delete set 3");
298
		}
299
	}
300

    
301
    captiveportal_unlock();
302
	
303
	return 0;
304
}
305

    
306
function captiveportal_rules_generate() {
307
	global $config, $g;
308

    
309
	$cpifn = $config['captiveportal']['interface'];
310
	$cpif = $config['interfaces'][$cpifn]['if'];
311
	$cpip = $config['interfaces'][$cpifn]['ipaddr'];
312
	$lanip = $config['interfaces']['lan']['ipaddr'];
313
	
314
	/* note: the captive portal daemon inserts all pass rules for authenticated
315
	   clients as skipto 50000 rules to make traffic shaping work */
316

    
317
	$cprules =  "add 500 set 1 allow pfsync from any to any\n";
318
	$cprules .= "add 500 set 1 allow carp from any to any\n";
319

    
320
	/*   allow nat redirects to work  see
321
	     http://cvstrac.pfsense.com/tktview?tn=651
322
	 */
323
	
324
	$captive_portal_interface = strtoupper($cpifn);
325

    
326
        /* if list */
327
        $iflist = get_configured_interface_list();
328

    
329
	foreach ($iflist as $ifent => $ifname) {
330
		if($captive_portal_interface == strtoupper($ifname))
331
			continue;
332
		$int = convert_friendly_interface_to_real_interface_name($ifname);
333
		$cprules .= "add 30 set 1 skipto 50000 all from any to any in via {$int} keep-state\n";
334
	}
335

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

    
343
EOD;
344
	}
345

    
346
	$cprules .= <<<EOD
347
# skip to traffic shaper if not on captive portal interface
348
add 1000 set 1 skipto 50000 all from any to any not layer2 not via $cpif
349
# pass all layer2 traffic on other interfaces
350
add 1001 set 1 pass layer2 not via $cpif
351

    
352
# layer 2: pass ARP
353
add 1100 set 1 pass layer2 mac-type arp
354
# pfsense requires for WPA
355
add 1100 set 1 pass layer2 mac-type 0x888e
356
add 1100 set 1 pass layer2 mac-type 0x88c7
357

    
358
# PPP Over Ethernet Discovery Stage
359
add 1100 set 1 pass layer2 mac-type 0x8863
360
# PPP Over Ethernet Session Stage
361
add 1100 set 1 pass layer2 mac-type 0x8864
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 20000 layer2
367

    
368
# allow access to our DHCP server (which needs to be able to ping clients as well)
369
add 1200 set 1 pass udp from any 68 to 255.255.255.255 67 in
370
add 1201 set 1 pass udp from any 68 to $cpip 67 in
371
add 1202 set 1 pass udp from $cpip 67 to any 68 out
372
add 1203 set 1 pass icmp from $cpip to any out icmptype 8
373
add 1204 set 1 pass icmp from any to $cpip in icmptype 0
374

    
375
# allow access to our DNS forwarder
376
add 1300 set 1 pass udp from any to $cpip 53 in
377
add 1301 set 1 pass udp from $cpip 53 to any out
378

    
379
# allow access to our DNS forwarder if it incorrectly resolves the hostname to $lanip
380
add 1300 set 1 pass udp from any to $lanip 53 in
381
add 1301 set 1 pass udp from $lanip 53 to any out
382

    
383
# allow access to our web server
384
add 1302 set 1 pass tcp from any to $cpip 8000 in
385
add 1303 set 1 pass tcp from $cpip 8000 to any out
386

    
387
# allow access to lan web server incase the dns name resolves incorrectly to $lanip
388
add 1302 set 1 pass tcp from any to $lanip 8000 in
389
add 1303 set 1 pass tcp from $lanip 8000 to any out
390

    
391
EOD;
392

    
393
	if (isset($config['captiveportal']['httpslogin'])) {
394
		$cprules .= <<<EOD
395
add 1304 set 1 pass tcp from any to $cpip 8001 in
396
add 1305 set 1 pass tcp from $cpip 8001 to any out
397
add 1306 set 1 pass tcp from any to $lanip 8001 in
398
add 1307 set 1 pass tcp from $lanip 8001 to any out
399

    
400
EOD;
401
	}
402

    
403
        $cprules .= <<<EOD
404
#PPPoE Discovery Stage
405
add 1100 set 1 pass layer2 mac-type 0x8863
406
#PPPoE Session Stage
407
add 1100 set 1 pass layer2 mac-type 0x8864
408

    
409
EOD;
410

    
411
        $cprules .= <<<EOD
412
# Allow WPA
413
add 1100 set 1 pass layer2 mac-type 0x888e
414

    
415
EOD;
416

    
417

    
418
    $cprules .= <<<EOD
419

    
420
# ... 10000-19899: rules per authenticated client go here...
421

    
422
# redirect non-authenticated clients to captive portal
423
add 19902 set 1 fwd 127.0.0.1,8000 tcp from any to any 80 in
424
# let the responses from the captive portal web server back out
425
add 19903 set 1 pass tcp from any 80 to any out
426
# block everything else
427
add 19904 set 1 deny all from any to any
428

    
429
# ... 20000-29899: layer2 block rules per authenticated client go here...
430

    
431
# pass everything else on layer2
432
add 29900 set 1 pass all from any to any layer2
433

    
434
EOD;
435

    
436
    return $cprules;
437
}
438

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

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

    
446
    global $g, $config;
447

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

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

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

    
462
    captiveportal_lock();
463

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

    
467
    $radiusservers = captiveportal_get_radius_servers();
468

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

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

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

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

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

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

    
523
        if ($timedout) {
524
            captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
525
            captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
526
            unset($cpdb[$i]);
527
        }
528

    
529
        /* do periodic RADIUS reauthentication? */
530
        if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
531
            ($radiusservers !== false)) {
532

    
533
            if (isset($config['captiveportal']['radacct_enable'])) {
534
                if ($config['captiveportal']['reauthenticateacct'] == "stopstart") {
535
                    /* stop and restart accounting */
536
                    RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
537
                                           $cpdb[$i][4], // username
538
                                           $cpdb[$i][5], // sessionid
539
                                           $cpdb[$i][0], // start time
540
                                           $radiusservers[0]['ipaddr'],
541
                                           $radiusservers[0]['acctport'],
542
                                           $radiusservers[0]['key'],
543
                                           $cpdb[$i][2], // clientip
544
                                           $cpdb[$i][3], // clientmac
545
                                           10); // NAS Request
546
                    exec("/sbin/ipfw zero {$cpdb[$i][1]}");
547
                    RADIUS_ACCOUNTING_START($cpdb[$i][1], // ruleno
548
                                            $cpdb[$i][4], // username
549
                                            $cpdb[$i][5], // sessionid
550
                                            $radiusservers[0]['ipaddr'],
551
                                            $radiusservers[0]['acctport'],
552
                                            $radiusservers[0]['key'],
553
                                            $cpdb[$i][2], // clientip
554
                                            $cpdb[$i][3]); // clientmac
555
                } else if ($config['captiveportal']['reauthenticateacct'] == "interimupdate") {
556
                    RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
557
                                           $cpdb[$i][4], // username
558
                                           $cpdb[$i][5], // sessionid
559
                                           $cpdb[$i][0], // start time
560
                                           $radiusservers[0]['ipaddr'],
561
                                           $radiusservers[0]['acctport'],
562
                                           $radiusservers[0]['key'],
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
                unset($cpdb[$i]);
582
            }
583
        }
584
    }
585

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

    
589
    captiveportal_unlock();
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']) && isset($radiusservers[0])) {
601
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
602
							   $dbent[4], // username
603
							   $dbent[5], // sessionid
604
							   $dbent[0], // start time
605
							   $radiusservers[0]['ipaddr'],
606
							   $radiusservers[0]['acctport'],
607
							   $radiusservers[0]['key'],
608
							   $dbent[2], // clientip
609
							   $dbent[3], // clientmac
610
							   $term_cause, // Acct-Terminate-Cause
611
							   false,
612
							   $stop_time);
613
	}
614

    
615
	mwexec("/sbin/ipfw delete " . $dbent[1] . " " . ($dbent[1]+10000));
616

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

    
626
	/* pfSense: ensure all pf states are killed (pfSense) */
627
	mwexec("pfctl -k {$dbent[2]}");
628
	mwexec("pfctl -K {$dbent[2]}");
629

    
630
}
631

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

    
635
	global $g, $config;
636

    
637
	captiveportal_lock();
638

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

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

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

    
656
	captiveportal_unlock();
657
}
658

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

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

    
666
	captiveportal_lock();
667
	$cpdb = captiveportal_read_db();
668

    
669
	$radiusservers = captiveportal_get_radius_servers();
670

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

    
688
function captiveportal_passthrumac_configure() {
689
	global $config, $g;
690

    
691
	captiveportal_lock();
692

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

    
696
	if (is_array($config['captiveportal']['passthrumac'])) {
697

    
698
		$fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db", "w");
699
		if (!$fd) {
700
			printf("Error: cannot open passthru mac DB file in captiveportal_passthrumac_configure().\n");
701
			captiveportal_unlock();
702
			return 1;
703
		}
704

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

    
710
		fclose($fd);
711
	}
712

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

    
731
	captiveportal_unlock();
732

    
733
	return 0;
734
}
735

    
736
function captiveportal_allowedip_configure() {
737
	global $config, $g;
738

    
739
	captiveportal_lock();
740

    
741
	/* clear out existing allowed ips, if necessary */
742
	if (file_exists("{$g['vardb_path']}/captiveportal_ip.db")) {
743
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "r");
744
		if ($fd) {
745
			while (!feof($fd)) {
746
				$line = trim(fgets($fd));
747
				if ($line) {
748
					list($ip,$rule) = explode(",",$line);
749
					mwexec("/sbin/ipfw delete $rule");
750
				}
751
			}
752
		}
753
		fclose($fd);
754
		unlink("{$g['vardb_path']}/captiveportal_ip.db");
755
	}
756

    
757
	/* get next ipfw rule number */
758
	if (file_exists("{$g['vardb_path']}/captiveportal.nextrule"))
759
		$ruleno = trim(file_get_contents("{$g['vardb_path']}/captiveportal.nextrule"));
760
	if (!$ruleno)
761
		$ruleno = 10000;	/* first rule number */
762

    
763
	if (is_array($config['captiveportal']['allowedip'])) {
764

    
765
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "w");
766
		if (!$fd) {
767
			printf("Error: cannot open allowed ip DB file in captiveportal_allowedip_configure().\n");
768
			captiveportal_unlock();
769
			return 1;
770
		}
771

    
772
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
773
            /* get next ipfw rule number */
774
            $ruleno = captiveportal_get_next_ipfw_ruleno();
775

    
776
            /* if the pool is empty, return apprioriate message and fail */
777
            if (is_null($ruleno)) {
778
                printf("Error: system reached maximum login capacity, no free FW rulenos in captiveportal_allowedip_configure().\n");
779
                fclose($fd);
780
                captiveportal_unlock();
781
                return 1;
782
            }
783

    
784
            /* record allowed ip so it can be recognized and removed later */
785
            fwrite($fd, $ipent['ip'] . "," . $ruleno ."\n");
786

    
787
            /* insert ipfw rule to allow ip thru */
788
            if ($ipent['dir'] == "from") {
789
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any in");
790
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " out");
791
            } else {
792
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " in");
793
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any out");
794
            }
795

    
796
        }
797

    
798
        fclose($fd);
799
    }
800

    
801
    captiveportal_unlock();
802
    return 0;
803
}
804

    
805
/* get last activity timestamp given ipfw rule number */
806
function captiveportal_get_last_activity($ruleno) {
807

    
808
	$ipfwoutput = "";
809

    
810
	exec("/sbin/ipfw -T list {$ruleno} 2>/dev/null", $ipfwoutput);
811

    
812
	/* in */
813
	if ($ipfwoutput[0]) {
814
		$ri = explode(" ", $ipfwoutput[0]);
815
		if ($ri[1])
816
			return $ri[1];
817
	}
818

    
819
	return 0;
820
}
821

    
822
/* read RADIUS servers into array */
823
function captiveportal_get_radius_servers() {
824

    
825
        global $g;
826

    
827
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
828
                $fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db","r");
829
                if ($fd) {
830
                        $radiusservers = array();
831
                        while (!feof($fd)) {
832
                                $line = trim(fgets($fd));
833
                                if ($line) {
834
                                        $radsrv = array();
835
                                        list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
836
                                        $radiusservers[] = $radsrv;
837
                                }
838
                        }
839
                        fclose($fd);
840

    
841
                        return $radiusservers;
842
                }
843
        }
844

    
845
        return false;
846
}
847

    
848
/* lock captive portal information, decide that the lock file is stale after 
849
   10 minutes and EXIT the process to not risk dataloss, issue warning in syslog every 1 minutes */
850
function captiveportal_lock() {
851

    
852
        global $lockfile;
853

    
854
        $n = 1;
855
        while ($n) {
856
                /* open the lock file in append mode to avoid race condition */
857
                if ($fd = @fopen($lockfile, "x")) {
858
                        /* succeeded */
859
                        fclose($fd);
860
						if($n > 10) {
861
						    captiveportal_syslog("LOCKINFO: Waiting for lock for $n seconds/s!");
862
						}
863
                        return;
864
                } else {
865
                        /* file locked, wait and try again */
866
                        sleep(1);
867

    
868
						if(($n % 60) == 0) {
869
						    captiveportal_syslog("LOCKWARNING: waiting for lock for " . $n/60 . " minute/s!");
870
						    if(($n % 600) == 0) {
871
						        captiveportal_syslog("LOCKERROR: waiting for lock for 10 minute/s - EXITING PROCESS!");
872
						        die("Can't get a lock");
873
						    }
874
					    }
875
                }
876
                $n++;
877
        }
878
		/* we never get here */
879
}
880

    
881
/* unlock captive portal information file */
882
function captiveportal_unlock() {
883

    
884
        global $lockfile;
885

    
886
        if (file_exists($lockfile))
887
                unlink($lockfile);
888
}
889

    
890
/* log successful captive portal authentication to syslog */
891
/* part of this code from php.net */
892
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
893
	$message = trim($message);
894
	// Log it
895
	if (!$message)
896
		$message = "$status: $user, $mac, $ip";
897
	else
898
		$message = "$status: $user, $mac, $ip, $message";
899
	captiveportal_syslog($message);
900
	closelog();
901
}
902

    
903
/* log simple messages to syslog */
904
function captiveportal_syslog($message) {
905
	define_syslog_variables();
906
	$message = trim($message);
907
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
908
	// Log it
909
	syslog(LOG_INFO, $message);
910
	closelog();
911
}
912

    
913
function radius($username,$password,$clientip,$clientmac,$type) {
914
    global $g, $config;
915

    
916
    /* Start locking from the beginning of an authentication session */
917
    captiveportal_lock();
918

    
919
    $ruleno = captiveportal_get_next_ipfw_ruleno();
920

    
921
    /* if the pool is empty, return apprioriate message and fail authentication */
922
    if (is_null($ruleno)) {
923
        $auth_list = array();
924
        $auth_list['auth_val'] = 1;
925
        $auth_list['error'] = "System reached maximum login capacity";
926
        captiveportal_unlock();
927
        return $auth_list;
928
    }
929

    
930
    $radiusservers = captiveportal_get_radius_servers();
931

    
932
    $auth_list = RADIUS_AUTHENTICATION($username,
933
                    $password,
934
                    $radiusservers,
935
                    $clientip,
936
                    $clientmac,
937
                    $ruleno);
938

    
939
    if ($auth_list['auth_val'] == 2) {
940
        captiveportal_logportalauth($username,$clientmac,$clientip,$type);
941
        $sessionid = portal_allow($clientip,
942
                    $clientmac,
943
                    $username,
944
                    $password,
945
                    $auth_list,
946
                    $ruleno);
947
    }
948
    else {
949
        captiveportal_unlock();
950
    }
951

    
952
    return $auth_list;
953

    
954
}
955

    
956
/* read captive portal DB into array */
957
function captiveportal_read_db() {
958

    
959
        global $g;
960

    
961
        $cpdb = array();
962
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
963
        if ($fd) {
964
                while (!feof($fd)) {
965
                        $line = trim(fgets($fd));
966
                        if ($line) {
967
                                $cpdb[] = explode(",", $line);
968
                        }
969
                }
970
                fclose($fd);
971
        }
972
        return $cpdb;
973
}
974

    
975
/* write captive portal DB */
976
function captiveportal_write_db($cpdb) {
977
                 
978
        global $g;
979
                
980
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
981
        if ($fd) { 
982
                foreach ($cpdb as $cpent) {
983
                        fwrite($fd, join(",", $cpent) . "\n");
984
                }       
985
                fclose($fd);
986
        }       
987
}
988

    
989
function captiveportal_write_elements() {
990
    global $g, $config;
991
    
992
    /* delete any existing elements */
993
    if (is_dir($g['captiveportal_element_path'])) {
994
        $dh = opendir($g['captiveportal_element_path']);
995
        while (($file = readdir($dh)) !== false) {
996
            if ($file != "." && $file != "..")
997
                unlink($g['captiveportal_element_path'] . "/" . $file);
998
        }
999
        closedir($dh);
1000
    } else {
1001
        mkdir($g['captiveportal_element_path']);
1002
    }
1003
    
1004
	if (is_array($config['captiveportal']['element'])) {
1005
		conf_mount_rw();
1006
		foreach ($config['captiveportal']['element'] as $data) {
1007
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
1008
			if (!$fd) {
1009
				printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
1010
				return 1;
1011
			}
1012
			$decoded = base64_decode($data['content']);
1013
			fwrite($fd,$decoded);
1014
			fclose($fd);
1015
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1016
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1017
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
1018
		}
1019
		conf_mount_ro();
1020
	}
1021
    
1022
    return 0;
1023
}
1024

    
1025
/*
1026
 * This function will calculate the lowest free firewall ruleno
1027
 * within the range specified based on the actual installed rules
1028
 *
1029
 */
1030

    
1031
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 10000, $rulenos_range_max = 9899) {
1032

    
1033
	$fwrules = "";
1034
	$matches = "";
1035
	exec("/sbin/ipfw show", $fwrules);
1036
	foreach ($fwrules as $fwrule) {
1037
		preg_match("/^(\d+)\s+/", $fwrule, $matches);
1038
		$rulenos_used[] = $matches[1];
1039
	}
1040
	$rulenos_used = array_unique($rulenos_used);
1041
	$rulenos_range = count($rulenos_used);
1042
	if ($rulenos_range > $rulenos_range_max) {
1043
		return NULL;
1044
	}
1045
	$rulenos_pool = range($rulenos_start, ($rulenos_start + $rulenos_range));
1046
	$rulenos_free = array_diff($rulenos_pool, $rulenos_used);
1047
	$ruleno = array_shift($rulenos_free);
1048

    
1049
	return $ruleno;
1050
}
1051

    
1052
/**
1053
 * This function will calculate the traffic produced by a client
1054
 * based on its firewall rule
1055
 *
1056
 * Point of view: NAS
1057
 *
1058
 * Input means: from the client
1059
 * Output means: to the client
1060
 *
1061
 */
1062

    
1063
function getVolume($ruleno) {
1064

    
1065
    $volume = array();
1066

    
1067
    // Initialize vars properly, since we don't want NULL vars
1068
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1069

    
1070
    // Ingress
1071
    $ipfw = "";
1072
    $matches = "";
1073
    exec("/sbin/ipfw show {$ruleno}", $ipfw);
1074
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[0], $matches);
1075
    $volume['input_pkts'] = $matches[2];
1076
    $volume['input_bytes'] = $matches[3];
1077

    
1078
    // Flush internal buffer
1079
    unset($matches);
1080

    
1081
    // Outgress
1082
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[1], $matches);
1083
    $volume['output_pkts'] = $matches[2];
1084
    $volume['output_bytes'] = $matches[3];
1085

    
1086
    return $volume;
1087
}
1088

    
1089
/**
1090
 * Get the NAS-Identifier
1091
 *
1092
 * We will use our local hostname to make up the nas_id
1093
 */
1094
function getNasID()
1095
{
1096
    $nasId = "";
1097
    exec("/bin/hostname", $nasId);
1098
    if(!$nasId[0])
1099
        $nasId[0] = "{$g['product_name']}";
1100
    return $nasId[0];
1101
}
1102

    
1103
/**
1104
 * Get the NAS-IP-Address based on the current wan address
1105
 *
1106
 * Use functions in interfaces.inc to find this out
1107
 *
1108
 */
1109

    
1110
function getNasIP()
1111
{
1112
    $nasIp = get_current_wan_address();
1113
    if(!$nasIp)
1114
        $nasIp = "0.0.0.0";
1115
    return $nasIp;
1116
}
1117

    
1118
function portal_mac_fixed($clientmac) {
1119
    global $g ;
1120

    
1121
    /* open captive portal mac db */
1122
    if (file_exists("{$g['vardb_path']}/captiveportal_mac.db")) {
1123
        $fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db","r") ;
1124
        if (!$fd) {
1125
            return FALSE;
1126
        }
1127
        while (!feof($fd)) {
1128
            $mac = trim(fgets($fd)) ;
1129
            if(strcasecmp($clientmac, $mac) == 0) {
1130
                fclose($fd) ;
1131
                return TRUE ;
1132
            }
1133
        }
1134
        fclose($fd) ;
1135
    }
1136
    return FALSE ;
1137
}
1138

    
1139
?>
(5-5/37)