Project

General

Profile

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

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

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

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

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

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

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

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

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

    
121
EOD;
122
		return $htmltext;
123
	}
124

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

    
200
EOD;
201

    
202
	return $htmltext;
203
}
204

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
334
EOD;
335
		}
336

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

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

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

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

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

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

    
362
	} else {
363
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
364
		killbypid("{$g['varrun_path']}/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
	foreach ($cpdb as $cpentry) {
656

    
657
		$timedout = false;
658
		$term_cause = 1;
659

    
660
		/* hard timeout? */
661
		if ($timeout) {
662
			if ((time() - $cpentry[0]) >= $timeout) {
663
				$timedout = true;
664
				$term_cause = 5; // Session-Timeout
665
			}
666
		}
667

    
668
		/* Session-Terminate-Time */
669
		if (!$timedout && !empty($cpentry[9])) {
670
			if (time() >= $cpentry[9]) {
671
				$timedout = true;
672
				$term_cause = 5; // Session-Timeout
673
			}
674
		}
675

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

    
692
		/* if vouchers are configured, activate session timeouts */
693
		if (!$timedout && isset($config['voucher']['enable']) && !empty($cpentry[7])) {
694
			if (time() >= ($cpentry[0] + $cpentry[7])) {
695
				$timedout = true;
696
				$term_cause = 5; // Session-Timeout
697
			}
698
		}
699

    
700
		/* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
701
		if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpentry[7])) {
702
			if (time() >= ($cpentry[0] + $cpentry[7])) {
703
				$timedout = true;
704
				$term_cause = 5; // Session-Timeout
705
			}
706
		}
707

    
708
		if ($timedout) {
709
			captiveportal_disconnect($cpentry, $radiusservers,$term_cause,$stop_time);
710
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
711
			$unsetindexes[] = $cpentry[5];
712
		}
713

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

    
748
			/* check this user against RADIUS again */
749
			if (isset($config['captiveportal']['reauthenticate'])) {
750
				$auth_list = RADIUS_AUTHENTICATION($cpentry[4], // username
751
					base64_decode($cpentry[6]), // password
752
					$radiusservers,
753
					$cpentry[2], // clientip
754
					$cpentry[3], // clientmac
755
					$cpentry[1]); // ruleno
756
				if ($auth_list['auth_val'] == 3) {
757
					captiveportal_disconnect($cpentry, $radiusservers, 17);
758
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
759
					$unsetindexes[] = $cpentry[5];
760
				}
761
			}
762
		}
763
	}
764

    
765
	/* write database */
766
	if (!empty($unsetindexes))
767
		captiveportal_write_db($cpdb, false, $unsetindexes);
768
}
769

    
770
/* remove a single client according to the DB entry */
771
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
772
	global $g, $config;
773

    
774
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
775

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

    
799
	/* 
800
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
801
	* We could get an error if the pipe doesn't exist but everything should still be fine
802
	*/
803
	if (isset($config['captiveportal']['peruserbw'])) {
804
		mwexec("/sbin/ipfw pipe " . ($dbent[1]+20000) . " delete");
805
		mwexec("/sbin/ipfw pipe " . ($dbent[1]+20001) . " delete");
806
	}
807

    
808
	/* Release the ruleno so it can be reallocated to new clients. */
809
	captiveportal_free_ipfw_ruleno($dbent[1]);
810

    
811
	// XMLRPC Call over to the master Voucher node
812
	$a_voucher = &$config['voucher'];
813
	if(!empty($a_voucher['vouchersyncdbip'])) {
814
		$syncip   = $a_voucher['vouchersyncdbip'];
815
		$syncport = $a_voucher['vouchersyncport'];
816
		$syncpass = $a_voucher['vouchersyncpass'];
817
		$vouchersyncusername = $a_voucher['vouchersyncusername'];
818
		$remote_status = xmlrpc_sync_voucher_disconnect($dben, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time);
819
	}
820

    
821
}
822

    
823
/* remove a single client by sessionid */
824
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
825
	global $g, $config;
826

    
827
	$radiusservers = captiveportal_get_radius_servers();
828
	$unsetindex = array();
829

    
830
	$cpdblck = lock('captiveportaldb', LOCK_EX);
831

    
832
	/* read database */
833
	$cpdb = captiveportal_read_db(true);
834

    
835
	/* find entry */
836
	if (isset($cpdb[$sessionid])) {
837
		$cpentry = $cpdb[$sessionid];
838
		/* write database */
839
		$unsetindex[] = $sessionid;
840
		captiveportal_write_db($cpdb, true, $unsetindex);
841
		unlock($cpdblck);
842

    
843
		captiveportal_disconnect($cpentry, $radiusservers, $term_cause);
844
		captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
845
	}		
846
}
847

    
848
/* send RADIUS acct stop for all current clients */
849
function captiveportal_radius_stop_all() {
850
	global $config;
851

    
852
	if (!isset($config['captiveportal']['radacct_enable']))
853
		return;
854

    
855
	$radiusservers = captiveportal_get_radius_servers();
856
	if (!empty($radiusservers)) {
857
		$cpdb = captiveportal_read_db();
858
		foreach ($cpdb as $cpentry) {
859
			RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
860
				$cpentry[4], // username
861
				$cpentry[5], // sessionid
862
				$cpentry[0], // start time
863
				$radiusservers,
864
				$cpentry[2], // clientip
865
				$cpentry[3], // clientmac
866
				7); // Admin Reboot
867
		}
868
	}
869
}
870

    
871
function captiveportal_passthrumac_configure_entry($macent) {
872
	$rules = "";
873
	$enBwup = isset($macent['bw_up']);
874
	$enBwdown = isset($macent['bw_down']);
875
	$actionup = "allow";
876
	$actiondown = "allow";
877

    
878
	if ($enBwup && $enBwdown)
879
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
880
	else
881
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
882

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

    
897
	return $rules;
898
}
899

    
900
function captiveportal_passthrumac_configure($lock = false) {
901
	global $config, $g;
902

    
903
	$rules = "";
904

    
905
	if (is_array($config['captiveportal']['passthrumac'])) {
906
		$macdb = array();
907
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
908
			$rules .= captiveportal_passthrumac_configure_entry($macent);
909
			$macdb[$macent['mac']]['active']  = true;
910

    
911
		}
912
	}
913

    
914
	return $rules;
915
}
916

    
917
function captiveportal_passthrumac_findbyname($username) {
918
	global $config;
919

    
920
	if (is_array($config['captiveportal']['passthrumac'])) {
921
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
922
			if ($macent['username'] == $username)
923
				return $macent;
924
		}
925
	}
926
	return NULL;
927
}
928

    
929
/* 
930
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
931
 * table (5=IN)/(6=OUT) hold allowed ip's with bw limit.
932
 */
933
function captiveportal_allowedip_configure_entry($ipent) {
934

    
935
	/* This function can deal with hostname or ipaddress */
936
	if($ipent['ip']) 	
937
		$ipaddress = $ipent['ip'];
938

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

    
948
	$rules = "";
949
	$enBwup = intval($ipent['bw_up']);
950
	$enBwdown = intval($ipent['bw_down']);
951
	$bw_up = "";
952
	$bw_down = "";
953
	$tablein = array();
954
	$tableout = array();
955

    
956
	if (intval($enBwup) > 0 or intval($enBwdown) > 0)
957
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
958
	else
959
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
960

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

    
1011
	return $rules;
1012
}
1013

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

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

    
1036
function captiveportal_allowedhostname_configure() {
1037
	global $config, $g;
1038

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

    
1048
function captiveportal_allowedip_configure() {
1049
	global $config, $g;
1050

    
1051
	$rules = "";
1052
	if (is_array($config['captiveportal']['allowedip'])) {
1053
		foreach ($config['captiveportal']['allowedip'] as $ipent) 
1054
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1055
	}
1056

    
1057
	return $rules;
1058
}
1059

    
1060
/* get last activity timestamp given client IP address */
1061
function captiveportal_get_last_activity($ip) {
1062

    
1063
	$ipfwoutput = "";
1064

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

    
1073
	return 0;
1074
}
1075

    
1076
function captiveportal_init_radius_servers() {
1077
	global $config, $g;
1078

    
1079
	/* generate radius server database */
1080
	if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
1081
		($config['captiveportal']['auth_method'] == "radius"))) {
1082
		$radiusip = $config['captiveportal']['radiusip'];
1083
		$radiusip2 = ($config['captiveportal']['radiusip2']) ? $config['captiveportal']['radiusip2'] : null;
1084

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

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

    
1116
/* read RADIUS servers into array */
1117
function captiveportal_get_radius_servers() {
1118
		global $g;
1119

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

    
1139
		unlock($cprdsrvlck);
1140
		return false;
1141
}
1142

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

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

    
1166
function radius($username,$password,$clientip,$clientmac,$type) {
1167
	global $g, $config;
1168

    
1169
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1170

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

    
1179
	$radiusservers = captiveportal_get_radius_servers();
1180

    
1181
	$auth_list = RADIUS_AUTHENTICATION($username,
1182
		$password,
1183
		$radiusservers,
1184
		$clientip,
1185
		$clientmac,
1186
		$ruleno);
1187

    
1188
	if ($auth_list['auth_val'] == 2) {
1189
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1190
		$sessionid = portal_allow($clientip,
1191
			$clientmac,
1192
			$username,
1193
			$password,
1194
			$auth_list,
1195
			$ruleno);
1196
	}
1197

    
1198
	return $auth_list;
1199
}
1200

    
1201
/* read captive portal DB into array */
1202
function captiveportal_read_db($locked = false) {
1203
	global $g;
1204

    
1205
	$cpdb = array();
1206

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

    
1226
/* write captive portal DB */
1227
function captiveportal_write_db($cpdb, $locked = false, $remove = false) {
1228
	global $g;
1229

    
1230
	if ($locked == false)
1231
		$cpdblck = lock('captiveportaldb', LOCK_EX);
1232

    
1233
	if (is_array($remove)) {
1234
		if (!empty($remove)) {
1235
			$cpdb = captiveportal_read_db(true);
1236
			foreach ($remove as $key)
1237
				unset($cpdb[$key]);
1238
		} else
1239
			return; //This makes sure no record removal calls
1240
	}
1241
	$fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
1242
	if ($fd) {
1243
		foreach ($cpdb as $cpent) {
1244
			fwrite($fd, join(",", $cpent) . "\n");
1245
		}
1246
		fclose($fd);
1247
	}
1248
	if ($locked == false)
1249
		unlock($cpdblck);
1250
}
1251

    
1252
function captiveportal_write_elements() {
1253
	global $g, $config;
1254
	
1255
	/* delete any existing elements */
1256
	if (is_dir($g['captiveportal_element_path'])) {
1257
		$dh = opendir($g['captiveportal_element_path']);
1258
		while (($file = readdir($dh)) !== false) {
1259
			if ($file != "." && $file != "..")
1260
				unlink($g['captiveportal_element_path'] . "/" . $file);
1261
		}
1262
		closedir($dh);
1263
	} else {
1264
		@mkdir($g['captiveportal_element_path']);
1265
	}
1266

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

    
1288
function captiveportal_init_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
1289
	global $g;
1290

    
1291
	@unlink("{$g['vardb_path']}/captiveportal.rules");
1292
	$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1293
	file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1294
}
1295

    
1296
/*
1297
 * This function will calculate the lowest free firewall ruleno
1298
 * within the range specified based on the actual logged on users
1299
 *
1300
 */
1301
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899, $usebw = false) {
1302
	global $config, $g;
1303

    
1304
	if(!isset($config['captiveportal']['enable']))
1305
		return NULL;
1306

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

    
1338
function captiveportal_free_ipfw_ruleno($ruleno, $usedbw = false) {
1339
	global $config, $g;
1340

    
1341
	if(!isset($config['captiveportal']['enable']))
1342
		return NULL;
1343

    
1344
	$cpruleslck = lock('captiveportalrules', LOCK_EX);
1345
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1346
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1347
		$rules[$ruleno] = false;
1348
		if (isset($config['captiveportal']['peruserbw']) || $usedbw == true)
1349
			$rules[++$ruleno] = false;
1350
		file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1351
	}
1352
	unlock($cpruleslck);
1353
}
1354

    
1355
function captiveportal_get_ipfw_passthru_ruleno($value) {
1356
	global $config, $g;
1357

    
1358
	if(!isset($config['captiveportal']['enable']))
1359
				return NULL;
1360

    
1361
	$cpruleslck = lock('captiveportalrules', LOCK_EX);
1362
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1363
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1364
		$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`);
1365
		if ($rules[$ruleno]) {
1366
			unlock($cpruleslck);
1367
			return $ruleno;
1368
		}
1369
	}
1370

    
1371
	unlock($cpruleslck);
1372
	return NULL;
1373
}
1374

    
1375
/**
1376
 * This function will calculate the traffic produced by a client
1377
 * based on its firewall rule
1378
 *
1379
 * Point of view: NAS
1380
 *
1381
 * Input means: from the client
1382
 * Output means: to the client
1383
 *
1384
 */
1385

    
1386
function getVolume($ip) {
1387

    
1388
	$volume = array();
1389

    
1390
	// Initialize vars properly, since we don't want NULL vars
1391
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1392

    
1393
	// Ingress
1394
	$ipfwin = "";
1395
	$ipfwout = "";
1396
	$matchesin = "";
1397
	$matchesout = "";
1398
	exec("/sbin/ipfw table 1 entrystats {$ip}", $ipfwin);
1399
	if ($ipfwin[0]) {
1400
		$ipfwin = split(" ", $ipfwin[0]);
1401
		$volume['input_pkts'] = $ipfwin[2];
1402
		$volume['input_bytes'] = $ipfwin[3];
1403
	}
1404

    
1405
	exec("/sbin/ipfw table 2 entrystats {$ip}", $ipfwout);
1406
	if ($ipfwout[0]) {
1407
		$ipfwout = split(" ", $ipfwout[0]);
1408
		$volume['output_pkts'] = $ipfwout[2];
1409
		$volume['output_bytes'] = $ipfwout[3];
1410
	}
1411

    
1412
	return $volume;
1413
}
1414

    
1415
/**
1416
 * Get the NAS-Identifier
1417
 *
1418
 * We will use our local hostname to make up the nas_id
1419
 */
1420
function getNasID()
1421
{
1422
	$nasId = "";
1423
	exec("/bin/hostname", $nasId);
1424
	if(!$nasId[0])
1425
		$nasId[0] = "{$g['product_name']}";
1426
	return $nasId[0];
1427
}
1428

    
1429
/**
1430
 * Get the NAS-IP-Address based on the current wan address
1431
 *
1432
 * Use functions in interfaces.inc to find this out
1433
 *
1434
 */
1435

    
1436
function getNasIP()
1437
{
1438
	global $config;
1439

    
1440
	if (empty($config['captiveportal']['radiussrcip_attribute'])) {
1441
			$nasIp = get_interface_ip();
1442
	} else {
1443
		if (is_ipaddr($config['captiveportal']['radiussrcip_attribute']))
1444
			$nasIp = $config['captiveportal']['radiussrcip_attribute'];
1445
		else
1446
			$nasIp = get_interface_ip($config['captiveportal']['radiussrcip_attribute']);
1447
	}
1448
		
1449
	if(!is_ipaddr($nasIp))
1450
		$nasIp = "0.0.0.0";
1451

    
1452
	return $nasIp;
1453
}
1454

    
1455
function portal_ip_from_client_ip($cliip) {
1456
	global $config;
1457

    
1458
	$interfaces = explode(",", $config['captiveportal']['interface']);
1459
	foreach ($interfaces as $cpif) {
1460
		$ip = get_interface_ip($cpif);
1461
		$sn = get_interface_subnet($cpif);
1462
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1463
			return $ip;
1464
	}
1465

    
1466
	// doesn't match up to any particular interface
1467
	// so let's set the portal IP to what PHP says 
1468
	// the server IP issuing the request is. 
1469
	// allows same behavior as 1.2.x where IP isn't 
1470
	// in the subnet of any CP interface (static routes, etc.)
1471
	// rather than forcing to DNS hostname resolution
1472
	$ip = $_SERVER['SERVER_ADDR'];
1473
	if (is_ipaddr($ip))
1474
		return $ip;
1475

    
1476
	return false;
1477
}
1478

    
1479
/* functions move from index.php */
1480

    
1481
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1482
	global $g, $config;
1483

    
1484
	/* Get captive portal layout */
1485
	if ($type == "redir") {
1486
		header("Location: {$redirurl}");
1487
		return;
1488
	} else if ($type == "login")
1489
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal.html");
1490
	else
1491
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-error.html");
1492

    
1493
	/* substitute the PORTAL_REDIRURL variable */
1494
	if ($config['captiveportal']['preauthurl']) {
1495
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$config['captiveportal']['preauthurl']}", $htmltext);
1496
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$config['captiveportal']['preauthurl']}", $htmltext);
1497
	}
1498

    
1499
	/* substitute other variables */
1500
	if (isset($config['captiveportal']['httpslogin'])) {
1501
		$htmltext = str_replace("\$PORTAL_ACTION\$", "https://{$config['captiveportal']['httpsname']}:8001/", $htmltext);
1502
		$htmltext = str_replace("#PORTAL_ACTION#", "https://{$config['captiveportal']['httpsname']}:8001/", $htmltext);
1503
	} else {
1504
		$ifip = portal_ip_from_client_ip($clientip);
1505
		if (!$ifip)
1506
			$ourhostname = $config['system']['hostname'] . ":8000";
1507
		else
1508
			$ourhostname = "{$ifip}:8000";
1509
		$htmltext = str_replace("\$PORTAL_ACTION\$", "http://{$ourhostname}/", $htmltext);
1510
		$htmltext = str_replace("#PORTAL_ACTION#", "http://{$ourhostname}/", $htmltext);
1511
	}
1512

    
1513
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1514
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1515
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1516
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1517

    
1518
	// Special handling case for captive portal master page so that it can be ran 
1519
	// through the PHP interpreter using the include method above.  We convert the
1520
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
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
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1526
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1527

    
1528
    echo $htmltext;
1529
}
1530

    
1531
function portal_mac_radius($clientmac,$clientip) {
1532
    global $config ;
1533

    
1534
    $radmac_secret = $config['captiveportal']['radmac_secret'];
1535

    
1536
    /* authentication against the radius server */
1537
    $username = mac_format($clientmac);
1538
    $auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1539
    if ($auth_list['auth_val'] == 2)
1540
        return TRUE;
1541
    if (!empty($auth_list['url_redirection']))
1542
	portal_reply_page($auth_list['url_redirection'], "redir");
1543

    
1544
    return FALSE;
1545
}
1546

    
1547
function portal_allow($clientip,$clientmac,$username,$password = null, $attributes = null, $ruleno = null)  {
1548

    
1549
	global $redirurl, $g, $config, $type, $passthrumac, $_POST;
1550

    
1551
	/* See if a ruleno is passed, if not start sessions because this means there isn't one atm */
1552
	if ($ruleno == null)
1553
		$ruleno = captiveportal_get_next_ipfw_ruleno();
1554

    
1555
	/* if the pool is empty, return appropriate message and exit */
1556
	if (is_null($ruleno)) {
1557
		portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1558
		log_error("WARNING!  Captive portal has reached maximum login capacity");
1559
		exit;
1560
	}
1561

    
1562
	// Ensure we create an array if we are missing attributes
1563
	if (!is_array($attributes))
1564
		$attributes = array();
1565

    
1566
	$radiusservers = captiveportal_get_radius_servers();
1567

    
1568
	/* Do not allow concurrent login execution. */
1569
	$cpdblck = lock('captiveportaldb', LOCK_EX);
1570

    
1571
	unset($sessionid);
1572

    
1573
	/* read in client database */
1574
	$cpdb = captiveportal_read_db(true);
1575

    
1576
	if ($attributes['voucher'])
1577
		$remaining_time = $attributes['session_timeout'];
1578

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

    
1616
	foreach ($cpdb as $sid => $cpentry) {
1617
		/* on the same ip */
1618
		if($cpentry[2] == $clientip) {
1619
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1620
			$sessionid = $sid;
1621
			break;
1622
		}
1623
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1624
			// user logged in with an active voucher. Check for how long and calculate 
1625
			// how much time we can give him (voucher credit - used time)
1626
			$remaining_time = $cpentry[0] + $cpentry[7] - time();
1627
			if ($remaining_time < 0)    // just in case. 
1628
				$remaining_time = 0;
1629

    
1630
			/* This user was already logged in so we disconnect the old one */
1631
			captiveportal_disconnect($cpentry,$radiusservers,13);
1632
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1633
			unset($cpdb[$sid]);
1634
			break;
1635
		}
1636
		elseif ((isset($config['captiveportal']['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1637
			/* on the same username */
1638
			if (strcasecmp($cpentry[4], $username) == 0) {
1639
				/* This user was already logged in so we disconnect the old one */
1640
				captiveportal_disconnect($cpentry,$radiusservers,13);
1641
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1642
				unset($cpdb[$sid]);
1643
				break;
1644
			}
1645
		}
1646
	}
1647

    
1648
	if ($attributes['voucher'] && $remaining_time <= 0)
1649
		return 0;       // voucher already used and no time left
1650

    
1651
	if (!isset($sessionid)) {
1652
		/* generate unique session ID */
1653
		$tod = gettimeofday();
1654
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1655

    
1656
		/* Add rules for traffic shaping
1657
		 * We don't need to add extra rules since traffic will pass due to the following kernel option
1658
		 * net.inet.ip.fw.one_pass: 1
1659
		 */
1660
		$peruserbw = isset($config['captiveportal']['peruserbw']);
1661

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

    
1665
		if ($passthrumac) {
1666
			$mac = array();
1667
			$mac['mac'] = $clientmac;
1668
			if (isset($config['captiveportal']['passthrumacaddusername']))
1669
				$mac['username'] = $username;
1670
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1671
			if (!empty($bw_up))
1672
				$mac['bw_up'] = $bw_up;
1673
			if (!empty($bw_down))
1674
				$mac['bw_down'] = $bw_down;
1675
			if (!is_array($config['captiveportal']['passthrumac']))
1676
				$config['captiveportal']['passthrumac'] = array();
1677
			$config['captiveportal']['passthrumac'][] = $mac;
1678
			unlock($cpdblck);
1679
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1680
			file_put_contents("{$g['tmp_path']}/macentry.rules.tmp", $macrules);
1681
			mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry.rules.tmp");
1682
			$writecfg = true;
1683
		} else {
1684
			if ($peruserbw && !empty($bw_up) && is_numeric($bw_up)) {
1685
				$bw_up_pipeno = $ruleno + 20000;
1686
				//$bw_up /= 1000; // Scale to Kbit/s
1687
				mwexec("/sbin/ipfw pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100");
1688

    
1689
				if (!isset($config['captiveportal']['nomacfilter']))
1690
					mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac} {$bw_up_pipeno}");
1691
				else
1692
					mwexec("/sbin/ipfw table 1 add {$clientip} {$bw_up_pipeno}");
1693
			} else {
1694
				if (!isset($config['captiveportal']['nomacfilter']))
1695
					mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac}");
1696
				else
1697
					mwexec("/sbin/ipfw table 1 add {$clientip}");
1698
			}
1699
			if ($peruserbw && !empty($bw_down) && is_numeric($bw_down)) {
1700
				$bw_down_pipeno = $ruleno + 20001;
1701
				//$bw_down /= 1000; // Scale to Kbit/s
1702
				mwexec("/sbin/ipfw pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100");
1703

    
1704
				if (!isset($config['captiveportal']['nomacfilter']))
1705
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac} {$bw_down_pipeno}");
1706
				else
1707
					mwexec("/sbin/ipfw table 2 add {$clientip} {$bw_down_pipeno}");
1708
			} else {
1709
				if (!isset($config['captiveportal']['nomacfilter']))
1710
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac}");
1711
				else
1712
					mwexec("/sbin/ipfw table 2 add {$clientip}");
1713
			}
1714

    
1715
			if ($attributes['voucher'])
1716
				$attributes['session_timeout'] = $remaining_time;
1717

    
1718
			/* encode password in Base64 just in case it contains commas */
1719
			$bpassword = base64_encode($password);
1720
			$cpdb[] = array(time(), $ruleno, $clientip, $clientmac, $username, $sessionid, $bpassword,
1721
				$attributes['session_timeout'], $attributes['idle_timeout'], $attributes['session_terminate_time']);
1722

    
1723
			/* rewrite information to database */
1724
			captiveportal_write_db($cpdb, true);
1725
			unlock($cpdblck);
1726

    
1727
			if (isset($config['captiveportal']['radacct_enable']) && !empty($radiusservers)) {
1728
				$acct_val = RADIUS_ACCOUNTING_START($ruleno,
1729
                                		$username, $sessionid, $radiusservers, $clientip, $clientmac);
1730
				if ($acct_val == 1)
1731
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1732
			}
1733
		}
1734
	} else
1735
		unlock($cpdblck);
1736

    
1737
	if ($writecfg == true)
1738
		write_config();
1739

    
1740
	/* redirect user to desired destination */
1741
	if (!empty($attributes['url_redirection']))
1742
		$my_redirurl = $attributes['url_redirection'];
1743
	else if (!empty($config['captiveportal']['redirurl']))
1744
		$my_redirurl = $config['captiveportal']['redirurl'];
1745
	else
1746
		$my_redirurl = $redirurl;
1747

    
1748
	if(isset($config['captiveportal']['logoutwin_enable']) && !$passthrumac) {
1749

    
1750
		if (isset($config['captiveportal']['httpslogin']))
1751
			$logouturl = "https://{$config['captiveportal']['httpsname']}:8001/";
1752
		else {
1753
			$ifip = portal_ip_from_client_ip($clientip);
1754
			if (!$ifip)
1755
				$ourhostname = $config['system']['hostname'] . ":8000";
1756
			else
1757
				$ourhostname = "{$ifip}:8000";
1758
			$logouturl = "http://{$ourhostname}/";
1759
		}
1760

    
1761
		if (isset($attributes['reply_message']))
1762
			$message = $attributes['reply_message'];
1763
		else
1764
			$message = 0;
1765

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

    
1768
	} else {
1769
		header("Location: " . $my_redirurl);
1770
	}
1771

    
1772
	return $sessionid;
1773
}
1774

    
1775

    
1776
/*
1777
 * Used for when pass-through credits are enabled.
1778
 * Returns true when there was at least one free login to deduct for the MAC.
1779
 * Expired entries are removed as they are seen.
1780
 * Active entries are updated according to the configuration.
1781
 */
1782
function portal_consume_passthrough_credit($clientmac) {
1783
	global $config;
1784

    
1785
	if (!empty($config['captiveportal']['freelogins_count']) && is_numeric($config['captiveportal']['freelogins_count']))
1786
		$freeloginscount = $config['captiveportal']['freelogins_count'];
1787
	else
1788
		return false;
1789

    
1790
	if (!empty($config['captiveportal']['freelogins_resettimeout']) && is_numeric($config['captiveportal']['freelogins_resettimeout']))
1791
		$resettimeout = $config['captiveportal']['freelogins_resettimeout'];
1792
	else
1793
		return false;
1794

    
1795
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1796
		return false;
1797

    
1798
	$updatetimeouts = isset($config['captiveportal']['freelogins_updatetimeouts']);
1799

    
1800
	/*
1801
	 * Read database of used MACs.  Lines are a comma-separated list
1802
	 * of the time, MAC, then the count of pass-through credits remaining.
1803
	 */
1804
	$usedmacs = captiveportal_read_usedmacs_db();
1805

    
1806
	$currenttime = time();
1807
	$found = false;
1808
	foreach ($usedmacs as $key => $usedmac) {
1809
		$usedmac = explode(",", $usedmac);
1810

    
1811
		if ($usedmac[1] == $clientmac) {
1812
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
1813
				if ($usedmac[2] < 1) {
1814
					if ($updatetimeouts) {
1815
						$usedmac[0] = $currenttime;
1816
						unset($usedmacs[$key]);
1817
						$usedmacs[] = implode(",", $usedmac);
1818
						captiveportal_write_usedmacs_db($usedmacs);
1819
					}
1820

    
1821
					return false;
1822
				} else {
1823
					$usedmac[2] -= 1;
1824
					$usedmacs[$key] = implode(",", $usedmac);
1825
				}
1826

    
1827
				$found = true;
1828
			} else
1829
				unset($usedmacs[$key]);
1830

    
1831
			break;
1832
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
1833
				unset($usedmacs[$key]);
1834
	}
1835

    
1836
	if (!$found) {
1837
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
1838
		$usedmacs[] = implode(",", $usedmac);
1839
	}
1840

    
1841
	captiveportal_write_usedmacs_db($usedmacs);
1842
	return true;
1843
}
1844

    
1845
function captiveportal_read_usedmacs_db() {
1846
	global $g;
1847

    
1848
	$cpumaclck = lock('captiveusedmacs');
1849
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs.db")) {
1850
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1851
		if (!$usedmacs)
1852
			$usedmacs = array();
1853
	} else
1854
		$usedmacs = array();
1855

    
1856
	unlock($cpumaclck);
1857
	return $usedmacs;
1858
}
1859

    
1860
function captiveportal_write_usedmacs_db($usedmacs) {
1861
	global $g;
1862

    
1863
	$cpumaclck = lock('captiveusedmacs', LOCK_EX);
1864
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs.db", implode("\n", $usedmacs));
1865
	unlock($cpumaclck);
1866
}
1867

    
1868
?>
(7-7/61)