/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * CDDL HEADER END
 */

/* 
 *Portions Copyright 2013 CFEngine AS 
 */

/*
 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <netdb.h>
#include <nss_dbdefs.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <string.h>
#include <stdio.h>
#include <sys/sockio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <net/if.h>
#include <unistd.h>
#include <solaris_ifaddrs.h>



int getallifaddrs(sa_family_t , struct ifaddrs **, int64_t);

int getallifs(int, sa_family_t, struct lifreq **, int *, int64_t);

/*
 * Create a linked list of `struct ifaddrs' structures, one for each
 * address that is UP. If successful, store the list in *ifap and
 * return 0.  On errors, return -1 and set `errno'.
 *
 * The storage returned in *ifap is allocated dynamically and can
 * only be properly freed by passing it to `freeifaddrs'.
 */
int
solaris_getifaddrs(struct ifaddrs **ifap)
{
    int        err;
    char        *cp;
    struct ifaddrs    *curr;

    if (ifap == NULL) {
        errno = EINVAL;
        return (-1);
    }
    *ifap = NULL;
    err = getallifaddrs(AF_UNSPEC, ifap, LIFC_ENABLED);
    if (err == 0) {
        for (curr = *ifap; curr != NULL; curr = curr->ifa_next) {
            if ((cp = strchr(curr->ifa_name, ':')) != NULL)
                *cp = '\0';
            
        }
    }
    return (err);
}

void
solaris_freeifaddrs(struct ifaddrs *ifa)
{
    struct ifaddrs *curr;

    while (ifa != NULL) {
        curr = ifa;
        ifa = ifa->ifa_next;
        free(curr->ifa_name);
        free(curr->ifa_addr);
        free(curr->ifa_netmask);
        free(curr->ifa_dstaddr);
        free(curr);
    }
}

/*
 * Returns all addresses configured on the system. If flags contain
 * LIFC_ENABLED, only the addresses that are UP are returned.
 * Address list that is returned by this function must be freed
 * using freeifaddrs().
 */
int
getallifaddrs(sa_family_t af, struct ifaddrs **ifap, int64_t flags)
{
    struct lifreq *buf = NULL;
    struct lifreq *lifrp;
    struct lifreq lifrl;
    int ret;
    int s, n, numifs;
    struct ifaddrs *curr, *prev;
    sa_family_t lifr_af;
    int sock4;
    int sock6;
    int err;

    if ((sock4 = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
        return (-1);
    if ((sock6 = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
        err = errno;
        close(sock4);
        errno = err;
        return (-1);
    }

retry:
    /* Get all interfaces from SIOCGLIFCONF */
    ret = getallifs(sock4, af, &buf, &numifs, (flags & ~LIFC_ENABLED));
    if (ret != 0)
        goto fail;

    /*
     * Loop through the interfaces obtained from SIOCGLIFCOMF
     * and retrieve the addresses, netmask and flags.
     */
    prev = NULL;
    lifrp = buf;
    *ifap = NULL;
    for (n = 0; n < numifs; n++, lifrp++) {

        /* Prepare for the ioctl call */
        (void) strlcpy(lifrl.lifr_name, lifrp->lifr_name,
                       sizeof(lifrl.lifr_name));
        lifr_af = lifrp->lifr_addr.ss_family;
        if (af != AF_UNSPEC && lifr_af != af)
            continue;

        s = (lifr_af == AF_INET ? sock4 : sock6);

        if (ioctl(s, SIOCGLIFFLAGS, (caddr_t)&lifrl) < 0)
            goto fail;
        if ((flags & LIFC_ENABLED) && !(lifrl.lifr_flags & IFF_UP))
            continue;

        /*
         * Allocate the current list node. Each node contains data
         * for one ifaddrs structure.
         */
        curr = calloc(1, sizeof (struct ifaddrs));
        if (curr == NULL)
            goto fail;

        if (prev != NULL) {
            prev->ifa_next = curr;
        } else {
            /* First node in the linked list */
            *ifap = curr;
        }
        prev = curr;

        curr->ifa_flags = lifrl.lifr_flags;
        if ((curr->ifa_name = strdup(lifrp->lifr_name)) == NULL)
            goto fail;

        curr->ifa_addr = malloc(sizeof (struct sockaddr_storage));
        if (curr->ifa_addr == NULL)
            goto fail;
        (void) memcpy(curr->ifa_addr, &lifrp->lifr_addr,
            sizeof (struct sockaddr_storage));

        /* Get the netmask */
        if (ioctl(s, SIOCGLIFNETMASK, (caddr_t)&lifrl) < 0)
            goto fail;
        curr->ifa_netmask = malloc(sizeof (struct sockaddr_storage));
        if (curr->ifa_netmask == NULL)
            goto fail;
        (void) memcpy(curr->ifa_netmask, &lifrl.lifr_addr,
            sizeof (struct sockaddr_storage));

        /* Get the destination for a pt-pt interface */
        if (curr->ifa_flags & IFF_POINTOPOINT) {
            if (ioctl(s, SIOCGLIFDSTADDR, (caddr_t)&lifrl) < 0)
                goto fail;
            curr->ifa_dstaddr = malloc(
                sizeof (struct sockaddr_storage));
            if (curr->ifa_dstaddr == NULL)
                goto fail;
            (void) memcpy(curr->ifa_dstaddr, &lifrl.lifr_addr,
                sizeof (struct sockaddr_storage));
        } else if (curr->ifa_flags & IFF_BROADCAST) {
            if (ioctl(s, SIOCGLIFBRDADDR, (caddr_t)&lifrl) < 0)
                goto fail;
            curr->ifa_broadaddr = malloc(
                sizeof (struct sockaddr_storage));
            if (curr->ifa_broadaddr == NULL)
                goto fail;
            (void) memcpy(curr->ifa_broadaddr, &lifrl.lifr_addr,
                sizeof (struct sockaddr_storage));
        }

    }
    free(buf);
    close(sock4);
    close(sock6);
    return (0);
fail:
    err = errno;
    free(buf);
    solaris_freeifaddrs(*ifap);
    *ifap = NULL;
    if (err == ENXIO)
        goto retry;
    close(sock4);
    close(sock6);
    errno = err;
    return (-1);
}

/*
 * Do a SIOCGLIFCONF and store all the interfaces in `buf'.
 */
int
getallifs(int s, sa_family_t af, struct lifreq **lifr, int *numifs,
    int64_t lifc_flags)
{
    struct lifnum lifn;
    struct lifconf lifc;
    size_t bufsize;
    char *tmp;
    caddr_t *buf = (caddr_t *)lifr;

    lifn.lifn_family = af;
    lifn.lifn_flags = lifc_flags;

    *buf = NULL;
retry:
    if (ioctl(s, SIOCGLIFNUM, &lifn) < 0)
        goto fail;

    /*
     * When calculating the buffer size needed, add a small number
     * of interfaces to those we counted.  We do this to capture
     * the interface status of potential interfaces which may have
     * been plumbed between the SIOCGLIFNUM and the SIOCGLIFCONF.
     */
    bufsize = (lifn.lifn_count + 4) * sizeof (struct lifreq);

    if ((tmp = realloc(*buf, bufsize)) == NULL)
        goto fail;

    *buf = tmp;
    lifc.lifc_family = af;
    lifc.lifc_flags = lifc_flags;
    lifc.lifc_len = bufsize;
    lifc.lifc_buf = *buf;
    if (ioctl(s, SIOCGLIFCONF, (char *)&lifc) < 0)
        goto fail;

    *numifs = lifc.lifc_len / sizeof (struct lifreq);
    if (*numifs >= (lifn.lifn_count + 4)) {
        /*
         * If every entry was filled, there are probably
         * more interfaces than (lifn.lifn_count + 4).
         * Redo the ioctls SIOCGLIFNUM and SIOCGLIFCONF to
         * get all the interfaces.
         */
        goto retry;
    }
    return (0);
fail:
    free(*buf);
    *buf = NULL;
    return (-1);
}
