/*

Copyright (C) 2000 - 2006 Christian Kreibich <christian@whoop.org>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies of the Software and its documentation and acknowledgment shall be
given in the documentation and software packages that this Software was
used.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#ifdef LINUX_HOST
#include <linux/version.h>
#endif

#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <time.h>

#include <pcapnav.h>
#include <libnd_globals.h>
#include <libnd_prefs.h>
#include <libnd_misc.h>

#define LND_SWAPNAME_PFX  "netdude-swap-"


guint     
libnd_misc_writen(int fd, const guchar *data, guint size)
{
  guint         nleft;
  int           nwritten;
  const guchar *ptr;

  /* See Stevens's UNP ... */

  ptr = data;
  nleft = size;

  while (nleft > 0)
    {
      if ( (nwritten = write(fd, ptr, nleft)) < 0)
	{
	  if (errno == EINTR)
	    {
	      D(("Interrupted write: %s\n", strerror(errno)));
	      nwritten = 0;
	    }
	  else
	    {
	      D(("Error when writing: %s\n", strerror(errno)));
	      return -1;
	    }
	}

      nleft -= nwritten;
      ptr += nwritten;
    }
  
  return size;
}


guint     
libnd_misc_readn(int fd, guchar *data, guint size)
{
  guint   nleft;
  int     nread;
  guchar *ptr;

  /* See Stevens's UNP ... */

  ptr = data;
  nleft = size;

  while (nleft > 0)
    {
      if ( (nread = read(fd, ptr, nleft)) < 0)
	{
	  if (errno == EINTR)
	    nread = 0;
	  else
	    return -1;
	}
      else if (nread == 0)
	{
	  break;              /* EOF */
	}

      nleft -= nread;
      ptr += nread;
    }
  
  return size - nleft;
}


guint
libnd_misc_readline(int fd, guchar *data, guint size)
{
  int remaining = size;
  int n = 0, total = 0;
  char *eol;

  while (remaining > 0)
    {
      if ((n = read(fd, data, remaining)) > 0)
	{
	  total += n;

	  if ( (eol = strchr((const char *) data, '\n')))
	    return total;
	  
	  data += n;
	  remaining -= n;	  
	}
    }
  
  return size;
}
  

int
libnd_misc_ones_complement_checksum(const void *p, int b, u_int32_t sum)
{
  const u_short *sp;

  sp = (u_short *) p; /* better be aligned! */
  b /= 2;             /* convert to count of short's */

  /* No need for endian conversions. */
  while ( --b >= 0 )
    sum += *sp++;

  while ( sum > 0xffff )
    sum = (sum & 0xffff) + (sum >> 16);

  return sum;
}

/*
 * Checksum routine for Internet Protocol family headers (C Version)
 */
u_short
libnd_misc_in_cksum(u_short *addr, int len, u_int preadd)
{
  register int nleft = len;
  register u_short *w = addr;
  register u_short answer;
  register int sum = 0;

  sum += preadd;

  /*
   *  Our algorithm is simple, using a 32 bit accumulator (sum),
   *  we add sequential 16 bit words to it, and at the end, fold
   *  back all the carry bits from the top 16 bits into the lower
   *  16 bits.
   */
  while (nleft > 1) {
    sum += *w++;
    nleft -= 2;
  }

  /* mop up an odd byte, if necessary */
  if (nleft == 1)
    sum += *(guchar *)w;

  /*
   * add back carry outs from top 16 bits to low 16 bits
   */
  sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
  sum += (sum >> 16);                 /* add carry */
  answer = ~sum;                      /* truncate to 16 bits */

  return answer;
}


gboolean  
libnd_misc_exists(const char *filename)
{
  struct stat st;
  
  if (!filename || filename[0] == '\0')
    return FALSE;

  if (lstat(filename, &st) < 0)
    return FALSE;

  return TRUE;
}


gboolean
libnd_misc_is_tcpdump_file(const char *name)
{
  FILE *f;
  u_int32_t magic = 0;

  if ((f = fopen(name, "r")))
    {
      fread((void*)&magic, sizeof(u_int32_t), 1, f);
      fclose(f);
  
      if ((magic == 0xa1b2c3d4) ||
	  (magic == 0xd4c3b2a1) ||
	  (magic == 0xa1b2cd34) || /* Extended tracefile with Alexey's patches */
	  (magic == 0x34cdb2a1))
	return TRUE;
    }

  D(("fopen() failed, reason: %s\n", strerror(errno)));

  return FALSE;
}


gboolean  
libnd_misc_is_dir(const char *filename)
{
  struct stat st;

  if (!filename || filename[0] == '\0')
    return FALSE;

  if (lstat(filename, &st) < 0)
    return FALSE;

  return S_ISDIR(st.st_mode);
}


guint64
libnd_misc_get_size(const char *filename)
{
  struct stat st;
  
  if (!filename || filename[0] == '\0')
    return 0;

  if (lstat(filename, &st) < 0)
    return 0;

  return st.st_size;
}


gboolean       
libnd_misc_can_exec(const char *filename)
{
  struct stat st;

  if (!filename || filename[0] == '\0')
    return FALSE;

  /* Stat the file to see if it's executable and
     if the user may run it.
  */
  if (lstat(filename, &st) < 0)
    return FALSE;

  if ((st.st_mode & S_IXUSR) ||
      (st.st_mode & S_IXGRP) ||
      (st.st_mode & S_IXOTH))
    return TRUE;

  return FALSE;
}


gboolean       
libnd_misc_can_read(const char *filename)
{
  struct stat st;

  if (!filename || filename[0] == '\0')
    return FALSE;

  /* Stat the file to see if it's executable and
     if the user may run it.
  */
  if (lstat(filename, &st) < 0)
    return FALSE;

  if ((st.st_mode & S_IRUSR) ||
      (st.st_mode & S_IRGRP) ||
      (st.st_mode & S_IROTH))
    return TRUE;

  return FALSE;
}


gboolean       
libnd_misc_can_write(const char *filename)
{
  struct stat st;

  if (!filename || filename[0] == '\0')
    return FALSE;

  /* Stat the file to see if it's executable and
     if the user may run it.
  */
  if (lstat(filename, &st) < 0)
    return FALSE;

  if ((st.st_mode & S_IWUSR) ||
      (st.st_mode & S_IWGRP) ||
      (st.st_mode & S_IWOTH))
    return TRUE;

  return FALSE;
}


gint
libnd_misc_int_cmp(gconstpointer row1, gconstpointer row2)
{
  if ((int) row1 < (int) row2)
    return -1;

  if ((int) row1 > (int) row2)
    return 1;

  return 0;
}


char     *
libnd_misc_add_slash(char *filename)
{
  int len;

  if (!filename)
    return NULL;

  len = strlen(filename);
  
  if (filename[len - 1] == '/')
    return filename;

  filename = realloc(filename, len + 2);
  filename[len] = '/';
  filename[len+1] = 0;

  return filename;
}


GList *
libnd_misc_get_if_names(int flags)
{
  int            fd, last_len, len, num_ifs;
  guchar        *buf = NULL, *ptr, *cptr;
  struct ifconf  ifconf;
  struct ifreq  *ifr, ifrcopy;
  GList         *l = NULL, *ifs = NULL;

  D_ENTER;

  if ( (fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
      D(("Socket error\n"));
      goto cleanup;
    }

  /* Start with looking for up to 10 adresses,
   * loop if that's not enough, doubling the amount each time.
   */
  num_ifs = 5;
  buf = NULL;
  memset(&ifconf, 0, sizeof(struct ifconf));
  
  do {
    last_len = ifconf.ifc_len;
    g_free(buf);

    num_ifs *= 2;
    len = sizeof(struct ifreq) * num_ifs;
    
    if (! (buf = malloc(len)))
      {
	D(("Out of memory.\n"));
	fflush(stderr);
	goto cleanup_fd;
      }

    ifconf.ifc_len = len;
    ifconf.ifc_buf = (char *) buf;
    
    if (ioctl(fd, SIOCGIFCONF, &ifconf) < 0)
      {
	D(("ioctl error\n"));
	goto cleanup_fd;
      }
    
  } while (ifconf.ifc_len != last_len);

  D(("Interface list aquired.\n"));
  
  for (ptr = buf; ptr < buf + ifconf.ifc_len; )
    {
      ifr = (struct ifreq *) ptr;

#ifdef HAVE_SOCKADDR_SA_LEN
      len = MAX(sizeof(struct sockaddr, ifr->ifr_addr.sa_len));
#else
      switch (ifr->ifr_addr.sa_family)
	{
#ifdef IPV6
	case AF_INET6:
	  len = sizeof(struct sockaddr_in6);
	  break;
#endif
	case AF_INET:
	default:
	  len = sizeof(struct sockaddr);
	}
#endif
      ptr += sizeof(ifr->ifr_name) + len;

      /* Create copy of ifreq for flags retrieval: */
      ifrcopy = *ifr;
      if (ioctl(fd, SIOCGIFFLAGS, &ifrcopy) >= 0)
	{
	  /* If the interface doesn't have what's asked for, skip. */
	  if ((ifrcopy.ifr_flags & flags) != flags)
	    continue;
	}

      /* Solaris interface alias handling, see Stevens */
      if ( (cptr = (guchar *) strchr(ifr->ifr_name, ':')))
	*cptr = '\0';

      for (l = ifs; l; l = g_list_next(l))
	{
	  /* If we've had that name already, skip it. */
	  if (strncmp(l->data, ifr->ifr_name, IFNAMSIZ) == 0)
	    continue;
	}

      ifs = g_list_prepend(ifs, g_strdup(ifr->ifr_name));
    }

  /* If we're on Linux, use at least kernel 2.2, and so
   * far we've found any up interfaces, also offer the "any" device:
   */
#ifdef LINUX_HOST
  if (ifs && (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 2, 0)))
    ifs = g_list_append(ifs, g_strdup("any"));
#endif

  /* Finally, sort the list alphanumerically */
  ifs = g_list_sort(ifs, (GCompareFunc) strcmp);

 cleanup_fd:
  close(fd);

 cleanup:
  g_free(buf);
  D_RETURN_(ifs);
}


int       
libnd_misc_get_if_mtu(const char *ifname)
{
  struct ifreq ifreq;
  int fd;
  int mtu = (64 * 1024); /* based on pcap's BIGGER_THAN_ALL_MTUS :) */

  if (!ifname)
    return mtu;

#ifdef SIOCGIFMTU
  memset(&ifreq, 0, sizeof(struct ifreq));
  memcpy(&ifreq.ifr_name, ifname, MIN(strlen(ifname), IFNAMSIZ));
  ifreq.ifr_name[IFNAMSIZ - 1] = '\0';

  if ( (fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
      D(("Socket error\n"));
      return mtu;
    }
  
  if (ioctl(fd, SIOCGIFMTU, &ifreq) < 0)
    {
      D(("ioctl error\n"));
      goto cleanup;      
    }

  mtu = ifreq.ifr_mtu;

 cleanup:
  close(fd);
#endif

  return mtu;
}


void      
libnd_misc_get_hardware_string(char *str, guint str_len, guchar *address, guint addr_len)
{
  guint  i;
  char  *str_ptr;

  if (!str)
    return;

  str_ptr = str;

  for (i = 0; i < addr_len - 1; i++)
    {
      g_snprintf(str_ptr, str_len - (str_ptr - str), "%.2x:", address[i]);
      str_ptr += 3;
    }
  g_snprintf(str_ptr, MAXPATHLEN - (str_ptr - str), "%.2x", address[addr_len - 1]);
}


/* fixme - this should be const char* */
char     *
libnd_misc_get_unnamed_string(void)
{
  static int index = 1;
  static char name[MAXPATHLEN];
  
  g_snprintf(name, MAXPATHLEN, "netdude-%i.trace", index++);
  return name;
}


char     *
libnd_misc_timeval_to_string(struct bpf_timeval *tv)
{
  static char s[MAXPATHLEN];

  if (!tv)
    return "";

  g_snprintf(s, MAXPATHLEN, "%lu days, %.2lu:%.2lu:%.2lu:%lu",
	     (long unsigned int) tv->tv_sec / 86400,
	     (long unsigned int) (tv->tv_sec % 86400) / 3600,
	     (long unsigned int) (tv->tv_sec % 3600) / 60,
	     (long unsigned int) (tv->tv_sec % 60),
	     (long unsigned int) tv->tv_usec);
  
  return s;
}


guchar   *
libnd_misc_memdup(const guchar *data, guint data_len)
{
  guchar *result = NULL;

  if (!data || data_len == 0)
    return NULL;

  if (! (result = malloc(data_len)))
    return NULL;

  memcpy(result, data, data_len);

  return result;
}


void
libnd_misc_ctime(const struct bpf_timeval *tv,
		 char *buf, int buflen,
		 gboolean fix_gmt, gboolean add_usec)
{
  char *timestr, *bufptr;
  time_t time_gmt;

  if (!tv || !buf || buflen == 0)
    return;

  time_gmt = tv->tv_sec;

  if (fix_gmt)
    time_gmt += libnd_misc_get_gmt_deviation();

  /* Get the "real" timestring  and paste it into the buffer */
  timestr = ctime(&time_gmt);
  g_snprintf(buf, buflen, "%s", timestr);

  /* Find last occurrences of whitespace -- that's where we
   * want to insert the microseconds.
   */
  timestr = strrchr(timestr, ' ');
  bufptr = strrchr(buf, ' ');
  *bufptr = '\0';

  /* Insert the microseconds */
  if (add_usec)
    g_snprintf(buf + strlen(buf), buflen - strlen(buf), ":%lu", (long unsigned) tv->tv_usec);
  /* And then the rest of the original ctime string */
  g_snprintf(buf + strlen(buf), buflen - strlen(buf), "%s", timestr);
  
}


int       
libnd_misc_get_gmt_deviation(void)
{
  static gboolean deja_vu = FALSE;
  static int deviation;

  time_t     t;
  int        dir;
  struct tm *gmttime, *loctime;
  struct tm  tm;

  if (deja_vu)
    return deviation;

  t = time(NULL);
  gmttime   = &tm;
  *gmttime  = *gmtime(&t);
  loctime = localtime(&t);
     
  deviation = (loctime->tm_hour - gmttime->tm_hour) * 3600 +
    (loctime->tm_min - gmttime->tm_min) * 60;
  if ((dir = loctime->tm_year - gmttime->tm_year) == 0)
    dir = loctime->tm_yday - gmttime->tm_yday;
  
  deviation += dir * 24 * 3600;

  D(("Deviation is %i\n", deviation));
  deja_vu = TRUE;

  return deviation;
}


char     *
libnd_misc_get_tmpfile(const char *filename)
{
  static int counter = 0;
  char *workdir;
  char  result[MAXPATHLEN];

  if (!filename || !*filename)
    filename = "none";

  libnd_prefs_get_str_item(LND_DOM_NETDUDE, "workdir", &workdir);

  g_snprintf(result, MAXPATHLEN, "%s/%s-%u-%i-%s",
	     workdir,
	     LND_SWAPNAME_PFX,
	     getpid(),
	     ++counter,
	     g_basename(filename));

  D(("Allocated swapname '%s'\n", result));

  return g_strdup(result);
}



syntax highlighted by Code2HTML, v. 0.9.1