/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
 * Copyright 2011 Pierre Ossman <ossman@cendio.se> for Cendio AB
 * Copyright 2012 Samuel Mannehed <samuel@cendio.se> for Cendio AB
 * Copyright (C) 2014-2025 m-privacy GmbH
 * 
 * This 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 2 of the License, or
 * (at your option) any later version.
 * 
 * This software 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 this software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef HAVE_GNUTLS
#include <rfb/CSecurityTLS.h>
#endif

#include <rfb/CSecurityKrb.h>
#include <rdr/FdInStream.h>

#include <rfb/MP.h>

#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#include <tchar.h>
#endif

#include "parameters.h"

#include <os/os.h>
#include <rfb/Exception.h>
#include <rfb/LogWriter.h>
#include <rfb/SecurityClient.h>

#include <FL/fl_utf8.h>

#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <assert.h>

#include "i18n.h"

#include <tgpro_environment.h>
#include <mp_utils.h>

using namespace rfb;
using namespace std;

static LogWriter vlog("Parameters");


IntParameter pointerEventInterval("PointerEventInterval",
                                  "Time in milliseconds to rate-limit"
                                  " successive pointer events", 17);
BoolParameter emulateMiddleButton("EmulateMiddleButton",
                                  "Emulate middle mouse button by pressing "
                                  "left and right mouse buttons simultaneously",
                                  false);
BoolParameter dotWhenNoCursor("DotWhenNoCursor",
                              "Show the dot cursor when the server sends an "
                              "invisible cursor", false);

BoolParameter alertOnFatalError("AlertOnFatalError",
                                "Give a dialog on connection problems rather "
                                "than exiting immediately", true);

StringParameter passwordFile("PasswordFile",
                             "Password file for VNC authentication", "");
AliasParameter passwd("passwd", "Alias for PasswordFile", &passwordFile);

BoolParameter autoSelect("AutoSelect",
                         "Auto select pixel format and encoding. "
                         "Default if PreferredEncoding and FullColor are not specified.", 
                         false);
BoolParameter fullColour("FullColor",
                         "Use full color", true);
AliasParameter fullColourAlias("FullColour", "Alias for FullColor", &fullColour);
IntParameter lowColourLevel("LowColorLevel",
                            "Color level to use on slow connections. "
                            "0 = Very Low, 1 = Low, 2 = Medium", 2, 0, 2);
AliasParameter lowColourLevelAlias("LowColourLevel", "Alias for LowColorLevel", &lowColourLevel);
StringParameter preferredEncoding("PreferredEncoding",
                                  "Preferred encoding to use (Tight, ZRLE, Hextile or"
                                  " Raw)", "Tight");
BoolParameter customCompressLevel("CustomCompressLevel",
                                  "Use custom compression level. "
                                  "Default if CompressLevel is specified.", false);
IntParameter compressLevel("CompressLevel",
                           "Use specified compression level 0 = Low, 9 = High",
                           2, 0, 6);
BoolParameter noJpeg("NoJPEG",
                     "Disable lossy JPEG compression in Tight encoding.",
                     false);
IntParameter qualityLevel("QualityLevel",
                          "JPEG quality level. 0 = Low, 9 = High",
                          8, 0, 9);
BoolParameter noMP("NoMP",
                     "Disable MP compression in Tight encoding.",
                     false);
IntParameter mpLevel("MPLevel",
                          "MP quality level, only used in some compression types. 0 = Low, 9 = High",
                          9, 0, 9);

IntParameter mpCompression("MpCompression",
                     "Set MP compression type, 0 = AUTO, 10 = H.264, 11 = JPEG, 12 = RAW, 13 = I420, 15 = ZSTD, 16 = I420+ZSTD, 18 = JPEGLB, 21 = WEBXL, 22 = WEBXLLB, 23 = WEBXLXLB",
                     MP_COMPRESSION_DEFAULT, MP_COMPRESSION_MIN, MP_COMPRESSION_MAX);

BoolParameter maximize("Maximize", "Maximize viewer window", false);
BoolParameter fullScreen("FullScreen", "Full screen mode", false);
BoolParameter fullScreenAllMonitors("FullScreenAllMonitors",
                                    "Enable full screen over all monitors",
                                    true);
StringParameter desktopSize("DesktopSize",
                            "Reconfigure desktop size on the server on "
                            "connect (if possible)", "");
StringParameter geometry("geometry",
                         "Specify size and position of viewer window", "");

BoolParameter listenMode("listen", "Listen for connections from VNC servers", false);

BoolParameter remoteResize("RemoteResize",
                           "Dynamically resize the remote desktop size as "
                           "the size of the local client window changes. "
                           "(Does not work with all servers)", true);

BoolParameter viewOnly("ViewOnly",
                       "Don't send any mouse or keyboard events to the server",
                       false);
BoolParameter shared("Shared",
                     "Don't disconnect other viewers upon connection - "
                     "share the desktop instead",
                     false);

BoolParameter acceptClipboard("AcceptClipboard",
                              "Accept clipboard changes from the server",
                              true);
BoolParameter sendClipboard("SendClipboard",
                            "Send clipboard changes to the server", true);
BoolParameter enforceClipboard("EnforceClipboard",
                            "Disable clipboard setting changes in F8 menu", false);
#if !defined(WIN32) && !defined(__APPLE__)
BoolParameter setPrimary("SetPrimary",
                         "Set the primary selection as well as the "
                         "clipboard selection", true);
BoolParameter sendPrimary("SendPrimary",
                          "Send the primary selection to the "
                          "server as well as the clipboard selection",
                          true);
StringParameter display("display",
			"Specifies the X display on which the VNC viewer window should appear.",
			"");
#endif

StringParameter menuKey("MenuKey", "The key which brings up the popup menu",
                        "F8");

StringParameter webcamName("WebcamName", "The name of selected webcam device",
                        "not selected");
IntParameter webcamSelect("webcamSelect", "The number of selected webcam device",
                        0);
BoolParameter webcamEnabled("webcamEnabled", "Enable/Disable webcam stream",
                        false);
#if 0
StringParameter micName("MicName", "The name of selected microphone device",
                        "not selected");
IntParameter micSelect("MicSelect", "The number of selected microphone device",
                        0);
BoolParameter micEnabled("MicEnabled", "Enable/Disable microphone with webcam stream",
                        false);
#endif
StringParameter webcamSize("WebcamSize", "Size of webcam video stream", "640x360", rfb::ConfViewer);
IntParameter webcamSizeSelect("WebcamSizeSelect", "The number of selected webcam size",
                        0);

BoolParameter fullscreenSystemKeys("FullscreenSystemKeys",
                                   "Pass special keys (like Alt+Tab) directly "
                                   "to the server when in full screen mode.",
                                   true);

#ifndef WIN32
StringParameter via("via", "Gateway to tunnel via", "");
#endif

StringParameter vendorName("VendorName", "Name of this software's producer.", "m-privacy GmbH");

StringParameter productName("ProductName", "Name of this software.", "TightGate-Pro");

StringParameter configDir("ConfigDir", "Where the certificates and tgpro.vnc hides.", "");

StringParameter configFile("ConfigFile",
                           "Configuration file for VNC viewer, read-only", "");
AliasParameter config("config", "Alias for ConfigFile", &configFile);

StringParameter rwConfigFile("RWConfigFile",
                             "Configuration file for VNC viewer, readed at start-up and written at program quit", "");
AliasParameter rwConfig("rwConfig", "Alias for RWConfigFile", &rwConfigFile);

BoolParameter noConfigFile("NoConfigFile",
                           "Do not load config files in standard paths automatically. Use standard configuration with password login.", false);
AliasParameter noConfig("noConfig", "Alias for NoConfigFile", &noConfigFile);

BoolParameter soundSupport("SoundSupport",
                           "Switch on sound via Pulseaudio.",
                           true);

BoolParameter micSupport("MicSupport",
                           "Switch on microphone via Pulseaudio.",
                           false);
//BoolParameter webcamEnabled("WebcamEnabled",
                          // "Start capturing video and send it to the TightGate-Pro Server.",
                         //  false);

IntParameter paPortMin("PAPortMin",
                       "Minimum port number to use for Pulseaudio sound server",
                       4713, 0, 65535);
IntParameter paPortMax("PAPortMax",
                       "Maximum port number to use for Pulseaudio sound server",
                       4713, 0, 65535);

BoolParameter noMulti("NoMulti",
                      "Disable VNC multiplexing (which is used for printing, sound, etc.).",
                      false);

BoolParameter printSupport("PrintSupport",
                           "Get print jobs from server and print it locally.",
                           false);
IntParameter printInterval("PrintInterval",
                           "How many seconds to wait between checks for new print jobs.",
                           60, 10);
IntParameter sshPort("SshPort",
                     "The print spooler and the Schleuse connects to this port.",
                     22, 1, 65535);
BoolParameter quickPrint("QuickPrint",
                         "Print every print job on standard printer and do not show printer dialog.",
                         false);
StringParameter printCommand("PrintCommand", "Binary used to print PDFs.", "lpr");
BoolParameter adobePrintDialog("AdobePrintDialog",
                         "Print with Adobe Acrobat Reader's print dialog if installed",
                         false);
BoolParameter winCursor("WinCursor",
                        "Obsolete, kept for compatibility.",
                        false);

#if defined(WIN32) || defined(WIN64)
StringParameter autotransferFolder("AutotransferFolder", "Folder to auto-download files to", "%userprofile%\\Downloads\\autotransfer");
#else
StringParameter autotransferFolder("AutotransferFolder", "Folder to auto-download files to", "~/Downloads/autotransfer");
#endif /* defined(WIN32) || defined(WIN64) */
BoolParameter autotransferSupport("AutotransferSupport", "Support Auto Transfer", false);

IntParameter mpScaling("MPScaling",
                     "Signal desired screen scaling in percent to server (default 0 to keep last saved value on server, 0-500)",
                     0, 0, 500);

BoolParameter tgproCC("TGProCC", "TightGate-Pro CC Mode", false);

BoolParameter standardBrowserPrompt("StandardBrowserPrompt", "Ask the user whether they want to set viewer as standard browser.", true);
BoolParameter standardBrowserEnforce("StandardBrowserEnforce", "Always set viewer as standard browser without asking.", false);

BoolParameter newSessionPrompt("NewSessionPrompt", "Ask the user whether they want to start a new session. If set to false, it will start a new session directly", true);

BoolParameter allowLocalCursor("AllowLocalCursor", "Allow cursor rendering on client", true);

static const char* IDENTIFIER_STRING = "TightGateVNC Configuration file Version 1.0";

static VoidParameter* parameterArray[] = {
#ifdef HAVE_GNUTLS
  &CSecurityTLS::X509CA,
  &CSecurityTLS::X509CRL,
  &CSecurityTLS::x509key,
  &CSecurityTLS::x509cert,
  &CSecurityKrb::KrbService,
  &CSecurityKrb::KrbAuthid,
  &CSecurityKrb::KrbHostname,
#endif // HAVE_GNUTLS
  &SecurityClient::secTypes,
  &emulateMiddleButton,
  &dotWhenNoCursor,
  &autoSelect,
  &preferredEncoding,
  &customCompressLevel,
  &compressLevel,
  &noJpeg,
  &qualityLevel,
//  &noMP,
  &mpLevel,
  &mpCompression,
  &fullScreen,
  &fullScreenAllMonitors,
  &desktopSize,
  &geometry,
  &remoteResize,
  &viewOnly,
  &shared,
  &acceptClipboard,
  &sendClipboard,
  &enforceClipboard,
#if !defined(WIN32) && !defined(__APPLE__)
  &sendPrimary,
  &setPrimary,
#endif
  &pointerEventInterval,
  &menuKey,
  &webcamName,
  &fullscreenSystemKeys,
  &productName,
  &vendorName,
  &noMulti,
  &soundSupport,
  &maximize,
  &vendorName,
  &printSupport,
  &printInterval,
  &printCommand,
  &sshPort,
  &quickPrint,
  &adobePrintDialog,
  &configDir,
  &paPortMin,
  &paPortMax,
  &standardBrowserPrompt,
  &standardBrowserEnforce,
  &autotransferFolder,
  &autotransferSupport,
  &newSessionPrompt,
  &mpScaling
};

// Encoding Table
static struct {
  const char first;
  const char second;
} replaceMap[] = { { '\n', 'n' },
                   { '\r', 'r' },
                   { '\\', '\\' } };

static bool encodeValue(const char* val, char* dest, size_t destSize) {

  size_t pos = 0;

  for (size_t i = 0; (val[i] != '\0') && (i < (destSize - 1)); i++) {
    bool normalCharacter;
    
    // Check for sequences which will need encoding
    normalCharacter = true;
    for (size_t j = 0; j < sizeof(replaceMap)/sizeof(replaceMap[0]); j++) {

      if (val[i] == replaceMap[j].first) {
        dest[pos] = '\\';
        pos++;
        if (pos >= destSize)
          return false;

        dest[pos] = replaceMap[j].second;
        normalCharacter = false;
        break;
      }

      if (normalCharacter) {
        dest[pos] = val[i];
      }
    }

    pos++;
    if (pos >= destSize)
      return false;
  }

  dest[pos] = '\0';
  return true;
}


static bool decodeValue(const char* val, char* dest, size_t destSize) {

  size_t pos = 0;
  
  for (size_t i = 0; (val[i] != '\0') && (i < (destSize - 1)); i++) {
    
    // Check for escape sequences
    if (val[i] == '\\') {
      bool escapedCharacter;
      
      escapedCharacter = false;
      for (size_t j = 0; j < sizeof(replaceMap)/sizeof(replaceMap[0]); j++) {
        if (val[i+1] == replaceMap[j].second) {
          dest[pos] = replaceMap[j].first;
          escapedCharacter = true;
          i++;
          break;
        }
      }

      if (!escapedCharacter)
        return false;

    } else {
      /* Ignore CR from some windows editors.  */
      if (val[i] != '\r')
        dest[pos] = val[i];
      else
        pos--;
    }

    pos++;
    if (pos >= destSize) {
      return false;
    }
  }
  
  dest[pos] = '\0';
  return true;
}



void saveViewerParameters(const char *filename, const char *servername) {

  const size_t buffersize = 256;
  char filepath[PATH_MAX - 4];
  char backuppath[PATH_MAX];
  char encodingBuffer[buffersize];
  struct stat statbuf;
  int err;

  // Write to a predefined file if no filename was specified.
  if(filename == NULL) {
    char* homeDir = NULL;
    if (getvnchomedir(&homeDir) == -1) {
      vlog.error("Failed to write configuration file, can't obtain home "
                   "directory path.");
      return;
    }

    snprintf(filepath, sizeof(filepath), "%s%s", homeDir, user_config_file);
    delete[] homeDir;
  } else {
    snprintf(filepath, sizeof(filepath), "%s", filename);
  }
  snprintf(backuppath, sizeof(backuppath), "%s.old", filepath);

  if (!access(filepath, F_OK) && !stat(filepath, &statbuf) && statbuf.st_size >= (off_t) strlen(IDENTIFIER_STRING)) {
    vlog.debug("Rename old configuration file %s to %s", filepath, backuppath);
    unlink(backuppath);
    err = rename(filepath, backuppath);
    if (err < 0) {
      vlog.error("Failed to rename old configuration file %s to %s, not saving, error: %s", filepath, backuppath, strerror(errno));
      return;
    }
  }

  /* Write parameters to file */
  FILE* f = fopen(filepath, "w+");
  if (!f) {
    vlog.error("Failed to write open configuration file %s - try to restore backup file %s", filepath, backuppath);
    err = rename(filepath, backuppath);
    if (err < 0) {
      vlog.error("Failed to rename backup configuration file %s back to %s, error: %s", backuppath, filepath, strerror(errno));
    }
    return;
  }
  vlog.info("Storing configuration in file %s", filepath);

  err = fprintf(f, "%s\r\n", IDENTIFIER_STRING);
  if (err < 0) {
    vlog.error("Failed to write into configuration file %s - try to restore backup file %s", filepath, backuppath);
    err = rename(filepath, backuppath);
    if (err < 0) {
      vlog.error("Failed to rename backup configuration file %s back to %s, error: %s", backuppath, filepath, strerror(errno));
    }
    return;
  }

  fprintf(f, "\r\n");

  if (encodeValue(servername, encodingBuffer, buffersize))
    fprintf(f, "ServerName=%s\n", encodingBuffer);

  for (size_t i = 0; i < sizeof(parameterArray)/sizeof(VoidParameter*); i++) {
    if (dynamic_cast<StringParameter*>(parameterArray[i]) != NULL) {
      const char* parameter = ((StringParameter*)parameterArray[i])->getName();
      if (   strstr_insensitive(parameter, "X509CA")
          || strstr_insensitive(parameter, "x509key")
          || strstr_insensitive(parameter, "x509cert")) {
        // BUG 0004717: vnc-Config-Ordner kann nicht verschoben werden, wenn ein mal mit X509Cert angemeldet
//        vlog.debug("Skipping saving parameter '%s' as an absolute path. It will be deduced while launching the viewer...", parameter);
        continue; // Do not save absolute path to certificates. These can be deduced on launch.
      }
      if (strstr_insensitive(parameter, "configdir")) {
//        vlog.debug("Not storing ConfigDir parameter. It can (and will) only cause confusion, as -ConfigDir is "
//                   "always called from the command line or from a Windows link. It can still be used during the current session");
        continue; // If you move a folder where you had once called the viewer with a -configdir, it will always use this config dir as standard.
      }
      if (strcmp(((StringParameter*)parameterArray[i])->getValueStr(), ((StringParameter*)parameterArray[i])->getDefaultStr())) {
        if (encodeValue(*(StringParameter*)parameterArray[i], encodingBuffer, buffersize))
          fprintf(f, "%s=%s\n", ((StringParameter*)parameterArray[i])->getName(), encodingBuffer);
      }
    } else if (dynamic_cast<IntParameter*>(parameterArray[i]) != NULL) {
      if (strcmp(((IntParameter*)parameterArray[i])->getValueStr(), ((IntParameter*)parameterArray[i])->getDefaultStr())) {
        fprintf(f, "%s=%d\n", ((IntParameter*)parameterArray[i])->getName(), (int)*(IntParameter*)parameterArray[i]);
      }
    } else if (dynamic_cast<BoolParameter*>(parameterArray[i]) != NULL) {
      if (strcmp(((BoolParameter*)parameterArray[i])->getValueStr(), ((BoolParameter*)parameterArray[i])->getDefaultStr())) {
        fprintf(f, "%s=%d\n", ((BoolParameter*)parameterArray[i])->getName(), (int)*(BoolParameter*)parameterArray[i]);
      }
    } else {
      vlog.error("Unknown parameter type for parameter %s",
                 parameterArray[i]->getName());
    }
  }
  fclose(f);
}

inline int get_tgpro_sys_cfg_dir(const char** const cfg_dir_p) {
  assert(cfg_dir_p && !*cfg_dir_p);

#ifndef WIN32
  *cfg_dir_p = "/etc/";
  return 0;
#else
  return (*cfg_dir_p = get_tgpro_path()) ? 0 : -1;
#endif /* WIN32 */
}

/*
 * We read first readBuffersize - 2 bytes from file and return servername
 */
char* loadViewerParameters(const char *filename) {

  const size_t decodeBuffersize = 256;
  const size_t readBuffersize = 65536;
  char readError[8192];
  char filepath[PATH_MAX - 4];
  char backuppath[PATH_MAX];
  struct stat statbuf;
  char *lineStart;
  char *lineEnd;
  char *readBuffer;
  size_t bytesRead;
  char decodingBuffer[decodeBuffersize];
  static char servername[decodeBuffersize];
  int err;

  memset(servername, '\0', sizeof(servername));

  // Load from a predefined file if no filename was specified.
  if(filename == NULL) {
    /* Check for the user's personal config file in home dir. */
    char* homeDir = NULL;
    if (getvnchomedir(&homeDir) != -1)
      snprintf(filepath, sizeof(filepath), "%s%s", homeDir, user_config_file);
    else
      vlog.info("I could not find homedir.");
    /* If there is no personal cfg file, check for system-wide file in cwd or in TG-Pro installpath. */
    if (fileexists(filepath) == -1) {
      vlog.info("I could not find tgpro.vnc in homedir. Searching for cfg file in current working dir.");
      if (fileexists(system_config_file) == 0)
        snprintf(filepath, sizeof(filepath), "%s", system_config_file);
      else {
        vlog.info("No cfg in cwd. Looking for cfg file in global config dir.");
        const char* sysCfgDir = NULL;
        if (get_tgpro_sys_cfg_dir(&sysCfgDir) != -1) {
          vlog.info("I found tgpro config dir: %s", sysCfgDir);
#ifdef WIN32
          snprintf(filepath, sizeof(filepath), "%s\\%s", sysCfgDir, system_config_file);
#else
          snprintf(filepath, sizeof(filepath), "%s/%s", sysCfgDir, system_config_file);
#endif /* WIN32 */
        } else
          vlog.info("I did not find global (system) tgpro config dir!");
        if (filepath[0] == '\0' || fileexists(filepath) == -1) {
          if (fileexists(user_config_file) == 0) {
            vlog.info("By last chance I checked for %s in cwd. There it is.", user_config_file);
            snprintf(filepath, sizeof(filepath), "%s", user_config_file);
          } else {
            vlog.info("I could not find any config file anywhere.");
          }
        }
      }
    } else {
      vlog.info("I found tgpro.vnc in homedir!");
    }
  } else {
    snprintf(filepath, sizeof(filepath), "%s", filename);
  }
  snprintf(backuppath, sizeof(backuppath), "%s.old", filepath);

  vlog.info("Looking for conf file now: %s", filepath);

  /* Read parameters from file */
  FILE* f = fopen(filepath, "rb");
  if (!f || (err = fstat(fileno(f), &statbuf)) < 0 || statbuf.st_size < (off_t) strlen(IDENTIFIER_STRING)) {
    vlog.info("Failed to open %s or file is too short, try backup %s", filepath, backuppath);
    if (f)
      fclose(f);
    f = fopen(backuppath, "rb");
    if (!f || (err = fstat(fileno(f), &statbuf)) < 0 || statbuf.st_size < (off_t) strlen(IDENTIFIER_STRING)) {
      vlog.info("Failed to open %s or file is too short, giving up", backuppath);
      if (f) {
        fclose(f);
        f = NULL;
      }
    }
  }
  if (!f) {
    if (!filename)
      return NULL; // Use defaults.
    snprintf(readError, sizeof(readError), "Ich kann die Konfigurationsdatei nicht lesen, "
             "Datei %s nicht lesbar: Fehler: %s", filepath, strerror(errno));
    throw Exception("Failed to read configuration file, can't open %s: %s",
                    filepath, strerror(errno));
  }

  readBuffer = (char *) malloc(readBuffersize);
  if (!readBuffer) {
    fclose(f);
    throw Exception("Failed to allocate buffer memory for configuration file %s",
                    filepath);
  }
  memset(readBuffer, 0, readBuffersize);

  // need two 0 at end of buffer, so read 2 bytes less than buffer size
  bytesRead = fread(readBuffer, 1, readBuffersize - 2, f);
  if (!feof(f))
    vlog.info("Configuration file %s not completely read, might be too long or invalid!",
              filepath);
  fclose(f);
  if (!bytesRead) {
    free(readBuffer);
    return servername;
  }

  lineEnd = lineStart = readBuffer;

  int lineNr = 1;
  while (*lineStart) {
    lineStart = lineEnd;
    // trim line start
    while (*lineStart == '\n' || *lineStart == '\r') {
      if (*lineStart == '\n')
        lineNr++;
      lineStart++;
    }

    // find end of line and terminate line there
    lineEnd = lineStart;
    while (*lineEnd && *lineEnd != '\n' && *lineEnd != '\r')
      lineEnd++;
    if (*lineEnd == '\n')
      lineNr++;
    *lineEnd = 0;
    vlog.verbose("Read line %u from configuration file %s: \"%s\"",
              lineNr, filepath, lineStart);
    // Skip empty lines
    if (lineStart == lineEnd) {
      vlog.verbose("Skipped empty line %u in configuration file %s",
                lineNr, filepath);
      lineEnd++;
      continue;
    }
    // next line must start there, CR and LF trimmed in next round
    lineEnd++;

    // Make sure that the first line of the file has the file identifier string
    if(lineNr == 1) {
      if(strncmp(lineStart, IDENTIFIER_STRING, strlen(IDENTIFIER_STRING)) == 0) {
        continue;
      } else
        vlog.info("Configuration file %s is missing identifier string, might be invalid!",
                        filepath);
    }
    // skip comment
    if (lineStart[0] == '#') {
      vlog.verbose("Skipped comment line in configuration file %s",
                filepath);
      continue;
    }
    // Find the parameter value
    char *value = strchr(lineStart, '=');
    if (value == NULL) {
      vlog.error("Failed to read line %d in file %s: %s",
                 lineNr, filepath, lineStart);
      continue;
    }
    *value = '\0'; // line only contains the parameter name below.
    value++;
    vlog.verbose("Parameter: \"%s\", value: \"%s\"",
                lineStart, value);

    bool invalidParameterName = true; // Will be set to false below if
                                      // the line contains a valid name.

    if (strcasecmp(lineStart, "ServerName") == 0 || strcasecmp(lineStart, "Host") == 0) {

      if(!decodeValue(value, decodingBuffer, sizeof(decodingBuffer))) {
        vlog.error("Failed to read line %d in file %s: %s",
                   lineNr, filepath, "Invalid format or too large value");
        continue;
      }
      snprintf(servername, sizeof(decodingBuffer), "%s", decodingBuffer);
      invalidParameterName = false;

    } else {

      // Find and set the correct parameter
      for (size_t i = 0; i < sizeof(parameterArray)/sizeof(VoidParameter*); i++) {

        if (dynamic_cast<StringParameter*>(parameterArray[i]) != NULL) {
          if (strcasecmp(lineStart, ((StringParameter*)parameterArray[i])->getName()) == 0) {

            if(!decodeValue(value, decodingBuffer, sizeof(decodingBuffer))) {
              vlog.error("Failed to read line %d in file %s: %s",
                         lineNr, filepath, "Invalid format or too large value");
              continue;
            }
            ((StringParameter*)parameterArray[i])->setParam(decodingBuffer);
            ((StringParameter*)parameterArray[i])->setHasBeenSet();
            invalidParameterName = false;
          }

        } else if (dynamic_cast<IntParameter*>(parameterArray[i]) != NULL) {
          if (strcasecmp(lineStart, ((IntParameter*)parameterArray[i])->getName()) == 0) {
            ((IntParameter*)parameterArray[i])->setParam(atoi(value));
            ((IntParameter*)parameterArray[i])->setHasBeenSet();
            invalidParameterName = false;
          }

        } else if (dynamic_cast<BoolParameter*>(parameterArray[i]) != NULL) {
          if (strcasecmp(lineStart, ((BoolParameter*)parameterArray[i])->getName()) == 0) {
            ((BoolParameter*)parameterArray[i])->setParam(atoi(value));
            ((BoolParameter*)parameterArray[i])->setHasBeenSet();
            invalidParameterName = false;
          }

        } else {
          vlog.error("Unknown parameter type for parameter %s",
                     parameterArray[i]->getName());
        }
      }
    }

    if (invalidParameterName)
      vlog.info("Unknown parameter %s on line %d in file %s",
                lineStart, lineNr, filepath);
  }

  vlog.verbose("Read %u lines from configuration file %s",
             lineNr, filepath);
  free(readBuffer);
  return servername;
}
