The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*  You may distribute under the terms of either the GNU General Public License
 *  or the Artistic License (the same terms as Perl itself)
 *
 *  (C) Paul Evans, 2008-2011 -- leonerd@leonerd.org.uk
 */

#include "../../socket-gai-config.h"

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#define NEED_newCONSTSUB
#define NEED_newRV_noinc
#define NEED_sv_2pv_flags
#include "../../ppport.h"

#ifdef HAS_GETADDRINFO

#include <stdlib.h>
#include <stdio.h>

#ifdef WIN32
# undef WINVER
# define WINVER          0x0501
# ifdef __GNUC__
#  define USE_W32_SOCKETS
# endif
# include <winsock2.h>
/* We need to include ws2tcpip.h to get the IPv6 definitions.
 * It will in turn include wspiapi.h.  Later versions of that
 * header in the Windows SDK generate C++ template code that
 * can't be compiled with VC6 anymore.  The _WSPIAPI_COUNTOF
 * definition below prevents wspiapi.h from generating this
 * incompatible code.
 */
# define _WSPIAPI_COUNTOF(_Array) (sizeof(_Array) / sizeof(_Array[0]))
# undef UNICODE
# include <ws2tcpip.h>
# ifndef NI_NUMERICSERV
#  error Microsoft Platform SDK (Aug. 2001) or later required.
# endif
# ifdef _MSC_VER
#  pragma comment(lib, "Ws2_32.lib")
# endif
#else
# include <sys/types.h>
# include <sys/socket.h>
# include <netdb.h>
#endif

/* These are not gni() constants; they're extensions for the perl API */
#define NIx_NOHOST   (1 << 0)
#define NIx_NOSERV   (1 << 1)

static SV *err_to_SV(pTHX_ int err)
{
  SV *ret = sv_newmortal();
  SvUPGRADE(ret, SVt_PVNV);

  if(err) {
    const char *error = gai_strerror(err);
    sv_setpv(ret, error);
  }
  else {
    sv_setpv(ret, "");
  }

  SvIV_set(ret, err); SvIOK_on(ret);

  return ret;
}

static void setup_constants(void)
{
  HV *stash;
  AV *export;

  stash = gv_stashpvn("Socket::GetAddrInfo", 19, TRUE);
  export = get_av("Socket::GetAddrInfo::EXPORT_OK", TRUE);

#define DO_CONSTANT(c) \
  newCONSTSUB(stash, #c, newSViv(c)); \
  av_push(export, newSVpv(#c, 0));

  DO_CONSTANT(AI_PASSIVE)
  DO_CONSTANT(AI_CANONNAME)
  DO_CONSTANT(AI_NUMERICHOST)

#ifdef AI_V4MAPPED
  DO_CONSTANT(AI_V4MAPPED)
#endif
#ifdef AI_ALL
  DO_CONSTANT(AI_ALL)
#endif
#ifdef AI_ADDRCONFIG
  DO_CONSTANT(AI_ADDRCONFIG)
#endif

#ifdef AI_NUMERICSERV
  DO_CONSTANT(AI_NUMERICSERV)
#endif

#ifdef AI_IDN
  DO_CONSTANT(AI_IDN)
#endif
#ifdef AI_CANONIDN
  DO_CONSTANT(AI_CANONIDN)
#endif
#ifdef AI_IDN_ALLOW_UNASSIGNED
  DO_CONSTANT(AI_IDN_ALLOW_UNASSIGNED)
#endif
#ifdef AI_IDN_USE_STD3_ASCII_RULES
  DO_CONSTANT(AI_IDN_USE_STD3_ASCII_RULES)
#endif

  DO_CONSTANT(EAI_BADFLAGS)
  DO_CONSTANT(EAI_NONAME)
  DO_CONSTANT(EAI_AGAIN)
  DO_CONSTANT(EAI_FAIL)
#ifdef EAI_NODATA
  DO_CONSTANT(EAI_NODATA)
#endif
  DO_CONSTANT(EAI_FAMILY)
  DO_CONSTANT(EAI_SOCKTYPE)
  DO_CONSTANT(EAI_SERVICE)
#ifdef EAI_ADDRFAMILY
  DO_CONSTANT(EAI_ADDRFAMILY)
#endif
  DO_CONSTANT(EAI_MEMORY)

#ifdef EAI_SYSTEM
  DO_CONSTANT(EAI_SYSTEM)
#endif
#ifdef EAI_BADHINTS
  DO_CONSTANT(EAI_BADHINTS)
#endif
#ifdef EAI_PROTOCOL
  DO_CONSTANT(EAI_PROTOCOL)
#endif

  DO_CONSTANT(NI_NUMERICHOST)
  DO_CONSTANT(NI_NUMERICSERV)
  DO_CONSTANT(NI_NAMEREQD)
  DO_CONSTANT(NI_DGRAM)

#ifdef NI_NOFQDN
  DO_CONSTANT(NI_NOFQDN)
#endif

#ifdef NI_IDN
  DO_CONSTANT(NI_IDN)
#endif
#ifdef NI_IDN_ALLOW_UNASSIGNED
  DO_CONSTANT(NI_IDN_ALLOW_UNASSIGNED)
#endif
#ifdef NI_IDN_USE_STD3_ASCII_RULES
  DO_CONSTANT(NI_IDN_USE_STD3_ASCII_RULES)
#endif

  DO_CONSTANT(NIx_NOHOST)
  DO_CONSTANT(NIx_NOSERV)
}

static void xs_getaddrinfo(pTHX_ CV *cv)
{
    dVAR;
    dXSARGS;

    SV   *host;
    SV   *service;
    SV   *hints;

    char *hostname = NULL;
    char *servicename = NULL;
    STRLEN len;
    struct addrinfo hints_s;
    struct addrinfo *res;
    struct addrinfo *res_iter;
    int err;
    int n_res;

    if(items > 3)
      croak("Usage: Socket::GetAddrInfo(host, service, hints)");

    SP -= items;

    if(items < 1)
      host = &PL_sv_undef;
    else
      host = ST(0);

    if(items < 2)
      service = &PL_sv_undef;
    else
      service = ST(1);

    if(items < 3)
      hints = NULL;
    else
      hints = ST(2);

    SvGETMAGIC(host);
    if(SvOK(host)) {
      hostname = SvPV_nomg(host, len);
      if (!len)
        hostname = NULL;
    }

    SvGETMAGIC(service);
    if(SvOK(service)) {
      servicename = SvPV_nomg(service, len);
      if (!len)
        servicename = NULL;
    }

    Zero(&hints_s, sizeof hints_s, char);
    hints_s.ai_family = PF_UNSPEC;

    if(hints && SvOK(hints)) {
      HV *hintshash;
      SV **valp;

      if(!SvROK(hints) || SvTYPE(SvRV(hints)) != SVt_PVHV)
        croak("hints is not a HASH reference");

      hintshash = (HV*)SvRV(hints);

      if((valp = hv_fetch(hintshash, "flags", 5, 0)) != NULL)
        hints_s.ai_flags = SvIV(*valp);
      if((valp = hv_fetch(hintshash, "family", 6, 0)) != NULL)
        hints_s.ai_family = SvIV(*valp);
      if((valp = hv_fetch(hintshash, "socktype", 8, 0)) != NULL)
        hints_s.ai_socktype = SvIV(*valp);
      if((valp = hv_fetch(hintshash, "protocol", 8, 0)) != NULL)
        hints_s.ai_protocol = SvIV(*valp);
    }

    err = getaddrinfo(hostname, servicename, &hints_s, &res);

    XPUSHs(err_to_SV(aTHX_ err));

    if(err)
      XSRETURN(1);

    n_res = 0;
    for(res_iter = res; res_iter; res_iter = res_iter->ai_next) {
      HV *res_hv = newHV();

      (void)hv_stores(res_hv, "family",   newSViv(res_iter->ai_family));
      (void)hv_stores(res_hv, "socktype", newSViv(res_iter->ai_socktype));
      (void)hv_stores(res_hv, "protocol", newSViv(res_iter->ai_protocol));

      (void)hv_stores(res_hv, "addr",     newSVpvn((char*)res_iter->ai_addr, res_iter->ai_addrlen));

      if(res_iter->ai_canonname)
        (void)hv_stores(res_hv, "canonname", newSVpv(res_iter->ai_canonname, 0));
      else
        (void)hv_stores(res_hv, "canonname", newSV(0));

      XPUSHs(sv_2mortal(newRV_noinc((SV*)res_hv)));
      n_res++;
    }

    freeaddrinfo(res);

    XSRETURN(1 + n_res);
}

static void xs_getnameinfo(pTHX_ CV *cv)
{
    dVAR;
    dXSARGS;

    SV  *addr;
    int  flags;
    int  xflags;

    char host[1024];
    char serv[256];
    char *sa; /* we'll cast to struct sockaddr * when necessary */
    STRLEN addr_len;
    int err;

    int want_host, want_serv;

    if(items < 1 || items > 3)
      croak("Usage: Socket::GetAddrInfo(addr, flags=0, xflags=0)");

    SP -= items;

    addr = ST(0);

    if(items < 2)
      flags = 0;
    else
      flags = SvIV(ST(1));

    if(items < 3)
      xflags = 0;
    else
      xflags = SvIV(ST(2));

    want_host = !(xflags & NIx_NOHOST);
    want_serv = !(xflags & NIx_NOSERV);

    if(!SvPOK(addr))
      croak("addr is not a string");

    addr_len = SvCUR(addr);

    /* We need to ensure the sockaddr is aligned, because a random SvPV might
     * not be due to SvOOK */
    Newx(sa, addr_len, char);
    Copy(SvPV_nolen(addr), sa, addr_len, char);
#ifdef HAS_SOCKADDR_SA_LEN
    ((struct sockaddr *)sa)->sa_len = addr_len;
#endif

    err = getnameinfo((struct sockaddr *)sa, addr_len,
      want_host ? host : NULL, want_host ? sizeof(host) : 0,
      want_serv ? serv : NULL, want_serv ? sizeof(serv) : 0,
      flags);

    Safefree(sa);

    XPUSHs(err_to_SV(aTHX_ err));

    if(err)
      XSRETURN(1);

    XPUSHs(want_host ? sv_2mortal(newSVpv(host, 0)) : &PL_sv_undef);
    XPUSHs(want_serv ? sv_2mortal(newSVpv(serv, 0)) : &PL_sv_undef);

    XSRETURN(3);
}

#endif

MODULE = Socket::GetAddrInfo  PACKAGE = Socket::GetAddrInfo

BOOT:
#ifdef HAS_GETADDRINFO
  setup_constants();
  newXS("Socket::GetAddrInfo::getaddrinfo", xs_getaddrinfo, __FILE__);
  newXS("Socket::GetAddrInfo::getnameinfo", xs_getnameinfo, __FILE__);
#endif