Project

General

Profile

Download (69.6 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
				} else if ($auth_list['auth_val'] == 2)
826
					captiveportal_reapply_attributes($cpentry, $auth_list);
827
			}
828
		}
829
	}
830

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

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

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

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

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

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

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

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

    
892
}
893

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

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

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

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

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

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

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

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

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

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

    
967
	return $rules;
968
}
969

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

    
973
	$rules = "";
974

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

    
981
		}
982
	}
983

    
984
	return $rules;
985
}
986

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

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

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

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

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

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

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

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

    
1081
	return $rules;
1082
}
1083

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

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

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

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

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

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

    
1127
	return $rules;
1128
}
1129

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

    
1134
	$ipfwoutput = "";
1135

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

    
1145
	return 0;
1146
}
1147

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

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

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

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

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

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

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

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

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

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

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

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

    
1267
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1268

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

    
1277
	$radiusservers = captiveportal_get_radius_servers();
1278

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

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

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

    
1300
	return $auth_list;
1301
}
1302

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

    
1307
	$cpdb = array();
1308

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

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

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

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

    
1361
function captiveportal_write_elements() {
1362
	global $g, $config, $cpzone;
1363
	
1364
	$cpcfg = $config['captiveportal'][$cpzone];
1365

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

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

    
1399
function captiveportal_init_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
1400
	global $g, $cpzone;
1401

    
1402
	@unlink("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
1403
	$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1404
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1405
}
1406

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

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

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

    
1450
function captiveportal_free_ipfw_ruleno($ruleno, $usedbw = false) {
1451
	global $config, $g, $cpzone;
1452

    
1453
	$cpcfg = $config['captiveportal'][$cpzone];
1454
	if(!isset($cpcfg['enable']))
1455
		return NULL;
1456

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

    
1468
function captiveportal_get_ipfw_passthru_ruleno($value) {
1469
	global $config, $g, $cpzone;
1470

    
1471
	$cpcfg = $config['captiveportal'][$cpzone];
1472
	if(!isset($cpcfg['enable']))
1473
		return NULL;
1474

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

    
1486
	unlock($cpruleslck);
1487
	return NULL;
1488
}
1489

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

    
1501
function getVolume($ip) {
1502
	global $cpzone;
1503

    
1504
	$volume = array();
1505

    
1506
	// Initialize vars properly, since we don't want NULL vars
1507
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1508

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

    
1522
	exec("/sbin/ipfw table 2 entrystats {$ip}", $ipfwout);
1523
	if ($ipfwout[0]) {
1524
		$ipfwout = explode(" ", $ipfwout[0]);
1525
		$volume['output_pkts'] = $ipfwout[2];
1526
		$volume['output_bytes'] = $ipfwout[3];
1527
	}
1528

    
1529
	return $volume;
1530
}
1531

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

    
1546
/**
1547
 * Get the NAS-IP-Address based on the current wan address
1548
 *
1549
 * Use functions in interfaces.inc to find this out
1550
 *
1551
 */
1552

    
1553
function getNasIP()
1554
{
1555
	global $config, $cpzone;
1556

    
1557
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1558
			$nasIp = get_interface_ip();
1559
	} else {
1560
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1561
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1562
		else
1563
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1564
	}
1565
		
1566
	if(!is_ipaddr($nasIp))
1567
		$nasIp = "0.0.0.0";
1568

    
1569
	return $nasIp;
1570
}
1571

    
1572
function portal_ip_from_client_ip($cliip) {
1573
	global $config, $cpzone;
1574

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

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

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

    
1601
	return false;
1602
}
1603

    
1604
/* functions move from index.php */
1605

    
1606
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1607
	global $g, $config, $cpzone;
1608

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

    
1618
	$cpcfg = $config['captiveportal'][$cpzone];
1619

    
1620
	/* substitute the PORTAL_REDIRURL variable */
1621
	if ($config['captiveportal'][$cpzone]['preauthurl']) {
1622
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$config['captiveportal'][$cpzone]['preauthurl']}", $htmltext);
1623
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$config['captiveportal'][$cpzone]['preauthurl']}", $htmltext);
1624
	}
1625

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

    
1642
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1643
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1644
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1645
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1646
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1647

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

    
1659
    echo $htmltext;
1660
}
1661

    
1662
function portal_mac_radius($clientmac,$clientip) {
1663
    global $config, $cpzone;
1664

    
1665
    $radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1666

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

    
1675
    return FALSE;
1676
}
1677

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

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

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

    
1723
        unset($bw_up_pipeno, $bw_Down_pipeno, $bw_up, $bw_down);
1724
}
1725

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

    
1728
	global $redirurl, $g, $config, $type, $passthrumac, $_POST, $cpzone;
1729

    
1730
	/* See if a ruleno is passed, if not start sessions because this means there isn't one atm */
1731
	if ($ruleno == null)
1732
		$ruleno = captiveportal_get_next_ipfw_ruleno();
1733

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

    
1741
	// Ensure we create an array if we are missing attributes
1742
	if (!is_array($attributes))
1743
		$attributes = array();
1744

    
1745
	$radiusservers = captiveportal_get_radius_servers();
1746

    
1747
	/* Do not allow concurrent login execution. */
1748
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1749

    
1750
	unset($sessionid);
1751

    
1752
	/* read in client database */
1753
	$cpdb = captiveportal_read_db(true);
1754

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

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

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

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

    
1837
	if ($attributes['voucher'] && $remaining_time <= 0)
1838
		return 0;       // voucher already used and no time left
1839

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

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

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

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

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

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

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

    
1907
			if ($attributes['voucher'])
1908
				$attributes['session_timeout'] = $remaining_time;
1909

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

    
1915
			/* rewrite information to database */
1916
			captiveportal_write_db($cpdb, true);
1917
			unlock($cpdblck);
1918

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

    
1929
	if ($writecfg == true)
1930
		write_config();
1931

    
1932
	/* redirect user to desired destination */
1933
	if (!empty($attributes['url_redirection']))
1934
		$my_redirurl = $attributes['url_redirection'];
1935
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
1936
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
1937
	else
1938
		$my_redirurl = $redirurl;
1939

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

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

    
1961
		if (isset($attributes['reply_message']))
1962
			$message = $attributes['reply_message'];
1963
		else
1964
			$message = 0;
1965

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

    
1968
	} else {
1969
		header("Location: " . $my_redirurl);
1970
	}
1971

    
1972
	return $sessionid;
1973
}
1974

    
1975

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

    
1985
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
1986
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
1987
	else
1988
		return false;
1989

    
1990
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
1991
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
1992
	else
1993
		return false;
1994

    
1995
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1996
		return false;
1997

    
1998
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
1999

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

    
2006
	$currenttime = time();
2007
	$found = false;
2008
	foreach ($usedmacs as $key => $usedmac) {
2009
		$usedmac = explode(",", $usedmac);
2010

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

    
2021
					return false;
2022
				} else {
2023
					$usedmac[2] -= 1;
2024
					$usedmacs[$key] = implode(",", $usedmac);
2025
				}
2026

    
2027
				$found = true;
2028
			} else
2029
				unset($usedmacs[$key]);
2030

    
2031
			break;
2032
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
2033
				unset($usedmacs[$key]);
2034
	}
2035

    
2036
	if (!$found) {
2037
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2038
		$usedmacs[] = implode(",", $usedmac);
2039
	}
2040

    
2041
	captiveportal_write_usedmacs_db($usedmacs);
2042
	return true;
2043
}
2044

    
2045
function captiveportal_read_usedmacs_db() {
2046
	global $g, $cpzone;
2047

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

    
2056
	unlock($cpumaclck);
2057
	return $usedmacs;
2058
}
2059

    
2060
function captiveportal_write_usedmacs_db($usedmacs) {
2061
	global $g, $cpzone;
2062

    
2063
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2064
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2065
	unlock($cpumaclck);
2066
}
2067

    
2068
?>
(8-8/65)