Project

General

Profile

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

    
121
EOD;
122
		return $htmltext;
123
	}
124

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

    
200
EOD;
201

    
202
	return $htmltext;
203
}
204

    
205
function captiveportal_configure() {
206
	global $config, $g;
207

    
208
	$captiveportallck = lock('captiveportal', LOCK_EX);
209
	
210
	if (isset($config['captiveportal']['enable'])) {
211

    
212
		if ($g['booting'])
213
			echo "Starting captive portal... ";
214

    
215
		/* kill any running mini_httpd */
216
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
217
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal-SSL.pid");
218

    
219
		/* remove old information */
220
		unlink_if_exists("{$g['vardb_path']}/captiveportal.db");
221
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
222
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip.db");
223
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius.db");
224

    
225
		/* setup new database in case someone tries to access the status -> captive portal page */
226
		touch("{$g['vardb_path']}/captiveportal.db");
227

    
228
		/* kill any running minicron */
229
		killbypid("{$g['varrun_path']}/cp_prunedb.pid");
230

    
231
		/* init ipfw rules */
232
		captiveportal_init_rules(true);
233

    
234
		/* stop accounting on all clients */
235
		captiveportal_radius_stop_all();
236

    
237
		/* initialize minicron interval value */
238
		$croninterval = $config['captiveportal']['croninterval'] ? $config['captiveportal']['croninterval'] : 60;
239

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

    
244
		/* write portal page */
245
		if ($config['captiveportal']['page']['htmltext'])
246
			$htmltext = base64_decode($config['captiveportal']['page']['htmltext']);
247
		else {
248
			/* example/template page */
249
			$htmltext = get_default_captive_portal_html();
250
		}
251

    
252
		$fd = @fopen("{$g['varetc_path']}/captiveportal.html", "w");
253
		if ($fd) {
254
			// Special case handling.  Convert so that we can pass this page
255
			// through the PHP interpreter later without clobbering the vars.
256
			$htmltext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $htmltext);
257
			$htmltext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $htmltext);
258
			$htmltext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $htmltext);
259
			$htmltext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $htmltext);
260
			$htmltext = str_replace("\$ORIGINAL_PORTAL_IP\$", "#ORIGINAL_PORTAL_IP#", $htmltext);
261
			$htmltext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $htmltext);
262
			if($config['captiveportal']['preauthurl']) {
263
				$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$config['captiveportal']['preauthurl']}", $htmltext);
264
				$htmltext = str_replace("#PORTAL_REDIRURL#", "{$config['captiveportal']['preauthurl']}", $htmltext);
265
			}
266
			fwrite($fd, $htmltext);
267
			fclose($fd);
268
		}
269

    
270
		/* write error page */
271
		if ($config['captiveportal']['page']['errtext'])
272
			$errtext = base64_decode($config['captiveportal']['page']['errtext']);
273
		else {
274
			/* example page */
275
			$errtext = <<<EOD
276
<html> 
277
	<body> 
278
		<form method="post" action="\$PORTAL_ACTION\$"> 
279
		<input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$">
280
			<center>
281
				<table cellpadding="6" cellspacing="0" width="550" height="380" style="border:1px solid #000000">
282
					<tr height="10" bgcolor="#990000">
283
						<td style="border-bottom:1px solid #000000">
284
							<font color='white'>
285
								<b>
286
									{$g['product_name']} captive portal
287
								</b>
288
							</font>
289
						</td>
290
					</tr>
291
					<tr>
292
						<td>
293
							<div id="mainlevel">
294
								<center>
295
									<table width="100%" border="0" cellpadding="5" cellspacing="0">
296
										<tr>
297
											<td>
298
												<center>
299
													<div id="mainarea">
300
														<center>
301
															<table width="100%" border="0" cellpadding="5" cellspacing="5">
302
																<tr>
303
																	<td>
304
																		<div id="maindivarea">
305
																			<center>
306
																				<div id='statusbox'>
307
																					<font color='red' face='arial' size='+1'>
308
																						<b>
309
																							\$PORTAL_MESSAGE\$
310
																						</b>
311
																					</font>
312
																				</div>
313
																				<br/>
314
																				<div id='loginbox'>
315
																					<table>
316
																					   <tr><td colspan="2"><center>Welcome to the {$g['product_name']} Captive Portal!</td></tr>
317
																					   <tr><td>&nbsp;</td></tr>
318
																					   <tr><td align="right">Username:</td><td><input name="auth_user" type="text" style="border: 1px dashed;"></td></tr>
319
																					   <tr><td align="right">Password:</td><td><input name="auth_pass" type="password" style="border: 1px dashed;"></td></tr>
320
																					   <tr><td>&nbsp;</td></tr>
321
																					   <tr>
322
																						 <td colspan="2">
323
																							<center><input name="accept" type="submit" value="Continue"></center>
324
																						 </td>
325
																					   </tr>
326
																					</table>
327
																				</div>
328
																			</center>
329
																		</div>
330
																	</td>
331
																</tr>
332
															</table>
333
														</center>
334
													</div>
335
												</center>
336
											</td>
337
										</tr>
338
									</table>
339
								</center>
340
							</div>
341
						</td>
342
					</tr>
343
				</table>
344
			</center>
345
		</form>
346
	</body> 
347
</html>
348

    
349
EOD;
350
		}
351

    
352
		$fd = @fopen("{$g['varetc_path']}/captiveportal-error.html", "w");
353
		if ($fd) {
354
			// Special case handling.  Convert so that we can pass this page
355
			// through the PHP interpreter later without clobbering the vars.
356
			$errtext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $errtext);
357
			$errtext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $errtext);
358
			$errtext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $errtext);
359
			$errtext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $errtext);
360
			$errtext = str_replace("\$ORIGINAL_PORTAL_IP\$", "#ORIGINAL_PORTAL_IP#", $errtext);
361
			$errtext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $errtext);
362
			if($config['captiveportal']['preauthurl']) {
363
				$errtext = str_replace("\$PORTAL_REDIRURL\$", "{$config['captiveportal']['preauthurl']}", $errtext);
364
				$errtext = str_replace("#PORTAL_REDIRURL#", "{$config['captiveportal']['preauthurl']}", $errtext);
365
			}
366
			fwrite($fd, $errtext);
367
			fclose($fd);
368
		}
369

    
370
		/* write error page */
371
		if ($config['captiveportal']['page']['logouttext'])
372
			$logouttext = base64_decode($config['captiveportal']['page']['logouttext']);
373
		else {
374
			/* example page */
375
			$logouttext = <<<EOD
376
<HTML>
377
<HEAD><TITLE>Redirecting...</TITLE></HEAD>
378
<BODY>
379
<SPAN STYLE="font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">
380
<B>Redirecting to <A HREF="<?=\$my_redirurl;?>"><?=\$my_redirurl;?></A>...</B>
381
</SPAN>
382
<SCRIPT LANGUAGE="JavaScript">
383
<!--
384
LogoutWin = window.open('', 'Logout', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=256,height=64');
385
if (LogoutWin) {
386
	LogoutWin.document.write('<HTML>');
387
	LogoutWin.document.write('<HEAD><TITLE>Logout</TITLE></HEAD>') ;
388
	LogoutWin.document.write('<BODY BGCOLOR="#435370">');
389
	LogoutWin.document.write('<DIV ALIGN="center" STYLE="color: #ffffff; font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">') ;
390
	LogoutWin.document.write('<B>Click the button below to disconnect</B><P>');
391
	LogoutWin.document.write('<FORM METHOD="POST" ACTION="<?=\$logouturl;?>">');
392
	LogoutWin.document.write('<INPUT NAME="logout_id" TYPE="hidden" VALUE="<?=\$sessionid;?>">');
393
	LogoutWin.document.write('<INPUT NAME="logout" TYPE="submit" VALUE="Logout">');
394
	LogoutWin.document.write('</FORM>');
395
	LogoutWin.document.write('</DIV></BODY>');
396
	LogoutWin.document.write('</HTML>');
397
	LogoutWin.document.close();
398
}
399

    
400
document.location.href="<?=\$my_redirurl;?>";
401
-->
402
</SCRIPT>
403
</BODY>
404
</HTML>
405

    
406
EOD;
407
		}
408

    
409
		$fd = @fopen("{$g['varetc_path']}/captiveportal-logout.html", "w");
410
		if ($fd) {
411
			fwrite($fd, $logouttext);
412
			fclose($fd);
413
		}
414
		/* write elements */
415
		captiveportal_write_elements();
416

    
417
		/* start up the webserving daemon */
418
		captiveportal_init_webgui();
419

    
420
		/* Kill any existing prunecaptiveportal processes */
421
		if(file_exists("{$g['varrun_path']}/cp_prunedb.pid"))
422
			killbypid("{$g['varrun_path']}/cp_prunedb.pid");
423

    
424
		/* start pruning process (interval defaults to 60 seconds) */
425
		mwexec("/usr/local/bin/minicron $croninterval {$g['varrun_path']}/cp_prunedb.pid " .
426
			"/etc/rc.prunecaptiveportal");
427

    
428
		/* generate radius server database */
429
		captiveportal_init_radius_servers();
430

    
431
		if ($g['booting'])
432
			echo "done\n";
433

    
434
	} else {
435
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
436
		killbypid("{$g['varrun_path']}/cp_prunedb.pid");
437

    
438
		captiveportal_radius_stop_all();
439

    
440
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
441

    
442
		/* unload ipfw */
443
		if (is_module_loaded("ipfw.ko"))		
444
			mwexec("/sbin/kldunload ipfw.ko");
445
		$listifs = get_configured_interface_list_by_realif();
446
		foreach ($listifs as $listrealif => $listif) {
447
			if (!empty($listrealif)) {
448
				if (does_interface_exist($listrealif)) {
449
					pfSense_interface_flags($listrealif, -IFF_IPFW_FILTER);
450
					$carpif = link_ip_to_carp_interface(find_interface_ip($listrealif));
451
					if (!empty($carpif)) {
452
						$carpsif = explode(" ", $carpif);
453
						foreach ($carpsif as $cpcarp)
454
							pfSense_interface_flags($cpcarp, -IFF_IPFW_FILTER);
455
					}
456
				}
457
			}
458
		}
459
	}
460

    
461
	unlock($captiveportallck);
462
	
463
	return 0;
464
}
465

    
466
function captiveportal_init_webgui() {
467
	global $g, $config;
468

    
469
	 if (!isset($config['captiveportal']['enable']))
470
				return;
471

    
472
	if ($config['captiveportal']['maxproc'])
473
		$maxproc = $config['captiveportal']['maxproc'];
474
	else
475
		$maxproc = 16;
476

    
477
	$use_fastcgi = true;
478

    
479
	if (isset($config['captiveportal']['httpslogin'])) {
480
		$cert = base64_decode($config['captiveportal']['certificate']);
481
		if (isset($config['captiveportal']['cacertificate']))
482
			$cacert = base64_decode($config['captiveportal']['cacertificate']);
483
		else
484
			$cacert = "";
485
		$key = base64_decode($config['captiveportal']['private-key']);
486
		/* generate lighttpd configuration */
487
		system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal-SSL.conf",
488
			$cert, $key, $cacert, "lighty-CaptivePortal-ssl.pid", "8001", "/usr/local/captiveportal/",
489
			"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
490
	}
491

    
492
	/* generate lighttpd configuration */
493
	system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal.conf",
494
		"", "", "", "lighty-CaptivePortal.pid", "8000", "/usr/local/captiveportal/",
495
		"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
496

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

    
500
	/* fire up https instance */
501
	if (isset($config['captiveportal']['httpslogin']))
502
		$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-CaptivePortal-SSL.conf");
503
}
504

    
505
/* reinit will disconnect all users, be careful! */
506
function captiveportal_init_rules($reinit = false) {
507
	global $config, $g;
508

    
509
	if (!isset($config['captiveportal']['enable']))
510
		return;
511

    
512
	$cpips = array();
513
	$ifaces = get_configured_interface_list();
514
	foreach ($ifaces as $kiface => $kiface2) {
515
		$tmpif = get_real_interface($kiface);
516
		pfSense_interface_flags($tmpif, -IFF_IPFW_FILTER);
517
	}
518
	$cpinterfaces = explode(",", $config['captiveportal']['interface']);
519
	$firsttime = 0;
520
	foreach ($cpinterfaces as $cpifgrp) {
521
		if (!isset($ifaces[$cpifgrp]))
522
			continue;
523
		$tmpif = get_real_interface($cpifgrp);
524
		if (!empty($tmpif)) {
525
			if ($firsttime > 0)
526
				$cpinterface .= " or ";
527
			$cpinterface .= "via {$tmpif}";
528
			$firsttime = 1;
529
			$cpipm = get_interface_ip($cpifgrp);
530
			if (is_ipaddr($cpipm)) {
531
				$carpif = link_ip_to_carp_interface($cpipm);
532
				if (!empty($carpif)) {
533
					$carpsif = explode(" ", $carpif);
534
					foreach ($carpsif as $cpcarp) {
535
						pfSense_interface_flags($cpcarp, IFF_IPFW_FILTER);
536
						$carpip = find_interface_ip($cpcarp);
537
						if (is_ipaddr($carpip))
538
							$cpips[] = $carpip;
539
					}
540
				}
541
				$cpips[] = $cpipm;
542
				pfSense_interface_flags($tmpif, IFF_IPFW_FILTER);
543
			}
544
		}
545
	}
546
	if (count($cpips) > 0) {
547
		$cpactive = true;
548
		$cpinterface = "{ {$cpinterface} } ";
549
		} else
550
		return false;
551

    
552
	if ($reinit == false)
553
		$captiveportallck = lock('captiveportal');
554

    
555
	/* init dummynet/ipfw rules number database */
556
	captiveportal_init_ipfw_ruleno();
557

    
558
	/* make sure ipfw is loaded */
559
	if (!is_module_loaded("ipfw.ko"))
560
		filter_load_ipfw();
561
	/* Always load dummynet now that even allowed ip and mac passthrough use it. */
562
	if (!is_module_loaded("dummynet.ko"))
563
		mwexec("/sbin/kldload dummynet");
564

    
565
	$cprules =	"add 65291 set 1 allow pfsync from any to any\n";
566
	$cprules .= "add 65292 set 1 allow carp from any to any\n";
567

    
568
	$cprules .= <<<EOD
569
# add 65300 set 1 skipto 65534 all from any to any not layer2
570
# layer 2: pass ARP
571
add 65301 set 1 pass layer2 mac-type arp
572
# pfsense requires for WPA
573
add 65302 set 1 pass layer2 mac-type 0x888e
574
add 65303 set 1 pass layer2 mac-type 0x88c7
575

    
576
# PPP Over Ethernet Discovery Stage
577
add 65304 set 1 pass layer2 mac-type 0x8863
578
# PPP Over Ethernet Session Stage
579
add 65305 set 1 pass layer2 mac-type 0x8864
580
# Allow WPA
581
add 65306 set 1 pass layer2 mac-type 0x888e
582

    
583
# layer 2: block anything else non-IP
584
add 65307 set 1 deny layer2 not mac-type ip
585

    
586
EOD;
587

    
588
	$rulenum = 65310;
589
	$ipcount = 0;
590
	$ips = "";
591
	foreach ($cpips as $cpip) {
592
		if($ipcount == 0) {
593
			$ips = "{$cpip} ";
594
		} else {
595
			$ips .= "or {$cpip} ";
596
		}
597
		$ipcount++;
598
	}
599
	$ips = "{ 255.255.255.255 or {$ips} }";
600
	$cprules .= "add {$rulenum} set 1 pass ip from any to {$ips} in\n";
601
	$rulenum++;
602
	$cprules .= "add {$rulenum} set 1 pass ip from {$ips} to any out\n";
603
	$rulenum++;
604
	$cprules .= "add {$rulenum} set 1 pass icmp from {$ips} to any out icmptype 0\n";
605
	$rulenum++;
606
	$cprules .= "add {$rulenum} set 1 pass icmp from any to {$ips} in icmptype 8 \n";
607
	$rulenum++;
608
	/* Allowed ips */
609
	$cprules .= "add {$rulenum} allow ip from table(3) to any in\n";
610
	$rulenum++;
611
	$cprules .= "add {$rulenum} allow ip from any to table(4) out\n";
612
	$rulenum++;
613
	$cprules .= "add {$rulenum} pipe tablearg ip from table(5) to any in\n";
614
	$rulenum++;
615
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(6) out\n";
616
	$rulenum++;
617
	$cprules .= "add {$rulenum} allow ip from any to table(7) in\n";
618
	$rulenum++;
619
	$cprules .= "add {$rulenum} allow ip from table(8) to any out\n";
620
	$rulenum++;
621
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(9) in\n";
622
	$rulenum++;
623
	$cprules .= "add {$rulenum} pipe tablearg ip from table(10) to any out\n";
624
	$rulenum++;
625

    
626
	/* Authenticated users rules. */
627
	if (isset($config['captiveportal']['peruserbw'])) {
628
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from table(1) to any in\n";
629
		$rulenum++;
630
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from any to table(2) out\n";
631
		$rulenum++;
632
	} else {
633
		$cprules .= "add {$rulenum} set 1 allow ip from table(1) to any in\n";
634
		$rulenum++;
635
		$cprules .= "add {$rulenum} set 1 allow ip from any to table(2) out\n";
636
		$rulenum++;
637
	}
638
	
639
	   $cprules .= <<<EOD
640

    
641
# redirect non-authenticated clients to captive portal
642
add 65531 set 1 fwd 127.0.0.1,8000 tcp from any to any in
643
# let the responses from the captive portal web server back out
644
add 65532 set 1 pass tcp from any to any out
645
# block everything else
646
add 65533 set 1 deny all from any to any
647
# pass everything else on layer2
648
add 65534 set 1 pass all from any to any layer2
649

    
650
EOD;
651

    
652
	/* generate passthru mac database */
653
	$cprules .= captiveportal_passthrumac_configure(true);
654
	$cprules .= "\n";
655

    
656
	/* allowed ipfw rules to make allowed ip work */
657
	$cprules .= captiveportal_allowedip_configure();
658

    
659
	/* allowed ipfw rules to make allowed hostnames work */
660
	$cprules .= captiveportal_allowedhostname_configure();
661
	
662
	/* load rules */
663
	if ($reinit == true)
664
		$cprules = "table all flush\nflush\n{$cprules}";
665
	else {
666
		$tmprules = "table 3 flush\n";
667
		$tmprules .= "table 4 flush\n";
668
		$tmprules .= "table 5 flush\n";
669
		$tmprules .= "table 6 flush\n";
670
		$tmprules .= "table 7 flush\n";
671
		$tmprules .= "table 8 flush\n";
672
		$tmprules .= "table 9 flush\n";
673
		$tmprules .= "table 10 flush\n";
674
		$tmprules .= "flush\n";
675
		$cprules = "{$tmprules}\n{$cprules}";
676
	}
677

    
678
	file_put_contents("{$g['tmp_path']}/ipfw.cp.rules", $cprules);
679
	mwexec("/sbin/ipfw -q {$g['tmp_path']}/ipfw.cp.rules", true);
680
	//@unlink("{$g['tmp_path']}/ipfw.cp.rules");
681

    
682
	if ($reinit == false)
683
		unlock($captiveportallck);
684

    
685
	/* filter on layer2 as well so we can check MAC addresses */
686
	mwexec("/sbin/sysctl net.link.ether.ipfw=1");
687

    
688
	return $cprules;
689
}
690

    
691
/* remove clients that have been around for longer than the specified amount of time
692
 * db file structure:
693
 * timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time
694
 * (password is in Base64 and only saved when reauthentication is enabled)
695
 */
696
function captiveportal_prune_old() {
697
	global $g, $config;
698

    
699
	/* check for expired entries */
700
	if (empty($config['captiveportal']['timeout']) ||
701
	!is_numeric($config['captiveportal']['timeout']))
702
		$timeout = 0;
703
	else
704
		$timeout = $config['captiveportal']['timeout'] * 60;
705

    
706
	if (empty($config['captiveportal']['idletimeout']) ||
707
	!is_numeric($config['captiveportal']['idletimeout']))
708
		$idletimeout = 0;
709
	else
710
		$idletimeout = $config['captiveportal']['idletimeout'] * 60;
711

    
712
	if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && 
713
	!isset($config['captiveportal']['radiussession_timeout']) && !isset($config['voucher']['enable']))
714
		return;
715

    
716
	/* read database */
717
	$cpdb = captiveportal_read_db();
718

    
719
	$radiusservers = captiveportal_get_radius_servers();
720

    
721
	/*	To make sure we iterate over ALL accounts on every run the count($cpdb) is moved
722
	 *	outside of the loop. Otherwise the loop would evaluate count() on every iteration
723
	 *	and since $i would increase and count() would decrement they would meet before we
724
	 *	had a chance to iterate over all accounts.
725
	 */
726
	$unsetindexes = array();
727
	$no_users = count($cpdb);
728
	for ($i = 0; $i < $no_users; $i++) {
729

    
730
		$timedout = false;
731
		$term_cause = 1;
732

    
733
		/* hard timeout? */
734
		if ($timeout) {
735
			if ((time() - $cpdb[$i][0]) >= $timeout) {
736
				$timedout = true;
737
				$term_cause = 5; // Session-Timeout
738
			}
739
		}
740

    
741
		/* Session-Terminate-Time */
742
		if (!$timedout && !empty($cpdb[$i][9])) {
743
			if (time() >= $cpdb[$i][9]) {
744
				$timedout = true;
745
				$term_cause = 5; // Session-Timeout
746
			}
747
		}
748

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

    
765
		/* if vouchers are configured, activate session timeouts */
766
		if (!$timedout && isset($config['voucher']['enable']) && !empty($cpdb[$i][7])) {
767
			if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
768
				$timedout = true;
769
				$term_cause = 5; // Session-Timeout
770
			}
771
		}
772

    
773
		/* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
774
		if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpdb[$i][7])) {
775
			if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
776
				$timedout = true;
777
				$term_cause = 5; // Session-Timeout
778
			}
779
		}
780

    
781
		if ($timedout) {
782
			captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
783
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
784
			$unsetindexes[$i] = $i;
785
		}
786

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

    
821
			/* check this user against RADIUS again */
822
			if (isset($config['captiveportal']['reauthenticate'])) {
823
				$auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
824
					base64_decode($cpdb[$i][6]), // password
825
					$radiusservers,
826
					$cpdb[$i][2], // clientip
827
					$cpdb[$i][3], // clientmac
828
					$cpdb[$i][1]); // ruleno
829
				if ($auth_list['auth_val'] == 3) {
830
					captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
831
					captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
832
					$unsetindexes[$i] = $i;
833
				}
834
			}
835
		}
836
	}
837
	/* This is a kludge to overcome some php weirdness */
838
	foreach($unsetindexes as $unsetindex)
839
	unset($cpdb[$unsetindex]);
840

    
841
	/* write database */
842
	captiveportal_write_db($cpdb);
843
}
844

    
845
/* remove a single client according to the DB entry */
846
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
847
	global $g, $config;
848

    
849
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
850

    
851
	/* this client needs to be deleted - remove ipfw rules */
852
	if (isset($config['captiveportal']['radacct_enable']) && !empty($radiusservers)) {
853
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
854
			$dbent[4], // username
855
			$dbent[5], // sessionid
856
			$dbent[0], // start time
857
			$radiusservers,
858
			$dbent[2], // clientip
859
			$dbent[3], // clientmac
860
			$term_cause, // Acct-Terminate-Cause
861
			false,
862
			$stop_time);
863
	}
864
	/* Delete client's ip entry from tables 3 and 4. */
865
	mwexec("/sbin/ipfw table 1 delete {$dbent[2]}");
866
	mwexec("/sbin/ipfw table 2 delete {$dbent[2]}");
867

    
868
	/* Release the ruleno so it can be reallocated to new clients. */
869
	captiveportal_free_ipfw_ruleno($dbent[1]);
870

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

    
880
	/* XXX: Redundant?! Ensure all pf(4) states are killed. */
881
	mwexec("pfctl -k {$dbent[2]}");
882
	mwexec("pfctl -K {$dbent[2]}");
883

    
884
}
885

    
886
/* remove a single client by ipfw rule number */
887
function captiveportal_disconnect_client($id,$term_cause = 1) {
888
	global $g, $config;
889

    
890
	/* read database */
891
	$cpdb = captiveportal_read_db();
892
	$radiusservers = captiveportal_get_radius_servers();
893

    
894
	/* find entry */
895
	foreach ($cpdb as $i => $cpentry) {
896
		if ($cpentry[1] == $id) {
897
			captiveportal_disconnect($cpentry, $radiusservers, $term_cause);
898
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
899
			unset($cpdb[$i]);
900
			break;
901
		}
902
	}		
903

    
904
	/* write database */
905
	captiveportal_write_db($cpdb);
906
}
907

    
908
/* send RADIUS acct stop for all current clients */
909
function captiveportal_radius_stop_all() {
910
	global $config;
911

    
912
	if (!isset($config['captiveportal']['radacct_enable']))
913
		return;
914

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

    
931
function captiveportal_passthrumac_configure_entry($macent) {
932
	$rules = "";
933
	$enBwup = isset($macent['bw_up']);
934
	$enBwdown = isset($macent['bw_down']);
935
	$actionup = "allow";
936
	$actiondown = "allow";
937

    
938
	if ($enBwup && $enBwdown)
939
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
940
	else
941
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
942

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

    
957
	return $rules;
958
}
959

    
960
function captiveportal_passthrumac_configure($lock = false) {
961
	global $config, $g;
962

    
963
	$rules = "";
964

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

    
971
		}
972
	}
973

    
974
	return $rules;
975
}
976

    
977
function captiveportal_passthrumac_findbyname($username) {
978
	global $config;
979

    
980
	if (is_array($config['captiveportal']['passthrumac'])) {
981
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
982
			if ($macent['username'] == $username)
983
				return $macent;
984
		}
985
	}
986
	return NULL;
987
}
988

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

    
995
	/* This function can deal with hostname or ipaddress */
996
	if($ipent['ip']) 	
997
		$ipaddress = $ipent['ip'];
998

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

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

    
1016
	if (intval($enBwup) > 0 or intval($enBwdown) > 0)
1017
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
1018
	else
1019
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
1020

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

    
1071
	return $rules;
1072
}
1073

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

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

    
1096
function captiveportal_allowedhostname_configure() {
1097
	global $config, $g;
1098

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

    
1108
function captiveportal_allowedip_configure() {
1109
	global $config, $g;
1110

    
1111
	$rules = "";
1112
	if (is_array($config['captiveportal']['allowedip'])) {
1113
		foreach ($config['captiveportal']['allowedip'] as $ipent) 
1114
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1115
	}
1116

    
1117
	return $rules;
1118
}
1119

    
1120
/* get last activity timestamp given client IP address */
1121
function captiveportal_get_last_activity($ip) {
1122

    
1123
	$ipfwoutput = "";
1124

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

    
1133
	return 0;
1134
}
1135

    
1136
function captiveportal_init_radius_servers() {
1137
	global $config, $g;
1138

    
1139
	/* generate radius server database */
1140
	if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
1141
		($config['captiveportal']['auth_method'] == "radius"))) {
1142
		$radiusip = $config['captiveportal']['radiusip'];
1143
		$radiusip2 = ($config['captiveportal']['radiusip2']) ? $config['captiveportal']['radiusip2'] : null;
1144

    
1145
		if ($config['captiveportal']['radiusport'])
1146
			$radiusport = $config['captiveportal']['radiusport'];
1147
		else
1148
			$radiusport = 1812;
1149
		if ($config['captiveportal']['radiusacctport'])
1150
			$radiusacctport = $config['captiveportal']['radiusacctport'];
1151
		else
1152
			$radiusacctport = 1813;
1153
		if ($config['captiveportal']['radiusport2'])
1154
			$radiusport2 = $config['captiveportal']['radiusport2'];
1155
		else
1156
			$radiusport2 = 1812;
1157
		$radiuskey = $config['captiveportal']['radiuskey'];
1158
		$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
1159

    
1160
		$cprdsrvlck = lock('captiveportalradius', LOCK_EX);
1161
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db", "w");
1162
		if (!$fd) {
1163
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1164
			unlock($cprdsrvlck);
1165
			return 1;
1166
		} else if (isset($radiusip2, $radiuskey2))
1167
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . "\n"
1168
			. $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2);
1169
		else
1170
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey);
1171
		fclose($fd);
1172
		unlock($cprdsrvlck);
1173
	}
1174
}
1175

    
1176
/* read RADIUS servers into array */
1177
function captiveportal_get_radius_servers() {
1178
		global $g;
1179

    
1180
		$cprdsrvlck = lock('captiveportalradius');
1181
		if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
1182
			$radiusservers = array();
1183
			$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius.db", 
1184
			FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1185
			if ($cpradiusdb) {
1186
				foreach($cpradiusdb as $cpradiusentry) {
1187
					$line = trim($cpradiusentry);
1188
					if ($line) {
1189
						$radsrv = array();
1190
						list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
1191
						$radiusservers[] = $radsrv;
1192
					}
1193
				}
1194
			}
1195
			unlock($cprdsrvlck);
1196
			return $radiusservers;
1197
		}
1198

    
1199
		unlock($cprdsrvlck);
1200
		return false;
1201
}
1202

    
1203
/* log successful captive portal authentication to syslog */
1204
/* part of this code from php.net */
1205
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1206
	// Log it
1207
	if (!$message)
1208
		$message = "$status: $user, $mac, $ip";
1209
	else {
1210
		$message = trim($message);
1211
		$message = "$status: $user, $mac, $ip, $message";
1212
	}
1213
	captiveportal_syslog($message);
1214
}
1215

    
1216
/* log simple messages to syslog */
1217
function captiveportal_syslog($message) {
1218
	define_syslog_variables();
1219
	$message = trim($message);
1220
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1221
	// Log it
1222
	syslog(LOG_INFO, $message);
1223
	closelog();
1224
}
1225

    
1226
function radius($username,$password,$clientip,$clientmac,$type) {
1227
	global $g, $config;
1228

    
1229
	$ruleno = captiveportal_get_next_ipfw_ruleno();
1230

    
1231
	/* If the pool is empty, return appropriate message and fail authentication */
1232
	if (is_null($ruleno)) {
1233
		$auth_list = array();
1234
		$auth_list['auth_val'] = 1;
1235
		$auth_list['error'] = "System reached maximum login capacity";
1236
		return $auth_list;
1237
	}
1238

    
1239
	$radiusservers = captiveportal_get_radius_servers();
1240

    
1241
	$auth_list = RADIUS_AUTHENTICATION($username,
1242
		$password,
1243
		$radiusservers,
1244
		$clientip,
1245
		$clientmac,
1246
		$ruleno);
1247

    
1248
	if ($auth_list['auth_val'] == 2) {
1249
		captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1250
		$sessionid = portal_allow($clientip,
1251
			$clientmac,
1252
			$username,
1253
			$password,
1254
			$auth_list,
1255
			$ruleno);
1256
	}
1257

    
1258
	return $auth_list;
1259
}
1260

    
1261
/* read captive portal DB into array */
1262
function captiveportal_read_db() {
1263
		global $g;
1264

    
1265
		$cpdb = array();
1266

    
1267
		$cpdblck = lock('captiveportaldb');
1268
		$fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
1269
		if ($fd) {
1270
			while (!feof($fd)) {
1271
				$line = trim(fgets($fd));
1272
				if ($line) 
1273
						$cpdb[] = explode(",", $line);
1274
			}
1275
			fclose($fd);
1276
		}
1277
		unlock($cpdblck);
1278
		return $cpdb;
1279
}
1280

    
1281
/* write captive portal DB */
1282
function captiveportal_write_db($cpdb) {
1283
		global $g;
1284

    
1285
		$cpdblck = lock('captiveportaldb', LOCK_EX);
1286
		$fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
1287
		if ($fd) {
1288
			foreach ($cpdb as $cpent) {
1289
					fwrite($fd, join(",", $cpent) . "\n");
1290
			}
1291
			fclose($fd);
1292
		}
1293
	unlock($cpdblck);
1294
}
1295

    
1296
function captiveportal_write_elements() {
1297
	global $g, $config;
1298
	
1299
	/* delete any existing elements */
1300
	if (is_dir($g['captiveportal_element_path'])) {
1301
		$dh = opendir($g['captiveportal_element_path']);
1302
		while (($file = readdir($dh)) !== false) {
1303
			if ($file != "." && $file != "..")
1304
				unlink($g['captiveportal_element_path'] . "/" . $file);
1305
		}
1306
		closedir($dh);
1307
	} else {
1308
		@mkdir($g['captiveportal_element_path']);
1309
	}
1310

    
1311
	if (is_array($config['captiveportal']['element'])) {
1312
		conf_mount_rw();
1313
		foreach ($config['captiveportal']['element'] as $data) {
1314
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
1315
			if (!$fd) {
1316
				printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
1317
				return 1;
1318
			}
1319
			$decoded = base64_decode($data['content']);
1320
			fwrite($fd,$decoded);
1321
			fclose($fd);
1322
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1323
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1324
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
1325
		}
1326
		conf_mount_ro();
1327
	}
1328
	
1329
	return 0;
1330
}
1331

    
1332
function captiveportal_init_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
1333
	global $g;
1334

    
1335
	@unlink("{$g['vardb_path']}/captiveportal.rules");
1336
	$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1337
	file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1338
}
1339

    
1340
/*
1341
 * This function will calculate the lowest free firewall ruleno
1342
 * within the range specified based on the actual logged on users
1343
 *
1344
 */
1345
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899, $usebw = false) {
1346
	global $config, $g;
1347

    
1348
	if(!isset($config['captiveportal']['enable']))
1349
		return NULL;
1350

    
1351
	$cpruleslck = lock('captiveportalrules', LOCK_EX);
1352
	$ruleno = 0;
1353
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1354
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1355
		for ($ridx = 2; $ridx < ($rulenos_range_max - $rulenos_start); $ridx++) {
1356
			if ($rules[$ridx]) {
1357
				/* 
1358
				 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1359
				 * and the out pipe ruleno + 1. This removes limitation that where present in 
1360
				 * previous version of the peruserbw.
1361
				 */
1362
				if (isset($config['captiveportal']['peruserbw']))
1363
					$ridx++;
1364
				continue;
1365
			}
1366
			$ruleno = $ridx;
1367
			$rules[$ridx] = "used";
1368
			if (isset($config['captiveportal']['peruserbw']) || $usebw == true)
1369
				$rules[++$ridx] = "used";
1370
			break;
1371
		}
1372
	} else {
1373
		$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1374
		$rules[2] = "used";
1375
		$ruleno = 2;
1376
	}
1377
	file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1378
	unlock($cpruleslck);
1379
	return $ruleno;
1380
}
1381

    
1382
function captiveportal_free_ipfw_ruleno($ruleno, $usedbw = false) {
1383
	global $config, $g;
1384

    
1385
	if(!isset($config['captiveportal']['enable']))
1386
		return NULL;
1387

    
1388
	$cpruleslck = lock('captiveportalrules', LOCK_EX);
1389
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1390
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1391
		$rules[$ruleno] = false;
1392
		if (isset($config['captiveportal']['peruserbw']) || $usedbw == true)
1393
			$rules[++$ruleno] = false;
1394
		file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1395
	}
1396
	unlock($cpruleslck);
1397
}
1398

    
1399
function captiveportal_get_ipfw_passthru_ruleno($value) {
1400
	global $config, $g;
1401

    
1402
	if(!isset($config['captiveportal']['enable']))
1403
				return NULL;
1404

    
1405
	$cpruleslck = lock('captiveportalrules', LOCK_EX);
1406
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1407
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1408
		$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`);
1409
		if ($rules[$ruleno]) {
1410
			unlock($cpruleslck);
1411
			return $ruleno;
1412
		}
1413
	}
1414

    
1415
	unlock($cpruleslck);
1416
	return NULL;
1417
}
1418

    
1419
/**
1420
 * This function will calculate the traffic produced by a client
1421
 * based on its firewall rule
1422
 *
1423
 * Point of view: NAS
1424
 *
1425
 * Input means: from the client
1426
 * Output means: to the client
1427
 *
1428
 */
1429

    
1430
function getVolume($ip) {
1431

    
1432
	$volume = array();
1433

    
1434
	// Initialize vars properly, since we don't want NULL vars
1435
	$volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1436

    
1437
	// Ingress
1438
	$ipfwin = "";
1439
	$ipfwout = "";
1440
	$matchesin = "";
1441
	$matchesout = "";
1442
	exec("/sbin/ipfw table 1 entrystats {$ip}", $ipfwin);
1443
	if ($ipfwin[0]) {
1444
		$ipfwin = split(" ", $ipfwin[0]);
1445
		$volume['input_pkts'] = $ipfwin[2];
1446
		$volume['input_bytes'] = $ipfwin[3];
1447
	}
1448

    
1449
	exec("/sbin/ipfw table 2 entrystats {$ip}", $ipfwout);
1450
	if ($ipfwout[0]) {
1451
		$ipfwout = split(" ", $ipfwout[0]);
1452
		$volume['output_pkts'] = $ipfwout[2];
1453
		$volume['output_bytes'] = $ipfwout[3];
1454
	}
1455

    
1456
	return $volume;
1457
}
1458

    
1459
/**
1460
 * Get the NAS-Identifier
1461
 *
1462
 * We will use our local hostname to make up the nas_id
1463
 */
1464
function getNasID()
1465
{
1466
	$nasId = "";
1467
	exec("/bin/hostname", $nasId);
1468
	if(!$nasId[0])
1469
		$nasId[0] = "{$g['product_name']}";
1470
	return $nasId[0];
1471
}
1472

    
1473
/**
1474
 * Get the NAS-IP-Address based on the current wan address
1475
 *
1476
 * Use functions in interfaces.inc to find this out
1477
 *
1478
 */
1479

    
1480
function getNasIP()
1481
{
1482
	global $config;
1483

    
1484
	if (empty($config['captiveportal']['radiussrcip_attribute'])) {
1485
			$nasIp = get_interface_ip();
1486
	} else {
1487
		if (is_ipaddr($config['captiveportal']['radiussrcip_attribute']))
1488
			$nasIp = $config['captiveportal']['radiussrcip_attribute'];
1489
		else
1490
			$nasIp = get_interface_ip($config['captiveportal']['radiussrcip_attribute']);
1491
	}
1492
		
1493
	if(!is_ipaddr($nasIp))
1494
		$nasIp = "0.0.0.0";
1495

    
1496
	return $nasIp;
1497
}
1498

    
1499
function portal_ip_from_client_ip($cliip) {
1500
	global $config;
1501

    
1502
	$interfaces = explode(",", $config['captiveportal']['interface']);
1503
	foreach ($interfaces as $cpif) {
1504
		$ip = get_interface_ip($cpif);
1505
		$sn = get_interface_subnet($cpif);
1506
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1507
			return $ip;
1508
	}
1509

    
1510
	// doesn't match up to any particular interface
1511
	// so let's set the portal IP to what PHP says 
1512
	// the server IP issuing the request is. 
1513
	// allows same behavior as 1.2.x where IP isn't 
1514
	// in the subnet of any CP interface (static routes, etc.)
1515
	// rather than forcing to DNS hostname resolution
1516
	$ip = $_SERVER['SERVER_ADDR'];
1517
	if (is_ipaddr($ip))
1518
		return $ip;
1519

    
1520
	return false;
1521
}
1522

    
1523
?>
(7-7/61)