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 ($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 ($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 ($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
	if ($enBwup && $enBwdown)
945
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
946
	else
947
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
948

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

    
963
	return $rules;
964
}
965

    
966
function captiveportal_passthrumac_configure($lock = false) {
967
	global $config, $g, $cpzone;
968

    
969
	$rules = "";
970

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

    
977
		}
978
	}
979

    
980
	return $rules;
981
}
982

    
983
function captiveportal_passthrumac_findbyname($username) {
984
	global $config, $cpzone;
985

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

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

    
1001
	/* This function can deal with hostname or ipaddress */
1002
	if($ipent['ip']) 	
1003
		$ipaddress = $ipent['ip'];
1004

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

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

    
1022
	if (intval($enBwup) > 0 or intval($enBwdown) > 0)
1023
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
1024
	else
1025
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
1026

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

    
1077
	return $rules;
1078
}
1079

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

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

    
1102
function captiveportal_allowedhostname_configure() {
1103
	global $config, $g, $cpzone;
1104

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

    
1114
function captiveportal_allowedip_configure() {
1115
	global $config, $g, $cpzone;
1116

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

    
1123
	return $rules;
1124
}
1125

    
1126
/* get last activity timestamp given client IP address */
1127
function captiveportal_get_last_activity($ip) {
1128
	global $cpzone;
1129

    
1130
	$ipfwoutput = "";
1131

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

    
1141
	return 0;
1142
}
1143

    
1144
function captiveportal_init_radius_servers() {
1145
	global $config, $g, $cpzone;
1146

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

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

    
1176
		$radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
1177
		$radiuskey2 = ($config['captiveportal'][$cpzone]['radiuskey2']) ? $config['captiveportal'][$cpzone]['radiuskey2'] : null;
1178
		$radiuskey3 = ($config['captiveportal']['radiuskey3']) ? $config['captiveportal']['radiuskey3'] : null;
1179
		$radiuskey4 = ($config['captiveportal']['radiuskey4']) ? $config['captiveportal']['radiuskey4'] : null;
1180

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

    
1198
		fclose($fd);
1199
		unlock($cprdsrvlck);
1200
	}
1201
}
1202

    
1203
/* read RADIUS servers into array */
1204
function captiveportal_get_radius_servers() {
1205
	global $g, $cpzone;
1206

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

    
1234
	unlock($cprdsrvlck);
1235
	return false;
1236
}
1237

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

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

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

    
1264
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1265

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

    
1274
	$radiusservers = captiveportal_get_radius_servers();
1275

    
1276
	if (is_null($radiusctx))
1277
		$radiusctx = 'first';
1278

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

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

    
1297
	return $auth_list;
1298
}
1299

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

    
1304
	$cpdb = array();
1305

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1462
function captiveportal_get_ipfw_passthru_ruleno($value) {
1463
	global $config, $g, $cpzone;
1464

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

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

    
1480
	unlock($cpruleslck);
1481
	return NULL;
1482
}
1483

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

    
1495
function getVolume($ip) {
1496
	global $cpzone;
1497

    
1498
	$volume = array();
1499

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

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

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

    
1523
	return $volume;
1524
}
1525

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

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

    
1547
function getNasIP()
1548
{
1549
	global $config, $cpzone;
1550

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

    
1563
	return $nasIp;
1564
}
1565

    
1566
function portal_ip_from_client_ip($cliip) {
1567
	global $config, $cpzone;
1568

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

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

    
1587
	return false;
1588
}
1589

    
1590
/* functions move from index.php */
1591

    
1592
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
1593
	global $g, $config, $cpzone;
1594

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

    
1604
	$cpcfg = $config['captiveportal'][$cpzone];
1605

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

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

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

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

    
1644
    echo $htmltext;
1645
}
1646

    
1647
function portal_mac_radius($clientmac,$clientip) {
1648
    global $config, $cpzone;
1649

    
1650
    $radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
1651

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

    
1660
    return FALSE;
1661
}
1662

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

    
1665
	global $redirurl, $g, $config, $type, $passthrumac, $_POST, $cpzone;
1666

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

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

    
1678
	// Ensure we create an array if we are missing attributes
1679
	if (!is_array($attributes))
1680
		$attributes = array();
1681

    
1682
	$radiusservers = captiveportal_get_radius_servers();
1683

    
1684
	/* Do not allow concurrent login execution. */
1685
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1686

    
1687
	unset($sessionid);
1688

    
1689
	/* read in client database */
1690
	$cpdb = captiveportal_read_db(true);
1691

    
1692
	if ($attributes['voucher'])
1693
		$remaining_time = $attributes['session_timeout'];
1694

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

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

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

    
1771
	if ($attributes['voucher'] && $remaining_time <= 0)
1772
		return 0;       // voucher already used and no time left
1773

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

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

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

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

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

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

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

    
1841
			if ($attributes['voucher'])
1842
				$attributes['session_timeout'] = $remaining_time;
1843

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

    
1849
			/* rewrite information to database */
1850
			captiveportal_write_db($cpdb, true);
1851
			unlock($cpdblck);
1852

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

    
1863
	if ($writecfg == true)
1864
		write_config();
1865

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

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

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

    
1889
		if (isset($attributes['reply_message']))
1890
			$message = $attributes['reply_message'];
1891
		else
1892
			$message = 0;
1893

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

    
1896
	} else {
1897
		header("Location: " . $my_redirurl);
1898
	}
1899

    
1900
	return $sessionid;
1901
}
1902

    
1903

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

    
1913
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count']))
1914
		$freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
1915
	else
1916
		return false;
1917

    
1918
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout']))
1919
		$resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
1920
	else
1921
		return false;
1922

    
1923
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac)
1924
		return false;
1925

    
1926
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
1927

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

    
1934
	$currenttime = time();
1935
	$found = false;
1936
	foreach ($usedmacs as $key => $usedmac) {
1937
		$usedmac = explode(",", $usedmac);
1938

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

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

    
1955
				$found = true;
1956
			} else
1957
				unset($usedmacs[$key]);
1958

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

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

    
1969
	captiveportal_write_usedmacs_db($usedmacs);
1970
	return true;
1971
}
1972

    
1973
function captiveportal_read_usedmacs_db() {
1974
	global $g, $cpzone;
1975

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

    
1984
	unlock($cpumaclck);
1985
	return $usedmacs;
1986
}
1987

    
1988
function captiveportal_write_usedmacs_db($usedmacs) {
1989
	global $g, $cpzone;
1990

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

    
1996
?>
(7-7/62)