· 6 years ago · Apr 18, 2019, 04:50 PM
1<?php
2/*
3 * captiveportal.inc
4 *
5 * part of pfSense (https://www.pfsense.org)
6 * Copyright (c) 2004-2018 Rubicon Communications, LLC (Netgate)
7 * All rights reserved.
8 *
9 * originally part of m0n0wall (http://m0n0.ch/wall)
10 * Copyright (c) 2003-2006 Manuel Kasper <mk@neon1.net>.
11 * All rights reserved.
12 *
13 * Licensed under the Apache License, Version 2.0 (the "License");
14 * you may not use this file except in compliance with the License.
15 * You may obtain a copy of the License at
16 *
17 * http://www.apache.org/licenses/LICENSE-2.0
18 *
19 * Unless required by applicable law or agreed to in writing, software
20 * distributed under the License is distributed on an "AS IS" BASIS,
21 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 * See the License for the specific language governing permissions and
23 * limitations under the License.
24 */
25
26/* include all configuration functions */
27require_once("auth.inc");
28require_once("PEAR.php"); // required for bcmath
29require_once("Auth/RADIUS.php"); // required for radius accounting
30require_once("config.inc");
31require_once("functions.inc");
32require_once("filter.inc");
33require_once("voucher.inc");
34
35/* Captiveportal Radius Accounting */
36PEAR::loadExtension('bcmath');
37// The RADIUS Package doesn't have these vars so we create them ourself
38define("CUSTOM_RADIUS_ACCT_INPUT_GIGAWORDS", "52");
39define("CUSTOM_RADIUS_ACCT_OUTPUT_GIGAWORDS", "53");
40define("GIGAWORDS_RIGHT_OPERAND", '4294967296'); // 2^32
41
42function get_default_captive_portal_html() {
43 global $config, $g, $cpzone;
44
45 $translated_text1 = gettext("User");
46 $translated_text2 = gettext("Password");
47 $translated_text3 = gettext("First Authentication Method ");
48 $translated_text4 = gettext("Second Authentication Method ");
49 // default images to use.
50 $logo_src = "captiveportal-default-logo.png";
51 $bg_src = "linear-gradient(135deg, #1475CF, #2B40B5, #1C1275)";
52 // Check if customlogo is set and if the element exists
53 // Check if the image is in the directory
54 if (isset($config['captiveportal'][$cpzone]['customlogo'])) {
55 foreach ($config['captiveportal'][$cpzone]['element'] as $element) {
56 if (strpos($element['name'], "captiveportal-logo.") !== false) {
57 if (file_exists("{$g['captiveportal_path']}/{$element['name']}")) {
58 $logo_src = $element['name'];
59 break;
60 }
61 }
62 }
63 }
64 // check if custombg is set and if the element exists
65 if (isset($config['captiveportal'][$cpzone]['custombg'])) {
66 foreach ($config['captiveportal'][$cpzone]['element'] as $element) {
67 if (strpos($element['name'],"captiveportal-background.") !== false) {
68 if( file_exists("{$g['captiveportal_path']}/{$element['name']}")) {
69 $bg_src = "url(" . $element['name'] . ")" . "center center no-repeat fixed";
70 break;
71 }
72 }
73 }
74
75 }
76 // bring in terms and conditions
77 $termsconditions = base64_decode($config['captiveportal'][$cpzone]['termsconditions']);
78 // if there is no terms and conditions do not require the checkbox to be selected.
79 $disabled = "";
80 if ($termsconditions) {
81 $disabled = "disabled";
82 }
83 $htmltext = <<<EOD
84<!DOCTYPE html>
85<html>
86
87<head>
88
89 <meta charset="UTF-8">
90 <meta name="viewport" content="width=device-width, initial-scale=1.0">
91 <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
92 <title>Captive Portal Login Page</title>
93 <style>
94 #content,.login,.login-card a,.login-card h1,.login-help{text-align:center}body,html{margin:0;padding:0;width:100%;height:100%;display:table}#content{font-family:'Source Sans Pro',sans-serif;background-color:#1C1275;background:{$bg_src};-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;display:table-cell;vertical-align:middle}.login-card{padding:40px;width:280px;background-color:#F7F7F7;margin:100px auto 10px;border-radius:2px;box-shadow:0 2px 2px rgba(0,0,0,.3);overflow:hidden}.login-card h1{font-weight:400;font-size:2.3em;color:#1383c6}.login-card h1 span{color:#f26721}.login-card img{width:70%;height:70%}.login-card input[type=submit]{width:100%;display:block;margin-bottom:10px;position:relative}.login-card input[type=text],input[type=password]{height:44px;font-size:16px;width:100%;margin-bottom:10px;-webkit-appearance:none;background:#fff;border:1px solid #d9d9d9;border-top:1px solid silver;padding:0 8px;box-sizing:border-box;-moz-box-sizing:border-box}.login-card input[type=text]:hover,input[type=password]:hover{border:1px solid #b9b9b9;border-top:1px solid #a0a0a0;-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.login{font-size:14px;font-family:Arial,sans-serif;font-weight:700;height:36px;padding:0 8px}.login-submit{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:0;color:#fff;text-shadow:0 1px rgba(0,0,0,.1);background-color:#4d90fe}.login-submit:disabled{opacity:.6}.login-submit:hover{border:0;text-shadow:0 1px rgba(0,0,0,.3);background-color:#357ae8}.login-card a{text-decoration:none;color:#222;font-weight:400;display:inline-block;opacity:.6;transition:opacity ease .5s}.login-card a:hover{opacity:1}.login-help{width:100%;font-size:12px}.list{list-style-type:none;padding:0}.list__item{margin:0 0 .7rem;padding:0}label{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;text-align:left;font-size:14px;}input[type=checkbox]{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;margin-right:10px;float:left}@media screen and (max-width:450px){.login-card{width:70%!important}.login-card img{width:30%;height:30%}}textarea{width:66%;margin:auto;height:120px;max-height:120px;background-color:#f7f7f7;padding:20px}#terms{display:none;padding-top:100px;padding-bottom:300px;}.auth_source{border: 1px solid lightgray; padding:20px 8px 0px 8px; margin-top: -2em; border-radius: 2px; }.auth_head{background-color:#f7f7f7;display:inline-block;}.auth_head_div{text-align:left;}#error-message{text-align:left;color:#ff3e3e;font-style:italic;}
95 </style>
96</head>
97
98<body>
99<div id="content">
100 <div class="login-card">
101 <img src="{$logo_src}"/><br>
102 <h1></h1>
103 <div id="error-message">
104 \$PORTAL_MESSAGE\$
105 </div>
106 <form name="login_form" method="post" action="\$PORTAL_ACTION\$">
107EOD;
108 if ($config['captiveportal'][$cpzone]['auth_method'] != "none"){
109 if ($config['captiveportal'][$cpzone]['auth_method'] === 'authserver' && !empty($config['captiveportal'][$cpzone]['auth_server2'])) {
110 $htmltext .= <<<EOD
111 <div class="auth_head_div">
112 <h6 class="auth_head">{$translated_text3}</h6>
113 </div>
114 <div class="auth_source">
115
116EOD;
117 }
118 $htmltext .=<<<EOD
119 <input type="text" name="auth_user" placeholder="{$translated_text1}" id="auth_user">
120 <input type="password" name="auth_pass" placeholder="{$translated_text2}" id="auth_pass">
121EOD;
122
123 if ($config['captiveportal'][$cpzone]['auth_method'] === 'authserver' && !empty($config['captiveportal'][$cpzone]['auth_server2'])) {
124 $htmltext .= <<<EOD
125 </div>
126 <div class="auth_head_div">
127 <h6 class="auth_head">{$translated_text4}</h6>
128 </div>
129 <div class="auth_source">
130
131 <input type="text" name="auth_user2" placeholder="{$translated_text1}" id="auth_user2">
132 <input type="password" name="auth_pass2" placeholder="{$translated_text2}" id="auth_pass2">
133 </div>
134EOD;
135 }
136
137
138 if (isset($config['voucher'][$cpzone]['enable'])) {
139 $translated_text = gettext("Voucher Code");
140 $htmltext .= <<<EOD
141 <br /><br />
142 <input name="auth_voucher" type="text" placeholder="{$translated_text}">
143EOD;
144 }
145 }
146
147if ($termsconditions) {
148 $htmltext .= <<<EOD
149 <div class="login-help">
150 <ul class="list">
151 <li class="list__item">
152 <label class="label--checkbox">
153 <input type="checkbox" class="checkbox" onchange="document.getElementById('login').disabled = !this.checked;">
154 <span>I agree with the <a rel="noopener" href="#terms" onclick="document.getElementById('terms').style.display = 'block';">terms & conditions</a></span>
155 </label>
156 </li>
157 </ul>
158 </div>
159EOD;
160}
161 $htmltext .= <<<EOD
162
163 <input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$">
164 <input type="submit" name="accept" class="login login-submit" value="Login" id="login" {$disabled}>
165 </form>
166 <br />
167 <span> <i>Made with ♥ by</i> <strong>Netgate</strong></span>
168 </div>
169 <div id="terms">
170 <textarea readonly>{$termsconditions}</textarea>
171 </div>
172</div>
173</body>
174</html>
175
176EOD;
177
178 return $htmltext;
179}
180
181function captiveportal_load_modules() {
182 global $config;
183
184 mute_kernel_msgs();
185 if (!is_module_loaded("ipfw.ko")) {
186 mwexec("/sbin/kldload ipfw");
187 /* make sure ipfw is not on pfil hooks */
188 set_sysctl(array(
189 "net.inet.ip.pfil.inbound" => "pf",
190 "net.inet6.ip6.pfil.inbound" => "pf",
191 "net.inet.ip.pfil.outbound" => "pf",
192 "net.inet6.ip6.pfil.outbound" => "pf"
193 ));
194 }
195 /* Activate layer2 filtering */
196 set_sysctl(array(
197 "net.link.ether.ipfw" => "1",
198 "net.inet.ip.fw.one_pass" => "1",
199 "net.inet.ip.fw.tables_max" => "65534"
200 ));
201
202 /* Always load dummynet now that even allowed ip and mac passthrough use it. */
203 if (!is_module_loaded("dummynet.ko")) {
204 mwexec("/sbin/kldload dummynet");
205 set_sysctl(array(
206 "net.inet.ip.dummynet.io_fast" => "1",
207 "net.inet.ip.dummynet.hash_size" => "256"
208 ));
209 }
210 unmute_kernel_msgs();
211}
212
213function captiveportal_configure() {
214 global $config, $cpzone, $cpzoneid;
215
216 if (is_array($config['captiveportal'])) {
217 foreach ($config['captiveportal'] as $cpkey => $cp) {
218 $cpzone = $cpkey;
219 $cpzoneid = $cp['zoneid'];
220 captiveportal_configure_zone($cp);
221 }
222 }
223}
224
225function captiveportal_configure_zone($cpcfg) {
226 global $config, $g, $cpzone, $cpzoneid;
227
228 if (isset($cpcfg['enable'])) {
229
230 if (platform_booting()) {
231 echo "Starting captive portal({$cpcfg['zone']})... ";
232 } else {
233 captiveportal_syslog("Reconfiguring captive portal({$cpcfg['zone']}).");
234 }
235
236 /* (re)init ipfw rules */
237 captiveportal_init_rules();
238
239 /* kill any running minicron */
240 killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
241
242 /* initialize minicron interval value */
243 $croninterval = $cpcfg['croninterval'] ? $cpcfg['croninterval'] : 60;
244
245 /* double check if the $croninterval is numeric and at least 10 seconds. If not we set it to 60 to avoid problems */
246 if ((!is_numeric($croninterval)) || ($croninterval < 10)) {
247 $croninterval = 60;
248 }
249
250 /* write portal page */
251 if (is_array($cpcfg['page']) && $cpcfg['page']['htmltext']) {
252 $htmltext = base64_decode($cpcfg['page']['htmltext']);
253 } else {
254 /* example/template page */
255 $htmltext = get_default_captive_portal_html();
256 }
257
258 $fd = @fopen("{$g['varetc_path']}/captiveportal_{$cpzone}.html", "w");
259 if ($fd) {
260 // Special case handling. Convert so that we can pass this page
261 // through the PHP interpreter later without clobbering the vars.
262 $htmltext = str_replace("\$PORTAL_ZONE\$", "#PORTAL_ZONE#", $htmltext);
263 $htmltext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $htmltext);
264 $htmltext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $htmltext);
265 $htmltext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $htmltext);
266 $htmltext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $htmltext);
267 $htmltext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $htmltext);
268 if ($cpcfg['preauthurl']) {
269 $htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
270 $htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
271 }
272 fwrite($fd, $htmltext);
273 fclose($fd);
274 }
275 unset($htmltext);
276
277 /* write error page */
278 if (is_array($cpcfg['page']) && $cpcfg['page']['errtext']) {
279 $errtext = base64_decode($cpcfg['page']['errtext']);
280 } else {
281 /* example page */
282 $errtext = get_default_captive_portal_html();
283 }
284
285 $fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html", "w");
286 if ($fd) {
287 // Special case handling. Convert so that we can pass this page
288 // through the PHP interpreter later without clobbering the vars.
289 $errtext = str_replace("\$PORTAL_ZONE\$", "#PORTAL_ZONE#", $errtext);
290 $errtext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $errtext);
291 $errtext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $errtext);
292 $errtext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $errtext);
293 $errtext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $errtext);
294 $errtext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $errtext);
295 if ($cpcfg['preauthurl']) {
296 $errtext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $errtext);
297 $errtext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $errtext);
298 }
299 fwrite($fd, $errtext);
300 fclose($fd);
301 }
302 unset($errtext);
303
304 /* write logout page */
305 if (is_array($cpcfg['page']) && $cpcfg['page']['logouttext']) {
306 $logouttext = base64_decode($cpcfg['page']['logouttext']);
307 } else {
308 /* example page */
309 $translated_text1 = gettext("Redirecting...");
310 $translated_text2 = gettext("Redirecting to");
311 $translated_text3 = gettext("Logout");
312 $translated_text4 = gettext("Click the button below to disconnect");
313 $logouttext = <<<EOD
314<html>
315<head><title>{$translated_text1}</title></head>
316<body>
317<span style="font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">
318<b>{$translated_text2} <a href="<?=\$my_redirurl;?>"><?=\$my_redirurl;?></a>...</b>
319</span>
320<script type="text/javascript">
321//<![CDATA[
322LogoutWin = window.open('', 'Logout', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=256,height=64');
323if (LogoutWin) {
324 LogoutWin.document.write('<html>');
325 LogoutWin.document.write('<head><title>{$translated_text3}</title></head>') ;
326 LogoutWin.document.write('<body style="background-color:#435370">');
327 LogoutWin.document.write('<div class="text-center" style="color: #ffffff; font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">') ;
328 LogoutWin.document.write('<b>{$translated_text4}</b><p />');
329 LogoutWin.document.write('<form method="POST" action="<?=\$logouturl;?>">');
330 LogoutWin.document.write('<input name="logout_id" type="hidden" value="<?=\$sessionid;?>" />');
331 LogoutWin.document.write('<input name="zone" type="hidden" value="<?=\$cpzone;?>" />');
332 LogoutWin.document.write('<input name="logout" type="submit" value="{$translated_text3}" />');
333 LogoutWin.document.write('</form>');
334 LogoutWin.document.write('</div></body>');
335 LogoutWin.document.write('</html>');
336 LogoutWin.document.close();
337}
338
339document.location.href="<?=\$my_redirurl;?>";
340//]]>
341</script>
342</body>
343</html>
344
345EOD;
346 }
347
348 $fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html", "w");
349 if ($fd) {
350 fwrite($fd, $logouttext);
351 fclose($fd);
352 }
353 unset($logouttext);
354
355 /* write elements */
356 captiveportal_write_elements();
357
358 /* kill any running CP nginx instances */
359 killbypid("{$g['varrun_path']}/nginx-{$cpzone}-CaptivePortal.pid");
360 killbypid("{$g['varrun_path']}/nginx-{$cpzone}-CaptivePortal-SSL.pid");
361
362 /* start up the webserving daemon */
363 captiveportal_init_webgui_zone($cpcfg);
364
365 /* Kill any existing prunecaptiveportal processes */
366 if (file_exists("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid")) {
367 killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
368 }
369
370 /* start pruning process (interval defaults to 60 seconds) */
371 mwexec("/usr/local/bin/minicron $croninterval {$g['varrun_path']}/cp_prunedb_{$cpzone}.pid " .
372 "/etc/rc.prunecaptiveportal {$cpzone}");
373
374 if (platform_booting()) {
375 /* send Accounting-On to server */
376 captiveportal_send_server_accounting('on');
377 echo "done\n";
378 }
379
380 } else {
381 killbypid("{$g['varrun_path']}/nginx-{$cpzone}-CaptivePortal.pid");
382 killbypid("{$g['varrun_path']}/nginx-{$cpzone}-CaptivePortal-SSL.pid");
383 killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
384 @unlink("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
385 @unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
386 @unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
387
388 captiveportal_radius_stop_all(10); // NAS-Request
389
390 captiveportal_filterdns_configure();
391
392 /* remove old information */
393 unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
394 /* Release allocated pipes for this zone */
395 $pipes_to_remove = captiveportal_free_dnrules();
396
397 captiveportal_delete_rules($pipes_to_remove);
398
399 if (empty($config['captiveportal'])) {
400 set_single_sysctl("net.link.ether.ipfw", "0");
401 } else {
402 /* Deactivate ipfw(4) if not needed */
403 $cpactive = false;
404 if (is_array($config['captiveportal'])) {
405 foreach ($config['captiveportal'] as $cpkey => $cp) {
406 if (isset($cp['enable'])) {
407 $cpactive = true;
408 break;
409 }
410 }
411 }
412 if ($cpactive === false) {
413 set_single_sysctl("net.link.ether.ipfw", "0");
414 }
415 }
416 }
417
418 return 0;
419}
420
421function captiveportal_init_webgui() {
422 global $config, $cpzone;
423
424 if (is_array($config['captiveportal'])) {
425 foreach ($config['captiveportal'] as $cpkey => $cp) {
426 $cpzone = $cpkey;
427 captiveportal_init_webgui_zone($cp);
428 }
429 }
430}
431
432function captiveportal_init_webgui_zonename($zone) {
433 global $config, $cpzone;
434
435 if (isset($config['captiveportal'][$zone])) {
436 $cpzone = $zone;
437 captiveportal_init_webgui_zone($config['captiveportal'][$zone]);
438 }
439}
440
441function captiveportal_init_webgui_zone($cpcfg) {
442 global $g, $config, $cpzone;
443
444 if (!isset($cpcfg['enable'])) {
445 return;
446 }
447
448 if (isset($cpcfg['httpslogin'])) {
449 $cert = lookup_cert($cpcfg['certref']);
450 $crt = base64_decode($cert['crt']);
451 $key = base64_decode($cert['prv']);
452 $ca = ca_chain($cert);
453
454 /* generate nginx configuration */
455 if (!empty($cpcfg['listenporthttps'])) {
456 $listenporthttps = $cpcfg['listenporthttps'];
457 } else {
458 $listenporthttps = 8001 + $cpcfg['zoneid'];
459 }
460 system_generate_nginx_config("{$g['varetc_path']}/nginx-{$cpzone}-CaptivePortal-SSL.conf",
461 $crt, $key, $ca, "nginx-{$cpzone}-CaptivePortal-SSL.pid", $listenporthttps, "/usr/local/captiveportal",
462 "cert-{$cpzone}-portal.pem", "ca-{$cpzone}-portal.pem", $cpzone, false);
463 }
464
465 /* generate nginx configuration */
466 if (!empty($cpcfg['listenporthttp'])) {
467 $listenporthttp = $cpcfg['listenporthttp'];
468 } else {
469 $listenporthttp = 8000 + $cpcfg['zoneid'];
470 }
471 system_generate_nginx_config("{$g['varetc_path']}/nginx-{$cpzone}-CaptivePortal.conf",
472 "", "", "", "nginx-{$cpzone}-CaptivePortal.pid", $listenporthttp, "/usr/local/captiveportal",
473 "", "", $cpzone, false);
474
475 @unlink("{$g['varrun']}/nginx-{$cpzone}-CaptivePortal.pid");
476 /* attempt to start nginx */
477 $res = mwexec("/usr/local/sbin/nginx -c {$g['varetc_path']}/nginx-{$cpzone}-CaptivePortal.conf");
478
479 /* fire up https instance */
480 if (isset($cpcfg['httpslogin'])) {
481 @unlink("{$g['varrun']}/nginx-{$cpzone}-CaptivePortal-SSL.pid");
482 $res = mwexec("/usr/local/sbin/nginx -c {$g['varetc_path']}/nginx-{$cpzone}-CaptivePortal-SSL.conf");
483 }
484}
485
486function captiveportal_init_rules_byinterface($interface) {
487 global $cpzone, $cpzoneid, $config;
488
489 if (!is_array($config['captiveportal'])) {
490 return;
491 }
492
493 foreach ($config['captiveportal'] as $cpkey => $cp) {
494 $cpzone = $cpkey;
495 $cpzoneid = $cp['zoneid'];
496 $cpinterfaces = explode(",", $cp['interface']);
497 if (in_array($interface, $cpinterfaces)) {
498 captiveportal_init_rules();
499 break;
500 }
501 }
502}
503
504/* Create basic rules used by all zones */
505function captiveportal_init_general_rules($flush = false) {
506 global $g;
507
508 $flush_rule = '';
509 if ($flush) {
510 $flush_rule = 'flush';
511 }
512
513 /* Already loaded */
514 if (!$flush && (mwexec("/sbin/ipfw list 1000", true) == 0)) {
515 return;
516 }
517
518 $cprules = <<<EOD
519{$flush_rule}
520# Table with interfaces that have CP enabled
521table cp_ifaces create type iface valtype skipto
522
523# Redirect each CP interface to its specific rule
524add 1000 skipto tablearg all from any to any via table(cp_ifaces)
525
526# This interface has no cp zone configured
527add 1100 allow all from any to any
528
529# block everything else
530add 65534 deny all from any to any
531EOD;
532
533 /* load rules */
534 file_put_contents("{$g['tmp_path']}/ipfw.cp.rules", $cprules);
535 mwexec("/sbin/ipfw -q {$g['tmp_path']}/ipfw.cp.rules", true);
536 @unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
537 unset($cprules);
538}
539
540/* Create a string with ipfw rule and increase rulenum */
541function captiveportal_create_ipfw_rule($cmd, &$rulenum, $args) {
542 $rule = "{$cmd} {$rulenum} {$args}\n";
543 $rulenum++;
544
545 return $rule;
546}
547
548/* Return first rule number for a cp zone */
549function captiveportal_ipfw_ruleno($id) {
550 global $g;
551
552 return 2000 + $id * $g['captiveportal_rules_interval'];
553}
554
555/* reinit will disconnect all users, be careful! */
556function captiveportal_init_rules($reinit = false) {
557 global $config, $g, $cpzone, $cpzoneid;
558
559 if (!isset($config['captiveportal'][$cpzone]['enable'])) {
560 return;
561 }
562
563 captiveportal_load_modules();
564 captiveportal_init_general_rules();
565
566 $skipto = captiveportal_ipfw_ruleno($cpzoneid);
567
568 $cprules = '';
569
570 $cpips = array();
571 $ifaces = get_configured_interface_list();
572 $cpinterfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
573 $firsttime = 0;
574 foreach ($cpinterfaces as $cpifgrp) {
575 if (!isset($ifaces[$cpifgrp])) {
576 continue;
577 }
578 $tmpif = get_real_interface($cpifgrp);
579 if (empty($tmpif)) {
580 continue;
581 }
582
583 $cpipm = get_interface_ip($cpifgrp);
584
585 if (!is_ipaddr($cpipm)) {
586 continue;
587 }
588
589 $cpips[] = $cpipm;
590 if (is_array($config['virtualip']['vip'])) {
591 foreach ($config['virtualip']['vip'] as $vip) {
592 if (($vip['interface'] == $cpifgrp) &&
593 (($vip['mode'] == "carp") ||
594 ($vip['mode'] == "ipalias"))) {
595 $cpips[] = $vip['subnet'];
596 }
597 }
598 }
599
600 $cprules .= "table cp_ifaces add {$tmpif} {$skipto}\n";
601 }
602 if (count($cpips) > 0) {
603 $cpactive = true;
604 } else {
605 return false;
606 }
607
608 $rulenum = $skipto;
609 $cprules .= "table {$cpzone}_pipe_mac create type mac valtype pipe\n";
610 $cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
611 "pipe tablearg MAC table({$cpzone}_pipe_mac)");
612 $cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
613 "allow pfsync from any to any");
614 $cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
615 "allow carp from any to any\n");
616 $cprules .= "# layer 2: pass ARP\n";
617 $cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
618 "pass layer2 mac-type arp,rarp");
619 $cprules .= "# pfsense requires for WPA\n";
620 $cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
621 "pass layer2 mac-type 0x888e,0x88c7");
622 $cprules .= "# PPP Over Ethernet Session Stage/Discovery Stage\n";
623 $cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
624 "pass layer2 mac-type 0x8863,0x8864\n");
625 $cprules .= "# layer 2: block anything else non-IP(v4/v6)\n";
626 $cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
627 "deny layer2 not mac-type ip,ipv6");
628
629 /* These tables contain host ips */
630 $cprules .= "table {$cpzone}_host_ips create type addr\n";
631 $cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
632 "pass ip from any to table({$cpzone}_host_ips) in");
633 $cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
634 "pass ip from table({$cpzone}_host_ips) to any out");
635 foreach ($cpips as $cpip) {
636 $cprules .= "table {$cpzone}_host_ips add {$cpip}\n";
637 }
638 $cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
639 "pass ip from any to 255.255.255.255 in");
640 $cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
641 "pass ip from 255.255.255.255 to any out");
642
643 /* Allowed ips */
644 $cprules .= "table {$cpzone}_allowed_up create type addr valtype pipe\n";
645 $cprules .= "table {$cpzone}_allowed_down create type addr valtype pipe\n";
646 $cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
647 "pipe tablearg ip from table({$cpzone}_allowed_up) to any in");
648 $cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
649 "pipe tablearg ip from any to table({$cpzone}_allowed_down) in");
650 $cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
651 "pipe tablearg ip from table({$cpzone}_allowed_up) to any out");
652 $cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
653 "pipe tablearg ip from any to table({$cpzone}_allowed_down) out");
654
655 /* Authenticated users rules. */
656 $cprules .= "table {$cpzone}_auth_up create type addr valtype pipe\n";
657 $cprules .= "table {$cpzone}_auth_down create type addr valtype pipe\n";
658 $cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
659 "pipe tablearg ip from table({$cpzone}_auth_up) to any layer2 in");
660 $cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
661 "pipe tablearg ip from any to table({$cpzone}_auth_down) layer2 out");
662
663 if (!empty($config['captiveportal'][$cpzone]['listenporthttp'])) {
664 $listenporthttp = $config['captiveportal'][$cpzone]['listenporthttp'];
665 } else {
666 $listenporthttp = 8000 + $cpzoneid;
667 }
668
669 if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
670 if (!empty($config['captiveportal'][$cpzone]['listenporthttps'])) {
671 $listenporthttps = $config['captiveportal'][$cpzone]['listenporthttps'];
672 } else {
673 $listenporthttps = 8001 + $cpzoneid;
674 }
675 if (!isset($config['captiveportal'][$cpzone]['nohttpsforwards'])) {
676 $cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
677 "fwd 127.0.0.1,{$listenporthttps} tcp from any to any dst-port 443 in");
678 }
679 }
680
681 $cprules .= "# redirect non-authenticated clients to captive portal\n";
682 $cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
683 "fwd 127.0.0.1,{$listenporthttp} tcp from any to any dst-port 80 in");
684 $cprules .= "# let the responses from the captive portal web server back out\n";
685 $cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
686 "pass tcp from any to any out");
687 $cprules .= "# This CP zone is over, skip to last rule\n";
688 $cprules .= captiveportal_create_ipfw_rule("add", $rulenum,
689 "skipto 65534 all from any to any");
690
691 /* generate passthru mac database */
692 $cprules .= captiveportal_passthrumac_configure(true);
693 $cprules .= "\n";
694
695 /* allowed ipfw rules to make allowed ip work */
696 $cprules .= captiveportal_allowedip_configure();
697
698 /* allowed ipfw rules to make allowed hostnames work */
699 $cprules .= captiveportal_allowedhostname_configure();
700
701 /* if reinit : flush pipes, so nothing is leaked. if not reinit, just destroy tables without flushing pipes */
702 if ($reinit) {
703 $pipes_to_remove = captiveportal_free_dnrules();
704 captiveportal_delete_rules($pipes_to_remove);
705 } else {
706 captiveportal_delete_rules();
707 }
708
709 /* load rules */
710 $captiveportallck = lock("captiveportal{$cpzone}");
711 file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
712 mwexec("/sbin/ipfw -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
713 @unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
714 unset($cprules);
715
716 captiveportal_filterdns_configure();
717 unlock($captiveportallck);
718
719 if ($reinit) {
720 unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
721 } else {
722 foreach (captiveportal_read_db() as $user) {
723 $bw_up = intval($user['bw_up']);
724 $bw_down = intval($user['bw_down']);
725 $clientip = $user['ip'];
726 $clientmac = $user['mac'];
727 $bw_up_pipeno = $user['pipeno'];
728 $bw_down_pipeno = $user['pipeno'] + 1;
729
730 $rule_entry = "{$clientip}/" . (is_ipaddrv6($clientip) ? "128" : "32");
731 if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
732 $rule_entry .= ",{$clientmac}";
733 }
734 $_gb = @pfSense_ipfw_pipe("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
735 $_gb = @pfSense_ipfw_pipe("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
736
737 $_gb = @pfSense_ipfw_table("{$cpzone}_auth_up", IP_FW_TABLE_XADD, "{$rule_entry}", $bw_up_pipeno);
738 $_gb = @pfSense_ipfw_table("{$cpzone}_auth_down", IP_FW_TABLE_XADD, "{$rule_entry}", $bw_down_pipeno);
739 }
740 }
741}
742
743/* Delete all rules related to specific cpzone */
744function captiveportal_delete_rules($pipes_to_remove = array()) {
745 global $g, $cpzoneid, $cpzone;
746
747 $skipto1 = captiveportal_ipfw_ruleno($cpzoneid);
748 $skipto2 = $skipto1 + $g['captiveportal_rules_interval'];
749
750 $cp_ifaces = pfSense_ipfw_table_list("cp_ifaces");
751 if (is_array($cp_ifaces)) {
752 foreach ($cp_ifaces as $cp_iface) {
753 if (empty($cp_iface['skipto']) ||
754 $cp_iface['skipto'] != $skipto1) {
755 continue;
756 }
757
758 pfSense_ipfw_table("cp_ifaces", IP_FW_TABLE_XDEL,
759 $cp_iface['iface']);
760 }
761 }
762
763 mwexec("/sbin/ipfw delete {$skipto1}-{$skipto2}", true);
764
765 $tables = captiveportal_get_ipfw_table_names();
766
767 $delrules = "";
768 foreach ($tables as $table) {
769 $delrules .= "table {$table} destroy\n";
770 }
771
772 foreach ($pipes_to_remove as $pipeno) {
773 $delrules .= "pipe delete {$pipeno}\n";
774 }
775
776 if (empty($delrules)) {
777 return;
778 }
779
780 file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.deltable.rules", $delrules);
781 mwexec("/sbin/ipfw -q {$g['tmp_path']}/ipfw_{$cpzone}.deltable.rules", true);
782 @unlink("{$g['tmp_path']}/ipfw_{$cpzone}.deltable.rules");
783}
784
785/*
786 * Remove clients that have been around for longer than the specified amount of time
787 * db file structure:
788 * timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time,interim_interval,traffic_quota,auth_method,context
789 * (password is in Base64 and only saved when reauthentication is enabled)
790 */
791function captiveportal_prune_old() {
792 global $g, $config, $cpzone, $cpzoneid;
793
794 if (empty($cpzone)) {
795 return;
796 }
797
798 $cpcfg = $config['captiveportal'][$cpzone];
799 $vcpcfg = $config['voucher'][$cpzone];
800
801 /* check for expired entries */
802 $idletimeout = 0;
803 $timeout = 0;
804 if (!empty($cpcfg['timeout']) && is_numeric($cpcfg['timeout'])) {
805 $timeout = $cpcfg['timeout'] * 60;
806 }
807
808 if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout'])) {
809 $idletimeout = $cpcfg['idletimeout'] * 60;
810 }
811
812 /* check for entries exceeding their traffic quota */
813 $trafficquota = 0;
814 if (!empty($cpcfg['trafficquota']) && is_numeric($cpcfg['trafficquota'])) {
815 $trafficquota = $cpcfg['trafficquota'] * 1048576;
816 }
817
818 /* Is there any job to do? */
819 if (!$timeout && !$idletimeout && !$trafficquota && !isset($cpcfg['reauthenticate']) &&
820 !isset($cpcfg['radiussession_timeout']) && !isset($cpcfg['radiustraffic_quota']) &&
821 !isset($vcpcfg['enable']) && !isset($cpcfg['radacct_enable'])) {
822 return;
823 }
824
825
826 /* Read database */
827 /* NOTE: while this can be simplified in non radius case keep as is for now */
828 $cpdb = captiveportal_read_db();
829
830 $unsetindexes = array();
831 $voucher_needs_sync = false;
832 /*
833 * Snapshot the time here to use for calculation to speed up the process.
834 * If something is missed next run will catch it!
835 */
836 $pruning_time = time();
837 foreach ($cpdb as $cpentry) {
838 $stop_time = $pruning_time;
839
840 $timedout = false;
841 $term_cause = 1;
842 /* hard timeout or session_timeout from radius if enabled */
843 if (isset($cpcfg['radiussession_timeout'])) {
844 $timeout = (is_numeric($cpentry[7])) ? $cpentry[7] : $timeout;
845 }
846 if ($timeout) {
847 if (($pruning_time - $cpentry[0]) >= $timeout) {
848 $timedout = true;
849 $term_cause = 5; // Session-Timeout
850 $logout_cause = 'SESSION TIMEOUT';
851 }
852 }
853
854 /* Session-Terminate-Time */
855 if (!$timedout && !empty($cpentry[9])) {
856 if ($pruning_time >= $cpentry[9]) {
857 $timedout = true;
858 $term_cause = 5; // Session-Timeout
859 $logout_cause = 'SESSION TIMEOUT';
860 }
861 }
862
863 /* check if an idle_timeout has been set and if its set change the idletimeout to this value */
864 $uidletimeout = (is_numeric($cpentry[8])) ? $cpentry[8] : $idletimeout;
865 /* if an idle timeout is specified, get last activity timestamp from ipfw */
866 if (!$timedout && $uidletimeout > 0) {
867 $lastact = captiveportal_get_last_activity($cpentry[2]);
868 /* If the user has logged on but not sent any traffic they will never be logged out.
869 * We "fix" this by setting lastact to the login timestamp.
870 */
871 $lastact = $lastact ? $lastact : $cpentry[0];
872 if ($lastact && (($pruning_time - $lastact) >= $uidletimeout)) {
873 $timedout = true;
874 $term_cause = 4; // Idle-Timeout
875 $logout_cause = 'IDLE TIMEOUT';
876 if (!isset($config['captiveportal'][$cpzone]['includeidletime'])) {
877 $stop_time = $lastact;
878 }
879 }
880 }
881
882 /* if vouchers are configured, activate session timeouts */
883 if (!$timedout && isset($vcpcfg['enable']) && !empty($cpentry[7])) {
884 if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
885 $timedout = true;
886 $term_cause = 5; // Session-Timeout
887 $logout_cause = 'SESSION TIMEOUT';
888 $voucher_needs_sync = true;
889 }
890 }
891
892 /* traffic quota, value retrieved from the radius attribute if the option is enabled */
893 if (isset($cpcfg['radiustraffic_quota'])) {
894 $utrafficquota = (is_numeric($cpentry[11])) ? $cpentry[11] : $trafficquota;
895 } else {
896 $utrafficquota = $trafficquota;
897 }
898 if (!$timedout && $utrafficquota > 0) {
899 $volume = getVolume($cpentry[2], $cpentry[3]);
900 if (($volume['input_bytes'] + $volume['output_bytes']) > $utrafficquota ) {
901 $timedout = true;
902 $term_cause = 10; // NAS-Request
903 $logout_cause = 'QUOTA EXCEEDED';
904 }
905 }
906
907 if ($timedout) {
908 captiveportal_disconnect($cpentry, $term_cause, $stop_time);
909 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], $logout_cause);
910 $unsetindexes[] = $cpentry[5];
911 }
912
913 /* do periodic reauthentication? For Radius servers, send accounting updates? */
914 if (!$timedout) {
915 //Radius servers : send accounting
916 if (isset($cpcfg['radacct_enable']) && $cpentry['authmethod'] === 'radius') {
917 if (substr($cpcfg['reauthenticateacct'], 0, 9) === "stopstart") {
918 /* stop and restart accounting */
919 if ($cpcfg['reauthenticateacct'] === "stopstartfreeradius") {
920 $rastart_time = 0;
921 $rastop_time = 60;
922 } else {
923 $rastart_time = $cpentry[0];
924 $rastop_time = time();
925 }
926 captiveportal_send_server_accounting('stop',
927 $cpentry[1], // ruleno
928 $cpentry[4], // username
929 $cpentry[2], // clientip
930 $cpentry[3], // clientmac
931 $cpentry[5], // sessionid
932 $rastart_time, // start time
933 $rastop_time, // Stop Time
934 10); // NAS Request
935 $clientsn = (is_ipaddrv6($cpentry[2])) ? 128 : 32;
936 pfSense_ipfw_table_zerocnt("{$cpzone}_auth_up", "{$cpentry[2]}/{$clientsn}");
937 pfSense_ipfw_table_zerocnt("{$cpzone}_auth_down", "{$cpentry[2]}/{$clientsn}");
938 if ($cpcfg['reauthenticateacct'] == "stopstartfreeradius") {
939 /* Need to pause here or the FreeRADIUS server gets confused about packet ordering. */
940 sleep(1);
941 }
942 captiveportal_send_server_accounting('start',
943 $cpentry[1], // ruleno
944 $cpentry[4], // username
945 $cpentry[2], // clientip
946 $cpentry[3], // clientmac
947 $cpentry[5]); // sessionid
948 } else if ($cpcfg['reauthenticateacct'] == "interimupdate") {
949 $session_time = $pruning_time - $cpentry[0];
950 if (!empty($cpentry[10]) && $cpentry[10] > 60) {
951 $interval = $cpentry[10];
952 } else {
953 $interval = 0;
954 }
955 $past_interval_min = ($session_time > $interval);
956 if ($interval != 0) {
957 $within_interval = ($session_time % $interval >= 0 && $session_time % $interval <= 59);
958 }
959 if ($interval === 0 || ($interval > 0 && $past_interval_min && $within_interval)) {
960 captiveportal_send_server_accounting('update',
961 $cpentry[1], // ruleno
962 $cpentry[4], // username
963 $cpentry[2], // clientip
964 $cpentry[3], // clientmac
965 $cpentry[5], // sessionid
966 $cpentry[0]); // start time
967 }
968 }
969 }
970
971 /* check this user again */
972 if (isset($cpcfg['reauthenticate']) && $cpentry['context'] !== 'voucher') {
973 $auth_result = captiveportal_authenticate_user(
974 $cpentry[4], // username
975 base64_decode($cpentry[6]), // password
976 $cpentry[3], // clientmac
977 $cpentry[2], // clientip
978 $cpentry[1], // ruleno
979 $cpentry['context']); // context
980 if ($auth_result['result'] === false) {
981 captiveportal_disconnect($cpentry, 17);
982 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT - REAUTHENTICATION FAILED", $auth_list['reply_message']);
983 $unsetindexes[] = $cpentry[5];
984 } else if ($auth_result['result'] === true) {
985 if ($cpentry['authmethod'] !== $auth_result['auth_method']) {
986 // if the user got authenticated against another server type: we update the database
987 if (!empty($cpentry[5])) {
988 captiveportal_update_entry($cpentry['sessionid'], $auth_result['auth_method'], 'authmethod');
989 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CHANGED AUTHENTICATION SERVER", $auth_list['reply_message']);
990 }
991 // User was logged on a RADIUS server, but is now logged in by another server type : we send an accounting Stop
992 if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && $cpentry['authmethod'] == 'radius') {
993 if ($cpcfg['reauthenticateacct'] === "stopstartfreeradius") {
994 $rastart_time = 0;
995 $rastop_time = 60;
996 } else {
997 $rastart_time = $cpentry[0];
998 $rastop_time = time();
999 }
1000 captiveportal_send_server_accounting('stop',
1001 $cpentry[1], // ruleno
1002 $cpentry[4], // username
1003 $cpentry[2], // clientip
1004 $cpentry[3], // clientmac
1005 $cpentry[5], // sessionid
1006 $rastart_time, // start time
1007 $rastop_time, // Stop Time
1008 3); // Lost Service
1009 // User was logged on a non-RADIUS Server but is now logged in by a RADIUS server : we send an accounting Start
1010 } else if(isset($config['captiveportal'][$cpzone]['radacct_enable']) && $auth_result['auth_method'] === 'radius') {
1011 captiveportal_send_server_accounting('start',
1012 $cpentry[1], // ruleno
1013 $cpentry[4], // username
1014 $cpentry[2], // clientip
1015 $cpentry[3], // clientmac
1016 $cpentry[5], // sessionid
1017 $cpentry[0]); // start_time
1018 }
1019 }
1020 captiveportal_reapply_attributes($cpentry, $auth_result['attributes']);
1021 }
1022 }
1023 }
1024 }
1025 unset($cpdb);
1026
1027 captiveportal_prune_old_automac();
1028
1029 if ($voucher_needs_sync == true) {
1030 /* Trigger a sync of the vouchers on config */
1031 send_event("service sync vouchers");
1032 }
1033
1034 /* write database */
1035 if (!empty($unsetindexes)) {
1036 captiveportal_remove_entries($unsetindexes);
1037 }
1038}
1039
1040function captiveportal_prune_old_automac() {
1041 global $g, $config, $cpzone, $cpzoneid;
1042
1043 if (is_array($config['captiveportal'][$cpzone]['passthrumac']) &&
1044 isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
1045 $tmpvoucherdb = array();
1046 $macrules = "";
1047 $writecfg = false;
1048 foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $eid => $emac) {
1049 if ($emac['logintype'] != "voucher") {
1050 continue;
1051 }
1052
1053 if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) {
1054 if (isset($tmpvoucherdb[$emac['username']])) {
1055 $temac = $config['captiveportal'][$cpzone]['passthrumac'][$tmpvoucherdb[$emac['username']]];
1056 $pipeno = captiveportal_get_dn_passthru_ruleno($temac['mac']);
1057 if ($pipeno) {
1058 captiveportal_free_dn_ruleno($pipeno);
1059 $macrules .= "table {$cpzone}_pipe_mac delete any,{$temac['mac']}\n";
1060 $macrules .= "table {$cpzone}_pipe_mac delete {$temac['mac']},any\n";
1061 $macrules .= "pipe delete {$pipeno}\n";
1062 ++$pipeno;
1063 $macrules .= "pipe delete {$pipeno}\n";
1064 }
1065 $writecfg = true;
1066 captiveportal_logportalauth($temac['username'], $temac['mac'],
1067 $temac['ip'], "DUPLICATE {$temac['username']} LOGIN - TERMINATING OLD SESSION");
1068 unset($config['captiveportal'][$cpzone]['passthrumac'][$tmpvoucherdb[$emac['username']]]);
1069 }
1070 $tmpvoucherdb[$emac['username']] = $eid;
1071 }
1072 if (voucher_auth($emac['username']) <= 0) {
1073 $pipeno = captiveportal_get_dn_passthru_ruleno($emac['mac']);
1074 if ($pipeno) {
1075 captiveportal_free_dn_ruleno($pipeno);
1076 $macrules .= "table {$cpzone}_pipe_mac delete any,{$emac['mac']}\n";
1077 $macrules .= "table {$cpzone}_pipe_mac delete {$emac['mac']},any\n";
1078 $macrules .= "pipe delete {$pipeno}\n";
1079 ++$pipeno;
1080 $macrules .= "pipe delete {$pipeno}\n";
1081 }
1082 $writecfg = true;
1083 captiveportal_logportalauth($emac['username'], $emac['mac'],
1084 $emac['ip'], "EXPIRED {$emac['username']} LOGIN - TERMINATING SESSION");
1085 unset($config['captiveportal'][$cpzone]['passthrumac'][$eid]);
1086 }
1087 }
1088 unset($tmpvoucherdb);
1089 if (!empty($macrules)) {
1090 @file_put_contents("{$g['tmp_path']}/macentry.prunerules.tmp", $macrules);
1091 unset($macrules);
1092 mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry.prunerules.tmp");
1093 }
1094 if ($writecfg === true) {
1095 write_config("Prune session for auto-added macs");
1096 }
1097 }
1098}
1099
1100/* remove a single client according to the DB entry */
1101function captiveportal_disconnect($dbent, $term_cause = 1, $stop_time = null) {
1102 global $g, $config, $cpzone, $cpzoneid;
1103
1104 $stop_time = (empty($stop_time)) ? time() : $stop_time;
1105
1106 /* this client needs to be deleted - remove ipfw rules */
1107 if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && $dbent['authmethod'] == 'radius') {
1108 if ($config['captiveportal'][$cpzone]['reauthenticateacct'] == "stopstartfreeradius") {
1109 /*
1110 * Interim updates are on so the session time must be
1111 * reported as the elapsed time since the previous
1112 * interim update.
1113 */
1114 $session_time = ($stop_time - $dbent[0]) % 60;
1115 $start_time = $stop_time - $session_time;
1116 } else {
1117 $start_time = $dbent[0];
1118 }
1119 captiveportal_send_server_accounting('stop',
1120 $dbent[1], // ruleno
1121 $dbent[4], // username
1122 $dbent[2], // clientip
1123 $dbent[3], // clientmac
1124 $dbent[5], // sessionid
1125 $start_time, // start time
1126 $stop_time, // stop time
1127 $term_cause); // Acct-Terminate-Cause
1128 }
1129
1130 if (is_ipaddr($dbent[2])) {
1131 /*
1132 * Delete client's ip entry from tables auth_up and auth_down.
1133 * It's not necessary to explicit specify mac address here
1134 */
1135 $cpsession = captiveportal_isip_logged($dbent[2]);
1136 if (!empty($cpsession)) {
1137 $clientsn = (is_ipaddrv6($dbent[2])) ? 128 : 32;
1138 pfSense_ipfw_table("{$cpzone}_auth_up",
1139 IP_FW_TABLE_XDEL, "{$dbent[2]}/{$clientsn}");
1140 pfSense_ipfw_table("{$cpzone}_auth_down",
1141 IP_FW_TABLE_XDEL, "{$dbent[2]}/{$clientsn}");
1142 }
1143 /* XXX: Redundant?! Ensure all pf(4) states are killed. */
1144 $_gb = @pfSense_kill_states($dbent[2]);
1145 $_gb = @pfSense_kill_srcstates($dbent[2]);
1146 }
1147
1148 /*
1149 * These are the pipe numbers we use to control traffic shaping for
1150 * each logged in user via captive portal
1151 * We could get an error if the pipe doesn't exist but everything
1152 * should still be fine
1153 */
1154 if (!empty($dbent[1])) {
1155 /*
1156 * Call captiveportal_free_dnrules() in dry_run mode to verify
1157 * if there are pipes to be removed and prevent the attempt to
1158 * delete invalid pipes
1159 */
1160 $removed_pipes = captiveportal_free_dnrules($dbent[1],
1161 $dbent[1]+1, true);
1162
1163 if (!empty($removed_pipes)) {
1164 $_gb = @pfSense_ipfw_pipe("pipe delete {$dbent[1]}");
1165 $_gb = @pfSense_ipfw_pipe("pipe delete " .
1166 ($dbent[1]+1));
1167
1168 /*
1169 * Release the ruleno so it can be reallocated to new
1170 * clients
1171 */
1172 captiveportal_free_dn_ruleno($dbent[1]);
1173 }
1174 }
1175
1176 // XMLRPC Call over to the master Voucher node
1177 if (xmlrpc_sync_voucher_details($syncip, $syncport,
1178 $vouchersyncusername, $syncpass)) {
1179 $remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip,
1180 $syncport, $syncpass, $vouchersyncusername, $term_cause,
1181 $stop_time);
1182 }
1183
1184}
1185
1186/* remove a single client by sessionid */
1187function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
1188 global $g, $config;
1189
1190 $sessionid = SQLite3::escapeString($sessionid);
1191 /* read database */
1192 $result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
1193
1194 /* find entry */
1195 if (!empty($result)) {
1196
1197 foreach ($result as $cpentry) {
1198 captiveportal_disconnect($cpentry, $term_cause);
1199 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
1200 }
1201 captiveportal_remove_entries(array($sessionid));
1202 unset($result);
1203 }
1204}
1205
1206/* remove all clients */
1207function captiveportal_disconnect_all($term_cause = 6, $logoutReason = "DISCONNECT") {
1208 global $g, $config, $cpzone, $cpzoneid;
1209
1210 /* check if we're pruning old entries and eventually wait */
1211 $rcprunelock = try_lock("rcprunecaptiveportal{$cpzone}", 15);
1212
1213 /* if we still don't have the lock, unlock forcefully and take it */
1214 if (!$rcprunelock) {
1215 log_error("CP zone ${cpzone}: could not obtain the lock for more than 15 seconds, lock taken forcefully to disconnect all users");
1216 unlock_force("rcprunecaptiveportal{$cpzone}");
1217 $rcprunelock = lock("rcprunecaptiveportal{$cpzone}", LOCK_EX);
1218 }
1219
1220 /* take a lock so new users won't be able to log in */
1221 $cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1222
1223 captiveportal_radius_stop_all($term_cause, $logoutReason);
1224
1225 /* reinit ipfw rules, flush user database */
1226 captiveportal_init_rules(true);
1227
1228 unlock($cpdblck);
1229 unlock($rcprunelock);
1230}
1231
1232/* send RADIUS acct stop for all current clients connected with RADIUS servers */
1233function captiveportal_radius_stop_all($term_cause = 6, $logoutReason = "DISCONNECT") {
1234 global $g, $config, $cpzone, $cpzoneid;
1235
1236 $cpdb = captiveportal_read_db();
1237
1238 $radacct = isset($config['captiveportal'][$cpzone]['radacct_enable']) ? true : false;
1239 foreach ($cpdb as $cpentry) {
1240 if ($cpentry['authmethod'] === 'radius' && $radacct) {
1241 if ($config['captiveportal'][$cpzone]['reauthenticateacct'] == "stopstartfreeradius") {
1242 $session_time = (time() - $cpentry[0]) % 60;
1243 $start_time = time() - $session_time;
1244 } else {
1245 $start_time = $cpentry[0];
1246 }
1247 captiveportal_send_server_accounting('stop',
1248 $cpentry[1], // ruleno
1249 $cpentry[4], // username
1250 $cpentry[2], // clientip
1251 $cpentry[3], // clientmac
1252 $cpentry[5], // sessionid
1253 $start_time, // start time
1254 $stop_time, // stop time
1255 $term_cause); // Acct-Terminate-Cause
1256 }
1257 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], $logoutReason);
1258 }
1259 unset($cpdb);
1260}
1261
1262function captiveportal_passthrumac_configure_entry($macent, $pipeinrule = false) {
1263 global $config, $g, $cpzone;
1264
1265 $bwUp = 0;
1266 if (!empty($macent['bw_up'])) {
1267 $bwUp = $macent['bw_up'];
1268 } else if (!empty($config['captiveportal'][$cpzone]['bwdefaultup'])) {
1269 $bwUp = $config['captiveportal'][$cpzone]['bwdefaultup'];
1270 }
1271 $bwDown = 0;
1272 if (!empty($macent['bw_down'])) {
1273 $bwDown = $macent['bw_down'];
1274 } else if (!empty($config['captiveportal'][$cpzone]['bwdefaultdn'])) {
1275 $bwDown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
1276 }
1277
1278 if ($macent['action'] == 'pass') {
1279 $rules = "";
1280
1281 $pipeno = captiveportal_get_next_dn_ruleno();
1282
1283 $pipeup = $pipeno;
1284 if ($pipeinrule == true) {
1285 $_gb = @pfSense_ipfw_pipe("pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16");
1286 } else {
1287 $rules .= "pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16\n";
1288 }
1289
1290 $pipedown = $pipeno + 1;
1291 if ($pipeinrule == true) {
1292 $_gb = @pfSense_ipfw_pipe("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
1293 } else {
1294 $rules .= "pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16\n";
1295 }
1296
1297 $rules .= "table {$cpzone}_pipe_mac add any,{$macent['mac']} {$pipeup}\n";
1298 $rules .= "table {$cpzone}_pipe_mac add {$macent['mac']},any {$pipedown}\n";
1299 }
1300
1301 return $rules;
1302}
1303
1304function captiveportal_passthrumac_delete_entry($macent) {
1305 global $cpzone;
1306 $rules = "";
1307
1308 if ($macent['action'] == 'pass') {
1309 $pipeno = captiveportal_get_dn_passthru_ruleno($macent['mac']);
1310
1311 if (!empty($pipeno)) {
1312 captiveportal_free_dn_ruleno($pipeno);
1313 $rules .= "table {$cpzone}_pipe_mac delete any,{$macent['mac']}\n";
1314 $rules .= "table {$cpzone}_pipe_mac delete {$macent['mac']},any\n";
1315 $rules .= "pipe delete " . $pipeno . "\n";
1316 $rules .= "pipe delete " . ++$pipeno . "\n";
1317 }
1318 }
1319
1320 return $rules;
1321}
1322
1323function captiveportal_passthrumac_configure($filename = false, $startindex = 0, $stopindex = 0) {
1324 global $config, $g, $cpzone;
1325
1326 $rules = "";
1327
1328 if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1329 if ($stopindex > 0) {
1330 $fd = fopen($filename, "w");
1331 for ($idx = $startindex; $idx <= $stopindex; $idx++) {
1332 if (isset($config['captiveportal'][$cpzone]['passthrumac'][$idx])) {
1333 $rules = captiveportal_passthrumac_configure_entry($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
1334 fwrite($fd, $rules);
1335 }
1336 }
1337 fclose($fd);
1338
1339 return;
1340 } else {
1341 $nentries = count($config['captiveportal'][$cpzone]['passthrumac']);
1342 if ($nentries > 2000) {
1343 $nloops = $nentries / 1000;
1344 $remainder= $nentries % 1000;
1345 for ($i = 0; $i < $nloops; $i++) {
1346 mwexec_bg("/usr/local/sbin/fcgicli -f /etc/rc.captiveportal_configure_mac -d \"cpzone={$cpzone}&startidx=" . ($i * 1000) . "&stopidx=" . ((($i+1) * 1000) - 1) . "\"");
1347 }
1348 if ($remainder > 0) {
1349 mwexec_bg("/usr/local/sbin/fcgicli -f /etc/rc.captiveportal_configure_mac -d \"cpzone={$cpzone}&startidx=" . ($i * 1000) . "&stopidx=" . (($i* 1000) + $remainder) ."\"");
1350 }
1351 } else {
1352 foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1353 $rules .= captiveportal_passthrumac_configure_entry($macent, true);
1354 }
1355 }
1356 }
1357 }
1358
1359 return $rules;
1360}
1361
1362function captiveportal_passthrumac_findbyname($username) {
1363 global $config, $cpzone;
1364
1365 if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1366 foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1367 if ($macent['username'] == $username) {
1368 return $macent;
1369 }
1370 }
1371 }
1372 return NULL;
1373}
1374
1375/*
1376 * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
1377 */
1378function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
1379 global $g, $config, $cpzone;
1380
1381 /* Instead of copying this entire function for something
1382 * easy such as hostname vs ip address add this check
1383 */
1384 if ($ishostname === true) {
1385 if (!platform_booting()) {
1386 $ipaddress = gethostbyname($ipent['hostname']);
1387 if (!is_ipaddr($ipaddress)) {
1388 return;
1389 }
1390 } else {
1391 $ipaddress = "";
1392 }
1393 } else {
1394 $ipaddress = $ipent['ip'];
1395 }
1396
1397 $rules = "";
1398 $cp_filterdns_conf = "";
1399 $enBwup = 0;
1400 if (!empty($ipent['bw_up'])) {
1401 $enBwup = intval($ipent['bw_up']);
1402 } else if (!empty($config['captiveportal'][$cpzone]['bwdefaultup'])) {
1403 $enBwup = $config['captiveportal'][$cpzone]['bwdefaultup'];
1404 }
1405 $enBwdown = 0;
1406 if (!empty($ipent['bw_down'])) {
1407 $enBwdown = intval($ipent['bw_down']);
1408 } else if (!empty($config['captiveportal'][$cpzone]['bwdefaultdn'])) {
1409 $enBwdown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
1410 }
1411
1412 $pipeup = captiveportal_get_next_dn_ruleno();
1413 $_gb = @pfSense_ipfw_pipe("pipe {$pipeup} config bw {$enBwup}Kbit/s queue 100 buckets 16");
1414 $pipedown = $pipeup + 1;
1415 $_gb = @pfSense_ipfw_pipe("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16");
1416
1417 if ($ishostname === true) {
1418 $cp_filterdns_conf .= "ipfw {$ipent['hostname']} {$cpzone}_allowed_up pipe {$pipeup}\n";
1419 $cp_filterdns_conf .= "ipfw {$ipent['hostname']} {$cpzone}_allowed_down pipe {$pipedown}\n";
1420 if (!is_ipaddr($ipaddress)) {
1421 return array("", $cp_filterdns_conf);
1422 }
1423 }
1424
1425 $subnet = "";
1426 if (!empty($ipent['sn'])) {
1427 $subnet = "/{$ipent['sn']}";
1428 }
1429 $rules .= "table {$cpzone}_allowed_up add {$ipaddress}{$subnet} {$pipeup}\n";
1430 $rules .= "table {$cpzone}_allowed_down add {$ipaddress}{$subnet} {$pipedown}\n";
1431
1432 if ($ishostname === true) {
1433 return array($rules, $cp_filterdns_conf);
1434 } else {
1435 return $rules;
1436 }
1437}
1438
1439function captiveportal_allowedhostname_configure() {
1440 global $config, $g, $cpzone, $cpzoneid;
1441
1442 $rules = "";
1443 if (!is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1444 return $rules;
1445 }
1446
1447 $rules = "\n# captiveportal_allowedhostname_configure()\n";
1448 $cp_filterdns_conf = "";
1449 foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) {
1450 $tmprules = captiveportal_allowedip_configure_entry($hostnameent, true);
1451 $rules .= $tmprules[0];
1452 $cp_filterdns_conf .= $tmprules[1];
1453 }
1454 $cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1455 @file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1456 unset($cp_filterdns_conf);
1457
1458 return $rules;
1459}
1460
1461function captiveportal_filterdns_configure() {
1462 global $config, $g, $cpzone, $cpzoneid;
1463
1464 $cp_filterdns_filename = $g['varetc_path'] .
1465 "/filterdns-{$cpzone}-captiveportal.conf";
1466
1467 if (isset($config['captiveportal'][$cpzone]['enable']) &&
1468 is_array($config['captiveportal'][$cpzone]['allowedhostname']) &&
1469 file_exists($cp_filterdns_filename)) {
1470 if (isvalidpid($g['varrun_path'] .
1471 "/filterdns-{$cpzone}-cpah.pid")) {
1472 sigkillbypid($g['varrun_path'] .
1473 "/filterdns-{$cpzone}-cpah.pid", "HUP");
1474 } else {
1475 mwexec("/usr/local/sbin/filterdns -p " .
1476 "{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid" .
1477 " -i 300 -c {$cp_filterdns_filename} -d 1");
1478 }
1479 } else {
1480 killbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1481 @unlink("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1482 }
1483
1484 return $rules;
1485}
1486
1487function captiveportal_allowedip_configure() {
1488 global $config, $g, $cpzone;
1489
1490 $rules = "";
1491 if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1492 foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) {
1493 $rules .= captiveportal_allowedip_configure_entry($ipent);
1494 }
1495 }
1496
1497 return $rules;
1498}
1499
1500/* get last activity timestamp given client IP address */
1501function captiveportal_get_last_activity($ip) {
1502 global $cpzone;
1503
1504 /* Reading only from one of the tables is enough of approximation. */
1505 $tables = array("{$cpzone}_allowed_up", "{$cpzone}_auth_up");
1506
1507 foreach ($tables as $table) {
1508 $ipfw = pfSense_ipfw_table_lookup($table, $ip);
1509 if (is_array($ipfw)) {
1510 /* Workaround for #46652 */
1511 if ($ipfw['packets'] > 0) {
1512 return $ipfw['timestamp'];
1513 } else {
1514 return 0;
1515 }
1516 }
1517 }
1518
1519 return 0;
1520}
1521
1522
1523/* log successful captive portal authentication to syslog */
1524/* part of this code from php.net */
1525function captiveportal_logportalauth($user, $mac, $ip, $status, $message = null) {
1526 // Log it
1527 if (!$message) {
1528 $message = "{$status}: {$user}, {$mac}, {$ip}";
1529 } else {
1530 $message = trim($message);
1531 $message = "{$status}: {$user}, {$mac}, {$ip}, {$message}";
1532 }
1533 captiveportal_syslog($message);
1534}
1535
1536/* log simple messages to syslog */
1537function captiveportal_syslog($message) {
1538 global $cpzone;
1539
1540 $message = trim($message);
1541 $message = "Zone: {$cpzone} - {$message}";
1542 openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1543 // Log it
1544 syslog(LOG_INFO, $message);
1545 closelog();
1546}
1547
1548/* Authenticate users using Authentication Backend */
1549function captiveportal_authenticate_user(&$login = '', &$password = '', $clientmac = '', $clientip = '', $pipeno = 'null', $context = 'first') {
1550 global $g, $config, $cpzone;
1551 $cpcfg = $config['captiveportal'][$cpzone];
1552
1553 $login_status = 'FAILURE';
1554 $login_msg = gettext('Invalid credentials specified');
1555 $reply_attributes = array();
1556 $auth_method = '';
1557 $auth_result = null;
1558
1559 /*
1560 Management of the reply Message (reason why the authentication failed) :
1561 multiple authentication servers can be used, so multiple reply messages could theoretically be returned.
1562 But only one message is returned (the most important one).
1563 The return value of authenticate_user() define how important messages are :
1564 - Reply message of a successful auth is more important than reply message of
1565 a user failed auth(invalid credentials/authorization)
1566
1567 - Reply message of a user failed auth is more important than reply message of
1568 a server failed auth (unable to contact server)
1569
1570 - When multiple user failed auth are encountered, messages returned by remote servers
1571 (eg. reply in RADIUS Access-Reject) are more important than pfSense error messages.
1572
1573 The $authlevel variable is a flag indicating the status of authentication
1574 0 = failed server auth
1575 1 = failed user auth
1576 2 = failed user auth with custom server reply recieved
1577 3 = successful auth
1578 */
1579 $authlevel = 0;
1580
1581 /* Getting authentication servers from captiveportal configuration */
1582 $auth_servers = array();
1583
1584 if ($cpcfg['auth_method'] === 'none') {
1585 $auth_servers[] = array('type' => 'none');
1586 } else {
1587 if ($context === 'second') {
1588 $fullauthservers = explode(",", $cpcfg['auth_server2']);
1589 } else {
1590 $fullauthservers = explode(",", $cpcfg['auth_server']);
1591 }
1592
1593 foreach ($fullauthservers as $authserver) {
1594 if (strpos($authserver, ' - ') !== false) {
1595 $authserver = explode(' - ', $authserver);
1596 array_shift($authserver);
1597 $authserver = implode(' - ', $authserver);
1598
1599 if (auth_get_authserver($authserver) !== null) {
1600 $auth_servers[] = auth_get_authserver($authserver);
1601 } else {
1602 log_error("Zone: {$cpzone} - Captive portal was unable to find the settings of the server '{$authserver}' used for authentication !");
1603 }
1604 }
1605 }
1606 }
1607
1608 /* Unable to find the any authentication server config - shouldn't happen! - bail out */
1609 if (count($auth_servers) === 0) {
1610 log_error("Zone: {$cpzone} - No valid server could be used for authentication.");
1611 $login_msg = gettext("Internal Error");
1612 } else {
1613 foreach ($auth_servers as $authcfg) {
1614 if ($authlevel < 3) {
1615 $radmac_error = false;
1616 $attributes = array("nas_identifier" => empty($cpcfg["radiusnasid"]) ? "CaptivePortal-{$cpzone}" : $cpcfg["radiusnasid"],
1617 "nas_port_type" => RADIUS_ETHERNET,
1618 "nas_port" => $pipeno,
1619 "framed_ip" => $clientip);
1620 if (mac_format($clientmac) !== null) {
1621 $attributes["calling_station_id"] = mac_format($clientmac);
1622 }
1623
1624 $result = null;
1625 $status = null;
1626 $msg = null;
1627
1628 /* Radius MAC authentication */
1629 if ($context === 'radmac' && $clientmac) {
1630 if ($authcfg['type'] === 'radius') {
1631 $login = mac_format($clientmac);
1632 $status = "MACHINE LOGIN";
1633 } else {
1634 /* Trying to perform a Radius MAC authentication on a non-radius server - shouldn't happen! - bail out */
1635 $msg = gettext("Internal Error");
1636 log_error("Zone: {$cpzone} - Trying to perform RADIUS MAC authentication on a non-RADIUS server !");
1637 $radmac_error = true;
1638 $result = null;
1639 }
1640 }
1641
1642 if (!$radmac_error) {
1643 if ($authcfg['type'] === 'none') {
1644 $result = true;
1645 } else {
1646 $result = authenticate_user($login, $password, $authcfg, $attributes);
1647 }
1648
1649 if (!empty($attributes['error_message'])) {
1650 $msg = $attributes['error_message'];
1651 }
1652
1653 if ($authcfg['type'] == 'Local Auth' && $result && isset($cpcfg['localauth_priv'])) {
1654 if (!userHasPrivilege(getUserEntry($login), "user-services-captiveportal-login")) {
1655 $result = false;
1656 $msg = gettext("Access Denied");
1657 }
1658 }
1659 if ($context === 'radmac' && $result === null && empty($attributes['reply_message'])) {
1660 $msg = gettext("RADIUS MAC Authentication Failed.");
1661 }
1662
1663 if (empty($status)) {
1664 if ($result === true) {
1665 $status = "ACCEPT";
1666 } elseif ($result === null) {
1667 $status = "ERROR";
1668 } else {
1669 $status = "FAILURE";
1670 }
1671 }
1672
1673 if ($context === 'radmac' && $login == mac_format($clientmac) || $authcfg['type'] === 'none' && empty($login)) {
1674 $login = "unauthenticated";
1675 }
1676 }
1677 // We determine a flag
1678 if ($result === true) {
1679 $val = 3;
1680 } elseif ($result === false && !empty($attributes['reply_message'])) {
1681 $val = 2;
1682 $msg = $attributes['reply_message'];
1683 } elseif ($result === false) {
1684 $val = 1;
1685 } elseif ($result === null) {
1686 $val = 0;
1687 }
1688
1689 if ($val >= $authlevel) {
1690 $authlevel = $val;
1691 $auth_method = $authcfg['type'];
1692 $login_status = $status;
1693 $login_msg = $msg;
1694 $reply_attributes = $attributes;
1695 $auth_result = $result;
1696 }
1697 }
1698 }
1699 }
1700
1701 return array('result'=>$auth_result, 'attributes'=>$reply_attributes, 'auth_method' =>$auth_method, 'login_status'=> $login_status, 'login_message' => $login_msg);
1702}
1703
1704function captiveportal_opendb() {
1705 global $g, $config, $cpzone, $cpzoneid;
1706
1707 $db_path = "{$g['vardb_path']}/captiveportal{$cpzone}.db";
1708 $createquery = "CREATE TABLE IF NOT EXISTS captiveportal (" .
1709 "allow_time INTEGER, pipeno INTEGER, ip TEXT, mac TEXT, username TEXT, " .
1710 "sessionid TEXT, bpassword TEXT, session_timeout INTEGER, idle_timeout INTEGER, " .
1711 "session_terminate_time INTEGER, interim_interval INTEGER, traffic_quota INTEGER, " .
1712 "bw_up INTEGER, bw_down INTEGER, authmethod TEXT, context TEXT); " .
1713 "CREATE UNIQUE INDEX IF NOT EXISTS idx_active ON captiveportal (sessionid, username); " .
1714 "CREATE INDEX IF NOT EXISTS user ON captiveportal (username); " .
1715 "CREATE INDEX IF NOT EXISTS ip ON captiveportal (ip); " .
1716 "CREATE INDEX IF NOT EXISTS starttime ON captiveportal (allow_time)";
1717
1718 try {
1719 $DB = new SQLite3($db_path);
1720 $DB->busyTimeout(60000);
1721 } catch (Exception $e) {
1722 captiveportal_syslog("Could not open {$db_path} as an sqlite database for {$cpzone}. Error message: " . $e->getMessage() . " -- Trying again.");
1723 unlink_if_exists($db_path);
1724 try {
1725 $DB = new SQLite3($db_path);
1726 $DB->busyTimeout(60000);
1727 } catch (Exception $e) {
1728 captiveportal_syslog("Still could not open {$db_path} as an sqlite database for {$cpzone}. Error message: " . $e->getMessage() . " -- Remove the database file manually and ensure there is enough free space.");
1729 return;
1730 }
1731 }
1732
1733 if (!$DB) {
1734 captiveportal_syslog("Could not open {$db_path} as an sqlite database for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Trying again.");
1735 unlink_if_exists($db_path);
1736 $DB = new SQLite3($db_path);
1737 $DB->busyTimeout(60000);
1738 if (!$DB) {
1739 captiveportal_syslog("Still could not open {$db_path} as an sqlite database for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Remove the database file manually and ensure there is enough free space.");
1740 return;
1741 }
1742 }
1743
1744 if (! $DB->exec($createquery)) {
1745 captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$DB->lastErrorMsg()}. Resetting and trying again.");
1746
1747 /* If unable to initialize the database, reset and try again. */
1748 $DB->close();
1749 unset($DB);
1750 unlink_if_exists($db_path);
1751 $DB = new SQLite3($db_path);
1752 $DB->busyTimeout(60000);
1753 if ($DB->exec($createquery)) {
1754 captiveportal_syslog("Successfully reinitialized tables for {$cpzone} -- database has been reset.");
1755 if (!is_numericint($cpzoneid)) {
1756 if (is_array($config['captiveportal'])) {
1757 foreach ($config['captiveportal'] as $cpkey => $cp) {
1758 if ($cpzone == $cpkey) {
1759 $cpzoneid = $cp['zoneid'];
1760 }
1761 }
1762 }
1763 }
1764 if (is_numericint($cpzoneid)) {
1765 $table_names = captiveportal_get_ipfw_table_names();
1766 foreach ($table_names as $table_name) {
1767 mwexec("/sbin/ipfw table {$table_name} flush");
1768 }
1769 captiveportal_syslog("Flushed tables for {$cpzone} after database reset.");
1770 }
1771 } else {
1772 captiveportal_syslog("Still unable to create tables for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Remove the database file manually and try again.");
1773 }
1774 }
1775
1776 return $DB;
1777}
1778
1779/* Get all tables for specific cpzone */
1780function captiveportal_get_ipfw_table_names() {
1781 global $cpzone;
1782
1783 $result = array();
1784 $tables = pfSense_ipfw_tables_list();
1785
1786 if (!is_array($tables)) {
1787 return $result;
1788 }
1789
1790 $len = strlen($cpzone) + 1;
1791 foreach ($tables as $table) {
1792 if (substr($table['name'], 0, $len) != $cpzone . '_') {
1793 continue;
1794 }
1795
1796 $result[] = $table['name'];
1797 }
1798
1799 return $result;
1800}
1801
1802/* read captive portal DB into array */
1803function captiveportal_read_db($query = "") {
1804 $cpdb = array();
1805
1806 $DB = captiveportal_opendb();
1807 if ($DB) {
1808 $response = $DB->query("SELECT * FROM captiveportal {$query}");
1809 if ($response != FALSE) {
1810 while ($row = $response->fetchArray()) {
1811 $cpdb[] = $row;
1812 }
1813 }
1814 $DB->close();
1815 }
1816
1817 return $cpdb;
1818}
1819
1820function captiveportal_remove_entries($remove) {
1821
1822 if (!is_array($remove) || empty($remove)) {
1823 return;
1824 }
1825
1826 $query = "DELETE FROM captiveportal WHERE sessionid in (";
1827 foreach ($remove as $idx => $unindex) {
1828 $query .= "'{$unindex}'";
1829 if ($idx < (count($remove) - 1)) {
1830 $query .= ",";
1831 }
1832 }
1833 $query .= ")";
1834 captiveportal_write_db($query);
1835}
1836
1837/* write captive portal DB */
1838function captiveportal_write_db($queries) {
1839 global $g;
1840
1841 if (is_array($queries)) {
1842 $query = implode(";", $queries);
1843 } else {
1844 $query = $queries;
1845 }
1846
1847 $DB = captiveportal_opendb();
1848 if ($DB) {
1849 $DB->exec("BEGIN TRANSACTION");
1850 $result = $DB->exec($query);
1851 if (!$result) {
1852 captiveportal_syslog("Trying to modify DB returned error: {$DB->lastErrorMsg()}");
1853 } else {
1854 $DB->exec("END TRANSACTION");
1855 }
1856 $DB->close();
1857 return $result;
1858 } else {
1859 return true;
1860 }
1861}
1862
1863function captiveportal_write_elements() {
1864 global $g, $config, $cpzone;
1865
1866 $cpcfg = $config['captiveportal'][$cpzone];
1867
1868 if (!is_dir($g['captiveportal_element_path'])) {
1869 @mkdir($g['captiveportal_element_path']);
1870 }
1871
1872 if (is_array($cpcfg['element'])) {
1873 foreach ($cpcfg['element'] as $data) {
1874 /* Do not attempt to decode or write out empty files. */
1875 if (isset($data['nocontent'])) {
1876 continue;
1877 }
1878 if (empty($data['content']) || empty(base64_decode($data['content']))) {
1879 unlink_if_exists("{$g['captiveportal_element_path']}/{$data['name']}");
1880 touch("{$g['captiveportal_element_path']}/{$data['name']}");
1881 } elseif (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1882 printf(gettext('Error: cannot open \'%1$s\' in captiveportal_write_elements()%2$s'), $data['name'], "\n");
1883 return 1;
1884 }
1885 if (!file_exists("{$g['captiveportal_path']}/{$data['name']}")) {
1886 @symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1887 }
1888 }
1889 }
1890
1891 return 0;
1892}
1893
1894function captiveportal_free_dnrules($rulenos_start = 2000,
1895 $rulenos_range_max = 64500, $dry_run = false) {
1896 global $g, $cpzone;
1897
1898 $removed_pipes = array();
1899
1900 if (!file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1901 return $removed_pipes;
1902 }
1903
1904 if (!$dry_run) {
1905 $cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1906 }
1907
1908 $rules = unserialize(file_get_contents(
1909 "{$g['vardb_path']}/captiveportaldn.rules"));
1910 $ridx = $rulenos_start;
1911 while ($ridx < $rulenos_range_max) {
1912 if ($rules[$ridx] == $cpzone) {
1913 if (!$dry_run) {
1914 $rules[$ridx] = false;
1915 }
1916 $removed_pipes[] = $ridx;
1917 $ridx++;
1918 if (!$dry_run) {
1919 $rules[$ridx] = false;
1920 }
1921 $removed_pipes[] = $ridx;
1922 $ridx++;
1923 } else {
1924 $ridx += 2;
1925 }
1926 }
1927
1928 if (!$dry_run) {
1929 file_put_contents("{$g['vardb_path']}/captiveportaldn.rules",
1930 serialize($rules));
1931 unlock($cpruleslck);
1932 }
1933
1934 unset($rules);
1935
1936 return $removed_pipes;
1937}
1938
1939function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
1940 global $config, $g, $cpzone;
1941
1942 $cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1943 $ruleno = 0;
1944 if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1945 $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1946 $ridx = $rulenos_start;
1947 while ($ridx < $rulenos_range_max) {
1948 if (empty($rules[$ridx])) {
1949 $ruleno = $ridx;
1950 $rules[$ridx] = $cpzone;
1951 $ridx++;
1952 $rules[$ridx] = $cpzone;
1953 break;
1954 } else {
1955 $ridx += 2;
1956 }
1957 }
1958 } else {
1959 $rules = array_pad(array(), $rulenos_range_max, false);
1960 $ruleno = $rulenos_start;
1961 $rules[$rulenos_start] = $cpzone;
1962 $rulenos_start++;
1963 $rules[$rulenos_start] = $cpzone;
1964 }
1965 file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1966 unlock($cpruleslck);
1967 unset($rules);
1968
1969 return $ruleno;
1970}
1971
1972function captiveportal_free_dn_ruleno($ruleno) {
1973 global $config, $g;
1974
1975 $cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1976 if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1977 $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1978 $rules[$ruleno] = false;
1979 $ruleno++;
1980 $rules[$ruleno] = false;
1981 file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1982 unset($rules);
1983 }
1984 unlock($cpruleslck);
1985}
1986
1987function captiveportal_get_dn_passthru_ruleno($value) {
1988 global $config, $g, $cpzone, $cpzoneid;
1989
1990 $cpcfg = $config['captiveportal'][$cpzone];
1991 if (!isset($cpcfg['enable'])) {
1992 return NULL;
1993 }
1994
1995 $cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1996 $ruleno = NULL;
1997 if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1998 unset($output);
1999 $item = pfSense_ipfw_table_lookup("{$cpzone}_pipe_mac",
2000 "any,{$value}");
2001 if (!is_array($item) || empty($item['pipe'])) {
2002 unlock($cpruleslck);
2003 return NULL;
2004 }
2005
2006 $ruleno = intval($item['pipe']);
2007 $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
2008 if (!$rules[$ruleno]) {
2009 $ruleno = NULL;
2010 }
2011 unset($rules);
2012 }
2013 unlock($cpruleslck);
2014
2015 return $ruleno;
2016}
2017
2018/**
2019 * This function will calculate the traffic produced by a client
2020 * based on its firewall rule
2021 *
2022 * Point of view: NAS
2023 *
2024 * Input means: from the client
2025 * Output means: to the client
2026 *
2027 */
2028
2029function getVolume($ip) {
2030 global $config, $cpzone;
2031
2032 $reverse = isset($config['captiveportal'][$cpzone]['reverseacct'])
2033 ? true : false;
2034 $volume = array();
2035 // Initialize vars properly, since we don't want NULL vars
2036 $volume['input_pkts'] = $volume['input_bytes'] = 0;
2037 $volume['output_pkts'] = $volume['output_bytes'] = 0;
2038
2039 $tables = array("allowed", "auth");
2040
2041 foreach($tables as $table) {
2042 $ipfw = pfSense_ipfw_table_lookup("{$cpzone}_{$table}_up", $ip);
2043 if (!is_array($ipfw)) {
2044 continue;
2045 }
2046 if ($reverse) {
2047 $volume['output_pkts'] = $ipfw['packets'];
2048 $volume['output_bytes'] = $ipfw['bytes'];
2049 } else {
2050 $volume['input_pkts'] = $ipfw['packets'];
2051 $volume['input_bytes'] = $ipfw['bytes'];
2052 }
2053 }
2054
2055 foreach($tables as $table) {
2056 $ipfw = pfSense_ipfw_table_lookup("{$cpzone}_{$table}_down",
2057 $ip);
2058 if (!is_array($ipfw)) {
2059 continue;
2060 }
2061 if ($reverse) {
2062 $volume['input_pkts'] = $ipfw['packets'];
2063 $volume['input_bytes'] = $ipfw['bytes'];
2064 } else {
2065 $volume['output_pkts'] = $ipfw['packets'];
2066 $volume['output_bytes'] = $ipfw['bytes'];
2067 }
2068 }
2069
2070 return $volume;
2071}
2072
2073function portal_ip_from_client_ip($cliip) {
2074 global $config, $cpzone;
2075
2076 $isipv6 = is_ipaddrv6($cliip);
2077 $interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
2078 foreach ($interfaces as $cpif) {
2079 if ($isipv6) {
2080 $ip = get_interface_ipv6($cpif);
2081 $sn = get_interface_subnetv6($cpif);
2082 } else {
2083 $ip = get_interface_ip($cpif);
2084 $sn = get_interface_subnet($cpif);
2085 }
2086 if (ip_in_subnet($cliip, "{$ip}/{$sn}")) {
2087 return $ip;
2088 }
2089 }
2090
2091 $inet = ($isipv6) ? '-inet6' : '-inet';
2092 $iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
2093 $iface = trim($iface, "\n");
2094 if (!empty($iface)) {
2095 $ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
2096 if (is_ipaddr($ip)) {
2097 return $ip;
2098 }
2099 }
2100
2101 // doesn't match up to any particular interface
2102 // so let's set the portal IP to what PHP says
2103 // the server IP issuing the request is.
2104 // allows same behavior as 1.2.x where IP isn't
2105 // in the subnet of any CP interface (static routes, etc.)
2106 // rather than forcing to DNS hostname resolution
2107 $ip = $_SERVER['SERVER_ADDR'];
2108 if (is_ipaddr($ip)) {
2109 return $ip;
2110 }
2111
2112 return false;
2113}
2114
2115function portal_hostname_from_client_ip($cliip) {
2116 global $config, $cpzone;
2117
2118 $cpcfg = $config['captiveportal'][$cpzone];
2119
2120 if (isset($cpcfg['httpslogin'])) {
2121 $listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 8001);
2122 $ourhostname = $cpcfg['httpsname'];
2123
2124 if ($listenporthttps != 443) {
2125 $ourhostname .= ":" . $listenporthttps;
2126 }
2127 } else {
2128 $listenporthttp = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : ($cpcfg['zoneid'] + 8000);
2129 $ifip = portal_ip_from_client_ip($cliip);
2130 if (!$ifip) {
2131 $ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
2132 } else {
2133 $ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
2134 }
2135
2136 if ($listenporthttp != 80) {
2137 $ourhostname .= ":" . $listenporthttp;
2138 }
2139 }
2140
2141 return $ourhostname;
2142}
2143
2144/* functions move from index.php */
2145
2146function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
2147 global $g, $config, $cpzone;
2148
2149 /* Get captive portal layout */
2150 if ($type == "redir") {
2151 header("Location: {$redirurl}");
2152 return;
2153 } else if ($type == "login") {
2154 $htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
2155 } else {
2156 $htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
2157 }
2158
2159 $cpcfg = $config['captiveportal'][$cpzone];
2160
2161 /* substitute the PORTAL_REDIRURL variable */
2162 if ($cpcfg['preauthurl']) {
2163 $htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
2164 $htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
2165 }
2166
2167 /* substitute other variables */
2168 $ourhostname = portal_hostname_from_client_ip($clientip);
2169 $protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
2170 $htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/index.php?zone={$cpzone}", $htmltext);
2171 $htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/index.php?zone={$cpzone}", $htmltext);
2172
2173 $htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
2174 $htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
2175 $htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
2176 $htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
2177 $htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
2178
2179 // Special handling case for captive portal master page so that it can be ran
2180 // through the PHP interpreter using the include method above. We convert the
2181 // $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
2182 $htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
2183 $htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
2184 $htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
2185 $htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
2186 $htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
2187 $htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
2188 $htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
2189
2190 echo $htmltext;
2191}
2192
2193function captiveportal_reapply_attributes($cpentry, $attributes) {
2194 global $config, $cpzone, $g;
2195
2196 if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
2197 $dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
2198 $dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
2199 } else {
2200 $dwfaultbw_up = $dwfaultbw_down = 0;
2201 }
2202 /* pipe throughputs must always be an integer, enforce that restriction again here. */
2203 if (isset($config['captiveportal'][$cpzone]['radiusperuserbw'])) {
2204 $bw_up = round(!empty($attributes['bw_up']) ? intval($attributes['bw_up'])/1000 : $dwfaultbw_up, 0);
2205 $bw_down = round(!empty($attributes['bw_down']) ? intval($attributes['bw_down'])/1000 : $dwfaultbw_down, 0);
2206 } else {
2207 $bw_up = round($dwfaultbw_up,0);
2208 $bw_down = round($dwfaultbw_down,0);
2209 }
2210
2211 $bw_up_pipeno = $cpentry[1];
2212 $bw_down_pipeno = $cpentry[1]+1;
2213
2214 if ($cpentry['bw_up'] !== $bw_up) {
2215 $_gb = @pfSense_ipfw_pipe("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
2216 captiveportal_update_entry($cpentry['sessionid'], $bw_up, 'bw_up');
2217 }
2218 if ($cpentry['bw_down'] !== $bw_down) {
2219 $_gb = @pfSense_ipfw_pipe("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
2220 captiveportal_update_entry($cpentry['sessionid'], $bw_down, 'bw_down');
2221 }
2222 unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
2223}
2224
2225function captiveportal_update_entry($sessionid, $new_value, $field_to_update) {
2226 global $config, $cpzone, $g;
2227
2228 if (!intval($new_value)) {
2229 $new_value = "'{$new_value}'";
2230 }
2231 captiveportal_write_db("UPDATE captiveportal SET {$field_to_update} = {$new_value} WHERE sessionid = '{$sessionid}'");
2232}
2233
2234function portal_allow($clientip, $clientmac, $username, $password = null, $attributes = null, $pipeno = null, $authmethod = null, $context = 'first') {
2235 global $redirurl, $g, $config, $type, $_POST, $cpzone, $cpzoneid;
2236
2237 // Ensure we create an array if we are missing attributes
2238 if (!is_array($attributes)) {
2239 $attributes = array();
2240 }
2241
2242 unset($sessionid);
2243
2244 /* Do not allow concurrent login execution. */
2245 $cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
2246
2247 if ($attributes['voucher']) {
2248 $remaining_time = $attributes['session_timeout'];
2249 $authmethod = "voucher"; // Set RADIUS-Attribute to Voucher to prevent ReAuth-Reqeuest for Vouchers Bug: #2155
2250 $context = "voucher";
2251 }
2252
2253 $writecfg = false;
2254 /* If both "Add MAC addresses of connected users as pass-through MAC" and "Disable concurrent logins" is 'last' ,
2255 then we need to check if the user was already authenticated using another MAC Address, remove the previous Pass-Through MAC. */
2256
2257 if (((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $config['captiveportal'][$cpzone]['noconcurrentlogins'] == 'last') &&
2258 ($username != 'unauthenticated') && isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
2259 $mac = captiveportal_passthrumac_findbyname($username);
2260 if (!empty($mac)) {
2261 foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
2262 if ($macent['mac'] != $mac['mac']) {
2263 continue;
2264 }
2265
2266 $pipeno = captiveportal_get_dn_passthru_ruleno($mac['mac']);
2267 if ($pipeno) {
2268 captiveportal_free_dn_ruleno($pipeno);
2269 @pfSense_ipfw_table("{$cpzone}_pipe_mac", IP_FW_TABLE_XDEL, "any,{$mac['mac']}");
2270 @pfSense_ipfw_table("{$cpzone}_pipe_mac", IP_FW_TABLE_XDEL, "{$mac['mac']},any");
2271 @pfSense_ipfw_pipe("pipe delete " . $pipeno+1);
2272 @pfSense_ipfw_pipe("pipe delete " . $pipeno);
2273 }
2274 unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
2275 }
2276 }
2277 }
2278
2279 /* read in client database */
2280 $query = "WHERE ip = '{$clientip}'";
2281 $tmpusername = SQLite3::escapeString(strtolower($username));
2282 if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) {
2283 $query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
2284 }
2285 $cpdb = captiveportal_read_db($query);
2286
2287 /* Snapshot the timestamp */
2288 $allow_time = time();
2289 $unsetindexes = array();
2290
2291 foreach ($cpdb as $cpentry) {
2292 /* on the same ip */
2293 if ($cpentry[2] == $clientip) {
2294 if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac) {
2295 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - REUSING OLD SESSION");
2296 } else {
2297 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
2298 }
2299 $sessionid = $cpentry[5];
2300 break;
2301 } elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
2302 // user logged in with an active voucher. Check for how long and calculate
2303 // how much time we can give him (voucher credit - used time)
2304 $remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
2305 if ($remaining_time < 0) { // just in case.
2306 $remaining_time = 0;
2307 }
2308
2309 /* This user was already logged in so we disconnect the old one, or
2310 keep the old one, refusing the new login, or
2311 allow the login */
2312
2313 if ( ! isset($config['captiveportal'][$cpzone]['noconcurrentlogins']) ) {
2314 /* 'noconcurrentlogins' not set : accept login 'username' creating multiple sessions.
2315 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - NOT TERMINATING EXISTING SESSION(S)");
2316 } else if ( $config['captiveportal'][$cpzone]['noconcurrentlogins'] == 'last' ) {
2317 /* Classic situation : accept the new login, disconnect the old - present - connection */
2318 captiveportal_disconnect($cpentry, 13);
2319 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT VOUCHER LOGIN - TERMINATING OLD SESSION");
2320 $unsetindexes[] = $cpentry[5];
2321 break;
2322 } else {
2323 /* Implicit 'first' : refuse the new login - 'username' is already logged in */
2324 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT VOUCHER LOGIN - NOT ALLOWED KEEPING OLD SESSION ");
2325 portal_reply_page($redirurl, "error", "Reuse of identification not allowed.");
2326 return;
2327 }
2328 } elseif ( isset( $config['captiveportal'][$cpzone]['noconcurrentlogins']) && ($username != 'unauthenticated') ) {
2329 if ( $config['captiveportal'][$cpzone]['noconcurrentlogins'] == 'last' ) {
2330 /* on the same username */
2331 if (strcasecmp($cpentry[4], $username) == 0) {
2332 /* This user was already logged in so we disconnect the old one */
2333 captiveportal_disconnect($cpentry, 13);
2334 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT USER LOGIN - TERMINATING OLD SESSION");
2335 $unsetindexes[] = $cpentry[5];
2336 break;
2337 }
2338 } else {
2339 /* Implicit 'first' : refuse the new login - 'username' is already logged in */
2340 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT USER LOGIN - NOT ALLOWED KEEPING OLD SESSION ");
2341 portal_reply_page($redirurl, "error", "Reuse of identification not allowed.");
2342 return;
2343 }
2344 }
2345 }
2346 unset($cpdb);
2347
2348 if (!empty($unsetindexes)) {
2349 captiveportal_remove_entries($unsetindexes);
2350 }
2351
2352 if ($attributes['voucher'] && $remaining_time <= 0) {
2353 return 0; // voucher already used and no time left
2354 }
2355
2356 if (!isset($sessionid)) {
2357 /* generate unique session ID */
2358 $tod = gettimeofday();
2359 $sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
2360
2361 if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
2362 $dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
2363 $dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
2364 } else {
2365 $dwfaultbw_up = $dwfaultbw_down = 0;
2366 }
2367 /* pipe throughputs must always be an integer, enforce that restriction again here. */
2368 if (isset($config['captiveportal'][$cpzone]['radiusperuserbw'])) {
2369 $bw_up = round(!empty($attributes['bw_up']) ? intval($attributes['bw_up'])/1000 : $dwfaultbw_up, 0);
2370 $bw_down = round(!empty($attributes['bw_down']) ? intval($attributes['bw_down'])/1000 : $dwfaultbw_down, 0);
2371 } else {
2372 $bw_up = round($dwfaultbw_up,0);
2373 $bw_down = round($dwfaultbw_down,0);
2374 }
2375
2376 if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
2377
2378 $mac = array();
2379 $mac['action'] = 'pass';
2380 $mac['mac'] = $clientmac;
2381 $mac['ip'] = $clientip; /* Used only for logging */
2382 $mac['username'] = $username;
2383 if ($attributes['voucher']) {
2384 $mac['logintype'] = "voucher";
2385 }
2386 if ($username == "unauthenticated") {
2387 $mac['descr'] = "Auto-added";
2388 } else if ($authmethod == "voucher") {
2389 $mac['descr'] = "Auto-added for voucher {$username}";
2390 } else {
2391 $mac['descr'] = "Auto-added for user {$username}";
2392 }
2393 if (!empty($bw_up)) {
2394 $mac['bw_up'] = $bw_up;
2395 }
2396 if (!empty($bw_down)) {
2397 $mac['bw_down'] = $bw_down;
2398 }
2399 if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
2400 $config['captiveportal'][$cpzone]['passthrumac'] = array();
2401 }
2402 //check for mac duplicates before adding it to config.
2403 $mac_duplicate = false;
2404 foreach($config['captiveportal'][$cpzone]['passthrumac'] as $mac_check){
2405 if($mac_check['mac'] == $mac['mac']){
2406 $mac_duplicate = true;
2407 }
2408 }
2409 if(!$mac_duplicate){
2410 $config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
2411 }
2412 unlock($cpdblck);
2413 $macrules = captiveportal_passthrumac_configure_entry($mac);
2414 file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
2415 mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
2416 $writecfg = true;
2417 } else {
2418 /* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
2419 if (is_null($pipeno)) {
2420 $pipeno = captiveportal_get_next_dn_ruleno();
2421 }
2422
2423 /* if the pool is empty, return appropriate message and exit */
2424 if (is_null($pipeno)) {
2425 portal_reply_page($redirurl, "error", "System reached maximum login capacity");
2426 log_error("Zone: {$cpzone} - WARNING! Captive portal has reached maximum login capacity");
2427 unlock($cpdblck);
2428 return;
2429 }
2430
2431 $bw_up_pipeno = $pipeno;
2432 $bw_down_pipeno = $pipeno + 1;
2433 //$bw_up /= 1000; // Scale to Kbit/s
2434 $_gb = @pfSense_ipfw_pipe("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
2435 $_gb = @pfSense_ipfw_pipe("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
2436
2437 $rule_entry = "{$clientip}/" . (is_ipaddrv6($clientip) ? "128" : "32");
2438 if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
2439 $rule_entry .= ",{$clientmac}";
2440 }
2441 $_gb = @pfSense_ipfw_table("{$cpzone}_auth_up", IP_FW_TABLE_XADD, "{$rule_entry}", $bw_up_pipeno);
2442 $_gb = @pfSense_ipfw_table("{$cpzone}_auth_down", IP_FW_TABLE_XADD, "{$rule_entry}", $bw_down_pipeno);
2443
2444 if ($attributes['voucher']) {
2445 $attributes['session_timeout'] = $remaining_time;
2446 }
2447
2448 /* handle empty attributes */
2449 $session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
2450 $idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
2451 $session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
2452 $interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
2453 $traffic_quota = (!empty($attributes['maxbytes'])) ? $attributes['maxbytes'] : 'NULL';
2454
2455 /* escape username */
2456 $safe_username = SQLite3::escapeString($username);
2457
2458 /* encode password in Base64 just in case it contains commas */
2459 $bpassword = (isset($config['captiveportal'][$cpzone]['reauthenticate'])) ? base64_encode($password) : '';
2460 $insertquery = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, traffic_quota, bw_up, bw_down, authmethod, context) ";
2461 $insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
2462 $insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, {$traffic_quota}, {$bw_up}, {$bw_down}, '{$authmethod}', '{$context}')";
2463
2464 /* store information to database */
2465 captiveportal_write_db($insertquery);
2466 unlock($cpdblck);
2467 unset($insertquery, $bpassword);
2468
2469 $radacct = isset($config['captiveportal'][$cpzone]['radacct_enable']) ? true : false;
2470 if ($authmethod === 'radius' && $radacct) {
2471 captiveportal_send_server_accounting('start',
2472 $pipeno, // ruleno
2473 $username, // username
2474 $clientip, // clientip
2475 $clientmac, // clientmac
2476 $sessionid, // sessionid
2477 time()); // start time
2478 }
2479 }
2480 } else {
2481 /* NOTE: #3062-11 If the pipeno has been allocated free it to not DoS the CP */
2482 if (!is_null($pipeno)) {
2483 captiveportal_free_dn_ruleno($pipeno);
2484 }
2485
2486 unlock($cpdblck);
2487 }
2488
2489 if ($writecfg == true) {
2490 write_config(gettext("Captive Portal allowed users configuration changed"));
2491 }
2492
2493 /* redirect user to desired destination */
2494 if (!empty($attributes['url_redirection'])) {
2495 $my_redirurl = $attributes['url_redirection'];
2496 } else if (!empty($redirurl)) {
2497 $my_redirurl = $redirurl;
2498 } else if (!empty($config['captiveportal'][$cpzone]['redirurl'])) {
2499 $my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
2500 }
2501
2502 if (isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
2503 $ourhostname = portal_hostname_from_client_ip($clientip);
2504 $protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
2505 $logouturl = "{$protocol}{$ourhostname}/";
2506
2507 if (isset($attributes['reply_message'])) {
2508 $message = $attributes['reply_message'];
2509 } else {
2510 $message = 0;
2511 }
2512
2513 include_once("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
2514
2515 } else {
2516 portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
2517 }
2518
2519 return $sessionid;
2520}
2521
2522
2523/*
2524 * Used for when pass-through credits are enabled.
2525 * Returns true when there was at least one free login to deduct for the MAC.
2526 * Expired entries are removed as they are seen.
2527 * Active entries are updated according to the configuration.
2528 */
2529function portal_consume_passthrough_credit($clientmac) {
2530 global $config, $cpzone;
2531
2532 if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count'])) {
2533 $freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
2534 } else {
2535 return false;
2536 }
2537
2538 if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout'])) {
2539 $resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
2540 } else {
2541 return false;
2542 }
2543
2544 if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac) {
2545 return false;
2546 }
2547
2548 $updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2549
2550 /*
2551 * Read database of used MACs. Lines are a comma-separated list
2552 * of the time, MAC, then the count of pass-through credits remaining.
2553 */
2554 $usedmacs = captiveportal_read_usedmacs_db();
2555
2556 $currenttime = time();
2557 $found = false;
2558 foreach ($usedmacs as $key => $usedmac) {
2559 $usedmac = explode(",", $usedmac);
2560
2561 if ($usedmac[1] == $clientmac) {
2562 if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2563 if ($usedmac[2] < 1) {
2564 if ($updatetimeouts) {
2565 $usedmac[0] = $currenttime;
2566 unset($usedmacs[$key]);
2567 $usedmacs[] = implode(",", $usedmac);
2568 captiveportal_write_usedmacs_db($usedmacs);
2569 }
2570
2571 return false;
2572 } else {
2573 $usedmac[2] -= 1;
2574 $usedmacs[$key] = implode(",", $usedmac);
2575 }
2576
2577 $found = true;
2578 } else {
2579 unset($usedmacs[$key]);
2580 }
2581
2582 break;
2583 } else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime) {
2584 unset($usedmacs[$key]);
2585 }
2586 }
2587
2588 if (!$found) {
2589 $usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2590 $usedmacs[] = implode(",", $usedmac);
2591 }
2592
2593 captiveportal_write_usedmacs_db($usedmacs);
2594 return true;
2595}
2596
2597function captiveportal_read_usedmacs_db() {
2598 global $g, $cpzone;
2599
2600 $cpumaclck = lock("captiveusedmacs{$cpzone}");
2601 if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2602 $usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2603 if (!$usedmacs) {
2604 $usedmacs = array();
2605 }
2606 } else {
2607 $usedmacs = array();
2608 }
2609
2610 unlock($cpumaclck);
2611 return $usedmacs;
2612}
2613
2614function captiveportal_write_usedmacs_db($usedmacs) {
2615 global $g, $cpzone;
2616
2617 $cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2618 @file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2619 unlock($cpumaclck);
2620}
2621
2622function captiveportal_blocked_mac($mac) {
2623 global $config, $g, $cpzone;
2624
2625 if (empty($mac) || !is_macaddr($mac)) {
2626 return false;
2627 }
2628
2629 if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
2630 return false;
2631 }
2632
2633 foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac) {
2634 if (($passthrumac['action'] == 'block') &&
2635 ($passthrumac['mac'] == strtolower($mac))) {
2636 return true;
2637 }
2638 }
2639
2640 return false;
2641
2642}
2643
2644/* Captiveportal Radius Accounting */
2645
2646function gigawords($bytes) {
2647
2648 /*
2649 * RFC2866 Specifies a 32bit unsigned integer, which is a max of 4294967295
2650 * Currently there is a fault in the PECL radius_put_int function which can handle only 32bit signed integer.
2651 */
2652
2653 // We use BCMath functions since normal integers don't work with so large numbers
2654 $gigawords = bcdiv( bcsub( $bytes, remainder($bytes) ) , GIGAWORDS_RIGHT_OPERAND) ;
2655
2656 // We need to manually set this to a zero instead of NULL for put_int() safety
2657 if (is_null($gigawords)) {
2658 $gigawords = 0;
2659 }
2660
2661 return $gigawords;
2662}
2663
2664function remainder($bytes) {
2665 // Calculate the bytes we are going to send to the radius
2666 $bytes = bcmod($bytes, GIGAWORDS_RIGHT_OPERAND);
2667
2668 if (is_null($bytes)) {
2669 $bytes = 0;
2670 }
2671
2672 return $bytes;
2673}
2674
2675function captiveportal_send_server_accounting($type = 'on', $ruleno = null, $username = null, $clientip = null, $clientmac = null, $sessionid = null, $start_time = null, $stop_time = null, $term_cause = null) {
2676 global $cpzone, $config;
2677
2678 $cpcfg = $config['captiveportal'][$cpzone];
2679 $acctcfg = auth_get_authserver($cpcfg['radacct_server']);
2680
2681 if (!isset($cpcfg['radacct_enable']) || empty($acctcfg)) {
2682 return null;
2683 }
2684
2685 if ($type === 'on') {
2686 $racct = new Auth_RADIUS_Acct_On;
2687 } elseif ($type === 'off') {
2688 $racct = new Auth_RADIUS_Acct_Off;
2689 } elseif ($type === 'start') {
2690 $racct = new Auth_RADIUS_Acct_Start;
2691 if (!is_int($start_time)) {
2692 $start_time = time();
2693 }
2694 } elseif ($type === 'stop') {
2695 $racct = new Auth_RADIUS_Acct_Stop;
2696 if (!is_int($stop_time)) {
2697 $stop_time = time();
2698 }
2699 } elseif ($type === 'update') {
2700 $racct = new Auth_RADIUS_Acct_Update;
2701 if (!is_int($stop_time)) {
2702 $stop_time = time(); // "top time" here will be used only for calculating session time.
2703 }
2704 } else {
2705 return null;
2706 }
2707
2708 $racct->addServer($acctcfg['host'], $acctcfg['radius_acct_port'],
2709 $acctcfg['radius_secret'], $acctcfg['radius_timeout']);
2710
2711 $racct->authentic = RADIUS_AUTH_RADIUS;
2712 if ($cpcfg['auth_method'] === 'radmac' && $username === "unauthenticated" && !empty($clientmac)) {
2713 $racct->username = mac_format($clientmac);
2714 } elseif (!empty($username)) {
2715 $racct->username = $username;
2716 }
2717
2718 if (PEAR::isError($racct->start())) {
2719 captiveportal_syslog('RADIUS ACCOUNTING FAILED : '.$racct->getError());
2720 $racct->close();
2721 return null;
2722 }
2723
2724 $nasip = $acctcfg['radius_nasip_attribute'];
2725 if (!is_ipaddr($nasip)) {
2726 $nasip = get_interface_ip($nasip);
2727 if (!is_ipaddr($nasip)) {
2728 $nasip = get_interface_ip();//We use WAN interface IP as fallback for NAS-IP-Address
2729 }
2730 }
2731 $nasmac = get_interface_mac(find_ip_interface($nasip));
2732 $racct->putAttribute(RADIUS_NAS_IP_ADDRESS, $nasip, "addr");
2733
2734 $racct->putAttribute(RADIUS_NAS_IDENTIFIER, empty($cpcfg["radiusnasid"]) ? "CaptivePortal-{$cpzone}" : $cpcfg["radiusnasid"] );
2735
2736 if (is_int($ruleno)) {
2737 $racct->putAttribute(RADIUS_NAS_PORT_TYPE, RADIUS_ETHERNET);
2738 $racct->putAttribute(RADIUS_NAS_PORT, intval($ruleno), 'integer');
2739 }
2740
2741 if (!empty($sessionid)) {
2742 $racct->putAttribute(RADIUS_ACCT_SESSION_ID, $sessionid);
2743 }
2744
2745 if (!empty($clientip) && is_ipaddr($clientip)) {
2746 $racct->putAttribute(RADIUS_FRAMED_IP_ADDRESS, $clientip, "addr");
2747 }
2748 if (!empty($clientmac)) {
2749 $racct->putAttribute(RADIUS_CALLING_STATION_ID, mac_format($clientmac));
2750 }
2751 if (!empty($nasmac)) {
2752 $racct->putAttribute(RADIUS_CALLED_STATION_ID, mac_format($nasmac).':'.gethostname());
2753 }
2754
2755 // Accounting request Stop and Update : send the current data volume
2756 if (($type === 'stop' || $type === 'update') && is_int($start_time)) {
2757 $volume = getVolume($clientip);
2758 $session_time = $stop_time - $start_time;
2759 $volume['input_bytes_radius'] = remainder($volume['input_bytes']);
2760 $volume['input_gigawords'] = gigawords($volume['input_bytes']);
2761 $volume['output_bytes_radius'] = remainder($volume['output_bytes']);
2762 $volume['output_gigawords'] = gigawords($volume['output_bytes']);
2763
2764 // Volume stuff: Ingress
2765 $racct->putAttribute(RADIUS_ACCT_INPUT_PACKETS, intval($volume['input_pkts']), "integer");
2766 $racct->putAttribute(RADIUS_ACCT_INPUT_OCTETS, intval($volume['input_bytes_radius']), "integer");
2767 // Volume stuff: Outgress
2768 $racct->putAttribute(RADIUS_ACCT_OUTPUT_PACKETS, intval($volume['output_pkts']), "integer");
2769 $racct->putAttribute(RADIUS_ACCT_OUTPUT_OCTETS, intval($volume['output_bytes_radius']), "integer");
2770 $racct->putAttribute(RADIUS_ACCT_SESSION_TIME, intval($session_time), "integer");
2771
2772 $racct->putAttribute(CUSTOM_RADIUS_ACCT_OUTPUT_GIGAWORDS, intval($volume['output_gigawords']), "integer");
2773 $racct->putAttribute(CUSTOM_RADIUS_ACCT_INPUT_GIGAWORDS, intval($volume['input_gigawords']), "integer");
2774 // Set session_time
2775 $racct->session_time = $session_time;
2776 }
2777
2778 if ($type === 'stop') {
2779 if (empty($term_cause)) {
2780 $term_cause = 1;
2781 }
2782 $racct->putAttribute(RADIUS_ACCT_TERMINATE_CAUSE, $term_cause);
2783 }
2784
2785 // Send request
2786 $result = $racct->send();
2787
2788 if (PEAR::isError($result)) {
2789 captiveportal_syslog('RADIUS ACCOUNTING FAILED : '.$racct->getError());
2790 $result = null;
2791 } elseif ($result !== true) {
2792 $result = false;
2793 }
2794
2795 $racct->close();
2796 return $result;
2797}
2798
2799function captiveportal_isip_logged($clientip) {
2800 global $g, $cpzone;
2801
2802 /* read in client database */
2803 $query = "WHERE ip = '{$clientip}'";
2804 $cpdb = captiveportal_read_db($query);
2805 foreach ($cpdb as $cpentry) {
2806 return $cpentry;
2807 }
2808}
2809?>