Project

General

Profile

Download (35.3 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
		/* unload ipfw */
271
		mwexec("/sbin/kldunload ipfw.ko");
272
	}
273

    
274
    captiveportal_unlock();
275
	
276
	return 0;
277
}
278

    
279
function captiveportal_rules_generate() {
280
	global $config, $g;
281

    
282
	$cpifn = $config['captiveportal']['interface'];
283
	$cpif = get_real_interface($cpifn);
284
	$cpip = get_interface_ip($cpifn);
285
	$lanip = get_interface_ip("lan");
286
	
287
	/* note: the captive portal daemon inserts all pass rules for authenticated
288
	   clients as skipto 50000 rules to make traffic shaping work */
289

    
290
	$cprules =  "add 500 set 1 allow pfsync from any to any\n";
291
	$cprules .= "add 500 set 1 allow carp from any to any\n";
292

    
293
	/*   allow nat redirects to work  see
294
	     http://cvstrac.pfsense.com/tktview?tn=651
295
	 */
296
        /* if list */
297
        $iflist = get_configured_interface_list();
298
	foreach ($iflist as $ifent => $ifname) {
299
		if($cpifn == $ifname)
300
			continue;
301
		$int = get_real_interface($ifname);
302
		$cprules .= "add 30 set 1 skipto 50000 all from any to any in via {$int} keep-state\n";
303
	}
304

    
305
	/* captive portal on LAN interface? */
306
	if ($cpifn == "lan") {
307
		/* add anti-lockout rules */
308
		$cprules .= <<<EOD
309
add 500 set 1 pass all from $cpip to any out via $cpif
310
add 501 set 1 pass all from any to $cpip in via $cpif
311

    
312
EOD;
313
	}
314

    
315
	$cprules .= <<<EOD
316
# skip to traffic shaper if not on captive portal interface
317
add 1000 set 1 skipto 50000 all from any to any not layer2 not via $cpif
318
# pass all layer2 traffic on other interfaces
319
add 1001 set 1 pass layer2 not via $cpif
320

    
321
# layer 2: pass ARP
322
add 1100 set 1 pass layer2 mac-type arp
323
# pfsense requires for WPA
324
add 1100 set 1 pass layer2 mac-type 0x888e
325
add 1100 set 1 pass layer2 mac-type 0x88c7
326

    
327
# PPP Over Ethernet Discovery Stage
328
add 1100 set 1 pass layer2 mac-type 0x8863
329
# PPP Over Ethernet Session Stage
330
add 1100 set 1 pass layer2 mac-type 0x8864
331
# Allow WPA
332
add 1100 set 1 pass layer2 mac-type 0x888e
333

    
334
# layer 2: block anything else non-IP
335
add 1101 set 1 deny layer2 not mac-type ip
336
# layer 2: check if MAC addresses of authenticated clients are correct
337
add 1102 set 1 skipto 20000 layer2
338

    
339
# allow access to our DHCP server (which needs to be able to ping clients as well)
340
add 1200 set 1 pass udp from any 68 to 255.255.255.255 67 in
341
add 1201 set 1 pass udp from any 68 to $cpip 67 in
342
add 1202 set 1 pass udp from $cpip 67 to any 68 out
343
add 1203 set 1 pass icmp from $cpip to any out icmptype 8
344
add 1204 set 1 pass icmp from any to $cpip in icmptype 0
345

    
346
# allow access to our DNS forwarder
347
add 1300 set 1 pass udp from any to $cpip 53 in
348
add 1301 set 1 pass udp from $cpip 53 to any out
349

    
350
# allow access to our DNS forwarder if it incorrectly resolves the hostname to $lanip
351
add 1300 set 1 pass udp from any to $lanip 53 in
352
add 1301 set 1 pass udp from $lanip 53 to any out
353

    
354
# allow access to our web server
355
add 1302 set 1 pass tcp from any to $cpip 8000 in
356
add 1303 set 1 pass tcp from $cpip 8000 to any out
357

    
358
# allow access to lan web server incase the dns name resolves incorrectly to $lanip
359
add 1302 set 1 pass tcp from any to $lanip 8000 in
360
add 1303 set 1 pass tcp from $lanip 8000 to any out
361

    
362
EOD;
363

    
364
	if (isset($config['captiveportal']['httpslogin'])) {
365
		$cprules .= <<<EOD
366
add 1304 set 1 pass tcp from any to $cpip 8001 in
367
add 1305 set 1 pass tcp from $cpip 8001 to any out
368

    
369
EOD;
370
	}
371

    
372
        $cprules .= <<<EOD
373

    
374
# ... 10000-19899: rules per authenticated client go here...
375

    
376
# redirect non-authenticated clients to captive portal
377
add 19902 set 1 fwd 127.0.0.1,8000 tcp from any to any 80 in
378
# let the responses from the captive portal web server back out
379
add 19903 set 1 pass tcp from any 80 to any out
380
# block everything else
381
add 19904 set 1 deny all from any to any
382

    
383
# ... 20000-29899: layer2 block rules per authenticated client go here...
384

    
385
# pass everything else on layer2
386
add 29900 set 1 pass all from any to any layer2
387

    
388
EOD;
389

    
390
    return $cprules;
391
}
392

    
393
/* remove clients that have been around for longer than the specified amount of time */
394
/* db file structure:
395
timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time */
396

    
397
/* (password is in Base64 and only saved when reauthentication is enabled) */
398
function captiveportal_prune_old() {
399

    
400
    global $g, $config;
401

    
402
    /* check for expired entries */
403
    if ($config['captiveportal']['timeout'])
404
        $timeout = $config['captiveportal']['timeout'] * 60;
405
    else
406
        $timeout = 0;
407

    
408
    if ($config['captiveportal']['idletimeout'])
409
        $idletimeout = $config['captiveportal']['idletimeout'] * 60;
410
    else
411
        $idletimeout = 0;
412

    
413
    if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && !isset($config['captiveportal']['radiussession_timeout']))
414
        return;
415

    
416
    captiveportal_lock();
417

    
418
    /* read database */
419
    $cpdb = captiveportal_read_db();
420

    
421
    $radiusservers = captiveportal_get_radius_servers();
422

    
423
 	/*  To make sure we iterate over ALL accounts on every run the count($cpdb) is moved outside of the loop. Otherwise
424
     *  the loop would evalate count() on every iteration and since $i would increase and count() would decrement they
425
     *  would meet before we had a chance to iterate over all accounts.
426
     */
427
    $no_users = count($cpdb);
428
    for ($i = 0; $i < $no_users; $i++) {
429

    
430
        $timedout = false;
431
        $term_cause = 1;
432

    
433
		/* no pruning for fixed mac address entry */
434
		if (portal_mac_fixed($cpdb[$i][3])) {
435
			continue; // check next value
436
		}
437
        /* hard timeout? */
438
        if ($timeout) {
439
            if ((time() - $cpdb[$i][0]) >= $timeout) {
440
                $timedout = true;
441
                $term_cause = 5; // Session-Timeout
442
            }
443
        }
444

    
445
        /* Session-Terminate-Time */
446
        if (!$timedout && !empty($cpdb[$i][9])) {
447
            if (time() >= $cpdb[$i][9]) {
448
                $timedout = true;
449
                $term_cause = 5; // Session-Timeout
450
            }
451
        }
452

    
453
        /* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
454
        $idletimeout = (is_numeric($cpdb[$i][8])) ? $cpdb[$i][8] : $idletimeout;
455
        /* if an idle timeout is specified, get last activity timestamp from ipfw */
456
        if (!$timedout && $idletimeout) {
457
            $lastact = captiveportal_get_last_activity($cpdb[$i][1]);
458
			/*  if the user has logged on but not sent any trafic they will never be logged out.
459
			 *  We "fix" this by setting lastact to the login timestamp 
460
			 */
461
			$lastact = $lastact ? $lastact : $cpdb[$i][0];
462
            if ($lastact && ((time() - $lastact) >= $idletimeout)) {
463
                $timedout = true;
464
                $term_cause = 4; // Idle-Timeout
465
                $stop_time = $lastact; // Entry added to comply with WISPr
466
            }
467
        }
468

    
469
        /* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
470
        if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpdb[$i][7])) {
471
            if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
472
                $timedout = true;
473
                $term_cause = 5; // Session-Timeout
474
            }
475
        }
476

    
477
        if ($timedout) {
478
            captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
479
            captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
480
            unset($cpdb[$i]);
481
        }
482

    
483
        /* do periodic RADIUS reauthentication? */
484
        if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
485
            ($radiusservers !== false)) {
486

    
487
            if (isset($config['captiveportal']['radacct_enable'])) {
488
                if ($config['captiveportal']['reauthenticateacct'] == "stopstart") {
489
                    /* stop and restart accounting */
490
                    RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
491
                                           $cpdb[$i][4], // username
492
                                           $cpdb[$i][5], // sessionid
493
                                           $cpdb[$i][0], // start time
494
                                           $radiusservers[0]['ipaddr'],
495
                                           $radiusservers[0]['acctport'],
496
                                           $radiusservers[0]['key'],
497
                                           $cpdb[$i][2], // clientip
498
                                           $cpdb[$i][3], // clientmac
499
                                           10); // NAS Request
500
                    exec("/sbin/ipfw zero {$cpdb[$i][1]}");
501
                    RADIUS_ACCOUNTING_START($cpdb[$i][1], // ruleno
502
                                            $cpdb[$i][4], // username
503
                                            $cpdb[$i][5], // sessionid
504
                                            $radiusservers[0]['ipaddr'],
505
                                            $radiusservers[0]['acctport'],
506
                                            $radiusservers[0]['key'],
507
                                            $cpdb[$i][2], // clientip
508
                                            $cpdb[$i][3]); // clientmac
509
                } else if ($config['captiveportal']['reauthenticateacct'] == "interimupdate") {
510
                    RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
511
                                           $cpdb[$i][4], // username
512
                                           $cpdb[$i][5], // sessionid
513
                                           $cpdb[$i][0], // start time
514
                                           $radiusservers[0]['ipaddr'],
515
                                           $radiusservers[0]['acctport'],
516
                                           $radiusservers[0]['key'],
517
                                           $cpdb[$i][2], // clientip
518
                                           $cpdb[$i][3], // clientmac
519
                                           10, // NAS Request
520
                                           true); // Interim Updates
521
                }
522
            }
523

    
524
            /* check this user against RADIUS again */
525
            $auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
526
                                          base64_decode($cpdb[$i][6]), // password
527
                                            $radiusservers,
528
                                          $cpdb[$i][2], // clientip
529
                                          $cpdb[$i][3], // clientmac
530
                                          $cpdb[$i][1]); // ruleno
531

    
532
            if ($auth_list['auth_val'] == 3) {
533
                captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
534
                captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
535
                unset($cpdb[$i]);
536
            }
537
        }
538
    }
539

    
540
    /* write database */
541
    captiveportal_write_db($cpdb);
542

    
543
    captiveportal_unlock();
544
}
545

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

    
549
	global $g, $config;
550

    
551
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
552

    
553
	/* this client needs to be deleted - remove ipfw rules */
554
	if (isset($config['captiveportal']['radacct_enable']) && isset($radiusservers[0])) {
555
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
556
							   $dbent[4], // username
557
							   $dbent[5], // sessionid
558
							   $dbent[0], // start time
559
							   $radiusservers[0]['ipaddr'],
560
							   $radiusservers[0]['acctport'],
561
							   $radiusservers[0]['key'],
562
							   $dbent[2], // clientip
563
							   $dbent[3], // clientmac
564
							   $term_cause, // Acct-Terminate-Cause
565
							   false,
566
							   $stop_time);
567
	}
568

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

    
571
    /* We need to delete +40500 and +45500 as well...
572
     * these are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
573
     * We could get an error if the pipe doesn't exist but everything should still be fine
574
     */
575
    if (isset($config['captiveportal']['peruserbw'])) {
576
        mwexec("/sbin/ipfw pipe " . ($dbent[1]+40500) . " delete");
577
        mwexec("/sbin/ipfw pipe " . ($dbent[1]+45500) . " delete");
578
    }
579

    
580
	/* pfSense: ensure all pf states are killed (pfSense) */
581
	mwexec("pfctl -k {$dbent[2]}");
582
	mwexec("pfctl -K {$dbent[2]}");
583

    
584
}
585

    
586
/* remove a single client by ipfw rule number */
587
function captiveportal_disconnect_client($id,$term_cause = 1) {
588

    
589
	global $g, $config;
590

    
591
	captiveportal_lock();
592

    
593
	/* read database */
594
	$cpdb = captiveportal_read_db();
595
	$radiusservers = captiveportal_get_radius_servers();
596

    
597
	/* find entry */
598
	for ($i = 0; $i < count($cpdb); $i++) {
599
		if ($cpdb[$i][1] == $id) {
600
			captiveportal_disconnect($cpdb[$i], $radiusservers, $term_cause);
601
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "DISCONNECT");
602
			unset($cpdb[$i]);
603
			break;
604
		}
605
	}
606

    
607
	/* write database */
608
	captiveportal_write_db($cpdb);
609

    
610
	captiveportal_unlock();
611
}
612

    
613
/* send RADIUS acct stop for all current clients */
614
function captiveportal_radius_stop_all() {
615
	global $g, $config;
616

    
617
	if (!isset($config['captiveportal']['radacct_enable']))
618
		return;
619

    
620
	captiveportal_lock();
621
	$cpdb = captiveportal_read_db();
622

    
623
	$radiusservers = captiveportal_get_radius_servers();
624

    
625
	if (isset($radiusservers[0])) {
626
		for ($i = 0; $i < count($cpdb); $i++) {
627
			RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
628
								   $cpdb[$i][4], // username
629
								   $cpdb[$i][5], // sessionid
630
								   $cpdb[$i][0], // start time
631
								   $radiusservers[0]['ipaddr'],
632
								   $radiusservers[0]['acctport'],
633
								   $radiusservers[0]['key'],
634
								   $cpdb[$i][2], // clientip
635
								   $cpdb[$i][3], // clientmac
636
								   7); // Admin Reboot
637
		}
638
	}
639
	captiveportal_unlock();
640
}
641

    
642
function captiveportal_passthrumac_configure() {
643
	global $config, $g;
644

    
645
	captiveportal_lock();
646

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

    
650
	if (is_array($config['captiveportal']['passthrumac'])) {
651

    
652
		$fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db", "w");
653
		if (!$fd) {
654
			printf("Error: cannot open passthru mac DB file in captiveportal_passthrumac_configure().\n");
655
			captiveportal_unlock();
656
			return 1;
657
		}
658

    
659
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
660
			/* record passthru mac so it can be recognized and let thru */
661
			fwrite($fd, $macent['mac'] . "\n");
662
		}
663

    
664
		fclose($fd);
665
	}
666

    
667
	/*    pfSense:
668
	 * 	  pass through mac entries should always exist.  the reason
669
	 *    for this is because we do not have native mac address filtering
670
	 *    mechanisms.  this allows us to filter by mac address easily
671
	 *    and get around this limitation.   I consider this a bug in
672
	 *    m0n0wall and pfSense as m0n0wall does not have native mac
673
	 *    filtering mechanisms as well. -Scott Ullrich
674
	 */
675
	if (is_array($config['captiveportal']['passthrumac'])) {
676
		mwexec("/sbin/ipfw delete 50");
677
		foreach($config['captiveportal']['passthrumac'] as $ptm) {
678
			/* create the pass through mac entry */
679
			//system("echo /sbin/ipfw add 50 skipto 65535 ip from any to any MAC {$ptm['mac']} any > /tmp/cp");
680
			mwexec("/sbin/ipfw add 50 skipto 29900 ip from any to any MAC {$ptm['mac']} any keep-state");
681
			mwexec("/sbin/ipfw add 50 skipto 29900 ip from any to any MAC any {$ptm['mac']} keep-state");
682
		}
683
	}
684

    
685
	captiveportal_unlock();
686

    
687
	return 0;
688
}
689

    
690
function captiveportal_allowedip_configure() {
691
	global $config, $g;
692

    
693
	captiveportal_lock();
694

    
695
	/* clear out existing allowed ips, if necessary */
696
	if (file_exists("{$g['vardb_path']}/captiveportal_ip.db")) {
697
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "r");
698
		if ($fd) {
699
			while (!feof($fd)) {
700
				$line = trim(fgets($fd));
701
				if ($line) {
702
					list($ip,$rule) = explode(",",$line);
703
					mwexec("/sbin/ipfw delete $rule");
704
				}
705
			}
706
		}
707
		fclose($fd);
708
		unlink("{$g['vardb_path']}/captiveportal_ip.db");
709
	}
710

    
711
	/* get next ipfw rule number */
712
	if (file_exists("{$g['vardb_path']}/captiveportal.nextrule"))
713
		$ruleno = trim(file_get_contents("{$g['vardb_path']}/captiveportal.nextrule"));
714
	if (!$ruleno)
715
		$ruleno = 10000;	/* first rule number */
716

    
717
	if (is_array($config['captiveportal']['allowedip'])) {
718

    
719
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "w");
720
		if (!$fd) {
721
			printf("Error: cannot open allowed ip DB file in captiveportal_allowedip_configure().\n");
722
			captiveportal_unlock();
723
			return 1;
724
		}
725

    
726
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
727
            /* get next ipfw rule number */
728
            $ruleno = captiveportal_get_next_ipfw_ruleno();
729

    
730
            /* if the pool is empty, return apprioriate message and fail */
731
            if (is_null($ruleno)) {
732
                printf("Error: system reached maximum login capacity, no free FW rulenos in captiveportal_allowedip_configure().\n");
733
                fclose($fd);
734
                captiveportal_unlock();
735
                return 1;
736
            }
737

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

    
741
            /* insert ipfw rule to allow ip thru */
742
            if ($ipent['dir'] == "from") {
743
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any in");
744
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " out");
745
            } else {
746
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " in");
747
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any out");
748
            }
749

    
750
        }
751

    
752
        fclose($fd);
753
    }
754

    
755
    captiveportal_unlock();
756
    return 0;
757
}
758

    
759
/* get last activity timestamp given ipfw rule number */
760
function captiveportal_get_last_activity($ruleno) {
761

    
762
	$ipfwoutput = "";
763

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

    
766
	/* in */
767
	if ($ipfwoutput[0]) {
768
		$ri = explode(" ", $ipfwoutput[0]);
769
		if ($ri[1])
770
			return $ri[1];
771
	}
772

    
773
	return 0;
774
}
775

    
776
/* read RADIUS servers into array */
777
function captiveportal_get_radius_servers() {
778

    
779
        global $g;
780

    
781
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
782
                $fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db","r");
783
                if ($fd) {
784
                        $radiusservers = array();
785
                        while (!feof($fd)) {
786
                                $line = trim(fgets($fd));
787
                                if ($line) {
788
                                        $radsrv = array();
789
                                        list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
790
                                        $radiusservers[] = $radsrv;
791
                                }
792
                        }
793
                        fclose($fd);
794

    
795
                        return $radiusservers;
796
                }
797
        }
798

    
799
        return false;
800
}
801

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

    
806
        global $lockfile;
807

    
808
        $n = 1;
809
        while ($n) {
810
                /* open the lock file in append mode to avoid race condition */
811
                if ($fd = @fopen($lockfile, "x")) {
812
                        /* succeeded */
813
                        fclose($fd);
814
						if($n > 10) {
815
						    captiveportal_syslog("LOCKINFO: Waiting for lock for $n seconds/s!");
816
						}
817
                        return;
818
                } else {
819
                        /* file locked, wait and try again */
820
                        sleep(1);
821

    
822
						if(($n % 60) == 0) {
823
						    captiveportal_syslog("LOCKWARNING: waiting for lock for " . $n/60 . " minute/s!");
824
						    if(($n % 600) == 0) {
825
						        captiveportal_syslog("LOCKERROR: waiting for lock for 10 minute/s - EXITING PROCESS!");
826
						        die("Can't get a lock");
827
						    }
828
					    }
829
                }
830
                $n++;
831
        }
832
		/* we never get here */
833
}
834

    
835
/* unlock captive portal information file */
836
function captiveportal_unlock() {
837

    
838
        global $lockfile;
839

    
840
        if (file_exists($lockfile))
841
                unlink($lockfile);
842
}
843

    
844
/* log successful captive portal authentication to syslog */
845
/* part of this code from php.net */
846
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
847
	$message = trim($message);
848
	// Log it
849
	if (!$message)
850
		$message = "$status: $user, $mac, $ip";
851
	else
852
		$message = "$status: $user, $mac, $ip, $message";
853
	captiveportal_syslog($message);
854
	closelog();
855
}
856

    
857
/* log simple messages to syslog */
858
function captiveportal_syslog($message) {
859
	define_syslog_variables();
860
	$message = trim($message);
861
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
862
	// Log it
863
	syslog(LOG_INFO, $message);
864
	closelog();
865
}
866

    
867
function radius($username,$password,$clientip,$clientmac,$type) {
868
    global $g, $config;
869

    
870
    /* Start locking from the beginning of an authentication session */
871
    captiveportal_lock();
872

    
873
    $ruleno = captiveportal_get_next_ipfw_ruleno();
874

    
875
    /* if the pool is empty, return apprioriate message and fail authentication */
876
    if (is_null($ruleno)) {
877
        $auth_list = array();
878
        $auth_list['auth_val'] = 1;
879
        $auth_list['error'] = "System reached maximum login capacity";
880
        captiveportal_unlock();
881
        return $auth_list;
882
    }
883

    
884
    $radiusservers = captiveportal_get_radius_servers();
885

    
886
    $auth_list = RADIUS_AUTHENTICATION($username,
887
                    $password,
888
                    $radiusservers,
889
                    $clientip,
890
                    $clientmac,
891
                    $ruleno);
892

    
893
    if ($auth_list['auth_val'] == 2) {
894
        captiveportal_logportalauth($username,$clientmac,$clientip,$type);
895
        $sessionid = portal_allow($clientip,
896
                    $clientmac,
897
                    $username,
898
                    $password,
899
                    $auth_list,
900
                    $ruleno);
901
    }
902
    else {
903
        captiveportal_unlock();
904
    }
905

    
906
    return $auth_list;
907

    
908
}
909

    
910
/* read captive portal DB into array */
911
function captiveportal_read_db() {
912

    
913
        global $g;
914

    
915
        $cpdb = array();
916
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
917
        if ($fd) {
918
                while (!feof($fd)) {
919
                        $line = trim(fgets($fd));
920
                        if ($line) {
921
                                $cpdb[] = explode(",", $line);
922
                        }
923
                }
924
                fclose($fd);
925
        }
926
        return $cpdb;
927
}
928

    
929
/* write captive portal DB */
930
function captiveportal_write_db($cpdb) {
931
                 
932
        global $g;
933
                
934
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
935
        if ($fd) { 
936
                foreach ($cpdb as $cpent) {
937
                        fwrite($fd, join(",", $cpent) . "\n");
938
                }       
939
                fclose($fd);
940
        }       
941
}
942

    
943
function captiveportal_write_elements() {
944
    global $g, $config;
945
    
946
    /* delete any existing elements */
947
    if (is_dir($g['captiveportal_element_path'])) {
948
        $dh = opendir($g['captiveportal_element_path']);
949
        while (($file = readdir($dh)) !== false) {
950
            if ($file != "." && $file != "..")
951
                unlink($g['captiveportal_element_path'] . "/" . $file);
952
        }
953
        closedir($dh);
954
    } else {
955
        @mkdir($g['captiveportal_element_path']);
956
    }
957
    
958
	if (is_array($config['captiveportal']['element'])) {
959
		conf_mount_rw();
960
		foreach ($config['captiveportal']['element'] as $data) {
961
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
962
			if (!$fd) {
963
				printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
964
				return 1;
965
			}
966
			$decoded = base64_decode($data['content']);
967
			fwrite($fd,$decoded);
968
			fclose($fd);
969
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
970
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
971
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
972
		}
973
		conf_mount_ro();
974
	}
975
    
976
    return 0;
977
}
978

    
979
/*
980
 * This function will calculate the lowest free firewall ruleno
981
 * within the range specified based on the actual installed rules
982
 *
983
 */
984

    
985
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 10000, $rulenos_range_max = 9899) {
986

    
987
	$fwrules = "";
988
	$matches = "";
989
	exec("/sbin/ipfw show", $fwrules);
990
	foreach ($fwrules as $fwrule) {
991
		preg_match("/^(\d+)\s+/", $fwrule, $matches);
992
		$rulenos_used[] = $matches[1];
993
	}
994
	$rulenos_used = array_unique($rulenos_used);
995
	$rulenos_range = count($rulenos_used);
996
	if ($rulenos_range > $rulenos_range_max) {
997
		return NULL;
998
	}
999
	$rulenos_pool = range($rulenos_start, ($rulenos_start + $rulenos_range));
1000
	$rulenos_free = array_diff($rulenos_pool, $rulenos_used);
1001
	$ruleno = array_shift($rulenos_free);
1002

    
1003
	return $ruleno;
1004
}
1005

    
1006
/**
1007
 * This function will calculate the traffic produced by a client
1008
 * based on its firewall rule
1009
 *
1010
 * Point of view: NAS
1011
 *
1012
 * Input means: from the client
1013
 * Output means: to the client
1014
 *
1015
 */
1016

    
1017
function getVolume($ruleno) {
1018

    
1019
    $volume = array();
1020

    
1021
    // Initialize vars properly, since we don't want NULL vars
1022
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1023

    
1024
    // Ingress
1025
    $ipfw = "";
1026
    $matches = "";
1027
    exec("/sbin/ipfw show {$ruleno}", $ipfw);
1028
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[0], $matches);
1029
    $volume['input_pkts'] = $matches[2];
1030
    $volume['input_bytes'] = $matches[3];
1031

    
1032
    // Flush internal buffer
1033
    unset($matches);
1034

    
1035
    // Outgress
1036
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[1], $matches);
1037
    $volume['output_pkts'] = $matches[2];
1038
    $volume['output_bytes'] = $matches[3];
1039

    
1040
    return $volume;
1041
}
1042

    
1043
/**
1044
 * Get the NAS-Identifier
1045
 *
1046
 * We will use our local hostname to make up the nas_id
1047
 */
1048
function getNasID()
1049
{
1050
    $nasId = "";
1051
    exec("/bin/hostname", $nasId);
1052
    if(!$nasId[0])
1053
        $nasId[0] = "{$g['product_name']}";
1054
    return $nasId[0];
1055
}
1056

    
1057
/**
1058
 * Get the NAS-IP-Address based on the current wan address
1059
 *
1060
 * Use functions in interfaces.inc to find this out
1061
 *
1062
 */
1063

    
1064
function getNasIP()
1065
{
1066
    $nasIp = get_interface_ip();
1067
    if(!$nasIp)
1068
        $nasIp = "0.0.0.0";
1069
    return $nasIp;
1070
}
1071

    
1072
function portal_mac_fixed($clientmac) {
1073
    global $g ;
1074

    
1075
    /* open captive portal mac db */
1076
    if (file_exists("{$g['vardb_path']}/captiveportal_mac.db")) {
1077
        $fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db","r") ;
1078
        if (!$fd) {
1079
            return FALSE;
1080
        }
1081
        while (!feof($fd)) {
1082
            $mac = trim(fgets($fd)) ;
1083
            if(strcasecmp($clientmac, $mac) == 0) {
1084
                fclose($fd) ;
1085
                return TRUE ;
1086
            }
1087
        }
1088
        fclose($fd) ;
1089
    }
1090
    return FALSE ;
1091
}
1092

    
1093
?>
(6-6/40)