Project

General

Profile

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

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

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

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

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

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

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

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

    
52
function get_default_captive_portal_html() {
53
	global $config, $g;
54
	// Detect if vouchers are being used and default to the voucher page
55
	if(isset($config['voucher']['enable'])) {
56
			$htmltext = <<<EOD
57
<html> 
58
	<body> 
59
		<form method="post" action="\$PORTAL_ACTION\$"> 
60
			<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
	/* read database */
719
	$cpdb = captiveportal_read_db();
720

    
721
	$radiusservers = captiveportal_get_radius_servers();
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
	$no_users = count($cpdb);
730
	for ($i = 0; $i < $no_users; $i++) {
731

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

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

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

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

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

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

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

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

    
823
			/* check this user against RADIUS again */
824
			if (isset($config['captiveportal']['reauthenticate'])) {
825
				$auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
826
					base64_decode($cpdb[$i][6]), // password
827
					$radiusservers,
828
					$cpdb[$i][2], // clientip
829
					$cpdb[$i][3], // clientmac
830
					$cpdb[$i][1]); // ruleno
831
				if ($auth_list['auth_val'] == 3) {
832
					captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
833
					captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
834
					$unsetindexes[$i] = $i;
835
				}
836
			}
837
		}
838
	}
839
	/* This is a kludge to overcome some php weirdness */
840
	foreach($unsetindexes as $unsetindex)
841
	unset($cpdb[$unsetindex]);
842

    
843
	/* write database */
844
	captiveportal_write_db($cpdb);
845
}
846

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

    
851
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
852

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

    
870
	/* Release the ruleno so it can be reallocated to new clients. */
871
	captiveportal_free_ipfw_ruleno($dbent[1]);
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
	/* XXX: Redundant?! Ensure all pf(4) states are killed. */
883
	mwexec("pfctl -k {$dbent[2]}");
884
	mwexec("pfctl -K {$dbent[2]}");
885

    
886
}
887

    
888
/* remove a single client by ipfw rule number */
889
function captiveportal_disconnect_client($id,$term_cause = 1) {
890
	global $g, $config;
891

    
892
	/* read database */
893
	$cpdb = captiveportal_read_db();
894
	$radiusservers = captiveportal_get_radius_servers();
895

    
896
	/* find entry */
897
	foreach ($cpdb as $i => $cpentry) {
898
		if ($cpentry[1] == $id) {
899
			captiveportal_disconnect($cpentry, $radiusservers, $term_cause);
900
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
901
			unset($cpdb[$i]);
902
			break;
903
		}
904
	}		
905

    
906
	/* write database */
907
	captiveportal_write_db($cpdb);
908
}
909

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

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

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

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

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

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

    
959
	return $rules;
960
}
961

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

    
965
	$rules = "";
966

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

    
973
		}
974
	}
975

    
976
	return $rules;
977
}
978

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

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

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

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

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

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

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

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

    
1073
	return $rules;
1074
}
1075

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

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

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

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

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

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

    
1119
	return $rules;
1120
}
1121

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

    
1125
	$ipfwoutput = "";
1126

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

    
1135
	return 0;
1136
}
1137

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

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

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

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

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

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

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

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

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

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

    
1231
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1232

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

    
1241
	$radiusservers = captiveportal_get_radius_servers();
1242

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

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

    
1260
	return $auth_list;
1261
}
1262

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

    
1267
		$cpdb = array();
1268

    
1269
		$cpdblck = lock('captiveportaldb');
1270
		$fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
1271
		if ($fd) {
1272
			while (!feof($fd)) {
1273
				$line = trim(fgets($fd));
1274
				if ($line) 
1275
						$cpdb[] = explode(",", $line);
1276
			}
1277
			fclose($fd);
1278
		}
1279
		unlock($cpdblck);
1280
		return $cpdb;
1281
}
1282

    
1283
/* write captive portal DB */
1284
function captiveportal_write_db($cpdb) {
1285
		global $g;
1286

    
1287
		$cpdblck = lock('captiveportaldb', LOCK_EX);
1288
		$fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
1289
		if ($fd) {
1290
			foreach ($cpdb as $cpent) {
1291
					fwrite($fd, join(",", $cpent) . "\n");
1292
			}
1293
			fclose($fd);
1294
		}
1295
	unlock($cpdblck);
1296
}
1297

    
1298
function captiveportal_write_elements() {
1299
	global $g, $config;
1300
	
1301
	/* delete any existing elements */
1302
	if (is_dir($g['captiveportal_element_path'])) {
1303
		$dh = opendir($g['captiveportal_element_path']);
1304
		while (($file = readdir($dh)) !== false) {
1305
			if ($file != "." && $file != "..")
1306
				unlink($g['captiveportal_element_path'] . "/" . $file);
1307
		}
1308
		closedir($dh);
1309
	} else {
1310
		@mkdir($g['captiveportal_element_path']);
1311
	}
1312

    
1313
	if (is_array($config['captiveportal']['element'])) {
1314
		conf_mount_rw();
1315
		foreach ($config['captiveportal']['element'] as $data) {
1316
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
1317
			if (!$fd) {
1318
				printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
1319
				return 1;
1320
			}
1321
			$decoded = base64_decode($data['content']);
1322
			fwrite($fd,$decoded);
1323
			fclose($fd);
1324
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1325
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1326
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
1327
		}
1328
		conf_mount_ro();
1329
	}
1330
	
1331
	return 0;
1332
}
1333

    
1334
function captiveportal_init_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
1335
	global $g;
1336

    
1337
	@unlink("{$g['vardb_path']}/captiveportal.rules");
1338
	$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1339
	file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1340
}
1341

    
1342
/*
1343
 * This function will calculate the lowest free firewall ruleno
1344
 * within the range specified based on the actual logged on users
1345
 *
1346
 */
1347
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899, $usebw = false) {
1348
	global $config, $g;
1349

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

    
1353
	$cpruleslck = lock('captiveportalrules', LOCK_EX);
1354
	$ruleno = 0;
1355
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1356
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1357
		for ($ridx = 2; $ridx < ($rulenos_range_max - $rulenos_start); $ridx++) {
1358
			if ($rules[$ridx]) {
1359
				/* 
1360
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1361
				 * and the out pipe ruleno + 1. This removes limitation that where present in 
1362
				 * previous version of the peruserbw.
1363
				 */
1364
				if (isset($config['captiveportal']['peruserbw']))
1365
					$ridx++;
1366
				continue;
1367
			}
1368
			$ruleno = $ridx;
1369
			$rules[$ridx] = "used";
1370
			if (isset($config['captiveportal']['peruserbw']) || $usebw == true)
1371
				$rules[++$ridx] = "used";
1372
			break;
1373
		}
1374
	} else {
1375
		$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1376
		$rules[2] = "used";
1377
		$ruleno = 2;
1378
	}
1379
	file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1380
	unlock($cpruleslck);
1381
	return $ruleno;
1382
}
1383

    
1384
function captiveportal_free_ipfw_ruleno($ruleno, $usedbw = false) {
1385
	global $config, $g;
1386

    
1387
	if(!isset($config['captiveportal']['enable']))
1388
		return NULL;
1389

    
1390
	$cpruleslck = lock('captiveportalrules', LOCK_EX);
1391
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1392
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1393
		$rules[$ruleno] = false;
1394
		if (isset($config['captiveportal']['peruserbw']) || $usedbw == true)
1395
			$rules[++$ruleno] = false;
1396
		file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1397
	}
1398
	unlock($cpruleslck);
1399
}
1400

    
1401
function captiveportal_get_ipfw_passthru_ruleno($value) {
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
		$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`);
1411
		if ($rules[$ruleno]) {
1412
			unlock($cpruleslck);
1413
			return $ruleno;
1414
		}
1415
	}
1416

    
1417
	unlock($cpruleslck);
1418
	return NULL;
1419
}
1420

    
1421
/**
1422
 * This function will calculate the traffic produced by a client
1423
 * based on its firewall rule
1424
 *
1425
 * Point of view: NAS
1426
 *
1427
 * Input means: from the client
1428
 * Output means: to the client
1429
 *
1430
 */
1431

    
1432
function getVolume($ip) {
1433

    
1434
	$volume = array();
1435

    
1436
	// Initialize vars properly, since we don't want NULL vars
1437
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1438

    
1439
	// Ingress
1440
	$ipfwin = "";
1441
	$ipfwout = "";
1442
	$matchesin = "";
1443
	$matchesout = "";
1444
	exec("/sbin/ipfw table 1 entrystats {$ip}", $ipfwin);
1445
	if ($ipfwin[0]) {
1446
		$ipfwin = split(" ", $ipfwin[0]);
1447
		$volume['input_pkts'] = $ipfwin[2];
1448
		$volume['input_bytes'] = $ipfwin[3];
1449
	}
1450

    
1451
	exec("/sbin/ipfw table 2 entrystats {$ip}", $ipfwout);
1452
	if ($ipfwout[0]) {
1453
		$ipfwout = split(" ", $ipfwout[0]);
1454
		$volume['output_pkts'] = $ipfwout[2];
1455
		$volume['output_bytes'] = $ipfwout[3];
1456
	}
1457

    
1458
	return $volume;
1459
}
1460

    
1461
/**
1462
 * Get the NAS-Identifier
1463
 *
1464
 * We will use our local hostname to make up the nas_id
1465
 */
1466
function getNasID()
1467
{
1468
	$nasId = "";
1469
	exec("/bin/hostname", $nasId);
1470
	if(!$nasId[0])
1471
		$nasId[0] = "{$g['product_name']}";
1472
	return $nasId[0];
1473
}
1474

    
1475
/**
1476
 * Get the NAS-IP-Address based on the current wan address
1477
 *
1478
 * Use functions in interfaces.inc to find this out
1479
 *
1480
 */
1481

    
1482
function getNasIP()
1483
{
1484
	global $config;
1485

    
1486
	if (empty($config['captiveportal']['radiussrcip_attribute'])) {
1487
			$nasIp = get_interface_ip();
1488
	} else {
1489
		if (is_ipaddr($config['captiveportal']['radiussrcip_attribute']))
1490
			$nasIp = $config['captiveportal']['radiussrcip_attribute'];
1491
		else
1492
			$nasIp = get_interface_ip($config['captiveportal']['radiussrcip_attribute']);
1493
	}
1494
		
1495
	if(!is_ipaddr($nasIp))
1496
		$nasIp = "0.0.0.0";
1497

    
1498
	return $nasIp;
1499
}
1500

    
1501
function portal_ip_from_client_ip($cliip) {
1502
	global $config;
1503

    
1504
	$interfaces = explode(",", $config['captiveportal']['interface']);
1505
	foreach ($interfaces as $cpif) {
1506
		$ip = get_interface_ip($cpif);
1507
		$sn = get_interface_subnet($cpif);
1508
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1509
			return $ip;
1510
	}
1511

    
1512
	// doesn't match up to any particular interface
1513
	// so let's set the portal IP to what PHP says 
1514
	// the server IP issuing the request is. 
1515
	// allows same behavior as 1.2.x where IP isn't 
1516
	// in the subnet of any CP interface (static routes, etc.)
1517
	// rather than forcing to DNS hostname resolution
1518
	$ip = $_SERVER['SERVER_ADDR'];
1519
	if (is_ipaddr($ip))
1520
		return $ip;
1521

    
1522
	return false;
1523
}
1524

    
1525
/* functions move from index.php */
1526

    
1527
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1528
	global $g, $config;
1529

    
1530
	/* Get captive portal layout */
1531
	if ($type == "redir") {
1532
		header("Location: {$redirurl}");
1533
		return;
1534
	} else if ($type == "login")
1535
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal.html");
1536
	else
1537
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-error.html");
1538

    
1539
	/* substitute the PORTAL_REDIRURL variable */
1540
	if ($config['captiveportal']['preauthurl']) {
1541
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$config['captiveportal']['preauthurl']}", $htmltext);
1542
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$config['captiveportal']['preauthurl']}", $htmltext);
1543
	}
1544

    
1545
	/* substitute other variables */
1546
	if (isset($config['captiveportal']['httpslogin'])) {
1547
		$htmltext = str_replace("\$PORTAL_ACTION\$", "https://{$config['captiveportal']['httpsname']}:8001/", $htmltext);
1548
		$htmltext = str_replace("#PORTAL_ACTION#", "https://{$config['captiveportal']['httpsname']}:8001/", $htmltext);
1549
	} else {
1550
		$ifip = portal_ip_from_client_ip($clientip);
1551
		if (!$ifip)
1552
			$ourhostname = $config['system']['hostname'] . ":8000";
1553
		else
1554
			$ourhostname = "{$ifip}:8000";
1555
		$htmltext = str_replace("\$PORTAL_ACTION\$", "http://{$ourhostname}/", $htmltext);
1556
		$htmltext = str_replace("#PORTAL_ACTION#", "http://{$ourhostname}/", $htmltext);
1557
	}
1558

    
1559
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1560
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1561
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1562
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1563

    
1564
	// Special handling case for captive portal master page so that it can be ran 
1565
	// through the PHP interpreter using the include method above.  We convert the
1566
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1567
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1568
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1569
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1570
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1571
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1572
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1573

    
1574
    echo $htmltext;
1575
}
1576

    
1577
function portal_mac_radius($clientmac,$clientip) {
1578
    global $config ;
1579

    
1580
    $radmac_secret = $config['captiveportal']['radmac_secret'];
1581

    
1582
    /* authentication against the radius server */
1583
    $username = mac_format($clientmac);
1584
    $auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1585
    if ($auth_list['auth_val'] == 2)
1586
        return TRUE;
1587
    if (!empty($auth_list['url_redirection']))
1588
	portal_reply_page($auth_list['url_redirection'], "redir");
1589

    
1590
    return FALSE;
1591
}
1592

    
1593
function portal_allow($clientip,$clientmac,$username,$password = null, $attributes = null, $ruleno = null)  {
1594

    
1595
	global $redirurl, $g, $config, $type, $passthrumac, $_POST;
1596

    
1597
	/* See if a ruleno is passed, if not start sessions because this means there isn't one atm */
1598
	if ($ruleno == null)
1599
		$ruleno = captiveportal_get_next_ipfw_ruleno();
1600

    
1601
	/* if the pool is empty, return appropriate message and exit */
1602
	if (is_null($ruleno)) {
1603
		portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1604
		log_error("WARNING!  Captive portal has reached maximum login capacity");
1605
		exit;
1606
	}
1607

    
1608
	// Ensure we create an array if we are missing attributes
1609
	if (!is_array($attributes))
1610
		$attributes = array();
1611

    
1612
	/* read in client database */
1613
	$cpdb = captiveportal_read_db();
1614

    
1615
	$radiusservers = captiveportal_get_radius_servers();
1616

    
1617
	if ($attributes['voucher'])
1618
		$remaining_time = $attributes['session_timeout'];
1619

    
1620
	$writecfg = false;
1621
	/* Find an existing session */
1622
	if ((isset($config['captiveportal']['noconcurrentlogins'])) && $passthrumac) {
1623
		if (isset($config['captiveportal']['passthrumacadd'])) {
1624
			$mac = captiveportal_passthrumac_findbyname($username);
1625
			if (!empty($mac)) {
1626
				if ($_POST['replacemacpassthru']) {
1627
					foreach ($config['captiveportal']['passthrumac'] as $idx => $macent) {
1628
						if ($macent['mac'] == $mac['mac']) {
1629
							$macrules = "";
1630
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1631
                                			if ($ruleno) {
1632
								captiveportal_free_ipfw_ruleno($ruleno, true);
1633
                                        			$macrules .= "delete {$ruleno}\n";
1634
								++$ruleno;
1635
                                        			$macrules .= "delete {$ruleno}\n";
1636
                                			}
1637
							unset($config['captiveportal']['passthrumac'][$idx]);
1638
							$mac['mac'] = $clientmac;
1639
							$config['captiveportal']['passthrumac'][] = $mac;
1640
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1641
							file_put_contents("{$g['tmp_path']}/macentry.rules.tmp", $macrules);
1642
							mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry.rules.tmp");
1643
							$writecfg = true;
1644
							$sessionid = true;
1645
							break;
1646
						}
1647
					}
1648
                                } else {
1649
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1650
						$clientmac, $clientip, $username, $password);
1651
					exit;
1652
				}
1653
			}
1654
		}
1655
	}
1656

    
1657
	$nousers = count($cpdb);
1658
	for ($i = 0; $i < $nousers; $i++) {
1659
		/* on the same ip */
1660
		if($cpdb[$i][2] == $clientip) {
1661
			captiveportal_logportalauth($cpdb[$i][4],$cpdb[$i][3],$cpdb[$i][2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1662
			$sessionid = $cpdb[$i][5];
1663
			break;
1664
		}
1665
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpdb[$i][4] == $username)) {
1666
			// user logged in with an active voucher. Check for how long and calculate 
1667
			// how much time we can give him (voucher credit - used time)
1668
			$remaining_time = $cpdb[$i][0] + $cpdb[$i][7] - time();
1669
			if ($remaining_time < 0)    // just in case. 
1670
				$remaining_time = 0;
1671

    
1672
			/* This user was already logged in so we disconnect the old one */
1673
			captiveportal_disconnect($cpdb[$i],$radiusservers,13);
1674
			captiveportal_logportalauth($cpdb[$i][4],$cpdb[$i][3],$cpdb[$i][2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1675
			unset($cpdb[$i]);
1676
			break;
1677
		}
1678
		elseif ((isset($config['captiveportal']['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1679
			/* on the same username */
1680
			if (strcasecmp($cpdb[$i][4], $username) == 0) {
1681
				/* This user was already logged in so we disconnect the old one */
1682
				captiveportal_disconnect($cpdb[$i],$radiusservers,13);
1683
				captiveportal_logportalauth($cpdb[$i][4],$cpdb[$i][3],$cpdb[$i][2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1684
				unset($cpdb[$i]);
1685
				break;
1686
			}
1687
		}
1688
	}
1689

    
1690
	if ($attributes['voucher'] && $remaining_time <= 0)
1691
		return 0;       // voucher already used and no time left
1692

    
1693
	if (!isset($sessionid)) {
1694
		/* generate unique session ID */
1695
		$tod = gettimeofday();
1696
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1697

    
1698
		/* Add rules for traffic shaping
1699
		 * We don't need to add extra rules since traffic will pass due to the following kernel option
1700
		 * net.inet.ip.fw.one_pass: 1
1701
		 */
1702
		$peruserbw = isset($config['captiveportal']['peruserbw']);
1703

    
1704
		$bw_up = isset($attributes['bw_up']) ? trim($attributes['bw_up']) : $config['captiveportal']['bwdefaultup'];
1705
		$bw_down = isset($attributes['bw_down']) ? trim($attributes['bw_down']) : $config['captiveportal']['bwdefaultdn'];
1706

    
1707
		if ($passthrumac) {
1708
			$mac = array();
1709
			$mac['mac'] = $clientmac;
1710
			if (isset($config['captiveportal']['passthrumacaddusername']))
1711
				$mac['username'] = $username;
1712
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1713
			if (!empty($bw_up))
1714
				$mac['bw_up'] = $bw_up;
1715
			if (!empty($bw_down))
1716
				$mac['bw_down'] = $bw_down;
1717
			if (!is_array($config['captiveportal']['passthrumac']))
1718
				$config['captiveportal']['passthrumac'] = array();
1719
			$config['captiveportal']['passthrumac'][] = $mac;
1720
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1721
			file_put_contents("{$g['tmp_path']}/macentry.rules.tmp", $macrules);
1722
			mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry.rules.tmp");
1723
			$writecfg = true;
1724
		} else {
1725
			if ($peruserbw && !empty($bw_up) && is_numeric($bw_up)) {
1726
				$bw_up_pipeno = $ruleno + 20000;
1727
				//$bw_up /= 1000; // Scale to Kbit/s
1728
				mwexec("/sbin/ipfw pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100");
1729

    
1730
				if (!isset($config['captiveportal']['nomacfilter']))
1731
					mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac} {$bw_up_pipeno}");
1732
				else
1733
					mwexec("/sbin/ipfw table 1 add {$clientip} {$bw_up_pipeno}");
1734
			} else {
1735
				if (!isset($config['captiveportal']['nomacfilter']))
1736
					mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac}");
1737
				else
1738
					mwexec("/sbin/ipfw table 1 add {$clientip}");
1739
			}
1740
			if ($peruserbw && !empty($bw_down) && is_numeric($bw_down)) {
1741
				$bw_down_pipeno = $ruleno + 20001;
1742
				//$bw_down /= 1000; // Scale to Kbit/s
1743
				mwexec("/sbin/ipfw pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100");
1744

    
1745
				if (!isset($config['captiveportal']['nomacfilter']))
1746
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac} {$bw_down_pipeno}");
1747
				else
1748
					mwexec("/sbin/ipfw table 2 add {$clientip} {$bw_down_pipeno}");
1749
			} else {
1750
				if (!isset($config['captiveportal']['nomacfilter']))
1751
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac}");
1752
				else
1753
					mwexec("/sbin/ipfw table 2 add {$clientip}");
1754
			}
1755

    
1756
			if ($attributes['voucher'])
1757
				$attributes['session_timeout'] = $remaining_time;
1758

    
1759
			/* encode password in Base64 just in case it contains commas */
1760
			$bpassword = base64_encode($password);
1761
			$cpdb[] = array(time(), $ruleno, $clientip, $clientmac, $username, $sessionid, $bpassword,
1762
				$attributes['session_timeout'], $attributes['idle_timeout'], $attributes['session_terminate_time']);
1763

    
1764
			if (isset($config['captiveportal']['radacct_enable']) && !empty($radiusservers)) {
1765
				$acct_val = RADIUS_ACCOUNTING_START($ruleno,
1766
                                		$username, $sessionid, $radiusservers, $clientip, $clientmac);
1767
				if ($acct_val == 1)
1768
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1769
			}
1770

    
1771
			/* rewrite information to database */
1772
			captiveportal_write_db($cpdb);
1773
		}
1774
	}
1775

    
1776
	if ($writecfg == true)
1777
		write_config();
1778

    
1779
	/* redirect user to desired destination */
1780
	if (!empty($attributes['url_redirection']))
1781
		$my_redirurl = $attributes['url_redirection'];
1782
	else if ($config['captiveportal']['redirurl'])
1783
		$my_redirurl = $config['captiveportal']['redirurl'];
1784
	else
1785
		$my_redirurl = $redirurl;
1786

    
1787
	if(isset($config['captiveportal']['logoutwin_enable']) && !$passthrumac) {
1788

    
1789
		if (isset($config['captiveportal']['httpslogin']))
1790
			$logouturl = "https://{$config['captiveportal']['httpsname']}:8001/";
1791
		else {
1792
			$ifip = portal_ip_from_client_ip($clientip);
1793
			if (!$ifip)
1794
				$ourhostname = $config['system']['hostname'] . ":8000";
1795
			else
1796
				$ourhostname = "{$ifip}:8000";
1797
			$logouturl = "http://{$ourhostname}/";
1798
		}
1799

    
1800
		if (isset($attributes['reply_message']))
1801
			$message = $attributes['reply_message'];
1802
		else
1803
			$message = 0;
1804

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

    
1807
	} else {
1808
		header("Location: " . $my_redirurl);
1809
	}
1810

    
1811
	return $sessionid;
1812
}
1813

    
1814

    
1815

    
1816
/* remove a single client by session ID
1817
 *  by Dinesh Nair
1818
 */
1819
function disconnect_client($sessionid, $logoutReason = "LOGOUT", $term_cause = 1) {
1820
    global $g, $config;
1821

    
1822
    /* read database */
1823
    $cpdb = captiveportal_read_db();
1824

    
1825
    $radiusservers = captiveportal_get_radius_servers();
1826

    
1827
    /* find entry */
1828
    $dbcount = count($cpdb);
1829
    for ($i = 0; $i < $dbcount; $i++) {
1830
        if ($cpdb[$i][5] == $sessionid) {
1831
            captiveportal_disconnect($cpdb[$i],$radiusservers, $term_cause);
1832
            captiveportal_logportalauth($cpdb[$i][4],$cpdb[$i][3],$cpdb[$i][2],$logoutReason);
1833
            unset($cpdb[$i]);
1834
            break;
1835
        }
1836
    }
1837

    
1838
    /* write database */
1839
    captiveportal_write_db($cpdb);
1840
}
1841

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

    
1851
	if (!empty($config['captiveportal']['freelogins_count']) && is_numeric($config['captiveportal']['freelogins_count']))
1852
		$freeloginscount = $config['captiveportal']['freelogins_count'];
1853
	else
1854
		return false;
1855

    
1856
	if (!empty($config['captiveportal']['freelogins_resettimeout']) && is_numeric($config['captiveportal']['freelogins_resettimeout']))
1857
		$resettimeout = $config['captiveportal']['freelogins_resettimeout'];
1858
	else
1859
		return false;
1860

    
1861
	if ($freeloginscount < 1 || $resettimeout <= 0 || !clientmac)
1862
		return false;
1863

    
1864
	$updatetimeouts = isset($config['captiveportal']['freelogins_updatetimeouts']);
1865

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

    
1872
	$currenttime = time();
1873
	$found = false;
1874
	foreach ($usedmacs as $key => $usedmac) {
1875
		$usedmac = explode(",", $usedmac);
1876

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

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

    
1893
				$found = true;
1894
			} else
1895
				unset($usedmacs[$key]);
1896

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

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

    
1907
	captiveportal_write_usedmacs_db($usedmacs);
1908
	return true;
1909
}
1910

    
1911
function captiveportal_read_usedmacs_db() {
1912
	global $g;
1913

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

    
1922
	unlock($cpumaclck);
1923
	return $usedmacs;
1924
}
1925

    
1926
function captiveportal_write_usedmacs_db($usedmacs) {
1927
	global $g;
1928

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

    
1934

    
1935

    
1936
?>
(7-7/61)