Browse Source

[Web] Allow CIDR as allowed API networks; other minor fixes

andryyy 5 years ago
parent
commit
aef15f004a

+ 47 - 54
data/web/admin.php

@@ -113,77 +113,71 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
         </form>
         </form>
         </div>
         </div>
 
 
-        <legend style="margin-top:20px">
-          <span style="font-size:12px" class="arrow rotate glyphicon glyphicon-wrench"></span> API
+        <legend style="cursor:pointer;" data-target="#admin_api" class="arrow-toggle" unselectable="on" data-toggle="collapse">
+          <span style="font-size:12px" class="arrow rotate glyphicon glyphicon-menu-down"></span> API
         </legend>
         </legend>
+        <div id="admin_api" class="collapse">
         <?php
         <?php
         $api_ro = admin_api('ro', 'get');
         $api_ro = admin_api('ro', 'get');
         $api_rw = admin_api('rw', 'get');
         $api_rw = admin_api('rw', 'get');
         ?>
         ?>
-        <div class="panel-group" id="accordion">
-          <div class="panel panel-default">
-            <div class="panel-heading">
-              <h4 class="panel-title">
-                <a data-toggle="collapse" data-parent="#accordion" href="#api-ro">
-                ⇇ Read-Only Access</a>
-              </h4>
-            </div>
-            <div id="api-ro" class="panel-collapse collapse">
-              <div class="panel-body">
-                <form class="form-horizontal" autocapitalize="none" autocorrect="off" role="form" method="post">
-                  <div class="form-group">
-                    <label class="control-label col-sm-3" for="allow_from_ro"><?=$lang['admin']['api_allow_from'];?>:</label>
-                    <div class="col-sm-9">
-                      <textarea class="form-control" rows="2" name="allow_from" id="allow_from_ro" <?=($api_ro['skip_ip_check'] == 1) ? 'disabled' : null;?> required><?=htmlspecialchars($api_ro['allow_from']);?></textarea>
+          <div class="col-lg-6">
+            <div class="panel panel-default">
+              <div class="panel-heading">
+                <h4 class="panel-title">⇇ Read-Only Access</h4>
+              </div>
+                <div class="panel-body">
+                  <form class="form-horizontal" autocapitalize="none" autocorrect="off" role="form" method="post">
+                    <div class="form-group">
+                      <label class="control-label col-sm-3" for="allow_from_ro"><?=$lang['admin']['api_allow_from'];?>:</label>
+                      <div class="col-sm-9">
+                        <textarea class="form-control textarea-code" rows="7" name="allow_from" id="allow_from_ro" <?=($api_ro['skip_ip_check'] == 1) ? 'disabled' : null;?> required><?=htmlspecialchars($api_ro['allow_from']);?></textarea>
+                      </div>
                     </div>
                     </div>
-                  </div>
-                  <div class="form-group">
-                    <div class="col-sm-offset-3 col-sm-9">
-                      <label>
-                        <input type="checkbox" name="skip_ip_check" id="skip_ip_check_ro" <?=($api_ro['skip_ip_check'] == 1) ? 'checked' : null;?>> <?=$lang['admin']['api_skip_ip_check'];?>
-                      </label>
+                    <div class="form-group">
+                      <div class="col-sm-offset-3 col-sm-9">
+                        <label>
+                          <input type="checkbox" name="skip_ip_check" id="skip_ip_check_ro" <?=($api_ro['skip_ip_check'] == 1) ? 'checked' : null;?>> <?=$lang['admin']['api_skip_ip_check'];?>
+                        </label>
+                      </div>
                     </div>
                     </div>
-                  </div>
-                  <div class="form-group">
-                    <label class="control-label col-sm-3"><?=$lang['admin']['api_key'];?>:</label>
-                    <div class="col-sm-9">
-                      <pre><?=(empty(htmlspecialchars($api_ro['api_key']))) ? '-' : htmlspecialchars($api_ro['api_key']);?></pre>
+                    <div class="form-group">
+                      <label class="control-label col-sm-3"><?=$lang['admin']['api_key'];?>:</label>
+                      <div class="col-sm-9">
+                        <pre><?=(empty(htmlspecialchars($api_ro['api_key']))) ? '-' : htmlspecialchars($api_ro['api_key']);?></pre>
+                      </div>
                     </div>
                     </div>
-                  </div>
-                  <div class="form-group">
-                    <div class="col-sm-offset-3 col-sm-9">
-                      <label>
-                        <input type="checkbox" name="active" <?=($api_ro['active'] == 1) ? 'checked' : null;?>> <?=$lang['admin']['activate_api'];?>
-                      </label>
+                    <div class="form-group">
+                      <div class="col-sm-offset-3 col-sm-9">
+                        <label>
+                          <input type="checkbox" name="active" <?=($api_ro['active'] == 1) ? 'checked' : null;?>> <?=$lang['admin']['activate_api'];?>
+                        </label>
+                      </div>
                     </div>
                     </div>
-                  </div>
-                  <div class="form-group">
-                    <div class="col-sm-offset-3 col-sm-9">
-                      <p class="help-block"><?=$lang['admin']['api_info'];?></p>
-                      <div class="btn-group">
-                        <button class="btn btn-sm btn-default" name="admin_api[ro]" type="submit" href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
-                        <button class="btn btn-sm btn-primary" name="admin_api_regen_key[ro]" type="submit" href="#"><?=$lang['admin']['regen_api_key'];?></button>
+                    <div class="form-group">
+                      <div class="col-sm-offset-3 col-sm-9">
+                        <p class="help-block"><?=$lang['admin']['api_info'];?></p>
+                        <div class="btn-group">
+                          <button class="btn btn-sm btn-success" name="admin_api[ro]" type="submit" href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
+                          <button class="btn btn-sm btn-default admin-ays-dialog" name="admin_api_regen_key[ro]" type="submit" href="#"><?=$lang['admin']['regen_api_key'];?></button>
+                        </div>
                       </div>
                       </div>
                     </div>
                     </div>
-                  </div>
-                </form>
-              </div>
+                  </form>
+                </div>
             </div>
             </div>
           </div>
           </div>
+          <div class="col-lg-6">
           <div class="panel panel-default">
           <div class="panel panel-default">
             <div class="panel-heading">
             <div class="panel-heading">
-              <h4 class="panel-title">
-                <a data-toggle="collapse" data-parent="#accordion" href="#api-rw">
-                ⇄ Read-Write Access</a>
-              </h4>
+              <h4 class="panel-title">⇄ Read-Write Access</h4>
             </div>
             </div>
-            <div id="api-rw" class="panel-collapse collapse">
               <div class="panel-body">
               <div class="panel-body">
                 <form class="form-horizontal" autocapitalize="none" autocorrect="off" role="form" method="post">
                 <form class="form-horizontal" autocapitalize="none" autocorrect="off" role="form" method="post">
                   <div class="form-group">
                   <div class="form-group">
                     <label class="control-label col-sm-3" for="allow_from_rw"><?=$lang['admin']['api_allow_from'];?>:</label>
                     <label class="control-label col-sm-3" for="allow_from_rw"><?=$lang['admin']['api_allow_from'];?>:</label>
                     <div class="col-sm-9">
                     <div class="col-sm-9">
-                      <textarea class="form-control" rows="2" name="allow_from" id="allow_from_rw" <?=($api_rw['skip_ip_check'] == 1) ? 'disabled' : null;?> required><?=htmlspecialchars($api_rw['allow_from']);?></textarea>
+                      <textarea class="form-control textarea-code" rows="7" name="allow_from" id="allow_from_rw" <?=($api_rw['skip_ip_check'] == 1) ? 'disabled' : null;?> required><?=htmlspecialchars($api_rw['allow_from']);?></textarea>
                     </div>
                     </div>
                   </div>
                   </div>
                   <div class="form-group">
                   <div class="form-group">
@@ -210,17 +204,16 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
                     <div class="col-sm-offset-3 col-sm-9">
                     <div class="col-sm-offset-3 col-sm-9">
                       <p class="help-block"><?=$lang['admin']['api_info'];?></p>
                       <p class="help-block"><?=$lang['admin']['api_info'];?></p>
                       <div class="btn-group">
                       <div class="btn-group">
-                        <button class="btn btn-sm btn-default" name="admin_api[rw]" type="submit" href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
-                        <button class="btn btn-sm btn-primary" name="admin_api_regen_key[rw]" type="submit" href="#"><?=$lang['admin']['regen_api_key'];?></button>
+                        <button class="btn btn-sm btn-success" name="admin_api[rw]" type="submit" href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
+                        <button class="btn btn-sm btn-default admin-ays-dialog" name="admin_api_regen_key[rw]" type="submit" href="#"><?=$lang['admin']['regen_api_key'];?></button>
                       </div>
                       </div>
                     </div>
                     </div>
                   </div>
                   </div>
                 </form>
                 </form>
               </div>
               </div>
-            </div>
+          </div>
           </div>
           </div>
         </div>
         </div>
-
       </div>
       </div>
     </div>
     </div>
 
 

+ 16 - 15
data/web/inc/functions.fail2ban.inc.php

@@ -1,19 +1,4 @@
 <?php
 <?php
-function valid_network($network) {
-  $cidr = explode('/', $network);
-  if (filter_var($cidr[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) && (!isset($cidr[1]) || ($cidr[1] >= 0 && $cidr[1] <= 32))) {
-    return true;
-  }
-  elseif (filter_var($cidr[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && (!isset($cidr[1]) || ($cidr[1] >= 0 && $cidr[1] <= 128))) {
-    return true;
-  }
-  return false;
-}
-
-function valid_hostname($hostname) {
-    return filter_var($hostname, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME);
-}
-
 function fail2ban($_action, $_data = null) {
 function fail2ban($_action, $_data = null) {
   global $redis;
   global $redis;
   global $lang;
   global $lang;
@@ -196,6 +181,14 @@ function fail2ban($_action, $_data = null) {
               if (valid_network($wl_item) || valid_hostname($wl_item)) {
               if (valid_network($wl_item) || valid_hostname($wl_item)) {
                 $redis->hSet('F2B_WHITELIST', $wl_item, 1);
                 $redis->hSet('F2B_WHITELIST', $wl_item, 1);
               }
               }
+              else {
+                $_SESSION['return'][] = array(
+                  'type' => 'danger',
+                  'log' => array(__FUNCTION__, $_action, $_data_log),
+                  'msg' => array('network_host_invalid', $wl_item)
+                );
+                continue;
+              }
             }
             }
           }
           }
         }
         }
@@ -206,6 +199,14 @@ function fail2ban($_action, $_data = null) {
               if (valid_network($bl_item) || valid_hostname($bl_item)) {
               if (valid_network($bl_item) || valid_hostname($bl_item)) {
                 $redis->hSet('F2B_BLACKLIST', $bl_item, 1);
                 $redis->hSet('F2B_BLACKLIST', $bl_item, 1);
               }
               }
+              else {
+                $_SESSION['return'][] = array(
+                  'type' => 'danger',
+                  'log' => array(__FUNCTION__, $_action, $_data_log),
+                  'msg' => array('network_host_invalid', $bl_item)
+                );
+                continue;
+              }
             }
             }
           }
           }
         }
         }

+ 168 - 170
data/web/inc/functions.inc.php

@@ -10,6 +10,76 @@ function isset_has_content($var) {
     return false;
     return false;
   }
   }
 }
 }
+// Validates ips and cidrs
+function valid_network($network) {
+  if (filter_var($network, FILTER_VALIDATE_IP)) {
+    return true;
+  }
+  $parts = explode('/', $network);
+  if (count($parts) != 2) {
+    return false;
+  }
+  $ip = $parts[0];
+  $netmask = $parts[1];
+  if (!preg_match("/^\d+$/", $netmask)){
+    return false;
+  }
+  $netmask = intval($parts[1]);
+  if ($netmask < 0) {
+    return false;
+  }
+  if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
+    return $netmask <= 32;
+  }
+  if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
+    return $netmask <= 128;
+  }
+  return false;
+}
+function valid_hostname($hostname) {
+  return filter_var($hostname, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME);
+}
+// Thanks to https://stackoverflow.com/a/49373789
+// Validates exact ip matches and ip-in-cidr, ipv4 and ipv6
+function ip_acl($ip, $networks) {
+  foreach($networks as $network) {
+    if (filter_var($network, FILTER_VALIDATE_IP)) {
+      if ($ip == $network) {
+        return true;
+      }
+      else {
+        continue;
+      }
+    }
+    $ipb = inet_pton($ip);
+    $iplen = strlen($ipb);
+    if (strlen($ipb) < 4) {
+      continue;
+    }
+    $ar = explode('/', $network);
+    $ip1 = $ar[0];
+    $ip1b = inet_pton($ip1);
+    $ip1len = strlen($ip1b);
+    if ($ip1len != $iplen) {
+      continue;
+    }
+    if (count($ar)>1) {
+      $bits=(int)($ar[1]);
+    }
+    else {
+      $bits = $iplen * 8;
+    }
+    for ($c=0; $bits>0; $c++) {
+      $bytemask = ($bits < 8) ? 0xff ^ ((1 << (8-$bits))-1) : 0xff;
+      if (((ord($ipb[$c]) ^ ord($ip1b[$c])) & $bytemask) != 0) {
+        continue 2;
+      }
+      $bits-=8;
+    }
+    return true;
+  }
+  return false;
+}
 function hash_password($password) {
 function hash_password($password) {
 	$salt_str = bin2hex(openssl_random_pseudo_bytes(8));
 	$salt_str = bin2hex(openssl_random_pseudo_bytes(8));
 	return "{SSHA256}".base64_encode(hash('sha256', $password . $salt_str, true) . $salt_str);
 	return "{SSHA256}".base64_encode(hash('sha256', $password . $salt_str, true) . $salt_str);
@@ -1160,178 +1230,106 @@ function admin_api($access, $action, $data = null) {
 		);
 		);
 		return false;
 		return false;
 	}
 	}
-	switch ($access) {
-    case "rw":
-      switch ($action) {
-        case "edit":
-          $active = (isset($data['active'])) ? 1 : 0;
-          $skip_ip_check = (isset($data['skip_ip_check'])) ? 1 : 0;
-          $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $data['allow_from']));
-          foreach ($allow_from as $key => $val) {
-            if (empty($val)) {
-              continue;
-            }
-            if (!filter_var($val, FILTER_VALIDATE_IP)) {
-              $_SESSION['return'][] =  array(
-                'type' => 'warning',
-                'log' => array(__FUNCTION__, $data),
-                'msg' => array('ip_invalid', htmlspecialchars($allow_from[$key]))
-              );
-              unset($allow_from[$key]);
-              continue;
-            }
-          }
-          $allow_from = implode(',', array_unique(array_filter($allow_from)));
-          if (empty($allow_from) && $skip_ip_check == 0) {
-            $_SESSION['return'][] =  array(
-              'type' => 'danger',
-              'log' => array(__FUNCTION__, $data),
-              'msg' => 'ip_list_empty'
-            );
-            return false;
-          }
-          $api_key = implode('-', array(
-            strtoupper(bin2hex(random_bytes(3))),
-            strtoupper(bin2hex(random_bytes(3))),
-            strtoupper(bin2hex(random_bytes(3))),
-            strtoupper(bin2hex(random_bytes(3))),
-            strtoupper(bin2hex(random_bytes(3)))
-          ));
-          $stmt = $pdo->query("SELECT `api_key` FROM `api` WHERE `access` = 'rw'");
-          $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
-          if (empty($num_results)) {
-            $stmt = $pdo->prepare("INSERT INTO `api` (`api_key`, `skip_ip_check`, `active`, `allow_from`, `access`)
-              VALUES (:api_key, :skip_ip_check, :active, :allow_from, 'rw');");
-            $stmt->execute(array(
-              ':api_key' => $api_key,
-              ':skip_ip_check' => $skip_ip_check,
-              ':active' => $active,
-              ':allow_from' => $allow_from
-            ));
-          }
-          else {
-            if ($skip_ip_check == 0) {
-              $stmt = $pdo->prepare("UPDATE `api` SET `skip_ip_check` = :skip_ip_check, `active` = :active, `allow_from` = :allow_from WHERE `access` = 'rw';");
-              $stmt->execute(array(
-                ':active' => $active,
-                ':skip_ip_check' => $skip_ip_check,
-                ':allow_from' => $allow_from
-              ));
-            }
-            else {
-              $stmt = $pdo->prepare("UPDATE `api` SET `skip_ip_check` = :skip_ip_check, `active` = :active WHERE `access` = 'rw';");
-              $stmt->execute(array(
-                ':active' => $active,
-                ':skip_ip_check' => $skip_ip_check
-              ));
-            }
-          }
-        break;
-        case "regen_key":
-          $api_key = implode('-', array(
-            strtoupper(bin2hex(random_bytes(3))),
-            strtoupper(bin2hex(random_bytes(3))),
-            strtoupper(bin2hex(random_bytes(3))),
-            strtoupper(bin2hex(random_bytes(3))),
-            strtoupper(bin2hex(random_bytes(3)))
-          ));
-          $stmt = $pdo->prepare("UPDATE `api` SET `api_key` = :api_key WHERE `access` = 'rw'");
-          $stmt->execute(array(
-            ':api_key' => $api_key
-          ));
-        break;
-        case "get":
-          $stmt = $pdo->query("SELECT * FROM `api` WHERE `access` = 'rw'");
-          $apidata = $stmt->fetch(PDO::FETCH_ASSOC);
-          return $apidata;
-        break;
+  if ($access !== "ro" && $access !== "rw") {
+		$_SESSION['return'][] =  array(
+			'type' => 'danger',
+      'log' => array(__FUNCTION__),
+			'msg' => 'invalid access type'
+		);
+		return false;
+  }
+  if ($action == "edit") {
+    $active = (!empty($data['active'])) ? 1 : 0;
+    $skip_ip_check = (isset($data['skip_ip_check'])) ? 1 : 0;
+    $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $data['allow_from']));
+    foreach ($allow_from as $key => $val) {
+      if (empty($val)) {
+        unset($allow_from[$key]);
+        continue;
       }
       }
-    case "ro":
-      switch ($action) {
-        case "edit":
-          $active = (isset($data['active'])) ? 1 : 0;
-          $skip_ip_check = (isset($data['skip_ip_check'])) ? 1 : 0;
-          $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $data['allow_from']));
-          foreach ($allow_from as $key => $val) {
-            if (empty($val)) {
-              continue;
-            }
-            if (!filter_var($val, FILTER_VALIDATE_IP)) {
-              $_SESSION['return'][] =  array(
-                'type' => 'warning',
-                'log' => array(__FUNCTION__, $data),
-                'msg' => array('ip_invalid', htmlspecialchars($allow_from[$key]))
-              );
-              unset($allow_from[$key]);
-              continue;
-            }
-          }
-          $allow_from = implode(',', array_unique(array_filter($allow_from)));
-          if (empty($allow_from) && $skip_ip_check == 0) {
-            $_SESSION['return'][] =  array(
-              'type' => 'danger',
-              'log' => array(__FUNCTION__, $data),
-              'msg' => 'ip_list_empty'
-            );
-            return false;
-          }
-          $api_key = implode('-', array(
-            strtoupper(bin2hex(random_bytes(3))),
-            strtoupper(bin2hex(random_bytes(3))),
-            strtoupper(bin2hex(random_bytes(3))),
-            strtoupper(bin2hex(random_bytes(3))),
-            strtoupper(bin2hex(random_bytes(3)))
-          ));
-          $stmt = $pdo->query("SELECT `api_key` FROM `api` WHERE `access` = 'ro'");
-          $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
-          if (empty($num_results)) {
-            $stmt = $pdo->prepare("INSERT INTO `api` (`api_key`, `skip_ip_check`, `active`, `allow_from`, `access`)
-              VALUES (:api_key, :skip_ip_check, :active, :allow_from, 'ro');");
-            $stmt->execute(array(
-              ':api_key' => $api_key,
-              ':skip_ip_check' => $skip_ip_check,
-              ':active' => $active,
-              ':allow_from' => $allow_from
-            ));
-          }
-          else {
-            if ($skip_ip_check == 0) {
-              $stmt = $pdo->prepare("UPDATE `api` SET `skip_ip_check` = :skip_ip_check, `active` = :active, `allow_from` = :allow_from WHERE `access` = 'ro';");
-              $stmt->execute(array(
-                ':active' => $active,
-                ':skip_ip_check' => $skip_ip_check,
-                ':allow_from' => $allow_from
-              ));
-            }
-            else {
-              $stmt = $pdo->prepare("UPDATE `api` SET `skip_ip_check` = :skip_ip_check, `active` = :active WHERE `access` = 'ro';");
-              $stmt->execute(array(
-                ':active' => $active,
-                ':skip_ip_check' => $skip_ip_check
-              ));
-            }
-          }
-        break;
-        case "regen_key":
-          $api_key = implode('-', array(
-            strtoupper(bin2hex(random_bytes(3))),
-            strtoupper(bin2hex(random_bytes(3))),
-            strtoupper(bin2hex(random_bytes(3))),
-            strtoupper(bin2hex(random_bytes(3))),
-            strtoupper(bin2hex(random_bytes(3)))
-          ));
-          $stmt = $pdo->prepare("UPDATE `api` SET `api_key` = :api_key WHERE `access` = 'ro'");
-          $stmt->execute(array(
-            ':api_key' => $api_key
-          ));
-        break;
-        case "get":
-          $stmt = $pdo->query("SELECT * FROM `api` WHERE `access` = 'ro'");
-          $apidata = $stmt->fetch(PDO::FETCH_ASSOC);
-          return $apidata;
-        break;
+      if (valid_network($val) !== true) {
+        $_SESSION['return'][] =  array(
+          'type' => 'warning',
+          'log' => array(__FUNCTION__, $data),
+          'msg' => array('ip_invalid', htmlspecialchars($allow_from[$key]))
+        );
+        unset($allow_from[$key]);
+        continue;
       }
       }
-    break;
+    }
+    $allow_from = implode(',', array_unique(array_filter($allow_from)));
+    if (empty($allow_from) && $skip_ip_check == 0) {
+      $_SESSION['return'][] =  array(
+        'type' => 'danger',
+        'log' => array(__FUNCTION__, $data),
+        'msg' => 'ip_list_empty'
+      );
+      return false;
+    }
+    $api_key = implode('-', array(
+      strtoupper(bin2hex(random_bytes(3))),
+      strtoupper(bin2hex(random_bytes(3))),
+      strtoupper(bin2hex(random_bytes(3))),
+      strtoupper(bin2hex(random_bytes(3))),
+      strtoupper(bin2hex(random_bytes(3)))
+    ));
+    $stmt = $pdo->query("SELECT `api_key` FROM `api` WHERE `access` = '" . $access . "'");
+    $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
+    if (empty($num_results)) {
+      $stmt = $pdo->prepare("INSERT INTO `api` (`api_key`, `skip_ip_check`, `active`, `allow_from`, `access`)
+        VALUES (:api_key, :skip_ip_check, :active, :allow_from, :access);");
+      $stmt->execute(array(
+        ':api_key' => $api_key,
+        ':skip_ip_check' => $skip_ip_check,
+        ':active' => $active,
+        ':allow_from' => $allow_from,
+        ':access' => $access
+      ));
+    }
+    else {
+      if ($skip_ip_check == 0) {
+        $stmt = $pdo->prepare("UPDATE `api` SET `skip_ip_check` = :skip_ip_check,
+          `active` = :active,
+          `allow_from` = :allow_from
+            WHERE `access` = :access;");
+        $stmt->execute(array(
+          ':active' => $active,
+          ':skip_ip_check' => $skip_ip_check,
+          ':allow_from' => $allow_from,
+          ':access' => $access
+        ));
+      }
+      else {
+        $stmt = $pdo->prepare("UPDATE `api` SET `skip_ip_check` = :skip_ip_check,
+          `active` = :active
+            WHERE `access` = :access;");
+        $stmt->execute(array(
+          ':active' => $active,
+          ':skip_ip_check' => $skip_ip_check,
+          ':access' => $access
+        ));
+      }
+    }
+  }
+  elseif ($action == "regen_key") {
+    $api_key = implode('-', array(
+      strtoupper(bin2hex(random_bytes(3))),
+      strtoupper(bin2hex(random_bytes(3))),
+      strtoupper(bin2hex(random_bytes(3))),
+      strtoupper(bin2hex(random_bytes(3))),
+      strtoupper(bin2hex(random_bytes(3)))
+    ));
+    $stmt = $pdo->prepare("UPDATE `api` SET `api_key` = :api_key WHERE `access` = :access");
+    $stmt->execute(array(
+      ':api_key' => $api_key,
+      ':access' => $access
+    ));
+  }
+  elseif ($action == "get") {
+    $stmt = $pdo->query("SELECT * FROM `api` WHERE `access` = '" . $access . "'");
+    $apidata = $stmt->fetch(PDO::FETCH_ASSOC);
+    $apidata['allow_from'] = str_replace(',', PHP_EOL, $apidata['allow_from']);
+    return $apidata;
   }
   }
 	$_SESSION['return'][] =  array(
 	$_SESSION['return'][] =  array(
 		'type' => 'success',
 		'type' => 'success',

+ 2 - 1
data/web/inc/prerequisites.inc.php

@@ -179,6 +179,8 @@ function get_remote_ip($anonymize = null) {
   }
   }
 }
 }
 
 
+// Load core functions first
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
 
 
 // IMAP lib
 // IMAP lib
@@ -215,7 +217,6 @@ if(file_exists($langFile)) {
   $lang = array_merge_real($lang, json_decode(file_get_contents($langFile), true));
   $lang = array_merge_real($lang, json_decode(file_get_contents($langFile), true));
 }
 }
 
 
-require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.acl.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.acl.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.app_passwd.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.app_passwd.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php';

+ 1 - 1
data/web/inc/sessions.inc.php

@@ -53,7 +53,7 @@ if (!empty($_SERVER['HTTP_X_API_KEY'])) {
     $skip_ip_check = ($api_return['skip_ip_check'] == 1);
     $skip_ip_check = ($api_return['skip_ip_check'] == 1);
     $remote = get_remote_ip(false);
     $remote = get_remote_ip(false);
     $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $api_return['allow_from']));
     $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $api_return['allow_from']));
-    if (in_array($remote, $allow_from) || $skip_ip_check === true) {
+    if ($skip_ip_check === true || ip_acl($remote, $allow_from)) {
       $_SESSION['mailcow_cc_username'] = 'API';
       $_SESSION['mailcow_cc_username'] = 'API';
       $_SESSION['mailcow_cc_role'] = 'admin';
       $_SESSION['mailcow_cc_role'] = 'admin';
       $_SESSION['mailcow_cc_api'] = true;
       $_SESSION['mailcow_cc_api'] = true;

+ 1 - 0
data/web/js/site/admin.js

@@ -21,6 +21,7 @@ jQuery(function($){
   $("#mass_exclude").change(function(){ $("#mass_include").selectpicker('deselectAll'); });
   $("#mass_exclude").change(function(){ $("#mass_include").selectpicker('deselectAll'); });
   $("#mass_include").change(function(){ $("#mass_exclude").selectpicker('deselectAll'); });
   $("#mass_include").change(function(){ $("#mass_exclude").selectpicker('deselectAll'); });
   $("#mass_disarm").click(function() { $("#mass_send").attr("disabled", !this.checked); });
   $("#mass_disarm").click(function() { $("#mass_send").attr("disabled", !this.checked); });
+  $(".admin-ays-dialog").click(function() { return confirm(lang.ays); });
   $(".validate_rspamd_regex").click(function( event ) {
   $(".validate_rspamd_regex").click(function( event ) {
     event.preventDefault();
     event.preventDefault();
     var regex_map_id = $(this).data('regex-map');
     var regex_map_id = $(this).data('regex-map');

+ 2 - 1
data/web/lang/lang.de.json

@@ -122,7 +122,7 @@
         "admin_details": "Administrator bearbeiten",
         "admin_details": "Administrator bearbeiten",
         "admin_domains": "Domain-Zuweisungen",
         "admin_domains": "Domain-Zuweisungen",
         "advanced_settings": "Erweiterte Einstellungen",
         "advanced_settings": "Erweiterte Einstellungen",
-        "api_allow_from": "IP-Adressen für Zugriff",
+        "api_allow_from": "IP-Adressen oder Netzwerke (CIDR Notation) für Zugriff auf API",
         "api_info": "Das API befindet sich noch in Entwicklung, die Dokumentation kann unter <a href=\"/api\">/api</a> abgerufen werden.",
         "api_info": "Das API befindet sich noch in Entwicklung, die Dokumentation kann unter <a href=\"/api\">/api</a> abgerufen werden.",
         "api_key": "API-Key",
         "api_key": "API-Key",
         "api_skip_ip_check": "IP-Check für API nicht ausführen",
         "api_skip_ip_check": "IP-Check für API nicht ausführen",
@@ -131,6 +131,7 @@
         "apps_name": "\"mailcow Apps\" Name",
         "apps_name": "\"mailcow Apps\" Name",
         "arrival_time": "Ankunftszeit (Serverzeit)",
         "arrival_time": "Ankunftszeit (Serverzeit)",
         "authed_user": "Auth. Benutzer",
         "authed_user": "Auth. Benutzer",
+        "ays": "Soll der Vorgang wirklich ausgeführt werden?",
         "ban_list_info": "Übersicht ausgesperrter Netzwerke: <b>Netzwerk (verbleibende Banzeit) - [Aktionen]</b>.<br />IPs, die zum Entsperren eingereiht werden, verlassen die Liste aktiver Bans nach wenigen Sekunden.<br />Rote Labels sind Indikatoren für aktive Blacklisteinträge.",
         "ban_list_info": "Übersicht ausgesperrter Netzwerke: <b>Netzwerk (verbleibende Banzeit) - [Aktionen]</b>.<br />IPs, die zum Entsperren eingereiht werden, verlassen die Liste aktiver Bans nach wenigen Sekunden.<br />Rote Labels sind Indikatoren für aktive Blacklisteinträge.",
         "change_logo": "Logo ändern",
         "change_logo": "Logo ändern",
         "configuration": "Konfiguration",
         "configuration": "Konfiguration",

+ 2 - 1
data/web/lang/lang.en.json

@@ -122,7 +122,7 @@
         "admin_details": "Edit administrator details",
         "admin_details": "Edit administrator details",
         "admin_domains": "Domain assignments",
         "admin_domains": "Domain assignments",
         "advanced_settings": "Advanced settings",
         "advanced_settings": "Advanced settings",
-        "api_allow_from": "Allow API access from these IPs (separated by comma or new line)",
+        "api_allow_from": "Allow API access from these IPs/CIDR network notations",
         "api_info": "The API is a work in progress. The documentation can be found at <a href=\"/api\">/api</a>",
         "api_info": "The API is a work in progress. The documentation can be found at <a href=\"/api\">/api</a>",
         "api_key": "API key",
         "api_key": "API key",
         "api_skip_ip_check": "Skip IP check for API",
         "api_skip_ip_check": "Skip IP check for API",
@@ -131,6 +131,7 @@
         "apps_name": "\"mailcow Apps\" name",
         "apps_name": "\"mailcow Apps\" name",
         "arrival_time": "Arrival time (server time)",
         "arrival_time": "Arrival time (server time)",
         "authed_user": "Auth. user",
         "authed_user": "Auth. user",
+        "ays": "Are you sure you want to proceed?",
         "ban_list_info": "See a list of banned IPs below: <b>network (remaining ban time) - [actions]</b>.<br />IPs queued to be unbanned will be removed from the active ban list within a few seconds.<br />Red labels indicate active permanent bans by blacklisting.",
         "ban_list_info": "See a list of banned IPs below: <b>network (remaining ban time) - [actions]</b>.<br />IPs queued to be unbanned will be removed from the active ban list within a few seconds.<br />Red labels indicate active permanent bans by blacklisting.",
         "change_logo": "Change logo",
         "change_logo": "Change logo",
         "configuration": "Configuration",
         "configuration": "Configuration",