Project

General

Profile

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

    
122
EOD;
123
		return $htmltext;
124
	}
125

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

    
201
EOD;
202

    
203
	return $htmltext;
204
}
205

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
346
EOD;
347
		}
348

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

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

    
393
document.location.href="<?=\$my_redirurl;?>";
394
-->
395
</SCRIPT>
396
</BODY>
397
</HTML>
398

    
399
EOD;
400
		}
401

    
402
		$fd = @fopen("{$g['varetc_path']}/captiveportal-logout.html", "w");
403
		if ($fd) {
404
			fwrite($fd, $logouttext);
405
			fclose($fd);
406
		}
407
		/* write elements */
408
		captiveportal_write_elements();
409

    
410
		/* start up the webserving daemon */
411
		captiveportal_init_webgui();
412

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

    
417
		/* generate radius server database */
418
		captiveportal_init_radius_servers();
419

    
420
		if ($g['booting'])
421
			echo "done\n";
422

    
423
	} else {
424
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
425
		killbypid("{$g['varrun_path']}/minicron.pid");
426

    
427
		captiveportal_radius_stop_all();
428

    
429
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
430

    
431
		/* unload ipfw */
432
		if (is_module_loaded("ipfw.ko"))		
433
			mwexec("/sbin/kldunload ipfw.ko");
434
		$listifs = get_configured_interface_list_by_realif();
435
		foreach ($listifs as $listrealif => $listif) {
436
			if (!empty($listrealif)) {
437
				if (does_interface_exist($listrealif)) {
438
					pfSense_interface_flags($listrealif, -IFF_IPFW_FILTER);
439
					$carpif = link_ip_to_carp_interface(find_interface_ip($listrealif));
440
                        		if (!empty($carpif)) {
441
						$carpsif = explode(" ", $carpif);
442
						foreach ($carpsif as $cpcarp)
443
							pfSense_interface_flags($cpcarp, -IFF_IPFW_FILTER);
444
					}
445
				}
446
			}
447
		}
448
	}
449

    
450
	unlock($captiveportallck);
451
	
452
	return 0;
453
}
454

    
455
function captiveportal_init_webgui() {
456
	global $g, $config;
457

    
458
	 if (!isset($config['captiveportal']['enable']))
459
                return;
460

    
461
	if ($config['captiveportal']['maxproc'])
462
		$maxproc = $config['captiveportal']['maxproc'];
463
	else
464
		$maxproc = 16;
465

    
466
	$use_fastcgi = true;
467

    
468
	if (isset($config['captiveportal']['httpslogin'])) {
469
		$cert = base64_decode($config['captiveportal']['certificate']);
470
		if (isset($config['captiveportal']['cacertificate']))
471
			$cacert = base64_decode($config['captiveportal']['cacertificate']);
472
		else
473
			$cacert = "";
474
		$key = base64_decode($config['captiveportal']['private-key']);
475
		/* generate lighttpd configuration */
476
		system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal-SSL.conf",
477
			$cert, $key, $cacert, "lighty-CaptivePortal-ssl.pid", "8001", "/usr/local/captiveportal/",
478
			"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
479
	}
480

    
481
	/* generate lighttpd configuration */
482
	system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal.conf",
483
		"", "", "", "lighty-CaptivePortal.pid", "8000", "/usr/local/captiveportal/",
484
		"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
485

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

    
489
	/* fire up https instance */
490
	if (isset($config['captiveportal']['httpslogin']))
491
		$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-CaptivePortal-SSL.conf");
492
}
493

    
494
function captiveportal_init_rules($reinit = false) {
495
	global $config, $g;
496

    
497
	if (!isset($config['captiveportal']['enable']))
498
		return;
499

    
500
	$cpips = array();
501
	$ifaces = get_configured_interface_list();
502
	foreach ($ifaces as $kiface => $kiface2) {
503
		$tmpif = get_real_interface($kiface);
504
		pfSense_interface_flags($tmpif, -IFF_IPFW_FILTER);
505
	}
506
	$cpinterfaces = explode(",", $config['captiveportal']['interface']);
507
	$firsttime = 0;
508
	foreach ($cpinterfaces as $cpifgrp) {
509
		if (!isset($ifaces[$cpifgrp]))
510
			continue;
511
		$tmpif = get_real_interface($cpifgrp);
512
		if (!empty($tmpif)) {
513
			if ($firsttime > 0)
514
				$cpinterface .= " or ";
515
			$cpinterface .= "via {$tmpif}";
516
			$firsttime = 1;
517
			$cpipm = get_interface_ip($cpifgrp);
518
			if (is_ipaddr($cpipm)) {
519
				$carpif = link_ip_to_carp_interface($cpipm);
520
				if (!empty($carpif)) {
521
					$carpsif = explode(" ", $carpif);
522
					foreach ($carpsif as $cpcarp) {
523
						pfSense_interface_flags($cpcarp, IFF_IPFW_FILTER);
524
						$carpip = find_interface_ip($cpcarp);
525
						if (is_ipaddr($carpip))
526
							$cpips[] = $carpip;
527
					}
528
				}
529
				$cpips[] = $cpipm;
530
				pfSense_interface_flags($tmpif, IFF_IPFW_FILTER);
531
			}
532
		}
533
	}
534
	if (count($cpips) > 0) {
535
		$cpactive = true;
536
		$cpinterface = "{ {$cpinterface} } ";
537
        } else
538
		return false;
539

    
540
	if ($reinit == false)
541
		$captiveportallck = lock('captiveportal');
542

    
543
	/* init dummynet/ipfw rules number database */
544
	captiveportal_init_ipfw_ruleno();
545

    
546
	/* make sure ipfw is loaded */
547
	if (!is_module_loaded("ipfw.ko"))
548
		filter_load_ipfw();
549
	/* Always load dummynet now that even allowed ip and mac passthrough use it. */
550
	if (!is_module_loaded("dummynet.ko"))
551
		mwexec("/sbin/kldload dummynet");
552

    
553
	$cprules =  "add 65291 set 1 allow pfsync from any to any\n";
554
	$cprules .= "add 65292 set 1 allow carp from any to any\n";
555

    
556
	$cprules .= <<<EOD
557
# add 65300 set 1 skipto 65534 all from any to any not layer2
558
# layer 2: pass ARP
559
add 65301 set 1 pass layer2 mac-type arp
560
# pfsense requires for WPA
561
add 65302 set 1 pass layer2 mac-type 0x888e
562
add 65303 set 1 pass layer2 mac-type 0x88c7
563

    
564
# PPP Over Ethernet Discovery Stage
565
add 65304 set 1 pass layer2 mac-type 0x8863
566
# PPP Over Ethernet Session Stage
567
add 65305 set 1 pass layer2 mac-type 0x8864
568
# Allow WPA
569
add 65306 set 1 pass layer2 mac-type 0x888e
570

    
571
# layer 2: block anything else non-IP
572
add 65307 set 1 deny layer2 not mac-type ip
573

    
574
EOD;
575

    
576
	$rulenum = 65310;
577
	$ipcount = 0;
578
	$ips = "";
579
	foreach ($cpips as $cpip) {
580
		if($ipcount == 0) {
581
			$ips = "{$cpip} ";
582
		} else {
583
			$ips .= "or {$cpip} ";
584
		}
585
		$ipcount++;
586
	}
587
	$ips = "{ 255.255.255.255 or {$ips} }";
588
	$cprules .= "add {$rulenum} set 1 pass ip from any to {$ips} in\n";
589
	$rulenum++;
590
	$cprules .= "add {$rulenum} set 1 pass ip from {$ips} to any out\n";
591
	$rulenum++;
592
	$cprules .= "add {$rulenum} set 1 pass icmp from {$ips} to any out icmptype 0\n";
593
	$rulenum++;
594
	$cprules .= "add {$rulenum} set 1 pass icmp from any to {$ips} in icmptype 8 \n";
595
	$rulenum++;
596
	/* Allowed ips */
597
	$cprules .= "add {$rulenum} allow ip from table(3) to any in\n";
598
	$rulenum++;
599
	$cprules .= "add {$rulenum} allow ip from any to table(4) out\n";
600
	$rulenum++;
601
	$cprules .= "add {$rulenum} pipe tablearg ip from table(5) to any in\n";
602
	$rulenum++;
603
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(6) out\n";
604
	$rulenum++;
605
	$cprules .= "add {$rulenum} allow ip from any to table(7) in\n";
606
	$rulenum++;
607
	$cprules .= "add {$rulenum} allow ip from table(8) to any out\n";
608
	$rulenum++;
609
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(9) in\n";
610
	$rulenum++;
611
	$cprules .= "add {$rulenum} pipe tablearg ip from table(10) to any out\n";
612
	$rulenum++;
613

    
614
	/* Authenticated users rules. */
615
	if (isset($config['captiveportal']['peruserbw'])) {
616
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from table(1) to any in\n";
617
		$rulenum++;
618
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from any to table(2) out\n";
619
		$rulenum++;
620
	} else {
621
		$cprules .= "add {$rulenum} set 1 allow ip from table(1) to any in\n";
622
                $rulenum++;
623
                $cprules .= "add {$rulenum} set 1 allow ip from any to table(2) out\n";
624
                $rulenum++;
625
	}
626
	
627
       $cprules .= <<<EOD
628

    
629
# redirect non-authenticated clients to captive portal
630
add 65531 set 1 fwd 127.0.0.1,8000 tcp from any to any in
631
# let the responses from the captive portal web server back out
632
add 65532 set 1 pass tcp from any to any out
633
# block everything else
634
add 65533 set 1 deny all from any to any
635
# pass everything else on layer2
636
add 65534 set 1 pass all from any to any layer2
637

    
638
EOD;
639

    
640
	/* generate passthru mac database */
641
	$cprules .= captiveportal_passthrumac_configure(true);
642
	$cprules .= "\n";
643
	/* allowed ipfw rules to make allowed ip work */
644
	$cprules .= captiveportal_allowedip_configure();
645

    
646
	/* load rules */
647
	if ($reinit == true)
648
		$cprules = "table all flush\nflush\n{$cprules}";
649
	else {
650
		$tmprules = "table 3 flush\n";
651
		$tmprules .= "table 4 flush\n";
652
		$tmprules .= "table 5 flush\n";
653
		$tmprules .= "table 6 flush\n";
654
		$tmprules .= "table 7 flush\n";
655
		$tmprules .= "table 8 flush\n";
656
		$tmprules .= "table 9 flush\n";
657
		$tmprules .= "table 10 flush\n";
658
		$tmprules .= "flush\n";
659
		$cprules = "{$tmprules}\n{$cprules}";
660
	}
661

    
662
	file_put_contents("{$g['tmp_path']}/ipfw.cp.rules", $cprules);
663
	mwexec("/sbin/ipfw -q {$g['tmp_path']}/ipfw.cp.rules", true);
664
	@unlink("{$g['tmp_path']}/ipfw.cp.rules");
665

    
666
	if ($reinit == false)
667
		unlock($captiveportallck);
668

    
669

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

    
673
	return $cprules;
674
}
675

    
676
/* remove clients that have been around for longer than the specified amount of time
677
 * db file structure:
678
 * timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time
679
 * (password is in Base64 and only saved when reauthentication is enabled)
680
 */
681
function captiveportal_prune_old() {
682
    global $g, $config;
683

    
684
    /* check for expired entries */
685
    if (empty($config['captiveportal']['timeout']) ||
686
	!is_numeric($config['captiveportal']['timeout']))
687
        $timeout = 0;
688
    else
689
        $timeout = $config['captiveportal']['timeout'] * 60;
690

    
691
    if (empty($config['captiveportal']['idletimeout']) ||
692
	!is_numeric($config['captiveportal']['idletimeout']))
693
        $idletimeout = 0;
694
    else
695
        $idletimeout = $config['captiveportal']['idletimeout'] * 60;
696

    
697
    if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && 
698
	!isset($config['captiveportal']['radiussession_timeout']) && !isset($config['voucher']['enable']))
699
        return;
700

    
701
    /* read database */
702
    $cpdb = captiveportal_read_db();
703

    
704
    $radiusservers = captiveportal_get_radius_servers();
705

    
706
    /*  To make sure we iterate over ALL accounts on every run the count($cpdb) is moved
707
     *  outside of the loop. Otherwise the loop would evaluate count() on every iteration
708
     *  and since $i would increase and count() would decrement they would meet before we
709
     *  had a chance to iterate over all accounts.
710
     */
711
    $unsetindexes = array();
712
    $no_users = count($cpdb);
713
    for ($i = 0; $i < $no_users; $i++) {
714

    
715
        $timedout = false;
716
        $term_cause = 1;
717

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

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

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

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

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

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

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

    
806
            /* check this user against RADIUS again */
807
	    if (isset($config['captiveportal']['reauthenticate'])) {
808
		    $auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
809
					  base64_decode($cpdb[$i][6]), // password
810
                                            $radiusservers,
811
                                          $cpdb[$i][2], // clientip
812
                                          $cpdb[$i][3], // clientmac
813
                                          $cpdb[$i][1]); // ruleno
814

    
815
		    if ($auth_list['auth_val'] == 3) {
816
			captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
817
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
818
			$unsetindexes[$i] = $i;
819
		    }
820
	    }
821
        }
822
    }
823
    /* This is a kludge to overcome some php weirdness */
824
    foreach($unsetindexes as $unsetindex)
825
	unset($cpdb[$unsetindex]);
826

    
827
    /* write database */
828
    captiveportal_write_db($cpdb);
829
}
830

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

    
835
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
836

    
837
	/* this client needs to be deleted - remove ipfw rules */
838
	if (isset($config['captiveportal']['radacct_enable']) && !empty($radiusservers)) {
839
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
840
				   $dbent[4], // username
841
				   $dbent[5], // sessionid
842
				   $dbent[0], // start time
843
				   $radiusservers,
844
				   $dbent[2], // clientip
845
				   $dbent[3], // clientmac
846
				   $term_cause, // Acct-Terminate-Cause
847
				   false,
848
				   $stop_time);
849
	}
850
	/* Delete client's ip entry from tables 3 and 4. */
851
	mwexec("/sbin/ipfw table 1 delete {$dbent[2]}");
852
	mwexec("/sbin/ipfw table 2 delete {$dbent[2]}");
853

    
854
	/* Release the ruleno so it can be reallocated to new clients. */
855
	captiveportal_free_ipfw_ruleno($dbent[1]);
856

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

    
866
	/* XXX: Redundant?! Ensure all pf(4) states are killed. */
867
	mwexec("pfctl -k {$dbent[2]}");
868
	mwexec("pfctl -K {$dbent[2]}");
869

    
870
}
871

    
872
/* remove a single client by ipfw rule number */
873
function captiveportal_disconnect_client($id,$term_cause = 1) {
874
	global $g, $config;
875

    
876
	/* read database */
877
	$cpdb = captiveportal_read_db();
878
	$radiusservers = captiveportal_get_radius_servers();
879

    
880
	/* find entry */
881
	foreach ($cpdb as $i => $cpentry) {
882
		if ($cpentry[1] == $id) {
883
			captiveportal_disconnect($cpentry, $radiusservers, $term_cause);
884
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
885
			unset($cpdb[$i]);
886
			break;
887
		}
888
	}		
889

    
890
	/* write database */
891
	captiveportal_write_db($cpdb);
892
}
893

    
894
/* send RADIUS acct stop for all current clients */
895
function captiveportal_radius_stop_all() {
896
	global $config;
897

    
898
	if (!isset($config['captiveportal']['radacct_enable']))
899
		return;
900

    
901
	$radiusservers = captiveportal_get_radius_servers();
902
	if (!empty($radiusservers)) {
903
		$cpdb = captiveportal_read_db();
904
		foreach ($cpdb as $cpentry) {
905
			RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
906
					   $cpentry[4], // username
907
					   $cpentry[5], // sessionid
908
					   $cpentry[0], // start time
909
					   $radiusservers,
910
					   $cpentry[2], // clientip
911
					   $cpentry[3], // clientmac
912
					   7); // Admin Reboot
913
		}
914
	}
915
}
916

    
917
function captiveportal_passthrumac_configure_entry($macent) {
918
	$rules = "";
919
        $enBwup = isset($macent['bw_up']);
920
        $enBwdown = isset($macent['bw_down']);
921
	$actionup = "allow";
922
	$actiondown = "allow";
923

    
924
        if ($enBwup && $enBwdown)
925
                $ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
926
        else
927
                $ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
928

    
929
	if ($enBwup) {
930
                $bw_up = $ruleno + 20000;
931
                $rules .= "pipe {$bw_up} config bw {$macent['bw_up']}Kbit/s queue 100\n";
932
		$actionup = "pipe {$bw_up}";
933
        }
934
        if ($enBwdown) {
935
		$bw_down = $ruleno + 20001;
936
		$rules .= "pipe {$bw_down} config bw {$macent['bw_down']}Kbit/s queue 100\n";
937
		$actiondown = "pipe {$bw_down}";
938
        }
939
	$rules .= "add {$ruleno} {$actiondown} ip from any to any MAC {$macent['mac']} any\n";
940
	$ruleno++;
941
	$rules .= "add {$ruleno} {$actionup} ip from any to any MAC any {$macent['mac']}\n";
942

    
943
	return $rules;
944
}
945

    
946
function captiveportal_passthrumac_configure($lock = false) {
947
	global $config, $g;
948

    
949
	$rules = "";
950

    
951
	if (is_array($config['captiveportal']['passthrumac'])) {
952
		$macdb = array();
953
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
954
			$rules .= captiveportal_passthrumac_configure_entry($macent);
955
			$macdb[$macent['mac']]['active']  = true;
956

    
957
		}
958
	}
959

    
960
	return $rules;
961
}
962

    
963
function captiveportal_passthrumac_findbyname($username) {
964
	global $config;
965

    
966
	if (is_array($config['captiveportal']['passthrumac'])) {
967
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
968
			if ($macent['username'] == $username)
969
				return $macent;
970
		}
971
	}
972
	return NULL;
973
}
974

    
975
/* 
976
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
977
 * table (5=IN)/(6=OUT) hold allowed ip's with bw limit.
978
 */
979
function captiveportal_allowedip_configure_entry($ipent) {
980

    
981
	$rules = "";
982
	$enBwup = isset($ipent['bw_up']);
983
	$enBwdown = isset($ipent['bw_down']);
984
	$bw_up = "";
985
        $bw_down = "";
986
        $tablein = array();
987
        $tableout = array();
988

    
989
	if ($enBwup && $enBwdown)
990
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
991
	else
992
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
993

    
994
        if ($ipent['dir'] == "from") {
995
        	if ($enBwup)
996
                	$tablein[] = 5;
997
                else
998
                	$tablein[] = 3;
999
                if ($enBwdown)
1000
                        $tableout[] = 6;
1001
                else
1002
                        $tableout[] = 4;
1003
        } else if ($ipent['dir'] == "to") {
1004
                if ($enBwup)
1005
                	$tablein[] = 9;
1006
                else
1007
                        $tablein[] = 7;
1008
                if ($enBwdown)
1009
                        $tableout[] = 10;
1010
                else
1011
                        $tableout[] = 8;
1012
        } else if ($ipent['dir'] == "both") {
1013
                if ($enBwup) {
1014
                        $tablein[] = 5;
1015
                        $tablein[] = 9;
1016
                } else {
1017
                        $tablein[] = 3;
1018
                        $tablein[] = 7;
1019
                }
1020
        	if ($enBwdown) {
1021
                        $tableout[] = 6;
1022
                        $tableout[] = 10;
1023
                } else {
1024
                        $tableout[] = 4;
1025
                	$tableout[] = 8;
1026
                }
1027
        }
1028
        if ($enBwup) {
1029
                $bw_up = $ruleno + 20000;
1030
        	$rules .= "pipe {$bw_up} config bw {$ipent['bw_up']}Kbit/s queue 100\n";
1031
        }
1032
	$subnet = "";
1033
	if (!empty($ipent['sn']))
1034
		$subnet = "/{$ipent['sn']}";
1035
	foreach ($tablein as $table)
1036
               $rules .= "table {$table} add {$ipent['ip']}{$subnet} {$bw_up}\n";
1037
        if ($enBwdown) {
1038
               $bw_down = $ruleno + 20001;
1039
               $rules .= "pipe {$bw_down} config bw {$ipent['bw_down']}Kbit/s queue 100\n";
1040
        }
1041
        foreach ($tableout as $table)
1042
        	$rules .= "table {$table} add {$ipent['ip']}{$subnet} {$bw_down}\n";
1043

    
1044
	return $rules;
1045
}
1046

    
1047
function captiveportal_allowedip_configure() {
1048
	global $config, $g;
1049

    
1050
	$rules = "";
1051
	if (is_array($config['captiveportal']['allowedip'])) {
1052
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
1053
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1054
		}
1055
	}
1056

    
1057
	return $rules;
1058
}
1059

    
1060
/* get last activity timestamp given client IP address */
1061
function captiveportal_get_last_activity($ip) {
1062

    
1063
	$ipfwoutput = "";
1064

    
1065
	exec("/sbin/ipfw table 1 entrystats {$ip} 2>/dev/null", $ipfwoutput);
1066
	/* Reading only from one of the tables is enough of approximation. */
1067
	if ($ipfwoutput[0]) {
1068
		$ri = explode(" ", $ipfwoutput[0]);
1069
		if ($ri[4])
1070
			return $ri[4];
1071
	}
1072

    
1073
	return 0;
1074
}
1075

    
1076
function captiveportal_init_radius_servers() {
1077
	global $config, $g;
1078

    
1079
	/* generate radius server database */
1080
	if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
1081
	    ($config['captiveportal']['auth_method'] == "radius"))) {
1082
		$radiusip = $config['captiveportal']['radiusip'];
1083
		$radiusip2 = ($config['captiveportal']['radiusip2']) ? $config['captiveportal']['radiusip2'] : null;
1084

    
1085
		if ($config['captiveportal']['radiusport'])
1086
			$radiusport = $config['captiveportal']['radiusport'];
1087
		else
1088
			$radiusport = 1812;
1089
		if ($config['captiveportal']['radiusacctport'])
1090
			$radiusacctport = $config['captiveportal']['radiusacctport'];
1091
		else
1092
			$radiusacctport = 1813;
1093
		if ($config['captiveportal']['radiusport2'])
1094
			$radiusport2 = $config['captiveportal']['radiusport2'];
1095
		else
1096
			$radiusport2 = 1812;
1097
		$radiuskey = $config['captiveportal']['radiuskey'];
1098
		$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
1099

    
1100
		$cprdsrvlck = lock('captiveportalradius', LOCK_EX);
1101
		$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db", "w");
1102
		if (!$fd) {
1103
			captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
1104
			unlock($cprdsrvlck);
1105
			return 1;
1106
		} else if (isset($radiusip2, $radiuskey2))
1107
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . "\n"
1108
			. $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2);
1109
		else
1110
			fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey);
1111
		fclose($fd);
1112
		unlock($cprdsrvlck);
1113
	}
1114
}
1115

    
1116
/* read RADIUS servers into array */
1117
function captiveportal_get_radius_servers() {
1118
        global $g;
1119

    
1120
	$cprdsrvlck = lock('captiveportalradius');
1121
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
1122
                $radiusservers = array();
1123
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius.db", 
1124
			FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1125
		if ($cpradiusdb)
1126
		foreach($cpradiusdb as $cpradiusentry) {
1127
                	$line = trim($cpradiusentry);
1128
                        if ($line) {
1129
                        	$radsrv = array();
1130
                                list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
1131
                        	$radiusservers[] = $radsrv;
1132
                        }
1133
		}
1134

    
1135
		unlock($cprdsrvlck);
1136
		return $radiusservers;
1137
        }
1138

    
1139
	unlock($cprdsrvlck);
1140
        return false;
1141
}
1142

    
1143
/* log successful captive portal authentication to syslog */
1144
/* part of this code from php.net */
1145
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1146
	// Log it
1147
	if (!$message)
1148
		$message = "$status: $user, $mac, $ip";
1149
	else {
1150
		$message = trim($message);
1151
		$message = "$status: $user, $mac, $ip, $message";
1152
	}
1153
	captiveportal_syslog($message);
1154
}
1155

    
1156
/* log simple messages to syslog */
1157
function captiveportal_syslog($message) {
1158
	define_syslog_variables();
1159
	$message = trim($message);
1160
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1161
	// Log it
1162
	syslog(LOG_INFO, $message);
1163
	closelog();
1164
}
1165

    
1166
function radius($username,$password,$clientip,$clientmac,$type) {
1167
    global $g, $config;
1168

    
1169
    $ruleno = captiveportal_get_next_ipfw_ruleno();
1170

    
1171
    /* If the pool is empty, return appropriate message and fail authentication */
1172
    if (is_null($ruleno)) {
1173
        $auth_list = array();
1174
        $auth_list['auth_val'] = 1;
1175
        $auth_list['error'] = "System reached maximum login capacity";
1176
        return $auth_list;
1177
    }
1178

    
1179
    $radiusservers = captiveportal_get_radius_servers();
1180

    
1181
    $auth_list = RADIUS_AUTHENTICATION($username,
1182
                    $password,
1183
                    $radiusservers,
1184
                    $clientip,
1185
                    $clientmac,
1186
                    $ruleno);
1187

    
1188
    if ($auth_list['auth_val'] == 2) {
1189
        captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1190
        $sessionid = portal_allow($clientip,
1191
                    $clientmac,
1192
                    $username,
1193
                    $password,
1194
                    $auth_list,
1195
                    $ruleno);
1196
    }
1197

    
1198
    return $auth_list;
1199
}
1200

    
1201
/* read captive portal DB into array */
1202
function captiveportal_read_db() {
1203
        global $g;
1204

    
1205
        $cpdb = array();
1206

    
1207
	$cpdblck = lock('captiveportaldb');
1208
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
1209
        if ($fd) {
1210
                while (!feof($fd)) {
1211
                        $line = trim(fgets($fd));
1212
                        if ($line) {
1213
                                $cpdb[] = explode(",", $line);
1214
                        }
1215
                }
1216
                fclose($fd);
1217
        }
1218
	unlock($cpdblck);
1219
        return $cpdb;
1220
}
1221

    
1222
/* write captive portal DB */
1223
function captiveportal_write_db($cpdb) {
1224
        global $g;
1225

    
1226
	$cpdblck = lock('captiveportaldb', LOCK_EX);
1227
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
1228
        if ($fd) {
1229
                foreach ($cpdb as $cpent) {
1230
                        fwrite($fd, join(",", $cpent) . "\n");
1231
                }
1232
                fclose($fd);
1233
        }
1234
	unlock($cpdblck);
1235
}
1236

    
1237
function captiveportal_write_elements() {
1238
	global $g, $config;
1239
    
1240
	/* delete any existing elements */
1241
	if (is_dir($g['captiveportal_element_path'])) {
1242
		$dh = opendir($g['captiveportal_element_path']);
1243
		while (($file = readdir($dh)) !== false) {
1244
			if ($file != "." && $file != "..")
1245
				unlink($g['captiveportal_element_path'] . "/" . $file);
1246
		}
1247
		closedir($dh);
1248
	} else
1249
		@mkdir($g['captiveportal_element_path']);
1250

    
1251
	if (is_array($config['captiveportal']['element'])) {
1252
		conf_mount_rw();
1253
		foreach ($config['captiveportal']['element'] as $data) {
1254
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
1255
			if (!$fd) {
1256
				printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
1257
				return 1;
1258
			}
1259
			$decoded = base64_decode($data['content']);
1260
			fwrite($fd,$decoded);
1261
			fclose($fd);
1262
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1263
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1264
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
1265
		}
1266
		conf_mount_ro();
1267
	}
1268
    
1269
	return 0;
1270
}
1271

    
1272
function captiveportal_init_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
1273
	global $g;
1274

    
1275
	@unlink("{$g['vardb_path']}/captiveportal.rules");
1276
	$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1277
	file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1278
}
1279

    
1280
/*
1281
 * This function will calculate the lowest free firewall ruleno
1282
 * within the range specified based on the actual logged on users
1283
 *
1284
 */
1285
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899, $usebw = false) {
1286
	global $config, $g;
1287

    
1288
	if(!isset($config['captiveportal']['enable']))
1289
		return NULL;
1290

    
1291
	$cpruleslck = lock('captiveportalrules', LOCK_EX);
1292
	$ruleno = 0;
1293
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1294
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1295
		for ($ridx = 2; $ridx < ($rulenos_range_max - $rulenos_start); $ridx++) {
1296
			if ($rules[$ridx]) {
1297
				/* 
1298
	 			 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1299
	 			 * and the out pipe ruleno + 1. This removes limitation that where present in 
1300
	 			 * previous version of the peruserbw.
1301
	 			 */
1302
				if (isset($config['captiveportal']['peruserbw']))
1303
					$ridx++;
1304
				continue;
1305
			}
1306
			$ruleno = $ridx;
1307
			$rules[$ridx] = "used";
1308
			if (isset($config['captiveportal']['peruserbw']) || $usebw == true)
1309
				$rules[++$ridx] = "used";
1310
			break;
1311
		}
1312
	} else {
1313
		$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1314
		$rules[2] = "used";
1315
		$ruleno = 2;
1316
	}
1317
	file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1318
	unlock($cpruleslck);
1319
	return $ruleno;
1320
}
1321

    
1322
function captiveportal_free_ipfw_ruleno($ruleno, $usedbw = false) {
1323
	global $config, $g;
1324

    
1325
	if(!isset($config['captiveportal']['enable']))
1326
		return NULL;
1327

    
1328
	$cpruleslck = lock('captiveportalrules', LOCK_EX);
1329
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1330
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1331
		$rules[$ruleno] = false;
1332
		if (isset($config['captiveportal']['peruserbw']) || $usedbw == true)
1333
			$rules[++$ruleno] = false;
1334
		file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1335
	}
1336
	unlock($cpruleslck);
1337
}
1338

    
1339
function captiveportal_get_ipfw_passthru_ruleno($value) {
1340
	global $config, $g;
1341

    
1342
	if(!isset($config['captiveportal']['enable']))
1343
                return NULL;
1344

    
1345
	$cpruleslck = lock('captiveportalrules', LOCK_EX);
1346
        if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1347
                $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1348
		$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`);
1349
		if ($rules[$ruleno]) {
1350
			unlock($cpruleslck);
1351
			return $ruleno;
1352
		}
1353
        }
1354

    
1355
	unlock($cpruleslck);
1356
	return NULL;
1357
}
1358

    
1359
/**
1360
 * This function will calculate the traffic produced by a client
1361
 * based on its firewall rule
1362
 *
1363
 * Point of view: NAS
1364
 *
1365
 * Input means: from the client
1366
 * Output means: to the client
1367
 *
1368
 */
1369

    
1370
function getVolume($ip) {
1371

    
1372
    $volume = array();
1373

    
1374
    // Initialize vars properly, since we don't want NULL vars
1375
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1376

    
1377
    // Ingress
1378
    $ipfwin = "";
1379
    $ipfwout = "";
1380
    $matchesin = "";
1381
    $matchesout = "";
1382
    exec("/sbin/ipfw table 1 entrystats {$ip}", $ipfwin);
1383
    if ($ipfwin[0]) {
1384
		$ipfwin = split(" ", $ipfwin[0]);
1385
		$volume['input_pkts'] = $ipfwin[2];
1386
		$volume['input_bytes'] = $ipfwin[3];
1387
    }
1388

    
1389
    exec("/sbin/ipfw table 2 entrystats {$ip}", $ipfwout);
1390
    if ($ipfwout[0]) {
1391
        $ipfwout = split(" ", $ipfwout[0]);
1392
        $volume['output_pkts'] = $ipfwout[2];
1393
        $volume['output_bytes'] = $ipfwout[3];
1394
    }
1395

    
1396
    return $volume;
1397
}
1398

    
1399
/**
1400
 * Get the NAS-Identifier
1401
 *
1402
 * We will use our local hostname to make up the nas_id
1403
 */
1404
function getNasID()
1405
{
1406
    $nasId = "";
1407
    exec("/bin/hostname", $nasId);
1408
    if(!$nasId[0])
1409
        $nasId[0] = "{$g['product_name']}";
1410
    return $nasId[0];
1411
}
1412

    
1413
/**
1414
 * Get the NAS-IP-Address based on the current wan address
1415
 *
1416
 * Use functions in interfaces.inc to find this out
1417
 *
1418
 */
1419

    
1420
function getNasIP()
1421
{
1422
	global $config;
1423

    
1424
	if (empty($config['captiveportal']['radiussrcip_attribute']))
1425
    		$nasIp = get_interface_ip();
1426
	else {
1427
		if (is_ipaddr($config['captiveportal']['radiussrcip_attribute']))
1428
                        $nasIp = $config['captiveportal']['radiussrcip_attribute'];
1429
                else
1430
                        $nasIp = get_interface_ip($config['captiveportal']['radiussrcip_attribute']);
1431
	}
1432
		
1433
    	if(!is_ipaddr($nasIp))
1434
        	$nasIp = "0.0.0.0";
1435

    
1436
	return $nasIp;
1437
}
1438

    
1439
function portal_ip_from_client_ip($cliip) {
1440
	global $config;
1441

    
1442
	$interfaces = explode(",", $config['captiveportal']['interface']);
1443
	foreach ($interfaces as $cpif) {
1444
		$ip = get_interface_ip($cpif);
1445
		$sn = get_interface_subnet($cpif);
1446
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1447
			return $ip;
1448
	}
1449

    
1450
	// doesn't match up to any particular interface
1451
	// so let's set the portal IP to what PHP says 
1452
	// the server IP issuing the request is. 
1453
	// allows same behavior as 1.2.x where IP isn't 
1454
	// in the subnet of any CP interface (static routes, etc.)
1455
	// rather than forcing to DNS hostname resolution
1456
	$ip = $_SERVER['SERVER_ADDR'];
1457
	if (is_ipaddr($ip))
1458
		return $ip;
1459

    
1460
	return false;
1461
}
1462

    
1463
?>
(6-6/54)