/*
 * $Id: read_conf.c,v 1.22 1999/09/13 21:18:28 labovit Exp $
 */


#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdlib.h>
#include <mrt.h>

#include <read_conf.h>
#include <hdr_comm.h>

extern config_info_t ci;

static int find_token (char **x, char **y);
static void db_line (char *line, src_t *start);
static char *char_line (char *line, char *host);
static int port_line (char *line, int *port);
static char *free_mem (char *p);
static source_t *find_src_object (src_t *start, char *source);
static char *get_footer_msg (char *);
static char *myconcat (char *x, char *y);

/* Control the process of parsing the conf file and intializing
 * the 'ci' struct with relevant values.  We are interested in 
 * the following values:
 *
 * config value name    default
 * -----------------    -------
 * irrd_port            43
 * irrd_host            localhost
 * super_passwd         foo
 * submission_log_dir   'irr_directory' irrd.conf value if present
 *                      log_dir == NULL means no logging
 * pgp_dir              NULL ie, user's ~/.pgp is assumed
 * db_admin             NULL ie, do not email new maintainer requests
 * 'ci' (sources struc) empty list
 *
 * Function assumes 'ci' has bee set to all 0's.  Command line options
 * override the irrd.conf options so if a value is set then the corresponding
 * irrd.conf value will be ignored.
 *
 * Result:
 *   1 if irrd.conf file was opened successfully
 *   -1 otherwise
 *   intialize the 'ci' struct
 */
int parse_irrd_conf_file (char *config_fname, trace_t *tr) {
  FILE *fin;
  char buf[1024], *p, *q, *s, *irr_dir = NULL;
  int len;
  config_info_t temp_ci;
  src_t sstart;

  /* load the defaults */
  sstart.first = sstart.last = NULL;
  temp_ci.irrd_port = 43;
  temp_ci.irrd_host = strdup ("localhost");
  temp_ci.super_passwd = strdup ("piSFDzJu5e1wY"); /* foo */
  temp_ci.log_dir = NULL;  /* default is 'irr_directory' value */
  temp_ci.pgp_dir = NULL;
  temp_ci.db_admin = NULL; /* email addr to send new maintainer req's */
  temp_ci.footer_msg = NULL; /* footer text */
  if (ci.footer_msg != NULL) {
    if (ci.footer_msg[0] != (char ) '"') {
      strcpy (buf, "\"");
      strcat (buf, ci.footer_msg);
      strcat (buf, "\"");
      s = get_footer_msg (buf);
    }
    else
      s = get_footer_msg (ci.footer_msg);
    free (ci.footer_msg);
    ci.footer_msg = strdup (s);
  }
  
  /* load the default config file name */
  if (config_fname == NULL)
    config_fname = "/etc/irrd.conf";
  
  /* open the config file */
  if ((fin = fopen (config_fname, "r")) == NULL) {
    fprintf (stderr, "Error opening IRRd conf file \"%s\": %s, exit!\n",
             config_fname, strerror(errno));
    return -1;
  }
  
  /* pick off the value of relavent fields */
  while (fgets (buf, 1023, fin) != NULL) {
    p = q = buf;
    if (find_token (&p, &q) > 0) {
      len = (int) (q - p);
      if (len > 4 && !strncmp (p, "irr_database", len)) {
	db_line (q, &sstart);
	trace (NORM, tr, "Config irr_database %s\n", q);
      }
      else if (len > 4 && !strncmp (p, "irr_port", len))
	temp_ci.irrd_port = port_line (q, &temp_ci.irrd_port);
      else if (len > 4 && !strncmp (p, "irr_host", len))
	temp_ci.irrd_host = char_line (q, temp_ci.irrd_host);
      else if (len > 3 && !strncmp (p, "override_cryptpw", len))
	temp_ci.super_passwd = char_line (q, temp_ci.super_passwd);
      else if (len > 2 && !strncmp (p, "submission_log_dir", len))
	temp_ci.log_dir = char_line (q, temp_ci.log_dir);
      else if (len > 0 && !strncmp (p, "pgp_dir", len))
	temp_ci.pgp_dir = char_line (q, temp_ci.pgp_dir);
      else if (len > 5 && !strncmp (p, "irr_directory", len))
	irr_dir = char_line (q, irr_dir);
      else if (len > 3 && !strncmp (p, "db_admin", len))
	temp_ci.db_admin = char_line (q, temp_ci.db_admin);
      else if ((len > 4) && (!strncmp (p, "debug", len)))
	config_trace_local (tr, strdup (p));
      else if (ci.footer_msg == NULL &&
	       !strncmp (p, "response_footer", len))
	temp_ci.footer_msg = myconcat (temp_ci.footer_msg, get_footer_msg (q));
    }
  }

  /* If the user has specified command line options,
   * then use those instead of the irrd.conf options
   */
  if (ci.srcs == NULL)
    ci.srcs = sstart.first;

  if (ci.irrd_port == 0)
    ci.irrd_port = temp_ci.irrd_port;

  if (ci.irrd_host == NULL)
   ci.irrd_host = temp_ci.irrd_host;
  else
    free_mem (temp_ci.irrd_host);

  if (ci.super_passwd == NULL)
    ci.super_passwd = temp_ci.super_passwd;
  else
    free_mem (temp_ci.super_passwd);

  if (ci.footer_msg == NULL) {
    if (temp_ci.footer_msg != NULL) {
        ci.footer_msg = strdup (temp_ci.footer_msg);
  	free_mem (temp_ci.footer_msg);
    }
  } 

  /* what the hey, we only leak a little memory */
  if (ci.log_dir == NULL) {
    if (temp_ci.log_dir == NULL)
      ci.log_dir = irr_dir;
    else
      ci.log_dir = temp_ci.log_dir;
  }
  else {
    free_mem (irr_dir);
    free_mem (temp_ci.log_dir);
  }
  
  if (ci.pgp_dir == NULL)
    ci.pgp_dir = temp_ci.pgp_dir;
  else
    free_mem (temp_ci.pgp_dir);

  if (ci.db_admin == NULL)
    ci.db_admin = temp_ci.db_admin;
  else
    free_mem (temp_ci.db_admin);

  fclose (fin);
  return 1;
}


/* Given an irrd.conf line starting with 'irr_port', return
 * the port value.
 *
 * Return:
 *   the port value
 *   echo back *port if there is a missing token after
 *   'irr_port'
 */
int port_line (char *line, int *port) {
  char *p, *q;

  p = q = line;
  if (find_token (&p, &q) > 0) {
    *q = '\0';
    return atoi (p);
  }

  return *port;
}

/* Given an irrd.conf line starting with 'irr_host', return
 * the host value.
 *
 * Routine should be invoked as 'host = host_line (line, host);'
 * as 'host' is freed.  
 *
 * Routine was originally written for 'irrd_host' but can be used for
 * any char * valued conf datum.
 *
 * Return:
 *   char *host value for the irrd.conf file if there are no errors
 *   echo back input 'host' value if there is a missing token
 */
char *char_line (char *line, char *host) {
  char *p, *q;

  p = q = line;
  if (find_token (&p, &q) > 0) {
    *q = '\0';
    free_mem (host);
    return strdup (p);
  }
  else
    return host;
}

/* JW not needed
 *
 * Given an irrd.conf line starting with 'db_password', return
 * the password value.  The password can have ' ', '\t', '""', ...
 * anything except a '\n' for a legal char.  
 *
 * Routine should be invoked as 'host = host_line (line, passwd);'
 * as 'passwd' is freed.  User could have multiple passwords
 * in the conf file.  For the mulitple case use the last one.
 *
 * Return:
 *   char *passwd value for the irrd.conf file if there are no errors
 *   otherwise echo back input 'host' 
 *
char *passwd_line (char *line, char *passwd) {
  char *p, *q, *r;

  p = q = line;
  find the begining of the first token 
  if (find_token (&p, &q) > 0) {
    r = p;
     find the end of the last non-blank char 
    while (find_token (&p, &q) > 0);
    *q = '\0';
    free_mem (passwd);
    return strdup (r);
  }
  else
    return passwd;
}
*/

/* Given an irrd.conf line of 'irr_database' create a list
 * entry with the DB name and whether the DB is authoritative.
 *
 * Return:
 *   void
 *   a list element appended to list pointed to by 'start' with 
 *   the DB name and authoritative field initialized
 */
void db_line (char *line, src_t *start) {
  char *p, *q, *r, *source;
  int len, auth;
  source_t *obj;

  /* first find the DB name */
  p = q = line;
  if (find_token (&p, &q) > 0) {
    auth = 0;
    source = p;
    r = q;
    /* now find the keyword following the DB name */
    if (find_token (&p, &q) > 0) {
      *q = '\0';
      len = strlen (p);
      if (len > 1 && !strncmp (p, "authoritative", len))
	auth = 1;
    }

    *r = '\0';
    if ((obj = find_src_object (start, source)) != NULL) {
      if (auth)
	obj->authoritative = auth;
    }
    else {
      obj = create_source_obj (source, auth);
      add_src_obj (start, obj);
    }
  }

  return;
}

/* Find the source 'source' in the source list pointed
 * to by 'start'.
 *
 * Return:
 *   pointer to element if 'source' is found
 *   NULL otherwise
 */
source_t *find_src_object (src_t *start, char *source) {
  source_t *obj;

  obj = start->first;
  while (obj != NULL) {
    if (!strcasecmp (obj->source, source))
      break;
    obj = obj->next;
  }

  return obj;
}

/* Create a source_t list element with value 'source' and 
 * 'authoritative'.
 *
 * Return:
 *   void
 */
source_t *create_source_obj (char *source, int authoritative) {
  source_t *obj;
  char *z;

  if (source == NULL)
    return NULL;

  obj = (source_t *) malloc (sizeof (source_t));
  obj->source          = strdup (source);
  obj->authoritative   = authoritative;
  for (z = obj->source; z != NULL && *z != '\0'; *z = toupper ((int) *z), z++);

  return (obj);
}

/* Add a source_t element to the list pointed to by 'start'.
 *
 * Return:
 *   void
 */
void add_src_obj (src_t *start, source_t *obj) {

  obj->next = NULL;
  if (start->first == NULL)
    start->first = obj;
  else
    start->last->next = obj;

  start->last = obj;
}

/* JW later make this routine globally accessible; many routines use
 * this routine (ie, reduce code size).
 */
/* This routine finds a token in the string.  *x will
 * point to the first character in the string and *y will
 * point to the first character after the token.  A token
 * is a printable character string.  A '\n' is not considered
 * part of a legal token.  
 *   This function is rpsl-capable.  It will look for '#'s in
 * the string and assume everything after is a comment.
 * Invoke this routine by setting 'x' and 'y' to the beginning
 * of the target string like this: 'x = y = target_string;
 * if (find_token (&x, &y) > 0) ...
 * each successive call to find_token () will move the char 
 * pointers along.
 *
 * Return:
 *   1 if a token is found (*x points to token, *y first char after)
 *  -1 if no token is found (*x and *y are to be ignored)
 */
int find_token (char **x, char **y) {

  /* It's possible the target string is NULL 
   * or we are in an rpsl comment 
   */
  if (*y == NULL || **y == '!')
    return -1;

  *x = *y;
  /* find the start of a token, ie, first printable character */
  while (**x == ' ' || **x == '\t') (*x)++;

  if (**x == '\0' || **x == '!' || **x == '\n')
    return -1;

  /* find the first char at the end of the token */
  *y = *x + 1;
  while (**y != '\0' && (isgraph ((int) **y) && **y != '!')) (*y)++;

  return 1;  
}

/* 
 * Free the memory pointed to by *p.  p may be NULL.
 * Function should be invoked as 'x = free_mem (x)'
 *
 * Return:
 *  NULL
 */
char *free_mem (char *p) {

  if (p != NULL)
    free (p);
  
  return NULL;
}

/* Return a footer message line.  The line should
 * be enclosed in quotes, eg, "Welcome to IRRd.".
 * If quotes are not supplied, return the string
 * starting with the first non-blank character.
 *
 * Return:
 *   String enclosed in quotes minus the quotes if in form "..."
 *   String verbatim if quotes are missing
 *   NULL otherwise
 */
char *get_footer_msg (char *s) {
  char *q, *r;
  int i  = 0;
  char buf[1024];

  if (s == NULL)
    return NULL;

  /* Find the first printable character */
  for (; isspace ((int) *s); s++);

  /* we have a "..." enclosed string */
  if ((q = strchr (s, '"')) != NULL &&
       (r = strrchr (++q, '"')) != NULL) {
    for (; q < r && i < 1022; q++, i++) {
      if (*q == '\\' && *(q + 1) == 'n') {
	memcpy (&buf[i], "\n", 1);
	q++;
      }
      else
	memcpy (&buf[i], q, 1);
    }

    if (i == 0)
      return NULL;
    else {
      if (buf[i - 1] != '\n')
	memcpy (&buf[i++], "\n", 1);
      buf[i] = '\0';
      return strdup (buf);
    }
  }
  else {
    /* Return whatever the user has typed in 
     * starting with the first non-blank character
     */
    if (*s == '\0')
      return NULL;
    else {
      if (*(s + strlen (s) - 1) != '\n') {
	strcpy (buf, s);
	strcat (buf, "\n");
	return strdup (buf);
      }
      return strdup (s);
    }
  }
}

char *myconcat (char *x, char *y) {
  char buf[2048];

  if (x == NULL)
    buf[0] = '\0';
  else {
    strcpy (buf, x);
    free_mem (x);
  }

  if (y != NULL)
    strcat (buf, y);

  if (buf[0] == '\0')
    return NULL;
  else
    return strdup (buf);
}
