/*
  chronyd/chronyc - Programs for keeping computer clocks accurate.

 **********************************************************************
 * Copyright (C) Richard P. Curnow  1997-2003
 * Copyright (C) Miroslav Lichvar  2013-2014
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 * 
 * This program 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
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * 
 **********************************************************************

  =======================================================================
  
  Module for parsing various forms of directive and command lines that
  are common to the configuration file and to the command client.

  */

#include "config.h"

#include "sysincl.h"

#include "cmdparse.h"
#include "memory.h"
#include "nameserv.h"
#include "ntp.h"
#include "util.h"

/* ================================================== */

CPS_Status
CPS_ParseNTPSourceAdd(char *line, CPS_NTP_Source *src)
{
  char *hostname, *cmd;
  int n, done;
  CPS_Status result;
  
  src->port = SRC_DEFAULT_PORT;
  src->params.minpoll = SRC_DEFAULT_MINPOLL;
  src->params.maxpoll = SRC_DEFAULT_MAXPOLL;
  src->params.presend_minpoll = SRC_DEFAULT_PRESEND_MINPOLL;
  src->params.authkey = INACTIVE_AUTHKEY;
  src->params.max_delay = SRC_DEFAULT_MAXDELAY;
  src->params.max_delay_ratio = SRC_DEFAULT_MAXDELAYRATIO;
  src->params.max_delay_dev_ratio = SRC_DEFAULT_MAXDELAYDEVRATIO;
  src->params.online = 1;
  src->params.auto_offline = 0;
  src->params.iburst = 0;
  src->params.min_stratum = SRC_DEFAULT_MINSTRATUM;
  src->params.poll_target = SRC_DEFAULT_POLLTARGET;
  src->params.version = NTP_VERSION;
  src->params.max_sources = SRC_DEFAULT_MAXSOURCES;
  src->params.min_samples = SRC_DEFAULT_MINSAMPLES;
  src->params.max_samples = SRC_DEFAULT_MAXSAMPLES;
  src->params.sel_option = SRC_SelectNormal;

  result = CPS_Success;
  
  hostname = line;
  line = CPS_SplitWord(line);

  if (!*hostname) {
    result = CPS_BadHost;
  } else {
    src->name = hostname;

    /* Parse subfields */
    done = 0;
    do {
      cmd = line;
      line = CPS_SplitWord(line);

      if (*cmd) {
        if (!strcasecmp(cmd, "port")) {
          if (sscanf(line, "%hu%n", &src->port, &n) != 1) {
            result = CPS_BadPort;
            done = 1;
          } else {
            line += n;
          }
        } else if (!strcasecmp(cmd, "minpoll")) {
          if (sscanf(line, "%d%n", &src->params.minpoll, &n) != 1) {
            result = CPS_BadMinpoll;
            done = 1;
          } else {
            line += n;
          }
        } else if (!strcasecmp(cmd, "maxpoll")) {
          if (sscanf(line, "%d%n", &src->params.maxpoll, &n) != 1) {
            result = CPS_BadMaxpoll;
            done = 1;
          } else {
            line += n;
          }
        } else if (!strcasecmp(cmd, "presend")) {
          if (sscanf(line, "%d%n", &src->params.presend_minpoll, &n) != 1) {
            result = CPS_BadPresend;
            done = 1;
          } else {
            line += n;
          }
        } else if (!strcasecmp(cmd, "maxdelaydevratio")) {
          if (sscanf(line, "%lf%n", &src->params.max_delay_dev_ratio, &n) != 1) {
            result = CPS_BadMaxdelaydevratio;
            done = 1;
          } else {
            line += n;
          }
        } else if (!strcasecmp(cmd, "maxdelayratio")) {
          if (sscanf(line, "%lf%n", &src->params.max_delay_ratio, &n) != 1) {
            result = CPS_BadMaxdelayratio;
            done = 1;
          } else {
            line += n;
          }
        } else if (!strcasecmp(cmd, "maxdelay")) {
          if (sscanf(line, "%lf%n", &src->params.max_delay, &n) != 1) {
            result = CPS_BadMaxdelay;
            done = 1;
          } else {
            line += n;
          }
        } else if (!strcasecmp(cmd, "key")) {
          if (sscanf(line, "%"SCNu32"%n", &src->params.authkey, &n) != 1 ||
              src->params.authkey == INACTIVE_AUTHKEY) {
            result = CPS_BadKey;
            done = 1;
          } else {
            line += n;
          }
        } else if (!strcasecmp(cmd, "offline")) {
          src->params.online = 0;

        } else if (!strcasecmp(cmd, "auto_offline")) {
          src->params.auto_offline = 1;
        
        } else if (!strcasecmp(cmd, "iburst")) {
          src->params.iburst = 1;

        } else if (!strcasecmp(cmd, "minstratum")) {
          if (sscanf(line, "%d%n", &src->params.min_stratum, &n) != 1) {
            result = CPS_BadMinstratum;
            done = 1;
          } else {
            line += n;
          }

        } else if (!strcasecmp(cmd, "polltarget")) {
          if (sscanf(line, "%d%n", &src->params.poll_target, &n) != 1) {
            result = CPS_BadPolltarget;
            done = 1;
          } else {
            line += n;
          }

        } else if (!strcasecmp(cmd, "noselect")) {
          src->params.sel_option = SRC_SelectNoselect;
        
        } else if (!strcasecmp(cmd, "prefer")) {
          src->params.sel_option = SRC_SelectPrefer;
        
        } else if (!strcasecmp(cmd, "version")) {
          if (sscanf(line, "%d%n", &src->params.version, &n) != 1) {
            result = CPS_BadVersion;
            done = 1;
          } else {
            line += n;
          }

        } else if (!strcasecmp(cmd, "maxsources")) {
          if (sscanf(line, "%d%n", &src->params.max_sources, &n) != 1) {
            result = CPS_BadMaxsources;
            done = 1;
          } else {
            line += n;
          }

        } else if (!strcasecmp(cmd, "minsamples")) {
          if (sscanf(line, "%d%n", &src->params.min_samples, &n) != 1) {
            result = CPS_BadMinsamples;
            done = 1;
          } else {
            line += n;
          }

        } else if (!strcasecmp(cmd, "maxsamples")) {
          if (sscanf(line, "%d%n", &src->params.max_samples, &n) != 1) {
            result = CPS_BadMaxsamples;
            done = 1;
          } else {
            line += n;
          }

        } else {
          result = CPS_BadOption;
          done = 1;
        }
      } else {
        done = 1;
      }
    } while (!done);
  }

  return result;
}

/* ================================================== */

void
CPS_StatusToString(CPS_Status status, char *dest, int len)
{
  const char *s = NULL;

  if (len > 0)
    dest[0] = '\0';

  switch (status) {
    case CPS_Success:
      return;
    case CPS_BadOption:
      s = "server/peer/pool option";
      break;
    case CPS_BadHost:
      s = "address";
      break;
    case CPS_BadPort:
      s = "port";
      break;
    case CPS_BadMinpoll:
      s = "minpoll";
      break;
    case CPS_BadMaxpoll:
      s = "maxpoll";
      break;
    case CPS_BadPresend:
      s = "presend";
      break;
    case CPS_BadMaxdelaydevratio:
      s = "maxdelaydevratio";
      break;
    case CPS_BadMaxdelayratio:
      s = "maxdelayratio";
      break;
    case CPS_BadMaxdelay:
      s = "maxdelay";
      break;
    case CPS_BadKey:
      s = "key";
      break;
    case CPS_BadMinstratum:
      s = "minstratum";
      break;
    case CPS_BadPolltarget:
      s = "polltarget";
      break;
    case CPS_BadVersion:
      s = "version";
      break;
    case CPS_BadMaxsources:
      s = "maxsources";
      break;
    case CPS_BadMinsamples:
      s = "minsamples";
      break;
    case CPS_BadMaxsamples:
      s = "maxsamples";
      break;
  }

  snprintf(dest, len, "Invalid %s", s);
}

/* ================================================== */

void
CPS_NormalizeLine(char *line)
{
  char *p, *q;
  int space = 1, first = 1;

  /* Remove white-space at beginning and replace white-spaces with space char */
  for (p = q = line; *p; p++) {
    if (isspace((unsigned char)*p)) {
      if (!space)
        *q++ = ' ';
      space = 1;
      continue;
    }

    /* Discard comment lines */
    if (first && strchr("!;#%", *p))
      break;

    *q++ = *p;
    space = first = 0;
  }

  /* Strip trailing space */
  if (q > line && q[-1] == ' ')
    q--;

  *q = '\0';
}

/* ================================================== */

char *
CPS_SplitWord(char *line)
{
  char *p = line, *q = line;

  /* Skip white-space before the word */
  while (*q && isspace((unsigned char)*q))
    q++;

  /* Move the word to the beginning */
  while (*q && !isspace((unsigned char)*q))
    *p++ = *q++;

  /* Find the next word */
  while (*q && isspace((unsigned char)*q))
    q++;

  *p = '\0';

  /* Return pointer to the next word or NUL */
  return q;
}

/* ================================================== */

int
CPS_ParseKey(char *line, uint32_t *id, const char **hash, char **key)
{
  char *s1, *s2, *s3, *s4;

  s1 = line;
  s2 = CPS_SplitWord(s1);
  s3 = CPS_SplitWord(s2);
  s4 = CPS_SplitWord(s3);

  /* Require two or three words */
  if (!*s2 || *s4)
    return 0;

  if (sscanf(s1, "%"SCNu32, id) != 1)
    return 0;

  if (*s3) {
    *hash = s2;
    *key = s3;
  } else {
    *hash = "MD5";
    *key = s2;
  }

  return 1;
}
