Project

General

Profile

Download (60.2 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
	if (!is_module_loaded("ipfw.ko"))
489
		filter_load_ipfw();
490
	/* Always load dummynet now that even allowed ip and mac passthrough use it. */
491
	if (!is_module_loaded("dummynet.ko"))
492
		mwexec("/sbin/kldload dummynet");
493

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

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

    
505
# PPP Over Ethernet Discovery Stage
506
add 65304 set 1 pass layer2 mac-type 0x8863
507
# PPP Over Ethernet Session Stage
508
add 65305 set 1 pass layer2 mac-type 0x8864
509
# Allow WPA
510
add 65306 set 1 pass layer2 mac-type 0x888e
511

    
512
# layer 2: block anything else non-IP
513
add 65307 set 1 deny layer2 not mac-type ip
514

    
515
EOD;
516

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

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

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

    
579
EOD;
580

    
581
	/* generate passthru mac database */
582
	$cprules .= captiveportal_passthrumac_configure(true);
583
	$cprules .= "\n";
584

    
585
	/* allowed ipfw rules to make allowed ip work */
586
	$cprules .= captiveportal_allowedip_configure();
587

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

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

    
611
	if ($reinit == false)
612
		unlock($captiveportallck);
613

    
614
	/* activate ipfw(4) so CP can work */
615
	mwexec("/sbin/sysctl net.link.ether.ipfw=1");
616

    
617
	return $cprules;
618
}
619

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

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

    
635
	if (empty($config['captiveportal']['idletimeout']) ||
636
	!is_numeric($config['captiveportal']['idletimeout']))
637
		$idletimeout = 0;
638
	else
639
		$idletimeout = $config['captiveportal']['idletimeout'] * 60;
640

    
641
	if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && 
642
	!isset($config['captiveportal']['radiussession_timeout']) && !isset($config['voucher']['enable']))
643
		return;
644

    
645
	$radiusservers = captiveportal_get_radius_servers();
646

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

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

    
665
		$timedout = false;
666
		$term_cause = 1;
667

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

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

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

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

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

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

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

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

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

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

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

    
787
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
788

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

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

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

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

    
833
}
834

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

    
839
	$radiusservers = captiveportal_get_radius_servers();
840
	$unsetindex = array();
841

    
842
	/* read database */
843
	$cpdb = captiveportal_read_db();
844

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

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

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

    
861
	if (!isset($config['captiveportal']['radacct_enable']))
862
		return;
863

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

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

    
887
	$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
888

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

    
903
	return $rules;
904
}
905

    
906
function captiveportal_passthrumac_configure($lock = false) {
907
	global $config, $g;
908

    
909
	$rules = "";
910

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

    
917
		}
918
	}
919

    
920
	return $rules;
921
}
922

    
923
function captiveportal_passthrumac_findbyname($username) {
924
	global $config;
925

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

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

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

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

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

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

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

    
1017
	return $rules;
1018
}
1019

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

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

    
1042
function captiveportal_allowedhostname_configure() {
1043
	global $config, $g;
1044

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

    
1054
function captiveportal_allowedip_configure() {
1055
	global $config, $g;
1056

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

    
1063
	return $rules;
1064
}
1065

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

    
1069
	$ipfwoutput = "";
1070

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

    
1079
	return 0;
1080
}
1081

    
1082
function captiveportal_init_radius_servers() {
1083
	global $config, $g;
1084

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

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

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

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

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

    
1145
	unlock($cprdsrvlck);
1146
	return false;
1147
}
1148

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

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

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

    
1175
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1176

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

    
1185
	$radiusservers = captiveportal_get_radius_servers();
1186

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

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

    
1204
	return $auth_list;
1205
}
1206

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

    
1211
	$cpdb = array();
1212

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

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

    
1236
	if ($locked == false)
1237
		$cpdblck = lock('captiveportaldb', LOCK_EX);
1238

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1371
	$cpruleslck = lock('captiveportalrules', LOCK_EX);
1372
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1373
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1374
		$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`);
1375
		if ($rules[$ruleno]) {
1376
			unlock($cpruleslck);
1377
			return $ruleno;
1378
		}
1379
	}
1380

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

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

    
1396
function getVolume($ip) {
1397

    
1398
	$volume = array();
1399

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

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

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

    
1422
	return $volume;
1423
}
1424

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

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

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

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

    
1462
	return $nasIp;
1463
}
1464

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

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

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

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

    
1494
	return false;
1495
}
1496

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

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

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

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

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

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

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

    
1546
    echo $htmltext;
1547
}
1548

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

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

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

    
1562
    return FALSE;
1563
}
1564

    
1565
function portal_allow($clientip,$clientmac,$username,$password = null, $attributes = null, $ruleno = null)  {
1566

    
1567
	global $redirurl, $g, $config, $type, $passthrumac, $_POST;
1568

    
1569
	/* See if a ruleno is passed, if not start sessions because this means there isn't one atm */
1570
	if ($ruleno == null)
1571
		$ruleno = captiveportal_get_next_ipfw_ruleno();
1572

    
1573
	/* if the pool is empty, return appropriate message and exit */
1574
	if (is_null($ruleno)) {
1575
		portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1576
		log_error("WARNING!  Captive portal has reached maximum login capacity");
1577
		exit;
1578
	}
1579

    
1580
	// Ensure we create an array if we are missing attributes
1581
	if (!is_array($attributes))
1582
		$attributes = array();
1583

    
1584
	$radiusservers = captiveportal_get_radius_servers();
1585

    
1586
	/* Do not allow concurrent login execution. */
1587
	$cpdblck = lock('captiveportaldb', LOCK_EX);
1588

    
1589
	unset($sessionid);
1590

    
1591
	/* read in client database */
1592
	$cpdb = captiveportal_read_db(true);
1593

    
1594
	if ($attributes['voucher'])
1595
		$remaining_time = $attributes['session_timeout'];
1596

    
1597
	$writecfg = false;
1598
	/* Find an existing session */
1599
	if ((isset($config['captiveportal']['noconcurrentlogins'])) && $passthrumac) {
1600
		if (isset($config['captiveportal']['passthrumacadd'])) {
1601
			$mac = captiveportal_passthrumac_findbyname($username);
1602
			if (!empty($mac)) {
1603
				if ($_POST['replacemacpassthru']) {
1604
					foreach ($config['captiveportal']['passthrumac'] as $idx => $macent) {
1605
						if ($macent['mac'] == $mac['mac']) {
1606
							$macrules = "";
1607
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1608
                                			if ($ruleno) {
1609
								captiveportal_free_ipfw_ruleno($ruleno, true);
1610
                                        			$macrules .= "delete {$ruleno}\n";
1611
								++$ruleno;
1612
                                        			$macrules .= "delete {$ruleno}\n";
1613
                                			}
1614
							unset($config['captiveportal']['passthrumac'][$idx]);
1615
							$mac['mac'] = $clientmac;
1616
							$config['captiveportal']['passthrumac'][] = $mac;
1617
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1618
							file_put_contents("{$g['tmp_path']}/macentry.rules.tmp", $macrules);
1619
							mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry.rules.tmp");
1620
							$writecfg = true;
1621
							$sessionid = true;
1622
							break;
1623
						}
1624
					}
1625
                                } else {
1626
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1627
						$clientmac, $clientip, $username, $password);
1628
					exit;
1629
				}
1630
			}
1631
		}
1632
	}
1633

    
1634
	/* Snaphost the timestamp */
1635
	$allow_time = time();
1636

    
1637
	foreach ($cpdb as $sid => $cpentry) {
1638
		/* on the same ip */
1639
		if($cpentry[2] == $clientip) {
1640
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1641
			$sessionid = $sid;
1642
			break;
1643
		}
1644
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1645
			// user logged in with an active voucher. Check for how long and calculate 
1646
			// how much time we can give him (voucher credit - used time)
1647
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1648
			if ($remaining_time < 0)    // just in case. 
1649
				$remaining_time = 0;
1650

    
1651
			/* This user was already logged in so we disconnect the old one */
1652
			captiveportal_disconnect($cpentry,$radiusservers,13);
1653
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1654
			unset($cpdb[$sid]);
1655
			break;
1656
		}
1657
		elseif ((isset($config['captiveportal']['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1658
			/* on the same username */
1659
			if (strcasecmp($cpentry[4], $username) == 0) {
1660
				/* This user was already logged in so we disconnect the old one */
1661
				captiveportal_disconnect($cpentry,$radiusservers,13);
1662
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1663
				unset($cpdb[$sid]);
1664
				break;
1665
			}
1666
		}
1667
	}
1668

    
1669
	if ($attributes['voucher'] && $remaining_time <= 0)
1670
		return 0;       // voucher already used and no time left
1671

    
1672
	if (!isset($sessionid)) {
1673
		/* generate unique session ID */
1674
		$tod = gettimeofday();
1675
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1676

    
1677
		/* Add rules for traffic shaping
1678
		 * We don't need to add extra rules since traffic will pass due to the following kernel option
1679
		 * net.inet.ip.fw.one_pass: 1
1680
		 */
1681
		$peruserbw = isset($config['captiveportal']['peruserbw']);
1682

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

    
1686
		if ($passthrumac) {
1687
			$mac = array();
1688
			$mac['mac'] = $clientmac;
1689
			if (isset($config['captiveportal']['passthrumacaddusername']))
1690
				$mac['username'] = $username;
1691
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1692
			if (!empty($bw_up))
1693
				$mac['bw_up'] = $bw_up;
1694
			if (!empty($bw_down))
1695
				$mac['bw_down'] = $bw_down;
1696
			if (!is_array($config['captiveportal']['passthrumac']))
1697
				$config['captiveportal']['passthrumac'] = array();
1698
			$config['captiveportal']['passthrumac'][] = $mac;
1699
			unlock($cpdblck);
1700
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1701
			file_put_contents("{$g['tmp_path']}/macentry.rules.tmp", $macrules);
1702
			mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry.rules.tmp");
1703
			$writecfg = true;
1704
		} else {
1705
			if ($peruserbw && !empty($bw_up) && is_numeric($bw_up)) {
1706
				$bw_up_pipeno = $ruleno + 20000;
1707
				//$bw_up /= 1000; // Scale to Kbit/s
1708
				mwexec("/sbin/ipfw pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100");
1709

    
1710
				if (!isset($config['captiveportal']['nomacfilter']))
1711
					mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac} {$bw_up_pipeno}");
1712
				else
1713
					mwexec("/sbin/ipfw table 1 add {$clientip} {$bw_up_pipeno}");
1714
			} else {
1715
				if (!isset($config['captiveportal']['nomacfilter']))
1716
					mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac}");
1717
				else
1718
					mwexec("/sbin/ipfw table 1 add {$clientip}");
1719
			}
1720
			if ($peruserbw && !empty($bw_down) && is_numeric($bw_down)) {
1721
				$bw_down_pipeno = $ruleno + 20001;
1722
				//$bw_down /= 1000; // Scale to Kbit/s
1723
				mwexec("/sbin/ipfw pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100");
1724

    
1725
				if (!isset($config['captiveportal']['nomacfilter']))
1726
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac} {$bw_down_pipeno}");
1727
				else
1728
					mwexec("/sbin/ipfw table 2 add {$clientip} {$bw_down_pipeno}");
1729
			} else {
1730
				if (!isset($config['captiveportal']['nomacfilter']))
1731
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac}");
1732
				else
1733
					mwexec("/sbin/ipfw table 2 add {$clientip}");
1734
			}
1735

    
1736
			if ($attributes['voucher'])
1737
				$attributes['session_timeout'] = $remaining_time;
1738

    
1739
			/* encode password in Base64 just in case it contains commas */
1740
			$bpassword = base64_encode($password);
1741
			$cpdb[] = array($allow_time, $ruleno, $clientip, $clientmac, $username, $sessionid, $bpassword,
1742
				$attributes['session_timeout'], $attributes['idle_timeout'], $attributes['session_terminate_time']);
1743

    
1744
			/* rewrite information to database */
1745
			captiveportal_write_db($cpdb, true);
1746
			unlock($cpdblck);
1747

    
1748
			if (isset($config['captiveportal']['radacct_enable']) && !empty($radiusservers)) {
1749
				$acct_val = RADIUS_ACCOUNTING_START($ruleno,
1750
                                		$username, $sessionid, $radiusservers, $clientip, $clientmac);
1751
				if ($acct_val == 1)
1752
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1753
			}
1754
		}
1755
	} else
1756
		unlock($cpdblck);
1757

    
1758
	if ($writecfg == true)
1759
		write_config();
1760

    
1761
	/* redirect user to desired destination */
1762
	if (!empty($attributes['url_redirection']))
1763
		$my_redirurl = $attributes['url_redirection'];
1764
	else if (!empty($config['captiveportal']['redirurl']))
1765
		$my_redirurl = $config['captiveportal']['redirurl'];
1766
	else
1767
		$my_redirurl = $redirurl;
1768

    
1769
	if(isset($config['captiveportal']['logoutwin_enable']) && !$passthrumac) {
1770

    
1771
		if (isset($config['captiveportal']['httpslogin']))
1772
			$logouturl = "https://{$config['captiveportal']['httpsname']}:8001/";
1773
		else {
1774
			$ifip = portal_ip_from_client_ip($clientip);
1775
			if (!$ifip)
1776
				$ourhostname = $config['system']['hostname'] . ":8000";
1777
			else
1778
				$ourhostname = "{$ifip}:8000";
1779
			$logouturl = "http://{$ourhostname}/";
1780
		}
1781

    
1782
		if (isset($attributes['reply_message']))
1783
			$message = $attributes['reply_message'];
1784
		else
1785
			$message = 0;
1786

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

    
1789
	} else {
1790
		header("Location: " . $my_redirurl);
1791
	}
1792

    
1793
	return $sessionid;
1794
}
1795

    
1796

    
1797
/*
1798
 * Used for when pass-through credits are enabled.
1799
 * Returns true when there was at least one free login to deduct for the MAC.
1800
 * Expired entries are removed as they are seen.
1801
 * Active entries are updated according to the configuration.
1802
 */
1803
function portal_consume_passthrough_credit($clientmac) {
1804
	global $config;
1805

    
1806
	if (!empty($config['captiveportal']['freelogins_count']) && is_numeric($config['captiveportal']['freelogins_count']))
1807
		$freeloginscount = $config['captiveportal']['freelogins_count'];
1808
	else
1809
		return false;
1810

    
1811
	if (!empty($config['captiveportal']['freelogins_resettimeout']) && is_numeric($config['captiveportal']['freelogins_resettimeout']))
1812
		$resettimeout = $config['captiveportal']['freelogins_resettimeout'];
1813
	else
1814
		return false;
1815

    
1816
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1817
		return false;
1818

    
1819
	$updatetimeouts = isset($config['captiveportal']['freelogins_updatetimeouts']);
1820

    
1821
	/*
1822
	 * Read database of used MACs.  Lines are a comma-separated list
1823
	 * of the time, MAC, then the count of pass-through credits remaining.
1824
	 */
1825
	$usedmacs = captiveportal_read_usedmacs_db();
1826

    
1827
	$currenttime = time();
1828
	$found = false;
1829
	foreach ($usedmacs as $key => $usedmac) {
1830
		$usedmac = explode(",", $usedmac);
1831

    
1832
		if ($usedmac[1] == $clientmac) {
1833
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
1834
				if ($usedmac[2] < 1) {
1835
					if ($updatetimeouts) {
1836
						$usedmac[0] = $currenttime;
1837
						unset($usedmacs[$key]);
1838
						$usedmacs[] = implode(",", $usedmac);
1839
						captiveportal_write_usedmacs_db($usedmacs);
1840
					}
1841

    
1842
					return false;
1843
				} else {
1844
					$usedmac[2] -= 1;
1845
					$usedmacs[$key] = implode(",", $usedmac);
1846
				}
1847

    
1848
				$found = true;
1849
			} else
1850
				unset($usedmacs[$key]);
1851

    
1852
			break;
1853
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
1854
				unset($usedmacs[$key]);
1855
	}
1856

    
1857
	if (!$found) {
1858
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
1859
		$usedmacs[] = implode(",", $usedmac);
1860
	}
1861

    
1862
	captiveportal_write_usedmacs_db($usedmacs);
1863
	return true;
1864
}
1865

    
1866
function captiveportal_read_usedmacs_db() {
1867
	global $g;
1868

    
1869
	$cpumaclck = lock('captiveusedmacs');
1870
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs.db")) {
1871
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1872
		if (!$usedmacs)
1873
			$usedmacs = array();
1874
	} else
1875
		$usedmacs = array();
1876

    
1877
	unlock($cpumaclck);
1878
	return $usedmacs;
1879
}
1880

    
1881
function captiveportal_write_usedmacs_db($usedmacs) {
1882
	global $g;
1883

    
1884
	$cpumaclck = lock('captiveusedmacs', LOCK_EX);
1885
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs.db", implode("\n", $usedmacs));
1886
	unlock($cpumaclck);
1887
}
1888

    
1889
?>
(7-7/62)