Project

General

Profile

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

    
6
	Copyright (C) 2009 Ermal Lu?i
7
	Copyright (C) 2003-2006 Manuel Kasper <mk@neon1.net>.
8
	All rights reserved.
9

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

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

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

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

    
31
	This version of captiveportal.inc has been modified by Rob Parker
32
	<rob.parker@keycom.co.uk> to include changes for per-user bandwidth management
33
	via returned RADIUS attributes. This page has been modified to delete any
34
	added rules which may have been created by other per-user code (index.php, etc).
35
	These changes are (c) 2004 Keycom PLC.
36
	
37
	pfSense_BUILDER_BINARIES:	/sbin/ifconfig	/sbin/ipfw	/sbin/sysctl	/sbin/kldunload
38
	pfSense_BUILDER_BINARIES:	/usr/local/sbin/lighttpd	/usr/local/bin/minicron	/sbin/pfctl
39
	pfSense_BUILDER_BINARIES:	/bin/hostname	/bin/cp	
40
	pfSense_MODULE:	captiveportal
41
*/
42

    
43
/* include all configuration functions */
44
require_once("radius_authentication.inc");
45
require_once("radius_accounting.inc");
46
require_once("radius.inc");
47
require_once("voucher.inc");
48

    
49
function captiveportal_configure() {
50
	global $config, $g;
51

    
52
	$captiveportallck = lock('captiveportal');
53
	
54
	$cpactive = false;
55
	if (isset($config['captiveportal']['enable'])) {
56
		$cpips = array();
57
		$ifaces = get_configured_interface_list();
58
		foreach ($ifaces as $kiface => $kiface2) {
59
			$tmpif = get_real_interface($kiface);
60
			mwexec("/sbin/ifconfig {$tmpif} -ipfwfilter");
61
		}
62
		$cpinterfaces = explode(",", $config['captiveportal']['interface']);
63
		$firsttime = 0;
64
		foreach ($cpinterfaces as $cpifgrp) {
65
			if (!isset($ifaces[$cpifgrp]))
66
				continue;
67
			$tmpif = get_real_interface($cpifgrp);
68
			if (!empty($tmpif)) {
69
				if ($firsttime > 0)
70
					$cpinterface .= " or ";
71
				$cpinterface .= "via {$tmpif}"; 
72
				$firsttime = 1;
73
				$cpipm = get_interface_ip($cpifgrp);
74
				if (is_ipaddr($cpipm)) {
75
					$carpif = link_ip_to_carp_interface($cpipm);
76
					if (!empty($carpif)) {
77
						$carpsif = explode(" ", $carpif);
78
						foreach ($carpsif as $cpcarp) {
79
							mwexec("/sbin/ifconfig {$cpcarp} ipfwfilter");
80
							$carpip = find_interface_ip($cpcarp);
81
							if (is_ipaddr($carpip))
82
								$cpips[] = $carpip;
83
						}
84
					}
85
					$cpips[] = $cpipm;
86
					mwexec("/sbin/ifconfig {$tmpif} ipfwfilter");
87
				}
88
			}
89
		}
90
		if (count($cpips) > 0) {
91
			$cpactive = true;
92
			$cpinterface = "{ {$cpinterface} } ";
93
		}
94
	}
95

    
96
	if ($cpactive == true) {
97

    
98
		if ($g['booting'])
99
			echo "Starting captive portal... ";
100

    
101
		/* kill any running mini_httpd */
102
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
103
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal-SSL.pid");
104

    
105
		/* kill any running minicron */
106
		killbypid("{$g['varrun_path']}/minicron.pid");
107

    
108
		/* make sure ipfw is loaded */
109
		if (!is_module_loaded("ipfw.ko"))
110
			filter_load_ipfw();
111
		if (isset($config['captiveportal']['peruserbw']) && !is_module_loaded("dummynet.ko"))
112
                        mwexec("/sbin/kldload dummynet");
113

    
114
		/* generate ipfw rules */
115
		$cprules = captiveportal_rules_generate($cpinterface, $cpips);
116

    
117
		/* stop accounting on all clients */
118
		captiveportal_radius_stop_all(true);
119

    
120
		/* initialize minicron interval value */
121
		$croninterval = $config['captiveportal']['croninterval'] ? $config['captiveportal']['croninterval'] : 60;
122

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

    
126
		/* remove old information */
127
		unlink_if_exists("{$g['vardb_path']}/captiveportal.nextrule");
128
		unlink_if_exists("{$g['vardb_path']}/captiveportal.db");
129
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
130
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip.db");
131
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius.db");
132
		mwexec("/sbin/ipfw table all flush");
133

    
134
		/* setup new database in case someone tries to access the status -> captive portal page */
135
		touch("{$g['vardb_path']}/captiveportal.db");
136

    
137
		/* write portal page */
138
		if ($config['captiveportal']['page']['htmltext'])
139
			$htmltext = base64_decode($config['captiveportal']['page']['htmltext']);
140
		else {
141
			/* example/template page */
142
			$htmltext = <<<EOD
143
<html>
144
<head>
145
<title>{$g['product_name']} captive portal</title>
146
</head>
147
<body>
148
<center>
149
<h2>{$g['product_name']} captive portal</h2>
150
Welcome to the {$g['product_name']} Captive Portal!  This is the default page since a custom page has not been defined.
151
<p>
152
<form method="post" action="\$PORTAL_ACTION\$">
153
<input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$">
154
<table>
155
   <tr><td>Username:</td><td><input name="auth_user" type="text"></td></tr>
156
   <tr><td>Password:</td><td><input name="auth_pass" type="password"></td></tr>
157
   <tr><td>&nbsp;</td></tr>
158
   <tr>
159
     <td colspan="2">
160
	<center><input name="accept" type="submit" value="Continue"></center>
161
     </td>
162
   </tr>
163
</table>
164
</center>
165
</form>
166
</body>
167
</html>
168

    
169

    
170

    
171
EOD;
172
		}
173

    
174
		$fd = @fopen("{$g['varetc_path']}/captiveportal.html", "w");
175
		if ($fd) {
176
			fwrite($fd, $htmltext);
177
			fclose($fd);
178
		}
179

    
180
		/* write error page */
181
		if ($config['captiveportal']['page']['errtext'])
182
			$errtext = base64_decode($config['captiveportal']['page']['errtext']);
183
		else {
184
			/* example page */
185
			$errtext = <<<EOD
186
<html>
187
<head>
188
<title>Authentication error</title>
189
</head>
190
<body>
191
<font color="#cc0000"><h2>Authentication error</h2></font>
192
<b>
193
Username and/or password invalid.
194
<br><br>
195
<a href="javascript:history.back(); ">Go back</a>
196
</b>
197
</body>
198
</html>
199

    
200
EOD;
201
		}
202

    
203
		$fd = @fopen("{$g['varetc_path']}/captiveportal-error.html", "w");
204
		if ($fd) {
205
			fwrite($fd, $errtext);
206
			fclose($fd);
207
		}
208

    
209
		/* write elements */
210
		captiveportal_write_elements();
211

    
212
		/* load rules */
213
		mwexec("/sbin/ipfw -f delete set 1");
214

    
215
		/* ipfw cannot accept rules directly on stdin,
216
		   so we have to write them to a temporary file first */
217
		$fd = @fopen("{$g['tmp_path']}/ipfw.cp.rules", "w");
218
		if (!$fd) {
219
			printf("Cannot open ipfw.cp.rules in captiveportal_configure()\n");
220
			return 1;
221
		}
222

    
223
		fwrite($fd, $cprules);
224
		fclose($fd);
225

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

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

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

    
233
		chdir($g['captiveportal_path']);
234

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

    
240
		$use_fastcgi = true;
241

    
242
		if(isset($config['captiveportal']['httpslogin'])) {
243
			$cert = base64_decode($config['captiveportal']['certificate']);
244
			$key = base64_decode($config['captiveportal']['private-key']);
245
			/* generate lighttpd configuration */
246
			system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal-SSL.conf",
247
				$cert, $key, "", "lighty-CaptivePortal-ssl.pid", "8001", "/usr/local/captiveportal/",
248
					"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
249
		}
250

    
251
		/* generate lighttpd configuration */
252
		system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal.conf",
253
			"", "", "", "lighty-CaptivePortal.pid", "8000", "/usr/local/captiveportal/",
254
				"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
255

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

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

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

    
267
		/* generate passthru mac database */
268
		captiveportal_passthrumac_configure(true);
269
		/* allowed ipfw rules to make allowed ip work */
270
		captiveportal_allowedip_configure();
271

    
272
		/* generate radius server database */
273
		if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
274
				($config['captiveportal']['auth_method'] == "radius"))) {
275
			$radiusip = $config['captiveportal']['radiusip'];
276
			$radiusip2 = ($config['captiveportal']['radiusip2']) ? $config['captiveportal']['radiusip2'] : null;
277

    
278
			if ($config['captiveportal']['radiusport'])
279
				$radiusport = $config['captiveportal']['radiusport'];
280
			else
281
				$radiusport = 1812;
282

    
283
			if ($config['captiveportal']['radiusacctport'])
284
				$radiusacctport = $config['captiveportal']['radiusacctport'];
285
			else
286
				$radiusacctport = 1813;
287

    
288
			if ($config['captiveportal']['radiusport2'])
289
				$radiusport2 = $config['captiveportal']['radiusport2'];
290
			else
291
				$radiusport2 = 1812;
292

    
293
			$radiuskey = $config['captiveportal']['radiuskey'];
294
			$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
295

    
296
			$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db", "w");
297
			if (!$fd) {
298
				printf("Error: cannot open radius DB file in captiveportal_configure().\n");
299
				return 1;
300
			} else if (isset($radiusip2, $radiuskey2)) {
301
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . "\n"
302
					 . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2);
303
			} else {
304
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey);
305
			}
306
			fclose($fd);
307
		}
308

    
309
		if ($g['booting'])
310
			echo "done\n";
311

    
312
	} else {
313
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
314
		killbypid("{$g['varrun_path']}/minicron.pid");
315

    
316
		captiveportal_radius_stop_all(true);
317

    
318
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
319

    
320
		/* unload ipfw */
321
		if (is_module_loaded("ipfw.ko"))		
322
			mwexec("/sbin/kldunload ipfw.ko");
323
		$listifs = get_configured_interface_list_by_realif();
324
		foreach ($listifs as $listrealif => $listif) {
325
			mwexec("/sbin/ifconfig {$listrealif} -ipfwfilter");
326
			$carpif = link_ip_to_carp_interface(find_interface_ip($listrealif));
327
                        if (!empty($carpif)) {
328
				$carpsif = explode(" ", $carpif);
329
				foreach ($carpsif as $cpcarp)
330
					mwexec("/sbin/ifconfig {$cpcarp} -ipfwfilter");
331
			}
332
		}
333
	}
334

    
335
	unlock($captiveportallck);
336
	
337
	return 0;
338
}
339

    
340
function captiveportal_rules_generate($cpif, &$cpiparray) {
341
	global $config, $g;
342

    
343
	$cpifn = $config['captiveportal']['interface'];
344
	$lanip = get_interface_ip("lan");
345
	
346
	/* note: the captive portal daemon inserts all pass rules for authenticated
347
	   clients as skipto 50000 rules to make traffic shaping work */
348

    
349
	$cprules =  "add 500 set 1 allow pfsync from any to any\n";
350
	$cprules .= "add 500 set 1 allow carp from any to any\n";
351

    
352
	$cprules .= <<<EOD
353
add 1000 set 1 skipto 1150 all from any to any not layer2
354
# layer 2: pass ARP
355
add 1100 set 1 pass layer2 mac-type arp
356
# pfsense requires for WPA
357
add 1100 set 1 pass layer2 mac-type 0x888e
358
add 1100 set 1 pass layer2 mac-type 0x88c7
359

    
360
# PPP Over Ethernet Discovery Stage
361
add 1100 set 1 pass layer2 mac-type 0x8863
362
# PPP Over Ethernet Session Stage
363
add 1100 set 1 pass layer2 mac-type 0x8864
364
# Allow WPA
365
add 1100 set 1 pass layer2 mac-type 0x888e
366

    
367
# layer 2: block anything else non-IP
368
add 1101 set 1 deny layer2 not mac-type ip
369

    
370
EOD;
371

    
372
	$rulenum = 1150;
373
	foreach ($cpiparray as $cpip) {
374
		//# allow access to our DHCP server (which needs to be able to ping clients as well)
375
		$cprules .= "add {$rulenum} set 1 pass udp from any 68 to 255.255.255.255 67 in \n";
376
		$rulenum++;
377
		$cprules .= "add {$rulenum} set 1 pass udp from any 68 to {$cpip} 67 in \n";
378
		$rulenum++;
379
		$cprules .= "add {$rulenum} set 1 pass udp from {$cpip} 67 to any 68 out \n";
380
		$rulenum++;
381
		$cprules .= "add {$rulenum} set 1 pass icmp from {$cpip} to any out icmptype 8\n";
382
		$rulenum++;
383
		$cprules .= "add {$rulenum} set 1 pass icmp from any to {$cpip} in icmptype 0 \n";
384
		$rulenum++;
385
		//# allow access to our DNS forwarder
386
		$cprules .= "add {$rulenum} set 1 pass udp from {$cpip} to any 53 in \n";
387
		$rulenum++;
388
		$cprules .= "add {$rulenum} set 1 pass udp from any to {$cpip} 53 in \n";
389
		$rulenum++;
390
		$cprules .= "add {$rulenum} set 1 pass udp from {$cpip} 53 to any out \n";
391
		$rulenum++;
392
		# allow access to our web server
393
		$cprules .= "add {$rulenum} set 1 pass tcp from any to {$cpip} 8000 in \n";
394
		$rulenum++;
395
		$cprules .= "add {$rulenum} set 1 pass tcp from {$cpip} 8000 to any out \n";
396

    
397
		if (isset($config['captiveportal']['httpslogin'])) {
398
			$rulenum++;
399
			$cprules .= "add {$rulenum} set 1 pass tcp from any to {$cpip} 8001 in \n";
400
			$rulenum++;
401
			$cprules .= "add {$rulenum} set 1 pass tcp from {$cpip} 8001 to any out \n";
402
		}
403
	}
404
	$rulenum++;
405

    
406
	if (isset($config['captiveportal']['peruserbw'])) {
407
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from table(3) to any in\n";
408
		$rulenum++;
409
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from any to table(4) out\n";
410
		$rulenum++;
411
	} else {
412
		$cprules .= "add {$rulenum} set 1 skipto 50000 ip from table(3) to any in\n";
413
                $rulenum++;
414
                $cprules .= "add {$rulenum} set 1 skipto 50000 ip from any to table(4) out\n";
415
                $rulenum++;
416
	}
417
	
418
       $cprules .= <<<EOD
419

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

    
427
# ... 2000-49899: layer2 block rules per authenticated client go here...
428

    
429
# pass everything else on layer2
430
add 49900 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']) && 
458
		!isset($config['captiveportal']['radiussession_timeout']) && !isset($config['voucher']['enable']))
459
        return;
460

    
461
    $captiveportallck = lock('captiveportal');
462

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

    
466
    $radiusservers = captiveportal_get_radius_servers();
467

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

    
477
        $timedout = false;
478
        $term_cause = 1;
479

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

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

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

    
516
	/* if vouchers are configured, activate session timeouts */
517
	if (!$timedout && isset($config['voucher']['enable']) && !empty($cpdb[$i][7])) {
518
		if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
519
			$timedout = true;
520
			$term_cause = 5; // Session-Timeout
521
		}
522
	}
523

    
524
        /* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
525
        if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpdb[$i][7])) {
526
            if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
527
                $timedout = true;
528
                $term_cause = 5; // Session-Timeout
529
            }
530
        }
531

    
532
        if ($timedout) {
533
            captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
534
            captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
535
	    $unsetindexes[$i] = $i;
536
        }
537

    
538
        /* do periodic RADIUS reauthentication? */
539
        if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
540
            !empty($radiusservers)) {
541

    
542
            if (isset($config['captiveportal']['radacct_enable'])) {
543
                if ($config['captiveportal']['reauthenticateacct'] == "stopstart") {
544
                    /* stop and restart accounting */
545
                    RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
546
                                           $cpdb[$i][4], // username
547
                                           $cpdb[$i][5], // sessionid
548
                                           $cpdb[$i][0], // start time
549
                                           $radiusservers,
550
                                           $cpdb[$i][2], // clientip
551
                                           $cpdb[$i][3], // clientmac
552
                                           10); // NAS Request
553
                    exec("/sbin/ipfw table 3 entryzerostats {$cpdb[$i][2]}");
554
                    exec("/sbin/ipfw table 4 entryzerostats {$cpdb[$i][2]}");
555
                    RADIUS_ACCOUNTING_START($cpdb[$i][1], // ruleno
556
                                            $cpdb[$i][4], // username
557
                                            $cpdb[$i][5], // sessionid
558
                                            $radiusservers,
559
                                            $cpdb[$i][2], // clientip
560
                                            $cpdb[$i][3]); // clientmac
561
                } else if ($config['captiveportal']['reauthenticateacct'] == "interimupdate") {
562
                    RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
563
                                           $cpdb[$i][4], // username
564
                                           $cpdb[$i][5], // sessionid
565
                                           $cpdb[$i][0], // start time
566
                                           $radiusservers,
567
                                           $cpdb[$i][2], // clientip
568
                                           $cpdb[$i][3], // clientmac
569
                                           10, // NAS Request
570
                                           true); // Interim Updates
571
                }
572
            }
573

    
574
            /* check this user against RADIUS again */
575
            $auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
576
                                          base64_decode($cpdb[$i][6]), // password
577
                                            $radiusservers,
578
                                          $cpdb[$i][2], // clientip
579
                                          $cpdb[$i][3], // clientmac
580
                                          $cpdb[$i][1]); // ruleno
581

    
582
            if ($auth_list['auth_val'] == 3) {
583
                captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
584
                captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
585
	        $unsetindexes[$i] = $i;
586
            }
587
        }
588
    }
589
    /* This is a kludge to overcome some php weirdness */
590
    foreach($unsetindexes as $unsetindex)
591
	unset($cpdb[$unsetindex]);
592

    
593
    /* write database */
594
    captiveportal_write_db($cpdb);
595

    
596
    unlock($captiveportallck);
597
}
598

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

    
602
	global $g, $config;
603

    
604
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
605

    
606
	/* this client needs to be deleted - remove ipfw rules */
607
	if (isset($config['captiveportal']['radacct_enable']) && !empty($radiusservers)) {
608
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
609
							   $dbent[4], // username
610
							   $dbent[5], // sessionid
611
							   $dbent[0], // start time
612
							   $radiusservers,
613
							   $dbent[2], // clientip
614
							   $dbent[3], // clientmac
615
							   $term_cause, // Acct-Terminate-Cause
616
							   false,
617
							   $stop_time);
618
	}
619
	/* Delete client's ip entry from tables 3 and 4. */
620
	mwexec("/sbin/ipfw table 3 delete {$dbent[2]}");
621
	mwexec("/sbin/ipfw table 4 delete {$dbent[2]}");
622

    
623
	/* 
624
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
625
	* We could get an error if the pipe doesn't exist but everything should still be fine
626
	*/
627
	if (isset($config['captiveportal']['peruserbw'])) {
628
		mwexec("/sbin/ipfw pipe " . ($dbent[1]+20000) . " delete");
629
		mwexec("/sbin/ipfw pipe " . ($dbent[1]+20001) . " delete");
630
	}
631

    
632
	/* Ensure all pf(4) states are killed. */
633
	mwexec("pfctl -k {$dbent[2]}");
634
	mwexec("pfctl -K {$dbent[2]}");
635

    
636
}
637

    
638
/* remove a single client by ipfw rule number */
639
function captiveportal_disconnect_client($id,$term_cause = 1) {
640

    
641
	global $g, $config;
642

    
643
	$captiveportallck = lock('captiveportal');
644

    
645
	/* read database */
646
	$cpdb = captiveportal_read_db();
647
	$radiusservers = captiveportal_get_radius_servers();
648

    
649
	/* find entry */
650
	$tmpindex = 0;
651
	for ($i = 0; $i < count($cpdb); $i++) {
652
		if ($cpdb[$i][1] == $id) {
653
			captiveportal_disconnect($cpdb[$i], $radiusservers, $term_cause);
654
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "DISCONNECT");
655
			unset($cpdb[$i]);
656
			break;
657
		}
658
	}		
659

    
660
	/* write database */
661
	captiveportal_write_db($cpdb);
662

    
663
	unlock($captiveportallck);
664
}
665

    
666
/* send RADIUS acct stop for all current clients */
667
function captiveportal_radius_stop_all($lock = false) {
668
	global $g, $config;
669

    
670
	if (!isset($config['captiveportal']['radacct_enable']))
671
		return;
672

    
673
	if (!$lock)
674
		$captiveportallck = lock('captiveportal');
675

    
676
	$cpdb = captiveportal_read_db();
677

    
678
	$radiusservers = captiveportal_get_radius_servers();
679
	if (!empty($radiusservers)) {
680
		for ($i = 0; $i < count($cpdb); $i++) {
681
			RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
682
								   $cpdb[$i][4], // username
683
								   $cpdb[$i][5], // sessionid
684
								   $cpdb[$i][0], // start time
685
								   $radiusservers,
686
								   $cpdb[$i][2], // clientip
687
								   $cpdb[$i][3], // clientmac
688
								   7); // Admin Reboot
689
		}
690
	}
691
	if (!$lock)
692
		unlock($captiveportallck);
693
}
694

    
695
function captiveportal_passthrumac_configure($lock = false) {
696
	global $config, $g;
697

    
698
	if (!$lock)
699
		$captiveportallck = lock('captiveportal');
700

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

    
704
	if (is_array($config['captiveportal']['passthrumac'])) {
705

    
706
		$fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db", "w");
707
		if (!$fd) {
708
			printf("Error: cannot open passthru mac DB file in captiveportal_passthrumac_configure().\n");
709
			unlock($captiveportallck);
710
			return 1;
711
		}
712

    
713
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
714
			/* record passthru mac so it can be recognized and let thru */
715
			fwrite($fd, $macent['mac'] . "\n");
716
		}
717

    
718
		fclose($fd);
719
	}
720

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

    
739
	if (!$lock)
740
		unlock($captiveportallck);
741

    
742
	return 0;
743
}
744

    
745
function captiveportal_allowedip_configure() {
746
	global $config, $g;
747

    
748
	/* clear out existing allowed ips, if necessary */
749
	mwexec("/sbin/ipfw table 1 flush");
750
	mwexec("/sbin/ipfw table 2 flush");
751

    
752
	if (is_array($config['captiveportal']['allowedip'])) {
753
		$tableone = false;
754
		$tabletwo = false;
755
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
756
			/* insert address in ipfw table */
757
			if ($ipent['dir'] == "from") {
758
				mwexec("/sbin/ipfw table 1 add {$ipent['ip']}");
759
				$tableone = true;
760
			} else { 
761
				mwexec("/sbin/ipfw table 2 add {$ipent['ip']}");
762
				$tabletwo = true;
763
			}
764
		}
765
		if ($tableone == true) {
766
			mwexec("/sbin/ipfw add 1890 set 1 skipto 50000 ip from table\(1\) to any in");
767
			mwexec("/sbin/ipfw add 1891 set 1 skipto 50000 ip from any to table\(1\) out");
768
		}
769
		if ($tabletwo == true) {
770
			mwexec("/sbin/ipfw add 1892 set 1 skipto 50000 ip from any to table\(2\) in");
771
			mwexec("/sbin/ipfw add 1893 set 1 skipto 50000 ip from table\(2\) to any out");
772
		}
773
	}
774

    
775
    return 0;
776
}
777

    
778
/* get last activity timestamp given client IP address */
779
function captiveportal_get_last_activity($ip) {
780

    
781
	$ipfwoutput = "";
782

    
783
	exec("/sbin/ipfw table 3 entrystats {$ip} 2>/dev/null", $ipfwoutput);
784
	/* Reading only from one of the tables is enough of approximation. */
785
	if ($ipfwoutput[0]) {
786
		$ri = explode(" ", $ipfwoutput[0]);
787
		if ($ri[4])
788
			return $ri[4];
789
	}
790

    
791
	return 0;
792
}
793

    
794
/* read RADIUS servers into array */
795
function captiveportal_get_radius_servers() {
796

    
797
        global $g;
798

    
799
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
800
                $radiusservers = array();
801
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius.db", 
802
			FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
803
		if ($cpradiusdb)
804
		foreach($cpradiusdb as $cpradiusentry) {
805
                	$line = trim($cpradiusentry);
806
                        if ($line) {
807
                        	$radsrv = array();
808
                                list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
809
                        	$radiusservers[] = $radsrv;
810
                        }
811
		}
812

    
813
		return $radiusservers;
814
        }
815

    
816
        return false;
817
}
818

    
819
/* log successful captive portal authentication to syslog */
820
/* part of this code from php.net */
821
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
822
	$message = trim($message);
823
	// Log it
824
	if (!$message)
825
		$message = "$status: $user, $mac, $ip";
826
	else
827
		$message = "$status: $user, $mac, $ip, $message";
828
	captiveportal_syslog($message);
829
	closelog();
830
}
831

    
832
/* log simple messages to syslog */
833
function captiveportal_syslog($message) {
834
	define_syslog_variables();
835
	$message = trim($message);
836
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
837
	// Log it
838
	syslog(LOG_INFO, $message);
839
	closelog();
840
}
841

    
842
function radius($username,$password,$clientip,$clientmac,$type) {
843
    global $g, $config;
844

    
845
    /* Start locking from the beginning of an authentication session */
846
    $captiveportallck = lock('captiveportal');
847

    
848
    $ruleno = captiveportal_get_next_ipfw_ruleno();
849

    
850
    /* If the pool is empty, return appropriate message and fail authentication */
851
    if (is_null($ruleno)) {
852
        $auth_list = array();
853
        $auth_list['auth_val'] = 1;
854
        $auth_list['error'] = "System reached maximum login capacity";
855
        unlock($captiveportallck);
856
        return $auth_list;
857
    }
858

    
859
    /*
860
     * Drop the lock since radius takes some time to finish.
861
     * The implementation is reentrant so we gain speed with this.
862
     */
863
    unlock($captiveportallck);
864

    
865
    $radiusservers = captiveportal_get_radius_servers();
866

    
867
    $auth_list = RADIUS_AUTHENTICATION($username,
868
                    $password,
869
                    $radiusservers,
870
                    $clientip,
871
                    $clientmac,
872
                    $ruleno);
873

    
874
    $captiveportallck = lock('captiveportal');
875

    
876
    if ($auth_list['auth_val'] == 2) {
877
        captiveportal_logportalauth($username,$clientmac,$clientip,$type);
878
        $sessionid = portal_allow($clientip,
879
                    $clientmac,
880
                    $username,
881
                    $password,
882
                    $auth_list,
883
                    $ruleno);
884
    }
885

    
886
    unlock($captiveportallck);
887

    
888
    return $auth_list;
889

    
890
}
891

    
892
/* read captive portal DB into array */
893
function captiveportal_read_db() {
894

    
895
        global $g;
896

    
897
        $cpdb = array();
898
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
899
        if ($fd) {
900
                while (!feof($fd)) {
901
                        $line = trim(fgets($fd));
902
                        if ($line) {
903
                                $cpdb[] = explode(",", $line);
904
                        }
905
                }
906
                fclose($fd);
907
        }
908
        return $cpdb;
909
}
910

    
911
/* write captive portal DB */
912
function captiveportal_write_db($cpdb) {
913
                 
914
        global $g;
915
                
916
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
917
        if ($fd) { 
918
                foreach ($cpdb as $cpent) {
919
                        fwrite($fd, join(",", $cpent) . "\n");
920
                }       
921
                fclose($fd);
922
        }       
923
}
924

    
925
function captiveportal_write_elements() {
926
    global $g, $config;
927
    
928
    /* delete any existing elements */
929
    if (is_dir($g['captiveportal_element_path'])) {
930
        $dh = opendir($g['captiveportal_element_path']);
931
        while (($file = readdir($dh)) !== false) {
932
            if ($file != "." && $file != "..")
933
                unlink($g['captiveportal_element_path'] . "/" . $file);
934
        }
935
        closedir($dh);
936
    } else {
937
        @mkdir($g['captiveportal_element_path']);
938
    }
939
    
940
	if (is_array($config['captiveportal']['element'])) {
941
		conf_mount_rw();
942
		foreach ($config['captiveportal']['element'] as $data) {
943
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
944
			if (!$fd) {
945
				printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
946
				return 1;
947
			}
948
			$decoded = base64_decode($data['content']);
949
			fwrite($fd,$decoded);
950
			fclose($fd);
951
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
952
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
953
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
954
		}
955
		conf_mount_ro();
956
	}
957
    
958
    return 0;
959
}
960

    
961
/*
962
 * This function will calculate the lowest free firewall ruleno
963
 * within the range specified based on the actual logged on users
964
 *
965
 */
966
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
967
	global $config, $g;
968
	if(!isset($config['captiveportal']['enable']))
969
		return NULL;
970
	$ruleno = 0;
971
	if (file_exists("{$g['vardb_path']}/captiveportal.nextrule"))
972
		$ruleno = intval(file_get_contents("{$g['vardb_path']}/captiveportal.nextrule"));
973
	else
974
		$ruleno = 1;
975
	if ($ruleno > 0 && (($rulenos_start + $ruleno) < $rulenos_range_max)) {
976
		/* 
977
		 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
978
		 * and the out pipe ruleno + 1. This removes limitation that where present in 
979
		 * previous version of the peruserbw.
980
		 */
981
		if (isset($config['captiveportal']['peruserbw']))
982
			$ruleno += 2;
983
		else
984
			$ruleno++;
985
		file_put_contents("{$g['vardb_path']}/captiveportal.nextrule", $ruleno);
986
		return $rulenos_start + $ruleno;
987
	}
988
	return NULL;
989
}
990

    
991
/**
992
 * This function will calculate the traffic produced by a client
993
 * based on its firewall rule
994
 *
995
 * Point of view: NAS
996
 *
997
 * Input means: from the client
998
 * Output means: to the client
999
 *
1000
 */
1001

    
1002
function getVolume($ip) {
1003

    
1004
    $volume = array();
1005

    
1006
    // Initialize vars properly, since we don't want NULL vars
1007
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1008

    
1009
    // Ingress
1010
    $ipfwin = "";
1011
    $ipfwout = "";
1012
    $matchesin = "";
1013
    $matchesout = "";
1014
    exec("/sbin/ipfw table 3 entrystats {$ip}", $ipfwin);
1015
    if ($ipfwin[0]) {
1016
		$ipfwin = split(" ", $ipfwin[0]);
1017
		$volume['input_pkts'] = $ipfwin[2];
1018
		$volume['input_bytes'] = $ipfwin[3];
1019
    }
1020

    
1021
    exec("/sbin/ipfw table 4 entrystats {$ip}", $ipfwout);
1022
    if ($ipfwout[0]) {
1023
        $ipfwout = split(" ", $ipfwout[0]);
1024
        $volume['output_pkts'] = $ipfwout[2];
1025
        $volume['output_bytes'] = $ipfwout[3];
1026
    }
1027

    
1028
    return $volume;
1029
}
1030

    
1031
/**
1032
 * Get the NAS-Identifier
1033
 *
1034
 * We will use our local hostname to make up the nas_id
1035
 */
1036
function getNasID()
1037
{
1038
    $nasId = "";
1039
    exec("/bin/hostname", $nasId);
1040
    if(!$nasId[0])
1041
        $nasId[0] = "{$g['product_name']}";
1042
    return $nasId[0];
1043
}
1044

    
1045
/**
1046
 * Get the NAS-IP-Address based on the current wan address
1047
 *
1048
 * Use functions in interfaces.inc to find this out
1049
 *
1050
 */
1051

    
1052
function getNasIP()
1053
{
1054
    $nasIp = get_interface_ip();
1055
    if(!$nasIp)
1056
        $nasIp = "0.0.0.0";
1057
    return $nasIp;
1058
}
1059

    
1060
function portal_mac_fixed($clientmac) {
1061
    global $g ;
1062

    
1063
    /* open captive portal mac db */
1064
    if (file_exists("{$g['vardb_path']}/captiveportal_mac.db")) {
1065
        $fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db","r") ;
1066
        if (!$fd) {
1067
            return FALSE;
1068
        }
1069
        while (!feof($fd)) {
1070
            $mac = trim(fgets($fd)) ;
1071
            if(strcasecmp($clientmac, $mac) == 0) {
1072
                fclose($fd) ;
1073
                return TRUE ;
1074
            }
1075
        }
1076
        fclose($fd) ;
1077
    }
1078
    return FALSE ;
1079
}
1080

    
1081
function portal_ip_from_client_ip($cliip) {
1082
	global $config;
1083

    
1084
	$interfaces = explode(",", $config['captiveportal']['interface']);
1085
	foreach ($interfaces as $cpif) {
1086
		$ip = get_interface_ip($cpif);
1087
		$sn = get_interface_subnet($cpif);
1088
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1089
			return $ip;
1090
	}
1091

    
1092
	// doesn't match up to any particular interface
1093
	// so let's set the portal IP to what PHP says 
1094
	// the server IP issuing the request is. 
1095
	// allows same behavior as 1.2.x where IP isn't 
1096
	// in the subnet of any CP interface (static routes, etc.)
1097
	// rather than forcing to DNS hostname resolution
1098
	$ip = $_SERVER['SERVER_ADDR'];
1099
	if (is_ipaddr($ip))
1100
		return $ip;
1101

    
1102
	return false;
1103
}
1104

    
1105
?>
(6-6/50)