/* mcap.c

   Routines for parsing /etc/modemcap... */

/*
 * Copyright (c) 1995 RadioMail Corporation.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of RadioMail Corporation nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY RADIOMAIL CORPORATION AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
 * RADIOMAIL CORPORATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software was written for RadioMail Corporation by Ted Lemon
 * under a contract with Vixie Enterprises, and is based on an earlier
 * design by Paul Vixie.
 */

#ifndef lint
static char copyright[] =
"@(#) Copyright (c) 1995 RadioMail Corporation.  All rights reserved.\n";
#endif /* not lint */

#include "osdep.h"
#include "cdefs.h"
#include "global.h"
#include <stdio.h>
#include "mcap.h"

static void enter_modemcap PROTO ((char *));
static void parse_mcstring PROTO ((char *, struct modemcap *));
static void read_mcap PROTO ((void));
static void parse_modemcap PROTO ((char *, struct modemcap *));

/* List of capability strings from modemcap file... */
static struct node {
  struct node *next;
  char *name, *string;
} *stringList;

struct modemcap modemcap;

/* Read in the modemcap file, then parse the specified capability
   string. */

void init_mcap (capName)
     char *capName;
{
  struct list *l;
  int i;

  /* Read in the modemcap file... */
  read_mcap ();

  /* Initialize modemcap to default... */
  modemcap.name = capName;
  modemcap.init_string = "";	/* Empty init string... */
  modemcap.parity = "none";	/* No parity... */
  modemcap.program = _PATH_LOGIN;	/* Standard login program... */
  modemcap.ppp_prog = (char *)0;	/* Incoming PPP support program... */
  modemcap.luser = (char *)0;	/* No default luser. */
  modemcap.banner = "";		/* No default banner. */
  modemcap.connect_addr = (char *)0;	/* No default TCP connect address. */
  modemcap.connect_port = (char *)0;	/* No default TCP connect port. */
  modemcap.factory_defaults = (char *)0; /* By default, no reset to factory. */
  modemcap.fc_rts_cts = 0;	/* Hardware flow control... */
  modemcap.fc_xon_xoff = 0;	/* No XON/XOFF flow control... */
  modemcap.match_speed = 0;	/* Don't match modem CONNECT speed... */
  modemcap.reset_dtr = 0;	/* Don't reset modem with DTR... */
  modemcap.do_login = 0;	/* Login program isn't stupid... */
  modemcap.clumpp = 0;		/* Don't clump AT command chars... */
  modemcap.slow_modem = 0;	/* Modem isn't unusually slow... */
  modemcap.raw_tty = 0;		/* Don't leave tty in raw mode... */
  modemcap.tx_speed = 57600;	/* Transmit speed... */
  modemcap.rx_speed = 57600;	/* Receive speed... */
  modemcap.bits = 8;		/* Bits per character... */
  modemcap.ibuf_size = 40;	/* Bytes in modem AT input buffer... */
  modemcap.skip_init = 0;	/* Skip initialization in mpoold... */
  modemcap.defer_init = 0;	/* Defer initialization in mpoold... */
  modemcap.busy_out = 0;	/* Busy out modem during initialization... */
  modemcap.logstderr = 0;	/* Log child output to stderr... */
  modemcap.msdelay = 50;	/* Inter-char delay in modem command mode. */
  
  /* Parse modemcap string for capName into modemcap... */
  parse_modemcap (capName, &modemcap);

  /* Parse the init string, if any... */
  if (modemcap.init_string)
    modemcap.init_vector = atparse (modemcap.init_string, modemcap.clumpp);
  /* Make a vector for all the devices... */
  for ((i = 0), (l = modemcap.devices); l; l = l -> next)
    ++i;
  modemcap.device_vector = (char **)malloc (i * sizeof (char **));
  if (!modemcap.device_vector)
    error ("No memory for device name vector.");
  modemcap.device_count = i;
  for ((i = 0), (l = modemcap.devices); l; l = l -> next)
    modemcap.device_vector [i++] = l -> value;
}

/* Open the modemcap file, then read it line by line, discarding comments
   and merging continuation lines.   After merging, each line is recorded
   in a list of modem capability strings for possible later parsing. */

static void read_mcap ()
{
  FILE *mcapf;
  char *ibuf = (char *)0;
  int ibuflen = 0;
  int ibufix = 0;
  int done = 0;

  /* Open the modemcap file... */
  mcapf = fopen (_PATH_MODEMCAP, "r");
  if (mcapf == NULL)
    error ("Can't open %s: %m", _PATH_MODEMCAP);

  /* Read from the input until there's nothing left.   Merge lines
     ending with \\n with the following line.   Discard lines beginning
     with #. */
  do {
    /* If it looks like it might be necessary,
       expand the input buffer... */
    if (ibuflen - ibufix < 256)
      {
	if (!ibuf)
	  ibuf = (char *)malloc (ibuflen + 256);
	else
	  ibuf = (char *)realloc (ibuf, ibuflen + 256);
	if (!ibuf)
	  error ("Can't grow modemcap input buffer to %d bytes",
		 ibuflen + 256);
	ibuflen += 256;
      }

    /* Get the next line of text... */
    if (!fgets (ibuf + ibufix, ibuflen - ibufix, mcapf))
      done = 1;
    else
      ibufix = strlen (ibuf);

    /* If we didn't have anything buffered and didn't read anything,
       we can stop now. */
    if (done && !ibufix)
      break;

    /* If we've partially read a long line, there won't be
       a trailing newline. */
    if (ibuf [ibufix - 1] != '\n')
      {
	if (done)
	  {
	    warn ("%s: no newline on last line.", _PATH_MODEMCAP);
	    warn ("ibufix = %d; ibuf = %s", ibufix, ibuf);
	  }
	else
	  continue;
      }
    else
      --ibufix;

    /* If the line is blank, discard it. */
    if (ibufix == 0)
      continue;

    /* At this point, we've read an entire line.   If it starts with #,
       discard it.   Comment lines can't be continued. */
    if (ibuf [0] == '#')
      {
	ibufix = 0;
	continue;
      }

    /* Now, if the line has a trailing backslash, read the next line
       starting where the backslash was. */
    if (ibuf [ibufix - 1] == '\\')
      {
	--ibufix;
	continue;
      }

    /* If we got here, we have a single modemcap entry in ibuf.
       Parse it, then go on. */
    enter_modemcap (ibuf);
    ibufix = 0;
  } while (!done);
}

/* Enter the specified modemcap string in the list of such strings.
   A modemcap string should start off with a name, followed by a set
   of capabilities. */

static void enter_modemcap (string)
     char *string;
{
  char *c;
  struct node *tmp;

  c = strchr (string, ':');
  if (!c)
    warn ("Malformed modemcap string: %s");
  else
    {
      /* Allocate space for entry... */
      if (!(tmp = (struct node *)malloc (sizeof (struct node)))
	  || !(tmp -> name = (char *)malloc (c - string + 1))
	  || !(tmp -> string = (char *)malloc (strlen (c + 1))))
	error ("Out of memory while allocating modemcap node");
      strncpy (tmp -> name, string, c - string);
      strcpy (tmp -> string, c + 1);
      tmp -> next = stringList;
      stringList = tmp;
    }
}

/* Process a modemcap entry, applying the results to the specified
   modemcap structure.  Iterate over the entry, passing each
   colon-delimited hunk of text to parse_mcstring for further
   processing.  parse_mcstring modifies the modemcap structure based
   on the hunk of text passed to it. */

static void parse_modemcap (name, cap)
     char *name;
     struct modemcap *cap;
{
  char *cp, *np, *dp;
  struct node *i;
  
  for (i = stringList; i != (struct node *)0; i = i -> next)
    if (!strcmp (i -> name, name))
      break;
  if (!i)
    {
      warn ("%s: no such entry.   Using default.", name);
      return;
    }

  cp = i -> string;
  do {
    dp = np = cp;
    /* Skip to next unquoted colon... */
    while (*np && *np != ':')
      {
	/* Copy np into dp in case there are quoted chars. */
	if (*np == '\\' && *(np + 1))
	  {
	    switch (*(np + 1))
	      {
	      case 'n':
		*dp = '\n';
		break;
	      case 't':
		*dp = '\t';
		break;
	      case 'a':
		*dp = '\007';
		break;
	      case 'e':
		*dp = '\033';
		break;
	      case 'r':
		*dp = '\r';
		break;
	      default:
		*dp = *(np + 1);
		break;
	      }
	    /* Skip over backslash and quoted character... */
	    np += 2;
	    ++dp;
	  }
	else
	  *dp++ = *np++;
      }
    if (*np)
      {
	*dp = 0;
	np += 1;
      }
    parse_mcstring (cp, cap);
    cp = np;
  } while (*cp);
}

static void parse_mcstring (string, cap)
     char *string;
     struct modemcap *cap;
{
  int nump = 0, nval;
  int stringp = 0;
  char *sval;
  int boolp = 0;

  /* Skip leading whitespace... */
  while (*string && isascii (*string) && isspace (*string))
    ++string;

  /* Skip empty hunks... */
  if (!string [0])
    return;

  /* All capability names are two characters. */
  if (!string [1])
    {
      warn ("Malformed capability string `%s' for modem %s\n",
	    string, cap -> name);
      return;
    }
  /* If just the name is given, the value is boolean (and true). */
  if (!string [2])
    {
      nump = 1;
      nval = 1;
    }
  else if (string [2] == '#')
    {
      nump = 1;
      nval = atoi (string + 3);
    }
  else if (string [2] == '=')
    {
      stringp = 1;
      sval = string + 3;
    }
  else
    {
      warn ("Unparsable modemcap hunk `%s' for entry %s\n",
	    string, cap -> name);
      return;
    }
#define DOCAP(str,elem,type,tname,val) \
  if (!strncmp (str, string, 2)) \
    { \
      if (!type) \
	warn ("%s: %s value expected", str, tname); \
      else \
        cap -> elem = val; \
    } \
  else

  /* Include another capability string if specified. */
  if (!strncmp ("tc", string, 2))
    {
      if (!stringp)
	warn ("tc: string value expected");
      else
	{
	  /* Check for and break loops... */
	  if (!strcmp (cap -> name, sval))
	    warn ("Recursion in modemcap entry %s", sval);
	  else
	    parse_modemcap (sval, cap);
	}
    }
  else
  /* Baud rate sets two values, so won't work with DOCAP macro. */
  if (!strncmp ("br", string, 2))
    {
      if (!nump)
	warn ("br: numeric value expected");
      else
	{
	  cap -> tx_speed = cap -> rx_speed = nval;
	}
    }
  else
  /* dv Adds a device to the list, so is handled slightly differently. */
  if (!strncmp ("dv", string, 2))
    {
      if (!stringp)
	warn ("dv: string value expected");
      else
	{
	  struct list *tmp = (struct list *)malloc (sizeof (struct list));
	  struct list *l;

	  if (!tmp)
	    error ("Unable to allocate space for device list.");
	  tmp -> next = (struct list *)0;
	  tmp -> value = sval;
	  if (cap -> devices)
	    {
	      for (l = cap -> devices; l -> next; l = l -> next)
		;
	      l -> next = tmp;
	    }
	  else
	    cap -> devices = tmp;
	}
    }
  else
    /* Strings... */
  DOCAP ("is", init_string, stringp, "string", sval)
  DOCAP ("pa", parity, stringp, "string", sval)
  DOCAP ("lp", program, stringp, "string", sval)
  DOCAP ("lu", luser, stringp, "string", sval)
  DOCAP ("ba", banner, stringp, "string", sval)
  DOCAP ("ca", connect_addr, stringp, "string", sval)
  DOCAP ("cp", connect_port, stringp, "string", sval)
  DOCAP ("fd", factory_defaults, stringp, "string", sval)
  DOCAP ("pp", ppp_prog, stringp, "string", sval)
    /* Booleans... */
  DOCAP ("hf", fc_rts_cts, nump, "no", nval)
  DOCAP ("sf", fc_xon_xoff, nump, "no", nval)
  DOCAP ("ms", match_speed, nump, "no", nval)
  DOCAP ("zm", reset_dtr, nump, "no", nval)
  DOCAP ("dl", do_login, nump, "no", nval)
  DOCAP ("cl", clumpp, nump, "no", nval)
  DOCAP ("sm", slow_modem, nump, "no", nval)
  DOCAP ("rt", raw_tty, nump, "no", nval)
  DOCAP ("si", skip_init, nump, "no", nval)
  DOCAP ("di", defer_init, nump, "no", nval)
  DOCAP ("bo", busy_out, nump, "no", nval)
  DOCAP ("ls", logstderr, nump, "no", nval)
    /* Numbers... */
  DOCAP ("os", tx_speed, nump, "numeric", nval)
  DOCAP ("rs", rx_speed, nump, "numeric", nval)
  DOCAP ("bt", bits, nump, "numeric", nval)
  DOCAP ("bs", ibuf_size, nump, "numeric", nval)
  DOCAP ("md", msdelay, nump, "numeric", nval)
    /* No match (remember implicit else in DOCAP)... */
  warn ("Unknown capability `%s'.", string);
}
