/*
 * iflist.c - figure out interface information
 * Copyright (C) 2000 Rex Feany <laeos@laeos.net>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/if.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <errno.h>

#ifdef HAVE_ASSERT_H
# include <assert.h>
#endif

#include "irc.h"
#include "ircaux.h"
#include "iflist.h"
#include "threads.h"
#include "gai.h"

/* List of my interfaces */
static struct iflist *iflist;

/* struct for passing information between threads */
struct thr_comm {
    iface_cb thr_ifcb;
    void *thr_data;
};

/* These were written/stolen from W Richard Steven's "Unix Network Programming" */
static struct iflist *iflist_get(void)
{
    struct iflist *ifi, *ifihead, **ifipnext;
    int sockfd, len, lastlen;
    char *ptr, *buf;
    struct ifconf ifc;
    struct ifreq *ifr, ifrcopy;

    errno = 0;
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
	return NULL;
    }

    lastlen = 0;
    len = 100 * sizeof(struct ifreq);	/* initial buffer size guess */
    for (;;) {
	buf = new_malloc(len);
	ifc.ifc_len = len;
	ifc.ifc_buf = buf;
	if (ioctl(sockfd, SIOCGIFCONF, &ifc) < 0) {
	    if (errno != EINVAL || lastlen != 0)
		return NULL;
	} else {
	    if (ifc.ifc_len == lastlen)
		break;		/* success, len has not changed */
	    lastlen = ifc.ifc_len;
	}
	len += 10 * sizeof(struct ifreq);	/* increment */
	new_free(&buf);
    }
    ifihead = NULL;
    ifipnext = &ifihead;

    for (ptr = buf; ptr < buf + ifc.ifc_len;) {
	ifr = (struct ifreq *) ptr;

#ifdef	HAVE_SOCKADDR_SA_LEN
	len = MAX(sizeof(struct sockaddr), ifr->ifr_addr.sa_len);
#else
	switch (ifr->ifr_addr.sa_family) {
#ifdef	IPV6
	case AF_INET6:
	    len = sizeof(struct sockaddr_in6);
	    break;
#endif
	case AF_INET:
	default:
	    len = sizeof(struct sockaddr);
	    break;
	}
#endif				/* HAVE_SOCKADDR_SA_LEN */
	ptr += sizeof(ifr->ifr_name) + len;	/* for next one in buffer */

#ifdef	AF_LINK
	/* we don't care about link level things. just ip */
	if (AF_LINK == ifr->ifr_addr.sa_family)
	    continue;
#endif
	ifrcopy = *ifr;

	if (ioctl(sockfd, SIOCGIFFLAGS, &ifrcopy) < 0) {
	    /* just ignore this error, and this interface ... */
	    continue;
	}

	if ((ifrcopy.ifr_flags & IFF_UP) == 0)
	    continue;		/* ignore if interface not up */
	if (ifrcopy.ifr_flags & IFF_LOOPBACK)
	    continue;		/* ignore if ... */

	ifi = new_malloc(sizeof(struct iflist));
	*ifipnext = ifi;	/* prev points to this new one */
	ifipnext = &ifi->ifi_next;	/* pointer to next one goes here */

	memcpy(ifi->ifi_name, ifr->ifr_name, IFI_NAME);;
	ifi->ifi_name[IFI_NAME - 1] = '\0';
	getnameinfo(&ifr->ifr_addr, len, ifi->ifi_host, NI_MAXHOST, NULL, 0, NI_NUMERICSERV);

	ifi->ifi_len = len;
	ifi->ifi_addr = new_malloc(len);
	memcpy(ifi->ifi_addr, &ifr->ifr_addr, len);
    }
    new_free(&buf);
    return (ifihead);		/* pointer to first structure in linked list */
}

/* free list of interfaces */
static void iflist_free(struct iflist *ifihead)
{
    struct iflist *ifi, *ifinext;

    for (ifi = ifihead; ifi != NULL; ifi = ifinext) {
	if (ifi->ifi_addr != NULL)
	    new_free(&ifi->ifi_addr);
	ifinext = ifi->ifi_next;	/* can't fetch ifi_next after free() */
	new_free(&ifi);		/* the ifi_info{} itself */
    }
}

/* get list of interfaces */
static void *ifaces_r(void *data)
{
    struct thr_comm *th = (struct thr_comm *) data;

    /* this could take a long time (usually not) */
    iflist_update();

    th->thr_ifcb(th->thr_data, iflist);
    new_free(&th);

    THR_EXIT();
}

/**
 * ifaces - fetch a list of interfaces/hostnames
 * @callback: called with the list of interfaces
 * @data: app-specific data also passed to callback
 *
 * A callback function is used here because we may
 * block while looking up hostnames.
 **/
int ifaces(iface_cb callback, void *data)
{
    struct thr_comm *c = new_malloc(sizeof(struct thr_comm));

    assert(callback);

    c->thr_ifcb = callback;
    c->thr_data = data;

    return (int)THR_CREATE(ifaces_r, c);
}

/**
 * iface_find - find an iface struct given a host
 * @host: hostname to look for
 *
 * Each "interface" has hostnames associated with it,
 * we search for the interface structure that is 
 * associated with this hostname. Mainly used to
 * verify that a hostname is actually valid on this machine.
 **/
const struct iflist *iface_find(const char *host)
{
    struct iflist *l;
    struct iflist *m = NULL;
    int hlen = strlen(host);
    int len;

    assert(host);

    len = strlen(host);
    for (l = iflist; l; l = l->ifi_next)
	if (end_strcmp(l->ifi_host, host, len) == 0) {

	    /* best match */
	    if (strlen(l->ifi_host) == hlen)
		return l;

	    /* else take first match */
	    if (m == NULL)
		m = l;
	}
    return m;
}

/**
 * iflist_update - fetch and update the interface list
 *
 * We store a list of interfaces, which we get from the 
 * operating system. It may change over time, this
 * updates that list. Normally this doesn't need to
 * be called.
 **/
const struct iflist *iflist_update(void)
{
    struct iflist *ifl = iflist_get();
    struct iflist *ift = iflist;

    /* we don't lock, but what are the chances..? */
    iflist = ifl;

    if (ift)
	iflist_free(ift);

    return iflist;
}
