Project

General

Profile

Download (35.9 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
		$installed_time_based_rules = false;
272
		if($config['schedules']) {
273
			foreach($config['schedules']['schedule'] as $sched) {
274
				$installed_time_based_rules = true;
275
				break;
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 = get_real_interface($cpifn);
298
	$cpip = get_interface_ip($cpifn);
299
	$lanip = get_interface_ip("lan");
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
        /* if list */
311
        $iflist = get_configured_interface_list();
312
	foreach ($iflist as $ifent => $ifname) {
313
		if($cpifn == $ifname)
314
			continue;
315
		$int = get_real_interface($ifname);
316
		$cprules .= "add 30 set 1 skipto 50000 all from any to any in via {$int} keep-state\n";
317
	}
318

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

    
326
EOD;
327
	}
328

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

    
335
# layer 2: pass ARP
336
add 1100 set 1 pass layer2 mac-type arp
337
# pfsense requires for WPA
338
add 1100 set 1 pass layer2 mac-type 0x888e
339
add 1100 set 1 pass layer2 mac-type 0x88c7
340

    
341
# PPP Over Ethernet Discovery Stage
342
add 1100 set 1 pass layer2 mac-type 0x8863
343
# PPP Over Ethernet Session Stage
344
add 1100 set 1 pass layer2 mac-type 0x8864
345

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

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

    
358
# allow access to our DNS forwarder
359
add 1300 set 1 pass udp from any to $cpip 53 in
360
add 1301 set 1 pass udp from $cpip 53 to any out
361

    
362
# allow access to our DNS forwarder if it incorrectly resolves the hostname to $lanip
363
add 1300 set 1 pass udp from any to $lanip 53 in
364
add 1301 set 1 pass udp from $lanip 53 to any out
365

    
366
# allow access to our web server
367
add 1302 set 1 pass tcp from any to $cpip 8000 in
368
add 1303 set 1 pass tcp from $cpip 8000 to any out
369

    
370
# allow access to lan web server incase the dns name resolves incorrectly to $lanip
371
add 1302 set 1 pass tcp from any to $lanip 8000 in
372
add 1303 set 1 pass tcp from $lanip 8000 to any out
373

    
374
EOD;
375

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

    
383
EOD;
384
	}
385

    
386
        $cprules .= <<<EOD
387
#PPPoE Discovery Stage
388
add 1100 set 1 pass layer2 mac-type 0x8863
389
#PPPoE Session Stage
390
add 1100 set 1 pass layer2 mac-type 0x8864
391

    
392
EOD;
393

    
394
        $cprules .= <<<EOD
395
# Allow WPA
396
add 1100 set 1 pass layer2 mac-type 0x888e
397

    
398
# ... 10000-19899: rules per authenticated client go here...
399

    
400
# redirect non-authenticated clients to captive portal
401
add 19902 set 1 fwd 127.0.0.1,8000 tcp from any to any 80 in
402
# let the responses from the captive portal web server back out
403
add 19903 set 1 pass tcp from any 80 to any out
404
# block everything else
405
add 19904 set 1 deny all from any to any
406

    
407
# ... 20000-29899: layer2 block rules per authenticated client go here...
408

    
409
# pass everything else on layer2
410
add 29900 set 1 pass all from any to any layer2
411

    
412
EOD;
413

    
414
    return $cprules;
415
}
416

    
417
/* remove clients that have been around for longer than the specified amount of time */
418
/* db file structure:
419
timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time */
420

    
421
/* (password is in Base64 and only saved when reauthentication is enabled) */
422
function captiveportal_prune_old() {
423

    
424
    global $g, $config;
425

    
426
    /* check for expired entries */
427
    if ($config['captiveportal']['timeout'])
428
        $timeout = $config['captiveportal']['timeout'] * 60;
429
    else
430
        $timeout = 0;
431

    
432
    if ($config['captiveportal']['idletimeout'])
433
        $idletimeout = $config['captiveportal']['idletimeout'] * 60;
434
    else
435
        $idletimeout = 0;
436

    
437
    if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && !isset($config['captiveportal']['radiussession_timeout']))
438
        return;
439

    
440
    captiveportal_lock();
441

    
442
    /* read database */
443
    $cpdb = captiveportal_read_db();
444

    
445
    $radiusservers = captiveportal_get_radius_servers();
446

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

    
454
        $timedout = false;
455
        $term_cause = 1;
456

    
457
		/* no pruning for fixed mac address entry */
458
		if (portal_mac_fixed($cpdb[$i][3])) {
459
			continue; // check next value
460
		}
461
        /* hard timeout? */
462
        if ($timeout) {
463
            if ((time() - $cpdb[$i][0]) >= $timeout) {
464
                $timedout = true;
465
                $term_cause = 5; // Session-Timeout
466
            }
467
        }
468

    
469
        /* Session-Terminate-Time */
470
        if (!$timedout && !empty($cpdb[$i][9])) {
471
            if (time() >= $cpdb[$i][9]) {
472
                $timedout = true;
473
                $term_cause = 5; // Session-Timeout
474
            }
475
        }
476

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

    
493
        /* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
494
        if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpdb[$i][7])) {
495
            if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
496
                $timedout = true;
497
                $term_cause = 5; // Session-Timeout
498
            }
499
        }
500

    
501
        if ($timedout) {
502
            captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
503
            captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
504
            unset($cpdb[$i]);
505
        }
506

    
507
        /* do periodic RADIUS reauthentication? */
508
        if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
509
            ($radiusservers !== false)) {
510

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

    
548
            /* check this user against RADIUS again */
549
            $auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
550
                                          base64_decode($cpdb[$i][6]), // password
551
                                            $radiusservers,
552
                                          $cpdb[$i][2], // clientip
553
                                          $cpdb[$i][3], // clientmac
554
                                          $cpdb[$i][1]); // ruleno
555

    
556
            if ($auth_list['auth_val'] == 3) {
557
                captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
558
                captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
559
                unset($cpdb[$i]);
560
            }
561
        }
562
    }
563

    
564
    /* write database */
565
    captiveportal_write_db($cpdb);
566

    
567
    captiveportal_unlock();
568
}
569

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

    
573
	global $g, $config;
574

    
575
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
576

    
577
	/* this client needs to be deleted - remove ipfw rules */
578
	if (isset($config['captiveportal']['radacct_enable']) && isset($radiusservers[0])) {
579
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
580
							   $dbent[4], // username
581
							   $dbent[5], // sessionid
582
							   $dbent[0], // start time
583
							   $radiusservers[0]['ipaddr'],
584
							   $radiusservers[0]['acctport'],
585
							   $radiusservers[0]['key'],
586
							   $dbent[2], // clientip
587
							   $dbent[3], // clientmac
588
							   $term_cause, // Acct-Terminate-Cause
589
							   false,
590
							   $stop_time);
591
	}
592

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

    
595
    /* We need to delete +40500 and +45500 as well...
596
     * these are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
597
     * We could get an error if the pipe doesn't exist but everything should still be fine
598
     */
599
    if (isset($config['captiveportal']['peruserbw'])) {
600
        mwexec("/sbin/ipfw pipe " . ($dbent[1]+40500) . " delete");
601
        mwexec("/sbin/ipfw pipe " . ($dbent[1]+45500) . " delete");
602
    }
603

    
604
	/* pfSense: ensure all pf states are killed (pfSense) */
605
	mwexec("pfctl -k {$dbent[2]}");
606
	mwexec("pfctl -K {$dbent[2]}");
607

    
608
}
609

    
610
/* remove a single client by ipfw rule number */
611
function captiveportal_disconnect_client($id,$term_cause = 1) {
612

    
613
	global $g, $config;
614

    
615
	captiveportal_lock();
616

    
617
	/* read database */
618
	$cpdb = captiveportal_read_db();
619
	$radiusservers = captiveportal_get_radius_servers();
620

    
621
	/* find entry */
622
	for ($i = 0; $i < count($cpdb); $i++) {
623
		if ($cpdb[$i][1] == $id) {
624
			captiveportal_disconnect($cpdb[$i], $radiusservers, $term_cause);
625
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "DISCONNECT");
626
			unset($cpdb[$i]);
627
			break;
628
		}
629
	}
630

    
631
	/* write database */
632
	captiveportal_write_db($cpdb);
633

    
634
	captiveportal_unlock();
635
}
636

    
637
/* send RADIUS acct stop for all current clients */
638
function captiveportal_radius_stop_all() {
639
	global $g, $config;
640

    
641
	if (!isset($config['captiveportal']['radacct_enable']))
642
		return;
643

    
644
	captiveportal_lock();
645
	$cpdb = captiveportal_read_db();
646

    
647
	$radiusservers = captiveportal_get_radius_servers();
648

    
649
	if (isset($radiusservers[0])) {
650
		for ($i = 0; $i < count($cpdb); $i++) {
651
			RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
652
								   $cpdb[$i][4], // username
653
								   $cpdb[$i][5], // sessionid
654
								   $cpdb[$i][0], // start time
655
								   $radiusservers[0]['ipaddr'],
656
								   $radiusservers[0]['acctport'],
657
								   $radiusservers[0]['key'],
658
								   $cpdb[$i][2], // clientip
659
								   $cpdb[$i][3], // clientmac
660
								   7); // Admin Reboot
661
		}
662
	}
663
	captiveportal_unlock();
664
}
665

    
666
function captiveportal_passthrumac_configure() {
667
	global $config, $g;
668

    
669
	captiveportal_lock();
670

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

    
674
	if (is_array($config['captiveportal']['passthrumac'])) {
675

    
676
		$fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db", "w");
677
		if (!$fd) {
678
			printf("Error: cannot open passthru mac DB file in captiveportal_passthrumac_configure().\n");
679
			captiveportal_unlock();
680
			return 1;
681
		}
682

    
683
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
684
			/* record passthru mac so it can be recognized and let thru */
685
			fwrite($fd, $macent['mac'] . "\n");
686
		}
687

    
688
		fclose($fd);
689
	}
690

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

    
709
	captiveportal_unlock();
710

    
711
	return 0;
712
}
713

    
714
function captiveportal_allowedip_configure() {
715
	global $config, $g;
716

    
717
	captiveportal_lock();
718

    
719
	/* clear out existing allowed ips, if necessary */
720
	if (file_exists("{$g['vardb_path']}/captiveportal_ip.db")) {
721
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "r");
722
		if ($fd) {
723
			while (!feof($fd)) {
724
				$line = trim(fgets($fd));
725
				if ($line) {
726
					list($ip,$rule) = explode(",",$line);
727
					mwexec("/sbin/ipfw delete $rule");
728
				}
729
			}
730
		}
731
		fclose($fd);
732
		unlink("{$g['vardb_path']}/captiveportal_ip.db");
733
	}
734

    
735
	/* get next ipfw rule number */
736
	if (file_exists("{$g['vardb_path']}/captiveportal.nextrule"))
737
		$ruleno = trim(file_get_contents("{$g['vardb_path']}/captiveportal.nextrule"));
738
	if (!$ruleno)
739
		$ruleno = 10000;	/* first rule number */
740

    
741
	if (is_array($config['captiveportal']['allowedip'])) {
742

    
743
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "w");
744
		if (!$fd) {
745
			printf("Error: cannot open allowed ip DB file in captiveportal_allowedip_configure().\n");
746
			captiveportal_unlock();
747
			return 1;
748
		}
749

    
750
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
751
            /* get next ipfw rule number */
752
            $ruleno = captiveportal_get_next_ipfw_ruleno();
753

    
754
            /* if the pool is empty, return apprioriate message and fail */
755
            if (is_null($ruleno)) {
756
                printf("Error: system reached maximum login capacity, no free FW rulenos in captiveportal_allowedip_configure().\n");
757
                fclose($fd);
758
                captiveportal_unlock();
759
                return 1;
760
            }
761

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

    
765
            /* insert ipfw rule to allow ip thru */
766
            if ($ipent['dir'] == "from") {
767
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any in");
768
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " out");
769
            } else {
770
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " in");
771
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any out");
772
            }
773

    
774
        }
775

    
776
        fclose($fd);
777
    }
778

    
779
    captiveportal_unlock();
780
    return 0;
781
}
782

    
783
/* get last activity timestamp given ipfw rule number */
784
function captiveportal_get_last_activity($ruleno) {
785

    
786
	$ipfwoutput = "";
787

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

    
790
	/* in */
791
	if ($ipfwoutput[0]) {
792
		$ri = explode(" ", $ipfwoutput[0]);
793
		if ($ri[1])
794
			return $ri[1];
795
	}
796

    
797
	return 0;
798
}
799

    
800
/* read RADIUS servers into array */
801
function captiveportal_get_radius_servers() {
802

    
803
        global $g;
804

    
805
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
806
                $fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db","r");
807
                if ($fd) {
808
                        $radiusservers = array();
809
                        while (!feof($fd)) {
810
                                $line = trim(fgets($fd));
811
                                if ($line) {
812
                                        $radsrv = array();
813
                                        list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
814
                                        $radiusservers[] = $radsrv;
815
                                }
816
                        }
817
                        fclose($fd);
818

    
819
                        return $radiusservers;
820
                }
821
        }
822

    
823
        return false;
824
}
825

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

    
830
        global $lockfile;
831

    
832
        $n = 1;
833
        while ($n) {
834
                /* open the lock file in append mode to avoid race condition */
835
                if ($fd = @fopen($lockfile, "x")) {
836
                        /* succeeded */
837
                        fclose($fd);
838
						if($n > 10) {
839
						    captiveportal_syslog("LOCKINFO: Waiting for lock for $n seconds/s!");
840
						}
841
                        return;
842
                } else {
843
                        /* file locked, wait and try again */
844
                        sleep(1);
845

    
846
						if(($n % 60) == 0) {
847
						    captiveportal_syslog("LOCKWARNING: waiting for lock for " . $n/60 . " minute/s!");
848
						    if(($n % 600) == 0) {
849
						        captiveportal_syslog("LOCKERROR: waiting for lock for 10 minute/s - EXITING PROCESS!");
850
						        die("Can't get a lock");
851
						    }
852
					    }
853
                }
854
                $n++;
855
        }
856
		/* we never get here */
857
}
858

    
859
/* unlock captive portal information file */
860
function captiveportal_unlock() {
861

    
862
        global $lockfile;
863

    
864
        if (file_exists($lockfile))
865
                unlink($lockfile);
866
}
867

    
868
/* log successful captive portal authentication to syslog */
869
/* part of this code from php.net */
870
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
871
	$message = trim($message);
872
	// Log it
873
	if (!$message)
874
		$message = "$status: $user, $mac, $ip";
875
	else
876
		$message = "$status: $user, $mac, $ip, $message";
877
	captiveportal_syslog($message);
878
	closelog();
879
}
880

    
881
/* log simple messages to syslog */
882
function captiveportal_syslog($message) {
883
	define_syslog_variables();
884
	$message = trim($message);
885
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
886
	// Log it
887
	syslog(LOG_INFO, $message);
888
	closelog();
889
}
890

    
891
function radius($username,$password,$clientip,$clientmac,$type) {
892
    global $g, $config;
893

    
894
    /* Start locking from the beginning of an authentication session */
895
    captiveportal_lock();
896

    
897
    $ruleno = captiveportal_get_next_ipfw_ruleno();
898

    
899
    /* if the pool is empty, return apprioriate message and fail authentication */
900
    if (is_null($ruleno)) {
901
        $auth_list = array();
902
        $auth_list['auth_val'] = 1;
903
        $auth_list['error'] = "System reached maximum login capacity";
904
        captiveportal_unlock();
905
        return $auth_list;
906
    }
907

    
908
    $radiusservers = captiveportal_get_radius_servers();
909

    
910
    $auth_list = RADIUS_AUTHENTICATION($username,
911
                    $password,
912
                    $radiusservers,
913
                    $clientip,
914
                    $clientmac,
915
                    $ruleno);
916

    
917
    if ($auth_list['auth_val'] == 2) {
918
        captiveportal_logportalauth($username,$clientmac,$clientip,$type);
919
        $sessionid = portal_allow($clientip,
920
                    $clientmac,
921
                    $username,
922
                    $password,
923
                    $auth_list,
924
                    $ruleno);
925
    }
926
    else {
927
        captiveportal_unlock();
928
    }
929

    
930
    return $auth_list;
931

    
932
}
933

    
934
/* read captive portal DB into array */
935
function captiveportal_read_db() {
936

    
937
        global $g;
938

    
939
        $cpdb = array();
940
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
941
        if ($fd) {
942
                while (!feof($fd)) {
943
                        $line = trim(fgets($fd));
944
                        if ($line) {
945
                                $cpdb[] = explode(",", $line);
946
                        }
947
                }
948
                fclose($fd);
949
        }
950
        return $cpdb;
951
}
952

    
953
/* write captive portal DB */
954
function captiveportal_write_db($cpdb) {
955
                 
956
        global $g;
957
                
958
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
959
        if ($fd) { 
960
                foreach ($cpdb as $cpent) {
961
                        fwrite($fd, join(",", $cpent) . "\n");
962
                }       
963
                fclose($fd);
964
        }       
965
}
966

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

    
1003
/*
1004
 * This function will calculate the lowest free firewall ruleno
1005
 * within the range specified based on the actual installed rules
1006
 *
1007
 */
1008

    
1009
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 10000, $rulenos_range_max = 9899) {
1010

    
1011
	$fwrules = "";
1012
	$matches = "";
1013
	exec("/sbin/ipfw show", $fwrules);
1014
	foreach ($fwrules as $fwrule) {
1015
		preg_match("/^(\d+)\s+/", $fwrule, $matches);
1016
		$rulenos_used[] = $matches[1];
1017
	}
1018
	$rulenos_used = array_unique($rulenos_used);
1019
	$rulenos_range = count($rulenos_used);
1020
	if ($rulenos_range > $rulenos_range_max) {
1021
		return NULL;
1022
	}
1023
	$rulenos_pool = range($rulenos_start, ($rulenos_start + $rulenos_range));
1024
	$rulenos_free = array_diff($rulenos_pool, $rulenos_used);
1025
	$ruleno = array_shift($rulenos_free);
1026

    
1027
	return $ruleno;
1028
}
1029

    
1030
/**
1031
 * This function will calculate the traffic produced by a client
1032
 * based on its firewall rule
1033
 *
1034
 * Point of view: NAS
1035
 *
1036
 * Input means: from the client
1037
 * Output means: to the client
1038
 *
1039
 */
1040

    
1041
function getVolume($ruleno) {
1042

    
1043
    $volume = array();
1044

    
1045
    // Initialize vars properly, since we don't want NULL vars
1046
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1047

    
1048
    // Ingress
1049
    $ipfw = "";
1050
    $matches = "";
1051
    exec("/sbin/ipfw show {$ruleno}", $ipfw);
1052
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[0], $matches);
1053
    $volume['input_pkts'] = $matches[2];
1054
    $volume['input_bytes'] = $matches[3];
1055

    
1056
    // Flush internal buffer
1057
    unset($matches);
1058

    
1059
    // Outgress
1060
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[1], $matches);
1061
    $volume['output_pkts'] = $matches[2];
1062
    $volume['output_bytes'] = $matches[3];
1063

    
1064
    return $volume;
1065
}
1066

    
1067
/**
1068
 * Get the NAS-Identifier
1069
 *
1070
 * We will use our local hostname to make up the nas_id
1071
 */
1072
function getNasID()
1073
{
1074
    $nasId = "";
1075
    exec("/bin/hostname", $nasId);
1076
    if(!$nasId[0])
1077
        $nasId[0] = "{$g['product_name']}";
1078
    return $nasId[0];
1079
}
1080

    
1081
/**
1082
 * Get the NAS-IP-Address based on the current wan address
1083
 *
1084
 * Use functions in interfaces.inc to find this out
1085
 *
1086
 */
1087

    
1088
function getNasIP()
1089
{
1090
    $nasIp = get_interface_ip();
1091
    if(!$nasIp)
1092
        $nasIp = "0.0.0.0";
1093
    return $nasIp;
1094
}
1095

    
1096
function portal_mac_fixed($clientmac) {
1097
    global $g ;
1098

    
1099
    /* open captive portal mac db */
1100
    if (file_exists("{$g['vardb_path']}/captiveportal_mac.db")) {
1101
        $fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db","r") ;
1102
        if (!$fd) {
1103
            return FALSE;
1104
        }
1105
        while (!feof($fd)) {
1106
            $mac = trim(fgets($fd)) ;
1107
            if(strcasecmp($clientmac, $mac) == 0) {
1108
                fclose($fd) ;
1109
                return TRUE ;
1110
            }
1111
        }
1112
        fclose($fd) ;
1113
    }
1114
    return FALSE ;
1115
}
1116

    
1117
?>
(6-6/40)