/*
 * netconfig_conf.c:
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id: netconfig_conf.c,v 1.1.1.1 2005/06/27 13:37:56 cm Exp $
 */

#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <net/if.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "vdr/config.h"
#include "netconfig_conf.h"

namespace {
  enum eNetconfigTokens { ctResolv = 0,
                          ctDevice,
                          ctSamba,
                          ctHostname,
                          ctGateway,
                          ctMAX };

  const char *configTokens[] = { "resolv",
                                 "device",
                                 "samba",
                                 "hostname",
                                 "gateway" };

  const char *sambaTokens[] = { "workgroup",
                                "netbios name",
                                "server string",
                                "comment",
                                "read only",
                                "valid users", NULL };
}

// --- cNetconfigParamter ------------------------------------------------------------

cNetconfigParameter::cNetconfigParameter(const char *key, const char *value, bool section, bool visible) {
  cNetconfigParameter::section = section;
  cNetconfigParameter::visible = visible;

  if (NULL != key)
    p_key = strdup(key);
  else
    p_key = strdup("");

  if (NULL != value)
    p_value = strdup(value);
  else
    p_value = strdup("");
}

cNetconfigParameter::~cNetconfigParameter() {
  free(p_key);
  free(p_value);
}

const char *cNetconfigParameter::SetKey(const char *key) {
  if (NULL == key)
    return NULL;

  if (NULL != p_key)
    free(p_key);

  return p_key = strdup(key);
}

const char *cNetconfigParameter::SetValue(const char *value) {
  if (NULL == value)
    return NULL;

  if (NULL != p_value)
    free(p_value);

  return p_value = strdup(value);
}

bool cNetconfigParameter::SetSection(bool set) {
  return section = set;
}

bool cNetconfigParameter::SetVisible(bool set) {
  return visible = set;
}

// --- cNetconfigParamters -----------------------------------------------------------

cNetconfigParameters::cNetconfigParameters() {
}

cNetconfigParameters::~cNetconfigParameters() {
}

// --- cNetconfigInterface -----------------------------------------------------------

cNetconfigInterface::cNetconfigInterface(char *key, in_addr_t address, char netmask) {
  addr = address;
  mask = netmask;

  if (NULL != key)
    p_key = strdup(key);
  else
    p_key = strdup("");
}

cNetconfigInterface::~cNetconfigInterface() {
  free(p_key);
}

in_addr_t cNetconfigInterface::SetAddr(in_addr_t address) {
  return addr = address;
}

char cNetconfigInterface::SetMask(char netmask) {
  return mask = netmask;
}

const char *cNetconfigInterface::SetKey(const char *key) {
  if (NULL == key)
    return NULL;

  if (NULL != p_key)
    free(p_key);

  return p_key = strdup(key);
}

// --- cNetconfigInterfaces ----------------------------------------------------------

cNetconfigInterfaces::cNetconfigInterfaces() {
}

cNetconfigInterfaces::~cNetconfigInterfaces() {
}

// --- cNetconfigConf ----------------------------------------------------------------

cNetconfigConf::cNetconfigConf(void) {
  ok = false;

  p_config = strdup("");
  p_dhcpScript =  strdup("");
  p_hostname =  strdup("");
  p_domain =  strdup("");

  Initialize();
}

cNetconfigConf::~cNetconfigConf(void) {
  free(p_config);
  free(p_dhcpScript);
  free(p_hostname);
  free(p_domain);
}

cNetconfigInterfaces *cNetconfigConf::Interfaces() {
  return &oInterfaces;
}

cNetconfigParameters *cNetconfigConf::SambaParameters() {
  return &oSambaParameters;
}

void cNetconfigConf::Initialize(void) {
  int sockfd = -1, ifr_length = 0;
  unsigned int i = 0;
  struct ifconf ifc;
  struct ifreq *p_ifr = NULL;

  sockfd = socket(AF_INET, SOCK_DGRAM, 0);

  if (-1 != sockfd) {
    ifr_length = 10 * sizeof(struct ifreq); // MAX DEVICES
    p_ifr = (struct ifreq *)malloc(ifr_length);

    if (NULL != p_ifr) {
      memset(p_ifr, 0x00, ifr_length);
      ifc.ifc_len = ifr_length;
      ifc.ifc_buf = (caddr_t)p_ifr;

      if (-1 != ioctl(sockfd, SIOCGIFCONF, &ifc)) {
        for (i = 0; i < (ifc.ifc_len / sizeof(struct ifreq)); i++) {
          if (0 == strncmp("eth", p_ifr[i].ifr_name, 3) || 0 == strncmp("lo", p_ifr[i].ifr_name, 2)) {
            oInterfaces.Add(new cNetconfigInterface(p_ifr[i].ifr_name));
#ifdef DEBUG
            printf("added device <%s>\n", p_ifr[i].ifr_name);
#endif
          }
        }
      }
      free(p_ifr);
    }
  } 
}

const char *cNetconfigConf::SetDhcpScript(const char *script) {
  if (NULL == script)
    return NULL;

  if (NULL != p_dhcpScript)
    free(p_dhcpScript);

  return p_dhcpScript = strdup(script);
}

const char *cNetconfigConf::SetConfig(const char *config) {
  if (NULL == config)
    return NULL;

  if (NULL != p_config)
    free(p_config);

  return p_config = strdup(config);
}

const char *cNetconfigConf::SetHostname(const char *name) {
  if (NULL == name)
    return NULL;

  if (NULL != p_hostname)
    free(p_hostname);

  return p_hostname = strdup(name);
}

const char *cNetconfigConf::SetDomain(const char *domain) {
  if (NULL == domain)
    return NULL;

  if (NULL != p_domain)
    free(p_domain);

  return p_domain = strdup(domain);
}

in_addr_t cNetconfigConf::SetGateway(in_addr_t addr) {
  return gateway = addr;
}

in_addr_t cNetconfigConf::SetNameserver(in_addr_t addr) {
  return nameserver = addr;
}

// pointer must be free'd by caller
char *cNetconfigConf::ReadAllFromSocket(int fd, int &length) {
  int i = 0, pc = 0;
  char *p = NULL, *p_copy = NULL;

  do { // read all what we can get
    i++;
    if (1 < i) {
      p_copy = p;
      p = NULL;
    }

    p = (char *)malloc(256*i+1); // add one byte to give some space for the last terminating \0

    if (NULL != p) {
      memset(p, 0x00, 256*i+1);

      if (1 < i) {
        memcpy(p, p_copy, 256*(i-1));
        free(p_copy);
        p_copy = NULL;
      }

      pc = read(fd, p + (256*(i-1)), 256);
    }
  } while (256 == pc && NULL != p);
  pc += 256*(i-1);

  length = pc;
  return p;
}

bool cNetconfigConf::SetData(const char *key, const char *value) {
  if (NULL == key)
    return false;

  if (0 == strcmp("domain", key)) {
    SetDomain(value);
  } else if (0 == strcmp("nameserver", key)) {
    if (0 >= inet_pton(AF_INET, value, &nameserver))
      printf("SD bad nameserver=%s\n", value);
  } else if (0 == strncmp("eth", key, 3) || 0 == strncmp("lo", key, 2)) {
    char *p_addr = NULL, *p_mask = NULL;
    int i = 0, bits = 0;
    in_addr_t addr = 0, mask = 0;

    if (0 != strncmp("dhcp", value, 4)) {
      i = sscanf(value, "inet %a[0-9.] netmask %a[0-9.]\n", &p_addr, &p_mask);
      if (2 == i) {
        if (0 >= inet_pton(AF_INET, p_addr, &addr))
          printf("SD bad address=%s\n", p_addr);
        if (0 >= inet_pton(AF_INET, p_mask, &mask))
          printf("SD bad netmask=%s\n", p_mask);
        else {
          mask = ntohl(mask);
          for (bits = 0; bits < 32; bits++) {
            if (! ((0x80000000 >> bits) & mask))
              break;
          }
        }
      }
    }

    for (cNetconfigInterface *po_int = oInterfaces.First(); NULL != po_int; po_int = oInterfaces.Next(po_int)) {
      if (0 == strcmp(key, po_int->GetKey())) {
        po_int->SetAddr(addr);
        po_int->SetMask(bits);
        break;
      }
    }
    free(p_addr);
  } else if (0 == strcmp("hostname", key)) {
    char *p_name = NULL, *p_domain = NULL;
    sscanf(value, "%a[^.\n].%a[^\n]\n", &p_name, &p_domain);
    SetHostname(p_name);
    SetDomain(p_domain);
    free(p_name);
    free(p_domain);
  } else if (0 == strcmp("gateway", key)) {
    if (0 >= inet_pton(AF_INET, value, &gateway))
      printf("SD bad gateway=%s\n", value);
  } else if (0 == strncmp("samba.", key, 6)) {
    bool visible = false;
    char *p_param = NULL;
    sscanf(key, "samba.%a[^]\n]", &p_param);
    if (NULL != p_param) {
      for (const char **p = sambaTokens; NULL != *p; p++) {
        if (0 == strcmp(*p, p_param)) {
          visible = true;
          break;
        }
      }
      oSambaParameters.Add(new cNetconfigParameter('[' == *p_param ? p_param + 1 : p_param, value, '[' == *p_param, visible));
    }
    free(p_param);
  }

  return true;
}

bool cNetconfigConf::StoreConfig(const char *key, const char *value) {
  char *p_line = NULL, addr[16], mask[16];
  int configfd = -1, bc = 0;

  if (NULL == key || NULL == value)
    return false;

  memset(addr, 0x00, 16);
  memset(mask, 0x00, 16);
  configfd = open(value, O_WRONLY | O_TRUNC);

  if (-1 != configfd) {
    if (0 == strcmp(configTokens[ctResolv], key)) {
      if (0 != strlen(p_domain)) {
        bc = asprintf(&p_line, "domain %s\n", p_domain);
        if (0 != bc && NULL != p_line);
          write(configfd, p_line, bc);
        free(p_line);
      }

      if (0 != nameserver) {
        inet_ntop(AF_INET, &nameserver, addr, 15);
        bc = asprintf(&p_line, "nameserver %s\n", addr);
        if (0 != bc && NULL != p_line && 0x00 != addr[0]);
          write(configfd, p_line, bc);
        free(p_line);
      }
    } else if (0 == strcmp(configTokens[ctDevice], key)) {
      char *p_dev = NULL;
      sscanf(value, "%*[^.].%as", &p_dev);
      if (NULL != p_dev) {
        for (cNetconfigInterface *po_int = oInterfaces.First(); NULL != po_int; po_int = oInterfaces.Next(po_int)) {
          if (0 == strcmp(p_dev, po_int->GetKey())) {
            in_addr_t in_addr = po_int->GetAddr(), in_mask = 0;
            if (0 == in_addr && 0 == po_int->GetMask())
              bc = asprintf(&p_line, "dhcp\n");
            else {
              for (int i = 0; i < po_int->GetMask(); i++)
                in_mask |= 0x80000000 >> i;

              in_mask = htonl(in_mask);

              inet_ntop(AF_INET, &in_addr, addr, 15);
              inet_ntop(AF_INET, &in_mask, mask, 15);
              bc = asprintf(&p_line, "inet %s netmask %s\n", addr, mask);
            }
            if (0 != bc && NULL != p_line) 
              write(configfd, p_line, bc); 
            free(p_line);
          }
        }
        free(p_dev);
      }
    } else if (0 == strcmp(configTokens[ctHostname], key)) {
      if (0 != strlen(p_hostname) && 0 != strlen(p_domain)) {
        bc = asprintf(&p_line, "%s.%s\n", p_hostname, p_domain);
        if (0 != bc && NULL != p_line)
          write(configfd, p_line, bc);
        free(p_line);
      }
    } else if (0 == strcmp(configTokens[ctGateway], key)) {
      if (0 != gateway) {
        inet_ntop(AF_INET, &gateway, addr, 15);
        bc = asprintf(&p_line, "%s\n", addr);
        if (0 != bc && NULL != p_line && NULL != addr);
          write(configfd, p_line, bc);
        free(p_line);
      }
    } else if (0 == strcmp(configTokens[ctSamba], key)) {
      bc = asprintf(&p_line, "# Configuration file created by vdr-netconfig\n\n");
      write(configfd, p_line, bc);
      free(p_line);
      for (cNetconfigParameter *po_param = oSambaParameters.First(); NULL != po_param; po_param = oSambaParameters.Next(po_param)) {
        if (po_param->isSection())
          bc = asprintf(&p_line, "[%s]\n", po_param->GetKey());
        else
          bc = asprintf(&p_line, "\t%s = %s\n", po_param->GetKey(), po_param->GetValue());
        if (0 != bc && NULL != p_line)
          write(configfd, p_line, bc);
        free(p_line);
      }
    }

    close(configfd);
  }

  return true;
}

bool cNetconfigConf::ParseConfig(const char *key, const char *value) {
  char *p = NULL, *p_cur = NULL, *p_key = NULL, *p_value = NULL, *p_tok = NULL, *p_file = NULL;
  int configfd = -1, pc = 0;
  cNetconfigInterface *po_item = oInterfaces.First();

  if (NULL == key || NULL == value)
    return false;

  do {
    if (0 == strcmp(configTokens[ctDevice], key)) {
      p_tok = p_file = NULL;
      if (NULL != (p_tok = strstr(value, "$1")) && NULL != po_item) {
          asprintf(&p_file, "%.*s%s", p_tok - value, value, po_item->GetKey());
          po_item = oInterfaces.Next(po_item);
      }
    } else
      p_file = strdup(value);

    configfd = open(p_file, O_RDONLY);

    if (-1 != configfd) {
      p = ReadAllFromSocket(configfd, pc);

      if (0 < pc) {
        p_cur = p;
        while (NULL != p_cur && 0x00 != p_cur[1]) {
          while ('\n' == *p_cur)
            p_cur++;
          p_key = p_value = NULL;

          if ('#' != *p_cur) {
            if (0 == strcmp(configTokens[ctResolv], key)) {
              sscanf(p_cur, "%as %a[^\n]\n", &p_key, &p_value);
            } else if (0 == strcmp(configTokens[ctDevice], key)) {
              sscanf(p_file, "%*[^.\n].%as\n", &p_key);
              sscanf(p_cur, "%a[^\n]\n", &p_value);
            } else if (0 == strcmp(configTokens[ctHostname], key)) {
              sscanf(p_cur, "%a[^\n]\n", &p_value);
              p_key = strdup("hostname");
            } else if (0 == strcmp(configTokens[ctGateway], key)) {
              sscanf(p_cur, "%a[^\n]\n", &p_value);
              p_key = strdup("gateway");
            } else if (0 == strcmp(configTokens[ctSamba], key)) {
              char *p_help = NULL;
              while (0x20 >= *p_cur) // skip leading tabs,spaces,etc
                p_cur++;
              sscanf(p_cur, "%a[^=\n]=%a[^\n]\n", &p_key, &p_value);
              FixString(p_key);
              FixString(p_value);

              asprintf(&p_help, "samba.%s", p_key);
              if (NULL != p_help) {
                free(p_key);
                p_key = p_help;
              }
            }
#ifdef DEBUG
            printf("PC key=<%s> value=<%s>\n", p_key, p_value);
#endif
            SetData(p_key, p_value);
          }

          p_cur = index(p_cur, '\n');
          free(p_key);
          free(p_value);
        }
        free(p);
        p = p_cur = NULL;
      }
      close(configfd);
    }
    free(p_file);
  } while (0 == strcmp(configTokens[ctDevice], key) && NULL != po_item && NULL != p_tok); // do {

  return true;
}

bool cNetconfigConf::Load(void) {
  int configfd = -1, pc = 0, i = 0;
  char *p_key = NULL, *p = NULL, *p_cur = NULL, *p_value = NULL;

  if (NULL == p_config)
    return false;

  configfd = open(p_config, O_RDONLY);

  if (-1 != configfd) {
    ok = true;
    p = ReadAllFromSocket(configfd, pc);
    p_cur = p;
    while (NULL != p_cur && 0x00 != p_cur[1]) {
      if ('\n' == *p_cur)
        p_cur++;
      p_key = p_value = NULL;
      i = sscanf(p_cur, "%a[^\n=]=%a[^\n]\n", &p_key, &p_value);
#ifdef DEBUG
      printf("LC key=<%s> value=<%s>\n", p_key, p_value);
#endif
      ParseConfig(p_key, p_value);
      p_cur = index(p_cur, '\n');
      free(p_key);
      free(p_value);
    }
    free(p);

    close(configfd);
  } else
    esyslog("NETCONFIG: failed to open <%s> while reading configuration\n", p_config);

  return true;
}

bool cNetconfigConf::Save(void) {
  int configfd = -1, pc = 0, i = 0;
  char *p_key = NULL, *p = NULL, *p_cur = NULL, *p_value = NULL;

  if (NULL == p_config)
    return false;

  configfd = open(p_config, O_RDONLY);

  if (-1 != configfd) {
    ok = true;
    p = ReadAllFromSocket(configfd, pc);
    p_cur = p;
    while (NULL != p_cur && 0x00 != p_cur[1]) {
      if ('\n' == *p_cur)
        p_cur++;
      p_key = p_value = NULL;
      i = sscanf(p_cur, "%a[^\n=]=%a[^\n]\n", &p_key, &p_value);
      if (0 == strcmp(p_key, configTokens[ctDevice])) {
        int len = strstr(p_value, "$1") - p_value;
        for (cNetconfigInterface *po_int = oInterfaces.First(); NULL != po_int; po_int = oInterfaces.Next(po_int)) {
          char *p_file = NULL;
          asprintf(&p_file, "%.*s%s", len, p_value, po_int->GetKey());
#ifdef DEBUG
          printf("SC key=<%s> value=<%s>\n", p_key, p_value);
#endif
          StoreConfig(p_key, p_file);
          free(p_file);
        }
      } else {
#ifdef DEBUG
        printf("SC key=<%s> value=<%s>\n", p_key, p_value);
#endif
        StoreConfig(p_key, p_value);
      }
      p_cur = index(p_cur, '\n');
      free(p_key);
      free(p_value);
    }
    free(p);

    close(configfd);
  } else
    esyslog("NETCONFIG: failed to open <%s> while saving configuration\n", p_config);

  return true;
}

char *cNetconfigConf::FixString(char *string) {
  char *p_pos = string;

  if (NULL != string) {
    // strip trailing space
    while (0x00 != *p_pos)
      p_pos++;

    if (0x20 == *(--p_pos))
      *p_pos = 0x00;

    // strip leading space
    p_pos = string;
    if (0x20 == *p_pos) {
      while (0x00 != *p_pos) {
        p_pos++;
        *(p_pos-1) = *p_pos;
      }
    }
  }

  return string;
}
