Project

General

Profile

Download (36.1 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
		filter_load_ipfw();
68

    
69
		/* stop accounting on all clients */
70
		captiveportal_radius_stop_all();
71

    
72
		/* initialize minicron interval value */
73
		$croninterval = $config['captiveportal']['croninterval'] ? $config['captiveportal']['croninterval'] : 60;
74

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

    
78
		/* remove old information */
79
		unlink_if_exists("{$g['vardb_path']}/captiveportal.nextrule");
80
		unlink_if_exists("{$g['vardb_path']}/captiveportal.db");
81
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
82
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip.db");
83
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius.db");
84

    
85
		/* write portal page */
86
		if ($config['captiveportal']['page']['htmltext'])
87
			$htmltext = base64_decode($config['captiveportal']['page']['htmltext']);
88
		else {
89
			/* example/template page */
90
			$htmltext = <<<EOD
91
<html>
92
<head>
93
<title>{$g['product_name']} captive portal</title>
94
</head>
95
<body>
96
<center>
97
<h2>{$g['product_name']} captive portal</h2>
98
Welcome to the {$g['product_name']} Captive Portal!  This is the default page since a custom page has not been defined.
99
<p>
100
<form method="post" action="\$PORTAL_ACTION\$">
101
<input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$">
102
<table>
103
   <tr><td>Username:</td><td><input name="auth_user" type="text"></td></tr>
104
   <tr><td>Password:</td><td><input name="auth_pass" type="password"></td></tr>
105
   <tr><td>&nbsp;</td></tr>
106
   <tr>
107
     <td colspan="2">
108
	<center><input name="accept" type="submit" value="Continue"></center>
109
     </td>
110
   </tr>
111
</table>
112
</center>
113
</form>
114
</body>
115
</html>
116

    
117

    
118

    
119
EOD;
120
		}
121

    
122
		$fd = @fopen("{$g['varetc_path']}/captiveportal.html", "w");
123
		if ($fd) {
124
			fwrite($fd, $htmltext);
125
			fclose($fd);
126
		}
127

    
128
		/* write error page */
129
		if ($config['captiveportal']['page']['errtext'])
130
			$errtext = base64_decode($config['captiveportal']['page']['errtext']);
131
		else {
132
			/* example page */
133
			$errtext = <<<EOD
134
<html>
135
<head>
136
<title>Authentication error</title>
137
</head>
138
<body>
139
<font color="#cc0000"><h2>Authentication error</h2></font>
140
<b>
141
Username and/or password invalid.
142
<br><br>
143
<a href="javascript:history.back()">Go back</a>
144
</b>
145
</body>
146
</html>
147

    
148
EOD;
149
		}
150

    
151
		$fd = @fopen("{$g['varetc_path']}/captiveportal-error.html", "w");
152
		if ($fd) {
153
			fwrite($fd, $errtext);
154
			fclose($fd);
155
		}
156

    
157
		/* write elements */
158
		captiveportal_write_elements();
159

    
160
		/* load rules */
161
		mwexec("/sbin/ipfw -f delete set 1");
162
		mwexec("/sbin/ipfw -f delete set 2");
163
		mwexec("/sbin/ipfw -f delete set 3");
164

    
165
		/* ipfw cannot accept rules directly on stdin,
166
		   so we have to write them to a temporary file first */
167
		$fd = @fopen("{$g['tmp_path']}/ipfw.cp.rules", "w");
168
		if (!$fd) {
169
			printf("Cannot open ipfw.cp.rules in captiveportal_configure()\n");
170
			return 1;
171
		}
172

    
173
		fwrite($fd, $cprules);
174
		fclose($fd);
175

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

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

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

    
183
		chdir($g['captiveportal_path']);
184

    
185
		if ($config['captiveportal']['maxproc'])
186
			$maxproc = $config['captiveportal']['maxproc'];
187
		else
188
			$maxproc = 16;
189

    
190
		$use_fastcgi = true;
191

    
192
		if(isset($config['captiveportal']['httpslogin'])) {
193
			$cert = base64_decode($config['captiveportal']['certificate']);
194
			$key = base64_decode($config['captiveportal']['private-key']);
195
			/* generate lighttpd configuration */
196
			system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal-SSL.conf",
197
				$cert, $key, "lighty-CaptivePortal-ssl.pid", "8001", "/usr/local/captiveportal/",
198
					"cert-portal.pem", "1", $maxproc, $use_fastcgi, true);
199
		}
200

    
201
		/* generate lighttpd configuration */
202
		system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal.conf",
203
			"", "", "lighty-CaptivePortal.pid", "8000", "/usr/local/captiveportal/",
204
				"cert-portal.pem", "1", $maxproc, $use_fastcgi, true);
205

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

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

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

    
217
		/* generate passthru mac database */
218
		captiveportal_passthrumac_configure();
219
		/* create allowed ip database and insert ipfw rules to make it so */
220
		captiveportal_allowedip_configure();
221

    
222
		/* generate radius server database */
223
		if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
224
				($config['captiveportal']['auth_method'] == "radius"))) {
225
			$radiusip = $config['captiveportal']['radiusip'];
226
			$radiusip2 = ($config['captiveportal']['radiusip2']) ? $config['captiveportal']['radiusip2'] : null;
227

    
228
			if ($config['captiveportal']['radiusport'])
229
				$radiusport = $config['captiveportal']['radiusport'];
230
			else
231
				$radiusport = 1812;
232

    
233
			if ($config['captiveportal']['radiusacctport'])
234
				$radiusacctport = $config['captiveportal']['radiusacctport'];
235
			else
236
				$radiusacctport = 1813;
237

    
238
			if ($config['captiveportal']['radiusport2'])
239
				$radiusport2 = $config['captiveportal']['radiusport2'];
240
			else
241
				$radiusport2 = 1812;
242

    
243
			$radiuskey = $config['captiveportal']['radiuskey'];
244
			$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
245

    
246
			$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db", "w");
247
			if (!$fd) {
248
				printf("Error: cannot open radius DB file in captiveportal_configure().\n");
249
				return 1;
250
			} else if (isset($radiusip2, $radiuskey2)) {
251
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . "\n"
252
					 . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2);
253
			} else {
254
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey);
255
			}
256
			fclose($fd);
257
		}
258

    
259
		if ($g['booting'])
260
			echo "done\n";
261

    
262
	} else {
263
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
264
		killbypid("{$g['varrun_path']}/minicron.pid");
265

    
266
		captiveportal_radius_stop_all();
267

    
268
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
269

    
270
		if (!isset($config['shaper']['enable'])) {
271
			/* unload ipfw */
272
			$installed_time_based_rules = false;
273
			if($config['schedules']) {
274
				foreach($config['schedules']['schedule'] as $sched) {
275
					$installed_time_based_rules = true;
276
				}
277
			}
278
			if($installed_time_based_rules == false)
279
				mwexec("/sbin/kldunload ipfw");
280
		} else {
281
			/* shaper is on - just remove our rules */
282
			mwexec("/sbin/ipfw -f delete set 1");
283
			mwexec("/sbin/ipfw -f delete set 2");
284
			mwexec("/sbin/ipfw -f delete set 3");
285
		}
286
	}
287

    
288
    captiveportal_unlock();
289
	
290
	return 0;
291
}
292

    
293
function captiveportal_rules_generate() {
294
	global $config, $g;
295

    
296
	$cpifn = $config['captiveportal']['interface'];
297
	$cpif = $config['interfaces'][$cpifn]['if'];
298
	$cpip = $config['interfaces'][$cpifn]['ipaddr'];
299
	$lanip = $config['interfaces']['lan']['ipaddr'];
300
	
301
	/* note: the captive portal daemon inserts all pass rules for authenticated
302
	   clients as skipto 50000 rules to make traffic shaping work */
303

    
304
	$cprules =  "add 500 set 1 allow pfsync from any to any\n";
305
	$cprules .= "add 500 set 1 allow carp from any to any\n";
306

    
307
	/*   allow nat redirects to work  see
308
	     http://cvstrac.pfsense.com/tktview?tn=651
309
	 */
310
	
311
	$captive_portal_interface = strtoupper($cpifn);
312

    
313
        /* if list */
314
        $iflist = get_configured_interface_list();
315

    
316
	foreach ($iflist as $ifent => $ifname) {
317
		if($captive_portal_interface == strtoupper($ifname))
318
			continue;
319
		$int = convert_friendly_interface_to_real_interface_name($ifname);
320
		$cprules .= "add 30 set 1 skipto 50000 all from any to any in via {$int} keep-state\n";
321
	}
322

    
323
	/* captive portal on LAN interface? */
324
	if ($cpifn == "lan") {
325
		/* add anti-lockout rules */
326
		$cprules .= <<<EOD
327
add 500 set 1 pass all from $cpip to any out via $cpif
328
add 501 set 1 pass all from any to $cpip in via $cpif
329

    
330
EOD;
331
	}
332

    
333
	$cprules .= <<<EOD
334
# skip to traffic shaper if not on captive portal interface
335
add 1000 set 1 skipto 50000 all from any to any not layer2 not via $cpif
336
# pass all layer2 traffic on other interfaces
337
add 1001 set 1 pass layer2 not via $cpif
338

    
339
# layer 2: pass ARP
340
add 1100 set 1 pass layer2 mac-type arp
341
# pfsense requires for WPA
342
add 1100 set 1 pass layer2 mac-type 0x888e
343
add 1100 set 1 pass layer2 mac-type 0x88c7
344

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

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

    
355
# allow access to our DHCP server (which needs to be able to ping clients as well)
356
add 1200 set 1 pass udp from any 68 to 255.255.255.255 67 in
357
add 1201 set 1 pass udp from any 68 to $cpip 67 in
358
add 1202 set 1 pass udp from $cpip 67 to any 68 out
359
add 1203 set 1 pass icmp from $cpip to any out icmptype 8
360
add 1204 set 1 pass icmp from any to $cpip in icmptype 0
361

    
362
# allow access to our DNS forwarder
363
add 1300 set 1 pass udp from any to $cpip 53 in
364
add 1301 set 1 pass udp from $cpip 53 to any out
365

    
366
# allow access to our DNS forwarder if it incorrectly resolves the hostname to $lanip
367
add 1300 set 1 pass udp from any to $lanip 53 in
368
add 1301 set 1 pass udp from $lanip 53 to any out
369

    
370
# allow access to our web server
371
add 1302 set 1 pass tcp from any to $cpip 8000 in
372
add 1303 set 1 pass tcp from $cpip 8000 to any out
373

    
374
# allow access to lan web server incase the dns name resolves incorrectly to $lanip
375
add 1302 set 1 pass tcp from any to $lanip 8000 in
376
add 1303 set 1 pass tcp from $lanip 8000 to any out
377

    
378
EOD;
379

    
380
	if (isset($config['captiveportal']['httpslogin'])) {
381
		$cprules .= <<<EOD
382
add 1304 set 1 pass tcp from any to $cpip 8001 in
383
add 1305 set 1 pass tcp from $cpip 8001 to any out
384
add 1306 set 1 pass tcp from any to $lanip 8001 in
385
add 1307 set 1 pass tcp from $lanip 8001 to any out
386

    
387
EOD;
388
	}
389

    
390
        $cprules .= <<<EOD
391
#PPPoE Discovery Stage
392
add 1100 set 1 pass layer2 mac-type 0x8863
393
#PPPoE Session Stage
394
add 1100 set 1 pass layer2 mac-type 0x8864
395

    
396
EOD;
397

    
398
        $cprules .= <<<EOD
399
# Allow WPA
400
add 1100 set 1 pass layer2 mac-type 0x888e
401

    
402
EOD;
403

    
404

    
405
    $cprules .= <<<EOD
406

    
407
# ... 10000-19899: rules per authenticated client go here...
408

    
409
# redirect non-authenticated clients to captive portal
410
add 19902 set 1 fwd 127.0.0.1,8000 tcp from any to any 80 in
411
# let the responses from the captive portal web server back out
412
add 19903 set 1 pass tcp from any 80 to any out
413
# block everything else
414
add 19904 set 1 deny all from any to any
415

    
416
# ... 20000-29899: layer2 block rules per authenticated client go here...
417

    
418
# pass everything else on layer2
419
add 29900 set 1 pass all from any to any layer2
420

    
421
EOD;
422

    
423
    return $cprules;
424
}
425

    
426
/* remove clients that have been around for longer than the specified amount of time */
427
/* db file structure:
428
timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time */
429

    
430
/* (password is in Base64 and only saved when reauthentication is enabled) */
431
function captiveportal_prune_old() {
432

    
433
    global $g, $config;
434

    
435
    /* check for expired entries */
436
    if ($config['captiveportal']['timeout'])
437
        $timeout = $config['captiveportal']['timeout'] * 60;
438
    else
439
        $timeout = 0;
440

    
441
    if ($config['captiveportal']['idletimeout'])
442
        $idletimeout = $config['captiveportal']['idletimeout'] * 60;
443
    else
444
        $idletimeout = 0;
445

    
446
    if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && !isset($config['captiveportal']['radiussession_timeout']))
447
        return;
448

    
449
    captiveportal_lock();
450

    
451
    /* read database */
452
    $cpdb = captiveportal_read_db();
453

    
454
    $radiusservers = captiveportal_get_radius_servers();
455

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

    
463
        $timedout = false;
464
        $term_cause = 1;
465

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

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

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

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

    
510
        if ($timedout) {
511
            captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
512
            captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
513
            unset($cpdb[$i]);
514
        }
515

    
516
        /* do periodic RADIUS reauthentication? */
517
        if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
518
            ($radiusservers !== false)) {
519

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

    
557
            /* check this user against RADIUS again */
558
            $auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
559
                                          base64_decode($cpdb[$i][6]), // password
560
                                            $radiusservers,
561
                                          $cpdb[$i][2], // clientip
562
                                          $cpdb[$i][3], // clientmac
563
                                          $cpdb[$i][1]); // ruleno
564

    
565
            if ($auth_list['auth_val'] == 3) {
566
                captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
567
                captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
568
                unset($cpdb[$i]);
569
            }
570
        }
571
    }
572

    
573
    /* write database */
574
    captiveportal_write_db($cpdb);
575

    
576
    captiveportal_unlock();
577
}
578

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

    
582
	global $g, $config;
583

    
584
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
585

    
586
	/* this client needs to be deleted - remove ipfw rules */
587
	if (isset($config['captiveportal']['radacct_enable']) && isset($radiusservers[0])) {
588
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
589
							   $dbent[4], // username
590
							   $dbent[5], // sessionid
591
							   $dbent[0], // start time
592
							   $radiusservers[0]['ipaddr'],
593
							   $radiusservers[0]['acctport'],
594
							   $radiusservers[0]['key'],
595
							   $dbent[2], // clientip
596
							   $dbent[3], // clientmac
597
							   $term_cause, // Acct-Terminate-Cause
598
							   false,
599
							   $stop_time);
600
	}
601

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

    
604
    /* We need to delete +40500 and +45500 as well...
605
     * these are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
606
     * We could get an error if the pipe doesn't exist but everything should still be fine
607
     */
608
    if (isset($config['captiveportal']['peruserbw'])) {
609
        mwexec("/sbin/ipfw pipe " . ($dbent[1]+40500) . " delete");
610
        mwexec("/sbin/ipfw pipe " . ($dbent[1]+45500) . " delete");
611
    }
612

    
613
	/* pfSense: ensure all pf states are killed (pfSense) */
614
	mwexec("pfctl -k {$dbent[2]}");
615
	mwexec("pfctl -K {$dbent[2]}");
616

    
617
}
618

    
619
/* remove a single client by ipfw rule number */
620
function captiveportal_disconnect_client($id,$term_cause = 1) {
621

    
622
	global $g, $config;
623

    
624
	captiveportal_lock();
625

    
626
	/* read database */
627
	$cpdb = captiveportal_read_db();
628
	$radiusservers = captiveportal_get_radius_servers();
629

    
630
	/* find entry */
631
	for ($i = 0; $i < count($cpdb); $i++) {
632
		if ($cpdb[$i][1] == $id) {
633
			captiveportal_disconnect($cpdb[$i], $radiusservers, $term_cause);
634
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "DISCONNECT");
635
			unset($cpdb[$i]);
636
			break;
637
		}
638
	}
639

    
640
	/* write database */
641
	captiveportal_write_db($cpdb);
642

    
643
	captiveportal_unlock();
644
}
645

    
646
/* send RADIUS acct stop for all current clients */
647
function captiveportal_radius_stop_all() {
648
	global $g, $config;
649

    
650
	if (!isset($config['captiveportal']['radacct_enable']))
651
		return;
652

    
653
	captiveportal_lock();
654
	$cpdb = captiveportal_read_db();
655

    
656
	$radiusservers = captiveportal_get_radius_servers();
657

    
658
	if (isset($radiusservers[0])) {
659
		for ($i = 0; $i < count($cpdb); $i++) {
660
			RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
661
								   $cpdb[$i][4], // username
662
								   $cpdb[$i][5], // sessionid
663
								   $cpdb[$i][0], // start time
664
								   $radiusservers[0]['ipaddr'],
665
								   $radiusservers[0]['acctport'],
666
								   $radiusservers[0]['key'],
667
								   $cpdb[$i][2], // clientip
668
								   $cpdb[$i][3], // clientmac
669
								   7); // Admin Reboot
670
		}
671
	}
672
	captiveportal_unlock();
673
}
674

    
675
function captiveportal_passthrumac_configure() {
676
	global $config, $g;
677

    
678
	captiveportal_lock();
679

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

    
683
	if (is_array($config['captiveportal']['passthrumac'])) {
684

    
685
		$fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db", "w");
686
		if (!$fd) {
687
			printf("Error: cannot open passthru mac DB file in captiveportal_passthrumac_configure().\n");
688
			captiveportal_unlock();
689
			return 1;
690
		}
691

    
692
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
693
			/* record passthru mac so it can be recognized and let thru */
694
			fwrite($fd, $macent['mac'] . "\n");
695
		}
696

    
697
		fclose($fd);
698
	}
699

    
700
	/*    pfSense:
701
	 * 	  pass through mac entries should always exist.  the reason
702
	 *    for this is because we do not have native mac address filtering
703
	 *    mechanisms.  this allows us to filter by mac address easily
704
	 *    and get around this limitation.   I consider this a bug in
705
	 *    m0n0wall and pfSense as m0n0wall does not have native mac
706
	 *    filtering mechanisms as well. -Scott Ullrich
707
	 */
708
	if (is_array($config['captiveportal']['passthrumac'])) {
709
		mwexec("/sbin/ipfw delete 50");
710
		foreach($config['captiveportal']['passthrumac'] as $ptm) {
711
			/* create the pass through mac entry */
712
			//system("echo /sbin/ipfw add 50 skipto 65535 ip from any to any MAC {$ptm['mac']} any > /tmp/cp");
713
			mwexec("/sbin/ipfw add 50 skipto 29900 ip from any to any MAC {$ptm['mac']} any keep-state");
714
			mwexec("/sbin/ipfw add 50 skipto 29900 ip from any to any MAC any {$ptm['mac']} keep-state");
715
		}
716
	}
717

    
718
	captiveportal_unlock();
719

    
720
	return 0;
721
}
722

    
723
function captiveportal_allowedip_configure() {
724
	global $config, $g;
725

    
726
	captiveportal_lock();
727

    
728
	/* clear out existing allowed ips, if necessary */
729
	if (file_exists("{$g['vardb_path']}/captiveportal_ip.db")) {
730
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "r");
731
		if ($fd) {
732
			while (!feof($fd)) {
733
				$line = trim(fgets($fd));
734
				if ($line) {
735
					list($ip,$rule) = explode(",",$line);
736
					mwexec("/sbin/ipfw delete $rule");
737
				}
738
			}
739
		}
740
		fclose($fd);
741
		unlink("{$g['vardb_path']}/captiveportal_ip.db");
742
	}
743

    
744
	/* get next ipfw rule number */
745
	if (file_exists("{$g['vardb_path']}/captiveportal.nextrule"))
746
		$ruleno = trim(file_get_contents("{$g['vardb_path']}/captiveportal.nextrule"));
747
	if (!$ruleno)
748
		$ruleno = 10000;	/* first rule number */
749

    
750
	if (is_array($config['captiveportal']['allowedip'])) {
751

    
752
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "w");
753
		if (!$fd) {
754
			printf("Error: cannot open allowed ip DB file in captiveportal_allowedip_configure().\n");
755
			captiveportal_unlock();
756
			return 1;
757
		}
758

    
759
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
760
            /* get next ipfw rule number */
761
            $ruleno = captiveportal_get_next_ipfw_ruleno();
762

    
763
            /* if the pool is empty, return apprioriate message and fail */
764
            if (is_null($ruleno)) {
765
                printf("Error: system reached maximum login capacity, no free FW rulenos in captiveportal_allowedip_configure().\n");
766
                fclose($fd);
767
                captiveportal_unlock();
768
                return 1;
769
            }
770

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

    
774
            /* insert ipfw rule to allow ip thru */
775
            if ($ipent['dir'] == "from") {
776
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any in");
777
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " out");
778
            } else {
779
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " in");
780
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any out");
781
            }
782

    
783
        }
784

    
785
        fclose($fd);
786
    }
787

    
788
    captiveportal_unlock();
789
    return 0;
790
}
791

    
792
/* get last activity timestamp given ipfw rule number */
793
function captiveportal_get_last_activity($ruleno) {
794

    
795
	$ipfwoutput = "";
796

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

    
799
	/* in */
800
	if ($ipfwoutput[0]) {
801
		$ri = explode(" ", $ipfwoutput[0]);
802
		if ($ri[1])
803
			return $ri[1];
804
	}
805

    
806
	return 0;
807
}
808

    
809
/* read RADIUS servers into array */
810
function captiveportal_get_radius_servers() {
811

    
812
        global $g;
813

    
814
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
815
                $fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db","r");
816
                if ($fd) {
817
                        $radiusservers = array();
818
                        while (!feof($fd)) {
819
                                $line = trim(fgets($fd));
820
                                if ($line) {
821
                                        $radsrv = array();
822
                                        list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
823
                                        $radiusservers[] = $radsrv;
824
                                }
825
                        }
826
                        fclose($fd);
827

    
828
                        return $radiusservers;
829
                }
830
        }
831

    
832
        return false;
833
}
834

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

    
839
        global $lockfile;
840

    
841
        $n = 1;
842
        while ($n) {
843
                /* open the lock file in append mode to avoid race condition */
844
                if ($fd = @fopen($lockfile, "x")) {
845
                        /* succeeded */
846
                        fclose($fd);
847
						if($n > 10) {
848
						    captiveportal_syslog("LOCKINFO: Waiting for lock for $n seconds/s!");
849
						}
850
                        return;
851
                } else {
852
                        /* file locked, wait and try again */
853
                        sleep(1);
854

    
855
						if(($n % 60) == 0) {
856
						    captiveportal_syslog("LOCKWARNING: waiting for lock for " . $n/60 . " minute/s!");
857
						    if(($n % 600) == 0) {
858
						        captiveportal_syslog("LOCKERROR: waiting for lock for 10 minute/s - EXITING PROCESS!");
859
						        die("Can't get a lock");
860
						    }
861
					    }
862
                }
863
                $n++;
864
        }
865
		/* we never get here */
866
}
867

    
868
/* unlock captive portal information file */
869
function captiveportal_unlock() {
870

    
871
        global $lockfile;
872

    
873
        if (file_exists($lockfile))
874
                unlink($lockfile);
875
}
876

    
877
/* log successful captive portal authentication to syslog */
878
/* part of this code from php.net */
879
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
880
	$message = trim($message);
881
	// Log it
882
	if (!$message)
883
		$message = "$status: $user, $mac, $ip";
884
	else
885
		$message = "$status: $user, $mac, $ip, $message";
886
	captiveportal_syslog($message);
887
	closelog();
888
}
889

    
890
/* log simple messages to syslog */
891
function captiveportal_syslog($message) {
892
	define_syslog_variables();
893
	$message = trim($message);
894
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
895
	// Log it
896
	syslog(LOG_INFO, $message);
897
	closelog();
898
}
899

    
900
function radius($username,$password,$clientip,$clientmac,$type) {
901
    global $g, $config;
902

    
903
    /* Start locking from the beginning of an authentication session */
904
    captiveportal_lock();
905

    
906
    $ruleno = captiveportal_get_next_ipfw_ruleno();
907

    
908
    /* if the pool is empty, return apprioriate message and fail authentication */
909
    if (is_null($ruleno)) {
910
        $auth_list = array();
911
        $auth_list['auth_val'] = 1;
912
        $auth_list['error'] = "System reached maximum login capacity";
913
        captiveportal_unlock();
914
        return $auth_list;
915
    }
916

    
917
    $radiusservers = captiveportal_get_radius_servers();
918

    
919
    $auth_list = RADIUS_AUTHENTICATION($username,
920
                    $password,
921
                    $radiusservers,
922
                    $clientip,
923
                    $clientmac,
924
                    $ruleno);
925

    
926
    if ($auth_list['auth_val'] == 2) {
927
        captiveportal_logportalauth($username,$clientmac,$clientip,$type);
928
        $sessionid = portal_allow($clientip,
929
                    $clientmac,
930
                    $username,
931
                    $password,
932
                    $auth_list,
933
                    $ruleno);
934
    }
935
    else {
936
        captiveportal_unlock();
937
    }
938

    
939
    return $auth_list;
940

    
941
}
942

    
943
/* read captive portal DB into array */
944
function captiveportal_read_db() {
945

    
946
        global $g;
947

    
948
        $cpdb = array();
949
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
950
        if ($fd) {
951
                while (!feof($fd)) {
952
                        $line = trim(fgets($fd));
953
                        if ($line) {
954
                                $cpdb[] = explode(",", $line);
955
                        }
956
                }
957
                fclose($fd);
958
        }
959
        return $cpdb;
960
}
961

    
962
/* write captive portal DB */
963
function captiveportal_write_db($cpdb) {
964
                 
965
        global $g;
966
                
967
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
968
        if ($fd) { 
969
                foreach ($cpdb as $cpent) {
970
                        fwrite($fd, join(",", $cpent) . "\n");
971
                }       
972
                fclose($fd);
973
        }       
974
}
975

    
976
function captiveportal_write_elements() {
977
    global $g, $config;
978
    
979
    /* delete any existing elements */
980
    if (is_dir($g['captiveportal_element_path'])) {
981
        $dh = opendir($g['captiveportal_element_path']);
982
        while (($file = readdir($dh)) !== false) {
983
            if ($file != "." && $file != "..")
984
                unlink($g['captiveportal_element_path'] . "/" . $file);
985
        }
986
        closedir($dh);
987
    } else {
988
        mkdir($g['captiveportal_element_path']);
989
    }
990
    
991
	if (is_array($config['captiveportal']['element'])) {
992
		conf_mount_rw();
993
		foreach ($config['captiveportal']['element'] as $data) {
994
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
995
			if (!$fd) {
996
				printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
997
				return 1;
998
			}
999
			$decoded = base64_decode($data['content']);
1000
			fwrite($fd,$decoded);
1001
			fclose($fd);
1002
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1003
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1004
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
1005
		}
1006
		conf_mount_ro();
1007
	}
1008
    
1009
    return 0;
1010
}
1011

    
1012
/*
1013
 * This function will calculate the lowest free firewall ruleno
1014
 * within the range specified based on the actual installed rules
1015
 *
1016
 */
1017

    
1018
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 10000, $rulenos_range_max = 9899) {
1019

    
1020
	$fwrules = "";
1021
	$matches = "";
1022
	exec("/sbin/ipfw show", $fwrules);
1023
	foreach ($fwrules as $fwrule) {
1024
		preg_match("/^(\d+)\s+/", $fwrule, $matches);
1025
		$rulenos_used[] = $matches[1];
1026
	}
1027
	$rulenos_used = array_unique($rulenos_used);
1028
	$rulenos_range = count($rulenos_used);
1029
	if ($rulenos_range > $rulenos_range_max) {
1030
		return NULL;
1031
	}
1032
	$rulenos_pool = range($rulenos_start, ($rulenos_start + $rulenos_range));
1033
	$rulenos_free = array_diff($rulenos_pool, $rulenos_used);
1034
	$ruleno = array_shift($rulenos_free);
1035

    
1036
	return $ruleno;
1037
}
1038

    
1039
/**
1040
 * This function will calculate the traffic produced by a client
1041
 * based on its firewall rule
1042
 *
1043
 * Point of view: NAS
1044
 *
1045
 * Input means: from the client
1046
 * Output means: to the client
1047
 *
1048
 */
1049

    
1050
function getVolume($ruleno) {
1051

    
1052
    $volume = array();
1053

    
1054
    // Initialize vars properly, since we don't want NULL vars
1055
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1056

    
1057
    // Ingress
1058
    $ipfw = "";
1059
    $matches = "";
1060
    exec("/sbin/ipfw show {$ruleno}", $ipfw);
1061
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[0], $matches);
1062
    $volume['input_pkts'] = $matches[2];
1063
    $volume['input_bytes'] = $matches[3];
1064

    
1065
    // Flush internal buffer
1066
    unset($matches);
1067

    
1068
    // Outgress
1069
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[1], $matches);
1070
    $volume['output_pkts'] = $matches[2];
1071
    $volume['output_bytes'] = $matches[3];
1072

    
1073
    return $volume;
1074
}
1075

    
1076
/**
1077
 * Get the NAS-Identifier
1078
 *
1079
 * We will use our local hostname to make up the nas_id
1080
 */
1081
function getNasID()
1082
{
1083
    $nasId = "";
1084
    exec("/bin/hostname", $nasId);
1085
    if(!$nasId[0])
1086
        $nasId[0] = "{$g['product_name']}";
1087
    return $nasId[0];
1088
}
1089

    
1090
/**
1091
 * Get the NAS-IP-Address based on the current wan address
1092
 *
1093
 * Use functions in interfaces.inc to find this out
1094
 *
1095
 */
1096

    
1097
function getNasIP()
1098
{
1099
    $nasIp = get_interface_ip();
1100
    if(!$nasIp)
1101
        $nasIp = "0.0.0.0";
1102
    return $nasIp;
1103
}
1104

    
1105
function portal_mac_fixed($clientmac) {
1106
    global $g ;
1107

    
1108
    /* open captive portal mac db */
1109
    if (file_exists("{$g['vardb_path']}/captiveportal_mac.db")) {
1110
        $fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db","r") ;
1111
        if (!$fd) {
1112
            return FALSE;
1113
        }
1114
        while (!feof($fd)) {
1115
            $mac = trim(fgets($fd)) ;
1116
            if(strcasecmp($clientmac, $mac) == 0) {
1117
                fclose($fd) ;
1118
                return TRUE ;
1119
            }
1120
        }
1121
        fclose($fd) ;
1122
    }
1123
    return FALSE ;
1124
}
1125

    
1126
?>
(5-5/37)