Project

General

Profile

Download (14.7 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
	for ($i = 0; $i < count($cpdb); $i++) {
254

    
255
		$timedout = false;
256

    
257
                /* hard timeout? */
258
		if ((time() - $cpdb[$i][0]) >= $timeout)
259
			$timedout = true;
260

    
261
		/* if an idle timeout is specified, get last activity timestamp from pf */
262
		// XXX: we need a solution for this.
263
		if (!$timedout && $idletimeout) {
264
			$isactive = captiveportal_get_last_activity($cpdb[$i][2]);
265
			if($isactive == 0)
266
				$timedout = true;
267
		}
268

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

    
283
			mwexec("/sbin/pfctl -t captiveportal -T delete {$cpdb[$i][2]}");
284

    
285
			unset($cpdb[$i]);
286
		}
287
	}
288

    
289
	/* write database */
290
	captiveportal_write_db($cpdb);
291

    
292
	captiveportal_unlock();
293
}
294

    
295
/* remove a single client */
296
function captiveportal_disconnect_client($id) {
297

    
298
	global $g, $config;
299

    
300
	captiveportal_lock();
301

    
302
	/* read database */
303
	$cpdb = captiveportal_read_db();
304
	$radiusservers = captiveportal_get_radius_servers();
305

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

    
322
			mwexec("/sbin/pfctl -t captiveportal -T delete {$ip}");
323

    
324
			unset($cpdb[$i]);
325

    
326
			break;
327
		}
328
	}
329

    
330

    
331
	/* write database */
332
	captiveportal_write_db($cpdb);
333

    
334
	captiveportal_unlock();
335
}
336

    
337
/* send RADIUS acct stop for all current clients */
338
function captiveportal_radius_stop_all() {
339
	global $g, $config;
340

    
341
	captiveportal_lock() ;
342
	$cpdb = captiveportal_read_db() ;
343

    
344
	$radiusservers = captiveportal_get_radius_servers();
345

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

    
361
function captiveportal_passthrumac_configure() {
362
	global $config, $g;
363

    
364
	/* clear out passthru macs, if necessary */
365
	if (file_exists("{$g['vardb_path']}/captiveportal_mac.db")) {
366
		unlink("{$g['vardb_path']}/captiveportal_mac.db");
367
	}
368

    
369
	if (is_array($config['captiveportal']['passthrumac'])) {
370

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

    
377
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
378
			/* record passthru mac so it can be recognized and let thru */
379
			fwrite($fd, $macent['mac'] . "\n");
380
		}
381

    
382
		fclose($fd);
383
	}
384

    
385
	return 0;
386
}
387

    
388
function captiveportal_allowedip_configure() {
389
	global $config, $g;
390

    
391
	captiveportal_lock() ;
392

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

    
409
	if (is_array($config['captiveportal']['allowedip'])) {
410

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

    
418
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
419
			/* record allowed ip so it can be recognized and removed later */
420
			fwrite($fd, $ipent['ip'] . "," . $ruleno ."\n");
421

    
422
			/* insert pf table item to allow traffic */
423
			mwexec("echo \"pfctl -t captiveportal -T add {$ipent['ip']} \"> /tmp/tmp");
424
			echo "Adding {$ipent['ip']}";
425
			mwexec("/sbin/pfctl -t captiveportal -T add {$ipent['ip']}");
426

    
427
			$ruleno = $ip;
428
		}
429

    
430
		fclose($fd);
431

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

    
440
	captiveportal_unlock() ;
441
	return 0;
442
}
443

    
444
/* get last activity timestamp given pf table item */
445
function captiveportal_get_last_activity($ip) {
446

    
447
	$info = `/usr/sbin/arp | /usr/bin/grep $ip`;
448

    
449
	if($info <> "") return 1;
450

    
451
	return 0;
452
}
453

    
454
/* read captive portal DB into array */
455
function captiveportal_read_db() {
456

    
457
	global $g;
458

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

    
473
/* write captive portal DB */
474
function captiveportal_write_db($cpdb) {
475

    
476
	global $g;
477

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

    
487
/* read RADIUS servers into array */
488
function captiveportal_get_radius_servers() {
489

    
490
	global $g;
491

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

    
506
			return $radiusservers;
507
		}
508
	}
509

    
510
	return false;
511
}
512

    
513
/* lock captive portal information, decide that the lock file is stale after
514
   10 seconds */
515
function captiveportal_lock() {
516

    
517
	global $g;
518

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

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

    
536
/* unlock configuration file */
537
function captiveportal_unlock() {
538

    
539
	global $g;
540

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

    
543
	if (file_exists($lockfile))
544
		unlink($lockfile);
545
}
546

    
547
?>
(2-2/19)