Project

General

Profile

Download (47.6 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');
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(true);
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)) { $croninterval = 60; }
243

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

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

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

    
345
EOD;
346
		}
347

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

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

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

    
398
EOD;
399
		}
400

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

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

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

    
416
		/* generate radius server database */
417
		if ($config['captiveportal']['radiusip'] && (!isset($config['captiveportal']['auth_method']) ||
418
				($config['captiveportal']['auth_method'] == "radius"))) {
419
			$radiusip = $config['captiveportal']['radiusip'];
420
			$radiusip2 = ($config['captiveportal']['radiusip2']) ? $config['captiveportal']['radiusip2'] : null;
421

    
422
			if ($config['captiveportal']['radiusport'])
423
				$radiusport = $config['captiveportal']['radiusport'];
424
			else
425
				$radiusport = 1812;
426

    
427
			if ($config['captiveportal']['radiusacctport'])
428
				$radiusacctport = $config['captiveportal']['radiusacctport'];
429
			else
430
				$radiusacctport = 1813;
431

    
432
			if ($config['captiveportal']['radiusport2'])
433
				$radiusport2 = $config['captiveportal']['radiusport2'];
434
			else
435
				$radiusport2 = 1812;
436

    
437
			$radiuskey = $config['captiveportal']['radiuskey'];
438
			$radiuskey2 = ($config['captiveportal']['radiuskey2']) ? $config['captiveportal']['radiuskey2'] : null;
439

    
440
			$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db", "w");
441
			if (!$fd) {
442
				printf("Error: cannot open radius DB file in captiveportal_configure().\n");
443
				return 1;
444
			} else if (isset($radiusip2, $radiuskey2)) {
445
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . "\n"
446
					 . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2);
447
			} else {
448
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey);
449
			}
450
			fclose($fd);
451
		}
452

    
453
		if ($g['booting'])
454
			echo "done\n";
455

    
456
	} else {
457
		killbypid("{$g['varrun_path']}/lighty-CaptivePortal.pid");
458
		killbypid("{$g['varrun_path']}/minicron.pid");
459

    
460
		captiveportal_radius_stop_all(true);
461

    
462
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
463

    
464
		/* unload ipfw */
465
		if (is_module_loaded("ipfw.ko"))		
466
			mwexec("/sbin/kldunload ipfw.ko");
467
		$listifs = get_configured_interface_list_by_realif();
468
		foreach ($listifs as $listrealif => $listif) {
469
			if (!empty($listrealif)) {
470
				if (does_interface_exist($listrealif)) {
471
					pfSense_interface_flags($listrealif, -IFF_IPFW_FILTER);
472
					$carpif = link_ip_to_carp_interface(find_interface_ip($listrealif));
473
                        		if (!empty($carpif)) {
474
						$carpsif = explode(" ", $carpif);
475
						foreach ($carpsif as $cpcarp)
476
							pfSense_interface_flags($cpcarp, -IFF_IPFW_FILTER);
477
					}
478
				}
479
			}
480
		}
481
	}
482

    
483
	unlock($captiveportallck);
484
	
485
	return 0;
486
}
487

    
488
function captiveportal_init_webgui() {
489
	global $g, $config;
490

    
491
	 if (!isset($config['captiveportal']['enable']))
492
                return;
493

    
494
	if ($config['captiveportal']['maxproc'])
495
		$maxproc = $config['captiveportal']['maxproc'];
496
	else
497
		$maxproc = 16;
498

    
499
	$use_fastcgi = true;
500

    
501
	if (isset($config['captiveportal']['httpslogin'])) {
502
		$cert = base64_decode($config['captiveportal']['certificate']);
503
		if (isset($config['captiveportal']['cacertificate']))
504
			$cacert = base64_decode($config['captiveportal']['cacertificate']);
505
		else
506
			$cacert = "";
507
		$key = base64_decode($config['captiveportal']['private-key']);
508
		/* generate lighttpd configuration */
509
		system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal-SSL.conf",
510
			$cert, $key, $cacert, "lighty-CaptivePortal-ssl.pid", "8001", "/usr/local/captiveportal/",
511
			"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
512
	}
513

    
514
	/* generate lighttpd configuration */
515
	system_generate_lighty_config("{$g['varetc_path']}/lighty-CaptivePortal.conf",
516
		"", "", "", "lighty-CaptivePortal.pid", "8000", "/usr/local/captiveportal/",
517
		"cert-portal.pem", "ca-portal.pem", "1", $maxproc, $use_fastcgi, true);
518

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

    
522
	/* fire up https instance */
523
	if (isset($config['captiveportal']['httpslogin']))
524
		$res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-CaptivePortal-SSL.conf");
525
}
526

    
527
function captiveportal_init_rules($reinit = false) {
528
	global $config, $g;
529

    
530
	if (!isset($config['captiveportal']['enable']))
531
		return;
532

    
533
	$cpips = array();
534
	$ifaces = get_configured_interface_list();
535
	foreach ($ifaces as $kiface => $kiface2) {
536
		$tmpif = get_real_interface($kiface);
537
		pfSense_interface_flags($tmpif, -IFF_IPFW_FILTER);
538
	}
539
	$cpinterfaces = explode(",", $config['captiveportal']['interface']);
540
	$firsttime = 0;
541
	foreach ($cpinterfaces as $cpifgrp) {
542
		if (!isset($ifaces[$cpifgrp]))
543
			continue;
544
		$tmpif = get_real_interface($cpifgrp);
545
		if (!empty($tmpif)) {
546
			if ($firsttime > 0)
547
				$cpinterface .= " or ";
548
			$cpinterface .= "via {$tmpif}";
549
			$firsttime = 1;
550
			$cpipm = get_interface_ip($cpifgrp);
551
			if (is_ipaddr($cpipm)) {
552
				$carpif = link_ip_to_carp_interface($cpipm);
553
				if (!empty($carpif)) {
554
					$carpsif = explode(" ", $carpif);
555
					foreach ($carpsif as $cpcarp) {
556
						pfSense_interface_flags($cpcarp, IFF_IPFW_FILTER);
557
						$carpip = find_interface_ip($cpcarp);
558
						if (is_ipaddr($carpip))
559
							$cpips[] = $carpip;
560
					}
561
				}
562
				$cpips[] = $cpipm;
563
				pfSense_interface_flags($tmpif, IFF_IPFW_FILTER);
564
			}
565
		}
566
	}
567
	if (count($cpips) > 0) {
568
		$cpactive = true;
569
		$cpinterface = "{ {$cpinterface} } ";
570
        } else
571
		return false;
572

    
573
	if ($reinit == false)
574
		$captiveportallck = lock('captiveportal');
575

    
576
	/* init dummynet/ipfw rules number database */
577
	captiveportal_init_ipfw_ruleno();
578

    
579
	/* make sure ipfw is loaded */
580
	if (!is_module_loaded("ipfw.ko"))
581
		filter_load_ipfw();
582
	/* Always load dummynet now that even allowed ip and mac passthrough use it. */
583
	if (!is_module_loaded("dummynet.ko"))
584
		mwexec("/sbin/kldload dummynet");
585

    
586
	$cprules =  "add 65291 set 1 allow pfsync from any to any\n";
587
	$cprules .= "add 65292 set 1 allow carp from any to any\n";
588

    
589
	$cprules .= <<<EOD
590
# add 65300 set 1 skipto 65534 all from any to any not layer2
591
# layer 2: pass ARP
592
add 65301 set 1 pass layer2 mac-type arp
593
# pfsense requires for WPA
594
add 65302 set 1 pass layer2 mac-type 0x888e
595
add 65303 set 1 pass layer2 mac-type 0x88c7
596

    
597
# PPP Over Ethernet Discovery Stage
598
add 65304 set 1 pass layer2 mac-type 0x8863
599
# PPP Over Ethernet Session Stage
600
add 65305 set 1 pass layer2 mac-type 0x8864
601
# Allow WPA
602
add 65306 set 1 pass layer2 mac-type 0x888e
603

    
604
# layer 2: block anything else non-IP
605
add 65307 set 1 deny layer2 not mac-type ip
606

    
607
EOD;
608

    
609
	$rulenum = 65310;
610
	$ipcount = 0;
611
	$ips = "";
612
	foreach ($cpips as $cpip) {
613
		if($ipcount == 0) {
614
			$ips = "{$cpip} ";
615
		} else {
616
			$ips .= "or {$cpip} ";
617
		}
618
		$ipcount++;
619
	}
620
	$ips = "{ 255.255.255.255 or {$ips} }";
621
	$cprules .= "add {$rulenum} set 1 pass ip from any to {$ips} in\n";
622
	$rulenum++;
623
	$cprules .= "add {$rulenum} set 1 pass ip from {$ips} to any out\n";
624
	$rulenum++;
625
	$cprules .= "add {$rulenum} set 1 pass icmp from {$ips} to any out icmptype 0\n";
626
	$rulenum++;
627
	$cprules .= "add {$rulenum} set 1 pass icmp from any to {$ips} in icmptype 8 \n";
628
	$rulenum++;
629
	/* Allowed ips */
630
	$cprules .= "add {$rulenum} allow ip from table(3) to any in\n";
631
	$rulenum++;
632
	$cprules .= "add {$rulenum} allow ip from any to table(4) out\n";
633
	$rulenum++;
634
	$cprules .= "add {$rulenum} pipe tablearg ip from table(5) to any in\n";
635
	$rulenum++;
636
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(6) out\n";
637
	$rulenum++;
638
	$cprules .= "add {$rulenum} allow ip from any to table(7) in\n";
639
	$rulenum++;
640
	$cprules .= "add {$rulenum} allow ip from table(8) to any out\n";
641
	$rulenum++;
642
	$cprules .= "add {$rulenum} pipe tablearg ip from any to table(9) in\n";
643
	$rulenum++;
644
	$cprules .= "add {$rulenum} pipe tablearg ip from table(10) to any out\n";
645
	$rulenum++;
646

    
647
	/* Authenticated users rules. */
648
	if (isset($config['captiveportal']['peruserbw'])) {
649
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from table(1) to any in\n";
650
		$rulenum++;
651
		$cprules .= "add {$rulenum} set 1 pipe tablearg ip from any to table(2) out\n";
652
		$rulenum++;
653
	} else {
654
		$cprules .= "add {$rulenum} set 1 allow ip from table(1) to any in\n";
655
                $rulenum++;
656
                $cprules .= "add {$rulenum} set 1 allow ip from any to table(2) out\n";
657
                $rulenum++;
658
	}
659
	
660
       $cprules .= <<<EOD
661

    
662
# redirect non-authenticated clients to captive portal
663
add 65531 set 1 fwd 127.0.0.1,8000 tcp from any to any in
664
# let the responses from the captive portal web server back out
665
add 65532 set 1 pass tcp from any to any out
666
# block everything else
667
add 65533 set 1 deny all from any to any
668
# pass everything else on layer2
669
add 65534 set 1 pass all from any to any layer2
670

    
671
EOD;
672

    
673
	/* generate passthru mac database */
674
	$cprules .= captiveportal_passthrumac_configure(true);
675
	$cprules .= "\n";
676
	/* allowed ipfw rules to make allowed ip work */
677
	$cprules .= captiveportal_allowedip_configure();
678

    
679
	/* load rules */
680
	if ($reinit == true)
681
		$cprules = "table all flush\nflush\n{$cprules}";
682
	else {
683
		$tmprules = "table 3 flush\n";
684
		$tmprules .= "table 4 flush\n";
685
		$tmprules .= "table 5 flush\n";
686
		$tmprules .= "table 6 flush\n";
687
		$tmprules .= "table 7 flush\n";
688
		$tmprules .= "table 8 flush\n";
689
		$tmprules .= "table 9 flush\n";
690
		$tmprules .= "table 10 flush\n";
691
		$tmprules .= "flush\n";
692
		$cprules = "{$tmprules}\n{$cprules}";
693
	}
694

    
695
	file_put_contents("{$g['tmp_path']}/ipfw.cp.rules", $cprules);
696
	mwexec("/sbin/ipfw -q {$g['tmp_path']}/ipfw.cp.rules", true);
697
	@unlink("{$g['tmp_path']}/ipfw.cp.rules");
698

    
699
	if ($reinit == false)
700
		unlock($captiveportallck);
701

    
702

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

    
706
	return $cprules;
707
}
708

    
709
/* remove clients that have been around for longer than the specified amount of time */
710
/* db file structure:
711
timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time */
712

    
713
/* (password is in Base64 and only saved when reauthentication is enabled) */
714
function captiveportal_prune_old() {
715

    
716
    global $g, $config;
717

    
718
    /* check for expired entries */
719
    if ($config['captiveportal']['timeout'])
720
        $timeout = $config['captiveportal']['timeout'] * 60;
721
    else
722
        $timeout = 0;
723

    
724
    if ($config['captiveportal']['idletimeout'])
725
        $idletimeout = $config['captiveportal']['idletimeout'] * 60;
726
    else
727
        $idletimeout = 0;
728

    
729
    if (!$timeout && !$idletimeout && !isset($config['captiveportal']['reauthenticate']) && 
730
		!isset($config['captiveportal']['radiussession_timeout']) && !isset($config['voucher']['enable']))
731
        return;
732

    
733
    $captiveportallck = lock('captiveportal');
734

    
735
    /* read database */
736
    $cpdb = captiveportal_read_db();
737

    
738
    $radiusservers = captiveportal_get_radius_servers();
739

    
740
    /*  To make sure we iterate over ALL accounts on every run the count($cpdb) is moved
741
     *  outside of the loop. Otherwise the loop would evaluate count() on every iteration
742
     *  and since $i would increase and count() would decrement they would meet before we
743
     *  had a chance to iterate over all accounts.
744
     */
745
    $unsetindexes = array();
746
    $no_users = count($cpdb);
747
    for ($i = 0; $i < $no_users; $i++) {
748

    
749
        $timedout = false;
750
        $term_cause = 1;
751

    
752
        /* hard timeout? */
753
        if ($timeout) {
754
            if ((time() - $cpdb[$i][0]) >= $timeout) {
755
                $timedout = true;
756
                $term_cause = 5; // Session-Timeout
757
            }
758
        }
759

    
760
        /* Session-Terminate-Time */
761
        if (!$timedout && !empty($cpdb[$i][9])) {
762
            if (time() >= $cpdb[$i][9]) {
763
                $timedout = true;
764
                $term_cause = 5; // Session-Timeout
765
            }
766
        }
767

    
768
        /* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
769
        $idletimeout = (is_numeric($cpdb[$i][8])) ? $cpdb[$i][8] : $idletimeout;
770
        /* if an idle timeout is specified, get last activity timestamp from ipfw */
771
        if (!$timedout && $idletimeout) {
772
            $lastact = captiveportal_get_last_activity($cpdb[$i][2]);
773
			/*  If the user has logged on but not sent any traffic they will never be logged out.
774
			 *  We "fix" this by setting lastact to the login timestamp. 
775
			 */
776
			$lastact = $lastact ? $lastact : $cpdb[$i][0];
777
            if ($lastact && ((time() - $lastact) >= $idletimeout)) {
778
                $timedout = true;
779
                $term_cause = 4; // Idle-Timeout
780
                $stop_time = $lastact; // Entry added to comply with WISPr
781
            }
782
        }
783

    
784
	/* if vouchers are configured, activate session timeouts */
785
	if (!$timedout && isset($config['voucher']['enable']) && !empty($cpdb[$i][7])) {
786
		if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
787
			$timedout = true;
788
			$term_cause = 5; // Session-Timeout
789
		}
790
	}
791

    
792
        /* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
793
        if (!$timedout && isset($config['captiveportal']['radiussession_timeout']) && !empty($cpdb[$i][7])) {
794
            if (time() >= ($cpdb[$i][0] + $cpdb[$i][7])) {
795
                $timedout = true;
796
                $term_cause = 5; // Session-Timeout
797
            }
798
        }
799

    
800
        if ($timedout) {
801
            captiveportal_disconnect($cpdb[$i], $radiusservers,$term_cause,$stop_time);
802
            captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "TIMEOUT");
803
	    $unsetindexes[$i] = $i;
804
        }
805

    
806
        /* do periodic RADIUS reauthentication? */
807
        if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
808
            !empty($radiusservers)) {
809

    
810
            if (isset($config['captiveportal']['radacct_enable'])) {
811
                if ($config['captiveportal']['reauthenticateacct'] == "stopstart") {
812
                    /* stop and restart accounting */
813
                    RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
814
                                           $cpdb[$i][4], // username
815
                                           $cpdb[$i][5], // sessionid
816
                                           $cpdb[$i][0], // start time
817
                                           $radiusservers,
818
                                           $cpdb[$i][2], // clientip
819
                                           $cpdb[$i][3], // clientmac
820
                                           10); // NAS Request
821
                    exec("/sbin/ipfw table 1 entryzerostats {$cpdb[$i][2]}");
822
                    exec("/sbin/ipfw table 2 entryzerostats {$cpdb[$i][2]}");
823
                    RADIUS_ACCOUNTING_START($cpdb[$i][1], // ruleno
824
                                            $cpdb[$i][4], // username
825
                                            $cpdb[$i][5], // sessionid
826
                                            $radiusservers,
827
                                            $cpdb[$i][2], // clientip
828
                                            $cpdb[$i][3]); // clientmac
829
                } else if ($config['captiveportal']['reauthenticateacct'] == "interimupdate") {
830
                    RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
831
                                           $cpdb[$i][4], // username
832
                                           $cpdb[$i][5], // sessionid
833
                                           $cpdb[$i][0], // start time
834
                                           $radiusservers,
835
                                           $cpdb[$i][2], // clientip
836
                                           $cpdb[$i][3], // clientmac
837
                                           10, // NAS Request
838
                                           true); // Interim Updates
839
                }
840
            }
841

    
842
            /* check this user against RADIUS again */
843
            $auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
844
                                          base64_decode($cpdb[$i][6]), // password
845
                                            $radiusservers,
846
                                          $cpdb[$i][2], // clientip
847
                                          $cpdb[$i][3], // clientmac
848
                                          $cpdb[$i][1]); // ruleno
849

    
850
            if ($auth_list['auth_val'] == 3) {
851
                captiveportal_disconnect($cpdb[$i], $radiusservers, 17);
852
                captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
853
	        $unsetindexes[$i] = $i;
854
            }
855
        }
856
    }
857
    /* This is a kludge to overcome some php weirdness */
858
    foreach($unsetindexes as $unsetindex)
859
	unset($cpdb[$unsetindex]);
860

    
861
    /* write database */
862
    captiveportal_write_db($cpdb);
863

    
864
    unlock($captiveportallck);
865
}
866

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

    
870
	global $g, $config;
871

    
872
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
873

    
874
	/* this client needs to be deleted - remove ipfw rules */
875
	if (isset($config['captiveportal']['radacct_enable']) && !empty($radiusservers)) {
876
		RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
877
							   $dbent[4], // username
878
							   $dbent[5], // sessionid
879
							   $dbent[0], // start time
880
							   $radiusservers,
881
							   $dbent[2], // clientip
882
							   $dbent[3], // clientmac
883
							   $term_cause, // Acct-Terminate-Cause
884
							   false,
885
							   $stop_time);
886
	}
887
	/* Delete client's ip entry from tables 3 and 4. */
888
	mwexec("/sbin/ipfw table 1 delete {$dbent[2]}");
889
	mwexec("/sbin/ipfw table 2 delete {$dbent[2]}");
890

    
891
	/* Release the ruleno so it can be reallocated to new clients. */
892
	captiveportal_free_ipfw_ruleno($dbent[1]);
893

    
894
	/* 
895
	* These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
896
	* We could get an error if the pipe doesn't exist but everything should still be fine
897
	*/
898
	if (isset($config['captiveportal']['peruserbw'])) {
899
		mwexec("/sbin/ipfw pipe " . ($dbent[1]+20000) . " delete");
900
		mwexec("/sbin/ipfw pipe " . ($dbent[1]+20001) . " delete");
901
	}
902

    
903
	/* XXX: Redundant?! Ensure all pf(4) states are killed. */
904
	mwexec("pfctl -k {$dbent[2]}");
905
	mwexec("pfctl -K {$dbent[2]}");
906

    
907
}
908

    
909
/* remove a single client by ipfw rule number */
910
function captiveportal_disconnect_client($id,$term_cause = 1) {
911

    
912
	global $g, $config;
913

    
914
	$captiveportallck = lock('captiveportal');
915

    
916
	/* read database */
917
	$cpdb = captiveportal_read_db();
918
	$radiusservers = captiveportal_get_radius_servers();
919

    
920
	/* find entry */
921
	$tmpindex = 0;
922
	$cpdbcount = count($cpdb);
923
	for ($i = 0; $i < $cpdbcount; $i++) {
924
		if ($cpdb[$i][1] == $id) {
925
			captiveportal_disconnect($cpdb[$i], $radiusservers, $term_cause);
926
			captiveportal_logportalauth($cpdb[$i][4], $cpdb[$i][3], $cpdb[$i][2], "DISCONNECT");
927
			unset($cpdb[$i]);
928
			break;
929
		}
930
	}		
931

    
932
	/* write database */
933
	captiveportal_write_db($cpdb);
934

    
935
	unlock($captiveportallck);
936
}
937

    
938
/* send RADIUS acct stop for all current clients */
939
function captiveportal_radius_stop_all($lock = false) {
940
	global $g, $config;
941

    
942
	if (!isset($config['captiveportal']['radacct_enable']))
943
		return;
944

    
945
	if (!$lock)
946
		$captiveportallck = lock('captiveportal');
947

    
948
	$cpdb = captiveportal_read_db();
949

    
950
	$radiusservers = captiveportal_get_radius_servers();
951
	if (!empty($radiusservers)) {
952
		for ($i = 0; $i < count($cpdb); $i++) {
953
			RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
954
								   $cpdb[$i][4], // username
955
								   $cpdb[$i][5], // sessionid
956
								   $cpdb[$i][0], // start time
957
								   $radiusservers,
958
								   $cpdb[$i][2], // clientip
959
								   $cpdb[$i][3], // clientmac
960
								   7); // Admin Reboot
961
		}
962
	}
963
	if (!$lock)
964
		unlock($captiveportallck);
965
}
966

    
967
function captiveportal_passthrumac_configure_entry($macent) {
968
	$rules = "";
969
        $enBwup = isset($macent['bw_up']);
970
        $enBwdown = isset($macent['bw_down']);
971
	$actionup = "allow";
972
	$actiondown = "allow";
973

    
974
        if ($enBwup && $enBwdown)
975
                $ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
976
        else
977
                $ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
978

    
979
	if ($enBwup) {
980
                $bw_up = $ruleno + 20000;
981
                $rules .= "pipe {$bw_up} config bw {$macent['bw_up']}Kbit/s queue 100\n";
982
		$actionup = "pipe {$bw_up}";
983
        }
984
        if ($enBwdown) {
985
		$bw_down = $ruleno + 20001;
986
		$rules .= "pipe {$bw_down} config bw {$macent['bw_down']}Kbit/s queue 100\n";
987
		$actiondown = "pipe {$bw_down}";
988
        }
989
	$rules .= "add {$ruleno} {$actiondown} ip from any to any MAC {$macent['mac']} any\n";
990
	$ruleno++;
991
	$rules .= "add {$ruleno} {$actionup} ip from any to any MAC any {$macent['mac']}\n";
992

    
993
	return $rules;
994
}
995

    
996
function captiveportal_passthrumac_configure($lock = false) {
997
	global $config, $g;
998

    
999
	$rules = "";
1000

    
1001
	if (is_array($config['captiveportal']['passthrumac'])) {
1002
		$macdb = array();
1003
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
1004
			$rules .= captiveportal_passthrumac_configure_entry($macent);
1005
			$macdb[$macent['mac']]['active']  = true;
1006

    
1007
		}
1008
	}
1009

    
1010
	return $rules;
1011
}
1012

    
1013
function captiveportal_passthrumac_findbyname($username) {
1014
	global $config;
1015

    
1016
	if (is_array($config['captiveportal']['passthrumac'])) {
1017
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
1018
			if ($macent['username'] == $username)
1019
				return $macent;
1020
		}
1021
	}
1022
	return NULL;
1023
}
1024

    
1025
/* 
1026
 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1027
 * table (5=IN)/(6=OUT) hold allowed ip's with bw limit.
1028
 */
1029
function captiveportal_allowedip_configure_entry($ipent) {
1030

    
1031
	$rules = "";
1032
	$enBwup = isset($ipent['bw_up']);
1033
	$enBwdown = isset($ipent['bw_down']);
1034
	$bw_up = "";
1035
        $bw_down = "";
1036
        $tablein = array();
1037
        $tableout = array();
1038

    
1039
	if ($enBwup && $enBwdown)
1040
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, true);
1041
	else
1042
		$ruleno = captiveportal_get_next_ipfw_ruleno(2000, 49899, false);
1043

    
1044
        if ($ipent['dir'] == "from") {
1045
        	if ($enBwup)
1046
                	$tablein[] = 5;
1047
                else
1048
                	$tablein[] = 3;
1049
                if ($enBwdown)
1050
                        $tableout[] = 6;
1051
                else
1052
                        $tableout[] = 4;
1053
        } else if ($ipent['dir'] == "to") {
1054
                if ($enBwup)
1055
                	$tablein[] = 9;
1056
                else
1057
                        $tablein[] = 7;
1058
                if ($enBwdown)
1059
                        $tableout[] = 10;
1060
                else
1061
                        $tableout[] = 8;
1062
        } else if ($ipent['dir'] == "both") {
1063
                if ($enBwup) {
1064
                        $tablein[] = 5;
1065
                        $tablein[] = 9;
1066
                } else {
1067
                        $tablein[] = 3;
1068
                        $tablein[] = 7;
1069
                }
1070
        	if ($enBwdown) {
1071
                        $tableout[] = 6;
1072
                        $tableout[] = 10;
1073
                } else {
1074
                        $tableout[] = 4;
1075
                	$tableout[] = 8;
1076
                }
1077
        }
1078
        if ($enBwup) {
1079
                $bw_up = $ruleno + 20000;
1080
        	$rules .= "pipe {$bw_up} config bw {$ipent['bw_up']}Kbit/s queue 100\n";
1081
        }
1082
	$subnet = "";
1083
	if (!empty($ipent['sn']))
1084
		$subnet = "/{$ipent['sn']}";
1085
	foreach ($tablein as $table)
1086
               $rules .= "table {$table} add {$ipent['ip']}{$subnet} {$bw_up}\n";
1087
        if ($enBwdown) {
1088
               $bw_down = $ruleno + 20001;
1089
               $rules .= "pipe {$bw_down} config bw {$ipent['bw_down']}Kbit/s queue 100\n";
1090
        }
1091
        foreach ($tableout as $table)
1092
        	$rules .= "table {$table} add {$ipent['ip']}{$subnet} {$bw_down}\n";
1093

    
1094
	return $rules;
1095
}
1096

    
1097
function captiveportal_allowedip_configure() {
1098
	global $config, $g;
1099

    
1100
	$rules = "";
1101
	if (is_array($config['captiveportal']['allowedip'])) {
1102
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
1103
			$rules .= captiveportal_allowedip_configure_entry($ipent);
1104
		}
1105
	}
1106

    
1107
	return $rules;
1108
}
1109

    
1110
/* get last activity timestamp given client IP address */
1111
function captiveportal_get_last_activity($ip) {
1112

    
1113
	$ipfwoutput = "";
1114

    
1115
	exec("/sbin/ipfw table 1 entrystats {$ip} 2>/dev/null", $ipfwoutput);
1116
	/* Reading only from one of the tables is enough of approximation. */
1117
	if ($ipfwoutput[0]) {
1118
		$ri = explode(" ", $ipfwoutput[0]);
1119
		if ($ri[4])
1120
			return $ri[4];
1121
	}
1122

    
1123
	return 0;
1124
}
1125

    
1126
/* read RADIUS servers into array */
1127
function captiveportal_get_radius_servers() {
1128

    
1129
        global $g;
1130

    
1131
        if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
1132
                $radiusservers = array();
1133
		$cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius.db", 
1134
			FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1135
		if ($cpradiusdb)
1136
		foreach($cpradiusdb as $cpradiusentry) {
1137
                	$line = trim($cpradiusentry);
1138
                        if ($line) {
1139
                        	$radsrv = array();
1140
                                list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
1141
                        	$radiusservers[] = $radsrv;
1142
                        }
1143
		}
1144

    
1145
		return $radiusservers;
1146
        }
1147

    
1148
        return false;
1149
}
1150

    
1151
/* log successful captive portal authentication to syslog */
1152
/* part of this code from php.net */
1153
function captiveportal_logportalauth($user,$mac,$ip,$status, $message = null) {
1154
	$message = trim($message);
1155
	// Log it
1156
	if (!$message)
1157
		$message = "$status: $user, $mac, $ip";
1158
	else
1159
		$message = "$status: $user, $mac, $ip, $message";
1160
	captiveportal_syslog($message);
1161
	closelog();
1162
}
1163

    
1164
/* log simple messages to syslog */
1165
function captiveportal_syslog($message) {
1166
	define_syslog_variables();
1167
	$message = trim($message);
1168
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1169
	// Log it
1170
	syslog(LOG_INFO, $message);
1171
	closelog();
1172
}
1173

    
1174
function radius($username,$password,$clientip,$clientmac,$type) {
1175
    global $g, $config;
1176

    
1177
    /* Start locking from the beginning of an authentication session */
1178
    $captiveportallck = lock('captiveportal');
1179

    
1180
    $ruleno = captiveportal_get_next_ipfw_ruleno();
1181

    
1182
    /* If the pool is empty, return appropriate message and fail authentication */
1183
    if (is_null($ruleno)) {
1184
        $auth_list = array();
1185
        $auth_list['auth_val'] = 1;
1186
        $auth_list['error'] = "System reached maximum login capacity";
1187
        unlock($captiveportallck);
1188
        return $auth_list;
1189
    }
1190

    
1191
    /*
1192
     * Drop the lock since radius takes some time to finish.
1193
     * The implementation is reentrant so we gain speed with this.
1194
     */
1195
    unlock($captiveportallck);
1196

    
1197
    $radiusservers = captiveportal_get_radius_servers();
1198

    
1199
    $auth_list = RADIUS_AUTHENTICATION($username,
1200
                    $password,
1201
                    $radiusservers,
1202
                    $clientip,
1203
                    $clientmac,
1204
                    $ruleno);
1205

    
1206
    $captiveportallck = lock('captiveportal');
1207

    
1208
    if ($auth_list['auth_val'] == 2) {
1209
        captiveportal_logportalauth($username,$clientmac,$clientip,$type);
1210
        $sessionid = portal_allow($clientip,
1211
                    $clientmac,
1212
                    $username,
1213
                    $password,
1214
                    $auth_list,
1215
                    $ruleno);
1216
    }
1217

    
1218
    unlock($captiveportallck);
1219

    
1220
    return $auth_list;
1221

    
1222
}
1223

    
1224
/* read captive portal DB into array */
1225
function captiveportal_read_db() {
1226

    
1227
        global $g;
1228

    
1229
        $cpdb = array();
1230
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
1231
        if ($fd) {
1232
                while (!feof($fd)) {
1233
                        $line = trim(fgets($fd));
1234
                        if ($line) {
1235
                                $cpdb[] = explode(",", $line);
1236
                        }
1237
                }
1238
                fclose($fd);
1239
        }
1240
        return $cpdb;
1241
}
1242

    
1243
/* write captive portal DB */
1244
function captiveportal_write_db($cpdb) {
1245
                 
1246
        global $g;
1247
                
1248
        $fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
1249
        if ($fd) { 
1250
                foreach ($cpdb as $cpent) {
1251
                        fwrite($fd, join(",", $cpent) . "\n");
1252
                }       
1253
                fclose($fd);
1254
        }       
1255
}
1256

    
1257
function captiveportal_write_elements() {
1258
	global $g, $config;
1259
    
1260
	/* delete any existing elements */
1261
	if (is_dir($g['captiveportal_element_path'])) {
1262
		$dh = opendir($g['captiveportal_element_path']);
1263
		while (($file = readdir($dh)) !== false) {
1264
			if ($file != "." && $file != "..")
1265
				unlink($g['captiveportal_element_path'] . "/" . $file);
1266
		}
1267
		closedir($dh);
1268
	} else
1269
		@mkdir($g['captiveportal_element_path']);
1270

    
1271
	if (is_array($config['captiveportal']['element'])) {
1272
		conf_mount_rw();
1273
		foreach ($config['captiveportal']['element'] as $data) {
1274
			$fd = @fopen($g['captiveportal_element_path'] . '/' . $data['name'], "wb");
1275
			if (!$fd) {
1276
				printf("Error: cannot open '{$data['name']}' in captiveportal_write_elements().\n");
1277
				return 1;
1278
			}
1279
			$decoded = base64_decode($data['content']);
1280
			fwrite($fd,$decoded);
1281
			fclose($fd);
1282
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1283
			unlink_if_exists("{$g['captiveportal_path']}/{$data['name']}");
1284
			mwexec("cd {$g['captiveportal_path']}/ && ln -s {$g['captiveportal_element_path']}/{$data['name']} {$data['name']}");
1285
		}
1286
		conf_mount_ro();
1287
	}
1288
    
1289
	return 0;
1290
}
1291

    
1292
function captiveportal_init_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899) {
1293
	global $g;
1294

    
1295
	@unlink("{$g['vardb_path']}/captiveportal.rules");
1296
	$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1297
	file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1298
}
1299

    
1300
/*
1301
 * This function will calculate the lowest free firewall ruleno
1302
 * within the range specified based on the actual logged on users
1303
 *
1304
 */
1305
function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2000, $rulenos_range_max = 49899, $usebw = false) {
1306
	global $config, $g;
1307

    
1308
	if(!isset($config['captiveportal']['enable']))
1309
		return NULL;
1310

    
1311
	$ruleno = 0;
1312
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1313
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1314
		for ($ridx = 2; $ridx < ($rulenos_range_max - $rulenos_start); $ridx++) {
1315
			if ($rules[$ridx]) {
1316
				/* 
1317
	 			 * This allows our traffic shaping pipes to be the in pipe the same as ruleno 
1318
	 			 * and the out pipe ruleno + 1. This removes limitation that where present in 
1319
	 			 * previous version of the peruserbw.
1320
	 			 */
1321
				if (isset($config['captiveportal']['peruserbw']))
1322
					$ridx++;
1323
				continue;
1324
			}
1325
			$ruleno = $ridx;
1326
			$rules[$ridx] = "used";
1327
			if (isset($config['captiveportal']['peruserbw']) || $usebw == true)
1328
				$rules[++$ridx] = "used";
1329
			break;
1330
		}
1331
	} else {
1332
		$rules = array_pad(array(), $rulenos_range_max - $rulenos_start, false);
1333
		$rules[2] = "used";
1334
		$ruleno = 2;
1335
	}
1336
	file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1337
	return $ruleno;
1338
}
1339

    
1340
function captiveportal_free_ipfw_ruleno($ruleno, $usedbw = false) {
1341
	global $config, $g;
1342

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

    
1346
	if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1347
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1348
		$rules[$ruleno] = false;
1349
		if (isset($config['captiveportal']['peruserbw']) || $usedbw == true)
1350
			$rules[++$ruleno] = false;
1351
		file_put_contents("{$g['vardb_path']}/captiveportal.rules", serialize($rules));
1352
	}
1353
}
1354

    
1355
function captiveportal_get_ipfw_passthru_ruleno($value) {
1356
	global $config, $g;
1357

    
1358
	if(!isset($config['captiveportal']['enable']))
1359
                return NULL;
1360

    
1361
        if (file_exists("{$g['vardb_path']}/captiveportal.rules")) {
1362
                $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal.rules"));
1363
		$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`);
1364
		if ($rules[$ruleno])
1365
			return $ruleno;
1366
        }
1367

    
1368
	return NULL;
1369
}
1370

    
1371
/**
1372
 * This function will calculate the traffic produced by a client
1373
 * based on its firewall rule
1374
 *
1375
 * Point of view: NAS
1376
 *
1377
 * Input means: from the client
1378
 * Output means: to the client
1379
 *
1380
 */
1381

    
1382
function getVolume($ip) {
1383

    
1384
    $volume = array();
1385

    
1386
    // Initialize vars properly, since we don't want NULL vars
1387
    $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
1388

    
1389
    // Ingress
1390
    $ipfwin = "";
1391
    $ipfwout = "";
1392
    $matchesin = "";
1393
    $matchesout = "";
1394
    exec("/sbin/ipfw table 1 entrystats {$ip}", $ipfwin);
1395
    if ($ipfwin[0]) {
1396
		$ipfwin = split(" ", $ipfwin[0]);
1397
		$volume['input_pkts'] = $ipfwin[2];
1398
		$volume['input_bytes'] = $ipfwin[3];
1399
    }
1400

    
1401
    exec("/sbin/ipfw table 2 entrystats {$ip}", $ipfwout);
1402
    if ($ipfwout[0]) {
1403
        $ipfwout = split(" ", $ipfwout[0]);
1404
        $volume['output_pkts'] = $ipfwout[2];
1405
        $volume['output_bytes'] = $ipfwout[3];
1406
    }
1407

    
1408
    return $volume;
1409
}
1410

    
1411
/**
1412
 * Get the NAS-Identifier
1413
 *
1414
 * We will use our local hostname to make up the nas_id
1415
 */
1416
function getNasID()
1417
{
1418
    $nasId = "";
1419
    exec("/bin/hostname", $nasId);
1420
    if(!$nasId[0])
1421
        $nasId[0] = "{$g['product_name']}";
1422
    return $nasId[0];
1423
}
1424

    
1425
/**
1426
 * Get the NAS-IP-Address based on the current wan address
1427
 *
1428
 * Use functions in interfaces.inc to find this out
1429
 *
1430
 */
1431

    
1432
function getNasIP()
1433
{
1434
	global $config;
1435

    
1436
	if (empty($config['captiveportal']['radiussrcip_attribute']))
1437
    		$nasIp = get_interface_ip();
1438
	else {
1439
		if (is_ipaddr($config['captiveportal']['radiussrcip_attribute']))
1440
                        $nasIp = $config['captiveportal']['radiussrcip_attribute'];
1441
                else
1442
                        $nasIp = get_interface_ip($config['captiveportal']['radiussrcip_attribute']);
1443
	}
1444
		
1445
    	if(!is_ipaddr($nasIp))
1446
        	$nasIp = "0.0.0.0";
1447

    
1448
	return $nasIp;
1449
}
1450

    
1451
function portal_ip_from_client_ip($cliip) {
1452
	global $config;
1453

    
1454
	$interfaces = explode(",", $config['captiveportal']['interface']);
1455
	foreach ($interfaces as $cpif) {
1456
		$ip = get_interface_ip($cpif);
1457
		$sn = get_interface_subnet($cpif);
1458
		if (ip_in_subnet($cliip, "{$ip}/{$sn}"))
1459
			return $ip;
1460
	}
1461

    
1462
	// doesn't match up to any particular interface
1463
	// so let's set the portal IP to what PHP says 
1464
	// the server IP issuing the request is. 
1465
	// allows same behavior as 1.2.x where IP isn't 
1466
	// in the subnet of any CP interface (static routes, etc.)
1467
	// rather than forcing to DNS hostname resolution
1468
	$ip = $_SERVER['SERVER_ADDR'];
1469
	if (is_ipaddr($ip))
1470
		return $ip;
1471

    
1472
	return false;
1473
}
1474

    
1475
?>
(6-6/54)