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
			if (!empty($listrealif)) {
326
				mwexec("/sbin/ifconfig {$listrealif} -ipfwfilter");
327
				$carpif = link_ip_to_carp_interface(find_interface_ip($listrealif));
328
			}
329
                        if (!empty($carpif)) {
330
				$carpsif = explode(" ", $carpif);
331
				foreach ($carpsif as $cpcarp)
332
					mwexec("/sbin/ifconfig {$cpcarp} -ipfwfilter");
333
			}
334
		}
335
	}
336

    
337
	unlock($captiveportallck);
338
	
339
	return 0;
340
}
341

    
342
function captiveportal_rules_generate($cpif, &$cpiparray) {
343
	global $config, $g;
344

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

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

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

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

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

    
372
EOD;
373

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

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

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

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

    
429
# ... 2000-49899: layer2 block rules per authenticated client go here...
430

    
431
# pass everything else on layer2
432
add 49900 set 1 pass all from any to any layer2
433

    
434
EOD;
435

    
436
    return $cprules;
437
}
438

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

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

    
446
    global $g, $config;
447

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

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

    
459
    if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && 
460
		!isset($config['captiveportal']['radiussession_timeout']) && !isset($config['voucher']['enable']))
461
        return;
462

    
463
    $captiveportallck = lock('captiveportal');
464

    
465
    /* read database */
466
    $cpdb = captiveportal_read_db();
467

    
468
    $radiusservers = captiveportal_get_radius_servers();
469

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

    
479
        $timedout = false;
480
        $term_cause = 1;
481

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

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

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

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

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

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

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

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

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

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

    
595
    /* write database */
596
    captiveportal_write_db($cpdb);
597

    
598
    unlock($captiveportallck);
599
}
600

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

    
604
	global $g, $config;
605

    
606
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
607

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

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

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

    
638
}
639

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

    
643
	global $g, $config;
644

    
645
	$captiveportallck = lock('captiveportal');
646

    
647
	/* read database */
648
	$cpdb = captiveportal_read_db();
649
	$radiusservers = captiveportal_get_radius_servers();
650

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

    
662
	/* write database */
663
	captiveportal_write_db($cpdb);
664

    
665
	unlock($captiveportallck);
666
}
667

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

    
672
	if (!isset($config['captiveportal']['radacct_enable']))
673
		return;
674

    
675
	if (!$lock)
676
		$captiveportallck = lock('captiveportal');
677

    
678
	$cpdb = captiveportal_read_db();
679

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

    
697
function captiveportal_passthrumac_configure($lock = false) {
698
	global $config, $g;
699

    
700
	if (!$lock)
701
		$captiveportallck = lock('captiveportal');
702

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

    
706
	if (is_array($config['captiveportal']['passthrumac'])) {
707

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

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

    
720
		fclose($fd);
721
	}
722

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

    
741
	if (!$lock)
742
		unlock($captiveportallck);
743

    
744
	return 0;
745
}
746

    
747
function captiveportal_allowedip_configure() {
748
	global $config, $g;
749

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

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

    
777
    return 0;
778
}
779

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

    
783
	$ipfwoutput = "";
784

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

    
793
	return 0;
794
}
795

    
796
/* read RADIUS servers into array */
797
function captiveportal_get_radius_servers() {
798

    
799
        global $g;
800

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

    
815
		return $radiusservers;
816
        }
817

    
818
        return false;
819
}
820

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

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

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

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

    
850
    $ruleno = captiveportal_get_next_ipfw_ruleno();
851

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

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

    
867
    $radiusservers = captiveportal_get_radius_servers();
868

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

    
876
    $captiveportallck = lock('captiveportal');
877

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

    
888
    unlock($captiveportallck);
889

    
890
    return $auth_list;
891

    
892
}
893

    
894
/* read captive portal DB into array */
895
function captiveportal_read_db() {
896

    
897
        global $g;
898

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

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

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

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

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

    
1004
function getVolume($ip) {
1005

    
1006
    $volume = array();
1007

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

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

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

    
1030
    return $volume;
1031
}
1032

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

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

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

    
1062
function portal_mac_fixed($clientmac) {
1063
    global $g ;
1064

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

    
1083
function portal_ip_from_client_ip($cliip) {
1084
	global $config;
1085

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

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

    
1104
	return false;
1105
}
1106

    
1107
?>
(6-6/51)