/*
 * selfaddresses() routines handle recognition of our own IP addresses,
 * so that we won't talk with ourself from smtp sender, and recognize,
 * when the SMTP target is ourself, even when using alternate IP
 * addresses that are not matched with our own name.
 *
 *
 * We try at first to get the current setup via  SIOCGIFCONF  ioctl, and
 * if it yields nothing, we try other method:  We rely on the system
 * configurer to do the right thing, and list them at the ZENV file
 * SELFADDRESSES= -entry  as a string of style:
 *
 *    "[1.2.3.4],[6.7.8.9],[IPv6:::ffff:1.2.3.4],my.domain.name"
 *              ^---------^---------------------^---- commas to separate them!
 *
 */

/*  loadifaddresses() -- for ZMailer

    A piece of code from  sendmail-8.7.1:src/conf.c
    with serious mutations...  We want a list of ADDRESSES,
    not hostnames per se...  Also unlike sendmail, we keep
    redoing this query every so often -- in fact for EVERY
    smtp connection open!

    Original copyright SunSoft/Berkeley/Almann, modifications
    by Matti Aarnio <mea@nic.funet.fi> 1997
*/

#include "hostenv.h"
#include <sys/types.h>
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#if (defined(__svr4__) || defined(__SVR4)) && defined(__sun)
# define BSD_COMP /* Damn Solaris, and its tricks... */
#endif
#include <sys/ioctl.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifndef EAI_AGAIN
# include "netdb6.h" /* IPv6 API stuff */
#endif
#include <netinet/in.h>
#ifdef HAVE_NETINET_IN6_H
# include <netinet/in6.h>
#endif
#ifdef HAVE_NETINET6_IN6_H
# include <netinet6/in6.h>
#endif
#ifdef HAVE_LINUX_IN6_H
# include <linux/in6.h>
#endif
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>

#include "libc.h"
#include "zmalloc.h"
#include "libz.h"

/*
**  LOADIFADDRESSES -- load interface-specific addresses
*/

/* autoconfig test script!

AC_CACHE_CHECK([for 'sa_len' in 'struct sockaddr'], ac_cv_struct_sa_len,
[AC_TRY_COMPILE([#include <sys/types.h>
#include <sys/socket.h>], [struct sockaddr sa; sa.sa_len = 0; ],
	ac_cv_struct_sa_len=yes, ac_cv_struct_sa_len=no)])
if test "$ac_cv_struct_sa_len" = yes; then
  AC_DEFINE(HAVE_SA_LEN)
fi

*/

#include "hostenv.h"

struct rtentry; /* Dummies for BSD systems */
struct mbuf;
#include <arpa/inet.h>
#include <net/if.h>

/* #include "l-if.h" --- just some fake test stuff for SIOCGLIF*** */

#ifdef HAVE_IFADDRS_H
#include <ifaddrs.h>
#endif

int
loadifaddresses(sockaddrp)
     Usockaddr ***sockaddrp;
{
	int i;
	int sapcount = -1;
	int sapspace = 2;
	Usockaddr **sap;

	sap = (void*)malloc(sizeof(Usockaddr*) * (sapspace + 2));

	if (! sap)
	  return -3; /* UAARGH! */

#ifdef HAVE_GETIFADDRS
	{
	  struct ifaddrs *ifar = NULL, *ifa;

	  i = getifaddrs( &ifar );
	  if (i < 0) {
	    free(sap);
	    return i;
	  }

	  for (i = 0, ifa = ifar; ifa ; ++i, ifa = ifa->ifa_next) {
	    if ((ifa->ifa_flags & IFF_UP) &&
		(ifa->ifa_addr != NULL)) {

	      struct sockaddr *sa = ifa->ifa_addr;

	      if (sapcount +2 >= sapspace) {
		sapspace <<= 1;
		sap = (void*)realloc(sap, (sizeof(Usockaddr*) *
					   (sapspace + 2)));
	      }

	      if (! sap) {
		return -5; /* UAARGH! */
	      }

	      if (sa->sa_family == AF_INET) {
		struct sockaddr_in *si4 = (void*)malloc(sizeof(*si4));
		if (si4 == NULL)
		  break;

		/* pick the whole sockaddr package! */
		memcpy(si4, sa, sizeof(*si4));
		sap[++sapcount] = (Usockaddr *)si4;
	      }

#if defined(AF_INET6) && defined(INET6)
	      if (sa->sa_family == AF_INET6) {
		struct sockaddr_in6 *si6 = (void*)malloc(sizeof(*si6));
		if (si6 == NULL)
		  break;

		/* pick the whole sockaddr package! */
		memcpy(si6, sa, sizeof(*si6));
		sap[++sapcount] = (Usockaddr *)si6;
	      }
#endif
	    }
	  }


#ifdef HAVE_FREEIFADDRS
	  freeifaddrs(ifar);
#else
	  free(ifar);
#endif
	}
#else /* not HAVE_GETIFADDRS */

#if defined(SIOCGLIFCONF) && defined(AF_INET6) && defined(INET6)
	/*#warning "experimental INET6 related SIOCGLIFCONF code activated!"*/
	{
	  struct lifconf lifc;
	  struct ifconf ifc;
	  int ifbufsize = 4 * sizeof(struct lifreq) + 4;
	  char *interfacebuf = NULL;
	  int s;

	  s = socket(PF_INET6, SOCK_DGRAM, 0);
	  if (s < 0)
	    goto done_this_ipv6;


	  /* Some utilities seem to do this probing also with sockets of
	     AF_X25, AF_IPX, AF_AX25, AF_INET6, etc. -address families,
	     but on Linux (very least) it can be done with any of them,
	     thus we use the one that is most likely available: AF_INET */

	  for (;;) {

	    /* get the list of known IP address from the kernel */
	    ifbufsize <<= 1;
	    interfacebuf = (void*)realloc(interfacebuf,ifbufsize);
	    memset(&lifc, 0, sizeof(lifc));
	    lifc.lifc_buf    = interfacebuf;
	    lifc.lifc_len    = ifbufsize;
	    lifc.lifc_family = AF_UNSPEC;
	    lifc.lifc_flags  = 0;
	    if (ioctl(s, SIOCGLIFCONF, (char *)&lifc) < 0) {
	      if (errno == EINTR)
		continue;
	      if (errno == EINVAL)
		continue;
	      goto done_this_ipv6; /* HUH!! ??? */
	    }

	    if (lifc.lifc_len < (ifbufsize - 2*sizeof(struct lifreq)))
	      break;

	    /* Redo the query, perhaps didn't get them all.. */
	  }

	  /* Likely got them all.. */

	  /* Count how many addresses listed */

	  for (i = 0; i < lifc.lifc_len; ) {

	    struct lifreq *lifr = (struct lifreq *) &lifc.lifc_buf[i];
	    Usockaddr *sa = (Usockaddr *) &lifr->lifr_addr;
#ifdef SIOCGLIFFLAGS
	    struct lifreq lifrf;
#endif

#if defined(SA_LEN)
	    if (SA_LEN(((struct sockaddr *)sa)) > sizeof(lifr->lifr_addr))
	      i += sizeof lifr->lifr_name + SA_LEN(((struct sockaddr *)sa));
	    else
#elif defined(HAVE_SA_LEN)
	      if (sa->sa.sa_len > sizeof lifr->lifr_addr)
		i += sizeof lifr->lifr_name + sa->sa.sa_len;
	      else
#endif
		i += sizeof *lifr;

	    /* Known address families ?
	       The one we scanned for ??*/

	    if ( sa->sa.sa_family != AF_INET &&
		 sa->sa.sa_family != AF_INET6 )
		/* Skip if not either of used protocols.. */
		continue;

	    /* Now, what do the flags say ? Are they alive ? */

#ifdef SIOCGLIFFLAGS

	    memset(&lifrf, 0, sizeof(struct lifreq));
	    /* lifrf.lifr_index = 0;
	       -- Solaris has _index, Linux has _ifindex */
	    strncpy(lifrf.lifr_name, lifr->lifr_name, sizeof(lifrf.lifr_name));

	    if (ioctl(s, SIOCGLIFFLAGS, (char *) &lifrf) < 0)
	      continue; /* Failed.. */

#if 0
	    printf("name='%s'  lifrf_flags=0x%x\n",
		   lifr->lifr_name,lifrf.lifr_flags);
#endif
	    if (!(IFF_UP & lifrf.lifr_flags))
	      continue;
#else

	    /* printf("lifr_flags=0x%x\n",lifr->lifr_flags); */

	    if (!(IFF_UP & lifr->lifr_flags))
	      continue;
#endif

	    if (sapcount +2 >= sapspace) {
	      sapspace <<= 1;
	      sap = (void*)realloc(sap, (sizeof(Usockaddr*) *
					 (sapspace + 2)));
	    }

	    if (! sap) {
	      close(s);
	      if (interfacebuf) free(interfacebuf);
	      return -5; /* UAARGH! */
	    }

	    if (sa->sa.sa_family == AF_INET) {
	      struct sockaddr_in *si4 = (void*)malloc(sizeof(*si4));
	      if (si4 == NULL)
		break;

	      /* pick the whole sockaddr package! */
	      memcpy(si4, sa, sizeof(*si4));
	      sap[++sapcount] = (Usockaddr *)si4;
	    } else if (sa->sa.sa_family == AF_INET6) {
	      struct sockaddr_in6 *si6 = (void*)malloc(sizeof(*si6));
	      if (si6 == NULL)
		break;

	      /* pick the whole sockaddr package! */
	      memcpy(si6, sa, sizeof(*si6));
	      sap[++sapcount] = (Usockaddr *)si6;
	    }
	  }

done_this_ipv6:

	  if (s >= 0) close(s);
	  if (interfacebuf) free(interfacebuf);

	}
#else /* SIOCGLIFCONF && IPv6 */

#ifdef SIOCGIFCONF
	{
	  struct ifconf ifc;
	  int ifbufsize = 4 * sizeof(struct ifreq) + 4;
	  char *interfacebuf = NULL;
	  int s;

	  s = socket(PF_INET, SOCK_DGRAM, 0);

	  if (s < 0) {
	    free(sap);
	    return -1;
	  }

	  /* Redo the buffer size increase until we get response size to
	     be something of LESS THAN the buffer size minus two-times
	     the sizeof(struct ifreq) -- because then we don't have
	     a potential case of having larger block of addresses in
	     system, but us being unable to get them all..
	     Usually system has TWO interfaces -- loopback, and the LAN,
	     thus the following loop is executed exactly once! */

	  /* Some utilities seem to do this probing also with sockets of
	     AF_X25, AF_IPX, AF_AX25, AF_INET6, etc. -address families,
	     but on Linux (very least) it can be done with any of them,
	     thus we use the one that is most likely available: AF_INET */

	  for (;;) {

	    /* get the list of known IP address from the kernel */
	    ifbufsize <<= 1;
	    interfacebuf = (void*)realloc(interfacebuf,ifbufsize);
	    memset(&ifc, 0, sizeof(ifc));
	    ifc.ifc_buf = interfacebuf;
	    ifc.ifc_len = ifbufsize;
	    if (ioctl(s, SIOCGIFCONF, (char *)&ifc) < 0)
	      if (errno == EINVAL)
		continue;

	    if (ifc.ifc_len < (ifbufsize - 2*sizeof(struct ifreq)))
	      break;

	    /* Redo the query, perhaps didn't get them all.. */
	  }


	  /* Count how many addresses listed */

	  for (i = 0; i < ifc.ifc_len; ) {

	    struct ifreq *ifr = (struct ifreq *) &ifc.ifc_buf[i];
	    Usockaddr *sa = (Usockaddr *) &ifr->ifr_addr;
#ifdef SIOCGIFFLAGS
	    struct ifreq ifrf;
#endif

#if defined(SA_LEN)
	    if (SA_LEN(((struct sockaddr*)sa)) > sizeof ifr->ifr_addr)
	      i += sizeof ifr->ifr_name + SA_LEN(((struct sockaddr *)sa));
	    else
#elif defined(HAVE_SA_LEN)
	      if (sa->sa.sa_len > sizeof ifr->ifr_addr)
		i += sizeof ifr->ifr_name + sa->sa.sa_len;
	      else
#endif
		i += sizeof *ifr;

	    /* Known address families ?
	       The one we scanned for ??*/

	    if (ifr->ifr_addr.sa_family != PF_INET)
	      continue;

	    /* Now, what do the flags say ? Are they alive ? */

#ifdef SIOCGIFFLAGS

	    memset(&ifrf, 0, sizeof(struct ifreq));
	    /* ifrf.ifr_index = 0;
	       -- Solaris has _index, Linux has _ifindex */
	    strncpy(ifrf.ifr_name, ifr->ifr_name, sizeof(ifrf.ifr_name));

	    if (ioctl(s, SIOCGIFFLAGS, (char *) &ifrf) < 0)
	      continue; /* Failed.. */

#if 0
	    printf("name='%s'  ifrf_flags=0x%x\n",
		   ifr->ifr_name,ifrf.ifr_flags);
#endif
	    if (!(IFF_UP & ifrf.ifr_flags))
	      continue;
#else

	    /* printf("ifr_flags=0x%x\n",ifr->ifr_flags); */

	    if (!(IFF_UP & ifr->ifr_flags))
	      continue;
#endif

	    if (sapcount +2 >= sapspace) {
	      sapspace <<= 1;
	      sap = (void*)realloc(sap, (sizeof(Usockaddr*) *
					 (sapspace + 2)));
	    }
	    if (! sap) {
	      close(s);
	      if (interfacebuf) free(interfacebuf);
	      return -4; /* UAARGH! */
	    }

	    if (sa->v4.sin_family == AF_INET) {
	      struct sockaddr_in *si4 = (void*)malloc(sizeof(*si4));
	      if (si4 == NULL)
		break;
	      /* pick the whole sockaddr package! */
	      memcpy(si4, &ifr->ifr_addr, sizeof(*si4));
	      sap[++sapcount] = (Usockaddr *)si4;
	    }
	  }
	  close(s);
	  if (interfacebuf) free(interfacebuf);
	}
#else

#warning "NO SELF-ADDRESS EXTRACTION CODE AVAILABLE !?"

#endif /* defined(SIOCGIFCONF) */
#endif /* SIOCGLIFCONF / SIOCGIFCONF */

#endif /* not HAVE_GETIFADDRS */

	  *sockaddrp = sap;

	  sap[++sapcount] = NULL;

	  return sapcount;
	}

#ifndef TESTMODE /* We test ONLY of  loadifaddresses() routine! */

static             int    nmyaddrs = 0;
static Usockaddr  ** myaddrs = NULL;

static void stashmyaddress __((const char *));
static void
stashmyaddress(host)
	const char *host;
{
	int naddrs;
	struct hostent *hp, hent;
	int addrsiz, af, rc;
	void *addrs[2];
	Usockaddr au;

	if (host == NULL || *host == 0) return;

	hp = NULL;
	if (*host != '[')
	  hp = gethostbyname(host);

	if (hp == NULL) { /* No such host ?? */

#ifndef INADDRSZ
#define INADDRSZ 4
#endif
#ifndef IN6ADDRSZ
#define IN6ADDRSZ 16
#endif

#if defined(AF_INET6) && defined(INET6)
	  if (strncasecmp(host,"[IPv6:",6)==0) {
	    af = AF_INET6;
	    addrsiz = IN6ADDRSZ;
	    rc = inet_pton(af, host+6, &au.v6);
	  } else
#endif
	    if (*host == '[') {
	      af = AF_INET;
	      addrsiz = INADDRSZ;
	      rc = inet_pton(af, host+1, &au.v4);
	    } else
	      return;

	  if (rc <= 0)
	    return; /* Umm.. Failed ? */


	  hp = &hent;
	  /* don't really care about gethostbyaddr() here */
	  hp->h_name     = (char*)host;
	  hp->h_addrtype = af;
	  hp->h_aliases  = NULL;
	  hp->h_length   = addrsiz;
	  addrs[0] = (void *)&au;
	  addrs[1] = NULL;
	  hp_setalist(hp, addrs);
	  naddrs = 1;
	} else {
	  naddrs = 0;
	  for (hp_init(hp); *hp_getaddr() != NULL; hp_nextaddr())
	    ++naddrs;
	  if (hp->h_addrtype == AF_INET)
	    addrsiz = sizeof(struct sockaddr_in);
#if defined(AF_INET6) && defined(INET6)
	  else if (hp->h_addrtype == AF_INET6)
	    addrsiz = sizeof(struct sockaddr_in6);
#endif
	  else
	    addrsiz = -1;
	}
	/* malloc(size) == realloc(NULL, size) */
	myaddrs = (void*)realloc((void*)myaddrs,
				 (nmyaddrs + naddrs +1) *
				 sizeof(Usockaddr*));

	if (!myaddrs) return; /* Uurgh.... */

	for (hp_init(hp); *hp_getaddr() != NULL; hp_nextaddr()) {
	  if (hp->h_addrtype == AF_INET) {
	    Usockaddr *si;
	    si = (void*)malloc(sizeof(*si));
	    if (!si) return;
	    memset(si,0,sizeof(*si));

	    myaddrs[nmyaddrs++] = si;
	    si->v4.sin_family = AF_INET;
	    memcpy(&si->v4.sin_addr.s_addr, *hp_getaddr(), hp->h_length);
	  }
#if defined(AF_INET6) && defined(INET6)
	  if (hp->h_addrtype == AF_INET6) {
	    Usockaddr *si6;
	    si6 = (void*)malloc(sizeof(*si6));
	    if (!si6) return;
	    memset(si6,0,sizeof(*si6));

	    myaddrs[nmyaddrs++] = si6;
	    si6->v6.sin6_family = AF_INET6;
	    memcpy(&si6->v6.sin6_addr.s6_addr, *hp_getaddr(), hp->h_length);
	  }
#endif
	}
	myaddrs[nmyaddrs] = NULL;
}

void
stashmyaddresses(host)
const char *host;
{
	const char *s1, *s2, *zenv;
	Usockaddr **sa;
	int sacnt;

	/* Clear them all away */
	if (myaddrs != NULL) {
	  int i;
	  for (i = 0; i < nmyaddrs; ++i)
	    if (myaddrs[i] != NULL)
	      free(myaddrs[i]);
	  free(myaddrs);
	  myaddrs = NULL;
	  nmyaddrs = 0;
	}

	/* Now start fillig -- interface addresses, if you can get them */

	sacnt = loadifaddresses( &sa );

	if (sacnt > 0) {
	  /* Okay, we GOT some addresses, I bet we got them all!
	     (All interfaces that we currently have active!) */
	  myaddrs = sa;
	  nmyaddrs = sacnt;

	}

	/* Independent of getting (or not) of interface data, stash in
	   also whatever we can get from our environment.  Thus was
	   we can block CLUSTER input addresses, for example. */

	zenv = getzenv("SELFADDRESSES");

	if (host && *host)
	  stashmyaddress(host);

	s1 = zenv;
	while (s1 && *s1) {
	  s2 = strchr(s1,',');
	  if (s2) *(char *)s2 = 0; /* Urgh... mumble mumble... */
	  stashmyaddress(s1);
	  if (s2) *(char *)s2 = ',';
	  if (s2)
	    s1 = s2+1;
	  else
	    s1 = NULL;
	}
}


/* Here we compare only the address portion, not ports, nor anything else! */


int
matchmyaddress(_sa)
	Usockaddr *_sa;
{
	int i;
	Usockaddr *sau = _sa;

	if (!myaddrs)
		stashmyaddresses(NULL);
	if (!myaddrs) return 0; /* Don't know my addresses ! */
	
	/* Match loopback net.. */
	if (sau->v4.sin_family == AF_INET) {
	  int net;

	  net = (ntohl(sau->v4.sin_addr.s_addr) >> 24) & 0xFF;
	  if (net == 127)
	    return 2; /* Loopback network */
	  if (net == 0 || net > 223)
	    return 3;
	}

	/* ... and then the normal thing -- listed interfaces */

	for (i = 0; i < nmyaddrs; ++i) {
	  /* if this is myself, skip to next MX host */
	  if (sau->v4.sin_family == myaddrs[i]->v4.sin_family) {
	    if (sau->v4.sin_family == AF_INET &&
		memcmp(&sau->v4.sin_addr, &myaddrs[i]->v4.sin_addr, 4) == 0)
	      return 1;
#if defined(AF_INET6) && defined(INET6)
	    if (sau->v4.sin_family == AF_INET6 &&
		memcmp(&sau->v6.sin6_addr, &myaddrs[i]->v6.sin6_addr, 16) == 0)
	      return 1;
#endif
	  }
	}
	return 0;
}

int
matchmyaddresses(ai)
	struct addrinfo *ai;
{
	int i;
	for ( ; ai ; ai = ai->ai_next ) {
	  i = 0;
	  if (ai->ai_addr)
	    i = matchmyaddress((Usockaddr*)ai->ai_addr);
	  if (i) return i;
	}
	return 0;
}
#endif /* TESTMODE */

#ifdef TESTMODE

const char *progname = "selfaddrstest";

int main(argc,argv)
int argc;
char *argv[];
{
  int cnt, i;
  struct sockaddr **sa;
  char buf[80];

  cnt = loadifaddresses(&sa);

  printf("loadifaddresses rc=%d\n", cnt);

  for (i = 0; i < cnt; ++i) {
    switch(sa[i]->sa_family) {
    case AF_INET:
      inet_ntop(AF_INET, &((struct sockaddr_in **) sa)[i]->sin_addr, buf, sizeof(buf));
      printf("IPv4: [%s]\n", buf);
      break;
#if defined(AF_INET6) && defined(INET6)
    case AF_INET6:
      inet_ntop(AF_INET6, &((struct sockaddr_in6 **) sa)[i]->sin6_addr, buf, sizeof(buf));
      printf("IPv6: [IPv6:%s]\n",buf);
      break;
#endif
    default:
      printf("Unknown socket address family: %d\n", sa[i]->sa_family);
      break;
    }
  }

  free(sa);

  return 0;
}
#endif /* TESTMODE */


syntax highlighted by Code2HTML, v. 0.9.1