/* ========================================================================== */
/*! \file
 * \brief Configuration handling
 *
 * Copyright (c) 2012-2024 by the developers. See the LICENSE file for details.
 *
 * If nothing else is specified, functions return zero to indicate success
 * and a negative value to indicate an error.
 */


/* ========================================================================== */
/* Include headers */

#include "posix.h"  /* Include this first because of feature test macros */

#include <stdio.h>
#include <string.h>

#include "conf.h"
#include "config.h"
#include "fileutils.h"
#include "main.h"
#include "secure.h"
#include "xdg.h"


/* ========================================================================== */
/*! \defgroup CONF CONF: Configuration handling
 *
 * Location of configfile: \c $XDG_CONFIG_HOME/$CFG_NAME/configfile
 *
 * Any line starting with \c # is treated as a comment (not parsed and ignored).
 *
 * To add a new entry:
 * - Add new entry type in file \ref conf.h to enum \ref conf_entry
 * - Increment \ref CONF_NUM in file \ref conf.h
 * - Modify static function \c conf_init_configuration() to init the new entry
 *   to a default
 */
/*! @{ */


/* ========================================================================== */
/* Constants */

/*! \brief Message prefix for CONF module */
#define MAIN_ERR_PREFIX  "CONF: "

/*! \brief Permissions for config file */
#define CONF_PERM  (api_posix_mode_t) (API_POSIX_S_IRUSR | API_POSIX_S_IWUSR)


/* ========================================================================== */
/* Variables */

/*! \brief Global configuration
 *
 * There must be \ref CONF_NUM entries of type \ref conf in ascending order
 * starting from zero without holes, so that \ref conf_entry IDs can be used
 * as index.
 */
struct conf  config[CONF_NUM];

/*! \brief Flag indicating that password should not be stored in configfile
 *
 * If this flag is nonzero a call to \ref conf_store() will delete the password
 * from the configuration before writing it to disk.
 */
int  conf_ephemeral_passwd = 0;

static int  conf_initialized = 0;
static const char  confname[] = "configfile";
static const char  tmpname[] = "configfile.new";


/* ========================================================================== */
/* Add integer configuration entry */

static void  conf_add_entry_int(struct conf*  conf, enum conf_entry  entry,
                                const char*  label, int  val)
{
   conf[entry].label = label;
   conf[entry].type = CONF_TYPE_INT;
   conf[entry].val.i = val;
   conf[entry].found = 0;
}


/* ========================================================================== */
/* Add string configuration entry */

static void  conf_add_entry_string(struct conf*  conf, enum conf_entry  entry,
                                   const char*  label, char*  val)
{
   conf[entry].label = label;
   conf[entry].type = CONF_TYPE_STRING;
   conf[entry].val.s = val;
   conf[entry].found = 0;
}


/* ========================================================================== */
/* Init configuration with default values (to use if there is no config file)
 *
 * \attention
 * The content of entries with string type must be stored in dynamic memory!
 * For empty strings, a memory block with NUL termination inside must be used.
 */

static void  conf_init_configuration(struct conf*  cfg)
{
   const char  server_default[] = "localhost";
   const char  service_default[] = "nntp";
   const char  test_ere_default[] = "\\.test$|^test$";
   const char  test_kwords_default[] = "ignore,no-reply";
   const char  crl_upd_ts_default[] = "1970-01-01T00:00:00Z";
   const char  sigfile_default[] = ".signature";
   /* This is a format string, '%s' will be replaced with cited authors name */
   const char  intro_default[] = "%s wrote:";
   char*  string;
   char*  empty;
   int  error = 0;

   /* Main window size, position and tiling */
   conf_add_entry_int(cfg, CONF_POS_X, "pos_x:", 10);
   conf_add_entry_int(cfg, CONF_POS_Y, "pos_y:", 40);
   conf_add_entry_int(cfg, CONF_SIZE_X, "size_x:", 750);
   conf_add_entry_int(cfg, CONF_SIZE_Y, "size_y:", 350);
   conf_add_entry_int(cfg, CONF_TILE_X, "tile_x:", 200);
   conf_add_entry_int(cfg, CONF_TILE_Y, "tile_y:", 120);

   /* NNTP server */
   string = (char*) api_posix_malloc(strlen(server_default) + (size_t) 1);
   if(NULL == string)  { error = 1; }
   else
   {
      strcpy(string, server_default);
      conf_add_entry_string(cfg, CONF_SERVER, "server:", string);
   }

   /* NNTP service (port) */
   string = (char*) api_posix_malloc(strlen(service_default) + (size_t) 1);
   if(NULL == string)  { error = 1; }
   else
   {
      strcpy(string, service_default);
      conf_add_entry_string(cfg, CONF_SERVICE, "service:", string);
   }

   /* NNTP connection encryption */
   conf_add_entry_int(cfg, CONF_ENC, "enc:", 0);

   /* Authentication against NNTP server */
   conf_add_entry_int(cfg, CONF_AUTH, "auth:", 0);
   conf_add_entry_int(cfg, CONF_IMMEDAUTH, "immedauth:", 1);
   empty = (char*) api_posix_malloc((size_t) 1);
   if(NULL == empty)  { error = 1; }
   else
   {
      empty[0] = 0;
      conf_add_entry_string(cfg, CONF_USER, "user:", empty);
   }
   empty = (char*) api_posix_malloc((size_t) 1);
   if(NULL == empty)  { error = 1; }
   else
   {
      empty[0] = 0;
      conf_add_entry_string(cfg, CONF_PASS, "pass:", empty);
   }

   /* Clamp article count limit */
   conf_add_entry_int(cfg, CONF_CAC, "cac:", 250);

   /* "From" name and E-Mail address  */
   empty = (char*) api_posix_malloc((size_t) 1);
   if(NULL == empty)  { error = 1; }
   else
   {
      empty[0] = 0;
      conf_add_entry_string(cfg, CONF_FROM, "from:", empty);
   }

   /* "Reply-To" name and E-Mail address  */
   empty = (char*) api_posix_malloc((size_t) 1);
   if(NULL == empty)  { error = 1; }
   else
   {
      empty[0] = 0;
      conf_add_entry_string(cfg, CONF_REPLYTO, "replyto:", empty);
   }

   /* Fully qualified domain name (FQDN) */
   empty = (char*) api_posix_malloc((size_t) 1);
   if(NULL == empty)  { error = 1; }
   else
   {
      empty[0] = 0;
      conf_add_entry_string(cfg, CONF_FQDN, "fqdn:", empty);
   }

   /* Pathname of shared newsrc file */
   empty = (char*) api_posix_malloc((size_t) 1);
   if(NULL == empty)  { error = 1; }
   else
   {
      empty[0] = 0;
      conf_add_entry_string(cfg, CONF_NEWSRC, "newsrc:", empty);
   }

   /* Pathname of shared scorerc file */
   empty = (char*) api_posix_malloc((size_t) 1);
   if(NULL == empty)  { error = 1; }
   else
   {
      empty[0] = 0;
      conf_add_entry_string(cfg, CONF_SCORERC, "scorerc:", empty);
   }

   /* Introduction line format */
   string = (char*) api_posix_malloc(strlen(intro_default) + (size_t) 1);
   if(NULL == string)  { error = 1; }
   else
   {
      strcpy(string, intro_default);
      conf_add_entry_string(cfg, CONF_INTRO, "intro:", string);
   }

   /* Organization name */
   empty = (char*) api_posix_malloc((size_t) 1);
   if(NULL == empty)  { error = 1; }
   else
   {
      empty[0] = 0;
      conf_add_entry_string(cfg, CONF_ORGANIZATION, "organization:", empty);
   }

   /* Threaded view */
   conf_add_entry_int(cfg, CONF_TVIEW, "tview:", 1);

   /* Show only unread articles (or threads containing unread articles) */
   conf_add_entry_int(cfg, CONF_ONLYUR, "onlyur:", 0);

   /* Secret for Cancel-Key */
   empty = (char*) api_posix_malloc((size_t) 1);
   if(NULL == empty)  { error = 1; }
   else
   {
      empty[0] = 0;
      conf_add_entry_string(cfg, CONF_CANCELKEY, "cancelkey:", empty);
   }

   /* Pathname of external article editor */
   empty = (char*) api_posix_malloc((size_t) 1);
   if(NULL == empty)  { error = 1; }
   else
   {
      empty[0] = 0;
      conf_add_entry_string(cfg, CONF_EDITOR, "editor:", empty);
   }

   /* Pathname of external article postprocessor */
   empty = (char*) api_posix_malloc((size_t) 1);
   if(NULL == empty)  { error = 1; }
   else
   {
      empty[0] = 0;
      conf_add_entry_string(cfg, CONF_PPROC, "post_proc:", empty);
   }

   /* Pathname of external inews */
   empty = (char*) api_posix_malloc((size_t) 1);
   if(NULL == empty)  { error = 1; }
   else
   {
      empty[0] = 0;
      conf_add_entry_string(cfg, CONF_INEWS, "inews:", empty);
   }

   /* Quoting style */
   conf_add_entry_int(cfg, CONF_QUOTESTYLE, "quote_style:", 1);

   /* Unification of quoting */
   conf_add_entry_int(cfg, CONF_QUOTEUNIFY, "quote_unify:", 1);

   /* POSIX ERE (Extended Regular Expression) to match test groups */
   string = (char*) api_posix_malloc(strlen(test_ere_default) + (size_t) 1);
   if(NULL == string)  { error = 1; }
   else
   {
      strcpy(string, test_ere_default);
      conf_add_entry_string(cfg, CONF_TESTGRP_ERE, "testgrp_ere:", string);
   }

   /* Keywords to set for test groups (on ERE match) */
   string = (char*) api_posix_malloc(strlen(test_kwords_default) + (size_t) 1);
   if(NULL == string)  { error = 1; }
   else
   {
      strcpy(string, test_kwords_default);
      conf_add_entry_string(cfg, CONF_TESTGRP_KWORDS, "testgrp_kwords:",
                            string);
   }

   /* TLS certificate checks use local anchors (root certificates) */
   conf_add_entry_int(cfg, CONF_TLS_OWNCERTS, "tls_owncerts:", 0);

   /* TLS certificate CRL update interval (in hours) */
   conf_add_entry_int(cfg, CONF_CRL_UPD_IV, "crl_upd_iv:", 20);

   /* TLS certificate CRL update timestamp */
   string = (char*) api_posix_malloc(strlen(crl_upd_ts_default) + (size_t) 1);
   if(NULL == string)  { error = 1; }
   else
   {
      strcpy(string, crl_upd_ts_default);
      conf_add_entry_string(cfg, CONF_CRL_UPD_TS, "crl_upd_ts:", string);
   }

   /* TLS certificate CRL check */
   conf_add_entry_int(cfg, CONF_CRL_CHECK, "crl_check:", 0);

   /* TLS certificate CRL update choice (Allow user to skip update) */
   conf_add_entry_int(cfg, CONF_CRL_UPD_C, "crl_upd_c:", 0);

   /* Sort unthreaded view in GUI using article numbers */
   conf_add_entry_int(cfg, CONF_UTVIEW_AN, "utview_an:", 0);

   /* Sort articles in GUI with inverted order */
   conf_add_entry_int(cfg, CONF_INV_ORDER, "inv_order:", 0);

   /* NNTP driver doesn't use overview (OVER command) if nonzero */
   conf_add_entry_int(cfg, CONF_NO_OVER, "nntp_no_over:", 0);

   /* Use distribution suggestions from server (if available) */
   conf_add_entry_int(cfg, CONF_DIST_SUGG, "dist_suggestions:", 0);

   /* Negotiate compression (if available) */
   conf_add_entry_int(cfg, CONF_COMPRESSION, "nntp_compression:", 0);

   /* Signature file */
   string = (char*) api_posix_malloc(strlen(sigfile_default) + (size_t) 1);
   if(NULL == string)  { error = 1; }
   else
   {
      strcpy(string, sigfile_default);
      conf_add_entry_string(cfg, CONF_SIGFILE, "signature_file:", string);
   }

   /* Use local time for timestamp generation (if timezone is available) */
   conf_add_entry_int(cfg, CONF_TS_LTIME, "timestamp_ltime:", 1);

   /* Generate timestamps with timezone comment (if timezone is available) */
   conf_add_entry_int(cfg, CONF_TS_COMMENT, "timestamp_comment:", 0);

   /* Generate empty line after paragraphs (format=flowed) */
   conf_add_entry_int(cfg, CONF_FLOWED_CRLF, "flowed_insert_crlf:", 0);

   /* Force Unicode for outgoing articles */
   conf_add_entry_int(cfg, CONF_FORCE_UNICODE, "force_unicode:", 0);

   /* Search case-insensitive */
   conf_add_entry_int(cfg, CONF_SEARCH_CASE_IS, "search_case_insensitive:", 0);

   /* Enable User-Agent header field */
   conf_add_entry_int(cfg, CONF_ENABLE_UAGENT, "enable_uagent_hfield:", 1);

   /* Initial greeting */
   empty = (char*) api_posix_malloc((size_t) 1);
   if(NULL == empty)  { error = 1; }
   else
   {
      empty[0] = 0;
      conf_add_entry_string(cfg, CONF_INITIAL_GREETING, "initial_greeting:",
                            empty);
   }

   /* Group list refresh interval */
   conf_add_entry_int(cfg, CONF_REFRESH_INTERVAL, "refresh_interval:", 0);

   /* Skip to next group for unread article */
   conf_add_entry_int(cfg, CONF_UNREAD_IN_NEXT_GROUP, "unread_in_next_group:",
                       0);

   /* Color for signature */
   conf_add_entry_int(cfg, CONF_COLOR_SIGNATURE, "color_signature:", 43);

   /* Color for external citation */
   conf_add_entry_int(cfg, CONF_COLOR_EXTERNAL, "color_external:", 88);

   /* Colors of thread citation level */
   conf_add_entry_int(cfg, CONF_COLOR_LEVEL1, "color_level1:", 140);
   conf_add_entry_int(cfg, CONF_COLOR_LEVEL2, "color_level2:", 152);
   conf_add_entry_int(cfg, CONF_COLOR_LEVEL3, "color_level3:", 91);
   conf_add_entry_int(cfg, CONF_COLOR_LEVEL4, "color_level4:", 75);

   /* Kill threshold (articles below that score will be killed) */
   conf_add_entry_int(cfg, CONF_SCORE_KILL_TH, "kill_threshold:", -9999);

   /* On success, store flag that configuration was initialized */
   if(error)  { PRINT_ERROR("Initializing configuration failed"); }
   else  { conf_initialized = 1; }
}


/* ========================================================================== */
/* Get config file pathname
 *
 * The caller is responsible to free the memory for the buffer on success.
 */

static int  conf_get_confpathname(const char**  confpathname,
                                  const char*  conffile)
{
   int  res = -1;
   int  rv;

   *confpathname = xdg_get_confdir(CFG_NAME);
   if(NULL != *confpathname)
   {
      rv = fu_create_path(*confpathname, (api_posix_mode_t) API_POSIX_S_IRWXU);
      if(0 == rv)
      {
         /* Store config file pathname */
         rv = xdg_append_to_path(confpathname, conffile);
         if(0 == rv)
         {
            res = 0;
         }
      }
   }

   /* Free memory on error */
   if(0 != res)
   {
      PRINT_ERROR("Cannot create config file pathname");
      api_posix_free((void*) *confpathname);
      *confpathname = NULL;
   }

   return(res);
}


/* ========================================================================== */
/* Extract integer value from configuration data */

static int  conf_extract_entry(char*  line, struct conf*  target)
{
   int  res = -1;
   size_t  labelsize = strlen(target->label);
   const char*  string;
   char*  sbuf;
   int  val;
   size_t  i;

   /* Check line length */
   if(strlen(line) > labelsize)
   {
      /* Search for label */
      if(!strncmp(target->label, line, labelsize))
      {
         /* Found */
         res = 0;
         if(CONF_TYPE_INT == target->type)
         {
            /* Extract integer value */
            string = &line[labelsize];
            if(1 == sscanf(string, "%d", &val))  target->val.i = val;
         }
         else
         {
            /* Extract string */
            i = labelsize;
            while(line[i])
            {
               if(' ' != line[i])  { break; }
               else  ++i;
            }
            string = &line[i];
            while(line[i])
            {
               if('\n' == line[i])
               {
                  line[i] = 0;
                  break;
               }
               else  ++i;
            }
            /*
             * Allocate memory for string entry
             * This memory is not released until program exit.
             */
            sbuf = (char*) api_posix_malloc(strlen(string) + (size_t) 1);
            if(NULL == sbuf)
            {
               PRINT_ERROR("Cannot allocate memory for config file entry");
               target->val.s = NULL;
               res = -1;
            }
            else
            {
               strcpy(sbuf, string);
               secure_clear_string(target->val.s);
               api_posix_free((void*) target->val.s);
               target->val.s = sbuf;
            }
         }
      }
   }

   return(res);
}


/* ========================================================================== */
/* Update configuration data */

static int  conf_update_entry(char**  line, size_t  len, struct conf*  target)
{
   int  res = -1;
   size_t  labelsize = strlen(target->label);
   char*  string;
   int  reqd;

   /* Check line length */
   if(strlen(*line) >= labelsize)
   {
      /* Search for label */
      if(!strncmp(target->label, *line, labelsize))
      {
         /* Found => Insert new value */
         res = 0;
         target->found = 1;
         do
         {
            reqd = -1;
            if(CONF_TYPE_INT == target->type)
            {
               /* Update integer entry */
               reqd = api_posix_snprintf(*line, len, "%s %d\n",
                                         target->label, target->val.i);
            }
            if(CONF_TYPE_STRING == target->type)
            {
               /* Update string entry */
               reqd = api_posix_snprintf(*line, len, "%s %s\n",
                                         target->label, target->val.s);
            }
            if(0 > reqd)  { break; }
            else if(len <= (size_t) reqd)
            {
               /* Allocate more memory */
               if(API_POSIX_SIZE_MAX <= (size_t) reqd)  { break; }
               string = (char*) api_posix_realloc(*line, (size_t) (reqd + 1));
               if(NULL == string)  { break; }
               else  { *line = string; }
            }
         }
         while(len <= (size_t) reqd);
      }
   }

   return(res);
}


/* ========================================================================== */
/* Import configuration */

static int  conf_import_configuration(struct conf*  cfg, FILE*  fs)
{
   int  res = -1;
   char*  line = NULL;
   size_t  len = 0;
   api_posix_ssize_t  readlen;
   unsigned int  i;

   while(1)
   {
      /* Read line */
      readlen = api_posix_getline(&line, &len, fs);
      if(-1 == readlen)
      {
         if(API_POSIX_ENOMEM == api_posix_errno)
         {
            PRINT_ERROR("Cannot assign memory for config file parser");
         }
         else
         {
            /* Check for error */
            if(ferror(fs))
            {
               PRINT_ERROR("Parse error in config file");
               break;
            }
            /* Check for EOF */
            if(feof(fs))
            {
               res = 0;
               break;
            }
         }
      }
      if(0 >= readlen)  { break; }
      else
      {
         /* Extract data */
         if(!strncmp("#", line, 1))  { continue; }
         for(i = 0; i < CONF_NUM; ++i)
         {
            if(!conf_extract_entry(line, &cfg[i]))  { break; }
         }
      }
   }

   /* Release memory for line buffer */
   secure_clear_string(line);
   api_posix_free((void*) line);

   return(res);
}


/* ========================================================================== */
/* Export configuration */

static int  conf_export_configuration(struct conf*  cfg,
                                      FILE*  fs, FILE*  fs_tmp)
{
   int  res = -1;
   char*  line = NULL;
   size_t  len = 0;
   api_posix_ssize_t  readlen;
   int  rv;
   unsigned int  i;
   size_t  reqd;
   char*  p;

   while(1)
   {
      /* Read line */
      readlen = api_posix_getline(&line, &len, fs);
      if(-1 == readlen)
      {
         if(API_POSIX_ENOMEM == api_posix_errno)
         {
            PRINT_ERROR("Cannot assign memory for config file parser");
         }
         else
         {
            /* Check for error */
            if(ferror(fs))
            {
               PRINT_ERROR("Parse error in config file");
               break;
            }
            /* Check for EOF */
            if(feof(fs))
            {
               res = 0;
               break;
            }
         }
      }
      if(0 >= readlen)  { break; }
      else
      {
         /* Update data */
         for(i = 0; i < CONF_NUM; ++i)
         {
            if(!conf_update_entry(&line, len, &cfg[i]))  { break; }
         }

         /* Write line to new config file */
         rv = fprintf(fs_tmp, "%s", line);
         if(0 > rv)  { break; }
      }
   }

   /* Add missing entries to end of config file */
   if(!res)
   {
      for(i = 0; i < CONF_NUM; ++i)
      {
         if(!cfg[i].found)
         {
            /* Create new entry */
            reqd = strlen(cfg[i].label);
            if(API_POSIX_SIZE_MAX <= reqd)  { break; }
            if(reqd + (size_t) 1 > len)
            {
               /* Allocate more memory */
               p = (char*) api_posix_realloc(line, reqd + (size_t) 1);
               if (NULL != p)  line = p;
            }
            strncpy(line, cfg[i].label, len);
            rv = conf_update_entry(&line, len, &cfg[i]);
            /* Write new entry */
            if(!rv)
            {
               rv = fprintf(fs_tmp, "%s", line);
               if(0 > rv)
               {
                  res = -1;
                  break;
               }
            }
         }
      }
   }

   /* Release memory for line buffer */
   api_posix_free((void*) line);

   return(res);
}


/* ========================================================================== */
/*! \brief Delete configuration 
 *
 * This function will free the memory allocated for the configuration array
 * entries with string type.
 *
 * \param[in] cfg  Pointer to configuration array
 */

void  conf_delete(struct conf*  cfg)
{
   unsigned int  i;

   if(conf_initialized)
   {
      for(i = 0; i < CONF_NUM; ++i)
      {
         if(CONF_TYPE_STRING == cfg[i].type)
         {
            secure_clear_string(config[i].val.s);
            api_posix_free((void*) cfg[i].val.s);
            cfg[i].val.s = NULL;
         }
      }
   }

   /* Clear flag that configuration is initialized */
   conf_initialized = 0;
}


/* ========================================================================== */
/*! \brief Load configuration from config file
 *
 * \param[in] cfg  Pointer to configuration array
 *
 * For configuration entries with string type, this function allocates memory.
 * On success the caller is responsible to free this memory with the function
 * \ref conf_delete() .
 *
 * \return
 * - 0 on success
 * - Negative value on error
 */

int  conf_load(struct conf*  cfg)
{
   int  res = -1;
   int  fd = -1;
   FILE*  fs = NULL;
   const char*  confpathname = NULL;

   /* Init configuration with default values */
   conf_delete(cfg);
   conf_init_configuration(cfg);
   if(conf_initialized)
   {
      /* Get config file pathname */
      res = conf_get_confpathname(&confpathname, confname);
      if(!res)
      {
         if(main_debug)
         {
            printf("%s: %sLoad from: %s\n", CFG_NAME, MAIN_ERR_PREFIX,
                   confpathname);
         }

         /* Open and lock config file */
         res = fu_open_file(confpathname, &fd,
                            API_POSIX_O_RDWR | API_POSIX_O_CREAT, CONF_PERM);
         if(!res)  { res = fu_lock_file(fd); }
         if(!res)  { res = fu_assign_stream(fd, &fs, "r"); }

         /* Import configuration from config file */
         if(!res)  { res = conf_import_configuration(cfg, fs); }
      }

      /* Close config file (this will also release the lock) */
      fu_close_file(&fd, &fs);

      /* Release memory for pathname */
      api_posix_free((void*) confpathname);

      /* Delete configuration again on error */
      if(res)  { conf_delete(cfg); }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Store configuration to config file
 *
 * \param[in] cfg  Pointer to configuration array
 *
 * \return
 * - 0 on success
 * - Negative value on error
 */

int  conf_store(struct conf*  cfg)
{
   int  res = -1;
   int  fd = -1;
   FILE*  fs = NULL;
   int  fd_tmp = -1;
   FILE*  fs_tmp = NULL;
   const char*  confpathname = NULL;
   const char*  tmppathname = NULL;

   /* Get file pathnames */
   res = conf_get_confpathname(&confpathname, confname);
   if(!res)  { res = conf_get_confpathname(&tmppathname, tmpname); }

   /* Open and lock config file */
   if(!res)
   {
      res = fu_open_file(confpathname, &fd,
                         API_POSIX_O_RDWR | API_POSIX_O_CREAT, CONF_PERM);
      if(!res)  { res = fu_lock_file(fd); }
      if(!res)  { res = fu_assign_stream(fd, &fs, "r"); }
   }

   /* Open temporary file for new configuration */
   if(!res)
   {
      res = fu_open_file(tmppathname, &fd_tmp,
                         API_POSIX_O_WRONLY | API_POSIX_O_CREAT
                         | API_POSIX_O_TRUNC, CONF_PERM);
      /*
       * Because we have the lock for the config file, it is allowed to
       * assume that no other instance of the program currently use the
       * temporary filename.
       */
      if(!res)  { res = fu_assign_stream(fd_tmp, &fs_tmp, "w"); }
   }

   /* Export configuration to temporary file */
   if(!res)
   {
      if(conf_ephemeral_passwd)
      {
         /* Delete password from configuration */
         secure_clear_string(config[CONF_PASS].val.s);
      }
      res = conf_export_configuration(cfg, fs, fs_tmp);
   }

   /* Flush stream of temporary file */
   if(!res)  { res = fu_sync(fd_tmp, fs_tmp); }

   /* Rename temporary file to config file */
   if(!res)  { res = api_posix_rename(tmppathname, confpathname); }

   /* Try to unlink temporary file after error */
   if(res)
   {
      PRINT_ERROR("Failed to store configuration in config file");
      if(tmppathname)  { (void) fu_unlink_file(tmppathname); }
   }

   /* The tmp file must be removed before releasing the config file lock! */
   fu_close_file(&fd_tmp, &fs_tmp);

   /* Close config file (this will also release the lock) */
   fu_close_file(&fd, &fs);

   if(main_debug)
   {
      printf("%s: %sStore to: %s\n", CFG_NAME, MAIN_ERR_PREFIX, confpathname);
   }

   /* Release memory for file pathnames */
   api_posix_free((void*) tmppathname);
   api_posix_free((void*) confpathname);

   return(res);
}


/* ========================================================================== */
/*! \brief Replace configuration string
 *
 * \param[in] cfg  Pointer to configuration array entry
 * \param[in] s    Pointer to new string
 *
 * \e cfg must point to a configuration entry of string type.
 * The current string will be replaced with \e s .
 *
 * \return
 * - 0 on success
 * - Negative value on error
 */

int  conf_string_replace(struct conf*  cfg, const char*  s)
{
   int  res = -1;
   size_t  len;
   char*  p;

   if(NULL != cfg && NULL != cfg->val.s && NULL != s)
   {
      if(CONF_TYPE_STRING == cfg->type)
      {
         len = strlen(s);
         p = api_posix_malloc(++len);
         if(NULL != p)
         {
            strcpy(p, s);
            secure_clear_string(cfg->val.s);
            api_posix_free((void*) cfg->val.s);
            cfg->val.s = p;
            res = 0;
         }
      }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Check integer value against lower and upper bounds
 *
 * \param[in] id   ID of integer value to check
 * \param[in] min  Lower limit
 * \param[in] max  Upper limit
 *
 * \attention The parameter \e id must be associated with an integer value!
 *
 * The integer value associated with \e id is compared against the specified
 * limits.
 * <br>
 * If the value is lower than \e min or greater than \e max , it is clamped to
 * the limits. Otherwise the value is unchanged.
 *
 * \return
 * - New value
 */

int  conf_integer_check(enum conf_entry  id, int  min, int max)
{
   if(min > config[id].val.i)
   {
      config[id].val.i = min;
   }

   if(max < config[id].val.i)
   {
      config[id].val.i = max;
   }

   return(config[id].val.i);
}


/*! @} */

/* EOF */
