/* sml3_nw_support.c: Wrapper fuer nichtunterstuetzte Netzwerk-Funktionen */

/* Copyright 2012-2017 Kurt Nienhaus
 *
 * This file is part of libsammel3.
 * libsammel3 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.
 * libsammel3 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 libsammel3.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <ctype.h>
#include <sys/un.h>
#include "config.h"
#include "sml3_fehler.h"
#include "sml3_util.h"
#include "sml3_nw_support.h"

int SML3_getaddrinfo(const char *, const char *, const struct addrinfo *, struct addrinfo **);
void SML3_freeaddrinfo(struct addrinfo *);
int SML3_getnameinfo(const struct sockaddr *, socklen_t, char *, size_t, char *, size_t, int);
int SML3_inet_pton(int, const char *, void *);
const char * SML3_inet_ntop(int, const void *, char *, size_t);
int SML3_getaddrinfo_unix(const char *, const struct addrinfo *, struct addrinfo **);
int SML3_getnameinfo_unix(const struct sockaddr *, char *, size_t);
void SML3_freeaddrinfo_unix(struct addrinfo *);

static int _gai_error(const char *, int);
static int ga_unix(const char *, struct addrinfo *, struct addrinfo **);
static int ga_aistruct_unix(struct addrinfo ***, const struct addrinfo *, const char *, int);


#ifndef SML3_HAVE_GETADDRINFO

/* from: UNIX Network Programming Vol. 1, 2nd Ed.
 *       W. Richard Stevens
 *
 * [nicht thread-sicher]
 */

struct search {
  const char *host;  /* hostname or address string */
  int family;        /* AF_* */
};

static int getaddrinfo(const char *, const char *, const struct addrinfo *, struct addrinfo **);
static void freeaddrinfo(struct addrinfo *);
static int getnameinfo(const struct sockaddr *, socklen_t, char *, size_t, char *, size_t, int);

static int ga_aistruct(struct addrinfo ***, const struct addrinfo *, const void *, int);
static int ga_echeck(const char *, const char *, int, int, int, int);
static struct addrinfo * ga_clone(struct addrinfo *);
static int ga_nsearch(const char *, const struct addrinfo *, struct search *);
static int ga_port(struct addrinfo *, int, int);
static int ga_serv(struct addrinfo *, const struct addrinfo *, const char *);

#define EAI_BADFLAGS   -1  /* Invalid value for `ai_flags' field */
#define EAI_NONAME     -2  /* NAME or SERVICE is unknown */
#define EAI_AGAIN      -3  /* Temporary failure in name resolution */
#define EAI_FAIL       -4  /* Non-recoverable failure in name res */
#define EAI_FAMILY     -6  /* `ai_family' not supported */
#define EAI_SOCKTYPE   -7  /* `ai_socktype' not supported */
#define EAI_SERVICE    -8  /* SERVICE not supported for `ai_socktype' */
#define EAI_MEMORY    -10  /* Memory allocation failure */
#define EAI_SYSTEM    -11  /* System error returned in `errno' */
#define EAI_OVERFLOW  -12  /* Argument buffer overflow */

#ifndef INADDRSZ
# define INADDRSZ    4
#endif

static int
getaddrinfo(const char *hostname, const char *servname, const struct addrinfo *hintsp, struct addrinfo **result)
{
  int rc, error;
  char *canon;
  struct addrinfo hints, *aihead, **aipnext;
# define gaderror(e)  do { error = (e); goto bad; } while (0)
  char **ap;
  struct hostent *hptr;
  struct search search;

  aihead = NULL;
  aipnext = &aihead;
  canon = NULL;
  if (hintsp == NULL) {
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
  } else {
    hints = *hintsp;
  }
  rc = ga_echeck(hostname, servname, hints.ai_flags, hints.ai_family, hints.ai_socktype, hints.ai_protocol);
  if (rc != 0) { gaderror(rc); }

  if (ga_nsearch(hostname, &hints, &search) == -1) { gaderror(-1); }
  if (isdigit((int)(unsigned char)search.host[0])) {
    struct in_addr inaddr;
    if (SML3_inet_pton(AF_INET, search.host, &inaddr) > 0) {
      if (hints.ai_family != AF_UNSPEC && hints.ai_family != AF_INET) { gaderror(EAI_FAMILY); }
      if (search.family != AF_INET) { gaderror(EAI_FAMILY); }
      rc = ga_aistruct(&aipnext, &hints, &inaddr, AF_INET);
      if (rc != 0) { gaderror(rc); }
    }
  } else {
    hptr = gethostbyname(search.host);
    if (hptr == NULL) {
      switch (h_errno) {
        case HOST_NOT_FOUND:  gaderror(EAI_NONAME);
        case TRY_AGAIN:       gaderror(EAI_AGAIN);
        case NO_RECOVERY:     gaderror(EAI_FAIL);
        case NO_DATA:         gaderror(EAI_FAMILY);
        default:              gaderror(EAI_NONAME);
      }
    }
    if (hints.ai_family != AF_UNSPEC && hints.ai_family != hptr->h_addrtype) { gaderror(EAI_FAMILY); }
    if (hostname != NULL && hostname[0] != '\0' && (hints.ai_flags & AI_CANONNAME) && canon == NULL) {
      if ((canon = strdup(hptr->h_name)) == NULL) { gaderror(EAI_MEMORY); }
    }
    for (ap = hptr->h_addr_list; *ap != NULL; ap++) {
      rc = ga_aistruct(&aipnext, &hints, *ap, hptr->h_addrtype);
      if (rc != 0) { gaderror(rc); }
    }
  }
  if (aihead == NULL) { gaderror(EAI_NONAME); }

  if (hostname != NULL && hostname[0] != '\0' && (hints.ai_flags & AI_CANONNAME)) {
    if (canon != NULL) { aihead->ai_canonname = canon; }
    else {
      if ((aihead->ai_canonname = strdup(search.host)) == NULL) { gaderror(EAI_MEMORY); }
    }
  }
  if (servname != NULL && servname[0] != '\0') {
    if ((rc = ga_serv(aihead, &hints, servname)) != 0) { gaderror(rc); }
  }
  *result = aihead;
  return 0;

bad:
  freeaddrinfo(aihead);
  return error;
} /* Ende getaddrinfo */


static void
freeaddrinfo(struct addrinfo *aihead)
{
  struct addrinfo *ai, *ainext;
  for (ai = aihead; ai != NULL; ai = ainext) {
    if (ai->ai_addr != NULL) { free(ai->ai_addr); }
    if (ai->ai_canonname != NULL) { free(ai->ai_canonname); }
    ainext = ai->ai_next;
    free(ai);
  }
} /* Ende freeaddrinfo */


static int
ga_aistruct(struct addrinfo ***paipnext, const struct addrinfo *hintsp, const void *addr, int family)
{
  struct addrinfo *ai;
  if ((ai = calloc(1, sizeof(struct addrinfo))) == NULL) { return EAI_MEMORY; }
  ai->ai_next = NULL;
  ai->ai_canonname = NULL;
  **paipnext = ai;
  *paipnext = &ai->ai_next;
  if ((ai->ai_socktype = hintsp->ai_socktype) == 0) { ai->ai_flags |= AI_CLONE; }
  ai->ai_protocol = hintsp->ai_protocol;

  switch((ai->ai_family = family)) {
    case AF_INET: {
      struct sockaddr_in *sinptr;
      if ((sinptr = calloc(1, sizeof(struct sockaddr_in))) == NULL) { return EAI_MEMORY; }
#ifdef SML3_HAVE_SOCKADDR_SA_LEN
      sinptr->sin_len = sizeof(struct sockaddr_in);
#endif
      sinptr->sin_family = AF_INET;
      memcpy(&sinptr->sin_addr, addr, sizeof(struct in_addr));
      ai->ai_addr = (struct sockaddr *)sinptr;
      ai->ai_addrlen = sizeof(struct sockaddr_in);
      break;
    }
  }
  return 0;
} /* Ende ga_aistruct */


static struct addrinfo *
ga_clone(struct addrinfo *ai)
{
  struct addrinfo *new;
  if ((new = calloc(1, sizeof(struct addrinfo))) == NULL) { return NULL; }
  new->ai_next = ai->ai_next;
  ai->ai_next = new;
  new->ai_flags = 0;
  new->ai_family = ai->ai_family;
  new->ai_socktype = ai->ai_socktype;
  new->ai_protocol = ai->ai_protocol;
  new->ai_canonname = NULL;
  new->ai_addrlen = ai->ai_addrlen;
  if ((new->ai_addr = malloc(ai->ai_addrlen)) == NULL) { return NULL; }
  memcpy(new->ai_addr, ai->ai_addr, ai->ai_addrlen);
  return new;
} /* Ende ga_clone */


static int
ga_echeck(const char *hostname, const char *servname, int flags, int family, int socktype, int protocol)
{
  if (flags & ~(AI_PASSIVE | AI_CANONNAME)) { return EAI_BADFLAGS; }
  if (hostname == NULL || hostname[0] == '\0') {
    if (servname == NULL || servname[0] == '\0') { return EAI_NONAME; }
  }
  switch(family) {
    case AF_UNSPEC:
    case AF_INET:
      if (socktype != 0 && socktype != SOCK_STREAM && socktype != SOCK_DGRAM && socktype != SOCK_RAW) { return EAI_SOCKTYPE; }
      break;
    default:
      return EAI_FAMILY;
  }
  (void)protocol;
  return 0;
} /* Ende ga_echeck */


static int
ga_nsearch(const char *hostname, const struct addrinfo *hintsp, struct search *search)
{
  int nsearch = 0;
  if (hostname == NULL) {
    if (hintsp->ai_flags & AI_PASSIVE) {  /* no host and AI_PASSIVE: wildcard bind */
      switch (hintsp->ai_family) {
        case AF_UNSPEC:
        case AF_INET:
          search[0].host = "0.0.0.0";
          search[0].family = AF_INET;
          nsearch++;
          break;
      }

    } else {  /* no host and not AI_PASSIVE: connect to local host */
      switch (hintsp->ai_family) {
        case AF_UNSPEC:
        case AF_INET:
          search[0].host = "localhost";
          search[0].family = AF_INET;
          nsearch++;
          break;
      }
    }

  } else {  /* host is specified */
    switch (hintsp->ai_family) {
      case AF_UNSPEC:
      case AF_INET:
        search[0].host = hostname;
        search[0].family = AF_INET;
        nsearch++;
        break;
    }
  }
  if (nsearch != 1) { return -1; }
  return nsearch;
} /* Ende ga_nsearch */


static int
ga_port(struct addrinfo *aihead, int port, int socktype)
{
  int nfound = 0;
  struct addrinfo *ai;
  for (ai = aihead; ai != NULL; ai = ai->ai_next) {
    if (ai->ai_flags & AI_CLONE) {
      if (ai->ai_socktype != 0) {
        if ((ai = ga_clone(ai)) == NULL) { return -1; }
      }
    } else if (ai->ai_socktype != socktype) {
      continue;
    }
    ai->ai_socktype = socktype;
    switch (ai->ai_family) {
      case AF_INET:
        ((struct sockaddr_in *)ai->ai_addr)->sin_port = port;
        nfound++;
        break;
    }
  }
  return nfound;
} /* Ende ga_port */


static int
ga_serv(struct addrinfo *aihead, const struct addrinfo *hintsp, const char *serv)
{
  int port, rc, nfound;
  struct servent *sptr;
  nfound = 0;
  if (isdigit((int)(unsigned char)serv[0])) {
    port = htons(atoi(serv));
    if (hintsp->ai_socktype) {
      if ((rc = ga_port(aihead, port, hintsp->ai_socktype)) < 0) { return EAI_MEMORY; }
      nfound += rc;
    } else {
      if ((rc = ga_port(aihead, port, SOCK_STREAM)) < 0) { return EAI_MEMORY; }
      nfound += rc;
      if ((rc = ga_port(aihead, port, SOCK_DGRAM)) < 0) { return EAI_MEMORY; }
      nfound += rc;
    }
  } else {
    if (hintsp->ai_socktype == 0 || hintsp->ai_socktype == SOCK_STREAM) {
      if ((sptr = getservbyname((char *)serv, "tcp")) != NULL) {
        if ((rc = ga_port(aihead, sptr->s_port, SOCK_STREAM)) < 0) { return EAI_MEMORY; }
        nfound += rc;
      }
    }
    if (hintsp->ai_socktype == 0 || hintsp->ai_socktype == SOCK_DGRAM) {
      if ((sptr = getservbyname((char *)serv, "udp")) != NULL) {
        if ((rc = ga_port(aihead, sptr->s_port, SOCK_DGRAM)) < 0) { return EAI_MEMORY; }
        nfound += rc;
      }
    }
  }
  if (nfound == 0) {
    if (hintsp->ai_socktype == 0) { return EAI_NONAME; } else { return EAI_SERVICE; }
  }
  return 0;
} /* Ende ga_serv */


static int
getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags)
{
  struct sockaddr_in *sain;
  if (sa->sa_family != AF_INET) { return EAI_FAMILY; }
  sain = (struct sockaddr_in *)sa;
  if (hostlen > 0) {
    struct hostent *hptr;
    if (flags & NI_NUMERICHOST) {
      if (SML3_inet_ntop(AF_INET, &sain->sin_addr, host, hostlen) == NULL) { return EAI_SYSTEM; }
    } else {
      hptr = gethostbyaddr((void *)&sain->sin_addr, sizeof(struct in_addr), AF_INET);
      if (hptr != NULL && hptr->h_name != NULL) {
        if (flags & NI_NOFQDN) {
          char *ptr;
          if ((ptr = strchr(hptr->h_name, '.')) != NULL) { *ptr = 0; }
        }
        snprintf(host, hostlen, "%s", hptr->h_name);
      } else {
        if (flags & NI_NAMEREQD) { return EAI_NONAME; }
        if (SML3_inet_ntop(AF_INET, &sain->sin_addr, host, hostlen) == NULL) { return EAI_SYSTEM; }
      }
    }
  }
  if (servlen > 0) {
    struct servent *sptr;
    if (flags & NI_NUMERICSERV) {
      snprintf(serv, servlen, "%d", ntohs(sain->sin_port));
    } else {
      sptr = getservbyport(sain->sin_port, (flags & NI_DGRAM) ? "udp" : NULL);
      if (sptr != NULL && sptr->s_name != NULL) {
        snprintf(serv, servlen, "%s", sptr->s_name);
      } else {
        snprintf(serv, servlen, "%d", ntohs(sain->sin_port));
      }
    }
  }
  (void)salen;
  return 0;
} /* Ende getnameinfo */

#endif /* SML3_HAVE_GETADDRINFO */


/* wandelt gai_strerror()-Fehlermeldung in SML3-errno-Fehlermeldung um */
static int
_gai_error(const char *fnk, int errnr)
{
#ifdef EAI_BADFLAGS
  if (errnr == EAI_BADFLAGS) {
    SML3_fehlernew(EINVAL, "%s: invalid value for `ai_flags' field (EAI_BADFLAGS)", fnk);
    return -1;
  }
#endif

#ifdef EAI_NONAME
  if (errnr == EAI_NONAME) {
    SML3_fehlernew(EINVAL, "%s: NAME or SERVICE is unknown (EAI_NONAME)", fnk);
    return -1;
  }
#endif

#ifdef EAI_AGAIN
  if (errnr == EAI_AGAIN) {
    SML3_fehlernew(EAGAIN, "%s: temporary failure in name resolution (EAI_AGAIN)", fnk);
    return -1;
  }
#endif

#ifdef EAI_FAIL
  if (errnr == EAI_FAIL) {
    SML3_fehlernew(ENXIO, "%s: non-recoverable failure in name resolution (EAI_FAIL)", fnk);
    return -1;
  }
#endif

#ifdef EAI_FAMILY
  if (errnr == EAI_FAMILY) {
    SML3_fehlernew(EINVAL, "%s: `ai_family' not supported (EAI_FAMILY)", fnk);
    return -1;
  }
#endif

#ifdef EAI_SOCKTYPE
  if (errnr == EAI_SOCKTYPE) {
    SML3_fehlernew(EINVAL, "%s: `ai_socktype' not supported (EAI_SOCKTYPE)", fnk);
    return -1;
  }
#endif

#ifdef EAI_SERVICE
  if (errnr == EAI_SERVICE) {
    SML3_fehlernew(EINVAL, "%s: SERVICE not supported for `ai_socktype' (EAI_SERVICE)", fnk);
    return -1;
  }
#endif

#ifdef EAI_MEMORY
  if (errnr == EAI_MEMORY) {
    SML3_fehlernew(ENOMEM, "%s: memory allocation failure (EAI_MEMORY)", fnk);
    return -1;
  }
#endif

#ifdef EAI_OVERFLOW
  if (errnr == EAI_OVERFLOW) {
    SML3_fehlernew(EINVAL, "%s: argument buffer overflow (EAI_OVERFLOW)", fnk);
    return -1;
  }
#endif

#ifdef EAI_SYSTEM
  if (errnr == EAI_SYSTEM) {
    SML3_fehlernew(EINVAL, "%s: system error: %s", fnk, SML3_strerror(errno));
    return -1;
  }
#endif

#ifdef EAI_ADDRFAMILY
  if (errnr == EAI_ADDRFAMILY) {
    SML3_fehlernew(EINVAL, "%s: address family for hostname not supported (EAI_ADDRFAMILY)", fnk);
    return -1;
  }
#endif

#ifdef EAI_BADHINTS
  if (errnr == EAI_BADHINTS) {
    SML3_fehlernew(EINVAL, "%s: invalid value for hints (EAI_BADHINTS)", fnk);
    return -1;
  }
#endif

#ifdef EAI_NODATA
  if (errnr == EAI_NODATA) {
    SML3_fehlernew(EINVAL, "%s: no address associated with hostname (EAI_NODATA)", fnk);
    return -1;
  }
#endif

#ifdef EAI_PROTOCOL
  if (errnr == EAI_PROTOCOL) {
    SML3_fehlernew(EINVAL, "%s: resolved protocol is unknown (EAI_PROTOCOL)", fnk);
    return -1;
  }
#endif

  SML3_fehlernew(EINVAL, "%s: unknown error (%d)", fnk, errnr);
  return -1;
} /* Ende _gai_error */


/* SML3_getaddrinfo: siehe getaddrinfo()
 * Args: wie bei getaddrinfo()
 * Rueckgabe: 0 = OK oder -1 = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 *                  EAGAIN = Namensaufloesung temporaer fehlgeschlagen
 *                  ENXIO = Namensaufloesung fehlgeschlagen
 *                  ENOMEM = Allokationsfehler
 */
int
SML3_getaddrinfo(const char *hostname, const char *servname, const struct addrinfo *hintsp, struct addrinfo **result)
{
  int retw = getaddrinfo(hostname, servname, hintsp, result);
  if (retw == 0) { return 0; }
  return _gai_error("SML3_getaddrinfo", retw);
} /* Ende SML3_getaddrinfo */


/* SML3_freeaddrinfo: siehe freeaddrinfo()
 * Args: wie bei freeaddrinfo()
 */
void
SML3_freeaddrinfo(struct addrinfo *aihead)
{
  freeaddrinfo(aihead);
} /* Ende SML3_freeaddrinfo */


/* SML3_getnameinfo: siehe getnameinfo()
 * Args: wie bei getnameinfo()
 * Rueckgabe: 0 = OK oder -1 = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 *                  EAGAIN = Namensaufloesung temporaer fehlgeschlagen
 *                  ENXIO = Namensaufloesung fehlgeschlagen
 *                  ENOMEM = Allokationsfehler
 */
int
SML3_getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags)
{
  int retw = getnameinfo(sa, salen, host, hostlen, serv, servlen, flags);
  if (retw == 0) { return 0; }
  return _gai_error("SML3_getnameinfo", retw);
} /* Ende SML3_getnameinfo */


/* SML3_inet_pton: siehe inet_pton()
 * Args: wie bei inet_pton()
 * Rueckgabe: 1 = OK oder 0 = keine gueltige Adresse uebergeben oder -1 = Fehler
 * SML3-errno-Wert: EAFNOSUPPORT = Adressfamilie nicht unterstuetzt
 */
int
SML3_inet_pton(int af, const char *src, void *dst)
{
#ifndef SML3_HAVE_INET_PTON
  /* from: Paul Vixie, 1996 */
  if (af == AF_INET) {
    unsigned int punktanz = 0, imrk;
    unsigned char mrk[INADDRSZ], *pmrk;
    char c1;
    memset(mrk, 0, INADDRSZ);
    pmrk = mrk;
    while ((c1 = *src++) != '\0') {
      switch(c1) {
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9': imrk = *pmrk * 10 + (c1 - '0');
                  if (imrk > 255) { return 0; }
                  *pmrk = imrk;
                  break;
        case '.': if (++punktanz >= INADDRSZ) { return 0; }
                  pmrk++;
                  break;
        default: return 0;
      }
    }
    if (dst != NULL) { memmove(dst, mrk, INADDRSZ); }
    return 1;
  }
  SML3_fehlernew(EAFNOSUPPORT, "%s", SML3_strerror(EAFNOSUPPORT));
  return -1;
#else /* SML3_HAVE_INET_PTON */
  int retw = inet_pton(af, src, dst);
  if (retw < 0) { SML3_fehlernew(EAFNOSUPPORT, "%s", SML3_strerror(errno)); }
  return retw;
#endif /* SML3_HAVE_INET_PTON */
} /* Ende SML3_inet_pton */


/* SML3_inet_ntop: siehe inet_ntop()
 * Args: wie bei inet_ntop()
 * Rueckgabe: Pointer auf 3.Arg oder NULL = Fehler
 * SML3-errno-Wert: EAFNOSUPPORT = Adressfamilie nicht unterstuetzt
 *                  ENOSPC = nicht genug Platz im 3.Arg
 */
const char *
SML3_inet_ntop(int af, const void *src, char *dst, size_t size)
{
#ifndef SML3_HAVE_INET_NTOP
  /* from: Paul Vixie, 1996 */
  if (af == AF_INET) {
    static const char fmt[] = "%u.%u.%u.%u";
    char tmp[16];
    unsigned char *csrc;
    csrc = (unsigned char *)src;
    snprintf(tmp, sizeof(tmp), fmt, csrc[0], csrc[1], csrc[2], csrc[3]);
    if (strlen(tmp) > size) { SML3_fehlernew(ENOSPC, "%s", SML3_strerror(ENOSPC)); return NULL; }
    if (dst != NULL) { strncpy(dst, tmp, size - 1); dst[size - 1] = '\0'; }
    return dst;
  }
  SML3_fehlernew(EAFNOSUPPORT, "%s", SML3_strerror(EAFNOSUPPORT));
  return NULL;
#else /* SML3_HAVE_INET_NTOP */
  const char *retw = inet_ntop(af, src, dst, size);
  if (retw == NULL) { SML3_fehlernew(errno == ENOSPC ? ENOSPC : EAFNOSUPPORT, "%s", SML3_strerror(errno)); }
  return retw;
#endif /* SML3_HAVE_INET_NTOP */
} /* Ende SML3_inet_ntop */


/* *** getaddrinfo_unix *** */

#ifdef SML3_HAVE_SOCKADDR_SA_LEN
# ifndef SUN_LEN
#  define SUN_LEN(A) (sizeof(*(A)) - sizeof((A)->sun_path) + strlen((A)->sun_path))
# endif
#endif


/* SML3_getaddrinfo_unix: wie getaddrinfo() fuer UNIXPath-Verbindungen
 * 1.Arg: UNIX-Socketpfad
 * weitere Args: wie bei getaddrinfo() ab 3.Arg
 * Rueckgabe: 0 = OK oder -1 = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 *                  ENOMEM = Allokationsfehler
 */
int
SML3_getaddrinfo_unix(const char *pathname, const struct addrinfo *hintsp, struct addrinfo **result)
{
  struct addrinfo hints;
  int retw;

  if (hintsp == NULL) {
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
  } else {
    hints = *hintsp;
  }

  if (hints.ai_flags & ~(AI_PASSIVE | AI_CANONNAME)) {
    return _gai_error("SML3_getaddrinfo_unix", EAI_BADFLAGS);
  }
  if (pathname == NULL || pathname[0] == '\0') { 
    return _gai_error("SML3_getaddrinfo_unix", EAI_NONAME);
  }

  switch(hints.ai_family) {
    case AF_UNSPEC:
    case AF_LOCAL:
      if (hints.ai_socktype != 0
          && hints.ai_socktype != SOCK_STREAM
          && hints.ai_socktype != SOCK_DGRAM
         ) {
        return _gai_error("SML3_getaddrinfo_unix", EAI_SOCKTYPE);
      }
      break;
    default:
      return _gai_error("SML3_getaddrinfo_unix", EAI_FAMILY);
  }

  retw = ga_unix(pathname, &hints, result);
  if (retw == 0) { return 0; }
  return _gai_error("SML3_getaddrinfo_unix", retw);
} /* Ende SML3_getaddrinfo_unix */


/* SML3_getnameinfo_unix: wie getnameinfo() fuer UNIXPath-Verbindungen
 * 1.Arg: wie bei getnameinfo() 1.Arg
 * 2.+3.Arg: wie bei getnameinfo() 3.+4.Arg fuer Rueckgabe UNIX-Socket-Pfad
 * Rueckgabe: 0 = OK oder -1 = Fehler
 * SML3-errno-Wert: EINVAL = Fehler Uebergabeparameter
 *                  ENOMEM = Allokationsfehler
 */
int
SML3_getnameinfo_unix(const struct sockaddr *sa, char *pathname, size_t pathlen)
{
  struct sockaddr_un *unp = (struct sockaddr_un *)sa;

  if (sa->sa_family != AF_LOCAL) {
    return _gai_error("SML3_getnameinfo_unix", EAI_FAMILY);
  }

  if (pathname != NULL && pathlen > 0) { snprintf(pathname, pathlen, "%s", unp->sun_path); }

  return 0;
} /* Ende SML3_getnameinfo_unix */


/* SML3_freeaddrinfo_unix: wie freeaddrinfo() fuer UNIXPath-Verbindungen
 * Args: wie bei freeaddrinfo()
 */
void
SML3_freeaddrinfo_unix(struct addrinfo *aihead)
{
  struct addrinfo *ai, *ainext;

  for (ai = aihead; ai != NULL; ai = ainext) {
    if (ai->ai_addr != NULL) { free(ai->ai_addr); }
    if (ai->ai_canonname != NULL) { free(ai->ai_canonname); }
    ainext = ai->ai_next;
    free(ai);
  }
} /* Ende SML3_freeaddrinfo_unix */


static int
ga_unix(const char *pathname, struct addrinfo *hintsp, struct addrinfo **result)
{
  int rc;
  struct addrinfo *aihead, **aipnext;

  aihead = NULL;
  aipnext = &aihead;
  if (hintsp->ai_family != AF_UNSPEC && hintsp->ai_family != AF_LOCAL) { return EAI_FAMILY; }
  if (hintsp->ai_socktype == 0) {
    hintsp->ai_socktype = SOCK_STREAM;
    if ((rc = ga_aistruct_unix(&aipnext, hintsp, pathname, AF_LOCAL)) != 0) { SML3_freeaddrinfo_unix(aihead); return rc; }
    hintsp->ai_socktype = SOCK_DGRAM;
  }
  if ((rc = ga_aistruct_unix(&aipnext, hintsp, pathname, AF_LOCAL)) != 0) { SML3_freeaddrinfo_unix(aihead); return rc; }
  if (hintsp->ai_flags & AI_CANONNAME) {
    char hname[256];
    if (gethostname(hname, sizeof(hname)) < 0) { SML3_freeaddrinfo_unix(aihead); return EAI_SYSTEM; }
    if ((aihead->ai_canonname = strdup(hname)) == NULL) { SML3_freeaddrinfo_unix(aihead); return EAI_MEMORY; }
  }
  *result = aihead;
  return 0;
} /* Ende ga_unix */


static int
ga_aistruct_unix(struct addrinfo ***paipnext, const struct addrinfo *hintsp, const char *pathname, int family) {
  struct addrinfo *ai;

  if ((ai = calloc(1, sizeof(struct addrinfo))) == NULL) { return EAI_MEMORY; }
  ai->ai_next = NULL;
  ai->ai_canonname = NULL;
  **paipnext = ai;
  *paipnext = &ai->ai_next;
  ai->ai_protocol = hintsp->ai_protocol;
  ai->ai_socktype = hintsp->ai_socktype;

  switch((ai->ai_family = family)) {
    case AF_LOCAL: {
      struct sockaddr_un *unp;
      if (strlen(pathname) >= sizeof(unp->sun_path)) { return EAI_SERVICE; }
      if ((unp = calloc(1, sizeof(struct sockaddr_un))) == NULL) { return EAI_MEMORY; }
      unp->sun_family = AF_LOCAL;
      snprintf(unp->sun_path, sizeof(unp->sun_path), "%s", pathname);
#ifdef SML3_HAVE_SOCKADDR_SA_LEN
      unp->sun_len = SUN_LEN(unp);
#endif
      ai->ai_addr = (struct sockaddr *)unp;
      ai->ai_addrlen = sizeof(struct sockaddr_un);
      if (hintsp->ai_flags & AI_PASSIVE) { unlink(unp->sun_path); }
      break;
    }
  }
  return 0;
} /* Ende ga_aistruct_unix */

/* *** Ende getaddrinfo_unix *** */
