Project

General

Profile

Download (61.7 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
			<center>
61
				<table cellpadding="6" cellspacing="0" width="550" height="380" style="border:1px solid #000000">
62
					<tr height="10" bgcolor="#990000">
63
						<td style="border-bottom:1px solid #000000">
64
							<font color='white'>
65
								<b>
66
									Guest Voucher code required to continue
67
								</b>
68
							</font>
69
						</td>
70
					</tr>
71
					<tr>
72
						<td>
73
							<div id="mainlevel">
74
								<center>
75
									<table width="100%" border="0" cellpadding="5" cellspacing="0">
76
										<tr>
77
											<td>
78
												<center>
79
													<div id="mainarea">
80
														<center>
81
															<table width="100%" border="0" cellpadding="5" cellspacing="5">
82
																<tr>
83
																	<td>
84
																		<div id="maindivarea">
85
																			<center>
86
																				<div id='statusbox'>
87
																					<font color='red' face='arial' size='+1'>
88
																						<b>
89
																							\$PORTAL_MESSAGE\$
90
																						</b>
91
																					</font>
92
																				</div>
93
																				<p/>
94
																				<div id='loginbox'>
95
																					Enter Voucher Code: 
96
																					<input name="auth_voucher" type="text" style="border:1px dashed;" size="22"> 
97
																					<input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$"> 
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 = <<<EOD
278
<html> 
279
	<body> 
280
		<form method="post" action="\$PORTAL_ACTION\$"> 
281
		<input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$">
282
			<center>
283
				<table cellpadding="6" cellspacing="0" width="550" height="380" style="border:1px solid #000000">
284
					<tr height="10" bgcolor="#990000">
285
						<td style="border-bottom:1px solid #000000">
286
							<font color='white'>
287
								<b>
288
									{$g['product_name']} captive portal
289
								</b>
290
							</font>
291
						</td>
292
					</tr>
293
					<tr>
294
						<td>
295
							<div id="mainlevel">
296
								<center>
297
									<table width="100%" border="0" cellpadding="5" cellspacing="0">
298
										<tr>
299
											<td>
300
												<center>
301
													<div id="mainarea">
302
														<center>
303
															<table width="100%" border="0" cellpadding="5" cellspacing="5">
304
																<tr>
305
																	<td>
306
																		<div id="maindivarea">
307
																			<center>
308
																				<div id='statusbox'>
309
																					<font color='red' face='arial' size='+1'>
310
																						<b>
311
																							\$PORTAL_MESSAGE\$
312
																						</b>
313
																					</font>
314
																				</div>
315
																				<br/>
316
																				<div id='loginbox'>
317
																					<table>
318
																					   <tr><td colspan="2"><center>Welcome to the {$g['product_name']} Captive Portal!</td></tr>
319
																					   <tr><td>&nbsp;</td></tr>
320
																					   <tr><td align="right">Username:</td><td><input name="auth_user" type="text" style="border: 1px dashed;"></td></tr>
321
																					   <tr><td align="right">Password:</td><td><input name="auth_pass" type="password" style="border: 1px dashed;"></td></tr>
322
																					   <tr><td>&nbsp;</td></tr>
323
																					   <tr>
324
																						 <td colspan="2">
325
																							<center><input name="accept" type="submit" value="Continue"></center>
326
																						 </td>
327
																					   </tr>
328
																					</table>
329
																				</div>
330
																			</center>
331
																		</div>
332
																	</td>
333
																</tr>
334
															</table>
335
														</center>
336
													</div>
337
												</center>
338
											</td>
339
										</tr>
340
									</table>
341
								</center>
342
							</div>
343
						</td>
344
					</tr>
345
				</table>
346
			</center>
347
		</form>
348
	</body> 
349
</html>
350

    
351
EOD;
352
		}
353

    
354
		$fd = @fopen("{$g['varetc_path']}/captiveportal-error.html", "w");
355
		if ($fd) {
356
			// Special case handling.  Convert so that we can pass this page
357
			// through the PHP interpreter later without clobbering the vars.
358
			$errtext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $errtext);
359
			$errtext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $errtext);
360
			$errtext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $errtext);
361
			$errtext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $errtext);
362
			$errtext = str_replace("\$ORIGINAL_PORTAL_IP\$", "#ORIGINAL_PORTAL_IP#", $errtext);
363
			$errtext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $errtext);
364
			if($config['captiveportal']['preauthurl']) {
365
				$errtext = str_replace("\$PORTAL_REDIRURL\$", "{$config['captiveportal']['preauthurl']}", $errtext);
366
				$errtext = str_replace("#PORTAL_REDIRURL#", "{$config['captiveportal']['preauthurl']}", $errtext);
367
			}
368
			fwrite($fd, $errtext);
369
			fclose($fd);
370
		}
371

    
372
		/* write error page */
373
		if ($config['captiveportal']['page']['logouttext'])
374
			$logouttext = base64_decode($config['captiveportal']['page']['logouttext']);
375
		else {
376
			/* example page */
377
			$logouttext = <<<EOD
378
<HTML>
379
<HEAD><TITLE>Redirecting...</TITLE></HEAD>
380
<BODY>
381
<SPAN STYLE="font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">
382
<B>Redirecting to <A HREF="<?=\$my_redirurl;?>"><?=\$my_redirurl;?></A>...</B>
383
</SPAN>
384
<SCRIPT LANGUAGE="JavaScript">
385
<!--
386
LogoutWin = window.open('', 'Logout', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=256,height=64');
387
if (LogoutWin) {
388
	LogoutWin.document.write('<HTML>');
389
	LogoutWin.document.write('<HEAD><TITLE>Logout</TITLE></HEAD>') ;
390
	LogoutWin.document.write('<BODY BGCOLOR="#435370">');
391
	LogoutWin.document.write('<DIV ALIGN="center" STYLE="color: #ffffff; font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">') ;
392
	LogoutWin.document.write('<B>Click the button below to disconnect</B><P>');
393
	LogoutWin.document.write('<FORM METHOD="POST" ACTION="<?=\$logouturl;?>">');
394
	LogoutWin.document.write('<INPUT NAME="logout_id" TYPE="hidden" VALUE="<?=\$sessionid;?>">');
395
	LogoutWin.document.write('<INPUT NAME="logout" TYPE="submit" VALUE="Logout">');
396
	LogoutWin.document.write('</FORM>');
397
	LogoutWin.document.write('</DIV></BODY>');
398
	LogoutWin.document.write('</HTML>');
399
	LogoutWin.document.close();
400
}
401

    
402
document.location.href="<?=\$my_redirurl;?>";
403
-->
404
</SCRIPT>
405
</BODY>
406
</HTML>
407

    
408
EOD;
409
		}
410

    
411
		$fd = @fopen("{$g['varetc_path']}/captiveportal-logout.html", "w");
412
		if ($fd) {
413
			fwrite($fd, $logouttext);
414
			fclose($fd);
415
		}
416
		/* write elements */
417
		captiveportal_write_elements();
418

    
419
		/* start up the webserving daemon */
420
		captiveportal_init_webgui();
421

    
422
		/* Kill any existing prunecaptiveportal processes */
423
		if(file_exists("{$g['varrun_path']}/cp_prunedb.pid"))
424
			killbypid("{$g['varrun_path']}/cp_prunedb.pid");
425

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

    
430
		/* generate radius server database */
431
		captiveportal_init_radius_servers();
432

    
433
		if ($g['booting'])
434
			echo "done\n";
435

    
436
	} else {
437
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
438
		killbypid("{$g['varrun_path']}/cp_prunedb.pid");
439

    
440
		captiveportal_radius_stop_all();
441

    
442
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
443

    
444
		/* unload ipfw */
445
		if (is_module_loaded("ipfw.ko"))		
446
			mwexec("/sbin/kldunload ipfw.ko");
447
		$listifs = get_configured_interface_list_by_realif();
448
		foreach ($listifs as $listrealif => $listif) {
449
			if (!empty($listrealif)) {
450
				if (does_interface_exist($listrealif)) {
451
					pfSense_interface_flags($listrealif, -IFF_IPFW_FILTER);
452
					$carpif = link_ip_to_carp_interface(find_interface_ip($listrealif));
453
					if (!empty($carpif)) {
454
						$carpsif = explode(" ", $carpif);
455
						foreach ($carpsif as $cpcarp)
456
							pfSense_interface_flags($cpcarp, -IFF_IPFW_FILTER);
457
					}
458
				}
459
			}
460
		}
461
	}
462

    
463
	unlock($captiveportallck);
464
	
465
	return 0;
466
}
467

    
468
function captiveportal_init_webgui() {
469
	global $g, $config;
470

    
471
	 if (!isset($config['captiveportal']['enable']))
472
				return;
473

    
474
	if ($config['captiveportal']['maxproc'])
475
		$maxproc = $config['captiveportal']['maxproc'];
476
	else
477
		$maxproc = 16;
478

    
479
	$use_fastcgi = true;
480

    
481
	if (isset($config['captiveportal']['httpslogin'])) {
482
		$cert = base64_decode($config['captiveportal']['certificate']);
483
		if (isset($config['captiveportal']['cacertificate']))
484
			$cacert = base64_decode($config['captiveportal']['cacertificate']);
485
		else
486
			$cacert = "";
487
		$key = base64_decode($config['captiveportal']['private-key']);
488
		/* generate lighttpd configuration */
489
		system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal-SSL.conf",
490
			$cert, $key, $cacert, "lighty-CaptivePortal-ssl.pid", "8001", "/usr/local/captiveportal/",
491
			"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
492
	}
493

    
494
	/* generate lighttpd configuration */
495
	system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal.conf",
496
		"", "", "", "lighty-CaptivePortal.pid", "8000", "/usr/local/captiveportal/",
497
		"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
498

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

    
502
	/* fire up https instance */
503
	if (isset($config['captiveportal']['httpslogin']))
504
		$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-CaptivePortal-SSL.conf");
505
}
506

    
507
/* reinit will disconnect all users, be careful! */
508
function captiveportal_init_rules($reinit = false) {
509
	global $config, $g;
510

    
511
	if (!isset($config['captiveportal']['enable']))
512
		return;
513

    
514
	$cpips = array();
515
	$ifaces = get_configured_interface_list();
516
	foreach ($ifaces as $kiface => $kiface2) {
517
		$tmpif = get_real_interface($kiface);
518
		pfSense_interface_flags($tmpif, -IFF_IPFW_FILTER);
519
	}
520
	$cpinterfaces = explode(",", $config['captiveportal']['interface']);
521
	$firsttime = 0;
522
	foreach ($cpinterfaces as $cpifgrp) {
523
		if (!isset($ifaces[$cpifgrp]))
524
			continue;
525
		$tmpif = get_real_interface($cpifgrp);
526
		if (!empty($tmpif)) {
527
			if ($firsttime > 0)
528
				$cpinterface .= " or ";
529
			$cpinterface .= "via {$tmpif}";
530
			$firsttime = 1;
531
			$cpipm = get_interface_ip($cpifgrp);
532
			if (is_ipaddr($cpipm)) {
533
				$carpif = link_ip_to_carp_interface($cpipm);
534
				if (!empty($carpif)) {
535
					$carpsif = explode(" ", $carpif);
536
					foreach ($carpsif as $cpcarp) {
537
						pfSense_interface_flags($cpcarp, IFF_IPFW_FILTER);
538
						$carpip = find_interface_ip($cpcarp);
539
						if (is_ipaddr($carpip))
540
							$cpips[] = $carpip;
541
					}
542
				}
543
				$cpips[] = $cpipm;
544
				pfSense_interface_flags($tmpif, IFF_IPFW_FILTER);
545
			}
546
		}
547
	}
548
	if (count($cpips) > 0) {
549
		$cpactive = true;
550
		$cpinterface = "{ {$cpinterface} } ";
551
		} else
552
		return false;
553

    
554
	if ($reinit == false)
555
		$captiveportallck = lock('captiveportal');
556

    
557
	/* init dummynet/ipfw rules number database */
558
	captiveportal_init_ipfw_ruleno();
559

    
560
	/* make sure ipfw is loaded */
561
	if (!is_module_loaded("ipfw.ko"))
562
		filter_load_ipfw();
563
	/* Always load dummynet now that even allowed ip and mac passthrough use it. */
564
	if (!is_module_loaded("dummynet.ko"))
565
		mwexec("/sbin/kldload dummynet");
566

    
567
	$cprules =	"add 65291 set 1 allow pfsync from any to any\n";
568
	$cprules .= "add 65292 set 1 allow carp from any to any\n";
569

    
570
	$cprules .= <<<EOD
571
# add 65300 set 1 skipto 65534 all from any to any not layer2
572
# layer 2: pass ARP
573
add 65301 set 1 pass layer2 mac-type arp
574
# pfsense requires for WPA
575
add 65302 set 1 pass layer2 mac-type 0x888e
576
add 65303 set 1 pass layer2 mac-type 0x88c7
577

    
578
# PPP Over Ethernet Discovery Stage
579
add 65304 set 1 pass layer2 mac-type 0x8863
580
# PPP Over Ethernet Session Stage
581
add 65305 set 1 pass layer2 mac-type 0x8864
582
# Allow WPA
583
add 65306 set 1 pass layer2 mac-type 0x888e
584

    
585
# layer 2: block anything else non-IP
586
add 65307 set 1 deny layer2 not mac-type ip
587

    
588
EOD;
589

    
590
	$rulenum = 65310;
591
	$ipcount = 0;
592
	$ips = "";
593
	foreach ($cpips as $cpip) {
594
		if($ipcount == 0) {
595
			$ips = "{$cpip} ";
596
		} else {
597
			$ips .= "or {$cpip} ";
598
		}
599
		$ipcount++;
600
	}
601
	$ips = "{ 255.255.255.255 or {$ips} }";
602
	$cprules .= "add {$rulenum} set 1 pass ip from any to {$ips} in\n";
603
	$rulenum++;
604
	$cprules .= "add {$rulenum} set 1 pass ip from {$ips} to any out\n";
605
	$rulenum++;
606
	$cprules .= "add {$rulenum} set 1 pass icmp from {$ips} to any out icmptype 0\n";
607
	$rulenum++;
608
	$cprules .= "add {$rulenum} set 1 pass icmp from any to {$ips} in icmptype 8 \n";
609
	$rulenum++;
610
	/* Allowed ips */
611
	$cprules .= "add {$rulenum} allow ip from table(3) to any in\n";
612
	$rulenum++;
613
	$cprules .= "add {$rulenum} allow ip from any to table(4) out\n";
614
	$rulenum++;
615
	$cprules .= "add {$rulenum} pipe tablearg ip from table(5) to any in\n";
616
	$rulenum++;
617
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(6) out\n";
618
	$rulenum++;
619
	$cprules .= "add {$rulenum} allow ip from any to table(7) in\n";
620
	$rulenum++;
621
	$cprules .= "add {$rulenum} allow ip from table(8) to any out\n";
622
	$rulenum++;
623
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(9) in\n";
624
	$rulenum++;
625
	$cprules .= "add {$rulenum} pipe tablearg ip from table(10) to any out\n";
626
	$rulenum++;
627

    
628
	/* Authenticated users rules. */
629
	if (isset($config['captiveportal']['peruserbw'])) {
630
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from table(1) to any in\n";
631
		$rulenum++;
632
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from any to table(2) out\n";
633
		$rulenum++;
634
	} else {
635
		$cprules .= "add {$rulenum} set 1 allow ip from table(1) to any in\n";
636
		$rulenum++;
637
		$cprules .= "add {$rulenum} set 1 allow ip from any to table(2) out\n";
638
		$rulenum++;
639
	}
640
	
641
	   $cprules .= <<<EOD
642

    
643
# redirect non-authenticated clients to captive portal
644
add 65531 set 1 fwd 127.0.0.1,8000 tcp from any to any in
645
# let the responses from the captive portal web server back out
646
add 65532 set 1 pass tcp from any to any out
647
# block everything else
648
add 65533 set 1 deny all from any to any
649
# pass everything else on layer2
650
add 65534 set 1 pass all from any to any layer2
651

    
652
EOD;
653

    
654
	/* generate passthru mac database */
655
	$cprules .= captiveportal_passthrumac_configure(true);
656
	$cprules .= "\n";
657

    
658
	/* allowed ipfw rules to make allowed ip work */
659
	$cprules .= captiveportal_allowedip_configure();
660

    
661
	/* allowed ipfw rules to make allowed hostnames work */
662
	$cprules .= captiveportal_allowedhostname_configure();
663
	
664
	/* load rules */
665
	if ($reinit == true)
666
		$cprules = "table all flush\nflush\n{$cprules}";
667
	else {
668
		$tmprules = "table 3 flush\n";
669
		$tmprules .= "table 4 flush\n";
670
		$tmprules .= "table 5 flush\n";
671
		$tmprules .= "table 6 flush\n";
672
		$tmprules .= "table 7 flush\n";
673
		$tmprules .= "table 8 flush\n";
674
		$tmprules .= "table 9 flush\n";
675
		$tmprules .= "table 10 flush\n";
676
		$tmprules .= "flush\n";
677
		$cprules = "{$tmprules}\n{$cprules}";
678
	}
679

    
680
	file_put_contents("{$g['tmp_path']}/ipfw.cp.rules", $cprules);
681
	mwexec("/sbin/ipfw -q {$g['tmp_path']}/ipfw.cp.rules", true);
682
	//@unlink("{$g['tmp_path']}/ipfw.cp.rules");
683

    
684
	if ($reinit == false)
685
		unlock($captiveportallck);
686

    
687
	/* activate ipfw(4) so CP can work */
688
	mwexec("/sbin/sysctl net.link.ether.ipfw=1");
689

    
690
	return $cprules;
691
}
692

    
693
/* remove clients that have been around for longer than the specified amount of time
694
 * db file structure:
695
 * timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time
696
 * (password is in Base64 and only saved when reauthentication is enabled)
697
 */
698
function captiveportal_prune_old() {
699
	global $g, $config;
700

    
701
	/* check for expired entries */
702
	if (empty($config['captiveportal']['timeout']) ||
703
	!is_numeric($config['captiveportal']['timeout']))
704
		$timeout = 0;
705
	else
706
		$timeout = $config['captiveportal']['timeout'] * 60;
707

    
708
	if (empty($config['captiveportal']['idletimeout']) ||
709
	!is_numeric($config['captiveportal']['idletimeout']))
710
		$idletimeout = 0;
711
	else
712
		$idletimeout = $config['captiveportal']['idletimeout'] * 60;
713

    
714
	if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && 
715
	!isset($config['captiveportal']['radiussession_timeout']) && !isset($config['voucher']['enable']))
716
		return;
717

    
718
	$radiusservers = captiveportal_get_radius_servers();
719

    
720
	/* read database */
721
	$cpdb = captiveportal_read_db();
722

    
723
	/*	To make sure we iterate over ALL accounts on every run the count($cpdb) is moved
724
	 *	outside of the loop. Otherwise the loop would evaluate count() on every iteration
725
	 *	and since $i would increase and count() would decrement they would meet before we
726
	 *	had a chance to iterate over all accounts.
727
	 */
728
	$unsetindexes = array();
729
	foreach ($cpdb as $cpentry) {
730

    
731
		$timedout = false;
732
		$term_cause = 1;
733

    
734
		/* hard timeout? */
735
		if ($timeout) {
736
			if ((time() - $cpentry[0]) >= $timeout) {
737
				$timedout = true;
738
				$term_cause = 5; // Session-Timeout
739
			}
740
		}
741

    
742
		/* Session-Terminate-Time */
743
		if (!$timedout && !empty($cpentry[9])) {
744
			if (time() >= $cpentry[9]) {
745
				$timedout = true;
746
				$term_cause = 5; // Session-Timeout
747
			}
748
		}
749

    
750
		/* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
751
		$uidletimeout = (is_numeric($cpentry[8])) ? $cpentry[8] : $idletimeout;
752
		/* if an idle timeout is specified, get last activity timestamp from ipfw */
753
		if (!$timedout && $uidletimeout) {
754
			$lastact = captiveportal_get_last_activity($cpentry[2]);
755
			/*	If the user has logged on but not sent any traffic they will never be logged out.
756
			 *	We "fix" this by setting lastact to the login timestamp. 
757
			 */
758
			$lastact = $lastact ? $lastact : $cpentry[0];
759
			if ($lastact && ((time() - $lastact) >= $uidletimeout)) {
760
				$timedout = true;
761
				$term_cause = 4; // Idle-Timeout
762
				$stop_time = $lastact; // Entry added to comply with WISPr
763
			}
764
		}
765

    
766
		/* if vouchers are configured, activate session timeouts */
767
		if (!$timedout && isset($config['voucher']['enable']) && !empty($cpentry[7])) {
768
			if (time() >= ($cpentry[0] + $cpentry[7])) {
769
				$timedout = true;
770
				$term_cause = 5; // Session-Timeout
771
			}
772
		}
773

    
774
		/* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
775
		if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpentry[7])) {
776
			if (time() >= ($cpentry[0] + $cpentry[7])) {
777
				$timedout = true;
778
				$term_cause = 5; // Session-Timeout
779
			}
780
		}
781

    
782
		if ($timedout) {
783
			captiveportal_disconnect($cpentry, $radiusservers,$term_cause,$stop_time);
784
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
785
			$unsetindexes[] = $cpentry[5];
786
		}
787

    
788
		/* do periodic RADIUS reauthentication? */
789
		if (!$timedout && !empty($radiusservers)) {
790
			if (isset($config['captiveportal']['radacct_enable'])) {
791
				if ($config['captiveportal']['reauthenticateacct'] == "stopstart") {
792
					/* stop and restart accounting */
793
					RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
794
						$cpentry[4], // username
795
						$cpentry[5], // sessionid
796
						$cpentry[0], // start time
797
						$radiusservers,
798
						$cpentry[2], // clientip
799
						$cpentry[3], // clientmac
800
						10); // NAS Request
801
					exec("/sbin/ipfw table 1 entryzerostats {$cpentry[2]}");
802
					exec("/sbin/ipfw table 2 entryzerostats {$cpentry[2]}");
803
					RADIUS_ACCOUNTING_START($cpentry[1], // ruleno
804
						$cpentry[4], // username
805
						$cpentry[5], // sessionid
806
						$radiusservers,
807
						$cpentry[2], // clientip
808
						$cpentry[3]); // clientmac
809
				} else if ($config['captiveportal']['reauthenticateacct'] == "interimupdate") {
810
					RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
811
						$cpentry[4], // username
812
						$cpentry[5], // sessionid
813
						$cpentry[0], // start time
814
						$radiusservers,
815
						$cpentry[2], // clientip
816
						$cpentry[3], // clientmac
817
						10, // NAS Request
818
						true); // Interim Updates
819
				}
820
			}
821

    
822
			/* check this user against RADIUS again */
823
			if (isset($config['captiveportal']['reauthenticate'])) {
824
				$auth_list = RADIUS_AUTHENTICATION($cpentry[4], // username
825
					base64_decode($cpentry[6]), // password
826
					$radiusservers,
827
					$cpentry[2], // clientip
828
					$cpentry[3], // clientmac
829
					$cpentry[1]); // ruleno
830
				if ($auth_list['auth_val'] == 3) {
831
					captiveportal_disconnect($cpentry, $radiusservers, 17);
832
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
833
					$unsetindexes[] = $cpentry[5];
834
				}
835
			}
836
		}
837
	}
838

    
839
	/* write database */
840
	if (!empty($unsetindexes))
841
		captiveportal_write_db($cpdb, false, $unsetindexes);
842
}
843

    
844
/* remove a single client according to the DB entry */
845
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
846
	global $g, $config;
847

    
848
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
849

    
850
	/* this client needs to be deleted - remove ipfw rules */
851
	if (isset($config['captiveportal']['radacct_enable']) && !empty($radiusservers)) {
852
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
853
			$dbent[4], // username
854
			$dbent[5], // sessionid
855
			$dbent[0], // start time
856
			$radiusservers,
857
			$dbent[2], // clientip
858
			$dbent[3], // clientmac
859
			$term_cause, // Acct-Terminate-Cause
860
			false,
861
			$stop_time);
862
	}
863
	
864
	if (is_ipaddr($dbent[2])) {
865
		/* Delete client's ip entry from tables 3 and 4. */
866
		mwexec("/sbin/ipfw table 1 delete {$dbent[2]}");
867
		mwexec("/sbin/ipfw table 2 delete {$dbent[2]}");
868
		/* XXX: Redundant?! Ensure all pf(4) states are killed. */
869
		mwexec("pfctl -k {$dbent[2]}");
870
		mwexec("pfctl -K {$dbent[2]}");
871
	}
872

    
873
	/* 
874
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
875
	* We could get an error if the pipe doesn't exist but everything should still be fine
876
	*/
877
	if (isset($config['captiveportal']['peruserbw'])) {
878
		mwexec("/sbin/ipfw pipe " . ($dbent[1]+20000) . " delete");
879
		mwexec("/sbin/ipfw pipe " . ($dbent[1]+20001) . " delete");
880
	}
881

    
882
	/* Release the ruleno so it can be reallocated to new clients. */
883
	captiveportal_free_ipfw_ruleno($dbent[1]);
884

    
885
	// XMLRPC Call over to the master Voucher node
886
	$a_voucher = &$config['voucher'];
887
	if(!empty($a_voucher['vouchersyncdbip'])) {
888
		$syncip   = $a_voucher['vouchersyncdbip'];
889
		$syncport = $a_voucher['vouchersyncport'];
890
		$syncpass = $a_voucher['vouchersyncpass'];
891
		$vouchersyncusername = $a_voucher['vouchersyncusername'];
892
		$remote_status = xmlrpc_sync_voucher_disconnect($dben, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time);
893
	}
894

    
895
}
896

    
897
/* remove a single client by sessionid */
898
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
899
	global $g, $config;
900

    
901
	$radiusservers = captiveportal_get_radius_servers();
902
	$unsetindex = array();
903

    
904
	$cpdblck = lock('captiveportaldb', LOCK_EX);
905

    
906
	/* read database */
907
	$cpdb = captiveportal_read_db(true);
908

    
909
	/* find entry */
910
	if (isset($cpdb[$sessionid])) {
911
		$cpentry = $cpdb[$sessionid];
912
		/* write database */
913
		$unsetindex[] = $sessionid;
914
		captiveportal_write_db($cpdb, true, $unsetindex);
915
		unlock($cpdblck);
916

    
917
		captiveportal_disconnect($cpentry, $radiusservers, $term_cause);
918
		captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
919
	}		
920
}
921

    
922
/* send RADIUS acct stop for all current clients */
923
function captiveportal_radius_stop_all() {
924
	global $config;
925

    
926
	if (!isset($config['captiveportal']['radacct_enable']))
927
		return;
928

    
929
	$radiusservers = captiveportal_get_radius_servers();
930
	if (!empty($radiusservers)) {
931
		$cpdb = captiveportal_read_db();
932
		foreach ($cpdb as $cpentry) {
933
			RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
934
				$cpentry[4], // username
935
				$cpentry[5], // sessionid
936
				$cpentry[0], // start time
937
				$radiusservers,
938
				$cpentry[2], // clientip
939
				$cpentry[3], // clientmac
940
				7); // Admin Reboot
941
		}
942
	}
943
}
944

    
945
function captiveportal_passthrumac_configure_entry($macent) {
946
	$rules = "";
947
	$enBwup = isset($macent['bw_up']);
948
	$enBwdown = isset($macent['bw_down']);
949
	$actionup = "allow";
950
	$actiondown = "allow";
951

    
952
	if ($enBwup && $enBwdown)
953
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
954
	else
955
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
956

    
957
	if ($enBwup) {
958
		$bw_up = $ruleno + 20000;
959
		$rules .= "pipe {$bw_up} config bw {$macent['bw_up']}Kbit/s queue 100\n";
960
		$actionup = "pipe {$bw_up}";
961
	}
962
	if ($enBwdown) {
963
		$bw_down = $ruleno + 20001;
964
		$rules .= "pipe {$bw_down} config bw {$macent['bw_down']}Kbit/s queue 100\n";
965
		$actiondown = "pipe {$bw_down}";
966
	}
967
	$rules .= "add {$ruleno} {$actiondown} ip from any to any MAC {$macent['mac']} any\n";
968
	$ruleno++;
969
	$rules .= "add {$ruleno} {$actionup} ip from any to any MAC any {$macent['mac']}\n";
970

    
971
	return $rules;
972
}
973

    
974
function captiveportal_passthrumac_configure($lock = false) {
975
	global $config, $g;
976

    
977
	$rules = "";
978

    
979
	if (is_array($config['captiveportal']['passthrumac'])) {
980
		$macdb = array();
981
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
982
			$rules .= captiveportal_passthrumac_configure_entry($macent);
983
			$macdb[$macent['mac']]['active']  = true;
984

    
985
		}
986
	}
987

    
988
	return $rules;
989
}
990

    
991
function captiveportal_passthrumac_findbyname($username) {
992
	global $config;
993

    
994
	if (is_array($config['captiveportal']['passthrumac'])) {
995
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
996
			if ($macent['username'] == $username)
997
				return $macent;
998
		}
999
	}
1000
	return NULL;
1001
}
1002

    
1003
/* 
1004
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1005
 * table (5=IN)/(6=OUT) hold allowed ip's with bw limit.
1006
 */
1007
function captiveportal_allowedip_configure_entry($ipent) {
1008

    
1009
	/* This function can deal with hostname or ipaddress */
1010
	if($ipent['ip']) 	
1011
		$ipaddress = $ipent['ip'];
1012

    
1013
	/*  Instead of copying this entire function for something
1014
	 *  easy such as hostname vs ip address add this check
1015
	 */
1016
	if($ipent['hostname']) {
1017
		$ipaddress = gethostbyname($ipent['hostname']);
1018
		if(!is_ipaddr($ipaddress)) 
1019
			return;
1020
	}
1021

    
1022
	$rules = "";
1023
	$enBwup = intval($ipent['bw_up']);
1024
	$enBwdown = intval($ipent['bw_down']);
1025
	$bw_up = "";
1026
	$bw_down = "";
1027
	$tablein = array();
1028
	$tableout = array();
1029

    
1030
	if (intval($enBwup) > 0 or intval($enBwdown) > 0)
1031
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
1032
	else
1033
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
1034

    
1035
	if ($ipent['dir'] == "from") {
1036
		if ($enBwup)
1037
			$tablein[] = 5;
1038
		else
1039
			$tablein[] = 3;
1040
		if ($enBwdown)
1041
			$tableout[] = 6;
1042
		else
1043
			$tableout[] = 4;
1044
	} else if ($ipent['dir'] == "to") {
1045
		if ($enBwup)
1046
			$tablein[] = 9;
1047
		else
1048
			$tablein[] = 7;
1049
		if ($enBwdown)
1050
			$tableout[] = 10;
1051
		else
1052
			$tableout[] = 8;
1053
	} else if ($ipent['dir'] == "both") {
1054
		if ($enBwup) {
1055
			$tablein[] = 5;
1056
			$tablein[] = 9;
1057
		} else {
1058
			$tablein[] = 3;
1059
			$tablein[] = 7;
1060
		}
1061
		if ($enBwdown) {
1062
			$tableout[] = 6;
1063
			$tableout[] = 10;
1064
		} else {
1065
			$tableout[] = 4;
1066
			$tableout[] = 8;
1067
		}
1068
	}
1069
	if ($enBwup) {
1070
		$bw_up = $ruleno + 20000;
1071
		$rules .= "pipe {$bw_up} config bw {$ipent['bw_up']}Kbit/s queue 100\n";
1072
	}
1073
	$subnet = "";
1074
	if (!empty($ipent['sn']))
1075
		$subnet = "/{$ipent['sn']}";
1076
	foreach ($tablein as $table)
1077
		$rules .= "table {$table} add {$ipaddress}{$subnet} {$bw_up}\n";
1078
	if ($enBwdown) {
1079
		$bw_down = $ruleno + 20001;
1080
		$rules .= "pipe {$bw_down} config bw {$ipent['bw_down']}Kbit/s queue 100\n";
1081
	}
1082
	foreach ($tableout as $table)
1083
		$rules .= "table {$table} add {$ipaddress}{$subnet} {$bw_down}\n";
1084

    
1085
	return $rules;
1086
}
1087

    
1088
/* 
1089
	Adds a dnsfilter entry and watches for hostname changes.
1090
	A change results in reloading the ruleset.
1091
*/
1092
function setup_dnsfilter_entries() {
1093
	global $g, $config;
1094

    
1095
	$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-captiveportal.conf";
1096
	$cp_filterdns_conf = "";
1097
	if (is_array($config['captiveportal']['allowedhostname'])) {
1098
		foreach ($config['captiveportal']['allowedhostname'] as $hostnameent)  {
1099
			$cp_filterdns_conf .= "ipfw {$hostnameent['hostname']} 3\n";
1100
			$cp_filterdns_conf .= "ipfw {$hostnameent['hostname']} 4\n";
1101
			$cp_filterdns_conf .= "ipfw {$hostnameent['hostname']} 7\n";
1102
			$cp_filterdns_conf .= "ipfw {$hostnameent['hostname']} 8\n";
1103
		}
1104
	}
1105
	file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1106
	killbypid("{$g['tmp_path']}/filterdns-cpah.pid");
1107
	mwexec("/usr/local/sbin/filterdns -p {$g['tmp_path']}/filterdns-cpah.pid -i 300 -c {$cp_filterdns_filename} -d 1");
1108
}
1109

    
1110
function captiveportal_allowedhostname_configure() {
1111
	global $config, $g;
1112

    
1113
	$rules = "\n# captiveportal_allowedhostname_configure()\n";
1114
	setup_dnsfilter_entries();
1115
	if (is_array($config['captiveportal']['allowedhostname'])) {
1116
		foreach ($config['captiveportal']['allowedhostname'] as $hostnameent) 
1117
			$rules .= captiveportal_allowedip_configure_entry($hostnameent);
1118
	}
1119
	return $rules;
1120
}
1121

    
1122
function captiveportal_allowedip_configure() {
1123
	global $config, $g;
1124

    
1125
	$rules = "";
1126
	if (is_array($config['captiveportal']['allowedip'])) {
1127
		foreach ($config['captiveportal']['allowedip'] as $ipent) 
1128
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1129
	}
1130

    
1131
	return $rules;
1132
}
1133

    
1134
/* get last activity timestamp given client IP address */
1135
function captiveportal_get_last_activity($ip) {
1136

    
1137
	$ipfwoutput = "";
1138

    
1139
	exec("/sbin/ipfw table 1 entrystats {$ip} 2>/dev/null", $ipfwoutput);
1140
	/* Reading only from one of the tables is enough of approximation. */
1141
	if ($ipfwoutput[0]) {
1142
		$ri = explode(" ", $ipfwoutput[0]);
1143
		if ($ri[4])
1144
			return $ri[4];
1145
	}
1146

    
1147
	return 0;
1148
}
1149

    
1150
function captiveportal_init_radius_servers() {
1151
	global $config, $g;
1152

    
1153
	/* generate radius server database */
1154
	if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
1155
		($config['captiveportal']['auth_method'] == "radius"))) {
1156
		$radiusip = $config['captiveportal']['radiusip'];
1157
		$radiusip2 = ($config['captiveportal']['radiusip2']) ? $config['captiveportal']['radiusip2'] : null;
1158

    
1159
		if ($config['captiveportal']['radiusport'])
1160
			$radiusport = $config['captiveportal']['radiusport'];
1161
		else
1162
			$radiusport = 1812;
1163
		if ($config['captiveportal']['radiusacctport'])
1164
			$radiusacctport = $config['captiveportal']['radiusacctport'];
1165
		else
1166
			$radiusacctport = 1813;
1167
		if ($config['captiveportal']['radiusport2'])
1168
			$radiusport2 = $config['captiveportal']['radiusport2'];
1169
		else
1170
			$radiusport2 = 1812;
1171
		$radiuskey = $config['captiveportal']['radiuskey'];
1172
		$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
1173

    
1174
		$cprdsrvlck = lock('captiveportalradius', LOCK_EX);
1175
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db", "w");
1176
		if (!$fd) {
1177
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1178
			unlock($cprdsrvlck);
1179
			return 1;
1180
		} else if (isset($radiusip2, $radiuskey2))
1181
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . "\n"
1182
			. $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2);
1183
		else
1184
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey);
1185
		fclose($fd);
1186
		unlock($cprdsrvlck);
1187
	}
1188
}
1189

    
1190
/* read RADIUS servers into array */
1191
function captiveportal_get_radius_servers() {
1192
		global $g;
1193

    
1194
		$cprdsrvlck = lock('captiveportalradius');
1195
		if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
1196
			$radiusservers = array();
1197
			$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius.db", 
1198
			FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1199
			if ($cpradiusdb) {
1200
				foreach($cpradiusdb as $cpradiusentry) {
1201
					$line = trim($cpradiusentry);
1202
					if ($line) {
1203
						$radsrv = array();
1204
						list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
1205
						$radiusservers[] = $radsrv;
1206
					}
1207
				}
1208
			}
1209
			unlock($cprdsrvlck);
1210
			return $radiusservers;
1211
		}
1212

    
1213
		unlock($cprdsrvlck);
1214
		return false;
1215
}
1216

    
1217
/* log successful captive portal authentication to syslog */
1218
/* part of this code from php.net */
1219
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1220
	// Log it
1221
	if (!$message)
1222
		$message = "$status: $user, $mac, $ip";
1223
	else {
1224
		$message = trim($message);
1225
		$message = "$status: $user, $mac, $ip, $message";
1226
	}
1227
	captiveportal_syslog($message);
1228
}
1229

    
1230
/* log simple messages to syslog */
1231
function captiveportal_syslog($message) {
1232
	define_syslog_variables();
1233
	$message = trim($message);
1234
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1235
	// Log it
1236
	syslog(LOG_INFO, $message);
1237
	closelog();
1238
}
1239

    
1240
function radius($username,$password,$clientip,$clientmac,$type) {
1241
	global $g, $config;
1242

    
1243
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1244

    
1245
	/* If the pool is empty, return appropriate message and fail authentication */
1246
	if (is_null($ruleno)) {
1247
		$auth_list = array();
1248
		$auth_list['auth_val'] = 1;
1249
		$auth_list['error'] = "System reached maximum login capacity";
1250
		return $auth_list;
1251
	}
1252

    
1253
	$radiusservers = captiveportal_get_radius_servers();
1254

    
1255
	$auth_list = RADIUS_AUTHENTICATION($username,
1256
		$password,
1257
		$radiusservers,
1258
		$clientip,
1259
		$clientmac,
1260
		$ruleno);
1261

    
1262
	if ($auth_list['auth_val'] == 2) {
1263
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1264
		$sessionid = portal_allow($clientip,
1265
			$clientmac,
1266
			$username,
1267
			$password,
1268
			$auth_list,
1269
			$ruleno);
1270
	}
1271

    
1272
	return $auth_list;
1273
}
1274

    
1275
/* read captive portal DB into array */
1276
function captiveportal_read_db($locked = false) {
1277
	global $g;
1278

    
1279
	$cpdb = array();
1280

    
1281
	if ($locked == false)
1282
		$cpdblck = lock('captiveportaldb');
1283
	$fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
1284
	if ($fd) {
1285
		while (!feof($fd)) {
1286
			$line = trim(fgets($fd));
1287
			if ($line) {
1288
				$cpe = explode(",", $line);
1289
				/* Hash by session id */
1290
				$cpdb[$cpe[5]] = $cpe;
1291
			}
1292
		}
1293
		fclose($fd);
1294
	}
1295
	if ($locked == false)
1296
		unlock($cpdblck);
1297
	return $cpdb;
1298
}
1299

    
1300
/* write captive portal DB */
1301
function captiveportal_write_db($cpdb, $locked = false, $remove = false) {
1302
	global $g;
1303

    
1304
	if ($locked == false)
1305
		$cpdblck = lock('captiveportaldb', LOCK_EX);
1306

    
1307
	if (is_array($remove)) {
1308
		if (!empty($remove)) {
1309
			$cpdb = captiveportal_read_db(true);
1310
			foreach ($remove as $key)
1311
				unset($cpdb[$key]);
1312
		} else
1313
			return; //This makes sure no record removal calls
1314
	}
1315
	$fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
1316
	if ($fd) {
1317
		foreach ($cpdb as $cpent) {
1318
			fwrite($fd, join(",", $cpent) . "\n");
1319
		}
1320
		fclose($fd);
1321
	}
1322
	if ($locked == false)
1323
		unlock($cpdblck);
1324
}
1325

    
1326
function captiveportal_write_elements() {
1327
	global $g, $config;
1328
	
1329
	/* delete any existing elements */
1330
	if (is_dir($g['captiveportal_element_path'])) {
1331
		$dh = opendir($g['captiveportal_element_path']);
1332
		while (($file = readdir($dh)) !== false) {
1333
			if ($file != "." && $file != "..")
1334
				unlink($g['captiveportal_element_path'] . "/" . $file);
1335
		}
1336
		closedir($dh);
1337
	} else {
1338
		@mkdir($g['captiveportal_element_path']);
1339
	}
1340

    
1341
	if (is_array($config['captiveportal']['element'])) {
1342
		conf_mount_rw();
1343
		foreach ($config['captiveportal']['element'] as $data) {
1344
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
1345
			if (!$fd) {
1346
				printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
1347
				return 1;
1348
			}
1349
			$decoded = base64_decode($data['content']);
1350
			fwrite($fd,$decoded);
1351
			fclose($fd);
1352
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1353
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1354
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
1355
		}
1356
		conf_mount_ro();
1357
	}
1358
	
1359
	return 0;
1360
}
1361

    
1362
function captiveportal_init_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
1363
	global $g;
1364

    
1365
	@unlink("{$g['vardb_path']}/captiveportal.rules");
1366
	$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1367
	file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1368
}
1369

    
1370
/*
1371
 * This function will calculate the lowest free firewall ruleno
1372
 * within the range specified based on the actual logged on users
1373
 *
1374
 */
1375
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899, $usebw = false) {
1376
	global $config, $g;
1377

    
1378
	if(!isset($config['captiveportal']['enable']))
1379
		return NULL;
1380

    
1381
	$cpruleslck = lock('captiveportalrules', LOCK_EX);
1382
	$ruleno = 0;
1383
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1384
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1385
		for ($ridx = 2; $ridx < ($rulenos_range_max - $rulenos_start); $ridx++) {
1386
			if ($rules[$ridx]) {
1387
				/* 
1388
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1389
				 * and the out pipe ruleno + 1. This removes limitation that where present in 
1390
				 * previous version of the peruserbw.
1391
				 */
1392
				if (isset($config['captiveportal']['peruserbw']))
1393
					$ridx++;
1394
				continue;
1395
			}
1396
			$ruleno = $ridx;
1397
			$rules[$ridx] = "used";
1398
			if (isset($config['captiveportal']['peruserbw']) || $usebw == true)
1399
				$rules[++$ridx] = "used";
1400
			break;
1401
		}
1402
	} else {
1403
		$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1404
		$rules[2] = "used";
1405
		$ruleno = 2;
1406
	}
1407
	file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1408
	unlock($cpruleslck);
1409
	return $ruleno;
1410
}
1411

    
1412
function captiveportal_free_ipfw_ruleno($ruleno, $usedbw = false) {
1413
	global $config, $g;
1414

    
1415
	if(!isset($config['captiveportal']['enable']))
1416
		return NULL;
1417

    
1418
	$cpruleslck = lock('captiveportalrules', LOCK_EX);
1419
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1420
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1421
		$rules[$ruleno] = false;
1422
		if (isset($config['captiveportal']['peruserbw']) || $usedbw == true)
1423
			$rules[++$ruleno] = false;
1424
		file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1425
	}
1426
	unlock($cpruleslck);
1427
}
1428

    
1429
function captiveportal_get_ipfw_passthru_ruleno($value) {
1430
	global $config, $g;
1431

    
1432
	if(!isset($config['captiveportal']['enable']))
1433
				return NULL;
1434

    
1435
	$cpruleslck = lock('captiveportalrules', LOCK_EX);
1436
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1437
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1438
		$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`);
1439
		if ($rules[$ruleno]) {
1440
			unlock($cpruleslck);
1441
			return $ruleno;
1442
		}
1443
	}
1444

    
1445
	unlock($cpruleslck);
1446
	return NULL;
1447
}
1448

    
1449
/**
1450
 * This function will calculate the traffic produced by a client
1451
 * based on its firewall rule
1452
 *
1453
 * Point of view: NAS
1454
 *
1455
 * Input means: from the client
1456
 * Output means: to the client
1457
 *
1458
 */
1459

    
1460
function getVolume($ip) {
1461

    
1462
	$volume = array();
1463

    
1464
	// Initialize vars properly, since we don't want NULL vars
1465
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1466

    
1467
	// Ingress
1468
	$ipfwin = "";
1469
	$ipfwout = "";
1470
	$matchesin = "";
1471
	$matchesout = "";
1472
	exec("/sbin/ipfw table 1 entrystats {$ip}", $ipfwin);
1473
	if ($ipfwin[0]) {
1474
		$ipfwin = split(" ", $ipfwin[0]);
1475
		$volume['input_pkts'] = $ipfwin[2];
1476
		$volume['input_bytes'] = $ipfwin[3];
1477
	}
1478

    
1479
	exec("/sbin/ipfw table 2 entrystats {$ip}", $ipfwout);
1480
	if ($ipfwout[0]) {
1481
		$ipfwout = split(" ", $ipfwout[0]);
1482
		$volume['output_pkts'] = $ipfwout[2];
1483
		$volume['output_bytes'] = $ipfwout[3];
1484
	}
1485

    
1486
	return $volume;
1487
}
1488

    
1489
/**
1490
 * Get the NAS-Identifier
1491
 *
1492
 * We will use our local hostname to make up the nas_id
1493
 */
1494
function getNasID()
1495
{
1496
	$nasId = "";
1497
	exec("/bin/hostname", $nasId);
1498
	if(!$nasId[0])
1499
		$nasId[0] = "{$g['product_name']}";
1500
	return $nasId[0];
1501
}
1502

    
1503
/**
1504
 * Get the NAS-IP-Address based on the current wan address
1505
 *
1506
 * Use functions in interfaces.inc to find this out
1507
 *
1508
 */
1509

    
1510
function getNasIP()
1511
{
1512
	global $config;
1513

    
1514
	if (empty($config['captiveportal']['radiussrcip_attribute'])) {
1515
			$nasIp = get_interface_ip();
1516
	} else {
1517
		if (is_ipaddr($config['captiveportal']['radiussrcip_attribute']))
1518
			$nasIp = $config['captiveportal']['radiussrcip_attribute'];
1519
		else
1520
			$nasIp = get_interface_ip($config['captiveportal']['radiussrcip_attribute']);
1521
	}
1522
		
1523
	if(!is_ipaddr($nasIp))
1524
		$nasIp = "0.0.0.0";
1525

    
1526
	return $nasIp;
1527
}
1528

    
1529
function portal_ip_from_client_ip($cliip) {
1530
	global $config;
1531

    
1532
	$interfaces = explode(",", $config['captiveportal']['interface']);
1533
	foreach ($interfaces as $cpif) {
1534
		$ip = get_interface_ip($cpif);
1535
		$sn = get_interface_subnet($cpif);
1536
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1537
			return $ip;
1538
	}
1539

    
1540
	// doesn't match up to any particular interface
1541
	// so let's set the portal IP to what PHP says 
1542
	// the server IP issuing the request is. 
1543
	// allows same behavior as 1.2.x where IP isn't 
1544
	// in the subnet of any CP interface (static routes, etc.)
1545
	// rather than forcing to DNS hostname resolution
1546
	$ip = $_SERVER['SERVER_ADDR'];
1547
	if (is_ipaddr($ip))
1548
		return $ip;
1549

    
1550
	return false;
1551
}
1552

    
1553
/* functions move from index.php */
1554

    
1555
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1556
	global $g, $config;
1557

    
1558
	/* Get captive portal layout */
1559
	if ($type == "redir") {
1560
		header("Location: {$redirurl}");
1561
		return;
1562
	} else if ($type == "login")
1563
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal.html");
1564
	else
1565
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-error.html");
1566

    
1567
	/* substitute the PORTAL_REDIRURL variable */
1568
	if ($config['captiveportal']['preauthurl']) {
1569
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$config['captiveportal']['preauthurl']}", $htmltext);
1570
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$config['captiveportal']['preauthurl']}", $htmltext);
1571
	}
1572

    
1573
	/* substitute other variables */
1574
	if (isset($config['captiveportal']['httpslogin'])) {
1575
		$htmltext = str_replace("\$PORTAL_ACTION\$", "https://{$config['captiveportal']['httpsname']}:8001/", $htmltext);
1576
		$htmltext = str_replace("#PORTAL_ACTION#", "https://{$config['captiveportal']['httpsname']}:8001/", $htmltext);
1577
	} else {
1578
		$ifip = portal_ip_from_client_ip($clientip);
1579
		if (!$ifip)
1580
			$ourhostname = $config['system']['hostname'] . ":8000";
1581
		else
1582
			$ourhostname = "{$ifip}:8000";
1583
		$htmltext = str_replace("\$PORTAL_ACTION\$", "http://{$ourhostname}/", $htmltext);
1584
		$htmltext = str_replace("#PORTAL_ACTION#", "http://{$ourhostname}/", $htmltext);
1585
	}
1586

    
1587
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1588
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1589
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1590
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1591

    
1592
	// Special handling case for captive portal master page so that it can be ran 
1593
	// through the PHP interpreter using the include method above.  We convert the
1594
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1595
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1596
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1597
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1598
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1599
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1600
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1601

    
1602
    echo $htmltext;
1603
}
1604

    
1605
function portal_mac_radius($clientmac,$clientip) {
1606
    global $config ;
1607

    
1608
    $radmac_secret = $config['captiveportal']['radmac_secret'];
1609

    
1610
    /* authentication against the radius server */
1611
    $username = mac_format($clientmac);
1612
    $auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1613
    if ($auth_list['auth_val'] == 2)
1614
        return TRUE;
1615
    if (!empty($auth_list['url_redirection']))
1616
	portal_reply_page($auth_list['url_redirection'], "redir");
1617

    
1618
    return FALSE;
1619
}
1620

    
1621
function portal_allow($clientip,$clientmac,$username,$password = null, $attributes = null, $ruleno = null)  {
1622

    
1623
	global $redirurl, $g, $config, $type, $passthrumac, $_POST;
1624

    
1625
	/* See if a ruleno is passed, if not start sessions because this means there isn't one atm */
1626
	if ($ruleno == null)
1627
		$ruleno = captiveportal_get_next_ipfw_ruleno();
1628

    
1629
	/* if the pool is empty, return appropriate message and exit */
1630
	if (is_null($ruleno)) {
1631
		portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1632
		log_error("WARNING!  Captive portal has reached maximum login capacity");
1633
		exit;
1634
	}
1635

    
1636
	// Ensure we create an array if we are missing attributes
1637
	if (!is_array($attributes))
1638
		$attributes = array();
1639

    
1640
	$radiusservers = captiveportal_get_radius_servers();
1641

    
1642
	/* Do not allow concurrent login execution. */
1643
	$cpdblck = lock('captiveportaldb', LOCK_EX);
1644

    
1645
	unset($sessionid);
1646

    
1647
	/* read in client database */
1648
	$cpdb = captiveportal_read_db(true);
1649

    
1650
	if ($attributes['voucher'])
1651
		$remaining_time = $attributes['session_timeout'];
1652

    
1653
	$writecfg = false;
1654
	/* Find an existing session */
1655
	if ((isset($config['captiveportal']['noconcurrentlogins'])) && $passthrumac) {
1656
		if (isset($config['captiveportal']['passthrumacadd'])) {
1657
			$mac = captiveportal_passthrumac_findbyname($username);
1658
			if (!empty($mac)) {
1659
				if ($_POST['replacemacpassthru']) {
1660
					foreach ($config['captiveportal']['passthrumac'] as $idx => $macent) {
1661
						if ($macent['mac'] == $mac['mac']) {
1662
							$macrules = "";
1663
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1664
                                			if ($ruleno) {
1665
								captiveportal_free_ipfw_ruleno($ruleno, true);
1666
                                        			$macrules .= "delete {$ruleno}\n";
1667
								++$ruleno;
1668
                                        			$macrules .= "delete {$ruleno}\n";
1669
                                			}
1670
							unset($config['captiveportal']['passthrumac'][$idx]);
1671
							$mac['mac'] = $clientmac;
1672
							$config['captiveportal']['passthrumac'][] = $mac;
1673
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1674
							file_put_contents("{$g['tmp_path']}/macentry.rules.tmp", $macrules);
1675
							mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry.rules.tmp");
1676
							$writecfg = true;
1677
							$sessionid = true;
1678
							break;
1679
						}
1680
					}
1681
                                } else {
1682
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1683
						$clientmac, $clientip, $username, $password);
1684
					exit;
1685
				}
1686
			}
1687
		}
1688
	}
1689

    
1690
	foreach ($cpdb as $sid => $cpentry) {
1691
		/* on the same ip */
1692
		if($cpentry[2] == $clientip) {
1693
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1694
			$sessionid = $sid;
1695
			break;
1696
		}
1697
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1698
			// user logged in with an active voucher. Check for how long and calculate 
1699
			// how much time we can give him (voucher credit - used time)
1700
			$remaining_time = $cpentry[0] + $cpentry[7] - time();
1701
			if ($remaining_time < 0)    // just in case. 
1702
				$remaining_time = 0;
1703

    
1704
			/* This user was already logged in so we disconnect the old one */
1705
			captiveportal_disconnect($cpentry,$radiusservers,13);
1706
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1707
			unset($cpdb[$sid]);
1708
			break;
1709
		}
1710
		elseif ((isset($config['captiveportal']['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1711
			/* on the same username */
1712
			if (strcasecmp($cpentry[4], $username) == 0) {
1713
				/* This user was already logged in so we disconnect the old one */
1714
				captiveportal_disconnect($cpentry,$radiusservers,13);
1715
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1716
				unset($cpdb[$sid]);
1717
				break;
1718
			}
1719
		}
1720
	}
1721

    
1722
	if ($attributes['voucher'] && $remaining_time <= 0)
1723
		return 0;       // voucher already used and no time left
1724

    
1725
	if (!isset($sessionid)) {
1726
		/* generate unique session ID */
1727
		$tod = gettimeofday();
1728
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1729

    
1730
		/* Add rules for traffic shaping
1731
		 * We don't need to add extra rules since traffic will pass due to the following kernel option
1732
		 * net.inet.ip.fw.one_pass: 1
1733
		 */
1734
		$peruserbw = isset($config['captiveportal']['peruserbw']);
1735

    
1736
		$bw_up = isset($attributes['bw_up']) ? trim($attributes['bw_up']) : $config['captiveportal']['bwdefaultup'];
1737
		$bw_down = isset($attributes['bw_down']) ? trim($attributes['bw_down']) : $config['captiveportal']['bwdefaultdn'];
1738

    
1739
		if ($passthrumac) {
1740
			$mac = array();
1741
			$mac['mac'] = $clientmac;
1742
			if (isset($config['captiveportal']['passthrumacaddusername']))
1743
				$mac['username'] = $username;
1744
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1745
			if (!empty($bw_up))
1746
				$mac['bw_up'] = $bw_up;
1747
			if (!empty($bw_down))
1748
				$mac['bw_down'] = $bw_down;
1749
			if (!is_array($config['captiveportal']['passthrumac']))
1750
				$config['captiveportal']['passthrumac'] = array();
1751
			$config['captiveportal']['passthrumac'][] = $mac;
1752
			unlock($cpdblck);
1753
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1754
			file_put_contents("{$g['tmp_path']}/macentry.rules.tmp", $macrules);
1755
			mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry.rules.tmp");
1756
			$writecfg = true;
1757
		} else {
1758
			if ($peruserbw && !empty($bw_up) && is_numeric($bw_up)) {
1759
				$bw_up_pipeno = $ruleno + 20000;
1760
				//$bw_up /= 1000; // Scale to Kbit/s
1761
				mwexec("/sbin/ipfw pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100");
1762

    
1763
				if (!isset($config['captiveportal']['nomacfilter']))
1764
					mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac} {$bw_up_pipeno}");
1765
				else
1766
					mwexec("/sbin/ipfw table 1 add {$clientip} {$bw_up_pipeno}");
1767
			} else {
1768
				if (!isset($config['captiveportal']['nomacfilter']))
1769
					mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac}");
1770
				else
1771
					mwexec("/sbin/ipfw table 1 add {$clientip}");
1772
			}
1773
			if ($peruserbw && !empty($bw_down) && is_numeric($bw_down)) {
1774
				$bw_down_pipeno = $ruleno + 20001;
1775
				//$bw_down /= 1000; // Scale to Kbit/s
1776
				mwexec("/sbin/ipfw pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100");
1777

    
1778
				if (!isset($config['captiveportal']['nomacfilter']))
1779
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac} {$bw_down_pipeno}");
1780
				else
1781
					mwexec("/sbin/ipfw table 2 add {$clientip} {$bw_down_pipeno}");
1782
			} else {
1783
				if (!isset($config['captiveportal']['nomacfilter']))
1784
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac}");
1785
				else
1786
					mwexec("/sbin/ipfw table 2 add {$clientip}");
1787
			}
1788

    
1789
			if ($attributes['voucher'])
1790
				$attributes['session_timeout'] = $remaining_time;
1791

    
1792
			/* encode password in Base64 just in case it contains commas */
1793
			$bpassword = base64_encode($password);
1794
			$cpdb[] = array(time(), $ruleno, $clientip, $clientmac, $username, $sessionid, $bpassword,
1795
				$attributes['session_timeout'], $attributes['idle_timeout'], $attributes['session_terminate_time']);
1796

    
1797
			/* rewrite information to database */
1798
			captiveportal_write_db($cpdb, true);
1799
			unlock($cpdblck);
1800

    
1801
			if (isset($config['captiveportal']['radacct_enable']) && !empty($radiusservers)) {
1802
				$acct_val = RADIUS_ACCOUNTING_START($ruleno,
1803
                                		$username, $sessionid, $radiusservers, $clientip, $clientmac);
1804
				if ($acct_val == 1)
1805
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1806
			}
1807
		}
1808
	} else
1809
		unlock($cpdblck);
1810

    
1811
	if ($writecfg == true)
1812
		write_config();
1813

    
1814
	/* redirect user to desired destination */
1815
	if (!empty($attributes['url_redirection']))
1816
		$my_redirurl = $attributes['url_redirection'];
1817
	else if ($config['captiveportal']['redirurl'])
1818
		$my_redirurl = $config['captiveportal']['redirurl'];
1819
	else
1820
		$my_redirurl = $redirurl;
1821

    
1822
	if(isset($config['captiveportal']['logoutwin_enable']) && !$passthrumac) {
1823

    
1824
		if (isset($config['captiveportal']['httpslogin']))
1825
			$logouturl = "https://{$config['captiveportal']['httpsname']}:8001/";
1826
		else {
1827
			$ifip = portal_ip_from_client_ip($clientip);
1828
			if (!$ifip)
1829
				$ourhostname = $config['system']['hostname'] . ":8000";
1830
			else
1831
				$ourhostname = "{$ifip}:8000";
1832
			$logouturl = "http://{$ourhostname}/";
1833
		}
1834

    
1835
		if (isset($attributes['reply_message']))
1836
			$message = $attributes['reply_message'];
1837
		else
1838
			$message = 0;
1839

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

    
1842
	} else {
1843
		header("Location: " . $my_redirurl);
1844
	}
1845

    
1846
	return $sessionid;
1847
}
1848

    
1849

    
1850
/*
1851
 * Used for when pass-through credits are enabled.
1852
 * Returns true when there was at least one free login to deduct for the MAC.
1853
 * Expired entries are removed as they are seen.
1854
 * Active entries are updated according to the configuration.
1855
 */
1856
function portal_consume_passthrough_credit($clientmac) {
1857
	global $config;
1858

    
1859
	if (!empty($config['captiveportal']['freelogins_count']) && is_numeric($config['captiveportal']['freelogins_count']))
1860
		$freeloginscount = $config['captiveportal']['freelogins_count'];
1861
	else
1862
		return false;
1863

    
1864
	if (!empty($config['captiveportal']['freelogins_resettimeout']) && is_numeric($config['captiveportal']['freelogins_resettimeout']))
1865
		$resettimeout = $config['captiveportal']['freelogins_resettimeout'];
1866
	else
1867
		return false;
1868

    
1869
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1870
		return false;
1871

    
1872
	$updatetimeouts = isset($config['captiveportal']['freelogins_updatetimeouts']);
1873

    
1874
	/*
1875
	 * Read database of used MACs.  Lines are a comma-separated list
1876
	 * of the time, MAC, then the count of pass-through credits remaining.
1877
	 */
1878
	$usedmacs = captiveportal_read_usedmacs_db();
1879

    
1880
	$currenttime = time();
1881
	$found = false;
1882
	foreach ($usedmacs as $key => $usedmac) {
1883
		$usedmac = explode(",", $usedmac);
1884

    
1885
		if ($usedmac[1] == $clientmac) {
1886
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
1887
				if ($usedmac[2] < 1) {
1888
					if ($updatetimeouts) {
1889
						$usedmac[0] = $currenttime;
1890
						unset($usedmacs[$key]);
1891
						$usedmacs[] = implode(",", $usedmac);
1892
						captiveportal_write_usedmacs_db($usedmacs);
1893
					}
1894

    
1895
					return false;
1896
				} else {
1897
					$usedmac[2] -= 1;
1898
					$usedmacs[$key] = implode(",", $usedmac);
1899
				}
1900

    
1901
				$found = true;
1902
			} else
1903
				unset($usedmacs[$key]);
1904

    
1905
			break;
1906
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
1907
				unset($usedmacs[$key]);
1908
	}
1909

    
1910
	if (!$found) {
1911
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
1912
		$usedmacs[] = implode(",", $usedmac);
1913
	}
1914

    
1915
	captiveportal_write_usedmacs_db($usedmacs);
1916
	return true;
1917
}
1918

    
1919
function captiveportal_read_usedmacs_db() {
1920
	global $g;
1921

    
1922
	$cpumaclck = lock('captiveusedmacs');
1923
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs.db")) {
1924
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1925
		if (!$usedmacs)
1926
			$usedmacs = array();
1927
	} else
1928
		$usedmacs = array();
1929

    
1930
	unlock($cpumaclck);
1931
	return $usedmacs;
1932
}
1933

    
1934
function captiveportal_write_usedmacs_db($usedmacs) {
1935
	global $g;
1936

    
1937
	$cpumaclck = lock('captiveusedmacs', LOCK_EX);
1938
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs.db", implode("\n", $usedmacs));
1939
	unlock($cpumaclck);
1940
}
1941

    
1942
?>
(7-7/61)