FileBasedConfiguration.java

/*
 * This file is part of Waarp Project (named also Waarp or GG).
 *
 *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
 *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
 * individual contributors.
 *
 *  All Waarp Project is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with
 * Waarp . If not, see <http://www.gnu.org/licenses/>.
 */

package org.waarp.gateway.ftp.config;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.ChannelGroupFuture;
import io.netty.channel.group.ChannelGroupFutureListener;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.traffic.AbstractTrafficShapingHandler;
import io.netty.util.concurrent.EventExecutorGroup;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.waarp.common.crypto.Des;
import org.waarp.common.crypto.ssl.WaarpSecureKeyStore;
import org.waarp.common.crypto.ssl.WaarpSslContextFactory;
import org.waarp.common.database.DbAdmin;
import org.waarp.common.database.DbPreparedStatement;
import org.waarp.common.database.exception.WaarpDatabaseNoConnectionException;
import org.waarp.common.database.exception.WaarpDatabaseSqlException;
import org.waarp.common.exception.CryptoException;
import org.waarp.common.exception.InvalidArgumentException;
import org.waarp.common.file.AbstractDir;
import org.waarp.common.file.DirInterface;
import org.waarp.common.file.FileParameterInterface;
import org.waarp.common.file.filesystembased.FilesystemBasedFileParameterImpl;
import org.waarp.common.file.filesystembased.specific.FilesystemBasedDirJdkAbstract;
import org.waarp.common.logging.WaarpLogger;
import org.waarp.common.logging.WaarpLoggerFactory;
import org.waarp.common.utility.WaarpNettyUtil;
import org.waarp.common.utility.WaarpStringUtils;
import org.waarp.common.utility.WaarpThreadFactory;
import org.waarp.common.xml.XmlDecl;
import org.waarp.common.xml.XmlHash;
import org.waarp.common.xml.XmlType;
import org.waarp.common.xml.XmlUtil;
import org.waarp.common.xml.XmlValue;
import org.waarp.ftp.core.config.FtpConfiguration;
import org.waarp.ftp.core.control.BusinessHandler;
import org.waarp.ftp.core.control.ftps.FtpsInitializer;
import org.waarp.ftp.core.data.handler.DataBusinessHandler;
import org.waarp.ftp.core.exception.FtpNoConnectionException;
import org.waarp.ftp.core.exception.FtpUnknownFieldException;
import org.waarp.gateway.ftp.adminssl.HttpSslInitializer;
import org.waarp.gateway.ftp.control.FtpConstraintLimitHandler;
import org.waarp.gateway.ftp.database.DbConstantFtp;
import org.waarp.gateway.ftp.database.data.DbTransferLog;
import org.waarp.gateway.ftp.database.model.DbModelFactoryFtp;
import org.waarp.gateway.ftp.exec.AbstractExecutor;
import org.waarp.gateway.ftp.exec.LocalExecClient;
import org.waarp.gateway.ftp.file.SimpleAuth;
import org.waarp.gateway.ftp.snmp.FtpMonitoring;
import org.waarp.gateway.ftp.snmp.FtpPrivateMib;
import org.waarp.gateway.ftp.snmp.FtpVariableFactory;
import org.waarp.snmp.SnmpConfiguration;
import org.waarp.snmp.WaarpMOFactory;
import org.waarp.snmp.WaarpSnmpAgent;

import java.io.File;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * FtpConfiguration based on a XML file
 */
public class FileBasedConfiguration extends FtpConfiguration {
  private static final String ERROR_DURING_WRITE_AUTHENTICATION_FILE =
      "Error during Write Authentication file";

  private static final String UNABLE_TO_FIND_LOCAL_EXEC_ADDRESS_IN_CONFIG_FILE =
      "Unable to find LocalExec Address in Config file";

  /**
   * Internal Logger
   */
  private static final WaarpLogger logger =
      WaarpLoggerFactory.getLogger(FileBasedConfiguration.class);

  /**
   * SERVER HOSTID
   */
  private static final String XML_SERVER_HOSTID = "hostid";
  /**
   * Authentication
   */
  private static final String XML_AUTHENTIFICATION_FILE = "authentfile";
  /**
   * SERVER CRYPTO for Password
   */
  private static final String XML_PATH_CRYPTOKEY = "cryptokey";

  /**
   * Structure of the Configuration file
   */
  private static final XmlDecl[] configIdentityDecls = {
      // identity
      new XmlDecl(XmlType.STRING, XML_SERVER_HOSTID),
      new XmlDecl(XmlType.STRING, XML_PATH_CRYPTOKEY),
      new XmlDecl(XmlType.STRING, XML_AUTHENTIFICATION_FILE)
  };
  /**
   * Use HTTP compression for R66 HTTP connection
   */
  private static final String XML_USEHTTPCOMP = "usehttpcomp";
  /**
   * Use external Waarp Local Exec for ExecTask and ExecMoveTask
   */
  private static final String XML_USELOCALEXEC = "uselocalexec";

  /**
   * Address of Waarp Local Exec for ExecTask and ExecMoveTask
   */
  private static final String XML_LEXECADDR = "lexecaddr";

  /**
   * Port of Waarp Local Exec for ExecTask and ExecMoveTask
   */
  private static final String XML_LEXECPORT = "lexecport";
  /**
   * ADMINISTRATOR SERVER NAME (shutdown)
   */
  private static final String XML_SERVER_ADMIN = "serveradmin";
  /**
   * SERVER PASSWORD (shutdown)
   */
  private static final String XML_SERVER_PASSWD = "serverpasswd"; //NOSONAR
  /**
   * SERVER SSL STOREKEY PATH ADMIN
   */
  private static final String XML_PATH_ADMIN_KEYPATH = "admkeypath";

  /**
   * SERVER SSL KEY PASS ADMIN
   */
  private static final String XML_PATH_ADMIN_KEYPASS = "admkeypass";

  /**
   * SERVER SSL STOREKEY PASS ADMIN
   */
  private static final String XML_PATH_ADMIN_KEYSTOREPASS = "admkeystorepass";
  /**
   * HTTP Admin Directory
   */
  private static final String XML_HTTPADMINPATH = "httpadmin";
  /**
   * Monitoring: snmp configuration file (if empty, no snmp support)
   */
  private static final String XML_MONITOR_SNMP_CONFIG = "snmpconfig";

  /**
   * Structure of the Configuration file
   */
  private static final XmlDecl[] configServerParamDecls = {
      // server
      new XmlDecl(XmlType.BOOLEAN, XML_USELOCALEXEC),
      new XmlDecl(XmlType.STRING, XML_LEXECADDR),
      new XmlDecl(XmlType.INTEGER, XML_LEXECPORT),
      new XmlDecl(XmlType.STRING, XML_SERVER_ADMIN),
      new XmlDecl(XmlType.STRING, XML_SERVER_PASSWD),
      new XmlDecl(XmlType.BOOLEAN, XML_USEHTTPCOMP),
      new XmlDecl(XmlType.STRING, XML_HTTPADMINPATH),
      new XmlDecl(XmlType.STRING, XML_PATH_ADMIN_KEYPATH),
      new XmlDecl(XmlType.STRING, XML_PATH_ADMIN_KEYSTOREPASS),
      new XmlDecl(XmlType.STRING, XML_PATH_ADMIN_KEYPASS),
      new XmlDecl(XmlType.STRING, XML_MONITOR_SNMP_CONFIG)
  };
  /**
   * SERVER PORT
   */
  private static final String XML_SERVER_PORT = "serverport";
  /**
   * SERVER ADDRESS if any
   */
  private static final String XML_SERVER_ADDRESS = "serveraddress";
  /**
   * RANGE of PORT for Passive Mode
   */
  private static final String XML_RANGE_PORT_MIN = "portmin";

  /**
   * RANGE of PORT for Passive Mode
   */
  private static final String XML_RANGE_PORT_MAX = "portmax";
  /**
   * SERVER HTTP PORT MONITORING
   */
  private static final String XML_SERVER_HTTP_PORT = "serverhttpport";
  /**
   * SERVER HTTPS PORT ADMINISTRATION
   */
  private static final String XML_SERVER_HTTPS_PORT = "serverhttpsport";

  /**
   * Structure of the Configuration file
   */
  private static final XmlDecl[] configNetworkServerDecls = {
      // network
      new XmlDecl(XmlType.INTEGER, XML_SERVER_PORT),
      new XmlDecl(XmlType.STRING, XML_SERVER_ADDRESS),
      new XmlDecl(XmlType.INTEGER, XML_RANGE_PORT_MIN),
      new XmlDecl(XmlType.INTEGER, XML_RANGE_PORT_MAX),
      new XmlDecl(XmlType.INTEGER, XML_SERVER_HTTP_PORT),
      new XmlDecl(XmlType.INTEGER, XML_SERVER_HTTPS_PORT)
  };
  /**
   * Database Driver as of oracle, mysql, postgresql, h2
   */
  private static final String XML_DBDRIVER = "dbdriver";

  /**
   * Database Server connection string as of jdbc:type://[host:port],[failoverhost:port]
   * .../[database][?propertyName1][ =propertyValue1][&propertyName2][=propertyValue2]...
   */
  private static final String XML_DBSERVER = "dbserver";

  /**
   * Database User
   */
  private static final String XML_DBUSER = "dbuser";

  /**
   * Database Password
   */
  private static final String XML_DBPASSWD = "dbpasswd";//NOSONAR
  /**
   * Structure of the Configuration file
   */
  private static final XmlDecl[] configDbDecls = {
      // db
      new XmlDecl(XmlType.STRING, XML_DBDRIVER),
      new XmlDecl(XmlType.STRING, XML_DBSERVER),
      new XmlDecl(XmlType.STRING, XML_DBUSER),
      new XmlDecl(XmlType.STRING, XML_DBPASSWD)
  };
  /**
   * Allow PASSIVE = -1 / ACTIVE = 1 / Both = 0
   */
  private static final String XML_ACTIVE_OR_PASSIVE = "activepassive";
  /**
   * Should a file be deleted when a Store like command is aborted
   */
  private static final String XML_DELETEONABORT = "deleteonabort";
  /**
   * Default number of threads in pool for Server.
   */
  private static final String XML_SERVER_THREAD = "serverthread";

  /**
   * Default number of threads in pool for Client.
   */
  private static final String XML_CLIENT_THREAD = "clientthread";
  /**
   * Memory Limit to use.
   */
  private static final String XML_MEMORY_LIMIT = "memorylimit";

  /**
   * Limit for Session
   */
  private static final String XML_LIMITSESSION = "sessionlimit";

  /**
   * Limit for Global
   */
  private static final String XML_LIMITGLOBAL = "globallimit";
  /**
   * Delay between two checks for Limit
   */
  private static final String XML_LIMITDELAY = "delaylimit";
  /**
   * Nb of milliseconds after connection is in timeout
   */
  private static final String XML_TIMEOUTCON = "timeoutcon";
  /**
   * Nb of milliseconds after data connection is in timeout
   */
  private static final String XML_DATA_TIMEOUTCON = "datatimeoutcon";
  /**
   * Size by default of block size for receive/sending files. Should be a
   * multiple of 8192 (maximum = 64K due to
   * block limitation to 2 bytes)
   */
  private static final String XML_BLOCKSIZE = "blocksize";
  /**
   * Should a file MD5 SHA1 be computed using NIO
   */
  private static final String XML_USENIO = "usenio";

  /**
   * Should a file MD5 be computed using FastMD5
   */
  private static final String XML_USEFASTMD5 = "usefastmd5";

  /**
   * If using Fast MD5, should we used the binary JNI library, empty meaning
   * no
   */
  private static final String XML_FASTMD5 = "fastmd5";
  /**
   * Usage of CPU Limit
   */
  private static final String XML_CSTRT_USECPULIMIT = "usecpulimit";

  /**
   * Usage of JDK CPU Limit (True) or SysMon CPU Limit
   */
  private static final String XML_CSTRT_USECPUJDKLIMIT = "usejdkcpulimit";

  /**
   * CPU LIMIT between 0 and 1, where 1 stands for no limit
   */
  private static final String XML_CSTRT_CPULIMIT = "cpulimit";
  /**
   * Connection limit where 0 stands for no limit
   */
  private static final String XML_CSTRT_CONNLIMIT = "connlimit";
  /**
   * CPU LOW limit to apply increase of throttle
   */
  private static final String XML_CSTRT_LOWCPULIMIT = "lowcpulimit";
  /**
   * CPU HIGH limit to apply decrease of throttle, 0 meaning no throttle
   * activated
   */
  private static final String XML_CSTRT_HIGHCPULIMIT = "highcpulimit";
  /**
   * PERCENTAGE DECREASE of Bandwidth
   */
  private static final String XML_CSTRT_PERCENTDECREASE = "percentdecrease";
  /**
   * Delay between 2 checks of throttle test
   */
  private static final String XML_CSTRT_DELAYTHROTTLE = "delaythrottle";
  /**
   * Bandwidth low limit to not got below
   */
  private static final String XML_CSTRT_LIMITLOWBANDWIDTH = "limitlowbandwidth";
  /**
   * Structure of the Configuration file
   */
  private static final XmlDecl[] configLimitDecls = {
      // limit
      new XmlDecl(XmlType.INTEGER, XML_ACTIVE_OR_PASSIVE),
      new XmlDecl(XmlType.BOOLEAN, XML_DELETEONABORT),
      new XmlDecl(XmlType.LONG, XML_LIMITSESSION),
      new XmlDecl(XmlType.LONG, XML_LIMITGLOBAL),
      new XmlDecl(XmlType.LONG, XML_LIMITDELAY),
      new XmlDecl(XmlType.INTEGER, XML_SERVER_THREAD),
      new XmlDecl(XmlType.INTEGER, XML_CLIENT_THREAD),
      new XmlDecl(XmlType.LONG, XML_MEMORY_LIMIT),
      new XmlDecl(XmlType.BOOLEAN, XML_CSTRT_USECPULIMIT),
      new XmlDecl(XmlType.BOOLEAN, XML_CSTRT_USECPUJDKLIMIT),
      new XmlDecl(XmlType.DOUBLE, XML_CSTRT_CPULIMIT),
      new XmlDecl(XmlType.INTEGER, XML_CSTRT_CONNLIMIT),
      new XmlDecl(XmlType.DOUBLE, XML_CSTRT_LOWCPULIMIT),
      new XmlDecl(XmlType.DOUBLE, XML_CSTRT_HIGHCPULIMIT),
      new XmlDecl(XmlType.DOUBLE, XML_CSTRT_PERCENTDECREASE),
      new XmlDecl(XmlType.LONG, XML_CSTRT_LIMITLOWBANDWIDTH),
      new XmlDecl(XmlType.LONG, XML_CSTRT_DELAYTHROTTLE),
      new XmlDecl(XmlType.LONG, XML_TIMEOUTCON),
      new XmlDecl(XmlType.LONG, XML_DATA_TIMEOUTCON),
      new XmlDecl(XmlType.BOOLEAN, XML_USENIO),
      new XmlDecl(XmlType.BOOLEAN, XML_USEFASTMD5),
      new XmlDecl(XmlType.STRING, XML_FASTMD5),
      new XmlDecl(XmlType.INTEGER, XML_BLOCKSIZE)
  };

  /**
   * RETRIEVE COMMAND
   */
  public static final String XML_RETRIEVE_COMMAND = "retrievecmd";

  /**
   * STORE COMMAND
   */
  public static final String XML_STORE_COMMAND = "storecmd";

  /**
   * DELAY RETRIEVE COMMAND
   */
  public static final String XML_DELAYRETRIEVE_COMMAND = "retrievedelay";

  /**
   * DELAY STORE COMMAND
   */
  public static final String XML_DELAYSTORE_COMMAND = "storedelay";
  /**
   * Structure of the Configuration file
   */
  private static final XmlDecl[] configExecDecls = {
      // Exec
      new XmlDecl(XmlType.STRING, XML_RETRIEVE_COMMAND),
      new XmlDecl(XmlType.LONG, XML_DELAYRETRIEVE_COMMAND),
      new XmlDecl(XmlType.STRING, XML_STORE_COMMAND),
      new XmlDecl(XmlType.LONG, XML_DELAYSTORE_COMMAND)
  };
  /**
   * Base Directory
   */
  private static final String XML_SERVER_HOME = "serverhome";
  /**
   * Structure of the Configuration file
   */
  private static final XmlDecl[] configDirectoryDecls = {
      // directory
      new XmlDecl(XmlType.STRING, XML_SERVER_HOME)
  };
  /**
   * SERVER SSL STOREKEY PATH
   */
  private static final String XML_PATH_KEYPATH = "keypath";

  /**
   * SERVER SSL KEY PASS
   */
  private static final String XML_PATH_KEYPASS = "keypass";

  /**
   * SERVER SSL STOREKEY PASS
   */
  private static final String XML_PATH_KEYSTOREPASS = "keystorepass";

  /**
   * SERVER SSL TRUSTSTOREKEY PATH
   */
  private static final String XML_PATH_TRUSTKEYPATH = "trustkeypath";

  /**
   * SERVER SSL TRUSTSTOREKEY PASS
   */
  private static final String XML_PATH_TRUSTKEYSTOREPASS = "trustkeystorepass";

  /**
   * SERVER SSL Use TrustStore for Client Authentication
   */
  private static final String XML_USECLIENT_AUTHENT =
      "trustuseclientauthenticate";
  /**
   * SERVER SSL Use Implicit FTPS
   */
  private static final String XML_IMPLICIT_FTPS = "useimplicitftps";
  /**
   * SERVER SSL Use Explicit FTPS
   */
  private static final String XML_EXPLICIT_FTPS = "useexplicitftps";

  /**
   * Structure of the Configuration file
   */
  private static final XmlDecl[] configSslDecls = {
      // ssl
      new XmlDecl(XmlType.STRING, XML_PATH_KEYPATH),
      new XmlDecl(XmlType.STRING, XML_PATH_KEYSTOREPASS),
      new XmlDecl(XmlType.STRING, XML_PATH_KEYPASS),
      new XmlDecl(XmlType.STRING, XML_PATH_TRUSTKEYPATH),
      new XmlDecl(XmlType.STRING, XML_PATH_TRUSTKEYSTOREPASS),
      new XmlDecl(XmlType.BOOLEAN, XML_USECLIENT_AUTHENT),
      new XmlDecl(XmlType.BOOLEAN, XML_IMPLICIT_FTPS),
      new XmlDecl(XmlType.BOOLEAN, XML_EXPLICIT_FTPS)
  };
  /**
   * Overall structure of the Configuration file
   */
  private static final String XML_ROOT = "/config/";
  private static final String XML_IDENTITY = "identity";
  private static final String XML_SERVER = "server";
  private static final String XML_DIRECTORY = "directory";
  private static final String XML_LIMIT = "limit";
  private static final String XML_NETWORK = "network";
  private static final String XML_EXEC = "exec";
  private static final String XML_DB = "db";
  private static final String XML_SSL = "ssl";
  /**
   * Global Structure for Server Configuration
   */
  private static final XmlDecl[] configServer = {
      new XmlDecl(XML_IDENTITY, XmlType.XVAL, XML_ROOT + XML_IDENTITY,
                  configIdentityDecls, false),
      new XmlDecl(XML_SERVER, XmlType.XVAL, XML_ROOT + XML_SERVER,
                  configServerParamDecls, false),
      new XmlDecl(XML_NETWORK, XmlType.XVAL, XML_ROOT + XML_NETWORK,
                  configNetworkServerDecls, false),
      new XmlDecl(XML_EXEC, XmlType.XVAL, XML_ROOT + XML_EXEC, configExecDecls,
                  false),
      new XmlDecl(XML_DIRECTORY, XmlType.XVAL, XML_ROOT + XML_DIRECTORY,
                  configDirectoryDecls, false),
      new XmlDecl(XML_LIMIT, XmlType.XVAL, XML_ROOT + XML_LIMIT,
                  configLimitDecls, false),
      new XmlDecl(XML_DB, XmlType.XVAL, XML_ROOT + XML_DB, configDbDecls,
                  false),
      new XmlDecl(XML_SSL, XmlType.XVAL, XML_ROOT + XML_SSL, configSslDecls,
                  false)
  };

  /**
   * Authentication Fields
   */
  private static final String XML_AUTHENTIFICATION_ROOT = "authent";
  /**
   * Authentication Fields
   */
  private static final String XML_AUTHENTIFICATION_ENTRY = "entry";
  /**
   * Authentication Fields
   */
  private static final String XML_AUTHENTIFICATION_BASED =
      '/' + XML_AUTHENTIFICATION_ROOT + '/' + XML_AUTHENTIFICATION_ENTRY;

  /**
   * Authentication Fields
   */
  private static final String XML_AUTHENTICATION_USER = "user";

  /**
   * Authentication Fields
   */
  private static final String XML_AUTHENTICATION_PASSWD = "passwd";//NOSONAR
  /**
   * Authentication Fields
   */
  private static final String XML_AUTHENTICATION_PASSWDFILE = //NOSONAR
      "passwdfile";//NOSONAR

  /**
   * Authentication Fields
   */
  private static final String XML_AUTHENTICATION_ACCOUNT = "account";

  /**
   * Authentication Fields
   */
  private static final String XML_AUTHENTICATION_ADMIN = "admin";
  /**
   * Structure of the Configuration file
   */
  private static final XmlDecl[] configAuthenticationDecls = {
      // identity
      new XmlDecl(XmlType.STRING, XML_AUTHENTICATION_USER),
      new XmlDecl(XmlType.STRING, XML_AUTHENTICATION_PASSWDFILE),
      new XmlDecl(XmlType.STRING, XML_AUTHENTICATION_PASSWD),
      new XmlDecl(XML_AUTHENTICATION_ACCOUNT, XmlType.STRING,
                  XML_AUTHENTICATION_ACCOUNT, true),
      new XmlDecl(XmlType.BOOLEAN, XML_AUTHENTICATION_ADMIN),
      // Exec
      new XmlDecl(XmlType.STRING, XML_RETRIEVE_COMMAND),
      new XmlDecl(XmlType.LONG, XML_DELAYRETRIEVE_COMMAND),
      new XmlDecl(XmlType.STRING, XML_STORE_COMMAND),
      new XmlDecl(XmlType.LONG, XML_DELAYSTORE_COMMAND)
  };
  /**
   * Global Structure for Server Configuration
   */
  private static final XmlDecl[] authentElements = {
      new XmlDecl(XML_AUTHENTIFICATION_ENTRY, XmlType.XVAL,
                  XML_AUTHENTIFICATION_BASED, configAuthenticationDecls, true)
  };

  /**
   * RANGE of PORT for Passive Mode
   */
  private static final String RANGE_PORT = "FTP_RANGE_PORT";
  /**
   * Use to access directly the configuration
   */
  public static FileBasedConfiguration fileBasedConfiguration;
  /**
   * All authentications
   */
  private final ConcurrentHashMap<String, SimpleAuth> authentications =
      new ConcurrentHashMap<String, SimpleAuth>();

  /**
   * File containing the authentications
   */
  private String authenticationFile;

  /**
   * Default HTTP server port
   */
  private int serverHttpsPort = 8067;
  /**
   * Http Admin base
   */
  private String httpBasePath = "src/main/admin/";
  /**
   * Does this server will try to compress HTTP connections
   */
  private boolean useHttpCompression;

  /**
   * Does this server will use Waarp LocalExec Daemon for Execute
   */
  private boolean useLocalExec;

  /**
   * Crypto Key
   */
  private Des cryptoKey;
  /**
   * Server Administration Key
   */
  private byte[] serverAdminKey;
  /**
   * FTP server ID
   */
  private String hostId = "noId";
  /**
   * Admin name Id
   */
  private String adminName = "noAdmin";
  /**
   * Limit on CPU and Connection
   */
  private FtpConstraintLimitHandler constraintLimitHandler;

  /**
   * List of all Http Channels to enable the close call on them using Netty
   * ChannelGroup
   */
  private ChannelGroup httpChannelGroup;

  /**
   * Server Group for HTTP
   */
  private EventLoopGroup serverGroup;

  /**
   * Worker Group for HTTP
   */
  private EventLoopGroup workerGroup;

  /**
   * ThreadPoolExecutor for Http and Https Server
   */
  private EventExecutorGroup httpExecutor;
  /**
   * Monitoring: snmp configuration file (empty means no snmp support)
   */
  private String snmpConfig;
  /**
   * SNMP Agent (if any)
   */
  private WaarpSnmpAgent agentSnmp;
  /**
   * Associated MIB
   */
  private FtpPrivateMib ftpMib;
  /**
   * Monitoring object
   */
  private FtpMonitoring monitoring;

  /**
   * @param classtype
   * @param businessHandler class that will be used for
   *     BusinessHandler
   * @param dataBusinessHandler class that will be used for
   *     DataBusinessHandler
   * @param fileParameter the FileParameter to use
   */
  public FileBasedConfiguration(final Class<?> classtype,
                                final Class<? extends BusinessHandler> businessHandler,
                                final Class<? extends DataBusinessHandler> dataBusinessHandler,
                                final FileParameterInterface fileParameter) {
    super(classtype, businessHandler, dataBusinessHandler, fileParameter);
    computeNbThreads();
  }

  private static XmlHash hashConfig;

  private boolean loadIdentity() {
    final XmlValue value = hashConfig.get(XML_SERVER_HOSTID);
    if (value != null && !value.isEmpty()) {
      setHostId(value.getString());
    } else {
      logger.error("Unable to find Host ID in Config file");
      return false;
    }
    return setCryptoKey();
  }

  private boolean loadAuthentication() {
    // if no database, must load authentication from file
    final XmlValue value = hashConfig.get(XML_AUTHENTIFICATION_FILE);
    if (value != null && !value.isEmpty()) {
      setAuthenticationFile(value.getString());
      return initializeAuthent(getAuthenticationFile(), false);
    } else {
      logger.warn("Unable to find Authentication file in Config file");
      return false;
    }
  }

  private boolean loadServerParam() {
    XmlValue value = hashConfig.get(XML_USEHTTPCOMP);
    if (value != null && !value.isEmpty()) {
      setUseHttpCompression(value.getBoolean());
    }
    value = hashConfig.get(XML_USELOCALEXEC);
    if (value != null && !value.isEmpty()) {
      setUseLocalExec(value.getBoolean());
      if (isUseLocalExec()) {
        value = hashConfig.get(XML_LEXECADDR);
        final String saddr;
        final InetAddress addr;
        if (value != null && !value.isEmpty()) {
          saddr = value.getString();
          try {
            addr = InetAddress.getByName(saddr);
          } catch (final UnknownHostException e) {
            logger.error(UNABLE_TO_FIND_LOCAL_EXEC_ADDRESS_IN_CONFIG_FILE);
            return false;
          }
        } else {
          logger.warn(UNABLE_TO_FIND_LOCAL_EXEC_ADDRESS_IN_CONFIG_FILE);
          try {
            addr = InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 });
          } catch (final UnknownHostException e) {
            logger.error(UNABLE_TO_FIND_LOCAL_EXEC_ADDRESS_IN_CONFIG_FILE);
            return false;
          }
        }
        value = hashConfig.get(XML_LEXECPORT);
        final int port;
        if (value != null && !value.isEmpty()) {
          port = value.getInteger();
        } else {
          port = 9999;
        }
        LocalExecClient.setAddress(new InetSocketAddress(addr, port));
      }
    }
    value = hashConfig.get(XML_SERVER_ADMIN);
    if (value != null && !value.isEmpty()) {
      setAdminName(value.getString());
    } else {
      logger.error("Unable to find Administrator name in Config file");
      return false;
    }
    if (getCryptoKey() == null && !setCryptoKey()) {
      logger.error("Unable to find Crypto Key in Config file");
      return false;
    }
    final String passwd;
    value = hashConfig.get(XML_SERVER_PASSWD);
    if (value != null && !value.isEmpty()) {
      passwd = value.getString();
    } else {
      logger.error("Unable to find Password in Config file");
      return false;
    }
    final byte[] decodedByteKeys;
    try {
      decodedByteKeys = getCryptoKey().decryptHexInBytes(passwd);
    } catch (final Exception e) {
      logger.error(
          "Unable to Decrypt Server Password in Config file from: " + passwd,
          e);
      return false;
    }
    setSERVERKEY(decodedByteKeys);
    value = hashConfig.get(XML_HTTPADMINPATH);
    if (value == null || value.isEmpty()) {
      logger.error("Unable to find Http Admin Base in Config file");
      return false;
    }
    final String path = value.getString();
    if (path == null || path.length() == 0) {
      logger.warn(
          "Unable to set correct Http Admin Base in Config file. No HTTPS support will be used.");
      setHttpBasePath(null);
    } else {
      final File file = new File(path);
      if (!file.isDirectory()) {
        logger.error("Http Admin is not a directory in Config file");
        return false;
      }
      try {
        setHttpBasePath(AbstractDir.normalizePath(file.getCanonicalPath()) +
                        DirInterface.SEPARATOR);
      } catch (final IOException e1) {
        logger.error("Unable to set Http Admin Path in Config file");
        return false;
      }
    }
    if (getHttpBasePath() != null) {
      // Key for HTTPS
      value = hashConfig.get(XML_PATH_ADMIN_KEYPATH);
      if (value != null && !value.isEmpty()) {
        final String keypath = value.getString();
        if (keypath == null || keypath.length() == 0) {
          logger.error("Bad Key Path");
          return false;
        }
        value = hashConfig.get(XML_PATH_ADMIN_KEYSTOREPASS);
        if (value == null || value.isEmpty()) {
          logger.error("Unable to find KeyStore Passwd");
          return false;
        }
        final String keystorepass = value.getString();
        if (keystorepass == null || keystorepass.length() == 0) {
          logger.error("Bad KeyStore Passwd");
          return false;
        }
        value = hashConfig.get(XML_PATH_ADMIN_KEYPASS);
        if (value == null || value.isEmpty()) {
          logger.error("Unable to find Key Passwd");
          return false;
        }
        final String keypass = value.getString();
        if (keypass == null || keypass.length() == 0) {
          logger.error("Bad Key Passwd");
          return false;
        }
        try {
          HttpSslInitializer.waarpSecureKeyStore =
              new WaarpSecureKeyStore(keypath, keystorepass, keypass);
        } catch (final CryptoException e) {
          logger.error("Bad SecureKeyStore construction for AdminSsl");
          return false;
        }
        // No client authentication
        HttpSslInitializer.waarpSecureKeyStore.initEmptyTrustStore();
        HttpSslInitializer.waarpSslContextFactory =
            new WaarpSslContextFactory(HttpSslInitializer.waarpSecureKeyStore,
                                       true);
      }
    }
    value = hashConfig.get(XML_MONITOR_SNMP_CONFIG);
    if (value != null && !value.isEmpty()) {
      setSnmpConfig(value.getString());
      logger.warn("SNMP configuration file: " + getSnmpConfig());
      final File snmpfile = new File(getSnmpConfig());
      if (snmpfile.canRead()) {
        if (!SnmpConfiguration.setConfigurationFromXml(snmpfile)) {
          logger.warn("Bad SNMP configuration file: " + getSnmpConfig());
          setSnmpConfig(null);
        }
      } else {
        logger.warn("Cannot read SNMP configuration file: " + getSnmpConfig());
        setSnmpConfig(null);
      }
    } else {
      logger.warn("NO SNMP configuration file");
    }
    return true;
  }

  private boolean loadDirectory() {
    final XmlValue value = hashConfig.get(XML_SERVER_HOME);
    if (value == null || value.isEmpty()) {
      logger.error("Unable to find Home in Config file");
      return false;
    }
    final String path = value.getString();
    final File file = new File(path);
    if (!file.isDirectory()) {
      logger.error("Home is not a directory in Config file");
      return false;
    }
    try {
      setBaseDirectory(AbstractDir.normalizePath(file.getCanonicalPath()));
    } catch (final IOException e1) {
      logger.error("Unable to set Home in Config file: " + path);
      return false;
    }
    return true;
  }

  private boolean loadLimit() {
    XmlValue value = hashConfig.get(XML_LIMITGLOBAL);
    if (value != null && !value.isEmpty()) {
      serverGlobalReadLimit = value.getLong();
      if (serverGlobalReadLimit <= 0) {
        serverGlobalReadLimit = 0;
      }
      serverGlobalWriteLimit = serverGlobalReadLimit;
      logger.info("Global Limit: {}", serverGlobalReadLimit);
    }
    value = hashConfig.get(XML_LIMITSESSION);
    if (value != null && !value.isEmpty()) {
      serverChannelReadLimit = value.getLong();
      if (serverChannelReadLimit <= 0) {
        serverChannelReadLimit = 0;
      }
      serverChannelWriteLimit = serverChannelReadLimit;
      logger.info("SessionInterface Limit: {}", serverChannelReadLimit);
    }
    delayLimit = AbstractTrafficShapingHandler.DEFAULT_CHECK_INTERVAL;
    value = hashConfig.get(XML_LIMITDELAY);
    if (value != null && !value.isEmpty()) {
      delayLimit = (value.getLong() / 10) * 10;
      if (delayLimit <= 0) {
        delayLimit = 0;
      }
      logger.info("Delay Limit: {}", delayLimit);
    }
    boolean useCpuLimit = false;
    boolean useCpuLimitJDK = false;
    double cpulimit = 1.0;
    value = hashConfig.get(XML_CSTRT_USECPULIMIT);
    if (value != null && !value.isEmpty()) {
      useCpuLimit = value.getBoolean();
      value = hashConfig.get(XML_CSTRT_USECPUJDKLIMIT);
      if (value != null && !value.isEmpty()) {
        useCpuLimitJDK = value.getBoolean();
      }
      value = hashConfig.get(XML_CSTRT_CPULIMIT);
      if (value != null && !value.isEmpty()) {
        cpulimit = value.getDouble();
      }
    }
    int connlimit = 0;
    value = hashConfig.get(XML_CSTRT_CONNLIMIT);
    if (value != null && !value.isEmpty()) {
      connlimit = value.getInteger();
    }
    double lowcpuLimit = 0;
    double highcpuLimit = 0;
    double percentageDecrease = 0;
    long delay = 1000000;
    long limitLowBandwidth = 4096;
    value = hashConfig.get(XML_CSTRT_LOWCPULIMIT);
    if (value != null && !value.isEmpty()) {
      lowcpuLimit = value.getDouble();
    }
    value = hashConfig.get(XML_CSTRT_HIGHCPULIMIT);
    if (value != null && !value.isEmpty()) {
      highcpuLimit = value.getDouble();
    }
    value = hashConfig.get(XML_CSTRT_PERCENTDECREASE);
    if (value != null && !value.isEmpty()) {
      percentageDecrease = value.getDouble();
    }
    value = hashConfig.get(XML_CSTRT_DELAYTHROTTLE);
    if (value != null && !value.isEmpty()) {
      delay = (value.getLong() / 10) * 10;
    }
    value = hashConfig.get(XML_CSTRT_LIMITLOWBANDWIDTH);
    if (value != null && !value.isEmpty()) {
      limitLowBandwidth = value.getLong();
    }
    value = hashConfig.get(XML_TIMEOUTCON);
    if (value != null && !value.isEmpty()) {
      setTimeoutCon((value.getLong() / 10) * 10);
      value = hashConfig.get(XML_DATA_TIMEOUTCON);
      if (value != null && !value.isEmpty()) {
        setDataTimeoutCon((value.getLong() / 10) * 10);
      } else {
        setDataTimeoutCon(getTimeoutCon());
      }
    } else {
      value = hashConfig.get(XML_DATA_TIMEOUTCON);
      if (value != null && !value.isEmpty()) {
        setDataTimeoutCon((value.getLong() / 10) * 10);
      }
    }
    if (highcpuLimit > 0) {
      setConstraintLimitHandler(
          new FtpConstraintLimitHandler(getTimeoutCon(), useCpuLimit,
                                        useCpuLimitJDK, cpulimit, connlimit,
                                        lowcpuLimit, highcpuLimit,
                                        percentageDecrease, null, delay,
                                        limitLowBandwidth));
    } else {
      setConstraintLimitHandler(
          new FtpConstraintLimitHandler(getTimeoutCon(), useCpuLimit,
                                        useCpuLimitJDK, cpulimit, connlimit));
    }
    value = hashConfig.get(XML_SERVER_THREAD);
    if (value != null && !value.isEmpty()) {
      setServerThread(value.getInteger());
    }
    value = hashConfig.get(XML_CLIENT_THREAD);
    if (value != null && !value.isEmpty()) {
      setClientThread(value.getInteger());
    }
    if (getServerThread() == 0 || getClientThread() == 0) {
      computeNbThreads();
    }
    value = hashConfig.get(XML_MEMORY_LIMIT);
    if (value != null && !value.isEmpty()) {
      long lvalue = value.getLong();
      if (lvalue > Integer.MAX_VALUE) {
        lvalue = Integer.MAX_VALUE;
      }
      setMaxGlobalMemory((int) lvalue);
    }
    ((FilesystemBasedFileParameterImpl) getFileParameter()).deleteOnAbort =
        false;
    value = hashConfig.get(XML_USENIO);
    if (value != null && !value.isEmpty()) {
      FilesystemBasedFileParameterImpl.useNio = value.getBoolean();
    }
    value = hashConfig.get(XML_BLOCKSIZE);
    if (value != null && !value.isEmpty()) {
      setBlocksize(value.getInteger());
    }
    value = hashConfig.get(XML_DELETEONABORT);
    if (value != null && !value.isEmpty()) {
      setDeleteOnAbort(value.getBoolean());
    }
    value = hashConfig.get(XML_ACTIVE_OR_PASSIVE);
    if (value != null && !value.isEmpty()) {
      setActivePassiveMode(value.getInteger());
    }
    // We use Apache Commons IO
    FilesystemBasedDirJdkAbstract.ueApacheCommonsIo = true;
    return true;
  }

  private boolean loadNetworkServer() {
    XmlValue value = hashConfig.get(XML_SERVER_PORT);
    final int port;
    if (value != null && !value.isEmpty()) {
      port = value.getInteger();
    } else {
      port = 21;
    }
    setServerPort(port);
    value = hashConfig.get(XML_SERVER_ADDRESS);
    String address = null;
    if (value != null && !value.isEmpty()) {
      address = value.getString();
    }
    setServerAddress(address);
    int min = 100;
    int max = 65535;
    value = hashConfig.get(XML_RANGE_PORT_MIN);
    if (value != null && !value.isEmpty()) {
      min = value.getInteger();
    }
    value = hashConfig.get(XML_RANGE_PORT_MAX);
    if (value != null && !value.isEmpty()) {
      max = value.getInteger();
    }
    logger.warn("Passive Port range Min: " + min + " Max: " + max);
    final CircularIntValue rangePort = new CircularIntValue(min, max);
    setRangePort(rangePort);
    value = hashConfig.get(XML_SERVER_HTTPS_PORT);
    int httpsport = 8067;
    if (value != null && !value.isEmpty()) {
      httpsport = value.getInteger();
    }
    serverHttpsPort = httpsport;
    return true;
  }

  /**
   * Set the Crypto Key from the Document
   *
   * @return True if OK
   */
  private boolean setCryptoKey() {
    final XmlValue value = hashConfig.get(XML_PATH_CRYPTOKEY);
    if (value == null || value.isEmpty()) {
      logger.error("Unable to find CryptoKey in Config file");
      return false;
    }
    final String filename = value.getString();
    final File key = new File(filename);
    final Des des = new Des();
    try {
      des.setSecretKey(key);
    } catch (final CryptoException e) {
      logger.error("Unable to load CryptoKey from Config file");
      return false;
    } catch (final IOException e) {
      logger.error("Unable to load CryptoKey from Config file");
      return false;
    }
    cryptoKey = des;
    return true;
  }

  /**
   * @return True if the global Exec parameters are correctly loaded
   */
  private boolean loadExec() {
    // Specific Exec command options
    XmlValue value = hashConfig.get(XML_RETRIEVE_COMMAND);
    if (value == null || value.isEmpty()) {
      logger.error("Unable to find Retrieve Command in Config file");
      return false;
    }
    final String retrieve = value.getString();
    value = hashConfig.get(XML_DELAYRETRIEVE_COMMAND);
    long retrievedelay = 0;
    if (value != null && !value.isEmpty()) {
      retrievedelay = (value.getLong() / 10) * 10;
    }
    value = hashConfig.get(XML_STORE_COMMAND);
    if (value == null || value.isEmpty()) {
      logger.error("Unable to find Store Command in Config file");
      return false;
    }
    final String store = value.getString();
    value = hashConfig.get(XML_DELAYSTORE_COMMAND);
    long storedelay = 0;
    if (value != null && !value.isEmpty()) {
      storedelay = (value.getLong() / 10) * 10;
    }
    AbstractExecutor.initializeExecutor(retrieve, retrievedelay, store,
                                        storedelay);
    return true;
  }

  /**
   * Load database parameter
   *
   * @return True if OK
   */
  private boolean loadDatabase() {
    XmlValue value = hashConfig.get(XML_DBDRIVER);
    if (value == null || value.isEmpty()) {
      logger.error("Unable to find DBDriver in Config file");
      DbConstantFtp.gatewayAdmin = new DbAdmin(); // no database support
    } else {
      final String dbdriver = value.getString();
      value = hashConfig.get(XML_DBSERVER);
      if (value == null || value.isEmpty()) {
        logger.error("Unable to find DBServer in Config file");
        return false;
      }
      final String dbserver = value.getString();
      value = hashConfig.get(XML_DBUSER);
      if (value == null || value.isEmpty()) {
        logger.error("Unable to find DBUser in Config file");
        return false;
      }
      final String dbuser = value.getString();
      value = hashConfig.get(XML_DBPASSWD);
      if (value == null || value.isEmpty()) {
        logger.error("Unable to find DBPassword in Config file");
        return false;
      }
      final String dbpasswd = value.getString();
      if (dbdriver == null || dbserver == null || dbuser == null ||
          dbpasswd == null || dbdriver.length() == 0 ||
          dbserver.length() == 0 || dbuser.length() == 0 ||
          dbpasswd.length() == 0) {
        logger.error("Unable to find Correct DB data in Config file");
        return false;
      }
      try {
        DbConstantFtp.gatewayAdmin =
            DbModelFactoryFtp.initialize(dbdriver, dbserver, dbuser, dbpasswd,
                                         true);
        org.waarp.common.database.DbConstant.admin = DbConstantFtp.gatewayAdmin;
      } catch (final WaarpDatabaseNoConnectionException e2) {
        logger.error("Unable to Connect to DB", e2);
        return false;
      }
    }
    return true;
  }

  protected final boolean loadSsl() {
    // StoreKey for Server
    XmlValue value = hashConfig.get(XML_PATH_KEYPATH);
    if (value == null || value.isEmpty()) {
      logger.info("Unable to find Key Path");
      getFtpInternalConfiguration().setUsingNativeSsl(false);
      getFtpInternalConfiguration().setAcceptAuthProt(false);
      return true;
    } else {
      final String keypath = value.getString();
      if (keypath == null || keypath.length() == 0) {
        logger.error("Bad Key Path");
        return false;
      }
      value = hashConfig.get(XML_PATH_KEYSTOREPASS);
      if (value == null || value.isEmpty()) {
        logger.error("Unable to find KeyStore Passwd");
        return false;
      }
      final String keystorepass = value.getString();
      if (keystorepass == null || keystorepass.length() == 0) {
        logger.error("Bad KeyStore Passwd");
        return false;
      }
      value = hashConfig.get(XML_PATH_KEYPASS);
      if (value == null || value.isEmpty()) {
        logger.error("Unable to find Key Passwd");
        return false;
      }
      final String keypass = value.getString();
      if (keypass == null || keypass.length() == 0) {
        logger.error("Bad Key Passwd");
        return false;
      }
      try {
        FtpsInitializer.waarpSecureKeyStore =
            new WaarpSecureKeyStore(keypath, keystorepass, keypass);
      } catch (final CryptoException e) {
        logger.error("Bad SecureKeyStore construction");
        return false;
      }

    }
    // TrustedKey for OpenR66 server
    value = hashConfig.get(XML_PATH_TRUSTKEYPATH);
    if (value == null || value.isEmpty()) {
      logger.info("Unable to find TRUST Key Path");
      FtpsInitializer.waarpSecureKeyStore.initEmptyTrustStore();
    } else {
      final String keypath = value.getString();
      if (keypath == null || keypath.length() == 0) {
        logger.error("Bad TRUST Key Path");
        return false;
      }
      value = hashConfig.get(XML_PATH_TRUSTKEYSTOREPASS);
      if (value == null || value.isEmpty()) {
        logger.error("Unable to find TRUST KeyStore Passwd");
        return false;
      }
      final String keystorepass = value.getString();
      if (keystorepass == null || keystorepass.length() == 0) {
        logger.error("Bad TRUST KeyStore Passwd");
        return false;
      }
      boolean useClientAuthent = false;
      value = hashConfig.get(XML_USECLIENT_AUTHENT);
      if (value != null && !value.isEmpty()) {
        useClientAuthent = value.getBoolean();
      }
      try {
        FtpsInitializer.waarpSecureKeyStore.initTrustStore(keypath,
                                                           keystorepass,
                                                           useClientAuthent);
      } catch (final CryptoException e) {
        logger.error("Bad TrustKeyStore construction");
        return false;
      }
    }
    FtpsInitializer.waarpSslContextFactory =
        new WaarpSslContextFactory(FtpsInitializer.waarpSecureKeyStore);
    boolean useImplicit = false;
    value = hashConfig.get(XML_IMPLICIT_FTPS);
    if (value != null && !value.isEmpty()) {
      useImplicit = value.getBoolean();
    }
    boolean useExplicit = false;
    value = hashConfig.get(XML_EXPLICIT_FTPS);
    if (value != null && !value.isEmpty()) {
      useExplicit = value.getBoolean();
    }
    if (useImplicit && useExplicit) {
      logger.error("Only one of IMPLICIT or EXPLICIT could be True");
      return false;
    }
    if (!useImplicit && !useExplicit) {
      logger.error(
          "Since all SecureStore are specified, one of IMPLICIT or EXPLICIT should be True");
      logger.warn("FTPS support will be ignored...");
      getFtpInternalConfiguration().setUsingNativeSsl(false);
      getFtpInternalConfiguration().setAcceptAuthProt(false);
      return true;
    }
    getFtpInternalConfiguration().setUsingNativeSsl(useImplicit);
    getFtpInternalConfiguration().setAcceptAuthProt(useExplicit);
    return true;
  }

  /**
   * Initiate the configuration from the xml file for server
   *
   * @param filename
   *
   * @return True if OK
   */
  public final boolean setConfigurationServerFromXml(final String filename) {
    final Document document;
    // Open config file
    try {
      document = XmlUtil.getNewSaxReader().read(filename);
    } catch (final DocumentException e) {
      logger.error("Unable to read the XML Config file: " + filename + ": {}",
                   e.getMessage());
      return false;
    }
    if (document == null) {
      logger.error("Unable to read the XML Config file: " + filename);
      return false;
    }
    XmlValue[] configuration = XmlUtil.read(document, configServer);
    hashConfig = new XmlHash(configuration);
    // Now read the configuration
    if (!loadIdentity()) {
      logger.error("Cannot load Identity");
      return false;
    }
    if (!loadDatabase()) {
      logger.error("Cannot load Database configuration");
      return false;
    }
    if (!loadServerParam()) {
      logger.error("Cannot load Server Parameters");
      return false;
    }
    if (!loadDirectory()) {
      logger.error("Cannot load Directory configuration");
      return false;
    }
    if (!loadLimit()) {
      logger.error("Cannot load Limit configuration");
      return false;
    }
    if (!loadNetworkServer()) {
      logger.error("Cannot load Network configuration");
      return false;
    }
    if (!loadExec()) {
      logger.error("Cannot load Exec configuration");
      return false;
    }
    // if no database, must load authentication from file
    if (!loadAuthentication()) {
      logger.error("Cannot load Authentication configuration");
      return false;
    }
    if (!loadSsl()) {
      // ignore and continue => No SSL
      getFtpInternalConfiguration().setUsingNativeSsl(false);
      getFtpInternalConfiguration().setAcceptAuthProt(false);
    }
    hashConfig.clear();
    hashConfig = null;
    configuration = null;
    logger.debug("File based configuration loaded");
    return true;
  }

  /**
   * Configure HTTPS
   */
  public final void configureHttps() {
    logger.debug("Start HTTPS");
    // Now start the HTTPS support
    // Configure the server.
    /*
     * Bootstrap for Https server
     */
    final ServerBootstrap httpsBootstrap = new ServerBootstrap();
    httpExecutor = new NioEventLoopGroup(getServerThread() * 10,
                                         new WaarpThreadFactory(
                                             "HttpExecutor"));
    serverGroup = new NioEventLoopGroup(getServerThread(),
                                        new WaarpThreadFactory("HTTP_Server"));
    workerGroup = new NioEventLoopGroup(getServerThread() * 10,
                                        new WaarpThreadFactory("HTTP_Worker"));
    WaarpNettyUtil.setServerBootstrap(httpsBootstrap, serverGroup, workerGroup,
                                      (int) getTimeoutCon());

    // Configure the pipeline factory.
    httpsBootstrap.childHandler(new HttpSslInitializer(isUseHttpCompression()));
    httpChannelGroup =
        new DefaultChannelGroup("HttpOpenR66", httpExecutor.next());

    // Bind and start to accept incoming connections.
    logger.warn("Start Https Support on port: " + serverHttpsPort + " with " +
                (isUseHttpCompression()? "" : "no") + " compression support");
    final ChannelFuture future =
        httpsBootstrap.bind(new InetSocketAddress(serverHttpsPort));
    if (WaarpNettyUtil.awaitIsSuccessOfInterrupted(future)) {
      httpChannelGroup.add(future.channel());
    }
  }

  /**
   * Configure ConstraintLimitHandler
   */
  public final void configureConstraint() {
    logger.debug("Configure constraints");
    getConstraintLimitHandler().setHandler(
        getFtpInternalConfiguration().getGlobalTrafficShapingHandler());
  }

  /**
   * Configure LocalExec
   */
  public final void configureLExec() {
    if (isUseLocalExec()) {
      logger.debug("Start LExec");
      LocalExecClient.initialize(getClientThread(), getMaxGlobalMemory());
    }
  }

  /**
   * Configure the SNMP support if needed
   *
   * @throws FtpNoConnectionException
   */
  public final void configureSnmp() throws FtpNoConnectionException {
    logger.debug("Start SNMP");
    setMonitoring(new FtpMonitoring(null));
    if (getSnmpConfig() != null) {
      final int snmpPortShow = getServerPort();
      setFtpMib(new FtpPrivateMib(snmpPortShow));
      WaarpMOFactory.setFactory(new FtpVariableFactory());
      setAgentSnmp(
          new WaarpSnmpAgent(new File(getSnmpConfig()), getMonitoring(),
                             getFtpMib()));
      try {
        getAgentSnmp().start();
        logger.debug("SNMP configured");
      } catch (final IOException e) {
        getMonitoring().releaseResources();
        setMonitoring(null);
        setFtpMib(null);
        setAgentSnmp(null);
        throw new FtpNoConnectionException("AgentSnmp Error while starting", e);
      }
    }
  }

  /**
   * @param serverkey the SERVERADMINKEY to set
   */
  public final void setSERVERKEY(final byte[] serverkey) {
    serverAdminKey = serverkey;
  }

  /**
   * Check the password for Shutdown
   *
   * @param password
   *
   * @return True if the password is OK
   */
  @Override
  public final boolean checkPassword(final String password) {
    if (password == null) {
      return false;
    }
    return Arrays.equals(serverAdminKey,
                         password.getBytes(WaarpStringUtils.UTF8));
  }

  /**
   * Initialize Authentication from current authenticationFile
   *
   * @param filename the filename from which authentication will be
   *     loaded
   * @param purge if True, the current authentications are totally
   *     replaced
   *     by the new ones
   *
   * @return True if OK
   */
  @SuppressWarnings("unchecked")
  public final boolean initializeAuthent(final String filename,
                                         final boolean purge) {
    logger.debug("Load authent");
    final Document document;
    try {
      document = XmlUtil.getNewSaxReader().read(filename);
    } catch (final DocumentException e) {
      logger.error(
          "Unable to read the XML Authentication file: " + filename + ": {}",
          e.getMessage());
      return false;
    }
    if (document == null) {
      logger.error("Unable to read the XML Authentication file: " + filename);
      return false;
    }
    final XmlValue[] configurationXml = XmlUtil.read(document, authentElements);
    XmlHash hashConfigXml = new XmlHash(configurationXml);

    XmlValue value = hashConfigXml.get(XML_AUTHENTIFICATION_ENTRY);
    final List<XmlValue[]> list = (List<XmlValue[]>) value.getList();
    final ConcurrentHashMap<String, SimpleAuth> newAuthents =
        new ConcurrentHashMap<String, SimpleAuth>();
    for (final XmlValue[] xmlValues : list) {
      hashConfigXml = new XmlHash(xmlValues);
      value = hashConfigXml.get(XML_AUTHENTICATION_USER);
      if (value == null || value.isEmpty()) {
        logger.error("Unable to find a User in Config file");
        continue;
      }
      final String user = value.getString();
      value = hashConfigXml.get(XML_AUTHENTICATION_ACCOUNT);
      if (value == null || value.isEmpty()) {
        logger.error("Unable to find a Account in Config file: " + user);
        continue;
      }
      final String[] account;
      final List<String> listaccount = (List<String>) value.getList();
      if (!listaccount.isEmpty()) {
        account = new String[listaccount.size()];
        int i = 0;
        for (final String s : listaccount) {
          account[i] = s;
          final File directory =
              new File(getBaseDirectory() + '/' + user + '/' + account[i]);
          directory.mkdirs();//NOSONAR
          i++;
        }
      } else {
        logger.error("Unable to find a Account in Config file: " + user);
        continue;
      }
      value = hashConfigXml.get(XML_AUTHENTICATION_ADMIN);
      boolean isAdmin = false;
      if (value != null && !value.isEmpty()) {
        isAdmin = value.getBoolean();
      }
      String retrcmd = null;
      long retrdelay = 0;
      String storcmd = null;
      long stordelay = 0;
      value = hashConfigXml.get(XML_RETRIEVE_COMMAND);
      if (value != null && !value.isEmpty()) {
        retrcmd = value.getString();
      }
      value = hashConfigXml.get(XML_DELAYRETRIEVE_COMMAND);
      if (value != null && !value.isEmpty()) {
        retrdelay = (value.getLong() / 10) * 10;
      }
      value = hashConfigXml.get(XML_STORE_COMMAND);
      if (value != null && !value.isEmpty()) {
        storcmd = value.getString();
      }
      value = hashConfigXml.get(XML_DELAYSTORE_COMMAND);
      if (value != null && !value.isEmpty()) {
        stordelay = (value.getLong() / 10) * 10;
      }
      final String passwd;
      value = hashConfigXml.get(XML_AUTHENTICATION_PASSWDFILE);
      if (value != null && !value.isEmpty()) {
        // load key from file
        final File key = new File(value.getString());
        if (!key.canRead()) {
          logger.error(
              "Cannot read key for user " + user + ':' + key.getName());
          continue;
        }
        try {
          final byte[] byteKeys = getCryptoKey().decryptHexFile(key);
          passwd = new String(byteKeys, WaarpStringUtils.UTF8);
        } catch (final Exception e2) {
          logger.error("Cannot read key for user " + user, e2);
          continue;
        }
      } else {
        value = hashConfigXml.get(XML_AUTHENTICATION_PASSWD);
        if (value != null && !value.isEmpty()) {
          final String encrypted = value.getString();
          final byte[] byteKeys;
          try {
            byteKeys = getCryptoKey().decryptHexInBytes(encrypted);
            passwd = new String(byteKeys, WaarpStringUtils.UTF8);
          } catch (final Exception e) {
            logger.error("Unable to Decrypt Key for user " + user, e);
            continue;
          }
        } else {
          logger.error("Unable to find Password in Config file");
          // DO NOT Allow empty key
          continue;
        }
      }
      final SimpleAuth auth =
          new SimpleAuth(user, passwd, account, storcmd, stordelay, retrcmd,
                         retrdelay);
      auth.setAdmin(isAdmin);
      newAuthents.put(user, auth);
      hashConfigXml.clear();
    }
    hashConfigXml.clear();
    if (purge) {
      authentications.clear();
    }
    authentications.putAll(newAuthents);
    newAuthents.clear();
    return true;
  }

  /**
   * Export the Authentication to the original files
   *
   * @param filename the filename where the authentication will be
   *     exported
   *
   * @return True if successful
   */
  public final boolean saveAuthenticationFile(final String filename) {
    final Document document = XmlUtil.createEmptyDocument();
    final XmlValue[] roots = new XmlValue[1];
    final XmlValue root = new XmlValue(authentElements[0]);
    roots[0] = root;
    final Enumeration<SimpleAuth> auths = authentications.elements();
    while (auths.hasMoreElements()) {
      final SimpleAuth auth = auths.nextElement();
      final XmlValue[] values = new XmlValue[configAuthenticationDecls.length];
      for (int i = 0; i < configAuthenticationDecls.length; i++) {
        values[i] = new XmlValue(configAuthenticationDecls[i]);
      }
      try {
        values[0].setFromString(auth.getUser());
        // PasswdFile: none values[1].setFromString()
        values[2].setFromString(auth.getPassword());
        // Accounts
        final String[] accts = auth.getAccounts();
        for (final String string : accts) {
          values[3].addFromString(string);
        }
        values[4].setValue(auth.isAdmin());
        values[5].setFromString(auth.getRetrCmd());
        values[6].setValue(auth.getRetrDelay());
        values[7].setFromString(auth.getStorCmd());
        values[8].setValue(auth.getStorDelay());
      } catch (final InvalidArgumentException e1) {
        logger.error(ERROR_DURING_WRITE_AUTHENTICATION_FILE, e1);
        return false;
      } catch (final InvalidObjectException e) {
        logger.error(ERROR_DURING_WRITE_AUTHENTICATION_FILE, e);
        return false;
      }
      try {
        root.addValue(values);
      } catch (final InvalidObjectException e) {
        logger.error(ERROR_DURING_WRITE_AUTHENTICATION_FILE, e);
        return false;
      }
    }
    XmlUtil.write(document, roots);
    try {
      XmlUtil.saveDocument(filename, document);
    } catch (final IOException e1) {
      logger.error("Cannot write to file: " + filename + " since {}",
                   e1.getMessage());
      return false;
    }
    return true;
  }

  /**
   * @param user
   *
   * @return the SimpleAuth if any for this user
   */
  public final SimpleAuth getSimpleAuth(final String user) {
    return authentications.get(user);
  }

  /**
   * @param format Format in HTML to use as ouput format
   *
   * @return the Html String containing the table of all Authentication
   *     entries
   */
  public final String getHtmlAuth(final String format) {
    final String result;
    final StringBuilder builder = new StringBuilder();
    /*
     * XXXUSERXXX XXXPWDXXX XXXACTSXXX XXXADMXXX XXXSTCXXX XXXSTDXXX XXXRTCXXX XXXRTDXXX
     */
    final Enumeration<SimpleAuth> simpleAuths = authentications.elements();
    SimpleAuth auth;
    while (simpleAuths.hasMoreElements()) {
      auth = simpleAuths.nextElement();
      String newElt = format.replace("XXXUSERXXX", auth.getUser());
      newElt = newElt.replace("XXXPWDXXX", auth.getPassword());
      if (auth.getStorCmd() != null) {
        newElt = newElt.replace("XXXSTCXXX", auth.getStorCmd());
      } else {
        newElt = newElt.replace("XXXSTCXXX", "");
      }
      if (auth.getRetrCmd() != null) {
        newElt = newElt.replace("XXXRTCXXX", auth.getRetrCmd());
      } else {
        newElt = newElt.replace("XXXRTCXXX", "");
      }
      newElt = newElt.replace("XXXSTDXXX", Long.toString(auth.getStorDelay()));
      newElt = newElt.replace("XXXRTDXXX", Long.toString(auth.getRetrDelay()));
      newElt = newElt.replace("XXXADMXXX", Boolean.toString(auth.isAdmin()));
      if (auth.getAccounts() != null) {
        final StringBuilder accts = new StringBuilder();
        for (int i = 0; i < auth.getAccounts().length - 1; i++) {
          accts.append(auth.getAccounts()[i]).append(", ");
        }
        accts.append(auth.getAccounts()[auth.getAccounts().length - 1]);
        newElt = newElt.replace("XXXACTSXXX", accts.toString());
      } else {
        newElt = newElt.replace("XXXACTSXXX", "No Account");
      }
      builder.append(newElt);
    }
    result = builder.toString();
    return result;
  }

  /**
   * Only available with Database support for Waarp
   *
   * @param format Format in HTML to use as ouput format
   * @param limit number of TransferLog to populate
   *
   * @return the Html String containing the table of all Transfer entries
   */
  public final String getHtmlTransfer(final String format, final int limit) {
    final String result;
    final StringBuilder builder = new StringBuilder();
    /*
     * XXXIDXXX XXXUSERXXX XXXACCTXXX XXXFILEXXX XXXMODEXXX XXXSTATUSXXX XXXINFOXXX XXXUPINFXXX XXXSTARTXXX
     * XXXSTOPXXX
     */
    DbPreparedStatement preparedStatement = null;
    try {
      try {
        preparedStatement = DbTransferLog.getStatusPrepareStament(
            DbConstantFtp.gatewayAdmin.getSession(), null, limit);
        preparedStatement.executeQuery();
      } catch (final WaarpDatabaseNoConnectionException e) {
        return "";
      } catch (final WaarpDatabaseSqlException e) {
        return "";
      }
      try {
        while (preparedStatement.getNext()) {
          final DbTransferLog log =
              DbTransferLog.getFromStatement(preparedStatement);
          String newElt =
              format.replace("XXXIDXXX", Long.toString(log.getSpecialId()));
          newElt = newElt.replace("XXXUSERXXX", log.getUser());
          newElt = newElt.replace("XXXACCTXXX", log.getAccount());
          newElt = newElt.replace("XXXFILEXXX", log.getFilename());
          newElt = newElt.replace("XXXMODEXXX", log.getMode());
          newElt = newElt.replace("XXXSTATUSXXX", log.getErrorInfo().getMesg());
          newElt = newElt.replace("XXXINFOXXX", log.getInfotransf());
          newElt = newElt.replace("XXXUPINFXXX", log.getUpdatedInfo().name());
          newElt = newElt.replace("XXXSTARTXXX", log.getStart().toString());
          newElt = newElt.replace("XXXSTOPXXX", log.getStop().toString());
          builder.append(newElt);
        }
      } catch (final WaarpDatabaseNoConnectionException e) {
        return "";
      } catch (final WaarpDatabaseSqlException e) {
        return "";
      }
      result = builder.toString();
      return result;
    } finally {
      if (preparedStatement != null) {
        preparedStatement.realClose();
      }
    }
  }

  /**
   * @see FtpConfiguration#getNextRangePort()
   */
  @Override
  public final int getNextRangePort() {
    try {
      return ((CircularIntValue) getProperty(RANGE_PORT)).getNext();
    } catch (final FtpUnknownFieldException e) {
      return -1;
    }
  }

  /**
   * @param rangePort the range of available ports for Passive
   *     connections
   */
  private void setRangePort(final CircularIntValue rangePort) {
    setProperty(RANGE_PORT, rangePort);
  }

  /**
   * @return the httpPipelineExecutor
   */
  public final EventExecutorGroup getHttpPipelineExecutor() {
    return httpExecutor;
  }

  /**
   * @return the httpChannelGroup
   */
  public final ChannelGroup getHttpChannelGroup() {
    return httpChannelGroup;
  }

  /**
   * Finalize resources attached to handlers
   */
  private static class GgChannelGroupFutureListener
      implements ChannelGroupFutureListener {
    final EventExecutorGroup executorWorker;
    final String name;

    private GgChannelGroupFutureListener(final String name,
                                         final EventExecutorGroup executorWorker) {
      this.name = name;
      this.executorWorker = executorWorker;
    }

    @Override
    public final void operationComplete(final ChannelGroupFuture future) {
      if (executorWorker != null) {
        executorWorker.shutdownGracefully();
      }
      logger.info("Done with shutdown {}", name);
    }
  }

  @Override
  public final void releaseResources() {
    logger.debug("Release resources");
    super.releaseResources();
    if (httpChannelGroup != null) {
      final int result = httpChannelGroup.size();
      logger.debug("HttpChannelGroup: {}", result);
      httpChannelGroup.close().addListener(
          new GgChannelGroupFutureListener("HttpChannelGroup", workerGroup));
    }
    if (httpExecutor != null) {
      httpExecutor.shutdownGracefully();
    }
    if (isUseLocalExec()) {
      LocalExecClient.releaseResources();
    }
    if (getConstraintLimitHandler() != null) {
      getConstraintLimitHandler().release();
    }
    if (getAgentSnmp() != null) {
      getAgentSnmp().stop();
    }
    if (workerGroup != null) {
      workerGroup.shutdownGracefully();
    }
    if (serverGroup != null) {
      serverGroup.shutdownGracefully();
    }
    DbAdmin.closeAllConnection();
  }

  @Override
  public final void inShutdownProcess() {
    if (getFtpMib() != null) {
      getFtpMib().notifyStartStop("Shutdown in progress for " + getHostId(),
                                  "Gives extra seconds: " + getTimeoutCon());
    }
  }

  /**
   * @return the authenticationFile
   */
  public final String getAuthenticationFile() {
    return authenticationFile;
  }

  /**
   * @param authenticationFile the authenticationFile to set
   */
  public final void setAuthenticationFile(final String authenticationFile) {
    this.authenticationFile = authenticationFile;
  }

  /**
   * @return the httpBasePath
   */
  public final String getHttpBasePath() {
    return httpBasePath;
  }

  /**
   * @param httpBasePath the httpBasePath to set
   */
  public final void setHttpBasePath(final String httpBasePath) {
    this.httpBasePath = httpBasePath;
  }

  /**
   * @return the useHttpCompression
   */
  public final boolean isUseHttpCompression() {
    return useHttpCompression;
  }

  /**
   * @param useHttpCompression the useHttpCompression to set
   */
  public final void setUseHttpCompression(final boolean useHttpCompression) {
    this.useHttpCompression = useHttpCompression;
  }

  /**
   * @return the useLocalExec
   */
  public final boolean isUseLocalExec() {
    return useLocalExec;
  }

  /**
   * @param useLocalExec the useLocalExec to set
   */
  public final void setUseLocalExec(final boolean useLocalExec) {
    this.useLocalExec = useLocalExec;
  }

  /**
   * @return the cryptoKey
   */
  public final Des getCryptoKey() {
    return cryptoKey;
  }

  /**
   * @param cryptoKey the cryptoKey to set
   */
  public final void setCryptoKey(final Des cryptoKey) {
    this.cryptoKey = cryptoKey;
  }

  /**
   * @return the hostId
   */
  public final String getHostId() {
    return hostId;
  }

  /**
   * @param hostId the hostId to set
   */
  public final void setHostId(final String hostId) {
    this.hostId = hostId;
  }

  /**
   * @return the adminName
   */
  public final String getAdminName() {
    return adminName;
  }

  /**
   * @param adminName the adminName to set
   */
  public final void setAdminName(final String adminName) {
    this.adminName = adminName;
  }

  /**
   * @return the constraintLimitHandler
   */
  public final FtpConstraintLimitHandler getConstraintLimitHandler() {
    return constraintLimitHandler;
  }

  /**
   * @param constraintLimitHandler the constraintLimitHandler to set
   */
  public final void setConstraintLimitHandler(
      final FtpConstraintLimitHandler constraintLimitHandler) {
    this.constraintLimitHandler = constraintLimitHandler;
  }

  /**
   * @return the snmpConfig
   */
  public final String getSnmpConfig() {
    return snmpConfig;
  }

  /**
   * @param snmpConfig the snmpConfig to set
   */
  public final void setSnmpConfig(final String snmpConfig) {
    this.snmpConfig = snmpConfig;
  }

  /**
   * @return the agentSnmp
   */
  public final WaarpSnmpAgent getAgentSnmp() {
    return agentSnmp;
  }

  /**
   * @param agentSnmp the agentSnmp to set
   */
  public final void setAgentSnmp(final WaarpSnmpAgent agentSnmp) {
    this.agentSnmp = agentSnmp;
  }

  /**
   * @return the ftpMib
   */
  public final FtpPrivateMib getFtpMib() {
    return ftpMib;
  }

  /**
   * @param ftpMib the ftpMib to set
   */
  public final void setFtpMib(final FtpPrivateMib ftpMib) {
    this.ftpMib = ftpMib;
  }

  /**
   * @return the monitoring
   */
  public final FtpMonitoring getMonitoring() {
    return monitoring;
  }

  /**
   * @param monitoring the monitoring to set
   */
  public final void setMonitoring(final FtpMonitoring monitoring) {
    this.monitoring = monitoring;
  }
}