/*++
/* NAME
/*	inet_addr_local 3
/* SUMMARY
/*	determine if IP address is local
/* SYNOPSIS
/*	#include <inet_addr_local.h>
/*
/*	int	inet_addr_local(addr_list, mask_list)
/*	INET_ADDR_LIST *addr_list;
/*	INET_ADDR_LIST *mask_list;
/* DESCRIPTION
/*	inet_addr_local() determines all active IP interface addresses
/*	of the local system. Any address found is appended to the
/*	specified address list. The result value is the number of
/*	active interfaces found.
/*
/*	The mask_list is either a null pointer, or it is a list that
/*	receives the netmasks of the interface addresses that were found.
/* DIAGNOSTICS
/*	Fatal errors: out of memory.
/* SEE ALSO
/*	inet_addr_list(3) address list management
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*--*/

/* System library. */

#include <sys_defs.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <unistd.h>
#ifdef USE_SYS_SOCKIO_H
#include <sys/sockio.h>
#endif
#include <errno.h>
#include <string.h>

/* Utility library. */

#include <msg.h>
#include <mymalloc.h>
#include <vstring.h>
#include <inet_addr_list.h>
#include <inet_addr_local.h>

 /*
  * Support for variable-length addresses.
  */
#ifdef _SIZEOF_ADDR_IFREQ
#define NEXT_INTERFACE(ifr) ((struct ifreq *) \
	((char *) ifr + _SIZEOF_ADDR_IFREQ(*ifr)))
#define IFREQ_SIZE(ifr)	_SIZEOF_ADDR_IFREQ(*ifr)
#else
#ifdef HAS_SA_LEN
#define NEXT_INTERFACE(ifr) ((struct ifreq *) \
	((char *) ifr + sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len))
#define IFREQ_SIZE(ifr)	(sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len)
#else
#define NEXT_INTERFACE(ifr) (ifr + 1)
#define IFREQ_SIZE(ifr)	sizeof(ifr[0])
#endif
#endif

/* inet_addr_local - find all IP addresses for this host */

int     inet_addr_local(INET_ADDR_LIST *addr_list, INET_ADDR_LIST *mask_list)
{
    char   *myname = "inet_addr_local";
    struct ifconf ifc;
    struct ifreq *ifr;
    struct ifreq *the_end;
    int     sock;
    VSTRING *buf = vstring_alloc(1024);
    int     initial_count = addr_list->used;
    struct in_addr addr;
    struct ifreq *ifr_mask;

    if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
	msg_fatal("%s: socket: %m", myname);

    /*
     * Get the network interface list. XXX The socket API appears to have no
     * function that returns the number of network interfaces, so we have to
     * guess how much space is needed to store the result.
     * 
     * On BSD-derived systems, ioctl SIOCGIFCONF returns as much information as
     * possible, leaving it up to the application to repeat the request with
     * a larger buffer if the result caused a tight fit.
     * 
     * Other systems, such as Solaris 2.5, generate an EINVAL error when the
     * buffer is too small for the entire result. Workaround: ignore EINVAL
     * errors and repeat the request with a larger buffer. The downside is
     * that the program can run out of memory due to a non-memory problem,
     * making it more difficult than necessary to diagnose the real problem.
     */
    for (;;) {
	ifc.ifc_len = vstring_avail(buf);
	ifc.ifc_buf = vstring_str(buf);
	if (ioctl(sock, SIOCGIFCONF, (char *) &ifc) < 0) {
	    if (errno != EINVAL)
		msg_fatal("%s: ioctl SIOCGIFCONF: %m", myname);
	} else if (ifc.ifc_len < vstring_avail(buf) / 2)
	    break;
	VSTRING_SPACE(buf, vstring_avail(buf) * 2);
    }

    /*
     * Get the address of each IP network interface. According to BIND we
     * must include interfaces that are down because the machine may still
     * receive packets for that address (yes, via some other interface).
     * Having no way to verify this claim on every machine, I will give them
     * the benefit of the doubt.
     */
    the_end = (struct ifreq *) (ifc.ifc_buf + ifc.ifc_len);
    for (ifr = ifc.ifc_req; ifr < the_end;) {
	if (ifr->ifr_addr.sa_family == AF_INET) {	/* IP interface */
	    addr = ((struct sockaddr_in *) & ifr->ifr_addr)->sin_addr;
	    if (addr.s_addr != INADDR_ANY) {	/* has IP address */
		inet_addr_list_append(addr_list, &addr);
		if (mask_list) {
		    ifr_mask = (struct ifreq *) mymalloc(IFREQ_SIZE(ifr));
		    memcpy((char *) ifr_mask, (char *) ifr, IFREQ_SIZE(ifr));
		    if (ioctl(sock, SIOCGIFNETMASK, ifr_mask) < 0)
			msg_fatal("%s: ioctl SIOCGIFNETMASK: %m", myname);
		    addr = ((struct sockaddr_in *) & ifr_mask->ifr_addr)->sin_addr;
		    inet_addr_list_append(mask_list, &addr);
		    myfree((char *) ifr_mask);
		}
	    }
	}
	ifr = NEXT_INTERFACE(ifr);
    }
    vstring_free(buf);
    (void) close(sock);
    return (addr_list->used - initial_count);
}

#ifdef TEST

#include <vstream.h>
#include <msg_vstream.h>

int     main(int unused_argc, char **argv)
{
    INET_ADDR_LIST addr_list;
    INET_ADDR_LIST mask_list;
    int     i;

    msg_vstream_init(argv[0], VSTREAM_ERR);

    inet_addr_list_init(&addr_list);
    inet_addr_list_init(&mask_list);
    inet_addr_local(&addr_list, &mask_list);

    if (addr_list.used == 0)
	msg_fatal("cannot find any active network interfaces");

    if (addr_list.used == 1)
	msg_warn("found only one active network interface");

    for (i = 0; i < addr_list.used; i++) {
	vstream_printf("%s/", inet_ntoa(addr_list.addrs[i]));
	vstream_printf("%s\n", inet_ntoa(mask_list.addrs[i]));
    }
    vstream_fflush(VSTREAM_OUT);
    inet_addr_list_free(&addr_list);
    inet_addr_list_free(&mask_list);
}

#endif
