/* Copyright (c) 1995-1999 NEC USA, Inc.  All rights reserved.               */
/*                                                                           */
/* The redistribution, use and modification in source or binary forms of     */
/* this software is subject to the conditions set forth in the copyright     */
/* document ("Copyright") included with this distribution.                   */

/*
 * $Id: addr.c,v 1.21.4.4 1999/05/19 16:53:31 steve Exp $
 */

#include "socks5p.h"
#include "threads.h"
#include "addr.h"
#include "log.h"

IFTHREADED(extern MUTEX_T gh_mutex;)
IFTHREADED(extern MUTEX_T gs_mutex;)

/* Given a name return the network ordered address associated with that      */
/* name or INVALIDADDR (-1) on an error.                                     */
int lsName2Addr(const char *name, S5NetAddr *na) {
    struct hostent *hp;

    if (!name || *name == '\0' || !strcmp(name, "-")) {
	return -1;
    }

    /* XXX needs IPv6 support eventually                                     */
    memset(&na->sin, 0, sizeof(ssi));
    na->sin.sin_family      = AF_INET;
    na->sin.sin_addr.s_addr = INVALIDADDR;

    if ((na->sin.sin_addr.s_addr = inet_addr((char *)name)) != INVALIDADDR) {
	return 0;
    }
    
    MUTEX_LOCK(gh_mutex);
    if ((hp = REAL(gethostbyname)(name))) memcpy(&na->sin.sin_addr.s_addr, hp->h_addr_list[0], hp->h_length);
    MUTEX_UNLOCK(gh_mutex);

    return hp?0:-1;
}

/* Given a name return the network ordered port associated with that name,   */
/* or INVALIDPORT (-1) on an error.                                          */
int lsName2Port(const char *name, const char *proto, u_short *port) {
    struct servent *sp;

    if (isdigit((unsigned char)*name)) {
	*port = (u_short)atoi(name);
	*port = htons(*port);
	return 0;
    }

    MUTEX_LOCK(gs_mutex);
    if ((sp = getservbyname((char *)name, proto))) *port = sp->s_port;
    MUTEX_UNLOCK(gs_mutex);
    if (sp) return 0;

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "Unresolvable service name: %s", name);
    *port = INVALIDPORT;
    return -1;
}

/* Return 0 if the address is NULL                                            */
int lsAddrIsNull(const S5NetAddr *addr) {
    int rval = 0;

    switch (addr->sa.sa_family) {
	case AF_S5NAME:
	    if (*addr->sn.sn_name != '\0') rval = -1;
	    break;
	case AF_INET:
	    if (addr->sin.sin_addr.s_addr != INADDR_ANY && addr->sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) rval = -1;
	    break;
#ifdef HAVE_NETINET6_IN6_H
	case AF_INET6:
	    if (addr->sin6.sin6_addr != INADDR_ANY && addr->sin6.sin6_addr != htonl(INADDR_LOOPBACK)) rval = -1;
	    break;
#endif
	default:
	    break;
    }

    return rval;
}

int lsAddrComp(const S5NetAddr *a1, const S5NetAddr *a2) {
    if (a1->sa.sa_family != a2->sa.sa_family) return -1;

    switch (a1->sa.sa_family) {
	case AF_S5NAME:
	    if (a1->sn.sn_port != a2->sn.sn_port) return -1;
	    return strcmp(a1->sn.sn_name, a2->sn.sn_name);
	case AF_INET:
	    if (a1->sin.sin_port != a2->sin.sin_port) return -1;
	    return memcmp(&a1->sin.sin_addr, &a2->sin.sin_addr, sizeof(struct in_addr));
#ifdef HAVE_NETINET6_IN6_H
	case AF_INET6:
	    if (a1->sin6.sin6_port != a2->sin6.sin6_port) return -1;
	    return memcmp(&a1->sin6.sin6_addr, &a2->sin6.sin6_addr, sizeof(struct in_addr6));
#endif
	default:
	    return -1;
    }
}

int lsAddrAddrComp(const S5NetAddr *a1, const S5NetAddr *a2) {
    if (a1->sa.sa_family != a2->sa.sa_family) return -1;

    switch (a1->sa.sa_family) {
	case AF_S5NAME:
	    return strcmp(a1->sn.sn_name, a2->sn.sn_name);
	case AF_INET:
	    return memcmp(&a1->sin.sin_addr, &a2->sin.sin_addr, sizeof(struct in_addr));
#ifdef HAVE_NETINET6_IN6_H
	case AF_INET6:
	    return memcmp(&a1->sin6.sin6_addr, &a2->sin6.sin6_addr, sizeof(struct in_addr6));
#endif
	default:
	    return -1;
    }
}

void lsAddrCopy(S5NetAddr *dest, const S5NetAddr *src, int len) {
    memcpy(dest, src, MIN(len, lsAddrSize(src)));
}

const char *lsAddr2Ascii(const S5NetAddr *na) {
    switch (na->sa.sa_family) {
	case AF_S5NAME:
	    return na->sn.sn_name;
	case AF_INET:
	    return inet_ntoa(na->sin.sin_addr);
#ifdef HAVE_NETINET6_IN6_H
	case AF_INET6:
	    return addr2ascii(AF_INET6, (char *)&na->sin6.sin6_addr, sizeof(struct in_addr6), NULL);
#endif
	default:
	    return "";
    }
}

u_short lsAddr2Port(const S5NetAddr *na) {
    switch (na->sa.sa_family) {
	case AF_S5NAME:
	    return na->sn.sn_port;
	case AF_INET:
	    return na->sin.sin_port;
#ifdef HAVE_NETINET6_IN6_H
	case AF_INET6:
	    return na->sin6.sin6_port;
#endif
	default:
	    return (u_short)0;
    }
}

void lsAddrSetPort(S5NetAddr *na, u_short port) {
    switch (na->sa.sa_family) {
	case AF_S5NAME:
	    na->sn.sn_port = port;
	    break;
	case AF_INET:
	    na->sin.sin_port = port;
	    break;
#ifdef HAVE_NETINET6_IN6_H
	case AF_INET6:
	    na->sin6.sin6_port = port;
	    break;
#endif
    }
}

const char *lsAddr2Ptr(const S5NetAddr *na) {
    switch (na->sa.sa_family) {
	case AF_S5NAME:
	    return na->sn.sn_name;
	case AF_INET:
	    return (char *)&na->sin.sin_addr;
#ifdef HAVE_NETINET6_IN6_H
	case AF_INET6:
	    return (char *)&na->sin6.sin6_addr;
#endif
	default:
	    return (char *)na->sa.sa_data;
    }
}
    
int lsAddrAddrSize(const S5NetAddr *na) {
    switch (na->sa.sa_family) {
	case AF_S5NAME:
	    return strlen(na->sn.sn_name);
	case AF_INET:
	    return sizeof(struct in_addr);
#ifdef HAVE_NETINET6_IN6_H
	case AF_INET6:
	    return sizeof(struct in_addr6);
#endif
	default:
	    return 0;
    }
}

int lsAddrSize(const S5NetAddr *na) {
    switch (na->sa.sa_family) {
	case AF_S5NAME:
	    return sizeof(ssn);
	case AF_INET:
	    return sizeof(ssi);
#ifdef HAVE_NETINET6_IN6_H
	case AF_INET6:
	    return sizeof(ssi6);
#endif
	default:
	    return 0;
    }
}
