/* IPV4 base transport support functions
 */

#include <net-snmp/net-snmp-config.h>

#include <net-snmp/types.h>
#include <net-snmp/library/snmpIPv4BaseDomain.h>

#include <stddef.h>
#include <stdio.h>
#include <sys/types.h>
#include <ctype.h>
#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#if HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#if HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#if HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#if HAVE_NETDB_H
#include <netdb.h>
#endif

#include <net-snmp/types.h>
#include <net-snmp/library/snmp_debug.h>
#include <net-snmp/library/default_store.h>
#include <net-snmp/library/system.h>


#ifndef INADDR_NONE
#define INADDR_NONE     -1
#endif

int
netsnmp_sockaddr_in(struct sockaddr_in *addr,
                    const char *inpeername, int remote_port)
{
    char buf[sizeof(int) * 3 + 2];
    sprintf(buf, ":%u", remote_port);
    return netsnmp_sockaddr_in2(addr, inpeername, remote_port ? buf : NULL);
}

int
netsnmp_sockaddr_in2(struct sockaddr_in *addr,
                     const char *inpeername, const char *default_target)
{
    int ret;

    if (addr == NULL) {
        return 0;
    }

    DEBUGMSGTL(("netsnmp_sockaddr_in",
                "addr %p, inpeername \"%s\", default_target \"%s\"\n",
                addr, inpeername ? inpeername : "[NIL]",
                default_target ? default_target : "[NIL]"));

    memset(addr, 0, sizeof(struct sockaddr_in));
    addr->sin_addr.s_addr = htonl(INADDR_ANY);
    addr->sin_family = AF_INET;
    addr->sin_port = htons((u_short)SNMP_PORT);

    {
	int port = netsnmp_ds_get_int(NETSNMP_DS_LIBRARY_ID,
				      NETSNMP_DS_LIB_DEFAULT_PORT);

	if (port != 0) {
	    addr->sin_port = htons((u_short)port);
	} else if (default_target != NULL)
	    netsnmp_sockaddr_in2(addr, default_target, NULL);
    }

    if (inpeername != NULL && *inpeername != '\0') {
	const char     *host, *port;
	char           *peername = NULL;
        char           *cp;
        /*
         * Duplicate the peername because we might want to mank around with
         * it.
         */

        peername = strdup(inpeername);
        if (peername == NULL) {
            return 0;
        }

        /*
         * Try and extract an appended port number.
         */
        cp = strchr(peername, ':');
        if (cp != NULL) {
            *cp = '\0';
            port = cp + 1;
            host = peername;
        } else {
            host = NULL;
            port = peername;
        }

        /*
         * Try to convert the user port specifier
         */
        if (port && *port == '\0')
            port = NULL;

        if (port != NULL) {
            long int l;
            char* ep;

            DEBUGMSGTL(("netsnmp_sockaddr_in", "check user service %s\n",
                        port));

            l = strtol(port, &ep, 10);
            if (ep != port && *ep == '\0' && 0 <= l && l <= 0x0ffff)
                addr->sin_port = htons((u_short)l);
            else {
                if (host == NULL) {
                    DEBUGMSGTL(("netsnmp_sockaddr_in",
                                "servname not numeric, "
				"check if it really is a destination)\n"));
                    host = port;
                    port = NULL;
                } else {
                    DEBUGMSGTL(("netsnmp_sockaddr_in",
                                "servname not numeric\n"));
                    free(peername);
                    return 0;
                }
            }
        }

        /*
         * Try to convert the user host specifier
         */
        if (host && *host == '\0')
            host = NULL;

        if (host != NULL) {
            DEBUGMSGTL(("netsnmp_sockaddr_in",
                        "check destination %s\n", host));


            if (strcmp(peername, "255.255.255.255") == 0 ) {
                /*
                 * The explicit broadcast address hack
                 */
                DEBUGMSGTL(("netsnmp_sockaddr_in", "Explicit UDP broadcast\n"));
                addr->sin_addr.s_addr = INADDR_NONE;
            } else {
                ret =
                    netsnmp_gethostbyname_v4(peername, &addr->sin_addr.s_addr);
                if (ret < 0) {
                    DEBUGMSGTL(("netsnmp_sockaddr_in",
                                "couldn't resolve hostname\n"));
                    free(peername);
                    return 0;
                }
                DEBUGMSGTL(("netsnmp_sockaddr_in",
                            "hostname (resolved okay)\n"));
            }
        }
	free(peername);
    }

    /*
     * Finished
     */

    DEBUGMSGTL(("netsnmp_sockaddr_in", "return { AF_INET, %s:%hu }\n",
                inet_ntoa(addr->sin_addr), ntohs(addr->sin_port)));
    return 1;
}

char *
netsnmp_ipv4_fmtaddr(const char *prefix, netsnmp_transport *t,
                     void *data, int len)
{
    netsnmp_indexed_addr_pair *addr_pair = NULL;
    struct hostent *host;
    char tmp[64];

    if (data != NULL && len == sizeof(netsnmp_indexed_addr_pair)) {
	addr_pair = (netsnmp_indexed_addr_pair *) data;
    } else if (t != NULL && t->data != NULL) {
	addr_pair = (netsnmp_indexed_addr_pair *) t->data;
    }

    if (addr_pair == NULL) {
        snprintf(tmp, sizeof(tmp), "%s: unknown", prefix);
    } else {
        struct sockaddr_in *to = NULL;
        to = (struct sockaddr_in *) &(addr_pair->remote_addr);
        if (to == NULL) {
            snprintf(tmp, sizeof(tmp), "%s: unknown->[%s]:%hu", prefix,
                     inet_ntoa(addr_pair->local_addr.sin_addr),
                     ntohs(addr_pair->local_addr.sin_port));
        } else if ( t && t->flags & NETSNMP_TRANSPORT_FLAG_HOSTNAME ) {
            /* XXX: hmm...  why isn't this prefixed */
            /* assuming intentional */
            host = gethostbyaddr((char *)&to->sin_addr, 4, AF_INET);
            return (host ? strdup(host->h_name) : NULL); 
        } else {
            snprintf(tmp, sizeof(tmp), "%s: [%s]:%hu->", prefix,
                     inet_ntoa(to->sin_addr), ntohs(to->sin_port));
            snprintf(tmp + strlen(tmp), sizeof(tmp)-strlen(tmp), "[%s]:%hu",
                     inet_ntoa(addr_pair->local_addr.sin_addr),
                     ntohs(addr_pair->local_addr.sin_port));
        }
    }
    tmp[sizeof(tmp)-1] = '\0';
    return strdup(tmp);
}

