/* tn5250 -- an implentation of the 5250 telnet protocol.
 * Copyright (C) 2000 Jason M. Felice
 *
 * This file is part of TN5250.
 *
 * TN5250 is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * TN5250 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "tn5250-private.h"

#define MAX_PREFIX_STACK 50

static void tn5250_config_str_destroy (Tn5250ConfigStr * This);
static Tn5250ConfigStr *tn5250_config_str_new (const char *name,
					       const char *value);
static Tn5250ConfigStr *tn5250_config_get_str (Tn5250Config * This,
					       const char *name);
static void tn5250_config_replace_vars (char *buf, int maxlen);
static void tn5250_config_replacedata (const char *from, const char *to,
				       char *line, int maxlen);
#ifdef WIN32
#define snprintf _snprintf
#define vsnprintf _vsnprintf
#endif

/*** Tn5250ConfigStr ***/

static void
tn5250_config_str_destroy (Tn5250ConfigStr * This)
{
  if (This->name != NULL)
    free (This->name);
  if (This->value != NULL)
    free (This->value);
  free (This);
  return;
}

static Tn5250ConfigStr *
tn5250_config_str_new (const char *name, const char *value)
{
  Tn5250ConfigStr *This = tn5250_new (Tn5250ConfigStr, 1);
  if (This == NULL)
    return NULL;

  This->name = (char *) malloc (strlen (name) + 1);
  if (This->name == NULL)
    {
      free (This);
      return NULL;
    }
  strcpy (This->name, name);

  This->value = (char *) malloc (strlen (value) + 1);
  if (This->value == NULL)
    {
      free (This->name);
      free (This);
      return NULL;
    }
  strcpy (This->value, value);

  return This;
}

/*** Tn5250Config ***/

Tn5250Config *
tn5250_config_new ()
{
  Tn5250Config *This = tn5250_new (Tn5250Config, 1);
  if (This == NULL)
    return NULL;

  This->ref = 1;
  This->vars = NULL;

  return This;
}

Tn5250Config *
tn5250_config_ref (Tn5250Config * This)
{
  This->ref++;
  return This;
}

void
tn5250_config_unref (Tn5250Config * This)
{
  if (--This->ref == 0)
    {
      Tn5250ConfigStr *iter, *next;

      /* Destroy all vars. */
      if ((iter = This->vars) != NULL)
	{
	  do
	    {
	      next = iter->next;
	      tn5250_config_str_destroy (iter);
	      iter = next;
	    }
	  while (iter != This->vars);
	}
      free (This);
    }
  return;
}

int
tn5250_config_load (Tn5250Config * This, const char *filename)
{
  FILE *f;
  char buf[128];
  char *scan;
  int len;
  int prefix_stack_size = 0;
  char *prefix_stack[MAX_PREFIX_STACK];
  char *list[100];
  int done;
  int curitem;
  int loop;
  int name_len, i;
  char *name;

  /* It is not an error for a config file not to exist. */
  if ((f = fopen (filename, "r")) == NULL)
    return errno == ENOENT ? 0 : -1;

  while (fgets (buf, sizeof (buf) - 1, f))
    {
      buf[sizeof (buf) - 1] = '\0';
      if (strchr (buf, '\n'))
	*strchr (buf, '\n') = '\0';

      tn5250_config_replace_vars (buf, sizeof (buf));

      scan = buf;
      while (*scan && isspace (*scan))
	scan++;

      if (*scan == '#' || *scan == '\0')
	continue;

      if (*scan == '+')
	{
	  scan++;
	  while (*scan && isspace (*scan))
	    scan++;
	  len = strlen (scan) - 1;
	  while (len > 0 && isspace (scan[len]))
	    scan[len--] = '\0';

	  for (i = 0, name_len = len + 3; i < prefix_stack_size; i++)
	    {
	      name_len += strlen (prefix_stack[i]) + 1;
	    }
	  if ((name = (char *) malloc (name_len)) == NULL)
	    goto config_error;
	  name[0] = '\0';
	  for (i = 0; i < prefix_stack_size; i++)
	    {
	      strcat (name, prefix_stack[i]);
	      strcat (name, ".");
	    }
	  strcat (name, scan);
	  tn5250_config_set (This, name, "1");
	  free (name);

	}
      else if (*scan == '-')
	{
	  scan++;
	  while (*scan && isspace (*scan))
	    scan++;
	  len = strlen (scan) - 1;
	  while (len > 0 && isspace (scan[len]))
	    scan[len--] = '\0';
	  for (i = 0, name_len = len + 3; i < prefix_stack_size; i++)
	    {
	      name_len += strlen (prefix_stack[i]) + 1;
	    }
	  if ((name = (char *) malloc (name_len)) == NULL)
	    goto config_error;
	  name[0] = '\0';
	  for (i = 0; i < prefix_stack_size; i++)
	    {
	      strcat (name, prefix_stack[i]);
	      strcat (name, ".");
	    }
	  strcat (name, scan);
	  tn5250_config_set (This, name, "0");
	  free (name);

	}
      else if (strchr (scan, '='))
	{

	  /* Set item. */

	  len = 0;
	  while (scan[len] && !isspace (scan[len]) && scan[len] != '=')
	    len++;
	  if (len == 0)
	    goto config_error;	/* Missing variable name. */

	  for (i = 0, name_len = len + 3; i < prefix_stack_size; i++)
	    {
	      name_len += strlen (prefix_stack[i]) + 1;
	    }

	  if ((name = (char *) malloc (name_len)) == NULL)
	    goto config_error;

	  name[0] = '\0';
	  for (i = 0; i < prefix_stack_size; i++)
	    {
	      strcat (name, prefix_stack[i]);
	      strcat (name, ".");
	    }
	  name_len = strlen (name) + len;
	  memcpy (name + strlen (name), scan, len);
	  name[name_len] = '\0';

	  scan += len;
	  while (*scan && isspace (*scan))
	    scan++;

	  if (*scan != '=')
	    {
	      free (name);
	      goto config_error;
	    }
	  scan++;

	  while (*scan && isspace (*scan))
	    scan++;

	  if (*scan == '[')
	    {
	      done = 0;
	      curitem = 0;
	      while (!done)
		{
		  fgets (buf, sizeof (buf) - 1, f);
		  buf[sizeof (buf) - 1] = '\0';
		  if (strchr (buf, '\n'))
		    *strchr (buf, '\n') = '\0';

		  scan = buf;

		  while (*scan && isspace (*scan))
		    scan++;

		  if (*scan == ']')
		    {
		      done = 1;
		    }
		  else
		    {
		      list[curitem] = malloc (strlen (scan) + 1);
		      strcpy (list[curitem], scan);
		      curitem++;
		    }

		}
	      for (loop = 0; loop < curitem; loop++)
		{
		  printf ("%s\n", list[loop]);
		}


	    }
	  else
	    {

	      len = strlen (scan) - 1;
	      while (len > 0 && isspace (scan[len]))
		scan[len--] = '\0';

	      tn5250_config_set (This, name, scan);
	      free (name);
	    }
	}
      else if (strchr (scan, '{'))
	{

	  /* Push level. */

	  len = 0;
	  while (scan[len] && !isspace (scan[len]) && scan[len] != '{')
	    len++;
	  if (len == 0)
	    goto config_error;	/* Missing section name. */

	  TN5250_ASSERT (prefix_stack_size < MAX_PREFIX_STACK);
	  prefix_stack[prefix_stack_size] = (char *) malloc (len + 1);
	  TN5250_ASSERT (prefix_stack[prefix_stack_size] != NULL);

	  memcpy (prefix_stack[prefix_stack_size], scan, len);
	  prefix_stack[prefix_stack_size][len] = '\0';

	  prefix_stack_size++;

	}
      else if (*scan == '}')
	{

	  /* Pop level. */

	  scan++;
	  while (*scan && isspace (*scan))
	    scan++;
	  if (*scan != '#' && *scan != '\0')
	    goto config_error;	/* Garbage after '}' */

	  TN5250_ASSERT (prefix_stack_size > 0);
	  prefix_stack_size--;

	  if (prefix_stack[prefix_stack_size] != NULL)
	    free (prefix_stack[prefix_stack_size]);

	}
      else
	goto config_error;	/* Garbage line. */
    }

  if (prefix_stack_size != 0)
    goto config_error;

  fclose (f);
  return 0;

config_error:

  while (prefix_stack_size > 0)
    {
      prefix_stack_size--;
      if (prefix_stack[prefix_stack_size] != NULL)
	free (prefix_stack[prefix_stack_size]);
    }
  fclose (f);
  return -1;
}

#ifdef __WIN32__
/*
 *    Load default configuration files.
 *    Win32 version -- Looks for a file in the same dir as the .exe
 *            file called "tn5250rc".
 */
int
tn5250_config_load_default (Tn5250Config * This)
{
#define PATHSIZE 1024
  LPTSTR apppath;
  LPTSTR dir;
  DWORD rc;
  DWORD len;
  LPTSTR lpMsgBuf;

  apppath = malloc (PATHSIZE + 1);
  TN5250_ASSERT (apppath != NULL);

  if (GetModuleFileName (NULL, apppath, PATHSIZE) < 1)
    {
      FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
		     FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError (),
		     MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), lpMsgBuf, 0,
		     NULL);
      TN5250_LOG (("GetModuleFileName Error: %s\n", lpMsgBuf));
      MessageBox (NULL, lpMsgBuf, "TN5250", MB_OK);
      LocalFree (lpMsgBuf);
      return -1;
    }

  if (strrchr (apppath, '\\'))
    {
      len = strrchr (apppath, '\\') - apppath;
      apppath[len + 1] = '\0';
    }

  dir = malloc (strlen (apppath) + 15);
  TN5250_ASSERT (apppath != NULL);

  strcpy (dir, apppath);
  strcat (dir, "tn5250rc");
  free (apppath);

  TN5250_LOG (("Config File = %s\n", dir));

  rc = tn5250_config_load (This, dir);
  free (dir);
  return rc;
}

#else

/*
 *    Load default configuration files.
 */
int
tn5250_config_load_default (Tn5250Config * This)
{
  struct passwd *pwent;
  char *dir;
  int ec;

  if (tn5250_config_load (This, SYSCONFDIR "/tn5250rc") == -1)
    {
      perror (SYSCONFDIR "/tn5250rc");
      return -1;
    }

  pwent = getpwuid (getuid ());
  if (pwent == NULL)
    {
      perror ("getpwuid");
      return -1;
    }

  dir = (char *) malloc (strlen (pwent->pw_dir) + 12);
  if (dir == NULL)
    {
      perror ("malloc");
      return -1;
    }

  strcpy (dir, pwent->pw_dir);
  strcat (dir, "/.tn5250rc");
  if ((ec = tn5250_config_load (This, dir)) == -1)
    perror (dir);
  free (dir);
  return ec;
}
#endif

int
tn5250_config_parse_argv (Tn5250Config * This, int argc, char **argv)
{
  int argn = 1;

  /* FIXME: Scan and promote entries first, then parse individual
   * settings.   This is so that we can use a particular session but
   * override one of it's settings. */

  while (argn < argc)
    {
      if (argv[argn][0] == '+')
	{
	  /* Set boolean option. */
	  char *opt = argv[argn] + 1;
	  tn5250_config_set (This, opt, "1");
	}
      else if (argv[argn][0] == '-')
	{
	  /* Clear boolean option. */
	  char *opt = argv[argn] + 1;
	  tn5250_config_set (This, opt, "0");
	}
      else if (strchr (argv[argn], '='))
	{
	  /* Set string option. */
	  char *opt;
	  char *val = strchr (argv[argn], '=') + 1;
	  opt = (char *) malloc (strchr (argv[argn], '=') - argv[argn] + 3);
	  if (opt == NULL)
	    return -1;		/* FIXME: Produce error message. */
	  memcpy (opt, argv[argn], strchr (argv[argn], '=') - argv[argn] + 1);
	  *strchr (opt, '=') = '\0';
	  tn5250_config_set (This, opt, val);
	}
      else
	{
	  /* Should be profile name/connect URL. */
	  tn5250_config_set (This, "host", argv[argn]);
	  tn5250_config_promote (This, argv[argn]);
	}
      argn++;
    }

  return 0;
}

const char *
tn5250_config_get (Tn5250Config * This, const char *name)
{
  Tn5250ConfigStr *str = tn5250_config_get_str (This, name);
  return (str == NULL ? NULL : str->value);
}

int
tn5250_config_get_bool (Tn5250Config * This, const char *name)
{
  const char *v = tn5250_config_get (This, name);
  return (v == NULL ? 0 :
	  !(!strcmp (v, "off")
	    || !strcmp (v, "no")
	    || !strcmp (v, "0") || !strcmp (v, "false")));
}

int
tn5250_config_get_int (Tn5250Config * This, const char *name)
{
  const char *v = tn5250_config_get (This, name);

  if (v == NULL)
    {
      return 0;
    }
  else
    {
      return (atoi (v));
    }

}

void
tn5250_config_set (Tn5250Config * This, const char *name, const char *value)
{
  Tn5250ConfigStr *str = tn5250_config_get_str (This, name);

  if (str != NULL)
    {
      if (str->value != NULL)
	free (str->value);
      str->value = (char *) malloc (strlen (value) + 1);
      TN5250_ASSERT (str->value != NULL);
      strcpy (str->value, value);
      return;
    }

  str = tn5250_config_str_new (name, value);
  if (This->vars == NULL)
    This->vars = str->next = str->prev = str;
  else
    {
      str->next = This->vars;
      str->prev = This->vars->prev;
      str->next->prev = str;
      str->prev->next = str;
    }
}

void
tn5250_config_unset (Tn5250Config * This, const char *name)
{
  Tn5250ConfigStr *str;

  if ((str = tn5250_config_get_str (This, name)) == NULL)
    return;			/* Not found */

  if (This->vars == str)
    This->vars = This->vars->next;
  if (This->vars == str)
    This->vars = NULL;
  else
    {
      str->next->prev = str->prev;
      str->prev->next = str->next;
    }
  tn5250_config_str_destroy (str);
}

/*
 *    Copy variables prefixed with `prefix' to variables without `prefix'.
 */
void
tn5250_config_promote (Tn5250Config * This, const char *prefix)
{
  Tn5250ConfigStr *iter;
  if ((iter = This->vars) == NULL)
    return;
  do
    {
      if (strlen (prefix) <= strlen (iter->name) + 2
	  && !memcmp (iter->name, prefix, strlen (prefix))
	  && iter->name[strlen (prefix)] == '.')
	{
	  tn5250_config_set (This, iter->name + strlen (prefix) + 1,
			     iter->value);
	}
      iter = iter->next;
    }
  while (iter != This->vars);
}

static Tn5250ConfigStr *
tn5250_config_get_str (Tn5250Config * This, const char *name)
{
  Tn5250ConfigStr *iter;

  if ((iter = This->vars) == NULL)
    return NULL;		/* No vars */

  do
    {
      if (!strcmp (iter->name, name))
	return iter;
      iter = iter->next;
    }
  while (iter != This->vars);

  return NULL;			/* Not found */
}


/****f* lib5250/tn5250_config_replace_vars
 * NAME
 *    tn5250_config_replace_vars
 * SYNOPSIS
 *    tn5250_config_replace_vars (buf, 128);
 * INPUTS
 *    char        *      buf          -
 *    int                maxlen       -
 * DESCRIPTION
 *    This searches for special "replacement variables" in the
 *    config file entries and converts them to their "real values".
 *    (At this time, the only var is "$loginname$".)
 *****/
#define USER_NAME_MAX 50
static void
tn5250_config_replace_vars (char *buf, int maxlen)
{

#ifdef WIN32
  {
    DWORD len;
    char usrnam[USER_NAME_MAX + 1];
    len = USER_NAME_MAX;
    if (GetUserName (usrnam, &len) != 0)
      {
	tn5250_config_replacedata ("$loginname$", usrnam, buf, maxlen);
      }
  }
#else
  {
    struct passwd *pwent;
    pwent = getpwuid (getuid ());
    if (pwent != NULL)
      {
	tn5250_config_replacedata ("$loginname$", pwent->pw_name, buf,
				   maxlen);
      }
  }
#endif

  return;
}


/****f* lib5250/tn5250_config_replacedata
 * NAME
 *    tn5250_config_replacedata
 * SYNOPSIS
 *    tn5250_config_replacedata ("$loginname$", "fred", line, sizeof(line));
 * INPUTS
 *    const char  *      from         -
 *    const char  *      to           -
 *    char        *      line         -
 *    int                maxlen       -
 * DESCRIPTION
 *    This will replace the first occurrance of the "from" string with 
 *    the "to" string in a line of text.  The from and to do not have to 
 *    be the same length. 
 *****/
static void
tn5250_config_replacedata (const char *from, const char *to,
			   char *line, int maxlen)
{

  char *p;
  int len;
  char *before, *after;

  if ((p = strstr (line, from)) != NULL)
    {
      if (p <= line)
	{
	  before = malloc (1);
	  *before = '\0';
	}
      else
	{
	  len = p - line;
	  before = malloc (len + 1);
	  memcpy (before, line, len);
	  before[len] = '\0';
	}
      p += strlen (from);
      if (strlen (p) < 1)
	{
	  after = malloc (1);
	  *after = '\0';
	}
      else
	{
	  len = strlen (p);
	  after = malloc (len + 1);
	  memcpy (after, p, len);
	  after[len] = '\0';
	}
      snprintf (line, maxlen - 1, "%s%s%s", before, to, after);
      free (before);
      free (after);
    }

}

/* vi:set sts=3 sw=3: */


syntax highlighted by Code2HTML, v. 0.9.1