Nowwhat47

/etc/inc/captiveportal.inc V4 2019-05-21

Apr 18th, 2019
668
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 100.10 KB | None | 0 0
  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 */
  27. require_once("auth.inc");
  28. require_once("PEAR.php"); // required for bcmath
  29. require_once("Auth/RADIUS.php"); // required for radius accounting
  30. require_once("config.inc");
  31. require_once("functions.inc");
  32. require_once("filter.inc");
  33. require_once("voucher.inc");
  34.  
  35. /* Captiveportal Radius Accounting */
  36. PEAR::loadExtension('bcmath');
  37. // The RADIUS Package doesn't have these vars so we create them ourself
  38. define("CUSTOM_RADIUS_ACCT_INPUT_GIGAWORDS", "52");
  39. define("CUSTOM_RADIUS_ACCT_OUTPUT_GIGAWORDS", "53");
  40. define("GIGAWORDS_RIGHT_OPERAND", '4294967296'); // 2^32
  41.  
  42. function 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\$">
  107. EOD;
  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.  
  116. EOD;
  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">
  121. EOD;
  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>
  134. EOD;
  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}">
  143. EOD;
  144.         }
  145.     }
  146.  
  147. if ($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>
  159. EOD;
  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 &hearts; 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.  
  176. EOD;
  177.  
  178.     return $htmltext;
  179. }
  180.  
  181. function 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.  
  213. function 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.  
  225. function 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[
  322. LogoutWin = window.open('', 'Logout', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=256,height=64');
  323. if (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.  
  339. document.location.href="<?=\$my_redirurl;?>";
  340. //]]>
  341. </script>
  342. </body>
  343. </html>
  344.  
  345. EOD;
  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.  
  421. function 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.  
  432. function 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.  
  441. function 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.  
  486. function 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 */
  505. function 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
  521. table cp_ifaces create type iface valtype skipto
  522.  
  523. # Redirect each CP interface to its specific rule
  524. add 1000 skipto tablearg all from any to any via table(cp_ifaces)
  525.  
  526. # This interface has no cp zone configured
  527. add 1100 allow all from any to any
  528.  
  529. # block everything else
  530. add 65534 deny all from any to any
  531. EOD;
  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 */
  541. function 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 */
  549. function 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! */
  556. function 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 */
  744. function 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.  */
  791. function 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.  
  1040. function 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 */
  1101. function 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 */
  1187. function 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 */
  1207. function 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 */
  1233. function 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.  
  1262. function 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.  
  1304. function 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.  
  1323. function 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.  
  1362. function 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.  */
  1378. function 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.  
  1439. function 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.  
  1461. function 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.  
  1487. function 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 */
  1501. function 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 */
  1525. function 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 */
  1537. function 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 */
  1549. function 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.  
  1704. function 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 */
  1780. function 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 */
  1803. function 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.  
  1820. function 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 */
  1838. function 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.  
  1863. function 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.  
  1894. function 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.  
  1939. function 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.  
  1972. function 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.  
  1987. function 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.  
  2029. function 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.  
  2073. function 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.  
  2115. function 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.  
  2146. function 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.  
  2193. function 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.  
  2225. function 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.  
  2234. function 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. captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "Enteringh portal_allow()");
  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.     if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) {
  2282.         $tmpusername = SQLite3::escapeString(strtolower($username));
  2283.         $query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
  2284.     }
  2285.     $cpdb = captiveportal_read_db($query);
  2286.    
  2287. captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2],"The SQL array ({$query}) : " . print_r($cpdb, TRUE));   
  2288.    
  2289.     /* Snapshot the timestamp */
  2290.     $allow_time = time();
  2291.     $unsetindexes = array();
  2292.  
  2293.     foreach ($cpdb as $cpentry)
  2294.     {
  2295.         captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "Entering for each loop $username = {$username}");      
  2296.         if ( isset($config['captiveportal'][$cpzone]['noconcurrentlogins'] ) )
  2297.             captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "config['captiveportal'][$cpzone]['noconcurrentlogins'] exists = set");     
  2298.         else
  2299.             captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "config['captiveportal'][$cpzone]['noconcurrentlogins'] does not exists = NOT set");    
  2300.  
  2301.        
  2302.         if ($cpentry[2] == $clientip) {
  2303.             if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac) {
  2304.                 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - REUSING OLD SESSION");
  2305.             } else {
  2306.                 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
  2307.             }
  2308.             $sessionid = $cpentry[5];
  2309.             break;
  2310.         } elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
  2311. captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "Voucher + ! unauthenticated + (cpentry == user)");
  2312.             // user logged in with an active voucher. Check for how long and calculate
  2313.             // how much time we can give him (voucher credit - used time)
  2314.             $remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
  2315.             if ($remaining_time < 0) { // just in case.
  2316.                 $remaining_time = 0;
  2317.             }
  2318.  
  2319.             /* This user was already logged in so we disconnect the old one, or
  2320.             keep the old one, refusing the new login, or
  2321.             allow the login */
  2322.  
  2323.             if ( !isset($config['captiveportal'][$cpzone]['noconcurrentlogins']))
  2324.                 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "config['captiveportal'][$cpzone]['noconcurrentlogins'] 2 does not exists = NOT set");      
  2325.             else
  2326.                 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "config['captiveportal'][$cpzone]['noconcurrentlogins'] 2 exists = set");       
  2327.  
  2328.             if ( $config['captiveportal'][$cpzone]['noconcurrentlogins'] == "last" )
  2329.                 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "Found last");
  2330.             else
  2331.                 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "Found NOT last");
  2332.                
  2333.                
  2334.             if ( !isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) {
  2335.                 /* 'noconcurrentlogins' not set : accept login 'username' creating multiple sessions. */
  2336.                 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "config['captiveportal'][$cpzone]['noconcurrentlogins'] 3 does not exists = NOT set");
  2337.                 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - NOT TERMINATING EXISTING SESSION(S)");          
  2338.             } elseif ($config['captiveportal'][$cpzone]['noconcurrentlogins'] == "last") {
  2339.                 /* Classic situation : accept the new login, disconnect the old - present - connection */
  2340.                
  2341.                 if ( isset($config['captiveportal'][$cpzone]['noconcurrentlogins']))
  2342.                     captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "config['captiveportal'][$cpzone]['noconcurrentlogins'] 4 exists = set");       
  2343.                 else
  2344.                     captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "config['captiveportal'][$cpzone]['noconcurrentlogins'] 4 does not exists = NOT set");      
  2345.                
  2346.                 captiveportal_disconnect($cpentry, 13);
  2347.                 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "XXxxxxXX CONCURRENT VOUCHER LOGIN - TERMINATING OLD SESSION {$config['captiveportal'][$cpzone]['noconcurrentlogins']}");
  2348.                 $unsetindexes[] = $cpentry[5];
  2349.                 break;
  2350.             } else {
  2351.                 /* Implicit 'first' : refuse the new login - 'username' is already logged in */
  2352.                 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT VOUCHER LOGIN - NOT ALLOWED KEEPING OLD SESSION ");
  2353.                 portal_reply_page($redirurl, "error", "Reuse of identification not allowed.");
  2354.                 unlock($cpdblck);
  2355.                 return 0;
  2356.             }
  2357.         } elseif ( isset($config['captiveportal'][$cpzone]['noconcurrentlogins']) && ($username != 'unauthenticated') ) {
  2358.             if ( $config['captiveportal'][$cpzone]['noconcurrentlogins'] == "last" ) {
  2359.                 /* on the same username */
  2360.                 if (strcasecmp($cpentry[4], $username) == 0) {
  2361.                     /* This user was already logged in so we disconnect the old one */
  2362.                     captiveportal_disconnect($cpentry, 13);
  2363.                     captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT USER LOGIN - TERMINATING OLD SESSION");
  2364.                     $unsetindexes[] = $cpentry[5];
  2365.                     break;
  2366.                 }
  2367.             } else {
  2368.                 /* Implicit 'first' : refuse the new login - 'username' is already logged in */
  2369.                 captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT USER LOGIN - NOT ALLOWED KEEPING OLD SESSION ");
  2370.                 portal_reply_page($redirurl, "error", "Reuse of identification not allowed.");
  2371.                 unlock($cpdblck);
  2372.                 return 0;              
  2373.             }
  2374.         }
  2375.     }
  2376.     unset($cpdb);
  2377.  
  2378.     if (!empty($unsetindexes)) {
  2379.         captiveportal_remove_entries($unsetindexes);
  2380.     }
  2381.  
  2382.     if ($attributes['voucher'] && $remaining_time <= 0) {
  2383.         unlock($cpdblck);
  2384.         return 0;       // voucher already used and no time left
  2385.     }
  2386.  
  2387.     if (!isset($sessionid)) {
  2388.         /* generate unique session ID */
  2389.         $tod = gettimeofday();
  2390.         $sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
  2391.  
  2392.         if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
  2393.             $dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
  2394.             $dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
  2395.         } else {
  2396.             $dwfaultbw_up = $dwfaultbw_down = 0;
  2397.         }
  2398.         /* pipe throughputs must always be an integer, enforce that restriction again here. */
  2399.         if (isset($config['captiveportal'][$cpzone]['radiusperuserbw'])) {
  2400.             $bw_up = round(!empty($attributes['bw_up']) ? intval($attributes['bw_up'])/1000 : $dwfaultbw_up, 0);
  2401.             $bw_down = round(!empty($attributes['bw_down']) ? intval($attributes['bw_down'])/1000 : $dwfaultbw_down, 0);
  2402.         } else {
  2403.             $bw_up = round($dwfaultbw_up,0);
  2404.             $bw_down = round($dwfaultbw_down,0);
  2405.         }
  2406.  
  2407.         if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
  2408.  
  2409.             $mac = array();
  2410.             $mac['action'] = 'pass';
  2411.             $mac['mac'] = $clientmac;
  2412.             $mac['ip'] = $clientip; /* Used only for logging */
  2413.             $mac['username'] = $username;
  2414.             if ($attributes['voucher']) {
  2415.                 $mac['logintype'] = "voucher";
  2416.             }
  2417.             if ($username == "unauthenticated") {
  2418.                 $mac['descr'] = "Auto-added";
  2419.             } else if ($authmethod == "voucher") {
  2420.                 $mac['descr'] = "Auto-added for voucher {$username}";
  2421.             } else {
  2422.                 $mac['descr'] = "Auto-added for user {$username}";
  2423.             }
  2424.             if (!empty($bw_up)) {
  2425.                 $mac['bw_up'] = $bw_up;
  2426.             }
  2427.             if (!empty($bw_down)) {
  2428.                 $mac['bw_down'] = $bw_down;
  2429.             }
  2430.             if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
  2431.                 $config['captiveportal'][$cpzone]['passthrumac'] = array();
  2432.             }
  2433.             //check for mac duplicates before adding it to config.
  2434.             $mac_duplicate = false;
  2435.             foreach($config['captiveportal'][$cpzone]['passthrumac'] as $mac_check){
  2436.                 if($mac_check['mac'] == $mac['mac']){
  2437.                     $mac_duplicate = true;
  2438.                 }
  2439.             }
  2440.             if(!$mac_duplicate){
  2441.                 $config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
  2442.             }
  2443.             unlock($cpdblck);
  2444.             $macrules = captiveportal_passthrumac_configure_entry($mac);
  2445.             file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
  2446.             mwexec("/sbin/ipfw -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
  2447.             $writecfg = true;
  2448.         } else {
  2449.             /* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
  2450.             if (is_null($pipeno)) {
  2451.                 $pipeno = captiveportal_get_next_dn_ruleno();
  2452.             }
  2453.  
  2454.             /* if the pool is empty, return appropriate message and exit */
  2455.             if (is_null($pipeno)) {
  2456.                 portal_reply_page($redirurl, "error", "System reached maximum login capacity");
  2457.                 log_error("Zone: {$cpzone} - WARNING!  Captive portal has reached maximum login capacity");
  2458.                 unlock($cpdblck);
  2459.                 return;
  2460.             }
  2461.  
  2462.             $bw_up_pipeno = $pipeno;
  2463.             $bw_down_pipeno = $pipeno + 1;
  2464.             //$bw_up /= 1000; // Scale to Kbit/s
  2465.             $_gb = @pfSense_ipfw_pipe("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
  2466.             $_gb = @pfSense_ipfw_pipe("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
  2467.  
  2468.             $rule_entry = "{$clientip}/" . (is_ipaddrv6($clientip) ? "128" : "32");
  2469.             if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
  2470.                 $rule_entry .= ",{$clientmac}";
  2471.             }
  2472.             $_gb = @pfSense_ipfw_table("{$cpzone}_auth_up", IP_FW_TABLE_XADD, "{$rule_entry}", $bw_up_pipeno);
  2473.             $_gb = @pfSense_ipfw_table("{$cpzone}_auth_down", IP_FW_TABLE_XADD, "{$rule_entry}", $bw_down_pipeno);
  2474.  
  2475.             if ($attributes['voucher']) {
  2476.                 $attributes['session_timeout'] = $remaining_time;
  2477.             }
  2478.  
  2479.             /* handle empty attributes */
  2480.             $session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
  2481.             $idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
  2482.             $session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
  2483.             $interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
  2484.             $traffic_quota = (!empty($attributes['maxbytes'])) ? $attributes['maxbytes'] : 'NULL';
  2485.  
  2486.             /* escape username */
  2487.             $safe_username = SQLite3::escapeString($username);
  2488.  
  2489.             /* encode password in Base64 just in case it contains commas */
  2490.             $bpassword = (isset($config['captiveportal'][$cpzone]['reauthenticate'])) ? base64_encode($password) : '';
  2491.             $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) ";
  2492.             $insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
  2493.             $insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, {$traffic_quota}, {$bw_up}, {$bw_down}, '{$authmethod}', '{$context}')";
  2494.  
  2495.             /* store information to database */
  2496.             captiveportal_write_db($insertquery);
  2497.             unlock($cpdblck);
  2498.             unset($insertquery, $bpassword);
  2499.  
  2500.             $radacct = isset($config['captiveportal'][$cpzone]['radacct_enable']) ? true : false;
  2501.             if ($authmethod === 'radius' && $radacct) {
  2502.                 captiveportal_send_server_accounting('start',
  2503.                     $pipeno, // ruleno
  2504.                     $username, // username
  2505.                     $clientip, // clientip
  2506.                     $clientmac, // clientmac
  2507.                     $sessionid, // sessionid
  2508.                     time());  // start time
  2509.             }
  2510.         }
  2511.     } else {
  2512.         /* NOTE: #3062-11 If the pipeno has been allocated free it to not DoS the CP */
  2513.         if (!is_null($pipeno)) {
  2514.             captiveportal_free_dn_ruleno($pipeno);
  2515.         }
  2516.  
  2517.         unlock($cpdblck);
  2518.     }
  2519.  
  2520.     if ($writecfg == true) {
  2521.         write_config(gettext("Captive Portal allowed users configuration changed"));
  2522.     }
  2523.  
  2524.     /* redirect user to desired destination */
  2525.     if (!empty($attributes['url_redirection'])) {
  2526.         $my_redirurl = $attributes['url_redirection'];
  2527.     } else if (!empty($redirurl)) {
  2528.         $my_redirurl = $redirurl;
  2529.     } else if (!empty($config['captiveportal'][$cpzone]['redirurl'])) {
  2530.         $my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
  2531.     }
  2532.  
  2533.     if (isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
  2534.         $ourhostname = portal_hostname_from_client_ip($clientip);
  2535.         $protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
  2536.         $logouturl = "{$protocol}{$ourhostname}/";
  2537.  
  2538.         if (isset($attributes['reply_message'])) {
  2539.             $message = $attributes['reply_message'];
  2540.         } else {
  2541.             $message = 0;
  2542.         }
  2543.  
  2544.         include_once("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
  2545.  
  2546.     } else {
  2547.         portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
  2548.     }
  2549.  
  2550.     return $sessionid;
  2551. }
  2552.  
  2553.  
  2554. /*
  2555.  * Used for when pass-through credits are enabled.
  2556.  * Returns true when there was at least one free login to deduct for the MAC.
  2557.  * Expired entries are removed as they are seen.
  2558.  * Active entries are updated according to the configuration.
  2559.  */
  2560. function portal_consume_passthrough_credit($clientmac) {
  2561.     global $config, $cpzone;
  2562.  
  2563.     if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count'])) {
  2564.         $freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
  2565.     } else {
  2566.         return false;
  2567.     }
  2568.  
  2569.     if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout'])) {
  2570.         $resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
  2571.     } else {
  2572.         return false;
  2573.     }
  2574.  
  2575.     if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac) {
  2576.         return false;
  2577.     }
  2578.  
  2579.     $updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
  2580.  
  2581.     /*
  2582.      * Read database of used MACs.  Lines are a comma-separated list
  2583.      * of the time, MAC, then the count of pass-through credits remaining.
  2584.      */
  2585.     $usedmacs = captiveportal_read_usedmacs_db();
  2586.  
  2587.     $currenttime = time();
  2588.     $found = false;
  2589.     foreach ($usedmacs as $key => $usedmac) {
  2590.         $usedmac = explode(",", $usedmac);
  2591.  
  2592.         if ($usedmac[1] == $clientmac) {
  2593.             if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
  2594.                 if ($usedmac[2] < 1) {
  2595.                     if ($updatetimeouts) {
  2596.                         $usedmac[0] = $currenttime;
  2597.                         unset($usedmacs[$key]);
  2598.                         $usedmacs[] = implode(",", $usedmac);
  2599.                         captiveportal_write_usedmacs_db($usedmacs);
  2600.                     }
  2601.  
  2602.                     return false;
  2603.                 } else {
  2604.                     $usedmac[2] -= 1;
  2605.                     $usedmacs[$key] = implode(",", $usedmac);
  2606.                 }
  2607.  
  2608.                 $found = true;
  2609.             } else {
  2610.                 unset($usedmacs[$key]);
  2611.             }
  2612.  
  2613.             break;
  2614.         } else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime) {
  2615.             unset($usedmacs[$key]);
  2616.         }
  2617.     }
  2618.  
  2619.     if (!$found) {
  2620.         $usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
  2621.         $usedmacs[] = implode(",", $usedmac);
  2622.     }
  2623.  
  2624.     captiveportal_write_usedmacs_db($usedmacs);
  2625.     return true;
  2626. }
  2627.  
  2628. function captiveportal_read_usedmacs_db() {
  2629.     global $g, $cpzone;
  2630.  
  2631.     $cpumaclck = lock("captiveusedmacs{$cpzone}");
  2632.     if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
  2633.         $usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
  2634.         if (!$usedmacs) {
  2635.             $usedmacs = array();
  2636.         }
  2637.     } else {
  2638.         $usedmacs = array();
  2639.     }
  2640.  
  2641.     unlock($cpumaclck);
  2642.     return $usedmacs;
  2643. }
  2644.  
  2645. function captiveportal_write_usedmacs_db($usedmacs) {
  2646.     global $g, $cpzone;
  2647.  
  2648.     $cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
  2649.     @file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
  2650.     unlock($cpumaclck);
  2651. }
  2652.  
  2653. function captiveportal_blocked_mac($mac) {
  2654.     global $config, $g, $cpzone;
  2655.  
  2656.     if (empty($mac) || !is_macaddr($mac)) {
  2657.         return false;
  2658.     }
  2659.  
  2660.     if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
  2661.         return false;
  2662.     }
  2663.  
  2664.     foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac) {
  2665.         if (($passthrumac['action'] == 'block') &&
  2666.             ($passthrumac['mac'] == strtolower($mac))) {
  2667.             return true;
  2668.         }
  2669.     }
  2670.  
  2671.     return false;
  2672.  
  2673. }
  2674.  
  2675. /* Captiveportal Radius Accounting */
  2676.  
  2677. function gigawords($bytes) {
  2678.  
  2679.     /*
  2680.      * RFC2866 Specifies a 32bit unsigned integer, which is a max of 4294967295
  2681.      * Currently there is a fault in the PECL radius_put_int function which can handle only 32bit signed integer.
  2682.      */
  2683.  
  2684.     // We use BCMath functions since normal integers don't work with so large numbers
  2685.     $gigawords = bcdiv( bcsub( $bytes, remainder($bytes) ) , GIGAWORDS_RIGHT_OPERAND) ;
  2686.  
  2687.     // We need to manually set this to a zero instead of NULL for put_int() safety
  2688.     if (is_null($gigawords)) {
  2689.         $gigawords = 0;
  2690.     }
  2691.  
  2692.     return $gigawords;
  2693. }
  2694.  
  2695. function remainder($bytes) {
  2696.     // Calculate the bytes we are going to send to the radius
  2697.     $bytes = bcmod($bytes, GIGAWORDS_RIGHT_OPERAND);
  2698.  
  2699.     if (is_null($bytes)) {
  2700.         $bytes = 0;
  2701.     }
  2702.  
  2703.     return $bytes;
  2704. }
  2705.  
  2706. function 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) {
  2707.     global $cpzone, $config;
  2708.  
  2709.     $cpcfg = $config['captiveportal'][$cpzone];
  2710.     $acctcfg = auth_get_authserver($cpcfg['radacct_server']);
  2711.  
  2712.     if (!isset($cpcfg['radacct_enable']) || empty($acctcfg)) {
  2713.         return null;
  2714.     }
  2715.  
  2716.     if ($type === 'on') {
  2717.         $racct = new Auth_RADIUS_Acct_On;
  2718.     } elseif ($type === 'off') {
  2719.         $racct = new Auth_RADIUS_Acct_Off;
  2720.     } elseif ($type === 'start') {
  2721.         $racct = new Auth_RADIUS_Acct_Start;
  2722.         if (!is_int($start_time)) {
  2723.             $start_time = time();
  2724.         }
  2725.     } elseif ($type === 'stop') {
  2726.         $racct = new Auth_RADIUS_Acct_Stop;
  2727.         if (!is_int($stop_time)) {
  2728.             $stop_time = time();
  2729.         }
  2730.     } elseif ($type === 'update') {
  2731.         $racct = new Auth_RADIUS_Acct_Update;
  2732.         if (!is_int($stop_time)) {
  2733.             $stop_time = time(); // "top time" here will be used only for calculating session time.
  2734.         }
  2735.     } else {
  2736.         return null;
  2737.     }
  2738.  
  2739.     $racct->addServer($acctcfg['host'], $acctcfg['radius_acct_port'],
  2740.         $acctcfg['radius_secret'], $acctcfg['radius_timeout']);
  2741.  
  2742.     $racct->authentic = RADIUS_AUTH_RADIUS;
  2743.     if ($cpcfg['auth_method'] === 'radmac' && $username === "unauthenticated" && !empty($clientmac)) {
  2744.         $racct->username = mac_format($clientmac);
  2745.     } elseif (!empty($username)) {
  2746.         $racct->username = $username;
  2747.     }
  2748.  
  2749.     if (PEAR::isError($racct->start())) {
  2750.         captiveportal_syslog('RADIUS ACCOUNTING FAILED : '.$racct->getError());
  2751.         $racct->close();
  2752.         return null;
  2753.     }
  2754.  
  2755.     $nasip = $acctcfg['radius_nasip_attribute'];
  2756.     if (!is_ipaddr($nasip)) {
  2757.         $nasip = get_interface_ip($nasip);
  2758.         if (!is_ipaddr($nasip)) {
  2759.             $nasip = get_interface_ip();//We use WAN interface IP as fallback for NAS-IP-Address
  2760.         }
  2761.     }
  2762.     $nasmac = get_interface_mac(find_ip_interface($nasip));
  2763.     $racct->putAttribute(RADIUS_NAS_IP_ADDRESS, $nasip, "addr");
  2764.  
  2765.     $racct->putAttribute(RADIUS_NAS_IDENTIFIER, empty($cpcfg["radiusnasid"]) ? "CaptivePortal-{$cpzone}" : $cpcfg["radiusnasid"] );
  2766.  
  2767.     if (is_int($ruleno)) {
  2768.         $racct->putAttribute(RADIUS_NAS_PORT_TYPE, RADIUS_ETHERNET);
  2769.         $racct->putAttribute(RADIUS_NAS_PORT, intval($ruleno), 'integer');
  2770.     }
  2771.  
  2772.     if (!empty($sessionid)) {
  2773.         $racct->putAttribute(RADIUS_ACCT_SESSION_ID, $sessionid);
  2774.     }
  2775.  
  2776.     if (!empty($clientip) && is_ipaddr($clientip)) {
  2777.         $racct->putAttribute(RADIUS_FRAMED_IP_ADDRESS, $clientip, "addr");
  2778.     }
  2779.     if (!empty($clientmac)) {
  2780.         $racct->putAttribute(RADIUS_CALLING_STATION_ID, mac_format($clientmac));
  2781.     }
  2782.     if (!empty($nasmac)) {
  2783.         $racct->putAttribute(RADIUS_CALLED_STATION_ID, mac_format($nasmac).':'.gethostname());
  2784.     }
  2785.  
  2786.     // Accounting request Stop and Update : send the current data volume
  2787.     if (($type === 'stop' || $type === 'update') && is_int($start_time)) {
  2788.         $volume = getVolume($clientip);
  2789.         $session_time = $stop_time - $start_time;
  2790.         $volume['input_bytes_radius'] = remainder($volume['input_bytes']);
  2791.         $volume['input_gigawords'] = gigawords($volume['input_bytes']);
  2792.         $volume['output_bytes_radius'] = remainder($volume['output_bytes']);
  2793.         $volume['output_gigawords'] = gigawords($volume['output_bytes']);
  2794.  
  2795.         // Volume stuff: Ingress
  2796.         $racct->putAttribute(RADIUS_ACCT_INPUT_PACKETS, intval($volume['input_pkts']), "integer");
  2797.         $racct->putAttribute(RADIUS_ACCT_INPUT_OCTETS, intval($volume['input_bytes_radius']), "integer");
  2798.         // Volume stuff: Outgress
  2799.         $racct->putAttribute(RADIUS_ACCT_OUTPUT_PACKETS, intval($volume['output_pkts']), "integer");
  2800.         $racct->putAttribute(RADIUS_ACCT_OUTPUT_OCTETS, intval($volume['output_bytes_radius']), "integer");
  2801.         $racct->putAttribute(RADIUS_ACCT_SESSION_TIME, intval($session_time), "integer");
  2802.  
  2803.         $racct->putAttribute(CUSTOM_RADIUS_ACCT_OUTPUT_GIGAWORDS, intval($volume['output_gigawords']), "integer");
  2804.         $racct->putAttribute(CUSTOM_RADIUS_ACCT_INPUT_GIGAWORDS, intval($volume['input_gigawords']), "integer");
  2805.         // Set session_time
  2806.         $racct->session_time = $session_time;
  2807.     }
  2808.  
  2809.     if ($type === 'stop') {
  2810.         if (empty($term_cause)) {
  2811.             $term_cause = 1;
  2812.         }
  2813.         $racct->putAttribute(RADIUS_ACCT_TERMINATE_CAUSE, $term_cause);
  2814.     }
  2815.  
  2816.     // Send request
  2817.     $result = $racct->send();
  2818.  
  2819.     if (PEAR::isError($result)) {
  2820.          captiveportal_syslog('RADIUS ACCOUNTING FAILED : '.$racct->getError());
  2821.          $result = null;
  2822.     } elseif ($result !== true) {
  2823.         $result = false;
  2824.     }
  2825.  
  2826.     $racct->close();
  2827.     return $result;
  2828. }
  2829.  
  2830. function captiveportal_isip_logged($clientip) {
  2831.     global $g, $cpzone;
  2832.  
  2833.     /* read in client database */
  2834.     $query = "WHERE ip = '{$clientip}'";
  2835.     $cpdb = captiveportal_read_db($query);
  2836.     foreach ($cpdb as $cpentry) {
  2837.         return $cpentry;
  2838.     }
  2839. }
  2840. ?>
Add Comment
Please, Sign In to add comment