Project

General

Profile

Download (62.5 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	captiveportal.inc
4
	part of pfSense (http://www.pfSense.org)
5
	Copyright (C) 2004-2011 Scott Ullrich <sullrich@gmail.com>
6
	Copyright (C) 2009 Ermal Lu?i <ermal.luci@gmail.com>
7
	Copyright (C) 2003-2006 Manuel Kasper <mk@neon1.net>.
8

    
9
	originally part of m0n0wall (http://m0n0.ch/wall)
10
	All rights reserved.
11

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

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

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

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

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

    
45
/* include all configuration functions */
46
require_once("config.inc");
47
require_once("functions.inc");
48
require_once("filter.inc");
49
require_once("radius.inc");
50
require_once("voucher.inc");
51

    
52
function get_default_captive_portal_html() {
53
	global $config, $g;
54
	// Detect if vouchers are being used and default to the voucher page
55
	if(isset($config['voucher']['enable'])) {
56
			$htmltext = <<<EOD
57
<html> 
58
	<body> 
59
		<form method="post" action="\$PORTAL_ACTION\$"> 
60
			<input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$"> 
61
			<center>
62
				<table cellpadding="6" cellspacing="0" width="550" height="380" style="border:1px solid #000000">
63
					<tr height="10" bgcolor="#990000">
64
						<td style="border-bottom:1px solid #000000">
65
							<font color='white'>
66
								<b>
67
									Guest Voucher code required to continue
68
								</b>
69
							</font>
70
						</td>
71
					</tr>
72
					<tr>
73
						<td>
74
							<div id="mainlevel">
75
								<center>
76
									<table width="100%" border="0" cellpadding="5" cellspacing="0">
77
										<tr>
78
											<td>
79
												<center>
80
													<div id="mainarea">
81
														<center>
82
															<table width="100%" border="0" cellpadding="5" cellspacing="5">
83
																<tr>
84
																	<td>
85
																		<div id="maindivarea">
86
																			<center>
87
																				<div id='statusbox'>
88
																					<font color='red' face='arial' size='+1'>
89
																						<b>
90
																							\$PORTAL_MESSAGE\$
91
																						</b>
92
																					</font>
93
																				</div>
94
																				<p/>
95
																				<div id='loginbox'>
96
																					Enter Voucher Code: 
97
																					<input name="auth_voucher" type="text" style="border:1px dashed;" size="22"> 
98
																					<input name="accept" type="submit" value="Continue"> 
99
																				</div>
100
																			</center>
101
																		</div>
102
																	</td>
103
																</tr>
104
															</table>
105
														</center>
106
													</div>
107
												</center>
108
											</td>
109
										</tr>
110
									</table>
111
								</center>
112
							</div>
113
						</td>
114
					</tr>
115
				</table>
116
			</center>
117
		</form>
118
	</body> 
119
</html>
120

    
121
EOD;
122
		return $htmltext;
123
	}
124

    
125
	// Vouchers are not found, return the normal user/pass auth page
126
	$htmltext = <<<EOD
127
<html> 
128
	<body> 
129
		<form method="post" action="\$PORTAL_ACTION\$"> 
130
		<input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$">
131
			<center>
132
				<table cellpadding="6" cellspacing="0" width="550" height="380" style="border:1px solid #000000">
133
					<tr height="10" bgcolor="#990000">
134
						<td style="border-bottom:1px solid #000000">
135
							<font color='white'>
136
								<b>
137
									{$g['product_name']} captive portal
138
								</b>
139
							</font>
140
						</td>
141
					</tr>
142
					<tr>
143
						<td>
144
							<div id="mainlevel">
145
								<center>
146
									<table width="100%" border="0" cellpadding="5" cellspacing="0">
147
										<tr>
148
											<td>
149
												<center>
150
													<div id="mainarea">
151
														<center>
152
															<table width="100%" border="0" cellpadding="5" cellspacing="5">
153
																<tr>
154
																	<td>
155
																		<div id="maindivarea">
156
																			<center>
157
																				<div id='statusbox'>
158
																					<font color='red' face='arial' size='+1'>
159
																						<b>
160
																							\$PORTAL_MESSAGE\$
161
																						</b>
162
																					</font>
163
																				</div>
164
																				<br/>
165
																				<div id='loginbox'>
166
																					<table>
167
																					   <tr><td colspan="2"><center>Welcome to the {$g['product_name']} Captive Portal!</td></tr>
168
																					   <tr><td>&nbsp;</td></tr>
169
																					   <tr><td align="right">Username:</td><td><input name="auth_user" type="text" style="border: 1px dashed;"></td></tr>
170
																					   <tr><td align="right">Password:</td><td><input name="auth_pass" type="password" style="border: 1px dashed;"></td></tr>
171
																					   <tr><td>&nbsp;</td></tr>
172
																					   <tr>
173
																						 <td colspan="2">
174
																							<center><input name="accept" type="submit" value="Continue"></center>
175
																						 </td>
176
																					   </tr>
177
																					</table>
178
																				</div>
179
																			</center>
180
																		</div>
181
																	</td>
182
																</tr>
183
															</table>
184
														</center>
185
													</div>
186
												</center>
187
											</td>
188
										</tr>
189
									</table>
190
								</center>
191
							</div>
192
						</td>
193
					</tr>
194
				</table>
195
			</center>
196
		</form>
197
	</body> 
198
</html>
199

    
200
EOD;
201

    
202
	return $htmltext;
203
}
204

    
205
function captiveportal_configure() {
206
	global $config, $g;
207

    
208
	$captiveportallck = lock('captiveportal', LOCK_EX);
209
	
210
	if (isset($config['captiveportal']['enable'])) {
211

    
212
		if ($g['booting'])
213
			echo "Starting captive portal... ";
214
		else
215
			captiveportal_syslog("Restarting captive portal.");
216

    
217
		/* kill any running mini_httpd */
218
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
219
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal-SSL.pid");
220

    
221
		/* remove old information */
222
		unlink_if_exists("{$g['vardb_path']}/captiveportal.db");
223
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
224
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip.db");
225
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius.db");
226

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

    
230
		/* kill any running minicron */
231
		killbypid("{$g['varrun_path']}/cp_prunedb.pid");
232

    
233
		/* init ipfw rules */
234
		captiveportal_init_rules(true);
235

    
236
		/* stop accounting on all clients */
237
		captiveportal_radius_stop_all();
238

    
239
		/* initialize minicron interval value */
240
		$croninterval = $config['captiveportal']['croninterval'] ? $config['captiveportal']['croninterval'] : 60;
241

    
242
		/* double check if the $croninterval is numeric and at least 10 seconds. If not we set it to 60 to avoid problems */
243
		if ((!is_numeric($croninterval)) || ($croninterval < 10))
244
			$croninterval = 60;
245

    
246
		/* write portal page */
247
		if ($config['captiveportal']['page']['htmltext'])
248
			$htmltext = base64_decode($config['captiveportal']['page']['htmltext']);
249
		else {
250
			/* example/template page */
251
			$htmltext = get_default_captive_portal_html();
252
		}
253

    
254
		$fd = @fopen("{$g['varetc_path']}/captiveportal.html", "w");
255
		if ($fd) {
256
			// Special case handling.  Convert so that we can pass this page
257
			// through the PHP interpreter later without clobbering the vars.
258
			$htmltext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $htmltext);
259
			$htmltext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $htmltext);
260
			$htmltext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $htmltext);
261
			$htmltext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $htmltext);
262
			$htmltext = str_replace("\$ORIGINAL_PORTAL_IP\$", "#ORIGINAL_PORTAL_IP#", $htmltext);
263
			$htmltext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $htmltext);
264
			if($config['captiveportal']['preauthurl']) {
265
				$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$config['captiveportal']['preauthurl']}", $htmltext);
266
				$htmltext = str_replace("#PORTAL_REDIRURL#", "{$config['captiveportal']['preauthurl']}", $htmltext);
267
			}
268
			fwrite($fd, $htmltext);
269
			fclose($fd);
270
		}
271

    
272
		/* write error page */
273
		if ($config['captiveportal']['page']['errtext'])
274
			$errtext = base64_decode($config['captiveportal']['page']['errtext']);
275
		else {
276
			/* example page  */
277
			$errtext = get_default_captive_portal_html();
278
		}
279

    
280
		$fd = @fopen("{$g['varetc_path']}/captiveportal-error.html", "w");
281
		if ($fd) {
282
			// Special case handling.  Convert so that we can pass this page
283
			// through the PHP interpreter later without clobbering the vars.
284
			$errtext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $errtext);
285
			$errtext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $errtext);
286
			$errtext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $errtext);
287
			$errtext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $errtext);
288
			$errtext = str_replace("\$ORIGINAL_PORTAL_IP\$", "#ORIGINAL_PORTAL_IP#", $errtext);
289
			$errtext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $errtext);
290
			if($config['captiveportal']['preauthurl']) {
291
				$errtext = str_replace("\$PORTAL_REDIRURL\$", "{$config['captiveportal']['preauthurl']}", $errtext);
292
				$errtext = str_replace("#PORTAL_REDIRURL#", "{$config['captiveportal']['preauthurl']}", $errtext);
293
			}
294
			fwrite($fd, $errtext);
295
			fclose($fd);
296
		}
297

    
298
		/* write error page */
299
		if ($config['captiveportal']['page']['logouttext'])
300
			$logouttext = base64_decode($config['captiveportal']['page']['logouttext']);
301
		else {
302
			/* example page */
303
			$logouttext = <<<EOD
304
<HTML>
305
<HEAD><TITLE>Redirecting...</TITLE></HEAD>
306
<BODY>
307
<SPAN STYLE="font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">
308
<B>Redirecting to <A HREF="<?=\$my_redirurl;?>"><?=\$my_redirurl;?></A>...</B>
309
</SPAN>
310
<SCRIPT LANGUAGE="JavaScript">
311
<!--
312
LogoutWin = window.open('', 'Logout', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=256,height=64');
313
if (LogoutWin) {
314
	LogoutWin.document.write('<HTML>');
315
	LogoutWin.document.write('<HEAD><TITLE>Logout</TITLE></HEAD>') ;
316
	LogoutWin.document.write('<BODY BGCOLOR="#435370">');
317
	LogoutWin.document.write('<DIV ALIGN="center" STYLE="color: #ffffff; font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">') ;
318
	LogoutWin.document.write('<B>Click the button below to disconnect</B><P>');
319
	LogoutWin.document.write('<FORM METHOD="POST" ACTION="<?=\$logouturl;?>">');
320
	LogoutWin.document.write('<INPUT NAME="logout_id" TYPE="hidden" VALUE="<?=\$sessionid;?>">');
321
	LogoutWin.document.write('<INPUT NAME="logout" TYPE="submit" VALUE="Logout">');
322
	LogoutWin.document.write('</FORM>');
323
	LogoutWin.document.write('</DIV></BODY>');
324
	LogoutWin.document.write('</HTML>');
325
	LogoutWin.document.close();
326
}
327

    
328
document.location.href="<?=\$my_redirurl;?>";
329
-->
330
</SCRIPT>
331
</BODY>
332
</HTML>
333

    
334
EOD;
335
		}
336

    
337
		$fd = @fopen("{$g['varetc_path']}/captiveportal-logout.html", "w");
338
		if ($fd) {
339
			fwrite($fd, $logouttext);
340
			fclose($fd);
341
		}
342
		/* write elements */
343
		captiveportal_write_elements();
344

    
345
		/* start up the webserving daemon */
346
		captiveportal_init_webgui();
347

    
348
		/* Kill any existing prunecaptiveportal processes */
349
		if(file_exists("{$g['varrun_path']}/cp_prunedb.pid"))
350
			killbypid("{$g['varrun_path']}/cp_prunedb.pid");
351

    
352
		/* start pruning process (interval defaults to 60 seconds) */
353
		mwexec("/usr/local/bin/minicron $croninterval {$g['varrun_path']}/cp_prunedb.pid " .
354
			"/etc/rc.prunecaptiveportal");
355

    
356
		/* generate radius server database */
357
		captiveportal_init_radius_servers();
358

    
359
		if ($g['booting'])
360
			echo "done\n";
361

    
362
	} else {
363
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
364
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal-SSL.pid");
365
		killbypid("{$g['varrun_path']}/cp_prunedb.pid");
366

    
367
		captiveportal_radius_stop_all();
368

    
369
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
370

    
371
		/* unload ipfw */
372
		if (is_module_loaded("ipfw.ko"))		
373
			mwexec("/sbin/kldunload ipfw.ko");
374
		$listifs = get_configured_interface_list_by_realif();
375
		foreach ($listifs as $listrealif => $listif) {
376
			if (!empty($listrealif)) {
377
				if (does_interface_exist($listrealif)) {
378
					pfSense_interface_flags($listrealif, -IFF_IPFW_FILTER);
379
					$carpif = link_ip_to_carp_interface(find_interface_ip($listrealif));
380
					if (!empty($carpif)) {
381
						$carpsif = explode(" ", $carpif);
382
						foreach ($carpsif as $cpcarp)
383
							pfSense_interface_flags($cpcarp, -IFF_IPFW_FILTER);
384
					}
385
				}
386
			}
387
		}
388
	}
389

    
390
	unlock($captiveportallck);
391
	
392
	return 0;
393
}
394

    
395
function captiveportal_init_webgui() {
396
	global $g, $config;
397

    
398
	 if (!isset($config['captiveportal']['enable']))
399
				return;
400

    
401
	if ($config['captiveportal']['maxprocperip'])
402
		$maxproc = $config['captiveportal']['maxprocperip'];
403
	else
404
		$maxproc = 16;
405

    
406
	$use_fastcgi = true;
407

    
408
	if (isset($config['captiveportal']['httpslogin'])) {
409
		$cert = base64_decode($config['captiveportal']['certificate']);
410
		if (isset($config['captiveportal']['cacertificate']))
411
			$cacert = base64_decode($config['captiveportal']['cacertificate']);
412
		else
413
			$cacert = "";
414
		$key = base64_decode($config['captiveportal']['private-key']);
415
		/* generate lighttpd configuration */
416
		system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal-SSL.conf",
417
			$cert, $key, $cacert, "lighty-CaptivePortal-SSL.pid", "8001", "/usr/local/captiveportal/",
418
			"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
419
	}
420

    
421
	/* generate lighttpd configuration */
422
	system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal.conf",
423
		"", "", "", "lighty-CaptivePortal.pid", "8000", "/usr/local/captiveportal/",
424
		"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
425

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

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

    
434
/* reinit will disconnect all users, be careful! */
435
function captiveportal_init_rules($reinit = false) {
436
	global $config, $g;
437

    
438
	if (!isset($config['captiveportal']['enable']))
439
		return;
440

    
441
	$cpips = array();
442
	$ifaces = get_configured_interface_list();
443
	foreach ($ifaces as $kiface => $kiface2) {
444
		$tmpif = get_real_interface($kiface);
445
		pfSense_interface_flags($tmpif, -IFF_IPFW_FILTER);
446
	}
447
	$cpinterfaces = explode(",", $config['captiveportal']['interface']);
448
	$firsttime = 0;
449
	foreach ($cpinterfaces as $cpifgrp) {
450
		if (!isset($ifaces[$cpifgrp]))
451
			continue;
452
		$tmpif = get_real_interface($cpifgrp);
453
		if (!empty($tmpif)) {
454
			if ($firsttime > 0)
455
				$cpinterface .= " or ";
456
			$cpinterface .= "via {$tmpif}";
457
			$firsttime = 1;
458
			$cpipm = get_interface_ip($cpifgrp);
459
			if (is_ipaddr($cpipm)) {
460
				$carpif = link_ip_to_carp_interface($cpipm);
461
				if (!empty($carpif)) {
462
					$carpsif = explode(" ", $carpif);
463
					foreach ($carpsif as $cpcarp) {
464
						pfSense_interface_flags($cpcarp, IFF_IPFW_FILTER);
465
						$carpip = find_interface_ip($cpcarp);
466
						if (is_ipaddr($carpip))
467
							$cpips[] = $carpip;
468
					}
469
				}
470
				$cpips[] = $cpipm;
471
				pfSense_interface_flags($tmpif, IFF_IPFW_FILTER);
472
			}
473
		}
474
	}
475
	if (count($cpips) > 0) {
476
		$cpactive = true;
477
		$cpinterface = "{ {$cpinterface} } ";
478
		} else
479
		return false;
480

    
481
	if ($reinit == false)
482
		$captiveportallck = lock('captiveportal');
483

    
484
	/* init dummynet/ipfw rules number database */
485
	captiveportal_init_ipfw_ruleno();
486

    
487
	/* make sure ipfw is loaded */
488
	filter_load_ipfw();
489
	/* Always load dummynet now that even allowed ip and mac passthrough use it. */
490
	if (!is_module_loaded("dummynet.ko"))
491
		mwexec("/sbin/kldload dummynet");
492

    
493
	$cprules =	"add 65291 set 1 allow pfsync from any to any\n";
494
	$cprules .= "add 65292 set 1 allow carp from any to any\n";
495

    
496
	$cprules .= <<<EOD
497
# add 65300 set 1 skipto 65534 all from any to any not layer2
498
# layer 2: pass ARP
499
add 65301 set 1 pass layer2 mac-type arp
500
# pfsense requires for WPA
501
add 65302 set 1 pass layer2 mac-type 0x888e
502
add 65303 set 1 pass layer2 mac-type 0x88c7
503

    
504
# PPP Over Ethernet Discovery Stage
505
add 65304 set 1 pass layer2 mac-type 0x8863
506
# PPP Over Ethernet Session Stage
507
add 65305 set 1 pass layer2 mac-type 0x8864
508

    
509
# layer 2: block anything else non-IP
510
add 65307 set 1 deny layer2 not mac-type ip
511

    
512
EOD;
513

    
514
	$rulenum = 65310;
515
	$ipcount = 0;
516
	$ips = "";
517
	foreach ($cpips as $cpip) {
518
		if($ipcount == 0) {
519
			$ips = "{$cpip} ";
520
		} else {
521
			$ips .= "or {$cpip} ";
522
		}
523
		$ipcount++;
524
	}
525
	$ips = "{ 255.255.255.255 or {$ips} }";
526
	$cprules .= "add {$rulenum} set 1 pass ip from any to {$ips} in\n";
527
	$rulenum++;
528
	$cprules .= "add {$rulenum} set 1 pass ip from {$ips} to any out\n";
529
	$rulenum++;
530
	$cprules .= "add {$rulenum} set 1 pass icmp from {$ips} to any out icmptype 0\n";
531
	$rulenum++;
532
	$cprules .= "add {$rulenum} set 1 pass icmp from any to {$ips} in icmptype 8 \n";
533
	$rulenum++;
534
	/* Allowed ips */
535
	$cprules .= "add {$rulenum} allow ip from table(3) to any in\n";
536
	$rulenum++;
537
	$cprules .= "add {$rulenum} allow ip from any to table(4) out\n";
538
	$rulenum++;
539
	$cprules .= "add {$rulenum} pipe tablearg ip from table(5) to any in\n";
540
	$rulenum++;
541
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(6) out\n";
542
	$rulenum++;
543
	$cprules .= "add {$rulenum} allow ip from any to table(7) in\n";
544
	$rulenum++;
545
	$cprules .= "add {$rulenum} allow ip from table(8) to any out\n";
546
	$rulenum++;
547
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(9) in\n";
548
	$rulenum++;
549
	$cprules .= "add {$rulenum} pipe tablearg ip from table(10) to any out\n";
550
	$rulenum++;
551

    
552
	/* Authenticated users rules. */
553
	if (isset($config['captiveportal']['peruserbw'])) {
554
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from table(1) to any in\n";
555
		$rulenum++;
556
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from any to table(2) out\n";
557
		$rulenum++;
558
	} else {
559
		$cprules .= "add {$rulenum} set 1 allow ip from table(1) to any in\n";
560
		$rulenum++;
561
		$cprules .= "add {$rulenum} set 1 allow ip from any to table(2) out\n";
562
		$rulenum++;
563
	}
564
	
565
	   $cprules .= <<<EOD
566

    
567
# redirect non-authenticated clients to captive portal
568
add 65531 set 1 fwd 127.0.0.1,8000 tcp from any to any in
569
# let the responses from the captive portal web server back out
570
add 65532 set 1 pass tcp from any to any out
571
# block everything else
572
add 65533 set 1 deny all from any to any
573
# pass everything else on layer2
574
add 65534 set 1 pass all from any to any layer2
575

    
576
EOD;
577

    
578
	/* generate passthru mac database */
579
	$cprules .= captiveportal_passthrumac_configure(true);
580
	$cprules .= "\n";
581

    
582
	/* allowed ipfw rules to make allowed ip work */
583
	$cprules .= captiveportal_allowedip_configure();
584

    
585
	/* allowed ipfw rules to make allowed hostnames work */
586
	$cprules .= captiveportal_allowedhostname_configure();
587
	
588
	/* load rules */
589
	if ($reinit == true)
590
		$cprules = "table all flush\nflush\n{$cprules}";
591
	else {
592
		$tmprules = "table 3 flush\n";
593
		$tmprules .= "table 4 flush\n";
594
		$tmprules .= "table 5 flush\n";
595
		$tmprules .= "table 6 flush\n";
596
		$tmprules .= "table 7 flush\n";
597
		$tmprules .= "table 8 flush\n";
598
		$tmprules .= "table 9 flush\n";
599
		$tmprules .= "table 10 flush\n";
600
		$tmprules .= "flush\n";
601
		$cprules = "{$tmprules}\n{$cprules}";
602
	}
603

    
604
	file_put_contents("{$g['tmp_path']}/ipfw.cp.rules", $cprules);
605
	mwexec("/sbin/ipfw -q {$g['tmp_path']}/ipfw.cp.rules", true);
606
	//@unlink("{$g['tmp_path']}/ipfw.cp.rules");
607

    
608
	if ($reinit == false)
609
		unlock($captiveportallck);
610

    
611
	/* activate ipfw(4) so CP can work */
612
	mwexec("/sbin/sysctl net.link.ether.ipfw=1");
613

    
614
	return $cprules;
615
}
616

    
617
/* remove clients that have been around for longer than the specified amount of time
618
 * db file structure:
619
 * timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time
620
 * (password is in Base64 and only saved when reauthentication is enabled)
621
 */
622
function captiveportal_prune_old() {
623
	global $g, $config;
624

    
625
	/* check for expired entries */
626
	if (empty($config['captiveportal']['timeout']) ||
627
	!is_numeric($config['captiveportal']['timeout']))
628
		$timeout = 0;
629
	else
630
		$timeout = $config['captiveportal']['timeout'] * 60;
631

    
632
	if (empty($config['captiveportal']['idletimeout']) ||
633
	!is_numeric($config['captiveportal']['idletimeout']))
634
		$idletimeout = 0;
635
	else
636
		$idletimeout = $config['captiveportal']['idletimeout'] * 60;
637

    
638
	if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && 
639
	!isset($config['captiveportal']['radiussession_timeout']) && !isset($config['voucher']['enable']))
640
		return;
641

    
642
	$radiusservers = captiveportal_get_radius_servers();
643

    
644
	/* read database */
645
	$cpdb = captiveportal_read_db();
646

    
647
	/*	To make sure we iterate over ALL accounts on every run the count($cpdb) is moved
648
	 *	outside of the loop. Otherwise the loop would evaluate count() on every iteration
649
	 *	and since $i would increase and count() would decrement they would meet before we
650
	 *	had a chance to iterate over all accounts.
651
	 */
652
	$unsetindexes = array();
653
	$voucher_needs_sync = false;
654
	/* 
655
	 * Snapshot the time here to use for calculation to speed up the process.
656
	 * If something is missed next run will catch it!
657
	 */
658
	$pruning_time = time();
659
	$stop_time = $pruning_time;
660
	foreach ($cpdb as $cpentry) {
661

    
662
		$timedout = false;
663
		$term_cause = 1;
664

    
665
		/* hard timeout? */
666
		if ($timeout) {
667
			if (($pruning_time - $cpentry[0]) >= $timeout) {
668
				$timedout = true;
669
				$term_cause = 5; // Session-Timeout
670
			}
671
		}
672

    
673
		/* Session-Terminate-Time */
674
		if (!$timedout && !empty($cpentry[9])) {
675
			if ($pruning_time >= $cpentry[9]) {
676
				$timedout = true;
677
				$term_cause = 5; // Session-Timeout
678
			}
679
		}
680

    
681
		/* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
682
		$uidletimeout = (is_numeric($cpentry[8])) ? $cpentry[8] : $idletimeout;
683
		/* if an idle timeout is specified, get last activity timestamp from ipfw */
684
		if (!$timedout && $uidletimeout) {
685
			$lastact = captiveportal_get_last_activity($cpentry[2]);
686
			/*	If the user has logged on but not sent any traffic they will never be logged out.
687
			 *	We "fix" this by setting lastact to the login timestamp. 
688
			 */
689
			$lastact = $lastact ? $lastact : $cpentry[0];
690
			if ($lastact && (($pruning_time - $lastact) >= $uidletimeout)) {
691
				$timedout = true;
692
				$term_cause = 4; // Idle-Timeout
693
				$stop_time = $lastact; // Entry added to comply with WISPr
694
			}
695
		}
696

    
697
		/* if vouchers are configured, activate session timeouts */
698
		if (!$timedout && isset($config['voucher']['enable']) && !empty($cpentry[7])) {
699
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
700
				$timedout = true;
701
				$term_cause = 5; // Session-Timeout
702
				$voucher_needs_sync = true;
703
			}
704
		}
705

    
706
		/* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
707
		if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpentry[7])) {
708
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
709
				$timedout = true;
710
				$term_cause = 5; // Session-Timeout
711
			}
712
		}
713

    
714
		if ($timedout) {
715
			captiveportal_disconnect($cpentry, $radiusservers,$term_cause,$stop_time);
716
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
717
			$unsetindexes[] = $cpentry[5];
718
		}
719

    
720
		/* do periodic RADIUS reauthentication? */
721
		if (!$timedout && !empty($radiusservers)) {
722
			if (isset($config['captiveportal']['radacct_enable'])) {
723
				if ($config['captiveportal']['reauthenticateacct'] == "stopstart") {
724
					/* stop and restart accounting */
725
					RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
726
						$cpentry[4], // username
727
						$cpentry[5], // sessionid
728
						$cpentry[0], // start time
729
						$radiusservers,
730
						$cpentry[2], // clientip
731
						$cpentry[3], // clientmac
732
						10); // NAS Request
733
					exec("/sbin/ipfw table 1 entryzerostats {$cpentry[2]}");
734
					exec("/sbin/ipfw table 2 entryzerostats {$cpentry[2]}");
735
					RADIUS_ACCOUNTING_START($cpentry[1], // ruleno
736
						$cpentry[4], // username
737
						$cpentry[5], // sessionid
738
						$radiusservers,
739
						$cpentry[2], // clientip
740
						$cpentry[3]); // clientmac
741
				} else if ($config['captiveportal']['reauthenticateacct'] == "interimupdate") {
742
					RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
743
						$cpentry[4], // username
744
						$cpentry[5], // sessionid
745
						$cpentry[0], // start time
746
						$radiusservers,
747
						$cpentry[2], // clientip
748
						$cpentry[3], // clientmac
749
						10, // NAS Request
750
						true); // Interim Updates
751
				}
752
			}
753

    
754
			/* check this user against RADIUS again */
755
			if (isset($config['captiveportal']['reauthenticate'])) {
756
				$auth_list = RADIUS_AUTHENTICATION($cpentry[4], // username
757
					base64_decode($cpentry[6]), // password
758
					$radiusservers,
759
					$cpentry[2], // clientip
760
					$cpentry[3], // clientmac
761
					$cpentry[1]); // ruleno
762
				if ($auth_list['auth_val'] == 3) {
763
					captiveportal_disconnect($cpentry, $radiusservers, 17);
764
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
765
					$unsetindexes[] = $cpentry[5];
766
				} else if ($auth_list['auth_val'] == 2) // SUCCESS, check if any optional attributes are returned
767
					captiveportal_reapply_attributes($cpentry, $auth_list);
768
			}
769
		}
770
	}
771

    
772
	if ($voucher_needs_sync == true)
773
		/* Triger a sync of the vouchers on config */
774
		send_event("service sync vouchers");
775

    
776
	/* write database */
777
	if (!empty($unsetindexes))
778
		captiveportal_write_db($cpdb, false, $unsetindexes);
779
}
780

    
781
/* remove a single client according to the DB entry */
782
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
783
	global $g, $config;
784

    
785
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
786

    
787
	/* this client needs to be deleted - remove ipfw rules */
788
	if (isset($config['captiveportal']['radacct_enable']) && !empty($radiusservers)) {
789
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
790
			$dbent[4], // username
791
			$dbent[5], // sessionid
792
			$dbent[0], // start time
793
			$radiusservers,
794
			$dbent[2], // clientip
795
			$dbent[3], // clientmac
796
			$term_cause, // Acct-Terminate-Cause
797
			false,
798
			$stop_time);
799
	}
800
	
801
	if (is_ipaddr($dbent[2])) {
802
		/* Delete client's ip entry from tables 3 and 4. */
803
		mwexec("/sbin/ipfw table 1 delete {$dbent[2]}");
804
		mwexec("/sbin/ipfw table 2 delete {$dbent[2]}");
805
		/* XXX: Redundant?! Ensure all pf(4) states are killed. */
806
		mwexec("pfctl -k {$dbent[2]}");
807
		mwexec("pfctl -K {$dbent[2]}");
808
	}
809

    
810
	/* 
811
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
812
	* We could get an error if the pipe doesn't exist but everything should still be fine
813
	*/
814
	if (isset($config['captiveportal']['peruserbw'])) {
815
		mwexec("/sbin/ipfw pipe " . ($dbent[1]+20000) . " delete");
816
		mwexec("/sbin/ipfw pipe " . ($dbent[1]+20001) . " delete");
817
	}
818

    
819
	/* Release the ruleno so it can be reallocated to new clients. */
820
	captiveportal_free_ipfw_ruleno($dbent[1]);
821

    
822
	// XMLRPC Call over to the master Voucher node
823
	if(!empty($config['voucher']['vouchersyncdbip'])) {
824
		$syncip   = $config['voucher']['vouchersyncdbip'];
825
		$syncport = $config['voucher']['vouchersyncport'];
826
		$syncpass = $config['voucher']['vouchersyncpass'];
827
		$vouchersyncusername = $config['voucher']['vouchersyncusername'];
828
		$remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time);
829
	}
830

    
831
}
832

    
833
/* remove a single client by sessionid */
834
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
835
	global $g, $config;
836

    
837
	$radiusservers = captiveportal_get_radius_servers();
838
	$unsetindex = array();
839

    
840
	/* read database */
841
	$cpdb = captiveportal_read_db();
842

    
843
	/* find entry */
844
	if (isset($cpdb[$sessionid])) {
845
		$cpentry = $cpdb[$sessionid];
846
		/* write database */
847
		$unsetindex[] = $sessionid;
848
		captiveportal_write_db($cpdb, false, $unsetindex);
849

    
850
		captiveportal_disconnect($cpentry, $radiusservers, $term_cause);
851
		captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
852
	}		
853
}
854

    
855
/* send RADIUS acct stop for all current clients */
856
function captiveportal_radius_stop_all() {
857
	global $config;
858

    
859
	if (!isset($config['captiveportal']['radacct_enable']))
860
		return;
861

    
862
	$radiusservers = captiveportal_get_radius_servers();
863
	if (!empty($radiusservers)) {
864
		$cpdb = captiveportal_read_db();
865
		foreach ($cpdb as $cpentry) {
866
			RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
867
				$cpentry[4], // username
868
				$cpentry[5], // sessionid
869
				$cpentry[0], // start time
870
				$radiusservers,
871
				$cpentry[2], // clientip
872
				$cpentry[3], // clientmac
873
				7); // Admin Reboot
874
		}
875
	}
876
}
877

    
878
function captiveportal_passthrumac_configure_entry($macent) {
879
	$rules = "";
880
	$enBwup = isset($macent['bw_up']);
881
	$enBwdown = isset($macent['bw_down']);
882
	$actionup = "allow";
883
	$actiondown = "allow";
884

    
885
	$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
886

    
887
	if ($enBwup) {
888
		$bw_up = $ruleno + 20000;
889
		$rules .= "pipe {$bw_up} config bw {$macent['bw_up']}Kbit/s queue 100\n";
890
		$actionup = "pipe {$bw_up}";
891
	}
892
	if ($enBwdown) {
893
		$bw_down = $ruleno + 20001;
894
		$rules .= "pipe {$bw_down} config bw {$macent['bw_down']}Kbit/s queue 100\n";
895
		$actiondown = "pipe {$bw_down}";
896
	}
897
	$rules .= "add {$ruleno} {$actiondown} ip from any to any MAC {$macent['mac']} any\n";
898
	$ruleno++;
899
	$rules .= "add {$ruleno} {$actionup} ip from any to any MAC any {$macent['mac']}\n";
900

    
901
	return $rules;
902
}
903

    
904
function captiveportal_passthrumac_configure($lock = false) {
905
	global $config, $g;
906

    
907
	$rules = "";
908

    
909
	if (is_array($config['captiveportal']['passthrumac'])) {
910
		$macdb = array();
911
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
912
			$rules .= captiveportal_passthrumac_configure_entry($macent);
913
			$macdb[$macent['mac']]['active']  = true;
914

    
915
		}
916
	}
917

    
918
	return $rules;
919
}
920

    
921
function captiveportal_passthrumac_findbyname($username) {
922
	global $config;
923

    
924
	if (is_array($config['captiveportal']['passthrumac'])) {
925
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
926
			if ($macent['username'] == $username)
927
				return $macent;
928
		}
929
	}
930
	return NULL;
931
}
932

    
933
/* 
934
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
935
 * table (5=IN)/(6=OUT) hold allowed ip's with bw limit.
936
 */
937
function captiveportal_allowedip_configure_entry($ipent) {
938

    
939
	/* This function can deal with hostname or ipaddress */
940
	if($ipent['ip']) 	
941
		$ipaddress = $ipent['ip'];
942

    
943
	/*  Instead of copying this entire function for something
944
	 *  easy such as hostname vs ip address add this check
945
	 */
946
	if($ipent['hostname']) {
947
		$ipaddress = gethostbyname($ipent['hostname']);
948
		if(!is_ipaddr($ipaddress)) 
949
			return;
950
	}
951

    
952
	$rules = "";
953
	$enBwup = intval($ipent['bw_up']);
954
	$enBwdown = intval($ipent['bw_down']);
955
	$bw_up = "";
956
	$bw_down = "";
957
	$tablein = array();
958
	$tableout = array();
959

    
960
	if (intval($enBwup) > 0 or intval($enBwdown) > 0)
961
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
962
	else
963
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
964

    
965
	if ($ipent['dir'] == "from") {
966
		if ($enBwup)
967
			$tablein[] = 5;
968
		else
969
			$tablein[] = 3;
970
		if ($enBwdown)
971
			$tableout[] = 6;
972
		else
973
			$tableout[] = 4;
974
	} else if ($ipent['dir'] == "to") {
975
		if ($enBwup)
976
			$tablein[] = 9;
977
		else
978
			$tablein[] = 7;
979
		if ($enBwdown)
980
			$tableout[] = 10;
981
		else
982
			$tableout[] = 8;
983
	} else if ($ipent['dir'] == "both") {
984
		if ($enBwup) {
985
			$tablein[] = 5;
986
			$tablein[] = 9;
987
		} else {
988
			$tablein[] = 3;
989
			$tablein[] = 7;
990
		}
991
		if ($enBwdown) {
992
			$tableout[] = 6;
993
			$tableout[] = 10;
994
		} else {
995
			$tableout[] = 4;
996
			$tableout[] = 8;
997
		}
998
	}
999
	if ($enBwup) {
1000
		$bw_up = $ruleno + 20000;
1001
		$rules .= "pipe {$bw_up} config bw {$ipent['bw_up']}Kbit/s queue 100\n";
1002
	}
1003
	$subnet = "";
1004
	if (!empty($ipent['sn']))
1005
		$subnet = "/{$ipent['sn']}";
1006
	foreach ($tablein as $table)
1007
		$rules .= "table {$table} add {$ipaddress}{$subnet} {$bw_up}\n";
1008
	if ($enBwdown) {
1009
		$bw_down = $ruleno + 20001;
1010
		$rules .= "pipe {$bw_down} config bw {$ipent['bw_down']}Kbit/s queue 100\n";
1011
	}
1012
	foreach ($tableout as $table)
1013
		$rules .= "table {$table} add {$ipaddress}{$subnet} {$bw_down}\n";
1014

    
1015
	return $rules;
1016
}
1017

    
1018
/* 
1019
	Adds a dnsfilter entry and watches for hostname changes.
1020
	A change results in reloading the ruleset.
1021
*/
1022
function setup_dnsfilter_entries() {
1023
	global $g, $config;
1024

    
1025
	$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-captiveportal.conf";
1026
	$cp_filterdns_conf = "";
1027
	if (is_array($config['captiveportal']['allowedhostname'])) {
1028
		foreach ($config['captiveportal']['allowedhostname'] as $hostnameent)  {
1029
			$cp_filterdns_conf .= "ipfw {$hostnameent['hostname']} 3\n";
1030
			$cp_filterdns_conf .= "ipfw {$hostnameent['hostname']} 4\n";
1031
			$cp_filterdns_conf .= "ipfw {$hostnameent['hostname']} 7\n";
1032
			$cp_filterdns_conf .= "ipfw {$hostnameent['hostname']} 8\n";
1033
		}
1034
	}
1035
	file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1036
	killbypid("{$g['tmp_path']}/filterdns-cpah.pid");
1037
	mwexec("/usr/local/sbin/filterdns -p {$g['tmp_path']}/filterdns-cpah.pid -i 300 -c {$cp_filterdns_filename} -d 1");
1038
}
1039

    
1040
function captiveportal_allowedhostname_configure() {
1041
	global $config, $g;
1042

    
1043
	$rules = "\n# captiveportal_allowedhostname_configure()\n";
1044
	setup_dnsfilter_entries();
1045
	if (is_array($config['captiveportal']['allowedhostname'])) {
1046
		foreach ($config['captiveportal']['allowedhostname'] as $hostnameent) 
1047
			$rules .= captiveportal_allowedip_configure_entry($hostnameent);
1048
	}
1049
	return $rules;
1050
}
1051

    
1052
function captiveportal_allowedip_configure() {
1053
	global $config, $g;
1054

    
1055
	$rules = "";
1056
	if (is_array($config['captiveportal']['allowedip'])) {
1057
		foreach ($config['captiveportal']['allowedip'] as $ipent) 
1058
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1059
	}
1060

    
1061
	return $rules;
1062
}
1063

    
1064
/* get last activity timestamp given client IP address */
1065
function captiveportal_get_last_activity($ip) {
1066

    
1067
	$ipfwoutput = "";
1068

    
1069
	exec("/sbin/ipfw table 1 entrystats {$ip} 2>/dev/null", $ipfwoutput);
1070
	/* Reading only from one of the tables is enough of approximation. */
1071
	if ($ipfwoutput[0]) {
1072
		$ri = explode(" ", $ipfwoutput[0]);
1073
		if ($ri[4])
1074
			return $ri[4];
1075
	}
1076

    
1077
	return 0;
1078
}
1079

    
1080
function captiveportal_init_radius_servers() {
1081
	global $config, $g;
1082

    
1083
	/* generate radius server database */
1084
	if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
1085
		($config['captiveportal']['auth_method'] == "radius"))) {
1086
		$radiusip = $config['captiveportal']['radiusip'];
1087
		$radiusip2 = ($config['captiveportal']['radiusip2']) ? $config['captiveportal']['radiusip2'] : null;
1088

    
1089
		if ($config['captiveportal']['radiusport'])
1090
			$radiusport = $config['captiveportal']['radiusport'];
1091
		else
1092
			$radiusport = 1812;
1093
		if ($config['captiveportal']['radiusacctport'])
1094
			$radiusacctport = $config['captiveportal']['radiusacctport'];
1095
		else
1096
			$radiusacctport = 1813;
1097
		if ($config['captiveportal']['radiusport2'])
1098
			$radiusport2 = $config['captiveportal']['radiusport2'];
1099
		else
1100
			$radiusport2 = 1812;
1101
		$radiuskey = $config['captiveportal']['radiuskey'];
1102
		$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
1103

    
1104
		$cprdsrvlck = lock('captiveportalradius', LOCK_EX);
1105
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db", "w");
1106
		if (!$fd) {
1107
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1108
			unlock($cprdsrvlck);
1109
			return 1;
1110
		} else if (isset($radiusip2, $radiuskey2))
1111
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . "\n"
1112
			. $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2);
1113
		else
1114
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey);
1115
		fclose($fd);
1116
		unlock($cprdsrvlck);
1117
	}
1118
}
1119

    
1120
/* read RADIUS servers into array */
1121
function captiveportal_get_radius_servers() {
1122
	global $g;
1123

    
1124
	$cprdsrvlck = lock('captiveportalradius');
1125
	if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
1126
		$radiusservers = array();
1127
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius.db", 
1128
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1129
		if ($cpradiusdb) {
1130
			foreach($cpradiusdb as $cpradiusentry) {
1131
				$line = trim($cpradiusentry);
1132
				if ($line) {
1133
					$radsrv = array();
1134
					list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
1135
					$radiusservers[] = $radsrv;
1136
				}
1137
			}
1138
		}
1139
		unlock($cprdsrvlck);
1140
		return $radiusservers;
1141
	}
1142

    
1143
	unlock($cprdsrvlck);
1144
	return false;
1145
}
1146

    
1147
/* log successful captive portal authentication to syslog */
1148
/* part of this code from php.net */
1149
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1150
	// Log it
1151
	if (!$message)
1152
		$message = "$status: $user, $mac, $ip";
1153
	else {
1154
		$message = trim($message);
1155
		$message = "$status: $user, $mac, $ip, $message";
1156
	}
1157
	captiveportal_syslog($message);
1158
}
1159

    
1160
/* log simple messages to syslog */
1161
function captiveportal_syslog($message) {
1162
	define_syslog_variables();
1163
	$message = trim($message);
1164
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1165
	// Log it
1166
	syslog(LOG_INFO, $message);
1167
	closelog();
1168
}
1169

    
1170
function radius($username,$password,$clientip,$clientmac,$type) {
1171
	global $g, $config;
1172

    
1173
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1174

    
1175
	/* If the pool is empty, return appropriate message and fail authentication */
1176
	if (is_null($ruleno)) {
1177
		$auth_list = array();
1178
		$auth_list['auth_val'] = 1;
1179
		$auth_list['error'] = "System reached maximum login capacity";
1180
		return $auth_list;
1181
	}
1182

    
1183
	$radiusservers = captiveportal_get_radius_servers();
1184

    
1185
	$auth_list = RADIUS_AUTHENTICATION($username,
1186
		$password,
1187
		$radiusservers,
1188
		$clientip,
1189
		$clientmac,
1190
		$ruleno);
1191

    
1192
	if ($auth_list['auth_val'] == 2) {
1193
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1194
		$sessionid = portal_allow($clientip,
1195
			$clientmac,
1196
			$username,
1197
			$password,
1198
			$auth_list,
1199
			$ruleno);
1200
	}
1201

    
1202
	return $auth_list;
1203
}
1204

    
1205
/* read captive portal DB into array */
1206
function captiveportal_read_db($locked = false, $index = 5 /* sessionid by default */) {
1207
	global $g;
1208

    
1209
	$cpdb = array();
1210

    
1211
	if ($locked == false)
1212
		$cpdblck = lock('captiveportaldb');
1213
	$fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
1214
	if ($fd) {
1215
		while (!feof($fd)) {
1216
			$line = trim(fgets($fd));
1217
			if ($line) {
1218
				$cpe = explode(",", $line);
1219
				/* Hash by session id */
1220
				$cpdb[$cpe[$index]] = $cpe;
1221
			}
1222
		}
1223
		fclose($fd);
1224
	}
1225
	if ($locked == false)
1226
		unlock($cpdblck);
1227
	return $cpdb;
1228
}
1229

    
1230
/* write captive portal DB */
1231
function captiveportal_write_db($cpdb, $locked = false, $remove = false) {
1232
	global $g;
1233

    
1234
	if ($locked == false)
1235
		$cpdblck = lock('captiveportaldb', LOCK_EX);
1236

    
1237
	if (is_array($remove)) {
1238
		if (!empty($remove)) {
1239
			$cpdb = captiveportal_read_db(true);
1240
			foreach ($remove as $key) {
1241
				if (is_array($key))
1242
					log_error("Captive portal Array passed as unset index: " . print_r($key, true));
1243
				else
1244
					unset($cpdb[$key]);
1245
			}
1246
		} else {
1247
			if ($locked == false)
1248
				unlock($cpdblck);
1249
			return; //This makes sure no record removal calls
1250
		}
1251
	}
1252
	$fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
1253
	if ($fd) {
1254
		foreach ($cpdb as $cpent) {
1255
			fwrite($fd, join(",", $cpent) . "\n");
1256
		}
1257
		fclose($fd);
1258
	}
1259
	if ($locked == false)
1260
		unlock($cpdblck);
1261
}
1262

    
1263
function captiveportal_write_elements() {
1264
	global $g, $config;
1265
	
1266
	/* delete any existing elements */
1267
	if (is_dir($g['captiveportal_element_path'])) {
1268
		$dh = opendir($g['captiveportal_element_path']);
1269
		while (($file = readdir($dh)) !== false) {
1270
			if ($file != "." && $file != "..")
1271
				unlink($g['captiveportal_element_path'] . "/" . $file);
1272
		}
1273
		closedir($dh);
1274
	} else {
1275
		@mkdir($g['captiveportal_element_path']);
1276
	}
1277

    
1278
	if (is_array($config['captiveportal']['element'])) {
1279
		conf_mount_rw();
1280
		foreach ($config['captiveportal']['element'] as $data) {
1281
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
1282
			if (!$fd) {
1283
				printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
1284
				return 1;
1285
			}
1286
			$decoded = base64_decode($data['content']);
1287
			fwrite($fd,$decoded);
1288
			fclose($fd);
1289
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1290
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1291
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
1292
		}
1293
		conf_mount_ro();
1294
	}
1295
	
1296
	return 0;
1297
}
1298

    
1299
function captiveportal_init_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
1300
	global $g;
1301

    
1302
	@unlink("{$g['vardb_path']}/captiveportal.rules");
1303
	$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1304
	file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1305
}
1306

    
1307
/*
1308
 * This function will calculate the lowest free firewall ruleno
1309
 * within the range specified based on the actual logged on users
1310
 *
1311
 */
1312
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899, $usebw = false) {
1313
	global $config, $g;
1314

    
1315
	if(!isset($config['captiveportal']['enable']))
1316
		return NULL;
1317

    
1318
	$cpruleslck = lock('captiveportalrules', LOCK_EX);
1319
	$ruleno = 0;
1320
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1321
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1322
		for ($ridx = 2; $ridx < ($rulenos_range_max - $rulenos_start); $ridx++) {
1323
			if ($rules[$ridx]) {
1324
				/* 
1325
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1326
				 * and the out pipe ruleno + 1. This removes limitation that where present in 
1327
				 * previous version of the peruserbw.
1328
				 */
1329
				if (isset($config['captiveportal']['peruserbw']) || $usebw == true)
1330
					$ridx++;
1331
				continue;
1332
			}
1333
			$ruleno = $ridx;
1334
			$rules[$ridx] = "used";
1335
			if (isset($config['captiveportal']['peruserbw']) || $usebw == true)
1336
				$rules[++$ridx] = "used";
1337
			break;
1338
		}
1339
	} else {
1340
		$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1341
		$rules[2] = "used";
1342
		$ruleno = 2;
1343
	}
1344
	file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1345
	unlock($cpruleslck);
1346
	return $ruleno;
1347
}
1348

    
1349
function captiveportal_free_ipfw_ruleno($ruleno, $usedbw = false) {
1350
	global $config, $g;
1351

    
1352
	if(!isset($config['captiveportal']['enable']))
1353
		return NULL;
1354

    
1355
	$cpruleslck = lock('captiveportalrules', LOCK_EX);
1356
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1357
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1358
		$rules[$ruleno] = false;
1359
		if (isset($config['captiveportal']['peruserbw']) || $usedbw == true)
1360
			$rules[++$ruleno] = false;
1361
		file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1362
	}
1363
	unlock($cpruleslck);
1364
}
1365

    
1366
function captiveportal_get_ipfw_passthru_ruleno($value) {
1367
	global $config, $g;
1368

    
1369
	if(!isset($config['captiveportal']['enable']))
1370
		return NULL;
1371

    
1372
	$cpruleslck = lock('captiveportalrules', LOCK_EX);
1373
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1374
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1375
		$ruleno = intval(`/sbin/ipfw show | /usr/bin/grep {$value} |  /usr/bin/grep -v grep | /usr/bin/cut -d " " -f 1 | /usr/bin/head -n 1`);
1376
		if ($rules[$ruleno]) {
1377
			unlock($cpruleslck);
1378
			return $ruleno;
1379
		}
1380
	}
1381

    
1382
	unlock($cpruleslck);
1383
	return NULL;
1384
}
1385

    
1386
/**
1387
 * This function will calculate the traffic produced by a client
1388
 * based on its firewall rule
1389
 *
1390
 * Point of view: NAS
1391
 *
1392
 * Input means: from the client
1393
 * Output means: to the client
1394
 *
1395
 */
1396

    
1397
function getVolume($ip) {
1398

    
1399
	$volume = array();
1400

    
1401
	// Initialize vars properly, since we don't want NULL vars
1402
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1403

    
1404
	// Ingress
1405
	$ipfwin = "";
1406
	$ipfwout = "";
1407
	$matchesin = "";
1408
	$matchesout = "";
1409
	exec("/sbin/ipfw table 1 entrystats {$ip}", $ipfwin);
1410
	if ($ipfwin[0]) {
1411
		$ipfwin = split(" ", $ipfwin[0]);
1412
		$volume['input_pkts'] = $ipfwin[2];
1413
		$volume['input_bytes'] = $ipfwin[3];
1414
	}
1415

    
1416
	exec("/sbin/ipfw table 2 entrystats {$ip}", $ipfwout);
1417
	if ($ipfwout[0]) {
1418
		$ipfwout = split(" ", $ipfwout[0]);
1419
		$volume['output_pkts'] = $ipfwout[2];
1420
		$volume['output_bytes'] = $ipfwout[3];
1421
	}
1422

    
1423
	return $volume;
1424
}
1425

    
1426
/**
1427
 * Get the NAS-Identifier
1428
 *
1429
 * We will use our local hostname to make up the nas_id
1430
 */
1431
function getNasID()
1432
{
1433
	$nasId = "";
1434
	exec("/bin/hostname", $nasId);
1435
	if(!$nasId[0])
1436
		$nasId[0] = "{$g['product_name']}";
1437
	return $nasId[0];
1438
}
1439

    
1440
/**
1441
 * Get the NAS-IP-Address based on the current wan address
1442
 *
1443
 * Use functions in interfaces.inc to find this out
1444
 *
1445
 */
1446

    
1447
function getNasIP()
1448
{
1449
	global $config;
1450

    
1451
	if (empty($config['captiveportal']['radiussrcip_attribute'])) {
1452
			$nasIp = get_interface_ip();
1453
	} else {
1454
		if (is_ipaddr($config['captiveportal']['radiussrcip_attribute']))
1455
			$nasIp = $config['captiveportal']['radiussrcip_attribute'];
1456
		else
1457
			$nasIp = get_interface_ip($config['captiveportal']['radiussrcip_attribute']);
1458
	}
1459
		
1460
	if(!is_ipaddr($nasIp))
1461
		$nasIp = "0.0.0.0";
1462

    
1463
	return $nasIp;
1464
}
1465

    
1466
function portal_ip_from_client_ip($cliip) {
1467
	global $config;
1468

    
1469
	$interfaces = explode(",", $config['captiveportal']['interface']);
1470
	foreach ($interfaces as $cpif) {
1471
		$ip = get_interface_ip($cpif);
1472
		$sn = get_interface_subnet($cpif);
1473
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1474
			return $ip;
1475
	}
1476

    
1477
	$iface = exec_command("/sbin/route -n get {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1478
	$iface = trim($iface, "\n");
1479
	if (!empty($iface)) {
1480
		$ip = find_interface_ip($iface);
1481
		if (is_ipaddr($ip))
1482
			return $ip;
1483
	}
1484

    
1485
	// doesn't match up to any particular interface
1486
	// so let's set the portal IP to what PHP says 
1487
	// the server IP issuing the request is. 
1488
	// allows same behavior as 1.2.x where IP isn't 
1489
	// in the subnet of any CP interface (static routes, etc.)
1490
	// rather than forcing to DNS hostname resolution
1491
	$ip = $_SERVER['SERVER_ADDR'];
1492
	if (is_ipaddr($ip))
1493
		return $ip;
1494

    
1495
	return false;
1496
}
1497

    
1498
/* functions move from index.php */
1499

    
1500
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1501
	global $g, $config;
1502

    
1503
	/* Get captive portal layout */
1504
	if ($type == "redir") {
1505
		header("Location: {$redirurl}");
1506
		return;
1507
	} else if ($type == "login")
1508
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal.html");
1509
	else
1510
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-error.html");
1511

    
1512
	/* substitute the PORTAL_REDIRURL variable */
1513
	if ($config['captiveportal']['preauthurl']) {
1514
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$config['captiveportal']['preauthurl']}", $htmltext);
1515
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$config['captiveportal']['preauthurl']}", $htmltext);
1516
	}
1517

    
1518
	/* substitute other variables */
1519
	if (isset($config['captiveportal']['httpslogin'])) {
1520
		$htmltext = str_replace("\$PORTAL_ACTION\$", "https://{$config['captiveportal']['httpsname']}:8001/", $htmltext);
1521
		$htmltext = str_replace("#PORTAL_ACTION#", "https://{$config['captiveportal']['httpsname']}:8001/", $htmltext);
1522
	} else {
1523
		$ifip = portal_ip_from_client_ip($clientip);
1524
		if (!$ifip)
1525
			$ourhostname = $config['system']['hostname'] . ":8000";
1526
		else
1527
			$ourhostname = "{$ifip}:8000";
1528
		$htmltext = str_replace("\$PORTAL_ACTION\$", "http://{$ourhostname}/", $htmltext);
1529
		$htmltext = str_replace("#PORTAL_ACTION#", "http://{$ourhostname}/", $htmltext);
1530
	}
1531

    
1532
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1533
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1534
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1535
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1536

    
1537
	// Special handling case for captive portal master page so that it can be ran 
1538
	// through the PHP interpreter using the include method above.  We convert the
1539
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1540
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1541
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1542
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1543
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1544
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1545
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1546

    
1547
    echo $htmltext;
1548
}
1549

    
1550
function portal_mac_radius($clientmac,$clientip) {
1551
    global $config ;
1552

    
1553
    $radmac_secret = $config['captiveportal']['radmac_secret'];
1554

    
1555
    /* authentication against the radius server */
1556
    $username = mac_format($clientmac);
1557
    $auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1558
    if ($auth_list['auth_val'] == 2)
1559
        return TRUE;
1560
    if (!empty($auth_list['url_redirection']))
1561
	portal_reply_page($auth_list['url_redirection'], "redir");
1562

    
1563
    return FALSE;
1564
}
1565

    
1566
function captiveportal_reapply_attributes($cpentry, $attributes) {
1567
	global $config;
1568

    
1569
	/* Add rules for traffic shaping
1570
	 * We don't need to add extra rules since traffic will pass due to the following kernel option
1571
	 * net.inet.ip.fw.one_pass: 1
1572
	 */
1573
	$peruserbw = isset($config['captiveportal']['peruserbw']);
1574

    
1575
	$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $config['captiveportal']['bwdefaultup'];
1576
	$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $config['captiveportal']['bwdefaultdn'];
1577
	$bw_up_pipeno = $cpentry[1]+20000;
1578
	$bw_down_pipeno = $cpentry[1]+20001;
1579
	$commands = "";
1580

    
1581
	if ($peruserbw && !empty($bw_up) && is_numeric($bw_up)) {
1582
		$commands .= "pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100\n";
1583

    
1584
		if (!isset($config['captiveportal']['nomacfilter'])) {
1585
			$commands .= "table 1 del {$cpentry[2]} mac {$cpentry[3]}\n";
1586
			$commands .= "table 1 add {$cpentry[2]} mac {$cpentry[3]} {$bw_up_pipeno}\n";
1587
		} else {
1588
			$commands .= "table 1 del {$cpentry[2]}\n";
1589
			$commands .= "table 1 add {$cpentry[2]} {$bw_up_pipeno}\n";
1590
		}
1591
	}
1592
	if ($peruserbw && !empty($bw_down) && is_numeric($bw_down)) {
1593
		$commands .= "pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100\n";
1594

    
1595
		if (!isset($config['captiveportal']['nomacfilter'])) {
1596
			$commands .= "table 2 del {$cpentry[2]} mac {$cpentry[3]}\n";
1597
			$commands .= "table 2 add {$cpentry[2]} mac {$cpentry[3]} {$bw_down_pipeno}\n";
1598
		} else {
1599
			$commands .= "table 2 del {$cpentry[2]}\n";
1600
			$commands .= "table 2 add {$cpentry[2]} {$bw_down_pipeno}\n";
1601
		}
1602
	}
1603

    
1604
	if (!empty($commands)) {
1605
		@file_put_contents("{$g['tmp_path']}/reattribute.rule.tmp", $commands);
1606
		mwexec("/sbin/ipfw -q {$g['tmp_path']}/reattribute.rule.tmp");
1607
		captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_BANDWIDTH_REAPPLY", "{$bw_up}/{$bw_down}");
1608
	}
1609

    
1610
	unset($bw_up_pipeno, $bw_Down_pipeno, $bw_up, $bw_down);
1611
}
1612

    
1613
function portal_allow($clientip,$clientmac,$username,$password = null, $attributes = null, $ruleno = null)  {
1614

    
1615
	global $redirurl, $g, $config, $type, $passthrumac, $_POST;
1616

    
1617
	/* See if a ruleno is passed, if not start sessions because this means there isn't one atm */
1618
	if ($ruleno == null)
1619
		$ruleno = captiveportal_get_next_ipfw_ruleno();
1620

    
1621
	/* if the pool is empty, return appropriate message and exit */
1622
	if (is_null($ruleno)) {
1623
		portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1624
		log_error("WARNING!  Captive portal has reached maximum login capacity");
1625
		exit;
1626
	}
1627

    
1628
	// Ensure we create an array if we are missing attributes
1629
	if (!is_array($attributes))
1630
		$attributes = array();
1631

    
1632
	$radiusservers = captiveportal_get_radius_servers();
1633

    
1634
	/* Do not allow concurrent login execution. */
1635
	$cpdblck = lock('captiveportaldb', LOCK_EX);
1636

    
1637
	unset($sessionid);
1638

    
1639
	/* read in client database */
1640
	$cpdb = captiveportal_read_db(true);
1641

    
1642
	if ($attributes['voucher'])
1643
		$remaining_time = $attributes['session_timeout'];
1644

    
1645
	$writecfg = false;
1646
	/* Find an existing session */
1647
	if ((isset($config['captiveportal']['noconcurrentlogins'])) && $passthrumac) {
1648
		if (isset($config['captiveportal']['passthrumacadd'])) {
1649
			$mac = captiveportal_passthrumac_findbyname($username);
1650
			if (!empty($mac)) {
1651
				if ($_POST['replacemacpassthru']) {
1652
					foreach ($config['captiveportal']['passthrumac'] as $idx => $macent) {
1653
						if ($macent['mac'] == $mac['mac']) {
1654
							$macrules = "";
1655
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1656
                                			if ($ruleno) {
1657
								captiveportal_free_ipfw_ruleno($ruleno, true);
1658
                                        			$macrules .= "delete {$ruleno}\n";
1659
								++$ruleno;
1660
                                        			$macrules .= "delete {$ruleno}\n";
1661
                                			}
1662
							unset($config['captiveportal']['passthrumac'][$idx]);
1663
							$mac['mac'] = $clientmac;
1664
							$config['captiveportal']['passthrumac'][] = $mac;
1665
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1666
							file_put_contents("{$g['tmp_path']}/macentry.rules.tmp", $macrules);
1667
							mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry.rules.tmp");
1668
							$writecfg = true;
1669
							$sessionid = true;
1670
							break;
1671
						}
1672
					}
1673
                                } else {
1674
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1675
						$clientmac, $clientip, $username, $password);
1676
					exit;
1677
				}
1678
			}
1679
		}
1680
	}
1681

    
1682
	/* Snaphost the timestamp */
1683
	$allow_time = time();
1684

    
1685
	foreach ($cpdb as $sid => $cpentry) {
1686
		/* on the same ip */
1687
		if($cpentry[2] == $clientip) {
1688
			if (isset($config['captiveportal']['nomacfilter']) || $cpentry[3] == $clientmac)
1689
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1690
			else
1691
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
1692
			$sessionid = $sid;
1693
			break;
1694
		}
1695
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1696
			// user logged in with an active voucher. Check for how long and calculate 
1697
			// how much time we can give him (voucher credit - used time)
1698
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1699
			if ($remaining_time < 0)    // just in case. 
1700
				$remaining_time = 0;
1701

    
1702
			/* This user was already logged in so we disconnect the old one */
1703
			captiveportal_disconnect($cpentry,$radiusservers,13);
1704
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1705
			unset($cpdb[$sid]);
1706
			break;
1707
		}
1708
		elseif ((isset($config['captiveportal']['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1709
			/* on the same username */
1710
			if (strcasecmp($cpentry[4], $username) == 0) {
1711
				/* This user was already logged in so we disconnect the old one */
1712
				captiveportal_disconnect($cpentry,$radiusservers,13);
1713
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1714
				unset($cpdb[$sid]);
1715
				break;
1716
			}
1717
		}
1718
	}
1719

    
1720
	if ($attributes['voucher'] && $remaining_time <= 0)
1721
		return 0;       // voucher already used and no time left
1722

    
1723
	if (!isset($sessionid)) {
1724
		/* generate unique session ID */
1725
		$tod = gettimeofday();
1726
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1727

    
1728
		/* Add rules for traffic shaping
1729
		 * We don't need to add extra rules since traffic will pass due to the following kernel option
1730
		 * net.inet.ip.fw.one_pass: 1
1731
		 */
1732
		$peruserbw = isset($config['captiveportal']['peruserbw']);
1733

    
1734
		$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $config['captiveportal']['bwdefaultup'];
1735
		$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $config['captiveportal']['bwdefaultdn'];
1736

    
1737
		if ($passthrumac) {
1738
			$mac = array();
1739
			$mac['mac'] = $clientmac;
1740
			if (isset($config['captiveportal']['passthrumacaddusername']))
1741
				$mac['username'] = $username;
1742
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1743
			if (!empty($bw_up))
1744
				$mac['bw_up'] = $bw_up;
1745
			if (!empty($bw_down))
1746
				$mac['bw_down'] = $bw_down;
1747
			if (!is_array($config['captiveportal']['passthrumac']))
1748
				$config['captiveportal']['passthrumac'] = array();
1749
			$config['captiveportal']['passthrumac'][] = $mac;
1750
			unlock($cpdblck);
1751
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1752
			@file_put_contents("{$g['tmp_path']}/macentry.rules.tmp", $macrules);
1753
			mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry.rules.tmp");
1754
			$writecfg = true;
1755
		} else {
1756
			if ($peruserbw && !empty($bw_up) && is_numeric($bw_up)) {
1757
				$bw_up_pipeno = $ruleno + 20000;
1758
				//$bw_up /= 1000; // Scale to Kbit/s
1759
				mwexec("/sbin/ipfw pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100");
1760

    
1761
				if (!isset($config['captiveportal']['nomacfilter']))
1762
					mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac} {$bw_up_pipeno}");
1763
				else
1764
					mwexec("/sbin/ipfw table 1 add {$clientip} {$bw_up_pipeno}");
1765
			} else {
1766
				if (!isset($config['captiveportal']['nomacfilter']))
1767
					mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac}");
1768
				else
1769
					mwexec("/sbin/ipfw table 1 add {$clientip}");
1770
			}
1771
			if ($peruserbw && !empty($bw_down) && is_numeric($bw_down)) {
1772
				$bw_down_pipeno = $ruleno + 20001;
1773
				//$bw_down /= 1000; // Scale to Kbit/s
1774
				mwexec("/sbin/ipfw pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100");
1775

    
1776
				if (!isset($config['captiveportal']['nomacfilter']))
1777
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac} {$bw_down_pipeno}");
1778
				else
1779
					mwexec("/sbin/ipfw table 2 add {$clientip} {$bw_down_pipeno}");
1780
			} else {
1781
				if (!isset($config['captiveportal']['nomacfilter']))
1782
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac}");
1783
				else
1784
					mwexec("/sbin/ipfw table 2 add {$clientip}");
1785
			}
1786

    
1787
			if ($attributes['voucher'])
1788
				$attributes['session_timeout'] = $remaining_time;
1789

    
1790
			/* encode password in Base64 just in case it contains commas */
1791
			$bpassword = base64_encode($password);
1792
			$cpdb[] = array($allow_time, $ruleno, $clientip, $clientmac, $username, $sessionid, $bpassword,
1793
				$attributes['session_timeout'], $attributes['idle_timeout'], $attributes['session_terminate_time']);
1794

    
1795
			/* rewrite information to database */
1796
			captiveportal_write_db($cpdb, true);
1797
			unlock($cpdblck);
1798

    
1799
			if (isset($config['captiveportal']['radacct_enable']) && !empty($radiusservers)) {
1800
				$acct_val = RADIUS_ACCOUNTING_START($ruleno,
1801
                                		$username, $sessionid, $radiusservers, $clientip, $clientmac);
1802
				if ($acct_val == 1)
1803
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1804
			}
1805
		}
1806
	} else
1807
		unlock($cpdblck);
1808

    
1809
	if ($writecfg == true)
1810
		write_config();
1811

    
1812
	/* redirect user to desired destination */
1813
	if (!empty($attributes['url_redirection']))
1814
		$my_redirurl = $attributes['url_redirection'];
1815
	else if (!empty($config['captiveportal']['redirurl']))
1816
		$my_redirurl = $config['captiveportal']['redirurl'];
1817
	else
1818
		$my_redirurl = $redirurl;
1819

    
1820
	if(isset($config['captiveportal']['logoutwin_enable']) && !$passthrumac) {
1821

    
1822
		if (isset($config['captiveportal']['httpslogin']))
1823
			$logouturl = "https://{$config['captiveportal']['httpsname']}:8001/";
1824
		else {
1825
			$ifip = portal_ip_from_client_ip($clientip);
1826
			if (!$ifip)
1827
				$ourhostname = $config['system']['hostname'] . ":8000";
1828
			else
1829
				$ourhostname = "{$ifip}:8000";
1830
			$logouturl = "http://{$ourhostname}/";
1831
		}
1832

    
1833
		if (isset($attributes['reply_message']))
1834
			$message = $attributes['reply_message'];
1835
		else
1836
			$message = 0;
1837

    
1838
		include("{$g['varetc_path']}/captiveportal-logout.html");
1839

    
1840
	} else {
1841
		header("Location: " . $my_redirurl);
1842
	}
1843

    
1844
	return $sessionid;
1845
}
1846

    
1847
/*
1848
 * Used for when pass-through credits are enabled.
1849
 * Returns true when there was at least one free login to deduct for the MAC.
1850
 * Expired entries are removed as they are seen.
1851
 * Active entries are updated according to the configuration.
1852
 */
1853
function portal_consume_passthrough_credit($clientmac) {
1854
	global $config;
1855

    
1856
	if (!empty($config['captiveportal']['freelogins_count']) && is_numeric($config['captiveportal']['freelogins_count']))
1857
		$freeloginscount = $config['captiveportal']['freelogins_count'];
1858
	else
1859
		return false;
1860

    
1861
	if (!empty($config['captiveportal']['freelogins_resettimeout']) && is_numeric($config['captiveportal']['freelogins_resettimeout']))
1862
		$resettimeout = $config['captiveportal']['freelogins_resettimeout'];
1863
	else
1864
		return false;
1865

    
1866
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1867
		return false;
1868

    
1869
	$updatetimeouts = isset($config['captiveportal']['freelogins_updatetimeouts']);
1870

    
1871
	/*
1872
	 * Read database of used MACs.  Lines are a comma-separated list
1873
	 * of the time, MAC, then the count of pass-through credits remaining.
1874
	 */
1875
	$usedmacs = captiveportal_read_usedmacs_db();
1876

    
1877
	$currenttime = time();
1878
	$found = false;
1879
	foreach ($usedmacs as $key => $usedmac) {
1880
		$usedmac = explode(",", $usedmac);
1881

    
1882
		if ($usedmac[1] == $clientmac) {
1883
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
1884
				if ($usedmac[2] < 1) {
1885
					if ($updatetimeouts) {
1886
						$usedmac[0] = $currenttime;
1887
						unset($usedmacs[$key]);
1888
						$usedmacs[] = implode(",", $usedmac);
1889
						captiveportal_write_usedmacs_db($usedmacs);
1890
					}
1891

    
1892
					return false;
1893
				} else {
1894
					$usedmac[2] -= 1;
1895
					$usedmacs[$key] = implode(",", $usedmac);
1896
				}
1897

    
1898
				$found = true;
1899
			} else
1900
				unset($usedmacs[$key]);
1901

    
1902
			break;
1903
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
1904
				unset($usedmacs[$key]);
1905
	}
1906

    
1907
	if (!$found) {
1908
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
1909
		$usedmacs[] = implode(",", $usedmac);
1910
	}
1911

    
1912
	captiveportal_write_usedmacs_db($usedmacs);
1913
	return true;
1914
}
1915

    
1916
function captiveportal_read_usedmacs_db() {
1917
	global $g;
1918

    
1919
	$cpumaclck = lock('captiveusedmacs');
1920
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs.db")) {
1921
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1922
		if (!$usedmacs)
1923
			$usedmacs = array();
1924
	} else
1925
		$usedmacs = array();
1926

    
1927
	unlock($cpumaclck);
1928
	return $usedmacs;
1929
}
1930

    
1931
function captiveportal_write_usedmacs_db($usedmacs) {
1932
	global $g;
1933

    
1934
	$cpumaclck = lock('captiveusedmacs', LOCK_EX);
1935
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs.db", implode("\n", $usedmacs));
1936
	unlock($cpumaclck);
1937
}
1938

    
1939
?>
(7-7/62)