/*
 *  SA - Socket Address Library
 *  Copyright (c) 2001 The OSSP Project (http://www.ossp.org/)
 *  Copyright (c) 2001 Cable & Wireless Deutschland (http://www.cw.com/de/)
 *
 *  This file is part of OSSP lmtp2nntp, an LMTP speaking local
 *  mailer which forwards mails as Usenet news articles via NNTP.
 *  It can be found at http://www.ossp.com/pkg/lmtp2nntp/.
 *
 *  Permission to use, copy, modify, and distribute this software for
 *  any purpose with or without fee is hereby granted, provided that
 *  the above copyright notice and this permission notice appear in all
 *  copies.
 *
 *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 *  IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
 *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 *  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 *  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 *  SUCH DAMAGE.
 *
 *  sa.c: implementation
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#if defined(HAVE_DMALLOC_H) && defined(DMALLOC)
#include "dmalloc.h"
#endif

#include "sa.h"

#if !defined(AF_LOCAL) && defined(AF_UNIX)
#define AF_LOCAL AF_UNIX
#endif

#ifndef HAVE_INET_PTON
#ifdef HAVE_INET_ATON
static int inet_pton(int family, const char *strptr, void *addrptr)
{
    struct in_addr in_val;

    if (family == AF_INET) {
        if (inet_aton(strptr, &in_val)) {
            memcpy(addrptr, &in_val, sizeof(struct in_addr));
            return 1;
        }
        return 0;
    }
    errno = EAFNOSUPPORT;
    return -1;
}
#else
#error "neither inet_pton nor inet_aton available"
#endif
#endif

#ifndef NUL
#define NUL '\0'
#endif

sa_t *sa_create(int sa_type, ...)
{
    va_list ap;
    sa_t *rc;
    int nPort;
    socklen_t sl;
    struct sockaddr *sa;
    struct sockaddr_in sa4;
#ifdef AF_INET6
    struct sockaddr_in6 sa6;
#endif
    struct sockaddr_un sau;
    struct hostent *he;
    struct servent *se;
    struct protoent *pe;
    int bNumeric;
    int i;
    char *cpPath;
    int   nPath;
    char *cpProto;
    int   nProto;
    char *cpHost;
    char *cpPort;

    va_start(ap, sa_type);
    sa = NULL;
    sl = 0;
    if (sa_type == SA_UNIX) {
#if defined(AF_LOCAL)
        if ((cpPath = va_arg(ap, char *)) == NULL)
            return NULL;
        if ((nPath = strlen(cpPath)) >= (sizeof(sau.sun_path)-1))
            return NULL;
        nProto = 0;
        sau.sun_family = AF_LOCAL;
        memcpy(sau.sun_path, cpPath, nPath + 1);
        sa = (struct sockaddr *)&sau;
        sl = sizeof(sau);
#else
        return NULL;
#endif
    }
    else if (sa_type == SA_IP) {
        if ((cpProto = va_arg(ap, char *)) == NULL)
            return NULL;
        if ((cpHost = va_arg(ap, char *)) == NULL)
            return NULL;
        if ((cpPort = va_arg(ap, char *)) == NULL)
            return NULL;

        /* resolve protocol */
        if ((pe = getprotobyname(cpProto)) == NULL)
            return NULL;
        nProto = pe->p_proto;

        /* resolve port */
        bNumeric = 1;
        for (i = 0; cpPort[i] != NUL; i++) {
            if (!isdigit(cpPort[i])) {
                bNumeric = 0;
                break;
            }
        }
        if (bNumeric)
            nPort = atoi(cpPort);
        else {
            if ((se = getservbyname(cpPort, cpProto)) == NULL)
                return NULL;
            nPort = ntohs(se->s_port);
        }

        /* resolve host */
        if (inet_pton(AF_INET, cpHost, &sa4.sin_addr.s_addr) == 1) {
            sa4.sin_family = AF_INET;
            sa4.sin_port = htons(nPort);
            sa = (struct sockaddr *)&sa4;
            sl = sizeof(sa4);
        }
#ifdef AF_INET6
        else if (inet_pton(AF_INET6, cpHost, &sa6.sin6_addr.s6_addr) == 1) {
            sa6.sin6_family = AF_INET6;
            sa6.sin6_port = htons(nPort);
            sa = (struct sockaddr *)&sa6;
            sl = sizeof(sa6);
        }
#endif
        else if ((he = gethostbyname(cpHost)) != NULL) {
            if (he->h_addrtype == AF_INET) {
                sa4.sin_family = AF_INET;
                sa4.sin_port = htons(nPort);
                memcpy(&sa4.sin_addr.s_addr, he->h_addr_list[0], sizeof(sa4.sin_addr.s_addr));
                sa = (struct sockaddr *)&sa4;
                sl = sizeof(sa4);
            }
#ifdef AF_INET6
            else if (he->h_addrtype == AF_INET6) {
                sa6.sin6_family = AF_INET6;
                sa6.sin6_port = htons(nPort);
                memcpy(&sa6.sin6_addr.s6_addr, he->h_addr_list[0], sizeof(sa6.sin6_addr.s6_addr));
                sa = (struct sockaddr *)&sa6;
                sl = sizeof(sa6);
            }
#endif
        }
    }
    else
        return NULL;
    va_end(ap);

    if (sa == NULL)
        return NULL;
        
    if ((rc = (sa_t *)malloc(sizeof(sa_t))) == NULL)
        return NULL;
    if ((rc->sa_buf = (struct sockaddr *)malloc(sl)) == NULL) {
        free(rc);
        return NULL;
    }
    memcpy(rc->sa_buf, sa, sl);
    rc->sa_len = sl;
    rc->sa_proto = nProto;

    return rc;
}

void sa_destroy(sa_t *sa)
{
    if (sa == NULL)
        return;

    if (sa->sa_buf != NULL)
        free(sa->sa_buf);
    free(sa);

    return;
}

