/***************************************************************************
 *
 * Copyright (c) 2000, 2001 BalaBit IT Ltd, Budapest, Hungary
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: sockaddr.c,v 1.18.2.1.2.4 2001/09/19 19:22:17 bazsi Exp $
 *
 * Author  : Bazsi
 * Auditor : kisza
 * Last version: 1.11
 * Notes:
 *   - process identifier in warnings
 *
 ***************************************************************************/

#include <zorp/sockaddr.h>
#include <zorp/log.h>
#include <zorp/cap.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>

/*+

  Thread friendly version of inet_ntoa(), converts an IP address to
  human readable form.

  Parameters:
    buf        store result in this buffer
    bufsize    the available space in buf
    a          address to convert.

  Returns:
    the address of buf

  +*/
gchar *
z_inet_ntoa(char *buf, size_t bufsize, struct in_addr a)
{
  unsigned int ip = ntohl(a.s_addr);

  snprintf(buf, bufsize, "%d.%d.%d.%d", 
	   (ip & 0xff000000) >> 24,
	   (ip & 0x00ff0000) >> 16,
	   (ip & 0x0000ff00) >> 8,
	   (ip & 0x000000ff));
  return buf;
}

gint
z_inet_aton(char *buf, struct in_addr *a)
{
  return inet_aton(buf, a);
}

/*+

  A thin interface around bind() using a ZSockAddr structure for
  socket address. It enables the NET_BIND_SERVICE capability (should be
  in the permitted set.

  Parameters:
    fd         fd to bind
    addr       address to bind to

  Returns:
    glib style I/O error

  +*/
GIOError z_bind(int fd, ZSockAddr *addr)
{
  cap_t saved_caps;
  GIOError rc;
  
  saved_caps = cap_save();
  
  cap_enable(CAP_NET_BIND_SERVICE); /* for binding low numbered ports */
  cap_enable(CAP_NET_ADMIN); /* for binding non-local interfaces, transparent proxying */
  
  if (addr->sa_funcs && addr->sa_funcs->sa_bind_prepare)
    addr->sa_funcs->sa_bind_prepare(fd, addr);

  if (addr->sa_funcs && addr->sa_funcs->sa_bind)
    rc = addr->sa_funcs->sa_bind(fd, addr);
  else
    {
      if (addr && bind(fd, &addr->sa, addr->salen) < 0)
        {
          z_warning(0, "bind(): failed due to: %s.", g_strerror(errno));
          cap_restore(saved_caps);
          return G_IO_ERROR_UNKNOWN;
        }
      rc = G_IO_ERROR_NONE;
    }
  cap_restore(saved_caps);
  return rc;
}

#if ENABLE_IPOPTIONS
static gboolean
z_get_ip_options(int fd, ZSockAddr *a)
{
  guchar buf[128]; /* ip options can be up to 40 bytes in size */
  guint buflen; 
  
  if (a->sa.sa_family != AF_INET)
    /* only AF_INET is supported */
    return FALSE;

  buflen = sizeof(buf);
  if (getsockopt(fd, SOL_IP, IP_OPTIONS, &buf, &buflen) == 0)
    {
      guchar newbuf[128];
      guchar *src = buf, *dst = newbuf;
      guint left = buflen, length;
      
      while (left)
        {
          if (*src == '\0')
            {
              /* EOOL */
              if (left > 1)
                {
                  /* hmm... this seems to be an invalid option list to me */
                  /*LOG
                    This message indicates that processing IP options failed,
                    because data was found after end-of-option-list. This might
                    be the result of a buggy TCP stack or an attack attempt.
                   */
                  z_log(NULL, CORE_ERROR, 1, "Invalid IP options, data after EOOL;");
                  return FALSE;
                }
              left--;
              break;
            }
          else if (*src == '\1')
            {
              /* NOP */
              left--;
            }
          else if (*src == 130 || *src == 133 || *src == 134)
            {
              /* RIPSO and CIPSO */
              
              /* this option is copied if present */
              if (left <= 1)
                {
                  /* no space for length field */
                  return FALSE;
                }
              length = *(src + 1);
              if (length > left)
                {
                  /* option too long */
                  return FALSE;
                }
              memcpy(dst, src, length);
              left -= length;
              dst += length;
              src += length;
            }
          else 
            {
              /* everything else is skipped */
              if (left <= 1)
                return FALSE;
              length = *(src + 1);
              left -= length;
              dst += length;
              src += length;
            }
        }
      z_sockaddr_inet_set_options(a, newbuf, dst - newbuf);
    }
  return TRUE;
}

static gboolean
z_set_ip_options(int fd, ZSockAddr *a)
{
  gpointer options = NULL;
  guint options_length;
    
  if (a->sa.sa_family != AF_INET)
    return FALSE;
  z_sockaddr_inet_get_options(a, &options, &options_length);
  if (options)
    {
      if (setsockopt(fd, SOL_IP, IP_OPTIONS, options, options_length) < 0)
        return FALSE;
    }
  return TRUE;    
}

#endif

/*+

  Accept a connection on the given fd, returning the newfd and the
  address of the client in a Zorp SockAddr structure.

  Parameters:
    fd         accept connection on this socket
    newfd      fd of the accepted connection
    addr       store the address of the client here

  Returns:
    glib style I/O error

  +*/
GIOError z_accept(int fd, int *newfd, ZSockAddr **addr)
{
  char sabuf[1024];
  int salen = sizeof(sabuf);
  
  do
    {
      *newfd = accept(fd, (struct sockaddr *) sabuf, &salen);
    }
  while (*newfd == -1 && errno == EINTR);
  if (*newfd != -1)
    {
      *addr = z_sockaddr_new((struct sockaddr *) sabuf, salen);
#if ENABLE_IPOPTIONS
      z_get_ip_options(*newfd, *addr);
#endif
    }
  else
    {
      /*LOG
        This message indicates that accepting a connection was failed
        because of the given reason.
       */
      z_log(NULL, CORE_ERROR, 1, "accept() failed; fd=%d, error='%m'", fd);
      return G_IO_ERROR_UNKNOWN;
    }
  return G_IO_ERROR_NONE;
}

/*+

  Connect a socket using Zorp style ZSockAddr structure.

  Parameters:
    fd         socket to connect to
    remote     remote address

  Returns:
    glib style I/O error

  +*/
GIOError 
z_connect(int fd, ZSockAddr *remote)
{
  int rc;

#if ENABLE_IPOPTIONS
  z_set_ip_options(fd, remote);
#endif
  do
    {
      rc = connect(fd, &remote->sa, remote->salen);
    }
  while (rc == -1 && errno == EINTR);
  if (rc == -1)
    {
      if (errno != EINPROGRESS)
        {
          int saved_errno = errno;
      
          /*LOG
            This message indicates that establishing connection failed for
            the given reason.
           */
          z_log(NULL, CORE_ERROR, 1, "connect() failed; fd=%d, error='%m'", fd);
          errno = saved_errno;
        }
      return G_IO_ERROR_UNKNOWN;
    }
  else
    {
      return G_IO_ERROR_NONE;
    }
}

/* general ZSockAddr functions */


/*+

  General function to allocate and initialize a ZSockAddr structure,
  and convert a libc style sockaddr * pointer to our representation.

  Parameters:
    sa         libc sockaddr * pointer to convert
    salen      size of sa

  Returns:
    a ZSockAddr instance

  +*/
ZSockAddr *
z_sockaddr_new(struct sockaddr *sa, int salen)
{
  switch (sa->sa_family)
    {
    case AF_INET:
      if (salen != sizeof(struct sockaddr_in))
        return NULL;
        
      return z_sockaddr_inet_new2((struct sockaddr_in *) sa);
    case AF_UNIX:
      return z_sockaddr_unix_new2((struct sockaddr_un *) sa, salen);
    default:
      /*LOG
        This message indicates an internal error, Zorp tries to use an
        address family it doesn't support.
       */
      z_log(NULL, CORE_ERROR, 3, "Unsupported socket family in z_sockaddr_new(); family='%d'", sa->sa_family);
      return NULL;
    }
}

/*+

  Format a ZSockAddr into human readable form, calls the format
  virtual method of ZSockAddr.

  Parameters:
    a        instance pointer of a ZSockAddr
    text     destination buffer
    n        the size of text

  Returns:
    text is filled with a human readable representation of a, and a
    pointer to text is returned.

  +*/
char *
z_sockaddr_format(ZSockAddr *a, gchar *text, gulong n)
{
  return a->sa_funcs->sa_format(a, text, n);
}

/*+

  Increment the reference count of a ZSockAddr instance.

  Parameters:
    a         pointer to ZSockAddr
  
  Returns:
    the same instance

  +*/
ZSockAddr *
z_sockaddr_ref(ZSockAddr *a)
{
  if (a)
    a->refcnt++;
  return a;
}

/*+

  Decrement the reference count of a ZSockAddr instance, and free if
  the refcnt reaches 0.

  Parameters:
    a        ZSockAddr instance

  Returns:
    none

  +*/
void 
z_sockaddr_unref(ZSockAddr *a)
{
  /* FIXME: maybe add a callback to funcs table */
  if (a) 
    {
      if (--a->refcnt == 0)
        {
          if (!a->sa_funcs->freefn)
            g_free(a);
          else
            a->sa_funcs->freefn(a);
        }
    }
}

/* AF_INET socket address */
/*+

  ZSockAddrInet is an implementation of the ZSockAddr interface,
  encapsulating an IPv4 host address and port number.

  +*/
typedef struct _ZSockAddrInet
{
  gint refcnt;
  guint32 flags;
  ZSockAddrFuncs *sa_funcs;
  int salen;
  struct sockaddr_in sin;
  gpointer options;
  guint options_length;
} ZSockAddrInet;

/*+ private function to prepare an IPv4 style bind, e.g. set
  SO_REUSEADDR +*/
static GIOError 
z_sockaddr_inet_bind_prepare(int sock, ZSockAddr *addr)
{
  int tmp = 1;
  
  setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof(tmp));
  return G_IO_ERROR_NONE;
}

/*+ format an IPv4 address into human readable form */
gchar *
z_sockaddr_inet_format(ZSockAddr *addr, gchar *text, gulong n)
{
  ZSockAddrInet *inet_addr = (ZSockAddrInet *) addr;
  char buf[32];
  
  g_snprintf(text, n, "AF_INET(%s:%d)", 
	     z_inet_ntoa(buf, sizeof(buf), inet_addr->sin.sin_addr), 
             htons(inet_addr->sin.sin_port));
  return text;
}

void
z_sockaddr_inet_free(ZSockAddr *addr)
{
  ZSockAddrInet *self = (ZSockAddrInet *) addr;

  if (self->options)
    g_free(self->options);
  g_free(addr);
}

static ZSockAddrFuncs inet_sockaddr_funcs = 
{
  z_sockaddr_inet_bind_prepare,
  NULL,
  z_sockaddr_inet_format,
  z_sockaddr_inet_free
};

/*+

  Allocate and initialize an IPv4 socket address.

  Parameters:
    ip          text representation of an IP address
    port        port number in host byte order

  Returns:
    the new instance

  +*/
ZSockAddr *
z_sockaddr_inet_new(gchar *ip, guint16 port)
{
  ZSockAddrInet *addr = g_new0(ZSockAddrInet, 1);
  
  addr->refcnt = 1;
  addr->flags = 0;
  addr->salen = sizeof(struct sockaddr_in);
  addr->sin.sin_family = AF_INET;
  inet_aton(ip, &addr->sin.sin_addr);
  addr->sin.sin_port = htons(port);
  addr->sa_funcs = &inet_sockaddr_funcs;
  
  return (ZSockAddr *) addr;
}


/*+

  Allocate and initialize an IPv4 socket address using libc sockaddr *
  structure.

  Parameters:
    sin         the sockaddr_in structure to convert

  Returns:
    the allocated instance.

  +*/
ZSockAddr *
z_sockaddr_inet_new2(struct sockaddr_in *sin)
{
  ZSockAddrInet *addr = g_new0(ZSockAddrInet, 1);
  
  addr->refcnt = 1;
  addr->flags = 0;
  addr->salen = sizeof(struct sockaddr_in);
  addr->sin = *sin;
  addr->sa_funcs = &inet_sockaddr_funcs;
  
  return (ZSockAddr *) addr;
}

#if ENABLE_IPOPTIONS
void
z_sockaddr_inet_get_options(ZSockAddr *s, gpointer *options, guint *options_length)
{
  ZSockAddrInet *self = (ZSockAddrInet *) s;
  
  *options = self->options;
  *options_length = self->options_length;
}

void
z_sockaddr_inet_set_options(ZSockAddr *s, gpointer options, guint options_length)
{
  ZSockAddrInet *self = (ZSockAddrInet *) s;
  
  if (self->options)
    g_free(self->options);
  
  self->options = g_malloc0(options_length);
  memcpy(self->options, options, options_length);
  self->options_length = options_length;
}
#endif


/*+ Similar to ZSockAddrInet, but binds to a port in the given range +*/

/* 
 * NOTE: it is assumed that it is safe to cast from ZSockAddrInetRange to
 * ZSockAddrInet
 */
typedef struct _ZSockAddrInetRange
{
  gint refcnt;
  guint32 flags;
  ZSockAddrFuncs *sa_funcs;
  int salen;
  struct sockaddr_in sin;
  gpointer options;
  guint options_length;
  guint16 min_port, max_port, last_port;
} ZSockAddrInetRange;

static GIOError
z_sockaddr_inet_range_bind(int sock, ZSockAddr *a)
{
  ZSockAddrInetRange *addr = (ZSockAddrInetRange *) a;
  int port;
  
  if (addr->min_port > addr->max_port)
    {
      /*LOG
        This message indicates that SockAddrInetRange was given incorrect
        parameters, the allowed min_port is greater than max_port.
       */
      z_log(NULL, CORE_ERROR, 2, "SockAddrInetRange, invalid range given; min_port='%d', max_port='%d'", addr->min_port, addr->max_port);
      return G_IO_ERROR_UNKNOWN;
    }
  for (port = addr->last_port; port <= addr->max_port; port++)
    {
      /* attempt to bind */
      addr->sin.sin_port = htons(port);
      if (bind(sock, (struct sockaddr *) &addr->sin, addr->salen) == 0)
        {
          /*LOG
            This debug message is used by SockAddrInetRange to inform you
            about the allocated dynamic port.
           */
          z_log(NULL, CORE_DEBUG, 6, "SockAddrInetRange, successfully bound; min_port='%d', max_port='%d', port='%d'", addr->min_port, addr->max_port, port);
          addr->last_port = port + 1;
          return G_IO_ERROR_NONE;
        }
    }
  for (port = addr->min_port; port <= addr->max_port; port++)
    {
      /* attempt to bind */
      addr->sin.sin_port = htons(port);
      if (bind(sock, (struct sockaddr *) &addr->sin, addr->salen) == 0)
        {
          /*NOLOG*/ /* the previous message is the same */
          z_log(NULL, CORE_DEBUG, 6, "SockAddrInetRange, successfully bound; min_port='%d', max_port='%d', port='%d'", addr->min_port, addr->max_port, port);
          addr->last_port = port + 1;
          return G_IO_ERROR_NONE;
        }
    }
  addr->last_port = addr->min_port;
  return G_IO_ERROR_UNKNOWN;
}

static ZSockAddrFuncs inet_range_sockaddr_funcs = 
{
  NULL,
  z_sockaddr_inet_range_bind,
  z_sockaddr_inet_format,
  z_sockaddr_inet_free,
};

ZSockAddr *
z_sockaddr_inet_range_new(gchar *ip, guint16 min_port, guint16 max_port)
{
  ZSockAddrInetRange *addr = g_new0(ZSockAddrInetRange, 1);
  
  addr->refcnt = 1;
  addr->flags = 0;
  addr->salen = sizeof(struct sockaddr_in);
  addr->sin.sin_family = AF_INET;
  inet_aton(ip, &addr->sin.sin_addr);
  addr->sin.sin_port = 0;
  addr->sa_funcs = &inet_range_sockaddr_funcs;
  if (max_port > min_port)
    {
      addr->last_port = (rand() % (max_port - min_port)) + min_port;
    }
  addr->min_port = min_port;
  addr->max_port = max_port;
  
  return (ZSockAddr *) addr;
}

/* AF_UNIX socket address */

/*+

  The ZSockAddrUnix class is an implementation of the ZSockAddr
  interface encapsulating AF_UNIX domain socket names.

  +*/
typedef struct _ZSockAddrUnix
{
  gint refcnt;
  guint32 flags;
  ZSockAddrFuncs *sa_funcs;
  int salen;
  struct sockaddr_un saun;
} ZSockAddrUnix;

static GIOError z_sockaddr_unix_bind_prepare(int sock, ZSockAddr *addr);
static gchar *z_sockaddr_unix_format(ZSockAddr *addr, gchar *text, gulong n);

static ZSockAddrFuncs unix_sockaddr_funcs = 
{
  z_sockaddr_unix_bind_prepare,
  NULL,
  z_sockaddr_unix_format
};

/* anonymous if name == NULL */

/*+

  Allocate and initialize a ZSockAddrUnix instance.

  Parameters:
    name              socket filename

  Returns:
    the new instance

  +*/
ZSockAddr *
z_sockaddr_unix_new(gchar *name)
{
  ZSockAddrUnix *addr = g_new0(ZSockAddrUnix, 1);
  
  addr->refcnt = 1;
  addr->flags = 0;
  addr->sa_funcs = &unix_sockaddr_funcs;
  addr->saun.sun_family = AF_UNIX;
  if (name)
    {
      strncpy(addr->saun.sun_path, name, sizeof(addr->saun.sun_path) - 1);
      addr->saun.sun_path[sizeof(addr->saun.sun_path) - 1] = 0;
      addr->salen = sizeof(addr->saun) - sizeof(addr->saun.sun_path) + strlen(addr->saun.sun_path) + 1;
    }
  else
    {
      addr->saun.sun_path[0] = 0;
      addr->salen = 2;
    }
  return (ZSockAddr *) addr;
}

/*+

  Allocate and initialize a ZSockAddrUnix instance, using libc
  sockaddr_un strucutre.

  Parameters:
    saun        sockaddr_un structure to convert
    sunlen      size of saun

  Returns:
    the new instance

  +*/
ZSockAddr *
z_sockaddr_unix_new2(struct sockaddr_un *saun, int sunlen)
{
  ZSockAddrUnix *addr = g_new0(ZSockAddrUnix, 1);
  
  addr->refcnt = 1;
  addr->flags = 0;
  addr->sa_funcs = &unix_sockaddr_funcs;
  addr->salen = sunlen;
  addr->saun = *saun;
  return (ZSockAddr *) addr;
}

/*+ private function to prepare a bind on AF_UNIX sockets, e.g. unlink
  the socket if it exists and is a socket. +*/
static GIOError 
z_sockaddr_unix_bind_prepare(int sock, ZSockAddr *addr)
{
  ZSockAddrUnix *unix_addr = (ZSockAddrUnix *) addr;
  struct stat st;
  
  if (unix_addr->saun.sun_path[0] == 0)
    return G_IO_ERROR_NONE;

  if (stat(unix_addr->saun.sun_path, &st) == -1 ||
      !S_ISSOCK(st.st_mode))
    return G_IO_ERROR_NONE;
  
  unlink(unix_addr->saun.sun_path);  
  return G_IO_ERROR_NONE;
}

/*+ Convert a ZSockAddrUnix into human readable form. +*/
gchar *
z_sockaddr_unix_format(ZSockAddr *addr, gchar *text, gulong n)
{
  ZSockAddrUnix *unix_addr = (ZSockAddrUnix *) addr;
  
  g_snprintf(text, n, "AF_UNIX(%s)", 
             unix_addr->saun.sun_path[0] ? unix_addr->saun.sun_path
                                          : "anonymous");
  return text;
}

