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

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

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

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

    
60
		/* stop accounting on all clients */
61
		captiveportal_radius_stop_all() ;
62

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

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

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

    
111
EOD;
112
		}
113

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

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

    
140
EOD;
141
		}
142

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

    
149
		chdir($g['captiveportal_path']);
150

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

    
155
		$fd = fopen("/tmp/captiveportal.txt", "w");
156
		fwrite($fd, "/usr/local/sbin/mini_httpd -a -M 0 -u root -maxproc 16 -p 8000 -i {$g['varrun_path']}/mini_httpd.cp.pid");
157
		fclose($fd);
158

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

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

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

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

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

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

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

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

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

    
205
			$radiuskey = $config['captiveportal']['radiuskey'];
206

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

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

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

    
225
	return 0;
226
}
227

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

    
232
	global $g, $config;
233

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

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

    
245
	if (!$timeout && !$idletimeout)
246
		return;
247

    
248
	captiveportal_lock();
249

    
250
	/* read database */
251
	$cpdb = captiveportal_read_db();
252

    
253
	$radiusservers = captiveportal_get_radius_servers();
254

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

    
260
	$after_prune = `/sbin/pfctl -t captiveportal -T show`;
261

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

    
268
		$timedout = false;
269

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

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

    
291
			unset($cpdb[$i]);
292
		}
293
	}
294

    
295
	/* write database */
296
	captiveportal_write_db($cpdb);
297

    
298
	captiveportal_unlock();
299
}
300

    
301
/* remove a single client */
302
function captiveportal_disconnect_client($id) {
303

    
304
	global $g, $config;
305

    
306
	captiveportal_lock();
307

    
308
	/* read database */
309
	$cpdb = captiveportal_read_db();
310
	$radiusservers = captiveportal_get_radius_servers();
311

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

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

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

    
332
			break;
333
		}
334
	}
335

    
336

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

    
340
	captiveportal_unlock();
341
}
342

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

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

    
350
	$radiusservers = captiveportal_get_radius_servers();
351

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

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

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

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

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

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

    
388
		fclose($fd);
389
	}
390

    
391
	return 0;
392
}
393

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

    
397
	captiveportal_lock() ;
398

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

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

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

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

    
431
			$ruleno = $ip;
432
		}
433

    
434
		fclose($fd);
435

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

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

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

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

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

    
455
	return 0;
456
}
457

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

    
461
	global $g;
462

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

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

    
480
	global $g;
481

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

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

    
494
	global $g;
495

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

    
510
			return $radiusservers;
511
		}
512
	}
513

    
514
	return false;
515
}
516

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

    
521
	global $g;
522

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

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

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

    
543
	global $g;
544

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

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

    
551
?>
(2-2/23)