Project

General

Profile

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
65
		/* make sure ipfw is loaded */
66
		mwexec("/sbin/kldload ipfw");
67

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

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

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

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

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

    
116

    
117

    
118
EOD;
119
		}
120

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

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

    
147
EOD;
148
		}
149

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
225
			if ($config['captiveportal']['radiusport'])
226
				$radiusport = $config['captiveportal']['radiusport'];
227
			else
228
				$radiusport = 1812;
229

    
230
			if ($config['captiveportal']['radiusacctport'])
231
				$radiusacctport = $config['captiveportal']['radiusacctport'];
232
			else
233
				$radiusacctport = 1813;
234

    
235
			if ($config['captiveportal']['radiusport2'])
236
				$radiusport2 = $config['captiveportal']['radiusport2'];
237
			else
238
				$radiusport2 = 1812;
239

    
240
			$radiuskey = $config['captiveportal']['radiuskey'];
241
			$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
242

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

    
256
		if ($g['booting'])
257
			echo "done\n";
258

    
259
	} else {
260
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
261
		killbypid("{$g['varrun_path']}/minicron.pid");
262

    
263
		captiveportal_radius_stop_all();
264

    
265
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
266

    
267
		if (!isset($config['shaper']['enable'])) {
268
			/* unload ipfw */
269
			mwexec("/sbin/kldunload ipfw");
270
		} else {
271
			/* shaper is on - just remove our rules */
272
			mwexec("/sbin/ipfw -f delete set 1");
273
			mwexec("/sbin/ipfw -f delete set 2");
274
			mwexec("/sbin/ipfw -f delete set 3");
275
		}
276
	}
277

    
278
	return 0;
279
}
280

    
281
function captiveportal_rules_generate() {
282
	global $config, $g;
283

    
284
	$cpifn = $config['captiveportal']['interface'];
285
	$cpif = $config['interfaces'][$cpifn]['if'];
286
	$cpip = $config['interfaces'][$cpifn]['ipaddr'];
287

    
288
	/* note: the captive portal daemon inserts all pass rules for authenticated
289
	   clients as skipto 50000 rules to make traffic shaping work */
290

    
291
	$cprules = "";
292

    
293
	/*   allow nat redirects to work  see
294
	     http://cvstrac.pfsense.com/tktview?tn=651
295
	 */
296
	$iflist = array("lan" => "LAN", "wan" => "WAN");
297
	$captive_portal_interface = strtoupper($cpifn);
298
	for ($i = 1; isset($config['interfaces']['opt' . $i]); $i++)
299
		$iflist['opt' . $i] = "OPT{$i}";
300
	foreach ($iflist as $ifent => $ifname) {
301
		if($captive_portal_interface == strtoupper($ifname))
302
			continue;
303
		$int = convert_friendly_interface_to_real_interface_name($ifname);
304
		$cprules .= "add 30 set 1 skipto 50000 all from any to any in via {$int} keep-state\n";
305
	}
306

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

    
314
EOD;
315
	}
316

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

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

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

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

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

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

    
349
# allow access to our web server
350
add 1302 set 1 pass tcp from any to $cpip 8000 in
351
add 1303 set 1 pass tcp from $cpip 8000 to any out
352

    
353
EOD;
354

    
355
	if (isset($config['captiveportal']['httpslogin'])) {
356
		$cprules .= <<<EOD
357
add 1304 set 1 pass tcp from any to $cpip 8001 in
358
add 1305 set 1 pass tcp from $cpip 8001 to any out
359

    
360
EOD;
361
	}
362

    
363
        $cprules .= <<<EOD
364
#PPPoE Discovery Stage
365
add 1100 set 1 pass layer2 mac-type 0x8863
366
#PPPoE Session Stage
367
add 1100 set 1 pass layer2 mac-type 0x8864
368

    
369
EOD;
370

    
371
        $cprules .= <<<EOD
372
# Allow WPA
373
add 1100 set 1 pass layer2 mac-type 0x888e
374

    
375
EOD;
376

    
377

    
378
    $cprules .= <<<EOD
379

    
380
# ... 10000-19899: rules per authenticated client go here...
381

    
382
# redirect non-authenticated clients to captive portal
383
add 19902 set 1 fwd 127.0.0.1,8000 tcp from any to any 80 in
384
# let the responses from the captive portal web server back out
385
add 19903 set 1 pass tcp from any 80 to any out
386
# block everything else
387
add 19904 set 1 deny all from any to any
388

    
389
# ... 20000-29899: layer2 block rules per authenticated client go here...
390

    
391
# pass everything else on layer2
392
add 29900 set 1 pass all from any to any layer2
393

    
394
EOD;
395

    
396
    return $cprules;
397
}
398

    
399
/* remove clients that have been around for longer than the specified amount of time */
400
/* db file structure:
401
timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time */
402

    
403
/* (password is in Base64 and only saved when reauthentication is enabled) */
404
function captiveportal_prune_old() {
405

    
406
    global $g, $config;
407

    
408
    /* check for expired entries */
409
    if ($config['captiveportal']['timeout'])
410
        $timeout = $config['captiveportal']['timeout'] * 60;
411
    else
412
        $timeout = 0;
413

    
414
    if ($config['captiveportal']['idletimeout'])
415
        $idletimeout = $config['captiveportal']['idletimeout'] * 60;
416
    else
417
        $idletimeout = 0;
418

    
419
    if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && !isset($config['captiveportal']['radiussession_timeout']))
420
        return;
421

    
422
    captiveportal_lock();
423

    
424
    /* read database */
425
    $cpdb = captiveportal_read_db();
426

    
427
    $radiusservers = captiveportal_get_radius_servers();
428

    
429
    for ($i = 0; $i < count($cpdb); $i++) {
430

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

    
434
        /* hard timeout? */
435
        if ($timeout) {
436
            if ((time() - $cpdb[$i][0]) >= $timeout) {
437
                $timedout = true;
438
                $term_cause = 5; // Session-Timeout
439
            }
440
        }
441

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

    
450
        /* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
451
        $idletimeout = (is_numeric($cpdb[$i][8])) ? $cpdb[$i][8] : $idletimeout;
452
        /* if an idle timeout is specified, get last activity timestamp from ipfw */
453
        if (!$timedout && $idletimeout) {
454
            $lastact = captiveportal_get_last_activity($cpdb[$i][1]);
455
            if ($lastact && ((time() - $lastact) >= $idletimeout)) {
456
                $timedout = true;
457
                $term_cause = 4; // Idle-Timeout
458
                $stop_time = $lastact; // Entry added to comply with WISPr
459
            }
460
        }
461

    
462
        /* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
463
        if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpdb[$i][7])) {
464
            if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
465
                $timedout = true;
466
                $term_cause = 5; // Session-Timeout
467
            }
468
        }
469

    
470
        if ($timedout) {
471
            captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
472
            captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
473
            unset($cpdb[$i]);
474
        }
475

    
476
        /* do periodic RADIUS reauthentication? */
477
        if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
478
            ($radiusservers !== false)) {
479

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

    
517
            /* check this user against RADIUS again */
518
            $auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
519
                                          base64_decode($cpdb[$i][6]), // password
520
                                            $radiusservers,
521
                                          $cpdb[$i][2], // clientip
522
                                          $cpdb[$i][3], // clientmac
523
                                          $cpdb[$i][1]); // ruleno
524

    
525
            if ($auth_list['auth_val'] == 3) {
526
                captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
527
                captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
528
                unset($cpdb[$i]);
529
            }
530
        }
531
    }
532

    
533
    /* write database */
534
    captiveportal_write_db($cpdb);
535

    
536
    captiveportal_unlock();
537
}
538

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

    
542
	global $g, $config;
543

    
544
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
545

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

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

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

    
573
	/* pfSense: ensure all pf states are killed (pfSense) */
574
	mwexec("pfctl -k {$dbent[2]}");
575

    
576
}
577

    
578
/* remove a single client by ipfw rule number */
579
function captiveportal_disconnect_client($id,$term_cause = 1) {
580

    
581
	global $g, $config;
582

    
583
	captiveportal_lock();
584

    
585
	/* read database */
586
	$cpdb = captiveportal_read_db();
587
	$radiusservers = captiveportal_get_radius_servers();
588

    
589
	/* find entry */
590
	for ($i = 0; $i < count($cpdb); $i++) {
591
		if ($cpdb[$i][1] == $id) {
592
			captiveportal_disconnect($cpdb[$i], $radiusservers, $term_cause);
593
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "DISCONNECT");
594
			unset($cpdb[$i]);
595
			break;
596
		}
597
	}
598

    
599
	/* write database */
600
	captiveportal_write_db($cpdb);
601

    
602
	captiveportal_unlock();
603
}
604

    
605
/* send RADIUS acct stop for all current clients */
606
function captiveportal_radius_stop_all() {
607
	global $g, $config;
608

    
609
	if (!isset($config['captiveportal']['radacct_enable']))
610
		return;
611

    
612
	captiveportal_lock();
613
	$cpdb = captiveportal_read_db();
614

    
615
	$radiusservers = captiveportal_get_radius_servers();
616

    
617
	if (isset($radiusservers[0])) {
618
		for ($i = 0; $i < count($cpdb); $i++) {
619
			RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
620
								   $cpdb[$i][4], // username
621
								   $cpdb[$i][5], // sessionid
622
								   $cpdb[$i][0], // start time
623
								   $radiusservers[0]['ipaddr'],
624
								   $radiusservers[0]['acctport'],
625
								   $radiusservers[0]['key'],
626
								   $cpdb[$i][2], // clientip
627
								   $cpdb[$i][3], // clientmac
628
								   7); // Admin Reboot
629
		}
630
	}
631
	captiveportal_unlock();
632
}
633

    
634
function captiveportal_passthrumac_configure() {
635
	global $config, $g;
636

    
637
	captiveportal_lock();
638

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

    
642
	if (is_array($config['captiveportal']['passthrumac'])) {
643

    
644
		$fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db", "w");
645
		if (!$fd) {
646
			printf("Error: cannot open passthru mac DB file in captiveportal_passthrumac_configure().\n");
647
			captiveportal_unlock();
648
			return 1;
649
		}
650

    
651
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
652
			/* record passthru mac so it can be recognized and let thru */
653
			fwrite($fd, $macent['mac'] . "\n");
654
		}
655

    
656
		fclose($fd);
657
	}
658

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

    
677
	captiveportal_unlock();
678

    
679
	return 0;
680
}
681

    
682
function captiveportal_allowedip_configure() {
683
	global $config, $g;
684

    
685
	captiveportal_lock();
686

    
687
	/* clear out existing allowed ips, if necessary */
688
	if (file_exists("{$g['vardb_path']}/captiveportal_ip.db")) {
689
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "r");
690
		if ($fd) {
691
			while (!feof($fd)) {
692
				$line = trim(fgets($fd));
693
				if ($line) {
694
					list($ip,$rule) = explode(",",$line);
695
					mwexec("/sbin/ipfw delete $rule");
696
				}
697
			}
698
		}
699
		fclose($fd);
700
		unlink("{$g['vardb_path']}/captiveportal_ip.db");
701
	}
702

    
703
	/* get next ipfw rule number */
704
	if (file_exists("{$g['vardb_path']}/captiveportal.nextrule"))
705
		$ruleno = trim(file_get_contents("{$g['vardb_path']}/captiveportal.nextrule"));
706
	if (!$ruleno)
707
		$ruleno = 10000;	/* first rule number */
708

    
709
	if (is_array($config['captiveportal']['allowedip'])) {
710

    
711
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "w");
712
		if (!$fd) {
713
			printf("Error: cannot open allowed ip DB file in captiveportal_allowedip_configure().\n");
714
			captiveportal_unlock();
715
			return 1;
716
		}
717

    
718
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
719
            /* get next ipfw rule number */
720
            $ruleno = captiveportal_get_next_ipfw_ruleno();
721

    
722
            /* if the pool is empty, return apprioriate message and fail */
723
            if (is_null($ruleno)) {
724
                printf("Error: system reached maximum login capacity, no free FW rulenos in captiveportal_allowedip_configure().\n");
725
                fclose($fd);
726
                captiveportal_unlock();
727
                return 1;
728
            }
729

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

    
733
            /* insert ipfw rule to allow ip thru */
734
            if ($ipent['dir'] == "from") {
735
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any in");
736
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " out");
737
            } else {
738
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to " . $ipent['ip'] . " in");
739
                mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from " . $ipent['ip'] . " to any out");
740
            }
741

    
742
        }
743

    
744
        fclose($fd);
745
    }
746

    
747
    captiveportal_unlock();
748
    return 0;
749
}
750

    
751
/* get last activity timestamp given ipfw rule number */
752
function captiveportal_get_last_activity($ruleno) {
753

    
754
	$ipfwoutput = "";
755

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

    
758
	/* in */
759
	if ($ipfwoutput[0]) {
760
		$ri = explode(" ", $ipfwoutput[0]);
761
		if ($ri[1])
762
			return $ri[1];
763
	}
764

    
765
	return 0;
766
}
767

    
768
/* read RADIUS servers into array */
769
function captiveportal_get_radius_servers() {
770

    
771
        global $g;
772

    
773
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
774
                $fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db","r");
775
                if ($fd) {
776
                        $radiusservers = array();
777
                        while (!feof($fd)) {
778
                                $line = trim(fgets($fd));
779
                                if ($line) {
780
                                        $radsrv = array();
781
                                        list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
782
                                        $radiusservers[] = $radsrv;
783
                                }
784
                        }
785
                        fclose($fd);
786

    
787
                        return $radiusservers;
788
                }
789
        }
790

    
791
        return false;
792
}
793

    
794
/* lock captive portal information, decide that the lock file is stale after
795
   10 seconds */
796
function captiveportal_lock() {
797

    
798
        global $lockfile;
799

    
800
        $n = 0;
801
        while ($n < 10) {
802
                /* open the lock file in append mode to avoid race condition */
803
                if ($fd = @fopen($lockfile, "x")) {
804
                        /* succeeded */
805
                        fclose($fd);
806
                        return;
807
                } else {
808
                        /* file locked, wait and try again */
809
                        sleep(1);
810
                        $n++;
811
                }
812
        }
813
}
814

    
815
/* unlock captive portal information file */
816
function captiveportal_unlock() {
817

    
818
        global $lockfile;
819

    
820
        if (file_exists($lockfile))
821
                unlink($lockfile);
822
}
823

    
824
/* log successful captive portal authentication to syslog */
825
/* part of this code from php.net */
826
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
827
	define_syslog_variables();
828
	$message = trim($message);
829
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
830
	// Log it
831
	if (!$message)
832
	syslog(LOG_INFO, "$status: $user, $mac, $ip");
833
	else
834
	syslog(LOG_INFO, "$status: $user, $mac, $ip, $message");
835
	closelog();
836
}
837

    
838
function radius($username,$password,$clientip,$clientmac,$type) {
839
    global $g, $config;
840

    
841
    /* Start locking from the beginning of an authentication session */
842
    captiveportal_lock();
843

    
844
    $ruleno = captiveportal_get_next_ipfw_ruleno();
845

    
846
    /* if the pool is empty, return apprioriate message and fail authentication */
847
    if (is_null($ruleno)) {
848
        $auth_list = array();
849
        $auth_list['auth_val'] = 1;
850
        $auth_list['error'] = "System reached maximum login capacity";
851
        captiveportal_unlock();
852
        return $auth_list;
853
    }
854

    
855
    $radiusservers = captiveportal_get_radius_servers();
856

    
857
    $auth_list = RADIUS_AUTHENTICATION($username,
858
                    $password,
859
                    $radiusservers,
860
                    $clientip,
861
                    $clientmac,
862
                    $ruleno);
863

    
864
    if ($auth_list['auth_val'] == 2) {
865
        captiveportal_logportalauth($username,$clientmac,$clientip,$type);
866
        $sessionid = portal_allow($clientip,
867
                    $clientmac,
868
                    $username,
869
                    $password,
870
                    $auth_list,
871
                    $ruleno);
872
    }
873
    else {
874
        captiveportal_unlock();
875
    }
876

    
877
    return $auth_list;
878

    
879
}
880

    
881
/* read captive portal DB into array */
882
function captiveportal_read_db() {
883

    
884
        global $g;
885

    
886
        $cpdb = array();
887
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
888
        if ($fd) {
889
                while (!feof($fd)) {
890
                        $line = trim(fgets($fd));
891
                        if ($line) {
892
                                $cpdb[] = explode(",", $line);
893
                        }
894
                }
895
                fclose($fd);
896
        }
897
        return $cpdb;
898
}
899

    
900
/* write captive portal DB */
901
function captiveportal_write_db($cpdb) {
902
                 
903
        global $g;
904
                
905
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
906
        if ($fd) { 
907
                foreach ($cpdb as $cpent) {
908
                        fwrite($fd, join(",", $cpent) . "\n");
909
                }       
910
                fclose($fd);
911
        }       
912
}
913

    
914
function captiveportal_write_elements() {
915
    global $g, $config;
916
    
917
    /* delete any existing elements */
918
    if (is_dir($g['captiveportal_element_path'])) {
919
        $dh = opendir($g['captiveportal_element_path']);
920
        while (($file = readdir($dh)) !== false) {
921
            if ($file != "." && $file != "..")
922
                unlink($g['captiveportal_element_path'] . "/" . $file);
923
        }
924
        closedir($dh);
925
    } else {
926
        mkdir($g['captiveportal_element_path']);
927
    }
928
    
929
    if (is_array($config['captiveportal']['element'])) {
930
    
931
        foreach ($config['captiveportal']['element'] as $data) {
932
            $fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
933
            if (!$fd) {
934
                printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
935
                return 1;
936
            }
937
            $decoded = base64_decode($data['content']);
938
            fwrite($fd,$decoded);
939
            fclose($fd);
940
        }
941
    }
942
    
943
    return 0;
944
}
945

    
946
/*
947
 * This function will calculate the lowest free firewall ruleno
948
 * within the range specified based on the actual installed rules
949
 *
950
 */
951

    
952
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 10000, $rulenos_range_max = 9899) {
953

    
954
	$fwrules = "";
955
	$matches = "";
956
	exec("/sbin/ipfw show", $fwrules);
957
	foreach ($fwrules as $fwrule) {
958
		preg_match("/^(\d+)\s+/", $fwrule, $matches);
959
		$rulenos_used[] = $matches[1];
960
	}
961
	$rulenos_used = array_unique($rulenos_used);
962
	$rulenos_range = count($rulenos_used);
963
	if ($rulenos_range > $rulenos_range_max) {
964
		return NULL;
965
	}
966
	$rulenos_pool = range($rulenos_start, ($rulenos_start + $rulenos_range));
967
	$rulenos_free = array_diff($rulenos_pool, $rulenos_used);
968
	$ruleno = array_shift($rulenos_free);
969

    
970
	return $ruleno;
971
}
972

    
973
/**
974
 * This function will calculate the traffic produced by a client
975
 * based on its firewall rule
976
 *
977
 * Point of view: NAS
978
 *
979
 * Input means: from the client
980
 * Output means: to the client
981
 *
982
 */
983

    
984
function getVolume($ruleno) {
985

    
986
    $volume = array();
987

    
988
    // Initialize vars properly, since we don't want NULL vars
989
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
990

    
991
    // Ingress
992
    $ipfw = "";
993
    $matches = "";
994
    exec("/sbin/ipfw show {$ruleno}", $ipfw);
995
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[0], $matches);
996
    $volume['input_pkts'] = $matches[2];
997
    $volume['input_bytes'] = $matches[3];
998

    
999
    // Flush internal buffer
1000
    unset($matches);
1001

    
1002
    // Outgress
1003
    preg_match("/(\d+)\s+(\d+)\s+(\d+)\s+.*/", $ipfw[1], $matches);
1004
    $volume['output_pkts'] = $matches[2];
1005
    $volume['output_bytes'] = $matches[3];
1006

    
1007
    return $volume;
1008
}
1009

    
1010
/**
1011
 * Get the NAS-Identifier
1012
 *
1013
 * We will use our local hostname to make up the nas_id
1014
 */
1015
function getNasID()
1016
{
1017
    $nasId = "";
1018
    exec("/bin/hostname", $nasId);
1019
    if(!$nasId[0])
1020
        $nasId[0] = "pfSense";
1021
    return $nasId[0];
1022
}
1023

    
1024
/**
1025
 * Get the NAS-IP-Address based on the current wan address
1026
 *
1027
 * Use functions in interfaces.inc to find this out
1028
 *
1029
 */
1030

    
1031
function getNasIP()
1032
{
1033
    $nasIp = get_current_wan_address();
1034
    if(!$nasIp)
1035
        $nasIp = "0.0.0.0";
1036
    return $nasIp;
1037
}
1038

    
1039
?>
(4-4/27)