Project

General

Profile

Download (65.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
		system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf",
461
			$cert, $key, $cacert, "lighty-{$cpzone}-CaptivePortal-SSL.pid", $cpcfg['zoneid'] + 1, "/usr/local/captiveportal",
462
			"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, $cpzone);
463
	}
464

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

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

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

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

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

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

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

    
526
	/* init dummynet/ipfw rules number database */
527
	captiveportal_init_ipfw_ruleno();
528

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

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

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

    
547
# PPP Over Ethernet Discovery Stage
548
add 65304 set 1 pass layer2 mac-type 0x8863
549
# PPP Over Ethernet Session Stage
550
add 65305 set 1 pass layer2 mac-type 0x8864
551
# Allow WPA
552
add 65306 set 1 pass layer2 mac-type 0x888e
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
	$cprules .= <<<EOD
611

    
612
# redirect non-authenticated clients to captive portal
613
add 65531 set 1 fwd 127.0.0.1,{$config['captiveportal'][$cpzone]['zoneid']} tcp from any to any in
614
# let the responses from the captive portal web server back out
615
add 65532 set 1 pass tcp from any to any out
616
# block everything else
617
add 65533 set 1 deny all from any to any
618
# pass everything else on layer2
619
add 65534 set 1 pass all from any to any layer2
620

    
621
EOD;
622

    
623
	/* generate passthru mac database */
624
	$cprules .= captiveportal_passthrumac_configure(true);
625
	$cprules .= "\n";
626

    
627
	/* allowed ipfw rules to make allowed ip work */
628
	$cprules .= captiveportal_allowedip_configure();
629

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

    
649
	file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
650
	captiveportal_ipfw_set_context($cpzone);
651
	mwexec("/sbin/ipfw -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
652
	//@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
653

    
654
	if ($reinit == false)
655
		unlock($captiveportallck);
656

    
657
	/* activate ipfw(4) so CP can work */
658
	mwexec("/sbin/sysctl net.link.ether.ipfw=1");
659

    
660
	return $cprules;
661
}
662

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

    
671
	if (empty($cpzone))
672
		return;
673

    
674
	/* check for expired entries */
675
	if (empty($config['captiveportal'][$cpzone]['timeout']) ||
676
	!is_numeric($config['captiveportal'][$cpzone]['timeout']))
677
		$timeout = 0;
678
	else
679
		$timeout = $config['captiveportal'][$cpzone]['timeout'] * 60;
680

    
681
	if (empty($config['captiveportal'][$cpzone]['idletimeout']) ||
682
	!is_numeric($config['captiveportal'][$cpzone]['idletimeout']))
683
		$idletimeout = 0;
684
	else
685
		$idletimeout = $config['captiveportal'][$cpzone]['idletimeout'] * 60;
686

    
687
	if (!$timeout && !$idletimeout && !isset($config['captiveportal'][$cpzone]['reauthenticate']) && 
688
	!isset($config['captiveportal'][$cpzone]['radiussession_timeout']) && !isset($config['voucher'][$cpzone]['enable']))
689
		return;
690

    
691
	$radiussrvs = captiveportal_get_radius_servers();
692

    
693
	/* read database */
694
	$cpdb = captiveportal_read_db();
695

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

    
711
		$timedout = false;
712
		$term_cause = 1;
713
		if (empty($cpentry[10]))
714
			$cpentry[10] = 'first';
715
		$radiusservers = $radiussrvs[$cpentry[10]];
716

    
717
		/* hard timeout? */
718
		if ($timeout) {
719
			if (($pruning_time - $cpentry[0]) >= $timeout) {
720
				$timedout = true;
721
				$term_cause = 5; // Session-Timeout
722
			}
723
		}
724

    
725
		/* Session-Terminate-Time */
726
		if (!$timedout && !empty($cpentry[9])) {
727
			if ($pruning_time >= $cpentry[9]) {
728
				$timedout = true;
729
				$term_cause = 5; // Session-Timeout
730
			}
731
		}
732

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

    
749
		/* if vouchers are configured, activate session timeouts */
750
		if (!$timedout && isset($config['voucher'][$cpzone]['enable']) && !empty($cpentry[7])) {
751
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
752
				$timedout = true;
753
				$term_cause = 5; // Session-Timeout
754
				$voucher_needs_sync = true;
755
			}
756
		}
757

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

    
766
		if ($timedout) {
767
			captiveportal_disconnect($cpentry, $radiusservers,$term_cause,$stop_time);
768
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
769
			$unsetindexes[] = $cpentry[5];
770
		}
771

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

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

    
824
	if ($voucher_needs_sync == true)
825
		/* Triger a sync of the vouchers on config */
826
		send_event("service sync vouchers");
827

    
828
	/* write database */
829
	if (!empty($unsetindexes))
830
		captiveportal_write_db($cpdb, false, $unsetindexes);
831
}
832

    
833
/* remove a single client according to the DB entry */
834
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
835
	global $g, $config, $cpzone;
836

    
837
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
838

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

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

    
873
	/* Release the ruleno so it can be reallocated to new clients. */
874
	captiveportal_free_ipfw_ruleno($dbent[1]);
875

    
876
	// XMLRPC Call over to the master Voucher node
877
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
878
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
879
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
880
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
881
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
882
		$remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time);
883
	}
884

    
885
}
886

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

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

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

    
897
	/* find entry */
898
	if (isset($cpdb[$sessionid])) {
899
		$cpentry = $cpdb[$sessionid];
900
		/* write database */
901
		$unsetindex[] = $sessionid;
902
		captiveportal_write_db($cpdb, false, $unsetindex);
903
		if (empty($cpentry[10]))
904
			$cpentry[10] = 'first';
905
		captiveportal_disconnect($cpentry, $radiusservers[$cpentry[10]], $term_cause);
906
		captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
907
	}		
908
}
909

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

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

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

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

    
944
	$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
945

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

    
960
	return $rules;
961
}
962

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

    
966
	$rules = "";
967

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

    
974
		}
975
	}
976

    
977
	return $rules;
978
}
979

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

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

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

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

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

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

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

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

    
1074
	return $rules;
1075
}
1076

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

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

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

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

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

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

    
1120
	return $rules;
1121
}
1122

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

    
1127
	$ipfwoutput = "";
1128

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

    
1138
	return 0;
1139
}
1140

    
1141
function captiveportal_init_radius_servers() {
1142
	global $config, $g, $cpzone;
1143

    
1144
	/* generate radius server database */
1145
	if ($config['captiveportal'][$cpzone]['radiusip'] && (!isset($config['captiveportal'][$cpzone]['auth_method']) ||
1146
		($config['captiveportal'][$cpzone]['auth_method'] == "radius"))) {
1147
		$radiusip = $config['captiveportal'][$cpzone]['radiusip'];
1148
		$radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
1149
		$radiusip3 = ($config['captiveportal']['radiusip3']) ? $config['captiveportal']['radiusip3'] : null;
1150
		$radiusip4 = ($config['captiveportal']['radiusip4']) ? $config['captiveportal']['radiusip4'] : null;
1151

    
1152
		if ($config['captiveportal'][$cpzone]['radiusport'])
1153
			$radiusport = $config['captiveportal'][$cpzone]['radiusport'];
1154
		else
1155
			$radiusport = 1812;
1156
		if ($config['captiveportal'][$cpzone]['radiusacctport'])
1157
			$radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
1158
		else
1159
			$radiusacctport = 1813;
1160
		if ($config['captiveportal'][$cpzone]['radiusport2'])
1161
			$radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
1162
		else
1163
			$radiusport2 = 1812;
1164
		if ($config['captiveportal']['radiusport3'])
1165
			$radiusport3 = $config['captiveportal']['radiusport3'];
1166
		else
1167
			$radiusport3 = 1812;
1168
		if ($config['captiveportal']['radiusport4'])
1169
			$radiusport4 = $config['captiveportal']['radiusport4'];
1170
		else
1171
			$radiusport4 = 1812;
1172

    
1173
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1174
		$radiuskey2 = ($config['captiveportal'][$cpzone]['radiuskey2']) ? $config['captiveportal'][$cpzone]['radiuskey2'] : null;
1175
		$radiuskey3 = ($config['captiveportal']['radiuskey3']) ? $config['captiveportal']['radiuskey3'] : null;
1176
		$radiuskey4 = ($config['captiveportal']['radiuskey4']) ? $config['captiveportal']['radiuskey4'] : null;
1177

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

    
1195
		fclose($fd);
1196
		unlock($cprdsrvlck);
1197
	}
1198
}
1199

    
1200
/* read RADIUS servers into array */
1201
function captiveportal_get_radius_servers() {
1202
	global $g, $cpzone;
1203

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

    
1231
	unlock($cprdsrvlck);
1232
	return false;
1233
}
1234

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

    
1248
/* log simple messages to syslog */
1249
function captiveportal_syslog($message) {
1250
	define_syslog_variables();
1251
	$message = trim($message);
1252
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1253
	// Log it
1254
	syslog(LOG_INFO, $message);
1255
	closelog();
1256
}
1257

    
1258
function radius($username,$password,$clientip,$clientmac,$type, $radiusctx = null) {
1259
	global $g, $config;
1260

    
1261
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1262

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

    
1271
	$radiusservers = captiveportal_get_radius_servers();
1272

    
1273
	if (is_null($radiusctx))
1274
		$radiusctx = 'first';
1275

    
1276
	$auth_list = RADIUS_AUTHENTICATION($username,
1277
		$password,
1278
		$radiusservers[$radiusctx],
1279
		$clientip,
1280
		$clientmac,
1281
		$ruleno);
1282

    
1283
	if ($auth_list['auth_val'] == 2) {
1284
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1285
		$sessionid = portal_allow($clientip,
1286
			$clientmac,
1287
			$username,
1288
			$password,
1289
			$auth_list,
1290
			$ruleno,
1291
			$radiusctx);
1292
	}
1293

    
1294
	return $auth_list;
1295
}
1296

    
1297
/* read captive portal DB into array */
1298
function captiveportal_read_db($locked = false, $index = 5 /* sessionid by default */) {
1299
	global $g, $cpzone;
1300

    
1301
	$cpdb = array();
1302

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

    
1322
/* write captive portal DB */
1323
function captiveportal_write_db($cpdb, $locked = false, $remove = false) {
1324
	global $g, $cpzone;
1325

    
1326
	if ($locked == false)
1327
		$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1328

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

    
1352
function captiveportal_write_elements() {
1353
	global $g, $config, $cpzone;
1354
	
1355
	$cpcfg = $config['captiveportal'][$cpzone];
1356

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

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

    
1390
function captiveportal_init_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
1391
	global $g, $cpzone;
1392

    
1393
	@unlink("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
1394
	$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1395
	file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
1396
}
1397

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

    
1406
	$cpcfg = $config['captiveportal'][$cpzone];
1407
	if(!isset($cpcfg['enable']))
1408
		return NULL;
1409

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

    
1441
function captiveportal_free_ipfw_ruleno($ruleno, $usedbw = false) {
1442
	global $config, $g, $cpzone;
1443

    
1444
	$cpcfg = $config['captiveportal'][$cpzone];
1445
	if(!isset($cpcfg['enable']))
1446
		return NULL;
1447

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

    
1459
function captiveportal_get_ipfw_passthru_ruleno($value) {
1460
	global $config, $g, $cpzone;
1461

    
1462
	$cpcfg = $config['captiveportal'][$cpzone];
1463
	if(!isset($cpcfg['enable']))
1464
		return NULL;
1465

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

    
1477
	unlock($cpruleslck);
1478
	return NULL;
1479
}
1480

    
1481
/**
1482
 * This function will calculate the traffic produced by a client
1483
 * based on its firewall rule
1484
 *
1485
 * Point of view: NAS
1486
 *
1487
 * Input means: from the client
1488
 * Output means: to the client
1489
 *
1490
 */
1491

    
1492
function getVolume($ip) {
1493
	global $cpzone;
1494

    
1495
	$volume = array();
1496

    
1497
	// Initialize vars properly, since we don't want NULL vars
1498
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1499

    
1500
	// Ingress
1501
	$ipfwin = "";
1502
	$ipfwout = "";
1503
	$matchesin = "";
1504
	$matchesout = "";
1505
	captiveportal_ipfw_set_context($cpzone);
1506
	exec("/sbin/ipfw table 1 entrystats {$ip}", $ipfwin);
1507
	if ($ipfwin[0]) {
1508
		$ipfwin = split(" ", $ipfwin[0]);
1509
		$volume['input_pkts'] = $ipfwin[2];
1510
		$volume['input_bytes'] = $ipfwin[3];
1511
	}
1512

    
1513
	exec("/sbin/ipfw table 2 entrystats {$ip}", $ipfwout);
1514
	if ($ipfwout[0]) {
1515
		$ipfwout = split(" ", $ipfwout[0]);
1516
		$volume['output_pkts'] = $ipfwout[2];
1517
		$volume['output_bytes'] = $ipfwout[3];
1518
	}
1519

    
1520
	return $volume;
1521
}
1522

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

    
1537
/**
1538
 * Get the NAS-IP-Address based on the current wan address
1539
 *
1540
 * Use functions in interfaces.inc to find this out
1541
 *
1542
 */
1543

    
1544
function getNasIP()
1545
{
1546
	global $config, $cpzone;
1547

    
1548
	if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
1549
			$nasIp = get_interface_ip();
1550
	} else {
1551
		if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute']))
1552
			$nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
1553
		else
1554
			$nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
1555
	}
1556
		
1557
	if(!is_ipaddr($nasIp))
1558
		$nasIp = "0.0.0.0";
1559

    
1560
	return $nasIp;
1561
}
1562

    
1563
function portal_ip_from_client_ip($cliip) {
1564
	global $config, $cpzone;
1565

    
1566
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1567
	foreach ($interfaces as $cpif) {
1568
		$ip = get_interface_ip($cpif);
1569
		$sn = get_interface_subnet($cpif);
1570
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1571
			return $ip;
1572
	}
1573

    
1574
	// doesn't match up to any particular interface
1575
	// so let's set the portal IP to what PHP says 
1576
	// the server IP issuing the request is. 
1577
	// allows same behavior as 1.2.x where IP isn't 
1578
	// in the subnet of any CP interface (static routes, etc.)
1579
	// rather than forcing to DNS hostname resolution
1580
	$ip = $_SERVER['SERVER_ADDR'];
1581
	if (is_ipaddr($ip))
1582
		return $ip;
1583

    
1584
	return false;
1585
}
1586

    
1587
/* functions move from index.php */
1588

    
1589
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1590
	global $g, $config, $cpzone;
1591

    
1592
	/* Get captive portal layout */
1593
	if ($type == "redir") {
1594
		header("Location: {$redirurl}");
1595
		return;
1596
	} else if ($type == "login")
1597
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1598
	else
1599
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1600

    
1601
	$cpcfg = $config['captiveportal'][$cpzone];
1602

    
1603
	/* substitute the PORTAL_REDIRURL variable */
1604
	if ($config['captiveportal'][$cpzone]['preauthurl']) {
1605
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$config['captiveportal'][$cpzone]['preauthurl']}", $htmltext);
1606
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$config['captiveportal'][$cpzone]['preauthurl']}", $htmltext);
1607
	}
1608

    
1609
	/* substitute other variables */
1610
	if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
1611
		$httpsport  = $cpcfg['zoneid'] + 1;
1612
		$htmltext = str_replace("\$PORTAL_ACTION\$", "https://{$config['captiveportal'][$cpzone]['httpsname']}:{$httpsport}/", $htmltext);
1613
		$htmltext = str_replace("#PORTAL_ACTION#", "https://{$config['captiveportal'][$cpzone]['httpsname']}:{$httpsport}/", $htmltext);
1614
	} else {
1615
		$ifip = portal_ip_from_client_ip($clientip);
1616
		if (!$ifip)
1617
			$ourhostname = $config['system']['hostname'] . ":{$cpcfg['zoneid']}";
1618
		else
1619
			$ourhostname = "{$ifip}:{$cpcfg['zoneid']}";
1620
		$htmltext = str_replace("\$PORTAL_ACTION\$", "http://{$ourhostname}/", $htmltext);
1621
		$htmltext = str_replace("#PORTAL_ACTION#", "http://{$ourhostname}/", $htmltext);
1622
	}
1623

    
1624
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1625
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1626
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1627
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1628
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1629

    
1630
	// Special handling case for captive portal master page so that it can be ran 
1631
	// through the PHP interpreter using the include method above.  We convert the
1632
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1633
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1634
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1635
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1636
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1637
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1638
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1639
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1640

    
1641
    echo $htmltext;
1642
}
1643

    
1644
function portal_mac_radius($clientmac,$clientip) {
1645
    global $config, $cpzone;
1646

    
1647
    $radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1648

    
1649
    /* authentication against the radius server */
1650
    $username = mac_format($clientmac);
1651
    $auth_list = radius($username,$radmac_secret,$clientip,$clientmac,"MACHINE LOGIN");
1652
    if ($auth_list['auth_val'] == 2)
1653
        return TRUE;
1654
    if (!empty($auth_list['url_redirection']))
1655
	portal_reply_page($auth_list['url_redirection'], "redir");
1656

    
1657
    return FALSE;
1658
}
1659

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

    
1662
	global $redirurl, $g, $config, $type, $passthrumac, $_POST, $cpzone;
1663

    
1664
	/* See if a ruleno is passed, if not start sessions because this means there isn't one atm */
1665
	if ($ruleno == null)
1666
		$ruleno = captiveportal_get_next_ipfw_ruleno();
1667

    
1668
	/* if the pool is empty, return appropriate message and exit */
1669
	if (is_null($ruleno)) {
1670
		portal_reply_page($redirurl, "error", "System reached maximum login capacity");
1671
		log_error("WARNING!  Captive portal has reached maximum login capacity");
1672
		exit;
1673
	}
1674

    
1675
	// Ensure we create an array if we are missing attributes
1676
	if (!is_array($attributes))
1677
		$attributes = array();
1678

    
1679
	$radiusservers = captiveportal_get_radius_servers();
1680

    
1681
	/* Do not allow concurrent login execution. */
1682
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1683

    
1684
	unset($sessionid);
1685

    
1686
	/* read in client database */
1687
	$cpdb = captiveportal_read_db(true);
1688

    
1689
	if ($attributes['voucher'])
1690
		$remaining_time = $attributes['session_timeout'];
1691

    
1692
	$writecfg = false;
1693
	/* Find an existing session */
1694
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
1695
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1696
			$mac = captiveportal_passthrumac_findbyname($username);
1697
			if (!empty($mac)) {
1698
				if ($_POST['replacemacpassthru']) {
1699
					foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
1700
						if ($macent['mac'] == $mac['mac']) {
1701
							$macrules = "";
1702
							$ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
1703
                                			if ($ruleno) {
1704
								captiveportal_free_ipfw_ruleno($ruleno, true);
1705
                                        			$macrules .= "delete {$ruleno}\n";
1706
								++$ruleno;
1707
                                        			$macrules .= "delete {$ruleno}\n";
1708
                                			}
1709
							unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1710
							$mac['mac'] = $clientmac;
1711
							$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1712
							$macrules .= captiveportal_passthrumac_configure_entry($mac);
1713
							file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1714
							captiveportal_ipfw_set_context($cpzone);
1715
							mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1716
							$writecfg = true;
1717
							$sessionid = true;
1718
							break;
1719
						}
1720
					}
1721
                                } else {
1722
					portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
1723
						$clientmac, $clientip, $username, $password);
1724
					exit;
1725
				}
1726
			}
1727
		}
1728
	}
1729

    
1730
	/* Snapshot the timestamp */
1731
	$allow_time = time();
1732
	if (is_null($radiusctx))
1733
		$radiusctx = 'first';
1734
	foreach ($cpdb as $sid => $cpentry) {
1735
		if (empty($cpentry[10]))
1736
			$cpentry[10] = 'first';
1737
		/* on the same ip */
1738
		if($cpentry[2] == $clientip) {
1739
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - REUSING OLD SESSION");
1740
			$sessionid = $sid;
1741
			break;
1742
		}
1743
		elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
1744
			// user logged in with an active voucher. Check for how long and calculate 
1745
			// how much time we can give him (voucher credit - used time)
1746
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
1747
			if ($remaining_time < 0)    // just in case. 
1748
				$remaining_time = 0;
1749

    
1750
			/* This user was already logged in so we disconnect the old one */
1751
			captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1752
			captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1753
			unset($cpdb[$sid]);
1754
			break;
1755
		}
1756
		elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
1757
			/* on the same username */
1758
			if (strcasecmp($cpentry[4], $username) == 0) {
1759
				/* This user was already logged in so we disconnect the old one */
1760
				captiveportal_disconnect($cpentry,$radiusservers[$cpentry[10]],13);
1761
				captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"CONCURRENT LOGIN - TERMINATING OLD SESSION");
1762
				unset($cpdb[$sid]);
1763
				break;
1764
			}
1765
		}
1766
	}
1767

    
1768
	if ($attributes['voucher'] && $remaining_time <= 0)
1769
		return 0;       // voucher already used and no time left
1770

    
1771
	if (!isset($sessionid)) {
1772
		/* generate unique session ID */
1773
		$tod = gettimeofday();
1774
		$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
1775

    
1776
		/* Add rules for traffic shaping
1777
		 * We don't need to add extra rules since traffic will pass due to the following kernel option
1778
		 * net.inet.ip.fw.one_pass: 1
1779
		 */
1780
		$peruserbw = isset($config['captiveportal'][$cpzone]['peruserbw']);
1781

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

    
1785
		if ($passthrumac) {
1786
			$mac = array();
1787
			$mac['mac'] = $clientmac;
1788
			if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername']))
1789
				$mac['username'] = $username;
1790
			$mac['descr'] =  "Auto added pass-through MAC for user {$username}";
1791
			if (!empty($bw_up))
1792
				$mac['bw_up'] = $bw_up;
1793
			if (!empty($bw_down))
1794
				$mac['bw_down'] = $bw_down;
1795
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac']))
1796
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
1797
			$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
1798
			unlock($cpdblck);
1799
			$macrules = captiveportal_passthrumac_configure_entry($mac);
1800
			file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
1801
			captiveportal_ipfw_set_context($cpzone);
1802
			mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
1803
			$writecfg = true;
1804
		} else {
1805
			captiveportal_ipfw_set_context($cpzone);
1806

    
1807
			if ($peruserbw && !empty($bw_up) && is_numeric($bw_up)) {
1808
				$bw_up_pipeno = $ruleno + 20000;
1809
				//$bw_up /= 1000; // Scale to Kbit/s
1810
				mwexec("/sbin/ipfw pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100");
1811

    
1812
				if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1813
					mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac} {$bw_up_pipeno}");
1814
				else
1815
					mwexec("/sbin/ipfw table 1 add {$clientip} {$bw_up_pipeno}");
1816
			} else {
1817
				if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1818
					mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac}");
1819
				else
1820
					mwexec("/sbin/ipfw table 1 add {$clientip}");
1821
			}
1822
			if ($peruserbw && !empty($bw_down) && is_numeric($bw_down)) {
1823
				$bw_down_pipeno = $ruleno + 20001;
1824
				//$bw_down /= 1000; // Scale to Kbit/s
1825
				mwexec("/sbin/ipfw pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100");
1826

    
1827
				if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1828
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac} {$bw_down_pipeno}");
1829
				else
1830
					mwexec("/sbin/ipfw table 2 add {$clientip} {$bw_down_pipeno}");
1831
			} else {
1832
				if (!isset($config['captiveportal'][$cpzone]['nomacfilter']))
1833
					mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac}");
1834
				else
1835
					mwexec("/sbin/ipfw table 2 add {$clientip}");
1836
			}
1837

    
1838
			if ($attributes['voucher'])
1839
				$attributes['session_timeout'] = $remaining_time;
1840

    
1841
			/* encode password in Base64 just in case it contains commas */
1842
			$bpassword = base64_encode($password);
1843
			$cpdb[] = array($allow_time, $ruleno, $clientip, $clientmac, $username, $sessionid, $bpassword,
1844
				$attributes['session_timeout'], $attributes['idle_timeout'], $attributes['session_terminate_time'], $radiusctx);
1845

    
1846
			/* rewrite information to database */
1847
			captiveportal_write_db($cpdb, true);
1848
			unlock($cpdblck);
1849

    
1850
			if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
1851
				$acct_val = RADIUS_ACCOUNTING_START($ruleno,
1852
                                		$username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
1853
				if ($acct_val == 1)
1854
					captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
1855
			}
1856
		}
1857
	} else
1858
		unlock($cpdblck);
1859

    
1860
	if ($writecfg == true)
1861
		write_config();
1862

    
1863
	/* redirect user to desired destination */
1864
	if (!empty($attributes['url_redirection']))
1865
		$my_redirurl = $attributes['url_redirection'];
1866
	else if (!empty($config['captiveportal'][$cpzone]['redirurl']))
1867
		$my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
1868
	else
1869
		$my_redirurl = $redirurl;
1870

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

    
1873
		if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
1874
			$httpsport = $config['captiveportal'][$cpzone]['zoneid'] + 1;
1875
			$logouturl = "https://{$config['captiveportal']['httpsname']}:{$httpsport}/";
1876
		} else {
1877
			$ifip = portal_ip_from_client_ip($clientip);
1878
			$httpport = $config['captiveportal'][$cpzone]['zoneid'];
1879
			if (!$ifip)
1880
				$ourhostname = $config['system']['hostname'] . ":{$httpport}";
1881
			else
1882
				$ourhostname = "{$ifip}:{$httpport}";
1883
			$logouturl = "http://{$ourhostname}/";
1884
		}
1885

    
1886
		if (isset($attributes['reply_message']))
1887
			$message = $attributes['reply_message'];
1888
		else
1889
			$message = 0;
1890

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

    
1893
	} else {
1894
		header("Location: " . $my_redirurl);
1895
	}
1896

    
1897
	return $sessionid;
1898
}
1899

    
1900

    
1901
/*
1902
 * Used for when pass-through credits are enabled.
1903
 * Returns true when there was at least one free login to deduct for the MAC.
1904
 * Expired entries are removed as they are seen.
1905
 * Active entries are updated according to the configuration.
1906
 */
1907
function portal_consume_passthrough_credit($clientmac) {
1908
	global $config, $cpzone;
1909

    
1910
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
1911
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
1912
	else
1913
		return false;
1914

    
1915
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
1916
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
1917
	else
1918
		return false;
1919

    
1920
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1921
		return false;
1922

    
1923
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
1924

    
1925
	/*
1926
	 * Read database of used MACs.  Lines are a comma-separated list
1927
	 * of the time, MAC, then the count of pass-through credits remaining.
1928
	 */
1929
	$usedmacs = captiveportal_read_usedmacs_db();
1930

    
1931
	$currenttime = time();
1932
	$found = false;
1933
	foreach ($usedmacs as $key => $usedmac) {
1934
		$usedmac = explode(",", $usedmac);
1935

    
1936
		if ($usedmac[1] == $clientmac) {
1937
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
1938
				if ($usedmac[2] < 1) {
1939
					if ($updatetimeouts) {
1940
						$usedmac[0] = $currenttime;
1941
						unset($usedmacs[$key]);
1942
						$usedmacs[] = implode(",", $usedmac);
1943
						captiveportal_write_usedmacs_db($usedmacs);
1944
					}
1945

    
1946
					return false;
1947
				} else {
1948
					$usedmac[2] -= 1;
1949
					$usedmacs[$key] = implode(",", $usedmac);
1950
				}
1951

    
1952
				$found = true;
1953
			} else
1954
				unset($usedmacs[$key]);
1955

    
1956
			break;
1957
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime)
1958
				unset($usedmacs[$key]);
1959
	}
1960

    
1961
	if (!$found) {
1962
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
1963
		$usedmacs[] = implode(",", $usedmac);
1964
	}
1965

    
1966
	captiveportal_write_usedmacs_db($usedmacs);
1967
	return true;
1968
}
1969

    
1970
function captiveportal_read_usedmacs_db() {
1971
	global $g, $cpzone;
1972

    
1973
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
1974
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
1975
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1976
		if (!$usedmacs)
1977
			$usedmacs = array();
1978
	} else
1979
		$usedmacs = array();
1980

    
1981
	unlock($cpumaclck);
1982
	return $usedmacs;
1983
}
1984

    
1985
function captiveportal_write_usedmacs_db($usedmacs) {
1986
	global $g, $cpzone;
1987

    
1988
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
1989
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
1990
	unlock($cpumaclck);
1991
}
1992

    
1993
?>
(7-7/63)