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

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

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

    
670
EOD;
671

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

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

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

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

    
701

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

    
705
	return $cprules;
706
}
707

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

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

    
715
    global $g, $config;
716

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

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

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

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

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

    
737
    $radiusservers = captiveportal_get_radius_servers();
738

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

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

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

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

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

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

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

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

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

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

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

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

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

    
863
    unlock($captiveportallck);
864
}
865

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

    
869
	global $g, $config;
870

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

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

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

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

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

    
906
}
907

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

    
911
	global $g, $config;
912

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

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

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

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

    
934
	unlock($captiveportallck);
935
}
936

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

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

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

    
947
	$cpdb = captiveportal_read_db();
948

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

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

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

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

    
992
	return $rules;
993
}
994

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

    
998
	$rules = "";
999

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

    
1006
		}
1007
	}
1008

    
1009
	return $rules;
1010
}
1011

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

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

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

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

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

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

    
1093
	return $rules;
1094
}
1095

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

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

    
1106
	return $rules;
1107
}
1108

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

    
1112
	$ipfwoutput = "";
1113

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

    
1122
	return 0;
1123
}
1124

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

    
1128
        global $g;
1129

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

    
1144
		return $radiusservers;
1145
        }
1146

    
1147
        return false;
1148
}
1149

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

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

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

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

    
1179
    $ruleno = captiveportal_get_next_ipfw_ruleno();
1180

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

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

    
1196
    $radiusservers = captiveportal_get_radius_servers();
1197

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

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

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

    
1217
    unlock($captiveportallck);
1218

    
1219
    return $auth_list;
1220

    
1221
}
1222

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

    
1226
        global $g;
1227

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1367
	return NULL;
1368
}
1369

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

    
1381
function getVolume($ip) {
1382

    
1383
    $volume = array();
1384

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

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

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

    
1407
    return $volume;
1408
}
1409

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

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

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

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

    
1447
	return $nasIp;
1448
}
1449

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

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

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

    
1471
	return false;
1472
}
1473

    
1474
?>
(6-6/54)