Project

General

Profile

Download (45.4 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	captiveportal.inc
4
	part of pfSense (http://www.pfSense.org)
5

    
6
	originally part of m0n0wall (http://m0n0.ch/wall)
7

    
8
	Copyright (C) 2010 Scott Ullrich <sullrich@gmail.com>
9
	Copyright (C) 2009 Ermal Lu?i <ermal.luci@gmail.com>
10
	Copyright (C) 2003-2006 Manuel Kasper <mk@neon1.net>.
11
	All rights reserved.
12

    
13
	Redistribution and use in source and binary forms, with or without
14
	modification, are permitted provided that the following conditions are met:
15

    
16
	1. Redistributions of source code must retain the above copyright notice,
17
	   this list of conditions and the following disclaimer.
18

    
19
	2. Redistributions in binary form must reproduce the above copyright
20
	   notice, this list of conditions and the following disclaimer in the
21
	   documentation and/or other materials provided with the distribution.
22

    
23
	THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
24
	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
25
	AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26
	AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
27
	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
	POSSIBILITY OF SUCH DAMAGE.
33

    
34
	This version of captiveportal.inc has been modified by Rob Parker
35
	<rob.parker@keycom.co.uk> to include changes for per-user bandwidth management
36
	via returned RADIUS attributes. This page has been modified to delete any
37
	added rules which may have been created by other per-user code (index.php, etc).
38
	These changes are (c) 2004 Keycom PLC.
39
	
40
	pfSense_BUILDER_BINARIES:	/sbin/ipfw	/sbin/sysctl	/sbin/kldunload
41
	pfSense_BUILDER_BINARIES:	/usr/local/sbin/lighttpd	/usr/local/bin/minicron	/sbin/pfctl
42
	pfSense_BUILDER_BINARIES:	/bin/hostname	/bin/cp	
43
	pfSense_MODULE:	captiveportal
44
*/
45

    
46
/* include all configuration functions */
47
require_once("config.inc");
48
require_once("functions.inc");
49
require_once("filter.inc");
50
require_once("radius.inc");
51
require_once("voucher.inc");
52

    
53
function captiveportal_configure() {
54
	global $config, $g;
55

    
56
	$captiveportallck = lock('captiveportal');
57
	
58
	if (isset($config['captiveportal']['enable'])) {
59

    
60
		if ($g['booting'])
61
			echo "Starting captive portal... ";
62

    
63
		/* kill any running mini_httpd */
64
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
65
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal-SSL.pid");
66

    
67
		/* remove old information */
68
		unlink_if_exists("{$g['vardb_path']}/captiveportal.db");
69
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
70
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip.db");
71
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius.db");
72

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

    
76
		/* kill any running minicron */
77
		killbypid("{$g['varrun_path']}/minicron.pid");
78

    
79
		/* init ipfw rules */
80
		captiveportal_init_rules(true);
81

    
82
		/* stop accounting on all clients */
83
		captiveportal_radius_stop_all(true);
84

    
85
		/* initialize minicron interval value */
86
		$croninterval = $config['captiveportal']['croninterval'] ? $config['captiveportal']['croninterval'] : 60;
87

    
88
		/* double check if the $croninterval is numeric and at least 10 seconds. If not we set it to 60 to avoid problems */
89
		if ((!is_numeric($croninterval)) || ($croninterval < 10)) { $croninterval = 60; }
90

    
91
		/* write portal page */
92
		if ($config['captiveportal']['page']['htmltext'])
93
			$htmltext = base64_decode($config['captiveportal']['page']['htmltext']);
94
		else {
95
			/* example/template page */
96
			$htmltext = <<<EOD
97
<html> 
98
	<body> 
99
		<form method="post" action="$PORTAL_ACTION$"> 
100
		<input name="redirurl" type="hidden" value="$PORTAL_REDIRURL$">
101
			<center>
102
				<table cellpadding="6" cellspacing="0" width="550" height="380" style="border:1px solid #000000">
103
					<tr height="10" bgcolor="#990000">
104
						<td style="border-bottom:1px solid #000000">
105
							<font color='white'>
106
								<b>
107
									{$g['product_name']} captive portal
108
								</b>
109
							</font>
110
						</td>
111
					</tr>
112
					<tr>
113
						<td>
114
							<div id="mainlevel">
115
								<center>
116
									<table width="100%" border="0" cellpadding="5" cellspacing="0">
117
								 		<tr>
118
								    		<td>
119
												<center>
120
													<div id="mainarea">
121
														<center>
122
															<table width="100%" border="0" cellpadding="5" cellspacing="5">
123
																<tr>
124
										     						<td>
125
																		<div id="maindivarea">
126
																			<center>
127
																				<div id='statusbox'>
128
																					<font color='red' face='arial' size='+1'>
129
																						<b>
130
																							$PORTAL_MESSAGE$
131
																						</b>
132
																					</font>
133
																				</div>
134
																				<br/>
135
																				<div id='loginbox'>
136
																					<table>
137
																					   <tr><td colspan="2"><center>Welcome to the {$g['product_name']} Captive Portal!</td></tr>
138
																					   <tr><td>&nbsp;</td></tr>
139
																					   <tr><td align="right">Username:</td><td><input name="auth_user" type="text" style="border: 1px dashed;"></td></tr>
140
																					   <tr><td align="right">Password:</td><td><input name="auth_pass" type="password" style="border: 1px dashed;"></td></tr>
141
																					   <tr><td>&nbsp;</td></tr>
142
																					   <tr>
143
																					     <td colspan="2">
144
																							<center><input name="accept" type="submit" value="Continue"></center>
145
																					     </td>
146
																					   </tr>
147
																					</table>
148
																				</div>
149
																			</center>
150
																		</div>
151
										     						</td>
152
																</tr>
153
															</table>
154
														</center>
155
													</div>
156
												</center>
157
											</td>
158
										</tr>
159
									</table>
160
								</center>
161
							</div>
162
						</td>
163
					</tr>
164
				</table>
165
			</center>
166
		</form>
167
	</body> 
168
</html>
169

    
170

    
171

    
172
EOD;
173
		}
174

    
175
		$fd = @fopen("{$g['varetc_path']}/captiveportal.html", "w");
176
		if ($fd) {
177
			// Special case handling.  Convert so that we can pass this page
178
			// through the PHP interpreter later without clobbering the vars.
179
			$htmltext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $htmltext);
180
			$htmltext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $htmltext);
181
			$htmltext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $htmltext);
182
			$htmltext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $htmltext);
183
			$htmltext = str_replace("\$ORIGINAL_PORTAL_IP\$", "#ORIGINAL_PORTAL_IP#", $htmltext);
184
			$htmltext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $htmltext);
185
			fwrite($fd, $htmltext);
186
			fclose($fd);
187
		}
188

    
189
		/* write error page */
190
		if ($config['captiveportal']['page']['errtext'])
191
			$errtext = base64_decode($config['captiveportal']['page']['errtext']);
192
		else {
193
			/* example page */
194
			$errtext = <<<EOD
195
<html> 
196
	<body> 
197
		<form method="post" action="$PORTAL_ACTION$"> 
198
		<input name="redirurl" type="hidden" value="$PORTAL_REDIRURL$">
199
			<center>
200
				<table cellpadding="6" cellspacing="0" width="550" height="380" style="border:1px solid #000000">
201
					<tr height="10" bgcolor="#990000">
202
						<td style="border-bottom:1px solid #000000">
203
							<font color='white'>
204
								<b>
205
									{$g['product_name']} captive portal
206
								</b>
207
							</font>
208
						</td>
209
					</tr>
210
					<tr>
211
						<td>
212
							<div id="mainlevel">
213
								<center>
214
									<table width="100%" border="0" cellpadding="5" cellspacing="0">
215
								 		<tr>
216
								    		<td>
217
												<center>
218
													<div id="mainarea">
219
														<center>
220
															<table width="100%" border="0" cellpadding="5" cellspacing="5">
221
																<tr>
222
										     						<td>
223
																		<div id="maindivarea">
224
																			<center>
225
																				<div id='statusbox'>
226
																					<font color='red' face='arial' size='+1'>
227
																						<b>
228
																							$PORTAL_MESSAGE$
229
																						</b>
230
																					</font>
231
																				</div>
232
																				<br/>
233
																				<div id='loginbox'>
234
																					<table>
235
																					   <tr><td colspan="2"><center>Welcome to the {$g['product_name']} Captive Portal!</td></tr>
236
																					   <tr><td>&nbsp;</td></tr>
237
																					   <tr><td align="right">Username:</td><td><input name="auth_user" type="text" style="border: 1px dashed;"></td></tr>
238
																					   <tr><td align="right">Password:</td><td><input name="auth_pass" type="password" style="border: 1px dashed;"></td></tr>
239
																					   <tr><td>&nbsp;</td></tr>
240
																					   <tr>
241
																					     <td colspan="2">
242
																							<center><input name="accept" type="submit" value="Continue"></center>
243
																					     </td>
244
																					   </tr>
245
																					</table>
246
																				</div>
247
																			</center>
248
																		</div>
249
										     						</td>
250
																</tr>
251
															</table>
252
														</center>
253
													</div>
254
												</center>
255
											</td>
256
										</tr>
257
									</table>
258
								</center>
259
							</div>
260
						</td>
261
					</tr>
262
				</table>
263
			</center>
264
		</form>
265
	</body> 
266
</html>
267

    
268
EOD;
269
		}
270

    
271
		$fd = @fopen("{$g['varetc_path']}/captiveportal-error.html", "w");
272
		if ($fd) {
273
			// Special case handling.  Convert so that we can pass this page
274
			// through the PHP interpreter later without clobbering the vars.
275
			$errtext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $errtext);
276
			$errtext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $errtext);
277
			$errtext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $errtext);
278
			$errtext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $errtext);
279
			$errtext = str_replace("\$ORIGINAL_PORTAL_IP\$", "#ORIGINAL_PORTAL_IP#", $errtext);
280
			$errtext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $errtext);
281
			fwrite($fd, $errtext);
282
			fclose($fd);
283
		}
284

    
285
		/* write error page */
286
		if ($config['captiveportal']['page']['logouttext'])
287
			$logouttext = base64_decode($config['captiveportal']['page']['logouttext']);
288
		else {
289
			/* example page */
290
			$logouttext = <<<EOD
291
<HTML>
292
<HEAD><TITLE>Redirecting...</TITLE></HEAD>
293
<BODY>
294
<SPAN STYLE="font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">
295
<B>Redirecting to <A HREF="{$my_redirurl}">{$my_redirurl}</A>...</B>
296
</SPAN>
297
<SCRIPT LANGUAGE="JavaScript">
298
<!--
299
LogoutWin = window.open('', 'Logout', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=256,height=64');
300
if (LogoutWin) {
301
    LogoutWin.document.write('<HTML>');
302
    LogoutWin.document.write('<HEAD><TITLE>Logout</TITLE></HEAD>') ;
303
    LogoutWin.document.write('<BODY BGCOLOR="#435370">');
304
    LogoutWin.document.write('<DIV ALIGN="center" STYLE="color: #ffffff; font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">') ;
305
    LogoutWin.document.write('<B>Click the button below to disconnect</B><P>');
306
    LogoutWin.document.write('<FORM METHOD="POST" ACTION="{$logouturl}">');
307
    LogoutWin.document.write('<INPUT NAME="logout_id" TYPE="hidden" VALUE="{$sessionid}">');
308
    LogoutWin.document.write('<INPUT NAME="logout" TYPE="submit" VALUE="Logout">');
309
    LogoutWin.document.write('</FORM>');
310
    LogoutWin.document.write('</DIV></BODY>');
311
    LogoutWin.document.write('</HTML>');
312
    LogoutWin.document.close();
313
}
314

    
315
document.location.href="{$my_redirurl}";
316
-->
317
</SCRIPT>
318
</BODY>
319
</HTML>
320

    
321
EOD;
322
		}
323

    
324
		$fd = @fopen("{$g['varetc_path']}/captiveportal-logout.html", "w");
325
		if ($fd) {
326
			fwrite($fd, $logouttext);
327
			fclose($fd);
328
		}
329
		/* write elements */
330
		captiveportal_write_elements();
331

    
332
		/* start up the webserving daemon */
333
		captiveportal_init_webgui();
334

    
335
		/* start pruning process (interval defaults to 60 seconds) */
336
		mwexec("/usr/local/bin/minicron $croninterval {$g['varrun_path']}/minicron.pid " .
337
			"/etc/rc.prunecaptiveportal");
338

    
339
		/* generate radius server database */
340
		if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
341
				($config['captiveportal']['auth_method'] == "radius"))) {
342
			$radiusip = $config['captiveportal']['radiusip'];
343
			$radiusip2 = ($config['captiveportal']['radiusip2']) ? $config['captiveportal']['radiusip2'] : null;
344

    
345
			if ($config['captiveportal']['radiusport'])
346
				$radiusport = $config['captiveportal']['radiusport'];
347
			else
348
				$radiusport = 1812;
349

    
350
			if ($config['captiveportal']['radiusacctport'])
351
				$radiusacctport = $config['captiveportal']['radiusacctport'];
352
			else
353
				$radiusacctport = 1813;
354

    
355
			if ($config['captiveportal']['radiusport2'])
356
				$radiusport2 = $config['captiveportal']['radiusport2'];
357
			else
358
				$radiusport2 = 1812;
359

    
360
			$radiuskey = $config['captiveportal']['radiuskey'];
361
			$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
362

    
363
			$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db", "w");
364
			if (!$fd) {
365
				printf("Error: cannot open radius DB file in captiveportal_configure().\n");
366
				return 1;
367
			} else if (isset($radiusip2, $radiuskey2)) {
368
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . "\n"
369
					 . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2);
370
			} else {
371
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey);
372
			}
373
			fclose($fd);
374
		}
375

    
376
		if ($g['booting'])
377
			echo "done\n";
378

    
379
	} else {
380
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
381
		killbypid("{$g['varrun_path']}/minicron.pid");
382

    
383
		captiveportal_radius_stop_all(true);
384

    
385
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
386

    
387
		/* unload ipfw */
388
		if (is_module_loaded("ipfw.ko"))		
389
			mwexec("/sbin/kldunload ipfw.ko");
390
		$listifs = get_configured_interface_list_by_realif();
391
		foreach ($listifs as $listrealif => $listif) {
392
			if (!empty($listrealif)) {
393
				if (does_interface_exist($listrealif)) {
394
					pfSense_interface_flags($listrealif, -IFF_IPFW_FILTER);
395
					$carpif = link_ip_to_carp_interface(find_interface_ip($listrealif));
396
                        		if (!empty($carpif)) {
397
						$carpsif = explode(" ", $carpif);
398
						foreach ($carpsif as $cpcarp)
399
							pfSense_interface_flags($cpcarp, -IFF_IPFW_FILTER);
400
					}
401
				}
402
			}
403
		}
404
	}
405

    
406
	unlock($captiveportallck);
407
	
408
	return 0;
409
}
410

    
411
function captiveportal_init_webgui() {
412
	global $g, $config;
413

    
414
	 if (!isset($config['captiveportal']['enable']))
415
                return;
416

    
417
	if ($config['captiveportal']['maxproc'])
418
		$maxproc = $config['captiveportal']['maxproc'];
419
	else
420
		$maxproc = 16;
421

    
422
	$use_fastcgi = true;
423

    
424
	if (isset($config['captiveportal']['httpslogin'])) {
425
		$cert = base64_decode($config['captiveportal']['certificate']);
426
		if (isset($config['captiveportal']['cacertificate']))
427
			$cacert = base64_decode($config['captiveportal']['cacertificate']);
428
		else
429
			$cacert = "";
430
		$key = base64_decode($config['captiveportal']['private-key']);
431
		/* generate lighttpd configuration */
432
		system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal-SSL.conf",
433
			$cert, $key, $cacert, "lighty-CaptivePortal-ssl.pid", "8001", "/usr/local/captiveportal/",
434
			"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
435
	}
436

    
437
	/* generate lighttpd configuration */
438
	system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal.conf",
439
		"", "", "", "lighty-CaptivePortal.pid", "8000", "/usr/local/captiveportal/",
440
		"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
441

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

    
445
	/* fire up https instance */
446
	if (isset($config['captiveportal']['httpslogin']))
447
		$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-CaptivePortal-SSL.conf");
448
}
449

    
450
function captiveportal_init_rules($reinit = false) {
451
	global $config, $g;
452

    
453
	if (!isset($config['captiveportal']['enable']))
454
		return;
455

    
456
	$cpips = array();
457
	$ifaces = get_configured_interface_list();
458
	foreach ($ifaces as $kiface => $kiface2) {
459
		$tmpif = get_real_interface($kiface);
460
		pfSense_interface_flags($tmpif, -IFF_IPFW_FILTER);
461
	}
462
	$cpinterfaces = explode(",", $config['captiveportal']['interface']);
463
	$firsttime = 0;
464
	foreach ($cpinterfaces as $cpifgrp) {
465
		if (!isset($ifaces[$cpifgrp]))
466
			continue;
467
		$tmpif = get_real_interface($cpifgrp);
468
		if (!empty($tmpif)) {
469
			if ($firsttime > 0)
470
				$cpinterface .= " or ";
471
			$cpinterface .= "via {$tmpif}";
472
			$firsttime = 1;
473
			$cpipm = get_interface_ip($cpifgrp);
474
			if (is_ipaddr($cpipm)) {
475
				$carpif = link_ip_to_carp_interface($cpipm);
476
				if (!empty($carpif)) {
477
					$carpsif = explode(" ", $carpif);
478
					foreach ($carpsif as $cpcarp) {
479
						pfSense_interface_flags($cpcarp, IFF_IPFW_FILTER);
480
						$carpip = find_interface_ip($cpcarp);
481
						if (is_ipaddr($carpip))
482
							$cpips[] = $carpip;
483
					}
484
				}
485
				$cpips[] = $cpipm;
486
				pfSense_interface_flags($tmpif, IFF_IPFW_FILTER);
487
			}
488
		}
489
	}
490
	if (count($cpips) > 0) {
491
		$cpactive = true;
492
		$cpinterface = "{ {$cpinterface} } ";
493
        } else
494
		return false;
495

    
496
	if ($reinit == false)
497
		$captiveportallck = lock('captiveportal');
498

    
499
	/* init dummynet/ipfw rules number database */
500
	captiveportal_init_ipfw_ruleno();
501

    
502
	/* make sure ipfw is loaded */
503
	if (!is_module_loaded("ipfw.ko"))
504
		filter_load_ipfw();
505
	/* Always load dummynet now that even allowed ip and mac passthrough use it. */
506
	if (!is_module_loaded("dummynet.ko"))
507
		mwexec("/sbin/kldload dummynet");
508

    
509
	$cprules =  "add 65291 set 1 allow pfsync from any to any\n";
510
	$cprules .= "add 65292 set 1 allow carp from any to any\n";
511

    
512
	$cprules .= <<<EOD
513
# add 65300 set 1 skipto 65534 all from any to any not layer2
514
# layer 2: pass ARP
515
add 65301 set 1 pass layer2 mac-type arp
516
# pfsense requires for WPA
517
add 65302 set 1 pass layer2 mac-type 0x888e
518
add 65303 set 1 pass layer2 mac-type 0x88c7
519

    
520
# PPP Over Ethernet Discovery Stage
521
add 65304 set 1 pass layer2 mac-type 0x8863
522
# PPP Over Ethernet Session Stage
523
add 65305 set 1 pass layer2 mac-type 0x8864
524
# Allow WPA
525
add 65306 set 1 pass layer2 mac-type 0x888e
526

    
527
# layer 2: block anything else non-IP
528
add 65307 set 1 deny layer2 not mac-type ip
529

    
530
EOD;
531

    
532
	$rulenum = 65310;
533
	$ipcount = 0;
534
	foreach ($cpips as $cpip) {
535
		if($ipcount == 0) {
536
			$ips = "{$cpip} ";
537
		} else {
538
			$ips .= "or {$cpip} ";
539
		}
540
		$ipcount++;
541
	}
542
	$ips = "{ {$ips} }";
543
	$cprules .= "add {$rulenum} set 1 pass ip from any to {$ips} in\n";
544
	$rulenum++;
545
	$cprules .= "add {$rulenum} set 1 pass ip from {$ips} to any out\n";
546
	$rulenum++;
547
	$cprules .= "add {$rulenum} set 1 pass icmp from {$ips} to any out icmptype 0\n";
548
	$rulenum++;
549
	$cprules .= "add {$rulenum} set 1 pass icmp from any to {$ips} in icmptype 8 \n";
550
	$rulenum++;
551
	/* Allowed ips */
552
	$cprules .= "add {$rulenum} allow ip from table(3) to any in\n";
553
	$rulenum++;
554
	$cprules .= "add {$rulenum} allow ip from any to table(4) out\n";
555
	$rulenum++;
556
	$cprules .= "add {$rulenum} pipe tablearg ip from table(5) to any in\n";
557
	$rulenum++;
558
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(6) out\n";
559
	$rulenum++;
560
	$cprules .= "add {$rulenum} allow ip from any to table(7) in\n";
561
	$rulenum++;
562
	$cprules .= "add {$rulenum} allow ip from table(8) to any out\n";
563
	$rulenum++;
564
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(9) in\n";
565
	$rulenum++;
566
	$cprules .= "add {$rulenum} pipe tablearg ip from table(10) to any out\n";
567
	$rulenum++;
568

    
569
	/* Authenticated users rules. */
570
	if (isset($config['captiveportal']['peruserbw'])) {
571
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from table(1) to any in\n";
572
		$rulenum++;
573
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from any to table(2) out\n";
574
		$rulenum++;
575
	} else {
576
		$cprules .= "add {$rulenum} set 1 allow ip from table(1) to any in\n";
577
                $rulenum++;
578
                $cprules .= "add {$rulenum} set 1 allow ip from any to table(2) out\n";
579
                $rulenum++;
580
	}
581
	
582
       $cprules .= <<<EOD
583

    
584
# redirect non-authenticated clients to captive portal
585
add 65531 set 1 fwd 127.0.0.1,8000 tcp from any to any in
586
# let the responses from the captive portal web server back out
587
add 65532 set 1 pass tcp from any to any out
588
# block everything else
589
add 65533 set 1 deny all from any to any
590
# pass everything else on layer2
591
add 65534 set 1 pass all from any to any layer2
592

    
593
EOD;
594

    
595
	/* generate passthru mac database */
596
	$cprules .= captiveportal_passthrumac_configure(true);
597
	$cprules .= "\n";
598
	/* allowed ipfw rules to make allowed ip work */
599
	$cprules .= captiveportal_allowedip_configure();
600

    
601
	/* load rules */
602
	if ($reinit == true)
603
		$cprules = "table all flush\nflush\n{$cprules}";
604
	else {
605
		$tmprules = "table 3 flush\n";
606
		$tmprules .= "table 4 flush\n";
607
		$tmprules .= "table 5 flush\n";
608
		$tmprules .= "table 6 flush\n";
609
		$tmprules .= "table 7 flush\n";
610
		$tmprules .= "table 8 flush\n";
611
		$tmprules .= "table 9 flush\n";
612
		$tmprules .= "table 10 flush\n";
613
		$tmprules .= "flush\n";
614
		$cprules = "{$tmprules}\n{$cprules}";
615
	}
616

    
617
	file_put_contents("{$g['tmp_path']}/ipfw.cp.rules", $cprules);
618
	mwexec("/sbin/ipfw -q {$g['tmp_path']}/ipfw.cp.rules", true);
619
	@unlink("{$g['tmp_path']}/ipfw.cp.rules");
620

    
621
	if ($reinit == false)
622
		unlock($captiveportallck);
623

    
624

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

    
628
	return $cprules;
629
}
630

    
631
/* remove clients that have been around for longer than the specified amount of time */
632
/* db file structure:
633
timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time */
634

    
635
/* (password is in Base64 and only saved when reauthentication is enabled) */
636
function captiveportal_prune_old() {
637

    
638
    global $g, $config;
639

    
640
    /* check for expired entries */
641
    if ($config['captiveportal']['timeout'])
642
        $timeout = $config['captiveportal']['timeout'] * 60;
643
    else
644
        $timeout = 0;
645

    
646
    if ($config['captiveportal']['idletimeout'])
647
        $idletimeout = $config['captiveportal']['idletimeout'] * 60;
648
    else
649
        $idletimeout = 0;
650

    
651
    if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && 
652
		!isset($config['captiveportal']['radiussession_timeout']) && !isset($config['voucher']['enable']))
653
        return;
654

    
655
    $captiveportallck = lock('captiveportal');
656

    
657
    /* read database */
658
    $cpdb = captiveportal_read_db();
659

    
660
    $radiusservers = captiveportal_get_radius_servers();
661

    
662
    /*  To make sure we iterate over ALL accounts on every run the count($cpdb) is moved
663
     *  outside of the loop. Otherwise the loop would evaluate count() on every iteration
664
     *  and since $i would increase and count() would decrement they would meet before we
665
     *  had a chance to iterate over all accounts.
666
     */
667
    $unsetindexes = array();
668
    $no_users = count($cpdb);
669
    for ($i = 0; $i < $no_users; $i++) {
670

    
671
        $timedout = false;
672
        $term_cause = 1;
673

    
674
        /* hard timeout? */
675
        if ($timeout) {
676
            if ((time() - $cpdb[$i][0]) >= $timeout) {
677
                $timedout = true;
678
                $term_cause = 5; // Session-Timeout
679
            }
680
        }
681

    
682
        /* Session-Terminate-Time */
683
        if (!$timedout && !empty($cpdb[$i][9])) {
684
            if (time() >= $cpdb[$i][9]) {
685
                $timedout = true;
686
                $term_cause = 5; // Session-Timeout
687
            }
688
        }
689

    
690
        /* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
691
        $idletimeout = (is_numeric($cpdb[$i][8])) ? $cpdb[$i][8] : $idletimeout;
692
        /* if an idle timeout is specified, get last activity timestamp from ipfw */
693
        if (!$timedout && $idletimeout) {
694
            $lastact = captiveportal_get_last_activity($cpdb[$i][2]);
695
			/*  If the user has logged on but not sent any traffic they will never be logged out.
696
			 *  We "fix" this by setting lastact to the login timestamp. 
697
			 */
698
			$lastact = $lastact ? $lastact : $cpdb[$i][0];
699
            if ($lastact && ((time() - $lastact) >= $idletimeout)) {
700
                $timedout = true;
701
                $term_cause = 4; // Idle-Timeout
702
                $stop_time = $lastact; // Entry added to comply with WISPr
703
            }
704
        }
705

    
706
	/* if vouchers are configured, activate session timeouts */
707
	if (!$timedout && isset($config['voucher']['enable']) && !empty($cpdb[$i][7])) {
708
		if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
709
			$timedout = true;
710
			$term_cause = 5; // Session-Timeout
711
		}
712
	}
713

    
714
        /* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
715
        if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpdb[$i][7])) {
716
            if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
717
                $timedout = true;
718
                $term_cause = 5; // Session-Timeout
719
            }
720
        }
721

    
722
        if ($timedout) {
723
            captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
724
            captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
725
	    $unsetindexes[$i] = $i;
726
        }
727

    
728
        /* do periodic RADIUS reauthentication? */
729
        if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
730
            !empty($radiusservers)) {
731

    
732
            if (isset($config['captiveportal']['radacct_enable'])) {
733
                if ($config['captiveportal']['reauthenticateacct'] == "stopstart") {
734
                    /* stop and restart accounting */
735
                    RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
736
                                           $cpdb[$i][4], // username
737
                                           $cpdb[$i][5], // sessionid
738
                                           $cpdb[$i][0], // start time
739
                                           $radiusservers,
740
                                           $cpdb[$i][2], // clientip
741
                                           $cpdb[$i][3], // clientmac
742
                                           10); // NAS Request
743
                    exec("/sbin/ipfw table 1 entryzerostats {$cpdb[$i][2]}");
744
                    exec("/sbin/ipfw table 2 entryzerostats {$cpdb[$i][2]}");
745
                    RADIUS_ACCOUNTING_START($cpdb[$i][1], // ruleno
746
                                            $cpdb[$i][4], // username
747
                                            $cpdb[$i][5], // sessionid
748
                                            $radiusservers,
749
                                            $cpdb[$i][2], // clientip
750
                                            $cpdb[$i][3]); // clientmac
751
                } else if ($config['captiveportal']['reauthenticateacct'] == "interimupdate") {
752
                    RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
753
                                           $cpdb[$i][4], // username
754
                                           $cpdb[$i][5], // sessionid
755
                                           $cpdb[$i][0], // start time
756
                                           $radiusservers,
757
                                           $cpdb[$i][2], // clientip
758
                                           $cpdb[$i][3], // clientmac
759
                                           10, // NAS Request
760
                                           true); // Interim Updates
761
                }
762
            }
763

    
764
            /* check this user against RADIUS again */
765
            $auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
766
                                          base64_decode($cpdb[$i][6]), // password
767
                                            $radiusservers,
768
                                          $cpdb[$i][2], // clientip
769
                                          $cpdb[$i][3], // clientmac
770
                                          $cpdb[$i][1]); // ruleno
771

    
772
            if ($auth_list['auth_val'] == 3) {
773
                captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
774
                captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
775
	        $unsetindexes[$i] = $i;
776
            }
777
        }
778
    }
779
    /* This is a kludge to overcome some php weirdness */
780
    foreach($unsetindexes as $unsetindex)
781
	unset($cpdb[$unsetindex]);
782

    
783
    /* write database */
784
    captiveportal_write_db($cpdb);
785

    
786
    unlock($captiveportallck);
787
}
788

    
789
/* remove a single client according to the DB entry */
790
function captiveportal_disconnect($dbent, $radiusservers,$term_cause = 1,$stop_time = null) {
791

    
792
	global $g, $config;
793

    
794
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
795

    
796
	/* this client needs to be deleted - remove ipfw rules */
797
	if (isset($config['captiveportal']['radacct_enable']) && !empty($radiusservers)) {
798
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
799
							   $dbent[4], // username
800
							   $dbent[5], // sessionid
801
							   $dbent[0], // start time
802
							   $radiusservers,
803
							   $dbent[2], // clientip
804
							   $dbent[3], // clientmac
805
							   $term_cause, // Acct-Terminate-Cause
806
							   false,
807
							   $stop_time);
808
	}
809
	/* Delete client's ip entry from tables 3 and 4. */
810
	mwexec("/sbin/ipfw table 1 delete {$dbent[2]}");
811
	mwexec("/sbin/ipfw table 2 delete {$dbent[2]}");
812

    
813
	/* Release the ruleno so it can be reallocated to new clients. */
814
	captiveportal_free_ipfw_ruleno($dbent[1]);
815

    
816
	/* 
817
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
818
	* We could get an error if the pipe doesn't exist but everything should still be fine
819
	*/
820
	if (isset($config['captiveportal']['peruserbw'])) {
821
		mwexec("/sbin/ipfw pipe " . ($dbent[1]+20000) . " delete");
822
		mwexec("/sbin/ipfw pipe " . ($dbent[1]+20001) . " delete");
823
	}
824

    
825
	/* XXX: Redundant?! Ensure all pf(4) states are killed. */
826
	mwexec("pfctl -k {$dbent[2]}");
827
	mwexec("pfctl -K {$dbent[2]}");
828

    
829
}
830

    
831
/* remove a single client by ipfw rule number */
832
function captiveportal_disconnect_client($id,$term_cause = 1) {
833

    
834
	global $g, $config;
835

    
836
	$captiveportallck = lock('captiveportal');
837

    
838
	/* read database */
839
	$cpdb = captiveportal_read_db();
840
	$radiusservers = captiveportal_get_radius_servers();
841

    
842
	/* find entry */
843
	$tmpindex = 0;
844
	$cpdbcount = count($cpdb);
845
	for ($i = 0; $i < $cpdbcount; $i++) {
846
		if ($cpdb[$i][1] == $id) {
847
			captiveportal_disconnect($cpdb[$i], $radiusservers, $term_cause);
848
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "DISCONNECT");
849
			unset($cpdb[$i]);
850
			break;
851
		}
852
	}		
853

    
854
	/* write database */
855
	captiveportal_write_db($cpdb);
856

    
857
	unlock($captiveportallck);
858
}
859

    
860
/* send RADIUS acct stop for all current clients */
861
function captiveportal_radius_stop_all($lock = false) {
862
	global $g, $config;
863

    
864
	if (!isset($config['captiveportal']['radacct_enable']))
865
		return;
866

    
867
	if (!$lock)
868
		$captiveportallck = lock('captiveportal');
869

    
870
	$cpdb = captiveportal_read_db();
871

    
872
	$radiusservers = captiveportal_get_radius_servers();
873
	if (!empty($radiusservers)) {
874
		for ($i = 0; $i < count($cpdb); $i++) {
875
			RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
876
								   $cpdb[$i][4], // username
877
								   $cpdb[$i][5], // sessionid
878
								   $cpdb[$i][0], // start time
879
								   $radiusservers,
880
								   $cpdb[$i][2], // clientip
881
								   $cpdb[$i][3], // clientmac
882
								   7); // Admin Reboot
883
		}
884
	}
885
	if (!$lock)
886
		unlock($captiveportallck);
887
}
888

    
889
function captiveportal_passthrumac_configure_entry($macent) {
890
	$rules = "";
891
        $enBwup = isset($macent['bw_up']);
892
        $enBwdown = isset($macent['bw_down']);
893
	$actionup = "allow";
894
	$actiondown = "allow";
895

    
896
        if ($enBwup && $enBwdown)
897
                $ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
898
        else
899
                $ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
900

    
901
	if ($enBwup) {
902
                $bw_up = $ruleno + 20000;
903
                $rules .= "pipe {$bw_up} config bw {$macent['bw_up']}Kbit/s queue 100\n";
904
		$actionup = "pipe {$bw_up}";
905
        }
906
        if ($enBwdown) {
907
		$bw_down = $ruleno + 20001;
908
		$rules .= "pipe {$bw_down} config bw {$macent['bw_down']}Kbit/s queue 100\n";
909
		$actiondown = "pipe {$bw_down}";
910
        }
911
	$rules .= "add {$ruleno} {$actiondown} ip from any to any MAC {$macent['mac']} any\n";
912
	$ruleno++;
913
	$rules .= "add {$ruleno} {$actionup} ip from any to any MAC any {$macent['mac']}\n";
914

    
915
	return $rules;
916
}
917

    
918
function captiveportal_passthrumac_configure($lock = false) {
919
	global $config, $g;
920

    
921
	$rules = "";
922

    
923
	if (is_array($config['captiveportal']['passthrumac'])) {
924
		$macdb = array();
925
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
926
			$rules .= captiveportal_passthrumac_configure_entry($macent);
927
			$macdb[$macent['mac']]['active']  = true;
928

    
929
		}
930
	}
931

    
932
	return $rules;
933
}
934

    
935
function captiveportal_passthrumac_findbyname($username) {
936
	global $config;
937

    
938
	if (is_array($config['captiveportal']['passthrumac'])) {
939
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
940
			if ($macent['username'] == $username)
941
				return $macent;
942
		}
943
	}
944
	return NULL;
945
}
946

    
947
/* 
948
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
949
 * table (5=IN)/(6=OUT) hold allowed ip's with bw limit.
950
 */
951
function captiveportal_allowedip_configure_entry($ipent) {
952

    
953
	$rules = "";
954
	$enBwup = isset($ipent['bw_up']);
955
	$enBwdown = isset($ipent['bw_down']);
956
	$bw_up = "";
957
        $bw_down = "";
958
        $tablein = array();
959
        $tableout = array();
960

    
961
	if ($enBwup && $enBwdown)
962
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
963
	else
964
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
965

    
966
        if ($ipent['dir'] == "from") {
967
        	if ($enBwup)
968
                	$tablein[] = 5;
969
                else
970
                	$tablein[] = 3;
971
                if ($enBwdown)
972
                        $tableout[] = 6;
973
                else
974
                        $tableout[] = 4;
975
        } else if ($ipent['dir'] == "to") {
976
                if ($enBwup)
977
                	$tablein[] = 9;
978
                else
979
                        $tablein[] = 7;
980
                if ($enBwdown)
981
                        $tableout[] = 10;
982
                else
983
                        $tableout[] = 8;
984
        } else if ($ipent['dir'] == "both") {
985
                if ($enBwup) {
986
                        $tablein[] = 5;
987
                        $tablein[] = 9;
988
                } else {
989
                        $tablein[] = 3;
990
                        $tablein[] = 7;
991
                }
992
        	if ($enBwdown) {
993
                        $tableout[] = 6;
994
                        $tableout[] = 10;
995
                } else {
996
                        $tableout[] = 4;
997
                	$tableout[] = 8;
998
                }
999
        }
1000
        if ($enBwup) {
1001
                $bw_up = $ruleno + 20000;
1002
        	$rules .= "pipe {$bw_up} config bw {$ipent['bw_up']}Kbit/s queue 100\n";
1003
        }
1004
	$subnet = "";
1005
	if (!empty($ipent['sn']))
1006
		$subnet = "/{$ipent['sn']}";
1007
	foreach ($tablein as $table)
1008
               $rules .= "table {$table} add {$ipent['ip']}{$subnet} {$bw_up}\n";
1009
        if ($enBwdown) {
1010
               $bw_down = $ruleno + 20001;
1011
               $rules .= "pipe {$bw_down} config bw {$ipent['bw_down']}Kbit/s queue 100\n";
1012
        }
1013
        foreach ($tableout as $table)
1014
        	$rules .= "table {$table} add {$ipent['ip']}{$subnet} {$bw_down}\n";
1015

    
1016
	return $rules;
1017
}
1018

    
1019
function captiveportal_allowedip_configure() {
1020
	global $config, $g;
1021

    
1022
	$rules = "";
1023
	if (is_array($config['captiveportal']['allowedip'])) {
1024
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
1025
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1026
		}
1027
	}
1028

    
1029
	return $rules;
1030
}
1031

    
1032
/* get last activity timestamp given client IP address */
1033
function captiveportal_get_last_activity($ip) {
1034

    
1035
	$ipfwoutput = "";
1036

    
1037
	exec("/sbin/ipfw table 1 entrystats {$ip} 2>/dev/null", $ipfwoutput);
1038
	/* Reading only from one of the tables is enough of approximation. */
1039
	if ($ipfwoutput[0]) {
1040
		$ri = explode(" ", $ipfwoutput[0]);
1041
		if ($ri[4])
1042
			return $ri[4];
1043
	}
1044

    
1045
	return 0;
1046
}
1047

    
1048
/* read RADIUS servers into array */
1049
function captiveportal_get_radius_servers() {
1050

    
1051
        global $g;
1052

    
1053
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
1054
                $radiusservers = array();
1055
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius.db", 
1056
			FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1057
		if ($cpradiusdb)
1058
		foreach($cpradiusdb as $cpradiusentry) {
1059
                	$line = trim($cpradiusentry);
1060
                        if ($line) {
1061
                        	$radsrv = array();
1062
                                list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
1063
                        	$radiusservers[] = $radsrv;
1064
                        }
1065
		}
1066

    
1067
		return $radiusservers;
1068
        }
1069

    
1070
        return false;
1071
}
1072

    
1073
/* log successful captive portal authentication to syslog */
1074
/* part of this code from php.net */
1075
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1076
	$message = trim($message);
1077
	// Log it
1078
	if (!$message)
1079
		$message = "$status: $user, $mac, $ip";
1080
	else
1081
		$message = "$status: $user, $mac, $ip, $message";
1082
	captiveportal_syslog($message);
1083
	closelog();
1084
}
1085

    
1086
/* log simple messages to syslog */
1087
function captiveportal_syslog($message) {
1088
	define_syslog_variables();
1089
	$message = trim($message);
1090
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1091
	// Log it
1092
	syslog(LOG_INFO, $message);
1093
	closelog();
1094
}
1095

    
1096
function radius($username,$password,$clientip,$clientmac,$type) {
1097
    global $g, $config;
1098

    
1099
    /* Start locking from the beginning of an authentication session */
1100
    $captiveportallck = lock('captiveportal');
1101

    
1102
    $ruleno = captiveportal_get_next_ipfw_ruleno();
1103

    
1104
    /* If the pool is empty, return appropriate message and fail authentication */
1105
    if (is_null($ruleno)) {
1106
        $auth_list = array();
1107
        $auth_list['auth_val'] = 1;
1108
        $auth_list['error'] = "System reached maximum login capacity";
1109
        unlock($captiveportallck);
1110
        return $auth_list;
1111
    }
1112

    
1113
    /*
1114
     * Drop the lock since radius takes some time to finish.
1115
     * The implementation is reentrant so we gain speed with this.
1116
     */
1117
    unlock($captiveportallck);
1118

    
1119
    $radiusservers = captiveportal_get_radius_servers();
1120

    
1121
    $auth_list = RADIUS_AUTHENTICATION($username,
1122
                    $password,
1123
                    $radiusservers,
1124
                    $clientip,
1125
                    $clientmac,
1126
                    $ruleno);
1127

    
1128
    $captiveportallck = lock('captiveportal');
1129

    
1130
    if ($auth_list['auth_val'] == 2) {
1131
        captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1132
        $sessionid = portal_allow($clientip,
1133
                    $clientmac,
1134
                    $username,
1135
                    $password,
1136
                    $auth_list,
1137
                    $ruleno);
1138
    }
1139

    
1140
    unlock($captiveportallck);
1141

    
1142
    return $auth_list;
1143

    
1144
}
1145

    
1146
/* read captive portal DB into array */
1147
function captiveportal_read_db() {
1148

    
1149
        global $g;
1150

    
1151
        $cpdb = array();
1152
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
1153
        if ($fd) {
1154
                while (!feof($fd)) {
1155
                        $line = trim(fgets($fd));
1156
                        if ($line) {
1157
                                $cpdb[] = explode(",", $line);
1158
                        }
1159
                }
1160
                fclose($fd);
1161
        }
1162
        return $cpdb;
1163
}
1164

    
1165
/* write captive portal DB */
1166
function captiveportal_write_db($cpdb) {
1167
                 
1168
        global $g;
1169
                
1170
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
1171
        if ($fd) { 
1172
                foreach ($cpdb as $cpent) {
1173
                        fwrite($fd, join(",", $cpent) . "\n");
1174
                }       
1175
                fclose($fd);
1176
        }       
1177
}
1178

    
1179
function captiveportal_write_elements() {
1180
	global $g, $config;
1181
    
1182
	/* delete any existing elements */
1183
	if (is_dir($g['captiveportal_element_path'])) {
1184
		$dh = opendir($g['captiveportal_element_path']);
1185
		while (($file = readdir($dh)) !== false) {
1186
			if ($file != "." && $file != "..")
1187
				unlink($g['captiveportal_element_path'] . "/" . $file);
1188
		}
1189
		closedir($dh);
1190
	} else
1191
		@mkdir($g['captiveportal_element_path']);
1192

    
1193
	if (is_array($config['captiveportal']['element'])) {
1194
		conf_mount_rw();
1195
		foreach ($config['captiveportal']['element'] as $data) {
1196
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
1197
			if (!$fd) {
1198
				printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
1199
				return 1;
1200
			}
1201
			$decoded = base64_decode($data['content']);
1202
			fwrite($fd,$decoded);
1203
			fclose($fd);
1204
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1205
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1206
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
1207
		}
1208
		conf_mount_ro();
1209
	}
1210
    
1211
	return 0;
1212
}
1213

    
1214
function captiveportal_init_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
1215
	global $g;
1216

    
1217
	@unlink("{$g['vardb_path']}/captiveportal.rules");
1218
	$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1219
	file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1220
}
1221

    
1222
/*
1223
 * This function will calculate the lowest free firewall ruleno
1224
 * within the range specified based on the actual logged on users
1225
 *
1226
 */
1227
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899, $usebw = false) {
1228
	global $config, $g;
1229

    
1230
	if(!isset($config['captiveportal']['enable']))
1231
		return NULL;
1232

    
1233
	$ruleno = 0;
1234
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1235
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1236
		for ($ridx = 2; $ridx < ($rulenos_range_max - $rulenos_start); $ridx++) {
1237
			if ($rules[$ridx]) {
1238
				/* 
1239
	 			 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1240
	 			 * and the out pipe ruleno + 1. This removes limitation that where present in 
1241
	 			 * previous version of the peruserbw.
1242
	 			 */
1243
				if (isset($config['captiveportal']['peruserbw']))
1244
					$ridx++;
1245
				continue;
1246
			}
1247
			$ruleno = $ridx;
1248
			$rules[$ridx] = "used";
1249
			if (isset($config['captiveportal']['peruserbw']) || $usebw == true)
1250
				$rules[++$ridx] = "used";
1251
			break;
1252
		}
1253
	} else {
1254
		$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1255
		$rules[2] = "used";
1256
		$ruleno = 2;
1257
	}
1258
	file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1259
	return $ruleno;
1260
}
1261

    
1262
function captiveportal_free_ipfw_ruleno($ruleno, $usedbw = false) {
1263
	global $config, $g;
1264

    
1265
	if(!isset($config['captiveportal']['enable']))
1266
		return NULL;
1267

    
1268
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1269
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1270
		$rules[$ruleno] = false;
1271
		if (isset($config['captiveportal']['peruserbw']) || $usedbw == true)
1272
			$rules[++$ruleno] = false;
1273
		file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1274
	}
1275
}
1276

    
1277
function captiveportal_get_ipfw_passthru_ruleno($value) {
1278
	global $config, $g;
1279

    
1280
	if(!isset($config['captiveportal']['enable']))
1281
                return NULL;
1282

    
1283
        if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1284
                $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1285
		$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`);
1286
		if ($rules[$ruleno])
1287
			return $ruleno;
1288
        }
1289

    
1290
	return NULL;
1291
}
1292

    
1293
/**
1294
 * This function will calculate the traffic produced by a client
1295
 * based on its firewall rule
1296
 *
1297
 * Point of view: NAS
1298
 *
1299
 * Input means: from the client
1300
 * Output means: to the client
1301
 *
1302
 */
1303

    
1304
function getVolume($ip) {
1305

    
1306
    $volume = array();
1307

    
1308
    // Initialize vars properly, since we don't want NULL vars
1309
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1310

    
1311
    // Ingress
1312
    $ipfwin = "";
1313
    $ipfwout = "";
1314
    $matchesin = "";
1315
    $matchesout = "";
1316
    exec("/sbin/ipfw table 1 entrystats {$ip}", $ipfwin);
1317
    if ($ipfwin[0]) {
1318
		$ipfwin = split(" ", $ipfwin[0]);
1319
		$volume['input_pkts'] = $ipfwin[2];
1320
		$volume['input_bytes'] = $ipfwin[3];
1321
    }
1322

    
1323
    exec("/sbin/ipfw table 2 entrystats {$ip}", $ipfwout);
1324
    if ($ipfwout[0]) {
1325
        $ipfwout = split(" ", $ipfwout[0]);
1326
        $volume['output_pkts'] = $ipfwout[2];
1327
        $volume['output_bytes'] = $ipfwout[3];
1328
    }
1329

    
1330
    return $volume;
1331
}
1332

    
1333
/**
1334
 * Get the NAS-Identifier
1335
 *
1336
 * We will use our local hostname to make up the nas_id
1337
 */
1338
function getNasID()
1339
{
1340
    $nasId = "";
1341
    exec("/bin/hostname", $nasId);
1342
    if(!$nasId[0])
1343
        $nasId[0] = "{$g['product_name']}";
1344
    return $nasId[0];
1345
}
1346

    
1347
/**
1348
 * Get the NAS-IP-Address based on the current wan address
1349
 *
1350
 * Use functions in interfaces.inc to find this out
1351
 *
1352
 */
1353

    
1354
function getNasIP()
1355
{
1356
	global $config;
1357

    
1358
	if (empty($config['captiveportal']['radiussrcip_attribute']))
1359
    		$nasIp = get_interface_ip();
1360
	else {
1361
		if (is_ipaddr($config['captiveportal']['radiussrcip_attribute']))
1362
                        $nasIp = $config['captiveportal']['radiussrcip_attribute'];
1363
                else
1364
                        $nasIp = get_interface_ip($config['captiveportal']['radiussrcip_attribute']);
1365
	}
1366
		
1367
    	if(!is_ipaddr($nasIp))
1368
        	$nasIp = "0.0.0.0";
1369

    
1370
	return $nasIp;
1371
}
1372

    
1373
function portal_ip_from_client_ip($cliip) {
1374
	global $config;
1375

    
1376
	$interfaces = explode(",", $config['captiveportal']['interface']);
1377
	foreach ($interfaces as $cpif) {
1378
		$ip = get_interface_ip($cpif);
1379
		$sn = get_interface_subnet($cpif);
1380
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1381
			return $ip;
1382
	}
1383

    
1384
	// doesn't match up to any particular interface
1385
	// so let's set the portal IP to what PHP says 
1386
	// the server IP issuing the request is. 
1387
	// allows same behavior as 1.2.x where IP isn't 
1388
	// in the subnet of any CP interface (static routes, etc.)
1389
	// rather than forcing to DNS hostname resolution
1390
	$ip = $_SERVER['SERVER_ADDR'];
1391
	if (is_ipaddr($ip))
1392
		return $ip;
1393

    
1394
	return false;
1395
}
1396

    
1397
?>
(6-6/54)