Project

General

Profile

Download (60 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']}/cp_prunedb.pid");
365

    
366
		captiveportal_radius_stop_all();
367

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

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

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

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

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

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

    
405
	$use_fastcgi = true;
406

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
514
EOD;
515

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

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

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

    
578
EOD;
579

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

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

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

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

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

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

    
616
	return $cprules;
617
}
618

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

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

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

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

    
644
	$radiusservers = captiveportal_get_radius_servers();
645

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
832
}
833

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

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

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

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

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

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

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

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

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

    
886
	if ($enBwup && $enBwdown)
887
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
888
	else
889
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
890

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

    
905
	return $rules;
906
}
907

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

    
911
	$rules = "";
912

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

    
919
		}
920
	}
921

    
922
	return $rules;
923
}
924

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

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

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

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

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

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

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

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

    
1019
	return $rules;
1020
}
1021

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

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

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

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

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

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

    
1065
	return $rules;
1066
}
1067

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

    
1071
	$ipfwoutput = "";
1072

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

    
1081
	return 0;
1082
}
1083

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

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

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

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

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

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

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

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

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

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

    
1177
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1178

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

    
1187
	$radiusservers = captiveportal_get_radius_servers();
1188

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

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

    
1206
	return $auth_list;
1207
}
1208

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

    
1213
	$cpdb = array();
1214

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1398
function getVolume($ip) {
1399

    
1400
	$volume = array();
1401

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

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

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

    
1424
	return $volume;
1425
}
1426

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

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

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

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

    
1464
	return $nasIp;
1465
}
1466

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

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

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

    
1488
	return false;
1489
}
1490

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

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

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

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

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

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

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

    
1540
    echo $htmltext;
1541
}
1542

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

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

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

    
1556
    return FALSE;
1557
}
1558

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

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

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

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

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

    
1578
	$radiusservers = captiveportal_get_radius_servers();
1579

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

    
1583
	unset($sessionid);
1584

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1787
	return $sessionid;
1788
}
1789

    
1790

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1883
?>
(7-7/61)