/* 
 * $Id: config_file.c,v 1.7 1999/03/19 02:18:53 labovit Exp $
 */

#include <ctype.h>
#include <stdarg.h>
#include <mrt.h>
#include <config_file.h>

/* globals */
config_t CONFIG;

char debug_config[255] = "No tracing enabled.";


void get_access_list_config (int num) {
  char *s;

  s = access_list_toa2 (num);
  config_add_output ("%s", s);
  /*Delete (s);*/
}


static void
config_call_module (config_module_t *module)
{
#ifdef HAVE_LIBPTHREAD
    if (module->flags & CF_WAIT) {
      pthread_mutex_lock (&CONFIG.mutex_cond_lock);
      module->call_fn (module->arg);
      pthread_cond_wait (&CONFIG.cond, &CONFIG.mutex_cond_lock);
      pthread_mutex_unlock (&CONFIG.mutex_cond_lock);
    }
    else
#endif /* HAVE_LIBPTHREAD */
      module->call_fn (module->arg);
}


void get_uii_password (char *password) {

  if ((UII->password == NULL) || (strlen (UII->password) == 0)) return;

  if (password == NULL)
    password = UII->password;

  if (UII->access_list > 0) 
    config_add_output ("password %s %d\r\n", password, UII->access_list);
  else
    config_add_output ("password %s\r\n", password);
  return;
}


int show_config (uii_connection_t *uii) {
  config_module_t *module;
  static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  
  pthread_mutex_lock (&mutex);

  LL_Iterate (CONFIG.ll_modules, module) {

    /* Why wasn't uii passed to config_call_module to be used in printing?
       If it were so, I think that show_config could be re-entrant.
       CONFIG.answer is shared, so I need lock/unlock here -- masaki */

    /* don't show password */
    if (module->call_fn == get_uii_password && UII->password) {
	void *arg = module->arg;
	module->arg = "********";
        config_call_module (module);
	module->arg = arg;
    }
    else {
        config_call_module (module);
    }

    /* we are a module like router_bgp and need a "!" delimiter */
    if (module->flags & CF_DELIM) {
        config_module_t *tmp_module;
        tmp_module = LL_GetNext (CONFIG.ll_modules, module);
        if ((tmp_module) && (tmp_module->call_fn != get_comment_config)) {
	    config_add_output ("!\r\n");
        }
    }
  }
    
  if (CONFIG.answer != NULL) {
    uii_add_bulk_output_raw (uii, CONFIG.answer);
    Delete (CONFIG.answer);
    CONFIG.answer = NULL;
  }
  pthread_mutex_unlock (&mutex);
  uii_send_bulk_data (uii);

  return 1;
}



int config_write (uii_connection_t *uii) {
  config_module_t *module;
  char *filename, *cp, *newline;
  FILE *fd;
  char yes[MAXLINE];

  /* we probably need to freeze config changes when we do this... i.e.
   * it would be bad if someone else was hacking the config file */

  if ((filename = CONFIG.filename) == NULL)
    return (-1);

  if (access (filename, F_OK) == 0) {
      int n;
      uii_send_data (uii, "Overwrite to %s (yes/no)? ", filename);
      n = read (uii->sockfd, yes, sizeof (yes));
      yes[n] = '\0';
      uii_send_data (uii, "%s\r\n", yes);
      if (n <= 0 || (yes[0] != 'y' && yes[0] != 'Y'))
	return (0);
  }

  if ((fd = fopen (filename, "w")) == NULL) {
    trace (NORM, CONFIG.trace, 
	   "Write to config file %s failed (%s)\n", filename,
	   strerror (errno));
    return (-1);
  }
  
  trace (NORM, CONFIG.trace, "Writing configuration file to %s\n", filename);
  uii_send_data (uii, "Writing to %s\r\n", filename);

  LL_Iterate (CONFIG.ll_modules, module) {
    config_call_module (module);

    /* we are a module like router_bgp and need a "!" delimiter */
    if (module->flags & CF_DELIM) {
      config_module_t *tmp_module;
      tmp_module = LL_GetNext (CONFIG.ll_modules, module);
      if ((tmp_module) && (tmp_module->call_fn != get_comment_config)) {
	config_add_output ("!\r\n");
      }
    }
  }

  cp = CONFIG.answer;
  while ((cp - CONFIG.answer) < CONFIG.answer_len) {
    int len;
    if ((newline = strchr(cp, '\n')) == NULL) {
      fwrite (cp, 1, strlen (cp), fd);
      fwrite ("\n", 1, 1, fd);
      break;
    }
    len = newline - cp;
    if (newline > CONFIG.answer && newline[-1] == '\r')
        len--;
    fwrite (cp, 1, len, fd);
    fwrite ("\n", 1, 1, fd);
    cp = newline + 1;
  }
  
  /* fwrite (CONFIG.answer, 1, strlen (CONFIG.answer), fd);*/
  Delete (CONFIG.answer);
  CONFIG.answer = NULL;
  fclose (fd);
  return (1);
}



void get_debug_config (void) {
  int first = 1;

  if (CONFIG.trace->flags & TR_ALL) {
    first = 0;
    config_add_output ("debug all ");
  }
  else {
    if (CONFIG.trace->flags & TR_INFO) {
      if (first != 1) config_add_output ("\r\n");
      config_add_output ("debug info ");
    }
    if (CONFIG.trace->flags & NORM) {
      if (first != 1) config_add_output ("\r\n");
      config_add_output ("debug norm ");
    }
    if (CONFIG.trace->flags & TRACE) {
      if (first != 1) config_add_output ("\r\n");
      config_add_output ("debug trace ");
    }
    if (CONFIG.trace->flags & TR_PARSE) {
      if (first != 1) config_add_output ("\r\n");
      config_add_output ("debug trace ");  
    }
    if (CONFIG.trace->flags & TR_PACKET) {
      if (first != 1) config_add_output ("\r\n");
      config_add_output ("debug packet ");
    }
    if (CONFIG.trace->flags & TR_STATE) {
      if (first != 1) config_add_output ("\r\n");
      config_add_output ("debug state ");
    }
  }

  if (first == 1) return; /* debugging is not turned on.... */

  config_add_output ("%s ", CONFIG.trace->logfile->logfile_name);

  if (!strcmp ("stdout", CONFIG.trace->logfile->logfile_name)) 
    config_add_output ("\r\n");
  else
    config_add_output ("%d\r\n", CONFIG.trace->logfile->max_filesize);
		       
  return;
}




void get_comment_config (char *comment) {
  config_add_output ("%s\r\n", comment);
  return;
}

void get_comment_config2 (char *comment) {
  char *cp, *cp2, line[MAXLINE];

  cp = comment;
  while ((cp2 = strchr (cp, '\n')) != NULL) {
      memcpy (line, cp, cp2 - cp + 1 ); /* up to newline */
      line[cp2 - cp + 1] = '\0';
      config_add_output ("!%s", line);
      cp = cp2 + 1;
  }
  if (*cp) {
      config_add_output ("!%s\n", cp);
  }
  return;
}


void get_uii_redirect () {
  config_add_output ("redirect %s\r\n", UII->redirect);
  return;
}



/*
 * config_add_module
 * Modules store configuration file information as a linked list of modules.
 * Each module has a call_back function and argument to display that line of
 * the configuration file. Since some configuration information may be stored in
 * memory used by threads, we may need to schedule the call_back routine to store
 * data in the CONFIG.answer location. The wait_flag argument specifies whether we
 * need to wait for another thread to fill in the data.
 */
int config_add_module (int flags, char *name, void_fn_t call_fn, void *arg) {
  config_module_t *module;

  /* need to make sure we don't pick up multiple times, i.e. for access list */
  LL_Iterate (CONFIG.ll_modules, module) {
    if ((module->call_fn == call_fn) && (module->arg == arg))
      return (0);
  }
  
  module = New (config_module_t);
  module->call_fn = call_fn;
  module->arg = arg;
  module->flags = flags;
  if (name) /* I don't know that all calls supply the name -- masaki */
     module->name = strdup (name);

  LL_Add (CONFIG.ll_modules, module);
  return (1);
}


int config_del_module (int flags, char *name, void_fn_t call_fn, void *arg) {
  config_module_t *module;

  LL_Iterate (CONFIG.ll_modules, module) {
    if (module->name && name && strcmp (module->name, name) != 0)
	continue;
    if ((module->call_fn == call_fn) && (module->arg == arg)) {

	if (module->name)
	    Delete (module->name);

        if (module->call_fn == get_uii_password) {
	    LL_Remove (CONFIG.ll_modules, module);
	    Delete (module);
	    return (1);
	}

        config_call_module (module);

	/* save it as a comment */
        module->call_fn = get_comment_config2;
        module->arg = CONFIG.answer;
	CONFIG.answer = NULL;
        module->flags = 0;
        module->name = strdup ("comment");

	return (1);
    }
  }
  return (0);
}


int config_comment (uii_connection_t *uii) {
  char *tmp;

  /* if this is a blank line comment, assume we have dropped out of a higher 
   * state, though it might not be to return to UII_CONFIG...
   * hmm... we need to remember immediate lower state (do we need a stack?)
   * we treat comments longer than one character as a regular comment and
   * don't change state
   */
  if (strlen (uii->cp) < 2) {
    /*trace (TR_PARSE, CONFIG.trace, "COMMENT -- STATE EOF\r\n");*/
    uii->state = UII_CONFIG;
  }

  tmp = NewArray (char, strlen (uii->cp) + 4);
  sprintf (tmp, "!%s", uii->cp);
  /*trace (TR_PARSE, CONFIG.trace, "CONFIG COMMENT %s\r\n", tmp);*/
  config_add_module (0, "comment", get_comment_config, tmp);
  return (1);
}



void get_config_uii_port () {
  if (UII->disabled) 
    config_add_output ("no uii_port\r\n", UII->port);
  else
    config_add_output ("uii_port %d\r\n", UII->port);
}


int no_config_uii_port (uii_connection_t *uii) {
  set_uii (UII, UII_DISABLED, 0);
  trace (NORM, CONFIG.trace, "Disabled UII\n");
  config_del_module (0, "uii_port", get_config_uii_port, NULL);
  return (1);
}

int config_uii_port (uii_connection_t *uii, u_int port) {
  set_uii (UII, UII_PORT, port, 0);
  trace (NORM, CONFIG.trace, "CONFIG uii_port %d\n", port);
  config_add_module (0, "uii_port", get_config_uii_port, NULL);
  return (1);
}


void get_config_uii_addr () {
    config_add_output ("uii_addr %s\r\n", prefix_toa (UII->prefix));
}


int no_config_uii_addr (uii_connection_t *uii) {
  /* XXX need to reopen uii */
  config_del_module (0, "uii_addr", get_config_uii_addr, NULL);
  return (1);
}

int config_uii_addr (uii_connection_t *uii, prefix_t *prefix) {
  set_uii (UII, UII_ADDR, prefix, 0);
  trace (NORM, CONFIG.trace, "CONFIG uii_addr %s\n", prefix_toa (prefix));
  config_add_module (0, "uii_addr", get_config_uii_addr, NULL);
  Deref_Prefix (prefix);
  return (1);
}


/* config_password
 * config password %s [%d]
 */
int config_password (uii_connection_t *uii, char *password, int num_optional,
		     int access_list) {
  char *cp;

  cp = strip_spaces (password);

  UII->initial_state = 0; /* password required */
  set_uii (UII, UII_PASSWORD, cp, 0);
  
  if (num_optional > 0) {
    set_uii (UII, UII_ACCESS_LIST, access_list, 0);
  }

  config_add_module (0, "password", get_uii_password, NULL);
  Delete (password);
  return 1;
}



/* no_config_password
 * no config password 
 */
int no_config_password (uii_connection_t *uii) {
  config_notice (NORM, uii, "CONFIG: ** No password specified! Telnet disallowed! **\r\n");
  set_uii (UII, UII_PASSWORD, "none", 0);
  return 1;
}


/* no_config_redirect
 * no direct
 */
int no_config_redirect (uii_connection_t *uii) {
  if (UII->redirect)
    Delete (UII->redirect);
  UII->redirect = NULL;

  return (1);
}


/* config_redirect
 * redirect %s
 * Allow redirection to files in this directory
 */
int config_redirect (uii_connection_t *uii, char *directory) {
  if (UII->redirect)
    Delete (UII->redirect);
  UII->redirect = directory;
  config_add_module (0, "redirect", get_uii_redirect, NULL);
  config_notice (NORM, uii, "CONFIG: redirect %s\r\n", directory);
  return 1;
}



/* start_config
 * call by uii thread. 
 */
int start_config (uii_connection_t *uii) {

  if (pthread_mutex_trylock (&CONFIG.mutex_lock) != 0) {
    config_notice (NORM, uii, "CONFIG mode is already locked\r\n");
    return (-1);
  }
  assert (uii->mutex_lock_p == NULL);
  uii->mutex_lock_p = &CONFIG.mutex_lock;

#ifdef notdef
  config_notice (NORM, uii, "** WARNING ** \r\n");
  config_notice (NORM, uii, "Telnet configuration is still unstable in this version\r\n");
  config_notice (NORM, uii, "of MRT. We strongly recommend editing the config \r\n");
  config_notice (NORM, uii, "file %s by hand\r\n",
		 MRT->config_file_name ? MRT->config_file_name : "unknown");
#endif
  
  uii->state = UII_CONFIG;
  return 0;
}


void end_config (uii_connection_t *uii) {
  assert (uii->mutex_lock_p);
  pthread_mutex_unlock (uii->mutex_lock_p);
  uii->mutex_lock_p = NULL;
}


int
get_alist_options (char *options, prefix_t **wildcard, int *refine, int *exact)
{
    if (options) {
	char *line = options, *opt;
	prefix_t *pp;

	/* this allows more than one occurence of the same option */
        while ((opt = uii_parse_line (&line)) != NULL) {
	    if (strcasecmp (opt, "refine") == 0) {
		*refine = 1;
	    }
	    else if (strcasecmp (opt, "exact") == 0) {
		*exact = 1;
	    }
	    else if ((pp = ascii2prefix (0, opt)) != NULL) {
	        if (*wildcard)
		    Deref_Prefix (*wildcard);
		*wildcard = pp;
	    }
	    else {
	        if (*wildcard)
		    Deref_Prefix (*wildcard);
		return (-1);
	    }
	}
        return (0);
    }
    return (0);
}


/* 
 * config_access_list
 */
int 
config_access_list (uii_connection_t * uii, int num, 
		    char *permit_or_deny, prefix_t *prefix, char *options)
{
    int permit, refine = 0, exact = 0;
    prefix_t *wildcard = NULL;
    char tmpx[MAXLINE];

    permit = (strcasecmp (permit_or_deny, "permit") == 0);
    Delete (permit_or_deny);

    if (options) {
        get_alist_options (options, &wildcard, &refine, &exact);
	Delete (options);
    }

    if (num <= 0 && num >= MAX_ALIST) {
        config_notice (TR_ERROR, uii, 
	               "CONFIG invalid invalid access-list number %d\r\n", 
			num);
        return (-1);
    }

    if (prefix) {
	if (wildcard)  {
	    sprintf (tmpx, "%s %s", prefix_toax (prefix), 
		     prefix_toa (wildcard));
	}
	else {
	    sprintf (tmpx, "%s", prefix_toax (prefix));
	}
	if (exact) strcat (tmpx, " exact");
	if (refine) strcat (tmpx, " refine");
    }
    else {
	sprintf (tmpx, "all");
    }

    /* Adding */
    config_notice (TR_INFO, uii, "CONFIG access-list %d %s %s\r\n",
		   num, (permit) ? "permit" : "deny", tmpx);
    if (add_access_list (num, permit, prefix, wildcard, exact, refine) < 1) {
        config_notice (TR_ERROR, uii, 
		       "CONFIG: Error -- access list %d not added\r\n", num);
        return (-1);
    }
    config_add_module (0, "access-list", get_access_list_config, (void *)num);
    return (1);
}


int 
config_access_list_all (uii_connection_t * uii, int num, 
		        char *permit_or_deny) 
{
    return (config_access_list (uii, num, permit_or_deny, NULL, NULL));
}


/* parse_debug
 * debug (norm|trace) %s [%d]
 * Set GLOBAL trace values
 */
int parse_debug (uii_connection_t * uii, trace_t *tr) {
    char *token, *line;
    u_long flag = 0;

    line = uii->cp;

    /* get flag */
    if ((token = (char *) uii_parse_line (&line)) == NULL) {
        uii_send_data (uii, 
		"Usage: debug trace_flag [file] [max_filesize]\r\n");
        return (-1);
    }
    flag = trace_flag (token);
    if (flag == 0) {
        uii_send_data (uii, 
		"Usage: debug trace_flag [file] [max_filesize]\r\n");
        return (-1);
    }

    trace (NORM, CONFIG.trace, "CONFIG debug %s\n", line);

    sprintf(debug_config, "Tracing %s output", token);

    set_trace (tr, TRACE_ADD_FLAGS, flag, NULL);

    /* get file name (OPTIONAL) */
    if ((token = (char *) uii_parse_line (&line)) == NULL) {
	strcat(debug_config, " to default MRT trace file /tmp/mrt.log");
        return (1);
    }

    set_trace (tr, TRACE_LOGFILE, token, NULL);
    strcat(debug_config, " to ");
    strcat(debug_config, token);

    /* get max. trace filesize (OPTIONAL) */
    if ((token = (char *) uii_parse_line (&line)) == NULL)
        return (1);

    set_trace (tr, TRACE_MAX_FILESIZE, atoi(token), NULL);
    strcat(debug_config, ", truncating log at ");
    strcat(debug_config, token);
    strcat(debug_config, " bytes");

    /* making global changes -- change all other trace routines */
    set_trace_global (tr);

    return (1);
}


int config_debug (uii_connection_t *uii) {
  parse_debug (uii, CONFIG.trace);
  config_add_module (0, "debug", get_debug_config, NULL);
  return 0;
}


#if 0
void wait_config_data (void_fn_t call_fn, void *arg) {
  char *tmp;

#ifdef HAVE_LIBPTHREAD
  pthread_mutex_lock (&CONFIG.mutex_cond_lock);
#endif /* HAVE_LIBPTHREAD */

  call_fn (arg);

#ifdef HAVE_LIBPTHREAD
  pthread_cond_wait (&CONFIG.cond, &CONFIG.mutex_cond_lock);
  pthread_mutex_unlock (&CONFIG.mutex_cond_lock);
#endif /* HAVE_LIBPTHREAD */

  return;
}
#endif

/* Okay, signal the waiting uii that data is ready for it */
void signal_config_data_ready () {

#ifdef HAVE_LIBPTHREAD
  pthread_mutex_lock (&CONFIG.mutex_cond_lock);
  pthread_cond_signal (&CONFIG.cond);
  pthread_mutex_unlock (&CONFIG.mutex_cond_lock);
#endif /* HAVE_LIBPTHREAD */
  return;
}

int config_add_output (char *format, ...) {
   va_list args;
   int len;

   if (CONFIG.answer == NULL) {
    CONFIG.answer = NewArray (char, UII_OUTPUT_BUFFER_SIZE);
    CONFIG.cp = CONFIG.answer;
    CONFIG.answer_len = 0;
   }

  va_start (args, format);
  vsprintf (CONFIG.cp, format, args);  

  len = strlen (CONFIG.cp);
  CONFIG.cp += len;
  CONFIG.answer_len += len;

  return (1);
}


/* config_notice
 * Convenience function to send error message both to log file
 * and out to uii
 */
void config_notice (int trace_flag, uii_connection_t *uii, char *format, ...)
{
    va_list args;
    char tmp[MAXLINE];

    va_start (args, format);
    vsprintf (tmp, format, args);  /* concatenate */
    user_notice (trace_flag, CONFIG.trace, uii, "%s", tmp);
}


int init_config (trace_t *tr) {
  CONFIG.ll_modules = LL_Create (0);
#ifdef HAVE_LIBPTHREAD
  pthread_cond_init (&CONFIG.cond, NULL);
  pthread_mutex_init (&CONFIG.mutex_cond_lock, NULL);
  pthread_mutex_init (&CONFIG.mutex_lock, NULL);
#endif /* HAVE_LIBPTHREAD */
  CONFIG.trace = tr;

  /* config top level */
  uii_add_command2 (UII_CONFIG, COMMAND_NORM, "password %s [%d]", config_password, 
		    "Set telnet interface password");
  uii_add_command2 (UII_CONFIG, COMMAND_NORM, "no password", no_config_password, 
		    "Delete password");


  uii_add_command2 (UII_CONFIG, COMMAND_NORM, "uii_port %d", config_uii_port,
		    "Set telnet interface port number");
  uii_add_command2 (UII_CONFIG, COMMAND_NORM, "no uii_port", no_config_uii_port,
		    "Disable telnet interface port number");
  uii_add_command2 (UII_CONFIG, COMMAND_NORM, "uii_addr %M", config_uii_addr,
		    "Set telnet interface address");
  uii_add_command2 (UII_CONFIG, COMMAND_NORM, "no uii_addr", no_config_uii_addr,
		    "Delete telnet interface address");

  /*uii_add_command2 (UII_CONFIG, COMMAND_NORM, "debug %S", config_debug, 
    "Configure logging information");*/
  uii_add_command2 (UII_CONFIG, COMMAND_NORM, 
			"access-list %d (permit|deny) %m %S", 
		    config_access_list, "IP based acl lists");
  uii_add_command2 (UII_CONFIG, COMMAND_NORM, 
			"access-list %d (permit|deny) all", 
		    config_access_list_all, "IP based acl lists");

  uii_add_command2 (UII_CONFIG, COMMAND_NORM, "redirect %s", config_redirect,
                    "Set redirect directory");
  uii_add_command2 (UII_CONFIG, COMMAND_NORM, "no redirect", no_config_redirect,
                    "Disable redirection");

  uii_add_command2 (UII_CONFIG_ALL, COMMAND_NODISPLAY | COMMAND_MATCH_FIRST, 
		       "#", config_comment, NULL);
  uii_add_command2 (UII_CONFIG_ALL, COMMAND_NODISPLAY  | COMMAND_MATCH_FIRST, 
		       "!", config_comment, NULL);

  return (1);
}

/* config_file_file
 * a backwards compatibility kludge
 */
int config_from_file (trace_t *tr, char *filename) {
  init_config(tr);
  return (config_from_file2 (tr, filename));
}

/* config_from_file2
 */
int config_from_file2 (trace_t *tr, char *filename) {
  uii_connection_t *uii;
  int errors = 0;
  FILE *fd;

  if (filename == NULL) return (-1);

  MRT->config_file_name = strdup (filename);
  CONFIG.filename = strdup (filename);
  uii = New (uii_connection_t);
  uii->sockfd = -1;
  uii->sockfd_out = -1;
  uii->mutex_lock_p = NULL;

  if ((filename == NULL) || ((fd = fopen (filename, "r")) == NULL)) {
    trace (NORM, CONFIG.trace, "Failed to open config file %s\n", filename);
    trace (NORM, CONFIG.trace, "Using default values only.\n");
	  Delete (uii);
          return (-1);
  }

  trace (NORM, CONFIG.trace, 
	 "Opened the config file %s\n", filename);

  start_config (uii);
  CONFIG.line = 0;

  while (fgets (uii->buffer, sizeof (uii->buffer)-1, fd) != NULL) {
    CONFIG.line++;
    if (uii_proccess_command (uii) < 0) {
      errors++;
      trace (ERROR, CONFIG.trace, "CONFIG ERROR at line %d\n", CONFIG.line);
      trace (ERROR, CONFIG.trace, "\t%s\n", uii->buffer);
      if (!CONFIG.ignore_errors) exit (0);
    }
  }
  
  fclose (fd);
  if (uii->ll_tokens != NULL)
    LL_Destroy (uii->ll_tokens);
  end_config (uii);
  Delete (uii);

  /* don't use fatal for now while developing code... just ignore */
  if (errors) {
    trace (NORM, CONFIG.trace, "%d CONFIG ERROR(s)\n", errors);
  }

  return (1);
}
