Project

General

Profile

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

    
122
EOD;
123
		return $htmltext;
124
	}
125

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

    
202
EOD;
203

    
204
	return $htmltext;
205
}
206

    
207
function captiveportal_configure() {
208
	global $config, $cpzone;
209

    
210
	if (is_array($config['captiveportal'])) {
211
		mwexec("/sbin/sysctl net.link.ether.ipfw=1");
212
		foreach ($config['captiveportal'] as $cpkey => $cp) {
213
			$cpzone = $cpkey;
214
			captiveportal_configure_zone($cp);
215
		}
216
	} else
217
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
218
}
219

    
220
function captiveportal_ipfw_set_context($cpzone) {
221
	mwexec("/usr/local/sbin/ipfw_context -s {$cpzone}", true);
222
}
223

    
224
function captiveportal_configure_zone($cpcfg) {
225
	global $config, $g, $cpzone;
226

    
227
	$captiveportallck = lock("captiveportal{$cpzone}", LOCK_EX);
228
	
229
	if (isset($cpcfg['enable'])) {
230

    
231
		if ($g['booting'])
232
			echo "Starting captive portal({$cpcfg['zone']})... ";
233
		else
234
			captiveportal_syslog("Restarting captive portal({$cpcfg['zone']}).");
235

    
236
		/* kill any running mini_httpd */
237
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
238
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
239

    
240
		/* remove old information */
241
		unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.db");
242
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac_{$cpzone}.db");
243
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip_{$cpzone}.db");
244
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
245

    
246
		/* setup new database in case someone tries to access the status -> captive portal page */
247
		touch("{$g['vardb_path']}/captiveportal_{$cpzone}.db");
248

    
249
		/* kill any running minicron */
250
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
251

    
252
		mwexec("/usr/local/sbin/ipfw_context -a {$cpzone}", true);
253
		captiveportal_ipfw_set_context($cpzone);
254

    
255
		/* init ipfw rules */
256
		captiveportal_init_rules(true);
257

    
258
		/* stop accounting on all clients */
259
		captiveportal_radius_stop_all();
260

    
261
		/* initialize minicron interval value */
262
		$croninterval = $cpcfg['croninterval'] ? $cpcfg['croninterval'] : 60;
263

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

    
268
		/* write portal page */
269
		if (is_array($cpcfg['page']) && $cpcfg['page']['htmltext'])
270
			$htmltext = base64_decode($cpcfg['page']['htmltext']);
271
		else {
272
			/* example/template page */
273
			$htmltext = get_default_captive_portal_html();
274
		}
275

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

    
295
		/* write error page */
296
		if (is_array($cpcfg['page']) && $cpcfg['page']['errtext'])
297
			$errtext = base64_decode($cpcfg['page']['errtext']);
298
		else {
299
			/* example page  */
300
			$errtext = get_default_captive_portal_html();
301
		}
302

    
303
		$fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html", "w");
304
		if ($fd) {
305
			// Special case handling.  Convert so that we can pass this page
306
			// through the PHP interpreter later without clobbering the vars.
307
			$errtext = str_replace("\$PORTAL_ZONE\$", "#PORTAL_ZONE#", $errtext);
308
			$errtext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $errtext);
309
			$errtext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $errtext);
310
			$errtext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $errtext);
311
			$errtext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $errtext);
312
			$errtext = str_replace("\$ORIGINAL_PORTAL_IP\$", "#ORIGINAL_PORTAL_IP#", $errtext);
313
			$errtext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $errtext);
314
			if($cpcfg['preauthurl']) {
315
				$errtext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $errtext);
316
				$errtext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $errtext);
317
			}
318
			fwrite($fd, $errtext);
319
			fclose($fd);
320
		}
321

    
322
		/* write logout page */
323
		if (is_array($cpcfg['page']) && $cpcfg['page']['logouttext'])
324
			$logouttext = base64_decode($cpcfg['page']['logouttext']);
325
		else {
326
			/* example page */
327
			$logouttext = <<<EOD
328
<HTML>
329
<HEAD><TITLE>Redirecting...</TITLE></HEAD>
330
<BODY>
331
<SPAN STYLE="font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">
332
<B>Redirecting to <A HREF="<?=\$my_redirurl;?>"><?=\$my_redirurl;?></A>...</B>
333
</SPAN>
334
<SCRIPT LANGUAGE="JavaScript">
335
<!--
336
LogoutWin = window.open('', 'Logout', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=256,height=64');
337
if (LogoutWin) {
338
	LogoutWin.document.write('<HTML>');
339
	LogoutWin.document.write('<HEAD><TITLE>Logout</TITLE></HEAD>') ;
340
	LogoutWin.document.write('<BODY BGCOLOR="#435370">');
341
	LogoutWin.document.write('<DIV ALIGN="center" STYLE="color: #ffffff; font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">') ;
342
	LogoutWin.document.write('<B>Click the button below to disconnect</B><P>');
343
	LogoutWin.document.write('<FORM METHOD="POST" ACTION="<?=\$logouturl;?>">');
344
	LogoutWin.document.write('<INPUT NAME="logout_id" TYPE="hidden" VALUE="<?=\$sessionid;?>">');
345
	LogoutWin.document.write('<INPUT NAME="zone" TYPE="hidden" VALUE="<?=\$cpzone;?>">');
346
	LogoutWin.document.write('<INPUT NAME="logout" TYPE="submit" VALUE="Logout">');
347
	LogoutWin.document.write('</FORM>');
348
	LogoutWin.document.write('</DIV></BODY>');
349
	LogoutWin.document.write('</HTML>');
350
	LogoutWin.document.close();
351
}
352

    
353
document.location.href="<?=\$my_redirurl;?>";
354
-->
355
</SCRIPT>
356
</BODY>
357
</HTML>
358

    
359
EOD;
360
		}
361

    
362
		$fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html", "w");
363
		if ($fd) {
364
			fwrite($fd, $logouttext);
365
			fclose($fd);
366
		}
367
		/* write elements */
368
		captiveportal_write_elements();
369

    
370
		/* start up the webserving daemon */
371
		captiveportal_init_webgui_zone($cpcfg);
372

    
373
		/* Kill any existing prunecaptiveportal processes */
374
		if(file_exists("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid"))
375
			killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
376

    
377
		/* start pruning process (interval defaults to 60 seconds) */
378
		mwexec("/usr/local/bin/minicron $croninterval {$g['varrun_path']}/cp_prunedb_{$cpzone}.pid " .
379
			"/etc/rc.prunecaptiveportal {$cpzone}");
380

    
381
		/* generate radius server database */
382
		captiveportal_init_radius_servers();
383

    
384
		if ($g['booting'])
385
			echo "done\n";
386

    
387
	} else {
388
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
389
		killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
390
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
391
		@unlink("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
392
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
393
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
394

    
395
		captiveportal_radius_stop_all();
396

    
397
		mwexec("/usr/local/sbin/ipfw_context -d {$cpzone}", true);
398

    
399
		if (empty($config['captiveportal']))
400
			mwexec("/sbin/sysctl net.link.ether.ipfw=0");
401

    
402
		/* unload ipfw */
403
		//if (is_module_loaded("ipfw.ko"))		
404
		//	mwexec("/sbin/kldunload ipfw.ko");
405
		$listifs = get_configured_interface_list();
406
		$cpinterfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
407
		foreach ($cpinterfaces as $cpifgrp) {
408
			if (!isset($listifs[$cpifgrp]))
409
				continue;
410
			$listrealif = get_real_interface($cpifgrp);
411
			if (does_interface_exist($listrealif)) {
412
				pfSense_interface_flags($listrealif, -IFF_IPFW_FILTER);
413
				$carpif = link_ip_to_carp_interface(find_interface_ip($listrealif));
414
				if (!empty($carpif)) {
415
					$carpsif = explode(" ", $carpif);
416
					foreach ($carpsif as $cpcarp)
417
						pfSense_interface_flags($cpcarp, -IFF_IPFW_FILTER);
418
				}
419
			}
420
		}
421
	}
422

    
423
	unlock($captiveportallck);
424
	
425
	return 0;
426
}
427

    
428
function captiveportal_init_webgui() {
429
	global $config, $cpzone;
430

    
431
	if (is_array($config['captiveportal'])) {
432
		foreach ($config['captiveportal'] as $cpkey => $cp) {
433
			$cpzone = $cpkey;
434
			captiveportal_init_webgui_zone($cp);
435
		}
436
	}
437
}
438

    
439
function captiveportal_init_webgui_zonename($zone) {
440
	global $config, $cpzone;
441
	
442
	if (isset($config['captiveportal'][$zone])) {
443
		$cpzone = $zone;
444
		captiveportal_init_webgui_zone($config['captiveportal'][$zone]);
445
	}
446
}
447

    
448
function captiveportal_init_webgui_zone($cpcfg) {
449
	global $g, $config, $cpzone;
450

    
451
	if (!isset($cpcfg['enable']))
452
		return;
453

    
454
	$use_fastcgi = true;
455

    
456
	if (isset($cpcfg['httpslogin'])) {
457
		$cert = base64_decode($cpcfg['certificate']);
458
		if (isset($cpcfg['cacertificate']))
459
			$cacert = base64_decode($cpcfg['cacertificate']);
460
		else
461
			$cacert = "";
462
		$key = base64_decode($cpcfg['private-key']);
463
		/* generate lighttpd configuration */
464
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
465
		system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf",
466
			$cert, $key, $cacert, "lighty-{$cpzone}-CaptivePortal-SSL.pid", $listenporthttps, "/usr/local/captiveportal",
467
			"cert-portal.pem", "ca-portal.pem", "1", $use_fastcgi, $cpzone);
468
	}
469

    
470
	/* generate lighttpd configuration */
471
	$listenporthttp = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : $cpcfg['zoneid'];
472
	system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal.conf",
473
		"", "", "", "lighty-{$cpzone}-CaptivePortal.pid", $listenporthttp, "/usr/local/captiveportal",
474
		"cert-portal.pem", "ca-portal.pem", "1", $use_fastcgi, $cpzone);
475

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

    
479
	/* fire up https instance */
480
	if (isset($cpcfg['httpslogin']) && $cpcfg['httpslogin'])
481
		$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf");
482
}
483

    
484
/* reinit will disconnect all users, be careful! */
485
function captiveportal_init_rules($reinit = false) {
486
	global $config, $g, $cpzone;
487

    
488
	if (!isset($config['captiveportal'][$cpzone]['enable']))
489
		return;
490

    
491
	$cpips = array();
492
	$ifaces = get_configured_interface_list();
493
	$cpinterfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
494
	$firsttime = 0;
495
	foreach ($cpinterfaces as $cpifgrp) {
496
		if (!isset($ifaces[$cpifgrp]))
497
			continue;
498
		$tmpif = get_real_interface($cpifgrp);
499
		if (!empty($tmpif)) {
500
			if ($firsttime > 0)
501
				$cpinterface .= " or ";
502
			$cpinterface .= "via {$tmpif}";
503
			$firsttime = 1;
504
			$cpipm = get_interface_ip($cpifgrp);
505
			if (is_ipaddr($cpipm)) {
506
				$carpif = link_ip_to_carp_interface($cpipm);
507
				if (!empty($carpif)) {
508
					$carpsif = explode(" ", $carpif);
509
					foreach ($carpsif as $cpcarp) {
510
						mwexec("/usr/local/sbin/ipfw_context -a {$cpzone} -n {$cpcarp}", true);
511
						pfSense_interface_flags($cpcarp, IFF_IPFW_FILTER);
512
						$carpip = find_interface_ip($cpcarp);
513
						if (is_ipaddr($carpip))
514
							$cpips[] = $carpip;
515
					}
516
				}
517
				$cpips[] = $cpipm;
518
				mwexec("/usr/local/sbin/ipfw_context -a {$cpzone} -n {$tmpif}", true);
519
				pfSense_interface_flags($tmpif, IFF_IPFW_FILTER);
520
			}
521
		}
522
	}
523
	if (count($cpips) > 0) {
524
		$cpactive = true;
525
		$cpinterface = "{ {$cpinterface} } ";
526
		} else
527
		return false;
528

    
529
	if ($reinit == false)
530
		$captiveportallck = lock("captiveportal{$cpzone}");
531

    
532
	/* init dummynet/ipfw rules number database */
533
	captiveportal_init_ipfw_ruleno();
534

    
535
	/* make sure ipfw is loaded */
536
	if (!is_module_loaded("ipfw.ko"))
537
		filter_load_ipfw();
538
	/* Always load dummynet now that even allowed ip and mac passthrough use it. */
539
	if (!is_module_loaded("dummynet.ko"))
540
		mwexec("/sbin/kldload dummynet");
541

    
542
	$cprules =	"add 65291 set 1 allow pfsync from any to any\n";
543
	$cprules .= "add 65292 set 1 allow carp from any to any\n";
544

    
545
	$cprules .= <<<EOD
546
# add 65300 set 1 skipto 65534 all from any to any not layer2
547
# layer 2: pass ARP
548
add 65301 set 1 pass layer2 mac-type arp
549
# pfsense requires for WPA
550
add 65302 set 1 pass layer2 mac-type 0x888e
551
add 65303 set 1 pass layer2 mac-type 0x88c7
552

    
553
# PPP Over Ethernet Discovery Stage
554
add 65304 set 1 pass layer2 mac-type 0x8863
555
# PPP Over Ethernet Session Stage
556
add 65305 set 1 pass layer2 mac-type 0x8864
557

    
558
# layer 2: block anything else non-IP
559
add 65307 set 1 deny layer2 not mac-type ip
560

    
561
EOD;
562

    
563
	$rulenum = 65310;
564
	$ipcount = 0;
565
	$ips = "";
566
	foreach ($cpips as $cpip) {
567
		if($ipcount == 0) {
568
			$ips = "{$cpip} ";
569
		} else {
570
			$ips .= "or {$cpip} ";
571
		}
572
		$ipcount++;
573
	}
574
	$ips = "{ 255.255.255.255 or {$ips} }";
575
	$cprules .= "add {$rulenum} set 1 pass ip from any to {$ips} in\n";
576
	$rulenum++;
577
	$cprules .= "add {$rulenum} set 1 pass ip from {$ips} to any out\n";
578
	$rulenum++;
579
	$cprules .= "add {$rulenum} set 1 pass icmp from {$ips} to any out icmptype 0\n";
580
	$rulenum++;
581
	$cprules .= "add {$rulenum} set 1 pass icmp from any to {$ips} in icmptype 8 \n";
582
	$rulenum++;
583
	/* Allowed ips */
584
	$cprules .= "add {$rulenum} allow ip from table(3) to any in\n";
585
	$rulenum++;
586
	$cprules .= "add {$rulenum} allow ip from any to table(4) out\n";
587
	$rulenum++;
588
	$cprules .= "add {$rulenum} pipe tablearg ip from table(5) to any in\n";
589
	$rulenum++;
590
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(6) out\n";
591
	$rulenum++;
592
	$cprules .= "add {$rulenum} allow ip from any to table(7) in\n";
593
	$rulenum++;
594
	$cprules .= "add {$rulenum} allow ip from table(8) to any out\n";
595
	$rulenum++;
596
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(9) in\n";
597
	$rulenum++;
598
	$cprules .= "add {$rulenum} pipe tablearg ip from table(10) to any out\n";
599
	$rulenum++;
600

    
601
	/* Authenticated users rules. */
602
	if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
603
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from table(1) to any in\n";
604
		$rulenum++;
605
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from any to table(2) out\n";
606
		$rulenum++;
607
	} else {
608
		$cprules .= "add {$rulenum} set 1 allow ip from table(1) to any in\n";
609
		$rulenum++;
610
		$cprules .= "add {$rulenum} set 1 allow ip from any to table(2) out\n";
611
		$rulenum++;
612
	}
613

    
614
	
615
	$listenporthttp =
616
		$config['captiveportal'][$cpzone]['listenporthttp'] ?
617
		$config['captiveportal'][$cpzone]['listenporthttp'] :
618
		$config['captiveportal'][$cpzone]['zoneid'];
619
	
620
	$cprules .= <<<EOD
621

    
622
# redirect non-authenticated clients to captive portal
623
add 65531 set 1 fwd 127.0.0.1,{$listenporthttp} tcp from any to any in
624
# let the responses from the captive portal web server back out
625
add 65532 set 1 pass tcp from any to any out
626
# block everything else
627
add 65533 set 1 deny all from any to any
628
# pass everything else on layer2
629
add 65534 set 1 pass all from any to any layer2
630

    
631
EOD;
632

    
633
	/* generate passthru mac database */
634
	$cprules .= captiveportal_passthrumac_configure(true);
635
	$cprules .= "\n";
636

    
637
	/* allowed ipfw rules to make allowed ip work */
638
	$cprules .= captiveportal_allowedip_configure();
639

    
640
	/* allowed ipfw rules to make allowed hostnames work */
641
	$cprules .= captiveportal_allowedhostname_configure();
642
	
643
	/* load rules */
644
	if ($reinit == true)
645
		$cprules = "table all flush\nflush\n{$cprules}";
646
	else {
647
		$tmprules = "table 3 flush\n";
648
		$tmprules .= "table 4 flush\n";
649
		$tmprules .= "table 5 flush\n";
650
		$tmprules .= "table 6 flush\n";
651
		$tmprules .= "table 7 flush\n";
652
		$tmprules .= "table 8 flush\n";
653
		$tmprules .= "table 9 flush\n";
654
		$tmprules .= "table 10 flush\n";
655
		$tmprules .= "flush\n";
656
		$cprules = "{$tmprules}\n{$cprules}";
657
	}
658

    
659
	file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
660
	captiveportal_ipfw_set_context($cpzone);
661
	mwexec("/sbin/ipfw -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
662
	//@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
663

    
664
	if ($reinit == false)
665
		unlock($captiveportallck);
666

    
667
	/* activate ipfw(4) so CP can work */
668
	mwexec("/sbin/sysctl net.link.ether.ipfw=1");
669

    
670
	return $cprules;
671
}
672

    
673
/* remove clients that have been around for longer than the specified amount of time
674
 * db file structure:
675
 * timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time
676
 * (password is in Base64 and only saved when reauthentication is enabled)
677
 */
678
function captiveportal_prune_old() {
679
	global $g, $config, $cpzone;
680

    
681
	if (empty($cpzone))
682
		return;
683

    
684
	/* check for expired entries */
685
	if (empty($config['captiveportal'][$cpzone]['timeout']) ||
686
	!is_numeric($config['captiveportal'][$cpzone]['timeout']))
687
		$timeout = 0;
688
	else
689
		$timeout = $config['captiveportal'][$cpzone]['timeout'] * 60;
690

    
691
	if (empty($config['captiveportal'][$cpzone]['idletimeout']) ||
692
	!is_numeric($config['captiveportal'][$cpzone]['idletimeout']))
693
		$idletimeout = 0;
694
	else
695
		$idletimeout = $config['captiveportal'][$cpzone]['idletimeout'] * 60;
696

    
697
	if (!$timeout && !$idletimeout && !isset($config['captiveportal'][$cpzone]['reauthenticate']) && 
698
	!isset($config['captiveportal'][$cpzone]['radiussession_timeout']) && !isset($config['voucher'][$cpzone]['enable']))
699
		return;
700

    
701
	$radiussrvs = captiveportal_get_radius_servers();
702

    
703
	/* read database */
704
	$cpdb = captiveportal_read_db();
705

    
706
	/*	To make sure we iterate over ALL accounts on every run the count($cpdb) is moved
707
	 *	outside of the loop. Otherwise the loop would evaluate count() on every iteration
708
	 *	and since $i would increase and count() would decrement they would meet before we
709
	 *	had a chance to iterate over all accounts.
710
	 */
711
	$unsetindexes = array();
712
	$voucher_needs_sync = false;
713
	/* 
714
	 * Snapshot the time here to use for calculation to speed up the process.
715
	 * If something is missed next run will catch it!
716
	 */
717
	$pruning_time = time();
718
	$stop_time = $pruning_time;
719
	foreach ($cpdb as $cpentry) {
720

    
721
		$timedout = false;
722
		$term_cause = 1;
723
		if (empty($cpentry[10]))
724
			$cpentry[10] = 'first';
725
		$radiusservers = $radiussrvs[$cpentry[10]];
726

    
727
		/* hard timeout? */
728
		if ($timeout) {
729
			if (($pruning_time - $cpentry[0]) >= $timeout) {
730
				$timedout = true;
731
				$term_cause = 5; // Session-Timeout
732
			}
733
		}
734

    
735
		/* Session-Terminate-Time */
736
		if (!$timedout && !empty($cpentry[9])) {
737
			if ($pruning_time >= $cpentry[9]) {
738
				$timedout = true;
739
				$term_cause = 5; // Session-Timeout
740
			}
741
		}
742

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

    
759
		/* if vouchers are configured, activate session timeouts */
760
		if (!$timedout && isset($config['voucher'][$cpzone]['enable']) && !empty($cpentry[7])) {
761
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
762
				$timedout = true;
763
				$term_cause = 5; // Session-Timeout
764
				$voucher_needs_sync = true;
765
			}
766
		}
767

    
768
		/* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
769
		if (!$timedout && isset($config['captiveportal'][$cpzone]['radiussession_timeout']) && !empty($cpentry[7])) {
770
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
771
				$timedout = true;
772
				$term_cause = 5; // Session-Timeout
773
			}
774
		}
775

    
776
		if ($timedout) {
777
			captiveportal_disconnect($cpentry, $radiusservers,$term_cause,$stop_time);
778
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
779
			$unsetindexes[] = $cpentry[5];
780
		}
781

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

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

    
835
	if ($voucher_needs_sync == true)
836
		/* Triger a sync of the vouchers on config */
837
		send_event("service sync vouchers");
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, $cpzone;
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'][$cpzone]['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
		captiveportal_ipfw_set_context($cpzone);
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
		/* XXX: Redundant?! Ensure all pf(4) states are killed. */
870
		mwexec("pfctl -k {$dbent[2]}");
871
		mwexec("pfctl -K {$dbent[2]}");
872
	}
873

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

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

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

    
896
}
897

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

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

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

    
908
	/* find entry */
909
	if (isset($cpdb[$sessionid])) {
910
		$cpentry = $cpdb[$sessionid];
911
		/* write database */
912
		$unsetindex[] = $sessionid;
913
		captiveportal_write_db($cpdb, false, $unsetindex);
914
		if (empty($cpentry[10]))
915
			$cpentry[10] = 'first';
916
		captiveportal_disconnect($cpentry, $radiusservers[$cpentry[10]], $term_cause);
917
		captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
918
	}		
919
}
920

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

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

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

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

    
955
	$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
956

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

    
971
	return $rules;
972
}
973

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

    
977
	$rules = "";
978

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

    
985
		}
986
	}
987

    
988
	return $rules;
989
}
990

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

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

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

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

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

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

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

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

    
1085
	return $rules;
1086
}
1087

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

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

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

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

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

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

    
1131
	return $rules;
1132
}
1133

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

    
1138
	$ipfwoutput = "";
1139

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

    
1149
	return 0;
1150
}
1151

    
1152
function captiveportal_init_radius_servers() {
1153
	global $config, $g, $cpzone;
1154

    
1155
	/* generate radius server database */
1156
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1157
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1158
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1159
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1160
		$radiusip3 = ($config['captiveportal']['radiusip3']) ? $config['captiveportal']['radiusip3'] : null;
1161
		$radiusip4 = ($config['captiveportal']['radiusip4']) ? $config['captiveportal']['radiusip4'] : null;
1162

    
1163
		if ($config['captiveportal'][$cpzone]['radiusport'])
1164
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1165
		else
1166
			$radiusport = 1812;
1167
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1168
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1169
		else
1170
			$radiusacctport = 1813;
1171
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1172
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1173
		else
1174
			$radiusport2 = 1812;
1175
		if ($config['captiveportal']['radiusport3'])
1176
			$radiusport3 = $config['captiveportal']['radiusport3'];
1177
		else
1178
			$radiusport3 = 1812;
1179
		if ($config['captiveportal']['radiusport4'])
1180
			$radiusport4 = $config['captiveportal']['radiusport4'];
1181
		else
1182
			$radiusport4 = 1812;
1183

    
1184
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1185
		$radiuskey2 = ($config['captiveportal'][$cpzone]['radiuskey2']) ? $config['captiveportal'][$cpzone]['radiuskey2'] : null;
1186
		$radiuskey3 = ($config['captiveportal']['radiuskey3']) ? $config['captiveportal']['radiuskey3'] : null;
1187
		$radiuskey4 = ($config['captiveportal']['radiuskey4']) ? $config['captiveportal']['radiuskey4'] : null;
1188

    
1189
		$cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX);
1190
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
1191
		if (!$fd) {
1192
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1193
			unlock($cprdsrvlck);
1194
			return 1;
1195
		}
1196
		if (isset($radiusip, $radiuskey))
1197
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
1198
		if (isset($radiusip2, $radiuskey2))
1199
			fwrite($fd,"\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
1200
		if (isset($radiusip3, $radiuskey3))
1201
			fwrite($fd,"\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
1202
		if (isset($radiusip4, $radiuskey4))
1203
			fwrite($fd,"\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
1204
		
1205

    
1206
		fclose($fd);
1207
		unlock($cprdsrvlck);
1208
	}
1209
}
1210

    
1211
/* read RADIUS servers into array */
1212
function captiveportal_get_radius_servers() {
1213
	global $g, $cpzone;
1214

    
1215
	$cprdsrvlck = lock("captiveportalradius{$cpzone}");
1216
	if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
1217
		$radiusservers = array();
1218
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", 
1219
		FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1220
		if ($cpradiusdb) {
1221
			foreach($cpradiusdb as $cpradiusentry) {
1222
				$line = trim($cpradiusentry);
1223
				if ($line) {
1224
					$radsrv = array();
1225
						list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key'], $context) = explode(",",$line);
1226
				}
1227
				if (empty($context)) {
1228
					if (!is_array($radiusservers['first']))
1229
						$radiusservers['first'] = array();
1230
					$radiusservers['first'] = $radsrv;
1231
				} else {
1232
					if (!is_array($radiusservers[$context]))
1233
						$radiusservers[$context] = array();
1234
					$radiusservers[$context][] = $radsrv;
1235
				}
1236
			}
1237
		}
1238
		unlock($cprdsrvlck);
1239
		return $radiusservers;
1240
	}
1241

    
1242
	unlock($cprdsrvlck);
1243
	return false;
1244
}
1245

    
1246
/* log successful captive portal authentication to syslog */
1247
/* part of this code from php.net */
1248
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1249
	// Log it
1250
	if (!$message)
1251
		$message = "$status: $user, $mac, $ip";
1252
	else {
1253
		$message = trim($message);
1254
		$message = "$status: $user, $mac, $ip, $message";
1255
	}
1256
	captiveportal_syslog($message);
1257
}
1258

    
1259
/* log simple messages to syslog */
1260
function captiveportal_syslog($message) {
1261
	$message = trim($message);
1262
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1263
	// Log it
1264
	syslog(LOG_INFO, $message);
1265
	closelog();
1266
}
1267

    
1268
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1269
	global $g, $config;
1270

    
1271
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1272

    
1273
	/* If the pool is empty, return appropriate message and fail authentication */
1274
	if (is_null($ruleno)) {
1275
		$auth_list = array();
1276
		$auth_list['auth_val'] = 1;
1277
		$auth_list['error'] = "System reached maximum login capacity";
1278
		return $auth_list;
1279
	}
1280

    
1281
	$radiusservers = captiveportal_get_radius_servers();
1282

    
1283
	if (is_null($radiusctx))
1284
		$radiusctx = 'first';
1285

    
1286
	$auth_list = RADIUS_AUTHENTICATION($username,
1287
		$password,
1288
		$radiusservers[$radiusctx],
1289
		$clientip,
1290
		$clientmac,
1291
		$ruleno);
1292

    
1293
	if ($auth_list['auth_val'] == 2) {
1294
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1295
		$sessionid = portal_allow($clientip,
1296
			$clientmac,
1297
			$username,
1298
			$password,
1299
			$auth_list,
1300
			$ruleno,
1301
			$radiusctx);
1302
	}
1303

    
1304
	return $auth_list;
1305
}
1306

    
1307
/* read captive portal DB into array */
1308
function captiveportal_read_db($locked = false, $index = 5 /* sessionid by default */) {
1309
	global $g, $cpzone;
1310

    
1311
	$cpdb = array();
1312

    
1313
	if ($locked == false)
1314
		$cpdblck = lock("captiveportaldb{$cpzone}");
1315
	$fd = @fopen("{$g['vardb_path']}/captiveportal_{$cpzone}.db", "r");
1316
	if ($fd) {
1317
		while (!feof($fd)) {
1318
			$line = trim(fgets($fd));
1319
			if ($line) {
1320
				$cpe = explode(",", $line);
1321
				/* Hash by session id */
1322
				$cpdb[$cpe[$index]] = $cpe;
1323
			}
1324
		}
1325
		fclose($fd);
1326
	}
1327
	if ($locked == false)
1328
		unlock($cpdblck);
1329
	return $cpdb;
1330
}
1331

    
1332
/* write captive portal DB */
1333
function captiveportal_write_db($cpdb, $locked = false, $remove = false) {
1334
	global $g, $cpzone;
1335

    
1336
	if ($locked == false)
1337
		$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1338

    
1339
	if (is_array($remove)) {
1340
		if (!empty($remove)) {
1341
			$cpdb = captiveportal_read_db(true);
1342
			foreach ($remove as $key) {
1343
				if (is_array($key))
1344
					log_error("Captive portal Array passed as unset index: " . print_r($key, true));
1345
				else
1346
					unset($cpdb[$key]);
1347
			}
1348
		} else {
1349
			if ($locked == false)
1350
				unlock($cpdblck);
1351
			return; //This makes sure no record removal calls
1352
		}
1353
	}
1354
	$fd = @fopen("{$g['vardb_path']}/captiveportal_{$cpzone}.db", "w");
1355
	if ($fd) {
1356
		foreach ($cpdb as $cpent) {
1357
			fwrite($fd, join(",", $cpent) . "\n");
1358
		}
1359
		fclose($fd);
1360
	}
1361
	if ($locked == false)
1362
		unlock($cpdblck);
1363
}
1364

    
1365
function captiveportal_write_elements() {
1366
	global $g, $config, $cpzone;
1367
	
1368
	$cpcfg = $config['captiveportal'][$cpzone];
1369

    
1370
	/* delete any existing elements */
1371
	if (is_dir($g['captiveportal_element_path'])) {
1372
		$dh = opendir($g['captiveportal_element_path']);
1373
		while (($file = readdir($dh)) !== false) {
1374
			if ($file != "." && $file != "..")
1375
				unlink($g['captiveportal_element_path'] . "/" . $file);
1376
		}
1377
		closedir($dh);
1378
	} else {
1379
		@mkdir($g['captiveportal_element_path']);
1380
	}
1381

    
1382
	if (is_array($cpcfg['element'])) {
1383
		conf_mount_rw();
1384
		foreach ($cpcfg['element'] as $data) {
1385
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
1386
			if (!$fd) {
1387
				printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
1388
				return 1;
1389
			}
1390
			$decoded = base64_decode($data['content']);
1391
			fwrite($fd,$decoded);
1392
			fclose($fd);
1393
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1394
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1395
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
1396
		}
1397
		conf_mount_ro();
1398
	}
1399
	
1400
	return 0;
1401
}
1402

    
1403
function captiveportal_init_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
1404
	global $g, $cpzone;
1405

    
1406
	@unlink("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
1407
	$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1408
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1409
}
1410

    
1411
/*
1412
 * This function will calculate the lowest free firewall ruleno
1413
 * within the range specified based on the actual logged on users
1414
 *
1415
 */
1416
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899, $usebw = false) {
1417
	global $config, $g, $cpzone;
1418

    
1419
	$cpcfg = $config['captiveportal'][$cpzone];
1420
	if(!isset($cpcfg['enable']))
1421
		return NULL;
1422

    
1423
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1424
	$ruleno = 0;
1425
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1426
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1427
		for ($ridx = 2; $ridx < ($rulenos_range_max - $rulenos_start); $ridx++) {
1428
			if ($rules[$ridx]) {
1429
				/* 
1430
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1431
				 * and the out pipe ruleno + 1. This removes limitation that where present in 
1432
				 * previous version of the peruserbw.
1433
				 */
1434
				if (isset($cpcfg['peruserbw']) || $usebw == true)
1435
					$ridx++;
1436
				continue;
1437
			}
1438
			$ruleno = $ridx;
1439
			$rules[$ridx] = "used";
1440
			if (isset($cpcfg['peruserbw']) || $usebw == true)
1441
				$rules[++$ridx] = "used";
1442
			break;
1443
		}
1444
	} else {
1445
		$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1446
		$rules[2] = "used";
1447
		$ruleno = 2;
1448
	}
1449
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1450
	unlock($cpruleslck);
1451
	return $ruleno;
1452
}
1453

    
1454
function captiveportal_free_ipfw_ruleno($ruleno, $usedbw = false) {
1455
	global $config, $g, $cpzone;
1456

    
1457
	$cpcfg = $config['captiveportal'][$cpzone];
1458
	if(!isset($cpcfg['enable']))
1459
		return NULL;
1460

    
1461
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1462
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1463
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1464
		$rules[$ruleno] = false;
1465
		if (isset($cpcfg['peruserbw']) || $usedbw == true)
1466
			$rules[++$ruleno] = false;
1467
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1468
	}
1469
	unlock($cpruleslck);
1470
}
1471

    
1472
function captiveportal_get_ipfw_passthru_ruleno($value) {
1473
	global $config, $g, $cpzone;
1474

    
1475
	$cpcfg = $config['captiveportal'][$cpzone];
1476
	if(!isset($cpcfg['enable']))
1477
		return NULL;
1478

    
1479
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1480
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1481
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1482
		captiveportal_ipfw_set_context($cpzone);
1483
		$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`);
1484
		if ($rules[$ruleno]) {
1485
			unlock($cpruleslck);
1486
			return $ruleno;
1487
		}
1488
	}
1489

    
1490
	unlock($cpruleslck);
1491
	return NULL;
1492
}
1493

    
1494
/**
1495
 * This function will calculate the traffic produced by a client
1496
 * based on its firewall rule
1497
 *
1498
 * Point of view: NAS
1499
 *
1500
 * Input means: from the client
1501
 * Output means: to the client
1502
 *
1503
 */
1504

    
1505
function getVolume($ip) {
1506
	global $cpzone;
1507

    
1508
	$volume = array();
1509

    
1510
	// Initialize vars properly, since we don't want NULL vars
1511
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1512

    
1513
	// Ingress
1514
	$ipfwin = "";
1515
	$ipfwout = "";
1516
	$matchesin = "";
1517
	$matchesout = "";
1518
	captiveportal_ipfw_set_context($cpzone);
1519
	exec("/sbin/ipfw table 1 entrystats {$ip}", $ipfwin);
1520
	if ($ipfwin[0]) {
1521
		$ipfwin = explode(" ", $ipfwin[0]);
1522
		$volume['input_pkts'] = $ipfwin[2];
1523
		$volume['input_bytes'] = $ipfwin[3];
1524
	}
1525

    
1526
	exec("/sbin/ipfw table 2 entrystats {$ip}", $ipfwout);
1527
	if ($ipfwout[0]) {
1528
		$ipfwout = explode(" ", $ipfwout[0]);
1529
		$volume['output_pkts'] = $ipfwout[2];
1530
		$volume['output_bytes'] = $ipfwout[3];
1531
	}
1532

    
1533
	return $volume;
1534
}
1535

    
1536
/**
1537
 * Get the NAS-Identifier
1538
 *
1539
 * We will use our local hostname to make up the nas_id
1540
 */
1541
function getNasID()
1542
{
1543
	$nasId = "";
1544
	exec("/bin/hostname", $nasId);
1545
	if(!$nasId[0])
1546
		$nasId[0] = "{$g['product_name']}";
1547
	return $nasId[0];
1548
}
1549

    
1550
/**
1551
 * Get the NAS-IP-Address based on the current wan address
1552
 *
1553
 * Use functions in interfaces.inc to find this out
1554
 *
1555
 */
1556

    
1557
function getNasIP()
1558
{
1559
	global $config, $cpzone;
1560

    
1561
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1562
			$nasIp = get_interface_ip();
1563
	} else {
1564
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1565
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1566
		else
1567
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1568
	}
1569
		
1570
	if(!is_ipaddr($nasIp))
1571
		$nasIp = "0.0.0.0";
1572

    
1573
	return $nasIp;
1574
}
1575

    
1576
function portal_ip_from_client_ip($cliip) {
1577
	global $config, $cpzone;
1578

    
1579
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1580
	foreach ($interfaces as $cpif) {
1581
		$ip = get_interface_ip($cpif);
1582
		$sn = get_interface_subnet($cpif);
1583
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1584
			return $ip;
1585
	}
1586

    
1587
	$iface = exec_command("/sbin/route -n get {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1588
	$iface = trim($iface, "\n");
1589
	if (!empty($iface)) {
1590
		$ip = find_interface_ip($iface);
1591
		if (is_ipaddr($ip))
1592
			return $ip;
1593
	}
1594

    
1595
	// doesn't match up to any particular interface
1596
	// so let's set the portal IP to what PHP says 
1597
	// the server IP issuing the request is. 
1598
	// allows same behavior as 1.2.x where IP isn't 
1599
	// in the subnet of any CP interface (static routes, etc.)
1600
	// rather than forcing to DNS hostname resolution
1601
	$ip = $_SERVER['SERVER_ADDR'];
1602
	if (is_ipaddr($ip))
1603
		return $ip;
1604

    
1605
	return false;
1606
}
1607

    
1608
/* functions move from index.php */
1609

    
1610
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1611
	global $g, $config, $cpzone;
1612

    
1613
	/* Get captive portal layout */
1614
	if ($type == "redir") {
1615
		header("Location: {$redirurl}");
1616
		return;
1617
	} else if ($type == "login")
1618
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1619
	else
1620
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1621

    
1622
	$cpcfg = $config['captiveportal'][$cpzone];
1623

    
1624
	/* substitute the PORTAL_REDIRURL variable */
1625
	if ($config['captiveportal'][$cpzone]['preauthurl']) {
1626
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$config['captiveportal'][$cpzone]['preauthurl']}", $htmltext);
1627
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$config['captiveportal'][$cpzone]['preauthurl']}", $htmltext);
1628
	}
1629

    
1630
	/* substitute other variables */
1631
	if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
1632
		$httpsport = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
1633
		$htmltext = str_replace("\$PORTAL_ACTION\$", "https://{$config['captiveportal'][$cpzone]['httpsname']}:{$httpsport}/", $htmltext);
1634
		$htmltext = str_replace("#PORTAL_ACTION#", "https://{$config['captiveportal'][$cpzone]['httpsname']}:{$httpsport}/", $htmltext);
1635
	} else {
1636
		$httpport = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : $cpcfg['zoneid'];
1637
		$ifip = portal_ip_from_client_ip($clientip);
1638
		if (!$ifip)
1639
			$ourhostname = $config['system']['hostname'] . ":{$httpport}";
1640
		else
1641
			$ourhostname = "{$ifip}:{$httpport}";
1642
		$htmltext = str_replace("\$PORTAL_ACTION\$", "http://{$ourhostname}/", $htmltext);
1643
		$htmltext = str_replace("#PORTAL_ACTION#", "http://{$ourhostname}/", $htmltext);
1644
	}
1645

    
1646
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1647
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1648
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1649
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1650
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1651

    
1652
	// Special handling case for captive portal master page so that it can be ran 
1653
	// through the PHP interpreter using the include method above.  We convert the
1654
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1655
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1656
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1657
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1658
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1659
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1660
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1661
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1662

    
1663
    echo $htmltext;
1664
}
1665

    
1666
function portal_mac_radius($clientmac,$clientip) {
1667
    global $config, $cpzone;
1668

    
1669
    $radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1670

    
1671
    /* authentication against the radius server */
1672
    $username = mac_format($clientmac);
1673
    $auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1674
    if ($auth_list['auth_val'] == 2)
1675
        return TRUE;
1676
    if (!empty($auth_list['url_redirection']))
1677
	portal_reply_page($auth_list['url_redirection'], "redir");
1678

    
1679
    return FALSE;
1680
}
1681

    
1682
function captiveportal_reapply_attributes($cpentry, $attributes) {
1683
	global $config, $cpzone, $g;
1684
                         
1685
        /* Add rules for traffic shaping
1686
         * We don't need to add extra rules since traffic will pass due to the following kernel option
1687
         * net.inet.ip.fw.one_pass: 1
1688
         */
1689
        $peruserbw = isset($config['captiveportal'][$cpzone]['peruserbw']);
1690
                
1691
        $bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $config['captiveportal'][$cpzone]['bwdefaultup'];
1692
        $bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $config['captiveportal'][$cpzone]['bwdefaultdn'];
1693
        $bw_up_pipeno = $cpentry[1]+20000;
1694
        $bw_down_pipeno = $cpentry[1]+20001;
1695
        $commands = "";
1696

    
1697
        if ($peruserbw && !empty($bw_up) && is_numeric($bw_up)) {
1698
                $commands .= "pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100\n";
1699
        
1700
                if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
1701
                        $commands .= "table 1 del {$cpentry[2]} mac {$cpentry[3]}\n";
1702
                        $commands .= "table 1 add {$cpentry[2]} mac {$cpentry[3]} {$bw_up_pipeno}\n";
1703
                } else {
1704
                        $commands .= "table 1 del {$cpentry[2]}\n";
1705
                        $commands .= "table 1 add {$cpentry[2]} {$bw_up_pipeno}\n";
1706
                }
1707
        }
1708
        if ($peruserbw && !empty($bw_down) && is_numeric($bw_down)) {
1709
                $commands .= "pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100\n";
1710
                        
1711
                if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
1712
                        $commands .= "table 2 del {$cpentry[2]} mac {$cpentry[3]}\n";
1713
                        $commands .= "table 2 add {$cpentry[2]} mac {$cpentry[3]} {$bw_down_pipeno}\n";
1714
                } else {
1715
                        $commands .= "table 2 del {$cpentry[2]}\n";
1716
                        $commands .= "table 2 add {$cpentry[2]} {$bw_down_pipeno}\n";
1717
                }
1718
        }
1719

    
1720
        if (!empty($commands)) {
1721
                @file_put_contents("{$g['tmp_path']}/reattribute{$cpzone}.rule.tmp", $commands);
1722
                captiveportal_ipfw_set_context($cpzone);
1723
                mwexec("/sbin/ipfw -q {$g['tmp_path']}/reattribute{$cpzone}.rule.tmp");
1724
                //captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_BANDWIDTH_REAPPLY", "{$bw_up}/{$bw_down}");
1725
        }
1726

    
1727
        unset($bw_up_pipeno, $bw_Down_pipeno, $bw_up, $bw_down);
1728
}
1729

    
1730
function portal_allow($clientip,$clientmac,$username,$password = null, $attributes = null, $ruleno = null, $radiusctx = null)  {
1731

    
1732
	global $redirurl, $g, $config, $type, $passthrumac, $_POST, $cpzone;
1733

    
1734
	/* See if a ruleno is passed, if not start sessions because this means there isn't one atm */
1735
	if ($ruleno == null)
1736
		$ruleno = captiveportal_get_next_ipfw_ruleno();
1737

    
1738
	/* if the pool is empty, return appropriate message and exit */
1739
	if (is_null($ruleno)) {
1740
		portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1741
		log_error("WARNING!  Captive portal has reached maximum login capacity");
1742
		exit;
1743
	}
1744

    
1745
	// Ensure we create an array if we are missing attributes
1746
	if (!is_array($attributes))
1747
		$attributes = array();
1748

    
1749
	$radiusservers = captiveportal_get_radius_servers();
1750

    
1751
	/* Do not allow concurrent login execution. */
1752
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1753

    
1754
	unset($sessionid);
1755

    
1756
	/* read in client database */
1757
	$cpdb = captiveportal_read_db(true);
1758

    
1759
	if ($attributes['voucher'])
1760
		$remaining_time = $attributes['session_timeout'];
1761

    
1762
	$writecfg = false;
1763
	/* Find an existing session */
1764
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
1765
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1766
			$mac = captiveportal_passthrumac_findbyname($username);
1767
			if (!empty($mac)) {
1768
				if ($_POST['replacemacpassthru']) {
1769
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
1770
						if ($macent['mac'] == $mac['mac']) {
1771
							$macrules = "";
1772
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1773
                                			if ($ruleno) {
1774
								captiveportal_free_ipfw_ruleno($ruleno, true);
1775
                                        			$macrules .= "delete {$ruleno}\n";
1776
								++$ruleno;
1777
                                        			$macrules .= "delete {$ruleno}\n";
1778
                                			}
1779
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1780
							$mac['mac'] = $clientmac;
1781
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1782
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1783
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1784
							captiveportal_ipfw_set_context($cpzone);
1785
							mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1786
							$writecfg = true;
1787
							$sessionid = true;
1788
							break;
1789
						}
1790
					}
1791
                                } else {
1792
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1793
						$clientmac, $clientip, $username, $password);
1794
					exit;
1795
				}
1796
			}
1797
		}
1798
	}
1799

    
1800
	/* Snapshot the timestamp */
1801
	$allow_time = time();
1802
	if (is_null($radiusctx))
1803
		$radiusctx = 'first';
1804
	foreach ($cpdb as $sid => $cpentry) {
1805
		if (empty($cpentry[10]))
1806
			$cpentry[10] = 'first';
1807
		/* on the same ip */
1808
		if ($cpentry[2] == $clientip) {
1809
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac)   
1810
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1811
			else
1812
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
1813
			$sessionid = $sid;
1814
			break;
1815
		}
1816
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1817
			// user logged in with an active voucher. Check for how long and calculate 
1818
			// how much time we can give him (voucher credit - used time)
1819
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1820
			if ($remaining_time < 0)    // just in case. 
1821
				$remaining_time = 0;
1822

    
1823
			/* This user was already logged in so we disconnect the old one */
1824
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1825
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1826
			unset($cpdb[$sid]);
1827
			break;
1828
		}
1829
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1830
			/* on the same username */
1831
			if (strcasecmp($cpentry[4], $username) == 0) {
1832
				/* This user was already logged in so we disconnect the old one */
1833
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1834
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1835
				unset($cpdb[$sid]);
1836
				break;
1837
			}
1838
		}
1839
	}
1840

    
1841
	if ($attributes['voucher'] && $remaining_time <= 0)
1842
		return 0;       // voucher already used and no time left
1843

    
1844
	if (!isset($sessionid)) {
1845
		/* generate unique session ID */
1846
		$tod = gettimeofday();
1847
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1848

    
1849
		/* Add rules for traffic shaping
1850
		 * We don't need to add extra rules since traffic will pass due to the following kernel option
1851
		 * net.inet.ip.fw.one_pass: 1
1852
		 */
1853
		$peruserbw = isset($config['captiveportal'][$cpzone]['peruserbw']);
1854

    
1855
		$bw_up = isset($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $config['captiveportal'][$cpzone]['bwdefaultup'];
1856
		$bw_down = isset($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $config['captiveportal'][$cpzone]['bwdefaultdn'];
1857

    
1858
		if ($passthrumac) {
1859
			$mac = array();
1860
			$mac['mac'] = $clientmac;
1861
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername']))
1862
				$mac['username'] = $username;
1863
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1864
			if (!empty($bw_up))
1865
				$mac['bw_up'] = $bw_up;
1866
			if (!empty($bw_down))
1867
				$mac['bw_down'] = $bw_down;
1868
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
1869
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
1870
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1871
			unlock($cpdblck);
1872
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1873
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1874
			captiveportal_ipfw_set_context($cpzone);
1875
			mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1876
			$writecfg = true;
1877
		} else {
1878
			captiveportal_ipfw_set_context($cpzone);
1879

    
1880
			if ($peruserbw && !empty($bw_up) && is_numeric($bw_up)) {
1881
				$bw_up_pipeno = $ruleno + 20000;
1882
				//$bw_up /= 1000; // Scale to Kbit/s
1883
				mwexec("/sbin/ipfw pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100");
1884

    
1885
				if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1886
					mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac} {$bw_up_pipeno}");
1887
				else
1888
					mwexec("/sbin/ipfw table 1 add {$clientip} {$bw_up_pipeno}");
1889
			} else {
1890
				if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1891
					mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac}");
1892
				else
1893
					mwexec("/sbin/ipfw table 1 add {$clientip}");
1894
			}
1895
			if ($peruserbw && !empty($bw_down) && is_numeric($bw_down)) {
1896
				$bw_down_pipeno = $ruleno + 20001;
1897
				//$bw_down /= 1000; // Scale to Kbit/s
1898
				mwexec("/sbin/ipfw pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100");
1899

    
1900
				if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1901
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac} {$bw_down_pipeno}");
1902
				else
1903
					mwexec("/sbin/ipfw table 2 add {$clientip} {$bw_down_pipeno}");
1904
			} else {
1905
				if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1906
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac}");
1907
				else
1908
					mwexec("/sbin/ipfw table 2 add {$clientip}");
1909
			}
1910

    
1911
			if ($attributes['voucher'])
1912
				$attributes['session_timeout'] = $remaining_time;
1913

    
1914
			/* encode password in Base64 just in case it contains commas */
1915
			$bpassword = base64_encode($password);
1916
			$cpdb[] = array($allow_time, $ruleno, $clientip, $clientmac, $username, $sessionid, $bpassword,
1917
				$attributes['session_timeout'], $attributes['idle_timeout'], $attributes['session_terminate_time'], $radiusctx);
1918

    
1919
			/* rewrite information to database */
1920
			captiveportal_write_db($cpdb, true);
1921
			unlock($cpdblck);
1922

    
1923
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
1924
				$acct_val = RADIUS_ACCOUNTING_START($ruleno,
1925
                                		$username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
1926
				if ($acct_val == 1)
1927
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1928
			}
1929
		}
1930
	} else
1931
		unlock($cpdblck);
1932

    
1933
	if ($writecfg == true)
1934
		write_config();
1935

    
1936
	/* redirect user to desired destination */
1937
	if (!empty($attributes['url_redirection']))
1938
		$my_redirurl = $attributes['url_redirection'];
1939
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
1940
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
1941
	else
1942
		$my_redirurl = $redirurl;
1943

    
1944
	if(isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
1945

    
1946
		if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
1947
			$httpsport =
1948
				$config['captiveportal'][$cpzone]['listenporthttps'] ?
1949
				$config['captiveportal'][$cpzone]['listenporthttps'] :
1950
				($config['captiveportal'][$cpzone]['zoneid'] + 1);
1951
			$logouturl = "https://{$config['captiveportal']['httpsname']}:{$httpsport}/";
1952
		} else {
1953
			$ifip = portal_ip_from_client_ip($clientip);
1954
			$httpport =
1955
				$config['captiveportal'][$cpzone]['listenporthttp'] ?
1956
				$config['captiveportal'][$cpzone]['listenporthttp'] :
1957
				$config['captiveportal'][$cpzone]['zoneid'];
1958
			if (!$ifip)
1959
				$ourhostname = $config['system']['hostname'] . ":{$httpport}";
1960
			else
1961
				$ourhostname = "{$ifip}:{$httpport}";
1962
			$logouturl = "http://{$ourhostname}/";
1963
		}
1964

    
1965
		if (isset($attributes['reply_message']))
1966
			$message = $attributes['reply_message'];
1967
		else
1968
			$message = 0;
1969

    
1970
		include("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
1971

    
1972
	} else {
1973
		header("Location: " . $my_redirurl);
1974
	}
1975

    
1976
	return $sessionid;
1977
}
1978

    
1979

    
1980
/*
1981
 * Used for when pass-through credits are enabled.
1982
 * Returns true when there was at least one free login to deduct for the MAC.
1983
 * Expired entries are removed as they are seen.
1984
 * Active entries are updated according to the configuration.
1985
 */
1986
function portal_consume_passthrough_credit($clientmac) {
1987
	global $config, $cpzone;
1988

    
1989
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
1990
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
1991
	else
1992
		return false;
1993

    
1994
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
1995
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
1996
	else
1997
		return false;
1998

    
1999
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
2000
		return false;
2001

    
2002
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2003

    
2004
	/*
2005
	 * Read database of used MACs.  Lines are a comma-separated list
2006
	 * of the time, MAC, then the count of pass-through credits remaining.
2007
	 */
2008
	$usedmacs = captiveportal_read_usedmacs_db();
2009

    
2010
	$currenttime = time();
2011
	$found = false;
2012
	foreach ($usedmacs as $key => $usedmac) {
2013
		$usedmac = explode(",", $usedmac);
2014

    
2015
		if ($usedmac[1] == $clientmac) {
2016
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2017
				if ($usedmac[2] < 1) {
2018
					if ($updatetimeouts) {
2019
						$usedmac[0] = $currenttime;
2020
						unset($usedmacs[$key]);
2021
						$usedmacs[] = implode(",", $usedmac);
2022
						captiveportal_write_usedmacs_db($usedmacs);
2023
					}
2024

    
2025
					return false;
2026
				} else {
2027
					$usedmac[2] -= 1;
2028
					$usedmacs[$key] = implode(",", $usedmac);
2029
				}
2030

    
2031
				$found = true;
2032
			} else
2033
				unset($usedmacs[$key]);
2034

    
2035
			break;
2036
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2037
				unset($usedmacs[$key]);
2038
	}
2039

    
2040
	if (!$found) {
2041
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2042
		$usedmacs[] = implode(",", $usedmac);
2043
	}
2044

    
2045
	captiveportal_write_usedmacs_db($usedmacs);
2046
	return true;
2047
}
2048

    
2049
function captiveportal_read_usedmacs_db() {
2050
	global $g, $cpzone;
2051

    
2052
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2053
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2054
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2055
		if (!$usedmacs)
2056
			$usedmacs = array();
2057
	} else
2058
		$usedmacs = array();
2059

    
2060
	unlock($cpumaclck);
2061
	return $usedmacs;
2062
}
2063

    
2064
function captiveportal_write_usedmacs_db($usedmacs) {
2065
	global $g, $cpzone;
2066

    
2067
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2068
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2069
	unlock($cpumaclck);
2070
}
2071

    
2072
?>
(8-8/66)