Project

General

Profile

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
130

    
131

    
132
EOD;
133
		}
134

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

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

    
161
EOD;
162
		}
163

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

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

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

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

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

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

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

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

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

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

    
203
		$use_fastcgi = true;
204

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
279
		captiveportal_radius_stop_all();
280

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

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

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

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

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

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

    
320
	/*   allow nat redirects to work  see
321
	     http://cvstrac.pfsense.com/tktview?tn=651
322
	 */
323
	$iflist = array("lan" => "LAN", "wan" => "WAN");
324
	$captive_portal_interface = strtoupper($cpifn);
325
	for ($i = 1; isset($config['interfaces']['opt' . $i]); $i++)
326
		$iflist['opt' . $i] = "OPT{$i}";
327
	foreach ($iflist as $ifent => $ifname) {
328
		if($captive_portal_interface == strtoupper($ifname))
329
			continue;
330
		$int = convert_friendly_interface_to_real_interface_name($ifname);
331
		$cprules .= "add 30 set 1 skipto 50000 all from any to any in via {$int} keep-state\n";
332
	}
333

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

    
341
EOD;
342
	}
343

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

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

    
356
# PPP Over Ethernet Discovery Stage
357
add 1100 set 1 pass layer2 mac-type 0x8863
358
# PPP Over Ethernet Session Stage
359
add 1100 set 1 pass layer2 mac-type 0x8864
360

    
361
# layer 2: block anything else non-IP
362
add 1101 set 1 deny layer2 not mac-type ip
363
# layer 2: check if MAC addresses of authenticated clients are correct
364
add 1102 set 1 skipto 20000 layer2
365

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

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

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

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

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

    
389
EOD;
390

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

    
398
EOD;
399
	}
400

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

    
407
EOD;
408

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

    
413
EOD;
414

    
415

    
416
    $cprules .= <<<EOD
417

    
418
# ... 10000-19899: rules per authenticated client go here...
419

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

    
427
# ... 20000-29899: layer2 block rules per authenticated client go here...
428

    
429
# pass everything else on layer2
430
add 29900 set 1 pass all from any to any layer2
431

    
432
EOD;
433

    
434
    return $cprules;
435
}
436

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

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

    
444
    global $g, $config;
445

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

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

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

    
460
    captiveportal_lock();
461

    
462
    /* read database */
463
    $cpdb = captiveportal_read_db();
464

    
465
    $radiusservers = captiveportal_get_radius_servers();
466

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

    
474
        $timedout = false;
475
        $term_cause = 1;
476

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

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

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

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

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

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

    
531
            if (isset($config['captiveportal']['radacct_enable'])) {
532
                if ($config['captiveportal']['reauthenticateacct'] == "stopstart") {
533
                    /* stop and restart accounting */
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
                    exec("/sbin/ipfw zero {$cpdb[$i][1]}");
545
                    RADIUS_ACCOUNTING_START($cpdb[$i][1], // ruleno
546
                                            $cpdb[$i][4], // username
547
                                            $cpdb[$i][5], // sessionid
548
                                            $radiusservers[0]['ipaddr'],
549
                                            $radiusservers[0]['acctport'],
550
                                            $radiusservers[0]['key'],
551
                                            $cpdb[$i][2], // clientip
552
                                            $cpdb[$i][3]); // clientmac
553
                } else if ($config['captiveportal']['reauthenticateacct'] == "interimupdate") {
554
                    RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
555
                                           $cpdb[$i][4], // username
556
                                           $cpdb[$i][5], // sessionid
557
                                           $cpdb[$i][0], // start time
558
                                           $radiusservers[0]['ipaddr'],
559
                                           $radiusservers[0]['acctport'],
560
                                           $radiusservers[0]['key'],
561
                                           $cpdb[$i][2], // clientip
562
                                           $cpdb[$i][3], // clientmac
563
                                           10, // NAS Request
564
                                           true); // Interim Updates
565
                }
566
            }
567

    
568
            /* check this user against RADIUS again */
569
            $auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
570
                                          base64_decode($cpdb[$i][6]), // password
571
                                            $radiusservers,
572
                                          $cpdb[$i][2], // clientip
573
                                          $cpdb[$i][3], // clientmac
574
                                          $cpdb[$i][1]); // ruleno
575

    
576
            if ($auth_list['auth_val'] == 3) {
577
                captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
578
                captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
579
                unset($cpdb[$i]);
580
            }
581
        }
582
    }
583

    
584
    /* write database */
585
    captiveportal_write_db($cpdb);
586

    
587
    captiveportal_unlock();
588
}
589

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

    
593
	global $g, $config;
594

    
595
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
596

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

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

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

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

    
628
}
629

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

    
633
	global $g, $config;
634

    
635
	captiveportal_lock();
636

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

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

    
651
	/* write database */
652
	captiveportal_write_db($cpdb);
653

    
654
	captiveportal_unlock();
655
}
656

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

    
661
	if (!isset($config['captiveportal']['radacct_enable']))
662
		return;
663

    
664
	captiveportal_lock();
665
	$cpdb = captiveportal_read_db();
666

    
667
	$radiusservers = captiveportal_get_radius_servers();
668

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

    
686
function captiveportal_passthrumac_configure() {
687
	global $config, $g;
688

    
689
	captiveportal_lock();
690

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

    
694
	if (is_array($config['captiveportal']['passthrumac'])) {
695

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

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

    
708
		fclose($fd);
709
	}
710

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

    
729
	captiveportal_unlock();
730

    
731
	return 0;
732
}
733

    
734
function captiveportal_allowedip_configure() {
735
	global $config, $g;
736

    
737
	captiveportal_lock();
738

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

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

    
761
	if (is_array($config['captiveportal']['allowedip'])) {
762

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

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

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

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

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

    
794
        }
795

    
796
        fclose($fd);
797
    }
798

    
799
    captiveportal_unlock();
800
    return 0;
801
}
802

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

    
806
	$ipfwoutput = "";
807

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

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

    
817
	return 0;
818
}
819

    
820
/* read RADIUS servers into array */
821
function captiveportal_get_radius_servers() {
822

    
823
        global $g;
824

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

    
839
                        return $radiusservers;
840
                }
841
        }
842

    
843
        return false;
844
}
845

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

    
850
        global $lockfile;
851

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

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

    
879
/* unlock captive portal information file */
880
function captiveportal_unlock() {
881

    
882
        global $lockfile;
883

    
884
        if (file_exists($lockfile))
885
                unlink($lockfile);
886
}
887

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

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

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

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

    
917
    $ruleno = captiveportal_get_next_ipfw_ruleno();
918

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

    
928
    $radiusservers = captiveportal_get_radius_servers();
929

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

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

    
950
    return $auth_list;
951

    
952
}
953

    
954
/* read captive portal DB into array */
955
function captiveportal_read_db() {
956

    
957
        global $g;
958

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

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

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

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

    
1029
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 10000, $rulenos_range_max = 9899) {
1030

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

    
1047
	return $ruleno;
1048
}
1049

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

    
1061
function getVolume($ruleno) {
1062

    
1063
    $volume = array();
1064

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

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

    
1076
    // Flush internal buffer
1077
    unset($matches);
1078

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

    
1084
    return $volume;
1085
}
1086

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

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

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

    
1116
function portal_mac_fixed($clientmac) {
1117
	global $g ;
1118

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

    
1137
?>
(5-5/29)