Project

General

Profile

Download (60.1 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
			printf(gettext("done%s"), "\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
	if ($enBwup && $enBwdown)
888
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
889
	else
890
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
891

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

    
906
	return $rules;
907
}
908

    
909
function captiveportal_passthrumac_configure($lock = false) {
910
	global $config, $g;
911

    
912
	$rules = "";
913

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

    
920
		}
921
	}
922

    
923
	return $rules;
924
}
925

    
926
function captiveportal_passthrumac_findbyname($username) {
927
	global $config;
928

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

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

    
944
	/* This function can deal with hostname or ipaddress */
945
	if($ipent['ip']) 	
946
		$ipaddress = $ipent['ip'];
947

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

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

    
965
	if (intval($enBwup) > 0 or intval($enBwdown) > 0)
966
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
967
	else
968
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
969

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

    
1020
	return $rules;
1021
}
1022

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

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

    
1045
function captiveportal_allowedhostname_configure() {
1046
	global $config, $g;
1047

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

    
1057
function captiveportal_allowedip_configure() {
1058
	global $config, $g;
1059

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

    
1066
	return $rules;
1067
}
1068

    
1069
/* get last activity timestamp given client IP address */
1070
function captiveportal_get_last_activity($ip) {
1071

    
1072
	$ipfwoutput = "";
1073

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

    
1082
	return 0;
1083
}
1084

    
1085
function captiveportal_init_radius_servers() {
1086
	global $config, $g;
1087

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

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

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

    
1125
/* read RADIUS servers into array */
1126
function captiveportal_get_radius_servers() {
1127
	global $g;
1128

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

    
1148
	unlock($cprdsrvlck);
1149
	return false;
1150
}
1151

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

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

    
1175
function radius($username,$password,$clientip,$clientmac,$type) {
1176
	global $g, $config;
1177

    
1178
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1179

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

    
1188
	$radiusservers = captiveportal_get_radius_servers();
1189

    
1190
	$auth_list = RADIUS_AUTHENTICATION($username,
1191
		$password,
1192
		$radiusservers,
1193
		$clientip,
1194
		$clientmac,
1195
		$ruleno);
1196

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

    
1207
	return $auth_list;
1208
}
1209

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

    
1214
	$cpdb = array();
1215

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

    
1235
/* write captive portal DB */
1236
function captiveportal_write_db($cpdb, $locked = false, $remove = false) {
1237
	global $g;
1238

    
1239
	if ($locked == false)
1240
		$cpdblck = lock('captiveportaldb', LOCK_EX);
1241

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

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

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

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

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

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

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

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

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

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

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

    
1368
function captiveportal_get_ipfw_passthru_ruleno($value) {
1369
	global $config, $g;
1370

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

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

    
1384
	unlock($cpruleslck);
1385
	return NULL;
1386
}
1387

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

    
1399
function getVolume($ip) {
1400

    
1401
	$volume = array();
1402

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

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

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

    
1425
	return $volume;
1426
}
1427

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

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

    
1449
function getNasIP()
1450
{
1451
	global $config;
1452

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

    
1465
	return $nasIp;
1466
}
1467

    
1468
function portal_ip_from_client_ip($cliip) {
1469
	global $config;
1470

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

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

    
1489
	return false;
1490
}
1491

    
1492
/* functions move from index.php */
1493

    
1494
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1495
	global $g, $config;
1496

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

    
1506
	/* substitute the PORTAL_REDIRURL variable */
1507
	if ($config['captiveportal']['preauthurl']) {
1508
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$config['captiveportal']['preauthurl']}", $htmltext);
1509
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$config['captiveportal']['preauthurl']}", $htmltext);
1510
	}
1511

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

    
1526
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1527
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1528
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1529
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1530

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

    
1541
    echo $htmltext;
1542
}
1543

    
1544
function portal_mac_radius($clientmac,$clientip) {
1545
    global $config ;
1546

    
1547
    $radmac_secret = $config['captiveportal']['radmac_secret'];
1548

    
1549
    /* authentication against the radius server */
1550
    $username = mac_format($clientmac);
1551
    $auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1552
    if ($auth_list['auth_val'] == 2)
1553
        return TRUE;
1554
    if (!empty($auth_list['url_redirection']))
1555
	portal_reply_page($auth_list['url_redirection'], "redir");
1556

    
1557
    return FALSE;
1558
}
1559

    
1560
function portal_allow($clientip,$clientmac,$username,$password = null, $attributes = null, $ruleno = null)  {
1561

    
1562
	global $redirurl, $g, $config, $type, $passthrumac, $_POST;
1563

    
1564
	/* See if a ruleno is passed, if not start sessions because this means there isn't one atm */
1565
	if ($ruleno == null)
1566
		$ruleno = captiveportal_get_next_ipfw_ruleno();
1567

    
1568
	/* if the pool is empty, return appropriate message and exit */
1569
	if (is_null($ruleno)) {
1570
		portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1571
		log_error("WARNING!  Captive portal has reached maximum login capacity");
1572
		exit;
1573
	}
1574

    
1575
	// Ensure we create an array if we are missing attributes
1576
	if (!is_array($attributes))
1577
		$attributes = array();
1578

    
1579
	$radiusservers = captiveportal_get_radius_servers();
1580

    
1581
	/* Do not allow concurrent login execution. */
1582
	$cpdblck = lock('captiveportaldb', LOCK_EX);
1583

    
1584
	unset($sessionid);
1585

    
1586
	/* read in client database */
1587
	$cpdb = captiveportal_read_db(true);
1588

    
1589
	if ($attributes['voucher'])
1590
		$remaining_time = $attributes['session_timeout'];
1591

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

    
1629
	/* Snaphost the timestamp */
1630
	$allow_time = time();
1631

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

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

    
1664
	if ($attributes['voucher'] && $remaining_time <= 0)
1665
		return 0;       // voucher already used and no time left
1666

    
1667
	if (!isset($sessionid)) {
1668
		/* generate unique session ID */
1669
		$tod = gettimeofday();
1670
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1671

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

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

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

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

    
1720
				if (!isset($config['captiveportal']['nomacfilter']))
1721
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac} {$bw_down_pipeno}");
1722
				else
1723
					mwexec("/sbin/ipfw table 2 add {$clientip} {$bw_down_pipeno}");
1724
			} else {
1725
				if (!isset($config['captiveportal']['nomacfilter']))
1726
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac}");
1727
				else
1728
					mwexec("/sbin/ipfw table 2 add {$clientip}");
1729
			}
1730

    
1731
			if ($attributes['voucher'])
1732
				$attributes['session_timeout'] = $remaining_time;
1733

    
1734
			/* encode password in Base64 just in case it contains commas */
1735
			$bpassword = base64_encode($password);
1736
			$cpdb[] = array($allow_time, $ruleno, $clientip, $clientmac, $username, $sessionid, $bpassword,
1737
				$attributes['session_timeout'], $attributes['idle_timeout'], $attributes['session_terminate_time']);
1738

    
1739
			/* rewrite information to database */
1740
			captiveportal_write_db($cpdb, true);
1741
			unlock($cpdblck);
1742

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

    
1753
	if ($writecfg == true)
1754
		write_config();
1755

    
1756
	/* redirect user to desired destination */
1757
	if (!empty($attributes['url_redirection']))
1758
		$my_redirurl = $attributes['url_redirection'];
1759
	else if (!empty($config['captiveportal']['redirurl']))
1760
		$my_redirurl = $config['captiveportal']['redirurl'];
1761
	else
1762
		$my_redirurl = $redirurl;
1763

    
1764
	if(isset($config['captiveportal']['logoutwin_enable']) && !$passthrumac) {
1765

    
1766
		if (isset($config['captiveportal']['httpslogin']))
1767
			$logouturl = "https://{$config['captiveportal']['httpsname']}:8001/";
1768
		else {
1769
			$ifip = portal_ip_from_client_ip($clientip);
1770
			if (!$ifip)
1771
				$ourhostname = $config['system']['hostname'] . ":8000";
1772
			else
1773
				$ourhostname = "{$ifip}:8000";
1774
			$logouturl = "http://{$ourhostname}/";
1775
		}
1776

    
1777
		if (isset($attributes['reply_message']))
1778
			$message = $attributes['reply_message'];
1779
		else
1780
			$message = 0;
1781

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

    
1784
	} else {
1785
		header("Location: " . $my_redirurl);
1786
	}
1787

    
1788
	return $sessionid;
1789
}
1790

    
1791

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

    
1801
	if (!empty($config['captiveportal']['freelogins_count']) && is_numeric($config['captiveportal']['freelogins_count']))
1802
		$freeloginscount = $config['captiveportal']['freelogins_count'];
1803
	else
1804
		return false;
1805

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

    
1811
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1812
		return false;
1813

    
1814
	$updatetimeouts = isset($config['captiveportal']['freelogins_updatetimeouts']);
1815

    
1816
	/*
1817
	 * Read database of used MACs.  Lines are a comma-separated list
1818
	 * of the time, MAC, then the count of pass-through credits remaining.
1819
	 */
1820
	$usedmacs = captiveportal_read_usedmacs_db();
1821

    
1822
	$currenttime = time();
1823
	$found = false;
1824
	foreach ($usedmacs as $key => $usedmac) {
1825
		$usedmac = explode(",", $usedmac);
1826

    
1827
		if ($usedmac[1] == $clientmac) {
1828
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
1829
				if ($usedmac[2] < 1) {
1830
					if ($updatetimeouts) {
1831
						$usedmac[0] = $currenttime;
1832
						unset($usedmacs[$key]);
1833
						$usedmacs[] = implode(",", $usedmac);
1834
						captiveportal_write_usedmacs_db($usedmacs);
1835
					}
1836

    
1837
					return false;
1838
				} else {
1839
					$usedmac[2] -= 1;
1840
					$usedmacs[$key] = implode(",", $usedmac);
1841
				}
1842

    
1843
				$found = true;
1844
			} else
1845
				unset($usedmacs[$key]);
1846

    
1847
			break;
1848
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
1849
				unset($usedmacs[$key]);
1850
	}
1851

    
1852
	if (!$found) {
1853
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
1854
		$usedmacs[] = implode(",", $usedmac);
1855
	}
1856

    
1857
	captiveportal_write_usedmacs_db($usedmacs);
1858
	return true;
1859
}
1860

    
1861
function captiveportal_read_usedmacs_db() {
1862
	global $g;
1863

    
1864
	$cpumaclck = lock('captiveusedmacs');
1865
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs.db")) {
1866
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1867
		if (!$usedmacs)
1868
			$usedmacs = array();
1869
	} else
1870
		$usedmacs = array();
1871

    
1872
	unlock($cpumaclck);
1873
	return $usedmacs;
1874
}
1875

    
1876
function captiveportal_write_usedmacs_db($usedmacs) {
1877
	global $g;
1878

    
1879
	$cpumaclck = lock('captiveusedmacs', LOCK_EX);
1880
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs.db", implode("\n", $usedmacs));
1881
	unlock($cpumaclck);
1882
}
1883

    
1884
?>
(7-7/61)