Project

General

Profile

Download (59.9 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']['maxproc'])
401
		$maxproc = $config['captiveportal']['maxproc'];
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
				unset($cpdb[$key]);
1246
		} else
1247
			return; //This makes sure no record removal calls
1248
	}
1249
	$fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
1250
	if ($fd) {
1251
		foreach ($cpdb as $cpent) {
1252
			fwrite($fd, join(",", $cpent) . "\n");
1253
		}
1254
		fclose($fd);
1255
	}
1256
	if ($locked == false)
1257
		unlock($cpdblck);
1258
}
1259

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

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

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

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

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

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

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

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

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

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

    
1363
function captiveportal_get_ipfw_passthru_ruleno($value) {
1364
	global $config, $g;
1365

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

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

    
1379
	unlock($cpruleslck);
1380
	return NULL;
1381
}
1382

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

    
1394
function getVolume($ip) {
1395

    
1396
	$volume = array();
1397

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

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

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

    
1420
	return $volume;
1421
}
1422

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

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

    
1444
function getNasIP()
1445
{
1446
	global $config;
1447

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

    
1460
	return $nasIp;
1461
}
1462

    
1463
function portal_ip_from_client_ip($cliip) {
1464
	global $config;
1465

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

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

    
1484
	return false;
1485
}
1486

    
1487
/* functions move from index.php */
1488

    
1489
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1490
	global $g, $config;
1491

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

    
1501
	/* substitute the PORTAL_REDIRURL variable */
1502
	if ($config['captiveportal']['preauthurl']) {
1503
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$config['captiveportal']['preauthurl']}", $htmltext);
1504
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$config['captiveportal']['preauthurl']}", $htmltext);
1505
	}
1506

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

    
1521
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1522
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1523
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1524
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1525

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

    
1536
    echo $htmltext;
1537
}
1538

    
1539
function portal_mac_radius($clientmac,$clientip) {
1540
    global $config ;
1541

    
1542
    $radmac_secret = $config['captiveportal']['radmac_secret'];
1543

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

    
1552
    return FALSE;
1553
}
1554

    
1555
function portal_allow($clientip,$clientmac,$username,$password = null, $attributes = null, $ruleno = null)  {
1556

    
1557
	global $redirurl, $g, $config, $type, $passthrumac, $_POST;
1558

    
1559
	/* See if a ruleno is passed, if not start sessions because this means there isn't one atm */
1560
	if ($ruleno == null)
1561
		$ruleno = captiveportal_get_next_ipfw_ruleno();
1562

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

    
1570
	// Ensure we create an array if we are missing attributes
1571
	if (!is_array($attributes))
1572
		$attributes = array();
1573

    
1574
	$radiusservers = captiveportal_get_radius_servers();
1575

    
1576
	/* Do not allow concurrent login execution. */
1577
	$cpdblck = lock('captiveportaldb', LOCK_EX);
1578

    
1579
	unset($sessionid);
1580

    
1581
	/* read in client database */
1582
	$cpdb = captiveportal_read_db(true);
1583

    
1584
	if ($attributes['voucher'])
1585
		$remaining_time = $attributes['session_timeout'];
1586

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

    
1624
	/* Snaphost the timestamp */
1625
	$allow_time = time();
1626

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

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

    
1659
	if ($attributes['voucher'] && $remaining_time <= 0)
1660
		return 0;       // voucher already used and no time left
1661

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

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

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

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

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

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

    
1726
			if ($attributes['voucher'])
1727
				$attributes['session_timeout'] = $remaining_time;
1728

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

    
1734
			/* rewrite information to database */
1735
			captiveportal_write_db($cpdb, true);
1736
			unlock($cpdblck);
1737

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

    
1748
	if ($writecfg == true)
1749
		write_config();
1750

    
1751
	/* redirect user to desired destination */
1752
	if (!empty($attributes['url_redirection']))
1753
		$my_redirurl = $attributes['url_redirection'];
1754
	else if (!empty($config['captiveportal']['redirurl']))
1755
		$my_redirurl = $config['captiveportal']['redirurl'];
1756
	else
1757
		$my_redirurl = $redirurl;
1758

    
1759
	if(isset($config['captiveportal']['logoutwin_enable']) && !$passthrumac) {
1760

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

    
1772
		if (isset($attributes['reply_message']))
1773
			$message = $attributes['reply_message'];
1774
		else
1775
			$message = 0;
1776

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

    
1779
	} else {
1780
		header("Location: " . $my_redirurl);
1781
	}
1782

    
1783
	return $sessionid;
1784
}
1785

    
1786

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

    
1796
	if (!empty($config['captiveportal']['freelogins_count']) && is_numeric($config['captiveportal']['freelogins_count']))
1797
		$freeloginscount = $config['captiveportal']['freelogins_count'];
1798
	else
1799
		return false;
1800

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

    
1806
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1807
		return false;
1808

    
1809
	$updatetimeouts = isset($config['captiveportal']['freelogins_updatetimeouts']);
1810

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

    
1817
	$currenttime = time();
1818
	$found = false;
1819
	foreach ($usedmacs as $key => $usedmac) {
1820
		$usedmac = explode(",", $usedmac);
1821

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

    
1832
					return false;
1833
				} else {
1834
					$usedmac[2] -= 1;
1835
					$usedmacs[$key] = implode(",", $usedmac);
1836
				}
1837

    
1838
				$found = true;
1839
			} else
1840
				unset($usedmacs[$key]);
1841

    
1842
			break;
1843
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
1844
				unset($usedmacs[$key]);
1845
	}
1846

    
1847
	if (!$found) {
1848
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
1849
		$usedmacs[] = implode(",", $usedmac);
1850
	}
1851

    
1852
	captiveportal_write_usedmacs_db($usedmacs);
1853
	return true;
1854
}
1855

    
1856
function captiveportal_read_usedmacs_db() {
1857
	global $g;
1858

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

    
1867
	unlock($cpumaclck);
1868
	return $usedmacs;
1869
}
1870

    
1871
function captiveportal_write_usedmacs_db($usedmacs) {
1872
	global $g;
1873

    
1874
	$cpumaclck = lock('captiveusedmacs', LOCK_EX);
1875
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs.db", implode("\n", $usedmacs));
1876
	unlock($cpumaclck);
1877
}
1878

    
1879
?>
(7-7/61)