Project

General

Profile

Download (61.3 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
	/* filter on layer2 as well so we can check MAC addresses */
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

    
886
/* remove a single client by sessionid */
887
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
888
	global $g, $config;
889

    
890
	$radiusservers = captiveportal_get_radius_servers();
891
	$unsetindex = array();
892

    
893
	$cpdblck = lock('captiveportaldb', LOCK_EX);
894

    
895
	/* read database */
896
	$cpdb = captiveportal_read_db(true);
897

    
898
	/* find entry */
899
	if (isset($cpdb[$sessionid])) {
900
		$cpentry = $cpdb[$sessionid];
901
		/* write database */
902
		$unsetindex[] = $sessionid;
903
		captiveportal_write_db($cpdb, true, $unsetindex);
904
		unlock($cpdblck);
905

    
906
		captiveportal_disconnect($cpentry, $radiusservers, $term_cause);
907
		captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
908
	}		
909
}
910

    
911
/* send RADIUS acct stop for all current clients */
912
function captiveportal_radius_stop_all() {
913
	global $config;
914

    
915
	if (!isset($config['captiveportal']['radacct_enable']))
916
		return;
917

    
918
	$radiusservers = captiveportal_get_radius_servers();
919
	if (!empty($radiusservers)) {
920
		$cpdb = captiveportal_read_db();
921
		foreach ($cpdb as $cpentry) {
922
			RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
923
				$cpentry[4], // username
924
				$cpentry[5], // sessionid
925
				$cpentry[0], // start time
926
				$radiusservers,
927
				$cpentry[2], // clientip
928
				$cpentry[3], // clientmac
929
				7); // Admin Reboot
930
		}
931
	}
932
}
933

    
934
function captiveportal_passthrumac_configure_entry($macent) {
935
	$rules = "";
936
	$enBwup = isset($macent['bw_up']);
937
	$enBwdown = isset($macent['bw_down']);
938
	$actionup = "allow";
939
	$actiondown = "allow";
940

    
941
	if ($enBwup && $enBwdown)
942
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
943
	else
944
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
945

    
946
	if ($enBwup) {
947
		$bw_up = $ruleno + 20000;
948
		$rules .= "pipe {$bw_up} config bw {$macent['bw_up']}Kbit/s queue 100\n";
949
		$actionup = "pipe {$bw_up}";
950
	}
951
	if ($enBwdown) {
952
		$bw_down = $ruleno + 20001;
953
		$rules .= "pipe {$bw_down} config bw {$macent['bw_down']}Kbit/s queue 100\n";
954
		$actiondown = "pipe {$bw_down}";
955
	}
956
	$rules .= "add {$ruleno} {$actiondown} ip from any to any MAC {$macent['mac']} any\n";
957
	$ruleno++;
958
	$rules .= "add {$ruleno} {$actionup} ip from any to any MAC any {$macent['mac']}\n";
959

    
960
	return $rules;
961
}
962

    
963
function captiveportal_passthrumac_configure($lock = false) {
964
	global $config, $g;
965

    
966
	$rules = "";
967

    
968
	if (is_array($config['captiveportal']['passthrumac'])) {
969
		$macdb = array();
970
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
971
			$rules .= captiveportal_passthrumac_configure_entry($macent);
972
			$macdb[$macent['mac']]['active']  = true;
973

    
974
		}
975
	}
976

    
977
	return $rules;
978
}
979

    
980
function captiveportal_passthrumac_findbyname($username) {
981
	global $config;
982

    
983
	if (is_array($config['captiveportal']['passthrumac'])) {
984
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
985
			if ($macent['username'] == $username)
986
				return $macent;
987
		}
988
	}
989
	return NULL;
990
}
991

    
992
/* 
993
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
994
 * table (5=IN)/(6=OUT) hold allowed ip's with bw limit.
995
 */
996
function captiveportal_allowedip_configure_entry($ipent) {
997

    
998
	/* This function can deal with hostname or ipaddress */
999
	if($ipent['ip']) 	
1000
		$ipaddress = $ipent['ip'];
1001

    
1002
	/*  Instead of copying this entire function for something
1003
	 *  easy such as hostname vs ip address add this check
1004
	 */
1005
	if($ipent['hostname']) {
1006
		$ipaddress = gethostbyname($ipent['hostname']);
1007
		if(!is_ipaddr($ipaddress)) 
1008
			return;
1009
	}
1010

    
1011
	$rules = "";
1012
	$enBwup = intval($ipent['bw_up']);
1013
	$enBwdown = intval($ipent['bw_down']);
1014
	$bw_up = "";
1015
	$bw_down = "";
1016
	$tablein = array();
1017
	$tableout = array();
1018

    
1019
	if (intval($enBwup) > 0 or intval($enBwdown) > 0)
1020
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
1021
	else
1022
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
1023

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

    
1074
	return $rules;
1075
}
1076

    
1077
/* 
1078
	Adds a dnsfilter entry and watches for hostname changes.
1079
	A change results in reloading the ruleset.
1080
*/
1081
function setup_dnsfilter_entries() {
1082
	global $g, $config;
1083

    
1084
	$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-captiveportal.conf";
1085
	$cp_filterdns_conf = "";
1086
	if (is_array($config['captiveportal']['allowedhostname'])) {
1087
		foreach ($config['captiveportal']['allowedhostname'] as $hostnameent)  {
1088
			$cp_filterdns_conf .= "ipfw {$hostnameent['hostname']} 3\n";
1089
			$cp_filterdns_conf .= "ipfw {$hostnameent['hostname']} 4\n";
1090
			$cp_filterdns_conf .= "ipfw {$hostnameent['hostname']} 7\n";
1091
			$cp_filterdns_conf .= "ipfw {$hostnameent['hostname']} 8\n";
1092
		}
1093
	}
1094
	file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1095
	killbypid("{$g['tmp_path']}/filterdns-cpah.pid");
1096
	mwexec("/usr/local/sbin/filterdns -p {$g['tmp_path']}/filterdns-cpah.pid -i 300 -c {$cp_filterdns_filename} -d 1");
1097
}
1098

    
1099
function captiveportal_allowedhostname_configure() {
1100
	global $config, $g;
1101

    
1102
	$rules = "\n# captiveportal_allowedhostname_configure()\n";
1103
	setup_dnsfilter_entries();
1104
	if (is_array($config['captiveportal']['allowedhostname'])) {
1105
		foreach ($config['captiveportal']['allowedhostname'] as $hostnameent) 
1106
			$rules .= captiveportal_allowedip_configure_entry($hostnameent);
1107
	}
1108
	return $rules;
1109
}
1110

    
1111
function captiveportal_allowedip_configure() {
1112
	global $config, $g;
1113

    
1114
	$rules = "";
1115
	if (is_array($config['captiveportal']['allowedip'])) {
1116
		foreach ($config['captiveportal']['allowedip'] as $ipent) 
1117
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1118
	}
1119

    
1120
	return $rules;
1121
}
1122

    
1123
/* get last activity timestamp given client IP address */
1124
function captiveportal_get_last_activity($ip) {
1125

    
1126
	$ipfwoutput = "";
1127

    
1128
	exec("/sbin/ipfw table 1 entrystats {$ip} 2>/dev/null", $ipfwoutput);
1129
	/* Reading only from one of the tables is enough of approximation. */
1130
	if ($ipfwoutput[0]) {
1131
		$ri = explode(" ", $ipfwoutput[0]);
1132
		if ($ri[4])
1133
			return $ri[4];
1134
	}
1135

    
1136
	return 0;
1137
}
1138

    
1139
function captiveportal_init_radius_servers() {
1140
	global $config, $g;
1141

    
1142
	/* generate radius server database */
1143
	if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
1144
		($config['captiveportal']['auth_method'] == "radius"))) {
1145
		$radiusip = $config['captiveportal']['radiusip'];
1146
		$radiusip2 = ($config['captiveportal']['radiusip2']) ? $config['captiveportal']['radiusip2'] : null;
1147

    
1148
		if ($config['captiveportal']['radiusport'])
1149
			$radiusport = $config['captiveportal']['radiusport'];
1150
		else
1151
			$radiusport = 1812;
1152
		if ($config['captiveportal']['radiusacctport'])
1153
			$radiusacctport = $config['captiveportal']['radiusacctport'];
1154
		else
1155
			$radiusacctport = 1813;
1156
		if ($config['captiveportal']['radiusport2'])
1157
			$radiusport2 = $config['captiveportal']['radiusport2'];
1158
		else
1159
			$radiusport2 = 1812;
1160
		$radiuskey = $config['captiveportal']['radiuskey'];
1161
		$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
1162

    
1163
		$cprdsrvlck = lock('captiveportalradius', LOCK_EX);
1164
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db", "w");
1165
		if (!$fd) {
1166
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1167
			unlock($cprdsrvlck);
1168
			return 1;
1169
		} else if (isset($radiusip2, $radiuskey2))
1170
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . "\n"
1171
			. $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2);
1172
		else
1173
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey);
1174
		fclose($fd);
1175
		unlock($cprdsrvlck);
1176
	}
1177
}
1178

    
1179
/* read RADIUS servers into array */
1180
function captiveportal_get_radius_servers() {
1181
		global $g;
1182

    
1183
		$cprdsrvlck = lock('captiveportalradius');
1184
		if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
1185
			$radiusservers = array();
1186
			$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius.db", 
1187
			FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1188
			if ($cpradiusdb) {
1189
				foreach($cpradiusdb as $cpradiusentry) {
1190
					$line = trim($cpradiusentry);
1191
					if ($line) {
1192
						$radsrv = array();
1193
						list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
1194
						$radiusservers[] = $radsrv;
1195
					}
1196
				}
1197
			}
1198
			unlock($cprdsrvlck);
1199
			return $radiusservers;
1200
		}
1201

    
1202
		unlock($cprdsrvlck);
1203
		return false;
1204
}
1205

    
1206
/* log successful captive portal authentication to syslog */
1207
/* part of this code from php.net */
1208
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1209
	// Log it
1210
	if (!$message)
1211
		$message = "$status: $user, $mac, $ip";
1212
	else {
1213
		$message = trim($message);
1214
		$message = "$status: $user, $mac, $ip, $message";
1215
	}
1216
	captiveportal_syslog($message);
1217
}
1218

    
1219
/* log simple messages to syslog */
1220
function captiveportal_syslog($message) {
1221
	define_syslog_variables();
1222
	$message = trim($message);
1223
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1224
	// Log it
1225
	syslog(LOG_INFO, $message);
1226
	closelog();
1227
}
1228

    
1229
function radius($username,$password,$clientip,$clientmac,$type) {
1230
	global $g, $config;
1231

    
1232
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1233

    
1234
	/* If the pool is empty, return appropriate message and fail authentication */
1235
	if (is_null($ruleno)) {
1236
		$auth_list = array();
1237
		$auth_list['auth_val'] = 1;
1238
		$auth_list['error'] = "System reached maximum login capacity";
1239
		return $auth_list;
1240
	}
1241

    
1242
	$radiusservers = captiveportal_get_radius_servers();
1243

    
1244
	$auth_list = RADIUS_AUTHENTICATION($username,
1245
		$password,
1246
		$radiusservers,
1247
		$clientip,
1248
		$clientmac,
1249
		$ruleno);
1250

    
1251
	if ($auth_list['auth_val'] == 2) {
1252
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1253
		$sessionid = portal_allow($clientip,
1254
			$clientmac,
1255
			$username,
1256
			$password,
1257
			$auth_list,
1258
			$ruleno);
1259
	}
1260

    
1261
	return $auth_list;
1262
}
1263

    
1264
/* read captive portal DB into array */
1265
function captiveportal_read_db($locked = false) {
1266
	global $g;
1267

    
1268
	$cpdb = array();
1269

    
1270
	if ($locked == false)
1271
		$cpdblck = lock('captiveportaldb');
1272
	$fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
1273
	if ($fd) {
1274
		while (!feof($fd)) {
1275
			$line = trim(fgets($fd));
1276
			if ($line) {
1277
				$cpe = explode(",", $line);
1278
				/* Hash by session id */
1279
				$cpdb[$cpe[5]] = $cpe;
1280
			}
1281
		}
1282
		fclose($fd);
1283
	}
1284
	if ($locked == false)
1285
		unlock($cpdblck);
1286
	return $cpdb;
1287
}
1288

    
1289
/* write captive portal DB */
1290
function captiveportal_write_db($cpdb, $locked = false, $remove = false) {
1291
	global $g;
1292

    
1293
	if ($locked == false)
1294
		$cpdblck = lock('captiveportaldb', LOCK_EX);
1295

    
1296
	if (is_array($remove)) {
1297
		if (!empty($remove)) {
1298
			$cpdb = captiveportal_read_db(true);
1299
			foreach ($remove as $key)
1300
				unset($cpdb[$key]);
1301
		} else
1302
			return; //This makes sure no record removal calls
1303
	}
1304
	$fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
1305
	if ($fd) {
1306
		foreach ($cpdb as $cpent) {
1307
			fwrite($fd, join(",", $cpent) . "\n");
1308
		}
1309
		fclose($fd);
1310
	}
1311
	if ($locked == false)
1312
		unlock($cpdblck);
1313
}
1314

    
1315
function captiveportal_write_elements() {
1316
	global $g, $config;
1317
	
1318
	/* delete any existing elements */
1319
	if (is_dir($g['captiveportal_element_path'])) {
1320
		$dh = opendir($g['captiveportal_element_path']);
1321
		while (($file = readdir($dh)) !== false) {
1322
			if ($file != "." && $file != "..")
1323
				unlink($g['captiveportal_element_path'] . "/" . $file);
1324
		}
1325
		closedir($dh);
1326
	} else {
1327
		@mkdir($g['captiveportal_element_path']);
1328
	}
1329

    
1330
	if (is_array($config['captiveportal']['element'])) {
1331
		conf_mount_rw();
1332
		foreach ($config['captiveportal']['element'] as $data) {
1333
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
1334
			if (!$fd) {
1335
				printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
1336
				return 1;
1337
			}
1338
			$decoded = base64_decode($data['content']);
1339
			fwrite($fd,$decoded);
1340
			fclose($fd);
1341
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1342
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1343
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
1344
		}
1345
		conf_mount_ro();
1346
	}
1347
	
1348
	return 0;
1349
}
1350

    
1351
function captiveportal_init_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
1352
	global $g;
1353

    
1354
	@unlink("{$g['vardb_path']}/captiveportal.rules");
1355
	$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1356
	file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1357
}
1358

    
1359
/*
1360
 * This function will calculate the lowest free firewall ruleno
1361
 * within the range specified based on the actual logged on users
1362
 *
1363
 */
1364
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899, $usebw = false) {
1365
	global $config, $g;
1366

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

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

    
1401
function captiveportal_free_ipfw_ruleno($ruleno, $usedbw = false) {
1402
	global $config, $g;
1403

    
1404
	if(!isset($config['captiveportal']['enable']))
1405
		return NULL;
1406

    
1407
	$cpruleslck = lock('captiveportalrules', LOCK_EX);
1408
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1409
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1410
		$rules[$ruleno] = false;
1411
		if (isset($config['captiveportal']['peruserbw']) || $usedbw == true)
1412
			$rules[++$ruleno] = false;
1413
		file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1414
	}
1415
	unlock($cpruleslck);
1416
}
1417

    
1418
function captiveportal_get_ipfw_passthru_ruleno($value) {
1419
	global $config, $g;
1420

    
1421
	if(!isset($config['captiveportal']['enable']))
1422
				return NULL;
1423

    
1424
	$cpruleslck = lock('captiveportalrules', LOCK_EX);
1425
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1426
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1427
		$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`);
1428
		if ($rules[$ruleno]) {
1429
			unlock($cpruleslck);
1430
			return $ruleno;
1431
		}
1432
	}
1433

    
1434
	unlock($cpruleslck);
1435
	return NULL;
1436
}
1437

    
1438
/**
1439
 * This function will calculate the traffic produced by a client
1440
 * based on its firewall rule
1441
 *
1442
 * Point of view: NAS
1443
 *
1444
 * Input means: from the client
1445
 * Output means: to the client
1446
 *
1447
 */
1448

    
1449
function getVolume($ip) {
1450

    
1451
	$volume = array();
1452

    
1453
	// Initialize vars properly, since we don't want NULL vars
1454
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1455

    
1456
	// Ingress
1457
	$ipfwin = "";
1458
	$ipfwout = "";
1459
	$matchesin = "";
1460
	$matchesout = "";
1461
	exec("/sbin/ipfw table 1 entrystats {$ip}", $ipfwin);
1462
	if ($ipfwin[0]) {
1463
		$ipfwin = split(" ", $ipfwin[0]);
1464
		$volume['input_pkts'] = $ipfwin[2];
1465
		$volume['input_bytes'] = $ipfwin[3];
1466
	}
1467

    
1468
	exec("/sbin/ipfw table 2 entrystats {$ip}", $ipfwout);
1469
	if ($ipfwout[0]) {
1470
		$ipfwout = split(" ", $ipfwout[0]);
1471
		$volume['output_pkts'] = $ipfwout[2];
1472
		$volume['output_bytes'] = $ipfwout[3];
1473
	}
1474

    
1475
	return $volume;
1476
}
1477

    
1478
/**
1479
 * Get the NAS-Identifier
1480
 *
1481
 * We will use our local hostname to make up the nas_id
1482
 */
1483
function getNasID()
1484
{
1485
	$nasId = "";
1486
	exec("/bin/hostname", $nasId);
1487
	if(!$nasId[0])
1488
		$nasId[0] = "{$g['product_name']}";
1489
	return $nasId[0];
1490
}
1491

    
1492
/**
1493
 * Get the NAS-IP-Address based on the current wan address
1494
 *
1495
 * Use functions in interfaces.inc to find this out
1496
 *
1497
 */
1498

    
1499
function getNasIP()
1500
{
1501
	global $config;
1502

    
1503
	if (empty($config['captiveportal']['radiussrcip_attribute'])) {
1504
			$nasIp = get_interface_ip();
1505
	} else {
1506
		if (is_ipaddr($config['captiveportal']['radiussrcip_attribute']))
1507
			$nasIp = $config['captiveportal']['radiussrcip_attribute'];
1508
		else
1509
			$nasIp = get_interface_ip($config['captiveportal']['radiussrcip_attribute']);
1510
	}
1511
		
1512
	if(!is_ipaddr($nasIp))
1513
		$nasIp = "0.0.0.0";
1514

    
1515
	return $nasIp;
1516
}
1517

    
1518
function portal_ip_from_client_ip($cliip) {
1519
	global $config;
1520

    
1521
	$interfaces = explode(",", $config['captiveportal']['interface']);
1522
	foreach ($interfaces as $cpif) {
1523
		$ip = get_interface_ip($cpif);
1524
		$sn = get_interface_subnet($cpif);
1525
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1526
			return $ip;
1527
	}
1528

    
1529
	// doesn't match up to any particular interface
1530
	// so let's set the portal IP to what PHP says 
1531
	// the server IP issuing the request is. 
1532
	// allows same behavior as 1.2.x where IP isn't 
1533
	// in the subnet of any CP interface (static routes, etc.)
1534
	// rather than forcing to DNS hostname resolution
1535
	$ip = $_SERVER['SERVER_ADDR'];
1536
	if (is_ipaddr($ip))
1537
		return $ip;
1538

    
1539
	return false;
1540
}
1541

    
1542
/* functions move from index.php */
1543

    
1544
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1545
	global $g, $config;
1546

    
1547
	/* Get captive portal layout */
1548
	if ($type == "redir") {
1549
		header("Location: {$redirurl}");
1550
		return;
1551
	} else if ($type == "login")
1552
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal.html");
1553
	else
1554
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-error.html");
1555

    
1556
	/* substitute the PORTAL_REDIRURL variable */
1557
	if ($config['captiveportal']['preauthurl']) {
1558
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$config['captiveportal']['preauthurl']}", $htmltext);
1559
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$config['captiveportal']['preauthurl']}", $htmltext);
1560
	}
1561

    
1562
	/* substitute other variables */
1563
	if (isset($config['captiveportal']['httpslogin'])) {
1564
		$htmltext = str_replace("\$PORTAL_ACTION\$", "https://{$config['captiveportal']['httpsname']}:8001/", $htmltext);
1565
		$htmltext = str_replace("#PORTAL_ACTION#", "https://{$config['captiveportal']['httpsname']}:8001/", $htmltext);
1566
	} else {
1567
		$ifip = portal_ip_from_client_ip($clientip);
1568
		if (!$ifip)
1569
			$ourhostname = $config['system']['hostname'] . ":8000";
1570
		else
1571
			$ourhostname = "{$ifip}:8000";
1572
		$htmltext = str_replace("\$PORTAL_ACTION\$", "http://{$ourhostname}/", $htmltext);
1573
		$htmltext = str_replace("#PORTAL_ACTION#", "http://{$ourhostname}/", $htmltext);
1574
	}
1575

    
1576
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1577
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1578
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1579
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1580

    
1581
	// Special handling case for captive portal master page so that it can be ran 
1582
	// through the PHP interpreter using the include method above.  We convert the
1583
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1584
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1585
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1586
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1587
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1588
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1589
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1590

    
1591
    echo $htmltext;
1592
}
1593

    
1594
function portal_mac_radius($clientmac,$clientip) {
1595
    global $config ;
1596

    
1597
    $radmac_secret = $config['captiveportal']['radmac_secret'];
1598

    
1599
    /* authentication against the radius server */
1600
    $username = mac_format($clientmac);
1601
    $auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1602
    if ($auth_list['auth_val'] == 2)
1603
        return TRUE;
1604
    if (!empty($auth_list['url_redirection']))
1605
	portal_reply_page($auth_list['url_redirection'], "redir");
1606

    
1607
    return FALSE;
1608
}
1609

    
1610
function portal_allow($clientip,$clientmac,$username,$password = null, $attributes = null, $ruleno = null)  {
1611

    
1612
	global $redirurl, $g, $config, $type, $passthrumac, $_POST;
1613

    
1614
	/* See if a ruleno is passed, if not start sessions because this means there isn't one atm */
1615
	if ($ruleno == null)
1616
		$ruleno = captiveportal_get_next_ipfw_ruleno();
1617

    
1618
	/* if the pool is empty, return appropriate message and exit */
1619
	if (is_null($ruleno)) {
1620
		portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1621
		log_error("WARNING!  Captive portal has reached maximum login capacity");
1622
		exit;
1623
	}
1624

    
1625
	// Ensure we create an array if we are missing attributes
1626
	if (!is_array($attributes))
1627
		$attributes = array();
1628

    
1629
	$radiusservers = captiveportal_get_radius_servers();
1630

    
1631
	/* Do not allow concurrent login execution. */
1632
	$cpdblck = lock('captiveportaldb', LOCK_EX);
1633

    
1634
	unset($sessionid);
1635

    
1636
	/* read in client database */
1637
	$cpdb = captiveportal_read_db(true);
1638

    
1639
	if ($attributes['voucher'])
1640
		$remaining_time = $attributes['session_timeout'];
1641

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

    
1679
	foreach ($cpdb as $sid => $cpentry) {
1680
		/* on the same ip */
1681
		if($cpentry[2] == $clientip) {
1682
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1683
			$sessionid = $sid;
1684
			break;
1685
		}
1686
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1687
			// user logged in with an active voucher. Check for how long and calculate 
1688
			// how much time we can give him (voucher credit - used time)
1689
			$remaining_time = $cpentry[0] + $cpentry[7] - time();
1690
			if ($remaining_time < 0)    // just in case. 
1691
				$remaining_time = 0;
1692

    
1693
			/* This user was already logged in so we disconnect the old one */
1694
			captiveportal_disconnect($cpentry,$radiusservers,13);
1695
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1696
			unset($cpdb[$sid]);
1697
			break;
1698
		}
1699
		elseif ((isset($config['captiveportal']['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1700
			/* on the same username */
1701
			if (strcasecmp($cpentry[4], $username) == 0) {
1702
				/* This user was already logged in so we disconnect the old one */
1703
				captiveportal_disconnect($cpentry,$radiusservers,13);
1704
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1705
				unset($cpdb[$sid]);
1706
				break;
1707
			}
1708
		}
1709
	}
1710

    
1711
	if ($attributes['voucher'] && $remaining_time <= 0)
1712
		return 0;       // voucher already used and no time left
1713

    
1714
	if (!isset($sessionid)) {
1715
		/* generate unique session ID */
1716
		$tod = gettimeofday();
1717
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1718

    
1719
		/* Add rules for traffic shaping
1720
		 * We don't need to add extra rules since traffic will pass due to the following kernel option
1721
		 * net.inet.ip.fw.one_pass: 1
1722
		 */
1723
		$peruserbw = isset($config['captiveportal']['peruserbw']);
1724

    
1725
		$bw_up = isset($attributes['bw_up']) ? trim($attributes['bw_up']) : $config['captiveportal']['bwdefaultup'];
1726
		$bw_down = isset($attributes['bw_down']) ? trim($attributes['bw_down']) : $config['captiveportal']['bwdefaultdn'];
1727

    
1728
		if ($passthrumac) {
1729
			$mac = array();
1730
			$mac['mac'] = $clientmac;
1731
			if (isset($config['captiveportal']['passthrumacaddusername']))
1732
				$mac['username'] = $username;
1733
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1734
			if (!empty($bw_up))
1735
				$mac['bw_up'] = $bw_up;
1736
			if (!empty($bw_down))
1737
				$mac['bw_down'] = $bw_down;
1738
			if (!is_array($config['captiveportal']['passthrumac']))
1739
				$config['captiveportal']['passthrumac'] = array();
1740
			$config['captiveportal']['passthrumac'][] = $mac;
1741
			unlock($cpdblck);
1742
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1743
			file_put_contents("{$g['tmp_path']}/macentry.rules.tmp", $macrules);
1744
			mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry.rules.tmp");
1745
			$writecfg = true;
1746
		} else {
1747
			if ($peruserbw && !empty($bw_up) && is_numeric($bw_up)) {
1748
				$bw_up_pipeno = $ruleno + 20000;
1749
				//$bw_up /= 1000; // Scale to Kbit/s
1750
				mwexec("/sbin/ipfw pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100");
1751

    
1752
				if (!isset($config['captiveportal']['nomacfilter']))
1753
					mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac} {$bw_up_pipeno}");
1754
				else
1755
					mwexec("/sbin/ipfw table 1 add {$clientip} {$bw_up_pipeno}");
1756
			} else {
1757
				if (!isset($config['captiveportal']['nomacfilter']))
1758
					mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac}");
1759
				else
1760
					mwexec("/sbin/ipfw table 1 add {$clientip}");
1761
			}
1762
			if ($peruserbw && !empty($bw_down) && is_numeric($bw_down)) {
1763
				$bw_down_pipeno = $ruleno + 20001;
1764
				//$bw_down /= 1000; // Scale to Kbit/s
1765
				mwexec("/sbin/ipfw pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100");
1766

    
1767
				if (!isset($config['captiveportal']['nomacfilter']))
1768
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac} {$bw_down_pipeno}");
1769
				else
1770
					mwexec("/sbin/ipfw table 2 add {$clientip} {$bw_down_pipeno}");
1771
			} else {
1772
				if (!isset($config['captiveportal']['nomacfilter']))
1773
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac}");
1774
				else
1775
					mwexec("/sbin/ipfw table 2 add {$clientip}");
1776
			}
1777

    
1778
			if ($attributes['voucher'])
1779
				$attributes['session_timeout'] = $remaining_time;
1780

    
1781
			/* encode password in Base64 just in case it contains commas */
1782
			$bpassword = base64_encode($password);
1783
			$cpdb[] = array(time(), $ruleno, $clientip, $clientmac, $username, $sessionid, $bpassword,
1784
				$attributes['session_timeout'], $attributes['idle_timeout'], $attributes['session_terminate_time']);
1785

    
1786
			/* rewrite information to database */
1787
			captiveportal_write_db($cpdb, true);
1788
			unlock($cpdblck);
1789

    
1790
			if (isset($config['captiveportal']['radacct_enable']) && !empty($radiusservers)) {
1791
				$acct_val = RADIUS_ACCOUNTING_START($ruleno,
1792
                                		$username, $sessionid, $radiusservers, $clientip, $clientmac);
1793
				if ($acct_val == 1)
1794
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1795
			}
1796
		}
1797
	} else
1798
		unlock($cpdblck);
1799

    
1800
	if ($writecfg == true)
1801
		write_config();
1802

    
1803
	/* redirect user to desired destination */
1804
	if (!empty($attributes['url_redirection']))
1805
		$my_redirurl = $attributes['url_redirection'];
1806
	else if ($config['captiveportal']['redirurl'])
1807
		$my_redirurl = $config['captiveportal']['redirurl'];
1808
	else
1809
		$my_redirurl = $redirurl;
1810

    
1811
	if(isset($config['captiveportal']['logoutwin_enable']) && !$passthrumac) {
1812

    
1813
		if (isset($config['captiveportal']['httpslogin']))
1814
			$logouturl = "https://{$config['captiveportal']['httpsname']}:8001/";
1815
		else {
1816
			$ifip = portal_ip_from_client_ip($clientip);
1817
			if (!$ifip)
1818
				$ourhostname = $config['system']['hostname'] . ":8000";
1819
			else
1820
				$ourhostname = "{$ifip}:8000";
1821
			$logouturl = "http://{$ourhostname}/";
1822
		}
1823

    
1824
		if (isset($attributes['reply_message']))
1825
			$message = $attributes['reply_message'];
1826
		else
1827
			$message = 0;
1828

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

    
1831
	} else {
1832
		header("Location: " . $my_redirurl);
1833
	}
1834

    
1835
	return $sessionid;
1836
}
1837

    
1838

    
1839
/*
1840
 * Used for when pass-through credits are enabled.
1841
 * Returns true when there was at least one free login to deduct for the MAC.
1842
 * Expired entries are removed as they are seen.
1843
 * Active entries are updated according to the configuration.
1844
 */
1845
function portal_consume_passthrough_credit($clientmac) {
1846
	global $config;
1847

    
1848
	if (!empty($config['captiveportal']['freelogins_count']) && is_numeric($config['captiveportal']['freelogins_count']))
1849
		$freeloginscount = $config['captiveportal']['freelogins_count'];
1850
	else
1851
		return false;
1852

    
1853
	if (!empty($config['captiveportal']['freelogins_resettimeout']) && is_numeric($config['captiveportal']['freelogins_resettimeout']))
1854
		$resettimeout = $config['captiveportal']['freelogins_resettimeout'];
1855
	else
1856
		return false;
1857

    
1858
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1859
		return false;
1860

    
1861
	$updatetimeouts = isset($config['captiveportal']['freelogins_updatetimeouts']);
1862

    
1863
	/*
1864
	 * Read database of used MACs.  Lines are a comma-separated list
1865
	 * of the time, MAC, then the count of pass-through credits remaining.
1866
	 */
1867
	$usedmacs = captiveportal_read_usedmacs_db();
1868

    
1869
	$currenttime = time();
1870
	$found = false;
1871
	foreach ($usedmacs as $key => $usedmac) {
1872
		$usedmac = explode(",", $usedmac);
1873

    
1874
		if ($usedmac[1] == $clientmac) {
1875
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
1876
				if ($usedmac[2] < 1) {
1877
					if ($updatetimeouts) {
1878
						$usedmac[0] = $currenttime;
1879
						unset($usedmacs[$key]);
1880
						$usedmacs[] = implode(",", $usedmac);
1881
						captiveportal_write_usedmacs_db($usedmacs);
1882
					}
1883

    
1884
					return false;
1885
				} else {
1886
					$usedmac[2] -= 1;
1887
					$usedmacs[$key] = implode(",", $usedmac);
1888
				}
1889

    
1890
				$found = true;
1891
			} else
1892
				unset($usedmacs[$key]);
1893

    
1894
			break;
1895
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
1896
				unset($usedmacs[$key]);
1897
	}
1898

    
1899
	if (!$found) {
1900
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
1901
		$usedmacs[] = implode(",", $usedmac);
1902
	}
1903

    
1904
	captiveportal_write_usedmacs_db($usedmacs);
1905
	return true;
1906
}
1907

    
1908
function captiveportal_read_usedmacs_db() {
1909
	global $g;
1910

    
1911
	$cpumaclck = lock('captiveusedmacs');
1912
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs.db")) {
1913
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1914
		if (!$usedmacs)
1915
			$usedmacs = array();
1916
	} else
1917
		$usedmacs = array();
1918

    
1919
	unlock($cpumaclck);
1920
	return $usedmacs;
1921
}
1922

    
1923
function captiveportal_write_usedmacs_db($usedmacs) {
1924
	global $g;
1925

    
1926
	$cpumaclck = lock('captiveusedmacs', LOCK_EX);
1927
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs.db", implode("\n", $usedmacs));
1928
	unlock($cpumaclck);
1929
}
1930

    
1931
?>
(7-7/61)