Project

General

Profile

Download (14.9 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/* $Id$ */
3
/*
4
	captiveportal.inc
5
	part of pfSense (http://www.pfSense.com)
6

    
7
	Copyright (C) 2005 Scott Ullrich <sullrich@gmail.com>
8
	All rights reserved.
9
        
10
        originally part of m0n0wall (http://m0n0.ch/wall)
11
	Copyright (C) 2003-2005 Manuel Kasper <mk@neon1.net>.
12
	All rights reserved.
13

    
14
	Redistribution and use in source and binary forms, with or without
15
	modification, are permitted provided that the following conditions are met:
16

    
17
	1. Redistributions of source code must retain the above copyright notice,
18
	   this list of conditions and the following disclaimer.
19

    
20
	2. Redistributions in binary form must reproduce the above copyright
21
	   notice, this list of conditions and the following disclaimer in the
22
	   documentation and/or other materials provided with the distribution.
23

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

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

    
42
/* include all configuration functions */
43
require_once("functions.inc");
44
require_once("radius_accounting.inc") ;
45

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

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

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

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

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

    
62
		/* stop accounting on all clients */
63
		captiveportal_radius_stop_all() ;
64

    
65
		/* remove old information */
66
		unlink_if_exists("{$g['vardb_path']}/captiveportal.nextrule");
67
		unlink_if_exists("{$g['vardb_path']}/captiveportal.db");
68
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
69
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip.db");
70
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius.db");
71

    
72
		/* write portal page */
73
		if ($config['captiveportal']['page']['htmltext'])
74
			$htmltext = base64_decode($config['captiveportal']['page']['htmltext']);
75
		else {
76
			/* example/template page */
77
			$htmltext = <<<EOD
78
<html>
79
<title>pfSense's captive portal</title>
80
<head>
81
 <STYLE type="text/css">
82
.listhdrr {
83
	background-color: #BBBBBB;
84
	padding-right: 16px;
85
	padding-left: 6px;
86
	font-weight: bold;
87
	border-right: 1px solid #999999;
88
	border-bottom: 1px solid #999999;
89
	font-size: 11px;
90
	padding-top: 5px;
91
	padding-bottom: 5px;
92
}
93

    
94
 </STYLE>
95
</head>
96
<body bgcolor="#990000">
97
<center>
98
<font color="white" face="arial" size="+1">Welcome to pfSense's captive portal!</font>
99
<p>
100
<form method="post" action="\$PORTAL_ACTION\$">
101
<table border="0" cellpadding="6" cellspacing="0">
102
<tr><td align="right" class="listhdrr"><font color="white">Username:</td><td class="listhdrr"><input name="auth_user" type="text"></td></tr>
103
<tr><td align="right" class="listhdrr"><font color="white">Password:</td><td class="listhdrr"><input name="auth_pass" type="password"></td></tr>
104
<input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$">
105
</table>
106
<p>
107
<center><input name="accept" type="submit" value="Continue">
108
</form>
109
</center>
110
</body>
111
</html>
112

    
113
EOD;
114
		}
115

    
116
		$fd = @fopen("{$g['varetc_path']}/captiveportal.html", "w");
117
		if ($fd) {
118
			fwrite($fd, $htmltext);
119
			fclose($fd);
120
		}
121

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

    
142
EOD;
143
		}
144

    
145
		$fd = @fopen("{$g['varetc_path']}/captiveportal-error.html", "w");
146
		if ($fd) {
147
			fwrite($fd, $errtext);
148
			fclose($fd);
149
		}
150

    
151
		chdir($g['captiveportal_path']);
152

    
153
		/* start web server */
154
		mwexec("/usr/local/sbin/mini_httpd -a -M 0 -u root -maxproc 16" .
155
			" -p 8000 -i {$g['varrun_path']}/mini_httpd.cp.pid");
156

    
157
		/* fire up another one for HTTPS if requested */
158
		if (isset($config['captiveportal']['httpslogin']) &&
159
			$config['captiveportal']['certificate'] && $config['captiveportal']['private-key']) {
160

    
161
			$cert = base64_decode($config['captiveportal']['certificate']);
162
			$key = base64_decode($config['captiveportal']['private-key']);
163

    
164
			$fd = fopen("{$g['varetc_path']}/cert-portal.pem", "w");
165
			if (!$fd) {
166
				printf("Error: cannot open cert-portal.pem in system_webgui_start().\n");
167
				return 1;
168
			}
169
			chmod("{$g['varetc_path']}/cert-portal.pem", 0600);
170
			fwrite($fd, $cert);
171
			fwrite($fd, "\n");
172
			fwrite($fd, $key);
173
			fclose($fd);
174

    
175
			mwexec("/usr/local/sbin/mini_httpd -S -a -M 0 -E {$g['varetc_path']}/cert-portal.pem" .
176
				" -u root -maxproc 16 -p 8001" .
177
				" -i {$g['varrun_path']}/mini_httpd.cps.pid");
178
		}
179

    
180
		/* start pruning process (interval = 60 seconds) */
181
		mwexec("/usr/local/bin/minicron 60 {$g['varrun_path']}/minicron.pid " .
182
			"/etc/rc.prunecaptiveportal");
183

    
184
		/* generate passthru mac database */
185
		captiveportal_passthrumac_configure() ;
186
		/* create allowed ip database and insert pf tables to make it so */
187
		captiveportal_allowedip_configure() ;
188

    
189
		/* generate radius server database */
190
		if($config['captiveportal']['radiusip'] && $config['captiveportal']['auth_method']=="radius") {
191
			$radiusip = $config['captiveportal']['radiusip'] ;
192

    
193
			if($config['captiveportal']['radiusport'])
194
				$radiusport = $config['captiveportal']['radiusport'] ;
195
			else
196
				$radiusport = 1812;
197

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

    
203
			$radiuskey = $config['captiveportal']['radiuskey'];
204

    
205
			$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db", "w");
206
			if (!$fd) {
207
				printf("Error: cannot open radius DB file in captiveportal_configure().\n");
208
				return 1;
209
			} else {
210
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey) ;
211
			}
212
			fclose($fd) ;
213
		}
214

    
215
		if($g['booting']) print "done.\n";
216

    
217
	} else {
218
		killbypid("{$g['varrun_path']}/mini_httpd.cp.pid");
219
		killbypid("{$g['varrun_path']}/minicron.pid");
220
		captiveportal_radius_stop_all() ;
221
	}
222

    
223
	return 0;
224
}
225

    
226
/* remove clients that have been around for longer than the specified amount of time */
227
/* db file structure: timestamp,clientip,clientmac,username,sessionid */
228
function captiveportal_prune_old() {
229

    
230
	global $g, $config;
231

    
232
	/* check for expired entries */
233
	if ($config['captiveportal']['timeout'])
234
		$timeout = $config['captiveportal']['timeout'] * 60;
235
	else
236
		$timeout = 0;
237

    
238
	if ($config['captiveportal']['idletimeout'])
239
		$idletimeout = $config['captiveportal']['idletimeout'] * 60;
240
	else
241
		$idletimeout = 0;
242

    
243
	if (!$timeout && !$idletimeout)
244
		return;
245

    
246
	captiveportal_lock();
247

    
248
	/* read database */
249
	$cpdb = captiveportal_read_db();
250

    
251
	$radiusservers = captiveportal_get_radius_servers();
252

    
253
	if($idletimeout <> 0) {
254
		/* launch expire table and remove entries older than $timeout */
255
		mwexec("/usr/bin/nice -n20 /usr/local/sbin/expiretable -v -t {$idletimeout} captiveportal");
256
	}
257

    
258
	$after_prune = `/sbin/pfctl -t captiveportal -T show`;
259

    
260
	/*
261
	 *   loop back through and deterimine if expiretable removed a client.
262
	 *   if we detect a client removale then update the internal db accordingly
263
         */
264
	for ($i = 0; $i < count($cpdb); $i++) {
265

    
266
		$timedout = false;
267

    
268
                /* hard timeout? */
269
		if ((time() - $cpdb[$i][0]) >= $timeout)
270
			$timedout = true;
271
		
272
		if(stristr($after_prune, $cpdb[$i][2]) == false)
273
			$timedout= true;
274

    
275
		if ($timedout) {
276
			/* this client needs to be deleted - remove pf table item */
277
			if (isset($config['captiveportal']['radacct_enable']) && isset($radiusservers[0])) {
278
				RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
279
									   $cpdb[$i][4], // username
280
									   $cpdb[$i][5], // sessionid
281
									   $cpdb[$i][0], // start time
282
									   $radiusservers[0]['ipaddr'],
283
									   $radiusservers[0]['acctport'],
284
									   $radiusservers[0]['key'],
285
									   $cpdb[$i][2]); //clientip
286
				syslog(LOG_INFO,"Authenticated user $cpdb[$i][4] timed out");
287
			}
288

    
289
			unset($cpdb[$i]);
290
		}
291
	}
292

    
293
	/* write database */
294
	captiveportal_write_db($cpdb);
295

    
296
	captiveportal_unlock();
297
}
298

    
299
/* remove a single client */
300
function captiveportal_disconnect_client($id) {
301

    
302
	global $g, $config;
303

    
304
	captiveportal_lock();
305

    
306
	/* read database */
307
	$cpdb = captiveportal_read_db();
308
	$radiusservers = captiveportal_get_radius_servers();
309

    
310
	/* find entry */
311
	for ($i = 0; $i < count($cpdb); $i++) {
312
		if ($cpdb[$i][1] == $id) {
313
			/* this client needs to be deleted - remove pf table item */
314
			if (isset($config['captiveportal']['radacct_enable']) && isset($radiusservers[0])) {
315
				RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
316
									   $cpdb[$i][4], // username
317
									   $cpdb[$i][5], // sessionid
318
									   $cpdb[$i][0], // start time
319
									   $radiusservers[0]['ipaddr'],
320
									   $radiusservers[0]['acctport'],
321
									   $radiusservers[0]['key'],
322
									   $cpdb[$i][2]); //clientip
323
				syslog(LOG_INFO,"Authenticated user $cpdb[$i][4] disconnected");
324
			}
325

    
326
			/* XXX: What's $ip? This can't be working?!?!?! --billm */
327
			mwexec("/sbin/pfctl -t captiveportal -T delete {$ip}");
328

    
329
			unset($cpdb[$i]);
330

    
331
			break;
332
		}
333
	}
334

    
335

    
336
	/* write database */
337
	captiveportal_write_db($cpdb);
338

    
339
	captiveportal_unlock();
340
}
341

    
342
/* send RADIUS acct stop for all current clients */
343
function captiveportal_radius_stop_all() {
344
	global $g, $config;
345

    
346
	captiveportal_lock() ;
347
	$cpdb = captiveportal_read_db() ;
348

    
349
	$radiusservers = captiveportal_get_radius_servers();
350

    
351
	if (isset($radiusservers[0])) {
352
		for ($i = 0; $i < count($cpdb); $i++) {
353
			RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
354
								   $cpdb[$i][4], // username
355
								   $cpdb[$i][5], // sessionid
356
								   $cpdb[$i][0], // start time
357
								   $radiusservers[0]['ipaddr'],
358
								   $radiusservers[0]['acctport'],
359
								   $radiusservers[0]['key'],
360
								   $cpdb[$i][2]); //clientip
361
		}
362
	}
363
	captiveportal_unlock() ;
364
}
365

    
366
function captiveportal_passthrumac_configure() {
367
	global $config, $g;
368

    
369
	/* clear out passthru macs, if necessary */
370
	if (file_exists("{$g['vardb_path']}/captiveportal_mac.db")) {
371
		unlink("{$g['vardb_path']}/captiveportal_mac.db");
372
	}
373

    
374
	if (is_array($config['captiveportal']['passthrumac'])) {
375

    
376
		$fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db", "w");
377
		if (!$fd) {
378
			printf("Error: cannot open passthru mac DB file in captiveportal_passthrumac_configure().\n");
379
			return 1;
380
		}
381

    
382
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
383
			/* record passthru mac so it can be recognized and let thru */
384
			fwrite($fd, $macent['mac'] . "\n");
385
		}
386

    
387
		fclose($fd);
388
	}
389

    
390
	return 0;
391
}
392

    
393
function captiveportal_allowedip_configure() {
394
	global $config, $g;
395

    
396
	captiveportal_lock() ;
397

    
398
	/* clear out existing allowed ips, if necessary */
399
	if (file_exists("{$g['vardb_path']}/captiveportal_ip.db")) {
400
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "r");
401
		if ($fd) {
402
			while (!feof($fd)) {
403
				$line = trim(fgets($fd));
404
				if($line) {
405
					list($ip,$rule) = explode(",",$line);
406
					mwexec("/sbin/pfctl -t captiveportal -T delete {$ip}");
407
				}
408
			}
409
		}
410
		fclose($fd) ;
411
		unlink("{$g['vardb_path']}/captiveportal_ip.db");
412
	}
413

    
414
	if (is_array($config['captiveportal']['allowedip'])) {
415

    
416
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "w");
417
		if (!$fd) {
418
			printf("Error: cannot open allowed ip DB file in captiveportal_allowedip_configure().\n");
419
			captiveportal_unlock() ;
420
			return 1;
421
		}
422

    
423
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
424
			/* record allowed ip so it can be recognized and removed later */
425
			fwrite($fd, $ipent['ip'] . "," . $ipent['ip'] ."\n");
426
			/* insert pf table item to allow traffic */
427
			mwexec("echo \"pfctl -t captiveportal -T add {$ipent['ip']} \"> /tmp/tmp");
428
			mwexec("/sbin/pfctl -t captiveportal -T add {$ipent['ip']}");
429

    
430
			$ruleno = $ip;
431
		}
432

    
433
		fclose($fd);
434

    
435
		/* write next rule number */
436
		$fd = @fopen("{$g['vardb_path']}/captiveportal.nextrule", "w");
437
		if ($fd) {
438
			fwrite($fd, $ruleno);
439
			fclose($fd);
440
		}
441
	}
442

    
443
	captiveportal_unlock() ;
444
	return 0;
445
}
446

    
447
/* get last activity timestamp given pf table item */
448
function captiveportal_get_last_activity($ip) {
449

    
450
	$info = `/usr/sbin/arp -an | /usr/bin/grep $ip`;
451

    
452
	if($info <> "") return 1;
453

    
454
	return 0;
455
}
456

    
457
/* read captive portal DB into array */
458
function captiveportal_read_db() {
459

    
460
	global $g;
461

    
462
	$cpdb = array();
463
	$fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
464
	if ($fd) {
465
		while (!feof($fd)) {
466
			$line = trim(fgets($fd));
467
			if ($line) {
468
				$cpdb[] = explode(",", $line);
469
			}
470
		}
471
		fclose($fd);
472
	}
473
	return $cpdb;
474
}
475

    
476
/* write captive portal DB */
477
function captiveportal_write_db($cpdb) {
478

    
479
	global $g;
480

    
481
	$fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
482
	if ($fd) {
483
		foreach ($cpdb as $cpent) {
484
			fwrite($fd, join(",", $cpent) . "\n");
485
		}
486
		fclose($fd);
487
	}
488
}
489

    
490
/* read RADIUS servers into array */
491
function captiveportal_get_radius_servers() {
492

    
493
	global $g;
494

    
495
	if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
496
	   	$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db","r");
497
		if ($fd) {
498
			$radiusservers = array();
499
			while (!feof($fd)) {
500
				$line = trim(fgets($fd));
501
				if ($line) {
502
					$radsrv = array();
503
					list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
504
					$radiusservers[] = $radsrv;
505
				}
506
			}
507
			fclose($fd);
508

    
509
			return $radiusservers;
510
		}
511
	}
512

    
513
	return false;
514
}
515

    
516
/* lock captive portal information, decide that the lock file is stale after
517
   10 seconds */
518
function captiveportal_lock() {
519

    
520
	global $g;
521

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

    
524
	$n = 0;
525
	while ($n < 10) {
526
		/* open the lock file in append mode to avoid race condition */
527
		if ($fd = @fopen($lockfile, "x")) {
528
			/* succeeded */
529
			fclose($fd);
530
			return;
531
		} else {
532
			/* file locked, wait and try again */
533
			sleep(1);
534
			$n++;
535
		}
536
	}
537
}
538

    
539
/* unlock configuration file */
540
function captiveportal_unlock() {
541

    
542
	global $g;
543

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

    
546
	if (file_exists($lockfile))
547
		unlink($lockfile);
548
}
549

    
550
?>
(2-2/22)