Project

General

Profile

Download (66.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_webguis($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_webguis($cp);
435
		}
436
	}
437
}
438

    
439
function captiveportal_init_webguis($cpcfg) {
440
	global $g, $config, $cpzone;
441

    
442
	 if (!isset($cpcfg['enable']))
443
		return;
444

    
445
	if ($cpcfg['maxproc'])
446
		$maxproc = $cpcfg['maxproc'];
447
	else
448
		$maxproc = 16;
449

    
450
	$use_fastcgi = true;
451

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

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

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

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

    
480
/* reinit will disconnect all users, be careful! */
481
function captiveportal_init_rules($reinit = false) {
482
	global $config, $g, $cpzone;
483

    
484
	if (!isset($config['captiveportal'][$cpzone]['enable']))
485
		return;
486

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

    
525
	if ($reinit == false)
526
		$captiveportallck = lock("captiveportal{$cpzone}");
527

    
528
	/* init dummynet/ipfw rules number database */
529
	captiveportal_init_ipfw_ruleno();
530

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

    
538
	$cprules =	"add 65291 set 1 allow pfsync from any to any\n";
539
	$cprules .= "add 65292 set 1 allow carp from any to any\n";
540

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

    
549
# PPP Over Ethernet Discovery Stage
550
add 65304 set 1 pass layer2 mac-type 0x8863
551
# PPP Over Ethernet Session Stage
552
add 65305 set 1 pass layer2 mac-type 0x8864
553

    
554
# layer 2: block anything else non-IP
555
add 65307 set 1 deny layer2 not mac-type ip
556

    
557
EOD;
558

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

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

    
610
	
611
	$listenporthttp =
612
		$config['captiveportal'][$cpzone]['listenporthttp'] ?
613
		$config['captiveportal'][$cpzone]['listenporthttp'] :
614
		$config['captiveportal'][$cpzone]['zoneid'];
615
	
616
	$cprules .= <<<EOD
617

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

    
627
EOD;
628

    
629
	/* generate passthru mac database */
630
	$cprules .= captiveportal_passthrumac_configure(true);
631
	$cprules .= "\n";
632

    
633
	/* allowed ipfw rules to make allowed ip work */
634
	$cprules .= captiveportal_allowedip_configure();
635

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

    
655
	file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
656
	captiveportal_ipfw_set_context($cpzone);
657
	mwexec("/sbin/ipfw -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
658
	//@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
659

    
660
	if ($reinit == false)
661
		unlock($captiveportallck);
662

    
663
	/* activate ipfw(4) so CP can work */
664
	mwexec("/sbin/sysctl net.link.ether.ipfw=1");
665

    
666
	return $cprules;
667
}
668

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

    
677
	if (empty($cpzone))
678
		return;
679

    
680
	/* check for expired entries */
681
	if (empty($config['captiveportal'][$cpzone]['timeout']) ||
682
	!is_numeric($config['captiveportal'][$cpzone]['timeout']))
683
		$timeout = 0;
684
	else
685
		$timeout = $config['captiveportal'][$cpzone]['timeout'] * 60;
686

    
687
	if (empty($config['captiveportal'][$cpzone]['idletimeout']) ||
688
	!is_numeric($config['captiveportal'][$cpzone]['idletimeout']))
689
		$idletimeout = 0;
690
	else
691
		$idletimeout = $config['captiveportal'][$cpzone]['idletimeout'] * 60;
692

    
693
	if (!$timeout && !$idletimeout && !isset($config['captiveportal'][$cpzone]['reauthenticate']) && 
694
	!isset($config['captiveportal'][$cpzone]['radiussession_timeout']) && !isset($config['voucher'][$cpzone]['enable']))
695
		return;
696

    
697
	$radiussrvs = captiveportal_get_radius_servers();
698

    
699
	/* read database */
700
	$cpdb = captiveportal_read_db();
701

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

    
717
		$timedout = false;
718
		$term_cause = 1;
719
		if (empty($cpentry[10]))
720
			$cpentry[10] = 'first';
721
		$radiusservers = $radiussrvs[$cpentry[10]];
722

    
723
		/* hard timeout? */
724
		if ($timeout) {
725
			if (($pruning_time - $cpentry[0]) >= $timeout) {
726
				$timedout = true;
727
				$term_cause = 5; // Session-Timeout
728
			}
729
		}
730

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

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

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

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

    
772
		if ($timedout) {
773
			captiveportal_disconnect($cpentry, $radiusservers,$term_cause,$stop_time);
774
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
775
			$unsetindexes[] = $cpentry[5];
776
		}
777

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

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

    
830
	if ($voucher_needs_sync == true)
831
		/* Triger a sync of the vouchers on config */
832
		send_event("service sync vouchers");
833

    
834
	/* write database */
835
	if (!empty($unsetindexes))
836
		captiveportal_write_db($cpdb, false, $unsetindexes);
837
}
838

    
839
/* remove a single client according to the DB entry */
840
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
841
	global $g, $config, $cpzone;
842

    
843
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
844

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

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

    
879
	/* Release the ruleno so it can be reallocated to new clients. */
880
	captiveportal_free_ipfw_ruleno($dbent[1]);
881

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

    
891
}
892

    
893
/* remove a single client by sessionid */
894
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
895
	global $g, $config, $cpzone;
896

    
897
	$radiusservers = captiveportal_get_radius_servers();
898
	$unsetindex = array();
899

    
900
	/* read database */
901
	$cpdb = captiveportal_read_db();
902

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

    
916
/* send RADIUS acct stop for all current clients */
917
function captiveportal_radius_stop_all() {
918
	global $config, $cpzone;
919

    
920
	if (!isset($config['captiveportal'][$cpzone]['radacct_enable']))
921
		return;
922

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

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

    
950
	$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
951

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

    
966
	return $rules;
967
}
968

    
969
function captiveportal_passthrumac_configure($lock = false) {
970
	global $config, $g, $cpzone;
971

    
972
	$rules = "";
973

    
974
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
975
		$macdb = array();
976
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
977
			$rules .= captiveportal_passthrumac_configure_entry($macent);
978
			$macdb[$macent['mac']][$cpzone]['active']  = true;
979

    
980
		}
981
	}
982

    
983
	return $rules;
984
}
985

    
986
function captiveportal_passthrumac_findbyname($username) {
987
	global $config, $cpzone;
988

    
989
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
990
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
991
			if ($macent['username'] == $username)
992
				return $macent;
993
		}
994
	}
995
	return NULL;
996
}
997

    
998
/* 
999
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1000
 * table (5=IN)/(6=OUT) hold allowed ip's with bw limit.
1001
 */
1002
function captiveportal_allowedip_configure_entry($ipent) {
1003

    
1004
	/* This function can deal with hostname or ipaddress */
1005
	if($ipent['ip']) 	
1006
		$ipaddress = $ipent['ip'];
1007

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

    
1017
	$rules = "";
1018
	$enBwup = intval($ipent['bw_up']);
1019
	$enBwdown = intval($ipent['bw_down']);
1020
	$bw_up = "";
1021
	$bw_down = "";
1022
	$tablein = array();
1023
	$tableout = array();
1024

    
1025
	if (intval($enBwup) > 0 or intval($enBwdown) > 0)
1026
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
1027
	else
1028
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
1029

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

    
1080
	return $rules;
1081
}
1082

    
1083
/* 
1084
	Adds a dnsfilter entry and watches for hostname changes.
1085
	A change results in reloading the ruleset.
1086
*/
1087
function setup_dnsfilter_entries() {
1088
	global $g, $config, $cpzone;
1089

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

    
1105
function captiveportal_allowedhostname_configure() {
1106
	global $config, $g, $cpzone;
1107

    
1108
	$rules = "\n# captiveportal_allowedhostname_configure()\n";
1109
	setup_dnsfilter_entries();
1110
	if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1111
		foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) 
1112
			$rules .= captiveportal_allowedip_configure_entry($hostnameent);
1113
	}
1114
	return $rules;
1115
}
1116

    
1117
function captiveportal_allowedip_configure() {
1118
	global $config, $g, $cpzone;
1119

    
1120
	$rules = "";
1121
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1122
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) 
1123
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1124
	}
1125

    
1126
	return $rules;
1127
}
1128

    
1129
/* get last activity timestamp given client IP address */
1130
function captiveportal_get_last_activity($ip) {
1131
	global $cpzone;
1132

    
1133
	$ipfwoutput = "";
1134

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

    
1144
	return 0;
1145
}
1146

    
1147
function captiveportal_init_radius_servers() {
1148
	global $config, $g, $cpzone;
1149

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

    
1158
		if ($config['captiveportal'][$cpzone]['radiusport'])
1159
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1160
		else
1161
			$radiusport = 1812;
1162
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1163
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1164
		else
1165
			$radiusacctport = 1813;
1166
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1167
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1168
		else
1169
			$radiusport2 = 1812;
1170
		if ($config['captiveportal']['radiusport3'])
1171
			$radiusport3 = $config['captiveportal']['radiusport3'];
1172
		else
1173
			$radiusport3 = 1812;
1174
		if ($config['captiveportal']['radiusport4'])
1175
			$radiusport4 = $config['captiveportal']['radiusport4'];
1176
		else
1177
			$radiusport4 = 1812;
1178

    
1179
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1180
		$radiuskey2 = ($config['captiveportal'][$cpzone]['radiuskey2']) ? $config['captiveportal'][$cpzone]['radiuskey2'] : null;
1181
		$radiuskey3 = ($config['captiveportal']['radiuskey3']) ? $config['captiveportal']['radiuskey3'] : null;
1182
		$radiuskey4 = ($config['captiveportal']['radiuskey4']) ? $config['captiveportal']['radiuskey4'] : null;
1183

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

    
1201
		fclose($fd);
1202
		unlock($cprdsrvlck);
1203
	}
1204
}
1205

    
1206
/* read RADIUS servers into array */
1207
function captiveportal_get_radius_servers() {
1208
	global $g, $cpzone;
1209

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

    
1237
	unlock($cprdsrvlck);
1238
	return false;
1239
}
1240

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

    
1254
/* log simple messages to syslog */
1255
function captiveportal_syslog($message) {
1256
	$message = trim($message);
1257
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1258
	// Log it
1259
	syslog(LOG_INFO, $message);
1260
	closelog();
1261
}
1262

    
1263
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1264
	global $g, $config;
1265

    
1266
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1267

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

    
1276
	$radiusservers = captiveportal_get_radius_servers();
1277

    
1278
	if (is_null($radiusctx))
1279
		$radiusctx = 'first';
1280

    
1281
	$auth_list = RADIUS_AUTHENTICATION($username,
1282
		$password,
1283
		$radiusservers[$radiusctx],
1284
		$clientip,
1285
		$clientmac,
1286
		$ruleno);
1287

    
1288
	if ($auth_list['auth_val'] == 2) {
1289
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1290
		$sessionid = portal_allow($clientip,
1291
			$clientmac,
1292
			$username,
1293
			$password,
1294
			$auth_list,
1295
			$ruleno,
1296
			$radiusctx);
1297
	}
1298

    
1299
	return $auth_list;
1300
}
1301

    
1302
/* read captive portal DB into array */
1303
function captiveportal_read_db($locked = false, $index = 5 /* sessionid by default */) {
1304
	global $g, $cpzone;
1305

    
1306
	$cpdb = array();
1307

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

    
1327
/* write captive portal DB */
1328
function captiveportal_write_db($cpdb, $locked = false, $remove = false) {
1329
	global $g, $cpzone;
1330

    
1331
	if ($locked == false)
1332
		$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1333

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

    
1357
function captiveportal_write_elements() {
1358
	global $g, $config, $cpzone;
1359
	
1360
	$cpcfg = $config['captiveportal'][$cpzone];
1361

    
1362
	/* delete any existing elements */
1363
	if (is_dir($g['captiveportal_element_path'])) {
1364
		$dh = opendir($g['captiveportal_element_path']);
1365
		while (($file = readdir($dh)) !== false) {
1366
			if ($file != "." && $file != "..")
1367
				unlink($g['captiveportal_element_path'] . "/" . $file);
1368
		}
1369
		closedir($dh);
1370
	} else {
1371
		@mkdir($g['captiveportal_element_path']);
1372
	}
1373

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

    
1395
function captiveportal_init_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
1396
	global $g, $cpzone;
1397

    
1398
	@unlink("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
1399
	$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1400
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1401
}
1402

    
1403
/*
1404
 * This function will calculate the lowest free firewall ruleno
1405
 * within the range specified based on the actual logged on users
1406
 *
1407
 */
1408
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899, $usebw = false) {
1409
	global $config, $g, $cpzone;
1410

    
1411
	$cpcfg = $config['captiveportal'][$cpzone];
1412
	if(!isset($cpcfg['enable']))
1413
		return NULL;
1414

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

    
1446
function captiveportal_free_ipfw_ruleno($ruleno, $usedbw = false) {
1447
	global $config, $g, $cpzone;
1448

    
1449
	$cpcfg = $config['captiveportal'][$cpzone];
1450
	if(!isset($cpcfg['enable']))
1451
		return NULL;
1452

    
1453
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1454
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1455
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1456
		$rules[$ruleno] = false;
1457
		if (isset($cpcfg['peruserbw']) || $usedbw == true)
1458
			$rules[++$ruleno] = false;
1459
		file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1460
	}
1461
	unlock($cpruleslck);
1462
}
1463

    
1464
function captiveportal_get_ipfw_passthru_ruleno($value) {
1465
	global $config, $g, $cpzone;
1466

    
1467
	$cpcfg = $config['captiveportal'][$cpzone];
1468
	if(!isset($cpcfg['enable']))
1469
		return NULL;
1470

    
1471
	$cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
1472
	if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
1473
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
1474
		captiveportal_ipfw_set_context($cpzone);
1475
		$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`);
1476
		if ($rules[$ruleno]) {
1477
			unlock($cpruleslck);
1478
			return $ruleno;
1479
		}
1480
	}
1481

    
1482
	unlock($cpruleslck);
1483
	return NULL;
1484
}
1485

    
1486
/**
1487
 * This function will calculate the traffic produced by a client
1488
 * based on its firewall rule
1489
 *
1490
 * Point of view: NAS
1491
 *
1492
 * Input means: from the client
1493
 * Output means: to the client
1494
 *
1495
 */
1496

    
1497
function getVolume($ip) {
1498
	global $cpzone;
1499

    
1500
	$volume = array();
1501

    
1502
	// Initialize vars properly, since we don't want NULL vars
1503
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1504

    
1505
	// Ingress
1506
	$ipfwin = "";
1507
	$ipfwout = "";
1508
	$matchesin = "";
1509
	$matchesout = "";
1510
	captiveportal_ipfw_set_context($cpzone);
1511
	exec("/sbin/ipfw table 1 entrystats {$ip}", $ipfwin);
1512
	if ($ipfwin[0]) {
1513
		$ipfwin = explode(" ", $ipfwin[0]);
1514
		$volume['input_pkts'] = $ipfwin[2];
1515
		$volume['input_bytes'] = $ipfwin[3];
1516
	}
1517

    
1518
	exec("/sbin/ipfw table 2 entrystats {$ip}", $ipfwout);
1519
	if ($ipfwout[0]) {
1520
		$ipfwout = explode(" ", $ipfwout[0]);
1521
		$volume['output_pkts'] = $ipfwout[2];
1522
		$volume['output_bytes'] = $ipfwout[3];
1523
	}
1524

    
1525
	return $volume;
1526
}
1527

    
1528
/**
1529
 * Get the NAS-Identifier
1530
 *
1531
 * We will use our local hostname to make up the nas_id
1532
 */
1533
function getNasID()
1534
{
1535
	$nasId = "";
1536
	exec("/bin/hostname", $nasId);
1537
	if(!$nasId[0])
1538
		$nasId[0] = "{$g['product_name']}";
1539
	return $nasId[0];
1540
}
1541

    
1542
/**
1543
 * Get the NAS-IP-Address based on the current wan address
1544
 *
1545
 * Use functions in interfaces.inc to find this out
1546
 *
1547
 */
1548

    
1549
function getNasIP()
1550
{
1551
	global $config, $cpzone;
1552

    
1553
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1554
			$nasIp = get_interface_ip();
1555
	} else {
1556
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1557
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1558
		else
1559
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1560
	}
1561
		
1562
	if(!is_ipaddr($nasIp))
1563
		$nasIp = "0.0.0.0";
1564

    
1565
	return $nasIp;
1566
}
1567

    
1568
function portal_ip_from_client_ip($cliip) {
1569
	global $config, $cpzone;
1570

    
1571
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1572
	foreach ($interfaces as $cpif) {
1573
		$ip = get_interface_ip($cpif);
1574
		$sn = get_interface_subnet($cpif);
1575
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1576
			return $ip;
1577
	}
1578

    
1579
	$iface = exec_command("/sbin/route -n get {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
1580
	$iface = trim($iface, "\n");
1581
	if (!empty($iface)) {
1582
		$ip = find_interface_ip($iface);
1583
		if (is_ipaddr($ip))
1584
			return $ip;
1585
	}
1586

    
1587
	// doesn't match up to any particular interface
1588
	// so let's set the portal IP to what PHP says 
1589
	// the server IP issuing the request is. 
1590
	// allows same behavior as 1.2.x where IP isn't 
1591
	// in the subnet of any CP interface (static routes, etc.)
1592
	// rather than forcing to DNS hostname resolution
1593
	$ip = $_SERVER['SERVER_ADDR'];
1594
	if (is_ipaddr($ip))
1595
		return $ip;
1596

    
1597
	return false;
1598
}
1599

    
1600
/* functions move from index.php */
1601

    
1602
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1603
	global $g, $config, $cpzone;
1604

    
1605
	/* Get captive portal layout */
1606
	if ($type == "redir") {
1607
		header("Location: {$redirurl}");
1608
		return;
1609
	} else if ($type == "login")
1610
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1611
	else
1612
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1613

    
1614
	$cpcfg = $config['captiveportal'][$cpzone];
1615

    
1616
	/* substitute the PORTAL_REDIRURL variable */
1617
	if ($config['captiveportal'][$cpzone]['preauthurl']) {
1618
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$config['captiveportal'][$cpzone]['preauthurl']}", $htmltext);
1619
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$config['captiveportal'][$cpzone]['preauthurl']}", $htmltext);
1620
	}
1621

    
1622
	/* substitute other variables */
1623
	if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
1624
		$httpsport = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 1);
1625
		$htmltext = str_replace("\$PORTAL_ACTION\$", "https://{$config['captiveportal'][$cpzone]['httpsname']}:{$httpsport}/", $htmltext);
1626
		$htmltext = str_replace("#PORTAL_ACTION#", "https://{$config['captiveportal'][$cpzone]['httpsname']}:{$httpsport}/", $htmltext);
1627
	} else {
1628
		$httpport = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : $cpcfg['zoneid'];
1629
		$ifip = portal_ip_from_client_ip($clientip);
1630
		if (!$ifip)
1631
			$ourhostname = $config['system']['hostname'] . ":{$httpport}";
1632
		else
1633
			$ourhostname = "{$ifip}:{$httpport}";
1634
		$htmltext = str_replace("\$PORTAL_ACTION\$", "http://{$ourhostname}/", $htmltext);
1635
		$htmltext = str_replace("#PORTAL_ACTION#", "http://{$ourhostname}/", $htmltext);
1636
	}
1637

    
1638
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1639
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1640
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1641
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1642
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1643

    
1644
	// Special handling case for captive portal master page so that it can be ran 
1645
	// through the PHP interpreter using the include method above.  We convert the
1646
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1647
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1648
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1649
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1650
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1651
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1652
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1653
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1654

    
1655
    echo $htmltext;
1656
}
1657

    
1658
function portal_mac_radius($clientmac,$clientip) {
1659
    global $config, $cpzone;
1660

    
1661
    $radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1662

    
1663
    /* authentication against the radius server */
1664
    $username = mac_format($clientmac);
1665
    $auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1666
    if ($auth_list['auth_val'] == 2)
1667
        return TRUE;
1668
    if (!empty($auth_list['url_redirection']))
1669
	portal_reply_page($auth_list['url_redirection'], "redir");
1670

    
1671
    return FALSE;
1672
}
1673

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

    
1676
	global $redirurl, $g, $config, $type, $passthrumac, $_POST, $cpzone;
1677

    
1678
	/* See if a ruleno is passed, if not start sessions because this means there isn't one atm */
1679
	if ($ruleno == null)
1680
		$ruleno = captiveportal_get_next_ipfw_ruleno();
1681

    
1682
	/* if the pool is empty, return appropriate message and exit */
1683
	if (is_null($ruleno)) {
1684
		portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1685
		log_error("WARNING!  Captive portal has reached maximum login capacity");
1686
		exit;
1687
	}
1688

    
1689
	// Ensure we create an array if we are missing attributes
1690
	if (!is_array($attributes))
1691
		$attributes = array();
1692

    
1693
	$radiusservers = captiveportal_get_radius_servers();
1694

    
1695
	/* Do not allow concurrent login execution. */
1696
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1697

    
1698
	unset($sessionid);
1699

    
1700
	/* read in client database */
1701
	$cpdb = captiveportal_read_db(true);
1702

    
1703
	if ($attributes['voucher'])
1704
		$remaining_time = $attributes['session_timeout'];
1705

    
1706
	$writecfg = false;
1707
	/* Find an existing session */
1708
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
1709
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1710
			$mac = captiveportal_passthrumac_findbyname($username);
1711
			if (!empty($mac)) {
1712
				if ($_POST['replacemacpassthru']) {
1713
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
1714
						if ($macent['mac'] == $mac['mac']) {
1715
							$macrules = "";
1716
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1717
                                			if ($ruleno) {
1718
								captiveportal_free_ipfw_ruleno($ruleno, true);
1719
                                        			$macrules .= "delete {$ruleno}\n";
1720
								++$ruleno;
1721
                                        			$macrules .= "delete {$ruleno}\n";
1722
                                			}
1723
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1724
							$mac['mac'] = $clientmac;
1725
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1726
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1727
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1728
							captiveportal_ipfw_set_context($cpzone);
1729
							mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1730
							$writecfg = true;
1731
							$sessionid = true;
1732
							break;
1733
						}
1734
					}
1735
                                } else {
1736
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1737
						$clientmac, $clientip, $username, $password);
1738
					exit;
1739
				}
1740
			}
1741
		}
1742
	}
1743

    
1744
	/* Snapshot the timestamp */
1745
	$allow_time = time();
1746
	if (is_null($radiusctx))
1747
		$radiusctx = 'first';
1748
	foreach ($cpdb as $sid => $cpentry) {
1749
		if (empty($cpentry[10]))
1750
			$cpentry[10] = 'first';
1751
		/* on the same ip */
1752
		if ($cpentry[2] == $clientip) {
1753
			if (isset($config['captiveportal']['nomacfilter']) || $cpentry[3] == $clientmac)   
1754
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1755
			else
1756
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
1757
			$sessionid = $sid;
1758
			break;
1759
		}
1760
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1761
			// user logged in with an active voucher. Check for how long and calculate 
1762
			// how much time we can give him (voucher credit - used time)
1763
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1764
			if ($remaining_time < 0)    // just in case. 
1765
				$remaining_time = 0;
1766

    
1767
			/* This user was already logged in so we disconnect the old one */
1768
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1769
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1770
			unset($cpdb[$sid]);
1771
			break;
1772
		}
1773
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1774
			/* on the same username */
1775
			if (strcasecmp($cpentry[4], $username) == 0) {
1776
				/* This user was already logged in so we disconnect the old one */
1777
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1778
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1779
				unset($cpdb[$sid]);
1780
				break;
1781
			}
1782
		}
1783
	}
1784

    
1785
	if ($attributes['voucher'] && $remaining_time <= 0)
1786
		return 0;       // voucher already used and no time left
1787

    
1788
	if (!isset($sessionid)) {
1789
		/* generate unique session ID */
1790
		$tod = gettimeofday();
1791
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1792

    
1793
		/* Add rules for traffic shaping
1794
		 * We don't need to add extra rules since traffic will pass due to the following kernel option
1795
		 * net.inet.ip.fw.one_pass: 1
1796
		 */
1797
		$peruserbw = isset($config['captiveportal'][$cpzone]['peruserbw']);
1798

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

    
1802
		if ($passthrumac) {
1803
			$mac = array();
1804
			$mac['mac'] = $clientmac;
1805
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername']))
1806
				$mac['username'] = $username;
1807
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1808
			if (!empty($bw_up))
1809
				$mac['bw_up'] = $bw_up;
1810
			if (!empty($bw_down))
1811
				$mac['bw_down'] = $bw_down;
1812
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
1813
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
1814
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1815
			unlock($cpdblck);
1816
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1817
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1818
			captiveportal_ipfw_set_context($cpzone);
1819
			mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1820
			$writecfg = true;
1821
		} else {
1822
			captiveportal_ipfw_set_context($cpzone);
1823

    
1824
			if ($peruserbw && !empty($bw_up) && is_numeric($bw_up)) {
1825
				$bw_up_pipeno = $ruleno + 20000;
1826
				//$bw_up /= 1000; // Scale to Kbit/s
1827
				mwexec("/sbin/ipfw pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100");
1828

    
1829
				if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1830
					mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac} {$bw_up_pipeno}");
1831
				else
1832
					mwexec("/sbin/ipfw table 1 add {$clientip} {$bw_up_pipeno}");
1833
			} else {
1834
				if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1835
					mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac}");
1836
				else
1837
					mwexec("/sbin/ipfw table 1 add {$clientip}");
1838
			}
1839
			if ($peruserbw && !empty($bw_down) && is_numeric($bw_down)) {
1840
				$bw_down_pipeno = $ruleno + 20001;
1841
				//$bw_down /= 1000; // Scale to Kbit/s
1842
				mwexec("/sbin/ipfw pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100");
1843

    
1844
				if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1845
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac} {$bw_down_pipeno}");
1846
				else
1847
					mwexec("/sbin/ipfw table 2 add {$clientip} {$bw_down_pipeno}");
1848
			} else {
1849
				if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1850
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac}");
1851
				else
1852
					mwexec("/sbin/ipfw table 2 add {$clientip}");
1853
			}
1854

    
1855
			if ($attributes['voucher'])
1856
				$attributes['session_timeout'] = $remaining_time;
1857

    
1858
			/* encode password in Base64 just in case it contains commas */
1859
			$bpassword = base64_encode($password);
1860
			$cpdb[] = array($allow_time, $ruleno, $clientip, $clientmac, $username, $sessionid, $bpassword,
1861
				$attributes['session_timeout'], $attributes['idle_timeout'], $attributes['session_terminate_time'], $radiusctx);
1862

    
1863
			/* rewrite information to database */
1864
			captiveportal_write_db($cpdb, true);
1865
			unlock($cpdblck);
1866

    
1867
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
1868
				$acct_val = RADIUS_ACCOUNTING_START($ruleno,
1869
                                		$username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
1870
				if ($acct_val == 1)
1871
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1872
			}
1873
		}
1874
	} else
1875
		unlock($cpdblck);
1876

    
1877
	if ($writecfg == true)
1878
		write_config();
1879

    
1880
	/* redirect user to desired destination */
1881
	if (!empty($attributes['url_redirection']))
1882
		$my_redirurl = $attributes['url_redirection'];
1883
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
1884
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
1885
	else
1886
		$my_redirurl = $redirurl;
1887

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

    
1890
		if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
1891
			$httpsport = $config['captiveportal'][$cpzone]['zoneid'] + 1;
1892
			$logouturl = "https://{$config['captiveportal']['httpsname']}:{$httpsport}/";
1893
		} else {
1894
			$ifip = portal_ip_from_client_ip($clientip);
1895
			$httpport =
1896
				$config['captiveportal'][$cpzone]['listenporthttp'] ?
1897
				$config['captiveportal'][$cpzone]['listenporthttp'] :
1898
				$config['captiveportal'][$cpzone]['zoneid'];
1899
			if (!$ifip)
1900
				$ourhostname = $config['system']['hostname'] . ":{$httpport}";
1901
			else
1902
				$ourhostname = "{$ifip}:{$httpport}";
1903
			$logouturl = "http://{$ourhostname}/";
1904
		}
1905

    
1906
		if (isset($attributes['reply_message']))
1907
			$message = $attributes['reply_message'];
1908
		else
1909
			$message = 0;
1910

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

    
1913
	} else {
1914
		header("Location: " . $my_redirurl);
1915
	}
1916

    
1917
	return $sessionid;
1918
}
1919

    
1920

    
1921
/*
1922
 * Used for when pass-through credits are enabled.
1923
 * Returns true when there was at least one free login to deduct for the MAC.
1924
 * Expired entries are removed as they are seen.
1925
 * Active entries are updated according to the configuration.
1926
 */
1927
function portal_consume_passthrough_credit($clientmac) {
1928
	global $config, $cpzone;
1929

    
1930
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
1931
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
1932
	else
1933
		return false;
1934

    
1935
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
1936
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
1937
	else
1938
		return false;
1939

    
1940
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1941
		return false;
1942

    
1943
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
1944

    
1945
	/*
1946
	 * Read database of used MACs.  Lines are a comma-separated list
1947
	 * of the time, MAC, then the count of pass-through credits remaining.
1948
	 */
1949
	$usedmacs = captiveportal_read_usedmacs_db();
1950

    
1951
	$currenttime = time();
1952
	$found = false;
1953
	foreach ($usedmacs as $key => $usedmac) {
1954
		$usedmac = explode(",", $usedmac);
1955

    
1956
		if ($usedmac[1] == $clientmac) {
1957
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
1958
				if ($usedmac[2] < 1) {
1959
					if ($updatetimeouts) {
1960
						$usedmac[0] = $currenttime;
1961
						unset($usedmacs[$key]);
1962
						$usedmacs[] = implode(",", $usedmac);
1963
						captiveportal_write_usedmacs_db($usedmacs);
1964
					}
1965

    
1966
					return false;
1967
				} else {
1968
					$usedmac[2] -= 1;
1969
					$usedmacs[$key] = implode(",", $usedmac);
1970
				}
1971

    
1972
				$found = true;
1973
			} else
1974
				unset($usedmacs[$key]);
1975

    
1976
			break;
1977
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
1978
				unset($usedmacs[$key]);
1979
	}
1980

    
1981
	if (!$found) {
1982
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
1983
		$usedmacs[] = implode(",", $usedmac);
1984
	}
1985

    
1986
	captiveportal_write_usedmacs_db($usedmacs);
1987
	return true;
1988
}
1989

    
1990
function captiveportal_read_usedmacs_db() {
1991
	global $g, $cpzone;
1992

    
1993
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
1994
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
1995
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1996
		if (!$usedmacs)
1997
			$usedmacs = array();
1998
	} else
1999
		$usedmacs = array();
2000

    
2001
	unlock($cpumaclck);
2002
	return $usedmacs;
2003
}
2004

    
2005
function captiveportal_write_usedmacs_db($usedmacs) {
2006
	global $g, $cpzone;
2007

    
2008
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2009
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2010
	unlock($cpumaclck);
2011
}
2012

    
2013
?>
(8-8/65)