/*
 *	Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 *	Copyright 1991-2003 by Matti Aarnio -- modifications,
 *	including MIME things.
 */

#include "smtp.h"

int
getmxrr(SS, host, mx, maxmx, depth, realname, realnamesize, realnamettlp)
	SmtpState *SS;
	const char *host;
	struct mxdata mx[];
	int maxmx, depth;
	char *realname;
	const int realnamesize;
	time_t *realnamettlp;
{
	HEADER *hp;
	msgdata *eom, *cp;
	struct mxdata mxtemp;
	int qlen, n, i, j, nmx, qdcount, ancount, nscount, arcount, maxpref;
	int class;
	long ttl;
	u_short type;
	int saw_cname = 0;
	int had_eai_again = 0;
	querybuf qbuf, answer;
	msgdata buf[8192];
	char mxtype[MAXFORWARDERS];

	h_errno = 0;

#ifndef TEST
	if (maxmx > 2) {
	  notary_setwtt  (NULL);
	  notary_setwttip(NULL);
	}
#endif

	if (depth == 0) {
	  SS->mxcount = 0;
	  sprintf(SS->remotemsg, "-- Lookup of MX/A for '%.200s'", host);
	}

	if (depth > 3) {
	  sprintf(SS->remotemsg,"smtp; 500 (DNS: Recursive CNAME on '%.200s')",host);
	  time(&endtime);
#ifndef TEST
	  notary_setxdelay((int)(endtime-starttime));
	  notaryreport(NULL,FAILED,"5.4.3 (Recursive DNS CNAME)",SS->remotemsg);
#endif
	  fprintf(stderr, "%s\n", SS->remotemsg);
	  return EX_NOHOST;
	}


	qlen = res_mkquery(QUERY, host, C_IN, T_MX, NULL, 0, NULL,
			   (void*)&qbuf, sizeof qbuf);
	if (qlen < 0) {
	  fprintf(stderr, "res_mkquery failed\n");
	  sprintf(SS->remotemsg,
		  "smtp; 466 (Internal: res_mkquery failed on host: %.200s)",host);
	  if (SS->verboselog)
	    fprintf(SS->verboselog,"  %s\n", SS->remotemsg);

	  time(&endtime);
#ifndef TEST
	  notary_setxdelay((int)(endtime-starttime));
	  notaryreport(NULL,FAILED,"5.4.3 (DNS-failure)",SS->remotemsg);
#endif
	  return EX_SOFTWARE;
	}
	n = res_send((void*)&qbuf, qlen, (void*)&answer, sizeof answer);
	if (n < 0) {
	  sprintf(SS->remotemsg,
		  "smtp; 466 (No DNS response for host: %.200s; h_errno=%d)",
		  host, h_errno);
	  if (SS->verboselog)
	    fprintf(SS->verboselog,"  %s\n", SS->remotemsg);

	  time(&endtime);
#ifndef TEST
	  notary_setxdelay((int)(endtime-starttime));
	  notaryreport(NULL,FAILED,"5.4.3 (DNS-failure)",SS->remotemsg);
#endif
	  return EX_DEFERALL;
	}

	time(&now);

	eom = (msgdata *)&answer + n;
	/*
	 * find first satisfactory answer
	 */
	hp = (HEADER *) &answer;
	qdcount = ntohs(hp->qdcount);
	ancount = ntohs(hp->ancount);
	nscount = ntohs(hp->nscount);
	arcount = ntohs(hp->arcount);

	if (SS->verboselog) {
	  fprintf(SS->verboselog, "DNS lookup reply: len=%d rcode=%d qdcount=%d ancount=%d nscount=%d arcount=%d RD=%d TC=%d AA=%d QR=%d RA=%d",
		  n, hp->rcode, qdcount, ancount, nscount, arcount,
		  hp->rd, hp->tc, hp->aa, hp->qr, hp->ra);
#ifdef HAS_HEADER_CD_AD
	  fprintf(SS->verboselog, " CD=%d AD=%d", hp->cd, hp->ad);
#endif
	  fprintf(SS->verboselog, "\n");
	}

	if (maxmx > 2)
	  rmsgappend(SS, 1,
		     "\r-> DNSreply: len=%d rcode=%d qd=%d an=%d ns=%d ar=%d",
		     n, hp->rcode, qdcount, ancount, nscount, arcount);

	if (hp->rcode != NOERROR || ancount == 0) {
	  switch (hp->rcode) {
	  case NXDOMAIN:
	    /* Non-authoritative iff response from cache.
	     * Old BINDs used to return non-auth NXDOMAINs
	     * due to a bug; if that is the case by you,
	     * change to return EX_TEMPFAIL iff hp->aa == 0.
	     */
	    sprintf(SS->remotemsg, "smtp; 500 (DNS: no such domain: %.200s)", host);
	    if (SS->verboselog)
	      fprintf(SS->verboselog," NXDOMAIN %s\n", SS->remotemsg);
	    endtime = now;
#ifndef TEST
	    notary_setxdelay((int)(endtime-starttime));
	    notaryreport(NULL,FAILED,"5.4.4 (DNS lookup report)",SS->remotemsg);
#endif
	    return EX_NOHOST;
	  case SERVFAIL:
	    sprintf(SS->remotemsg, "smtp; 500 (DNS: server failure: %.200s)", host);
	    if (SS->verboselog)
	      fprintf(SS->verboselog," SERVFAIL %s\n", SS->remotemsg);
	    endtime = now;
#ifndef TEST
	    notary_setxdelay((int)(endtime-starttime));
	    notaryreport(NULL,FAILED,"5.4.4 (DNS lookup report)",SS->remotemsg);
#endif
	    return EX_DEFERALL;
	  case NOERROR:
	    mx[0].host = NULL;
	    return EX_OK;
	  case FORMERR:
	  case NOTIMP:
	  case REFUSED:
	    sprintf(SS->remotemsg, "smtp; 500 (DNS: unsupported query: %.200s)", host);
	    if (SS->verboselog)
	      fprintf(SS->verboselog," FORMERR/NOTIMP/REFUSED(%d) %s\n",
		      hp->rcode, SS->remotemsg);
	    endtime = now;
#ifndef TEST
	    notary_setxdelay((int)(endtime-starttime));
	    notaryreport(NULL,FAILED,"5.4.4 (DNS lookup report)",SS->remotemsg);
#endif
	    return EX_NOPERM;
	  }
	  sprintf(SS->remotemsg, "smtp; 500 (DNS: unknown error, MX info unavailable: %.200s)", host);
	  if (SS->verboselog)
	    fprintf(SS->verboselog,"  %s\n", SS->remotemsg);
	  endtime = now;
#ifndef TEST
	  notary_setxdelay((int)(endtime-starttime));
	  notaryreport(NULL,FAILED,"5.4.4 (DNS lookup report)",SS->remotemsg);
#endif

	  if (had_eai_again)
	    return EX_DEFERALL;
	  return EX_UNAVAILABLE;
	}
	nmx = 0;
	cp = (msgdata *)&answer + sizeof(HEADER);
	for (; qdcount > 0; --qdcount) {
#if	defined(BIND_VER) && (BIND_VER >= 473)
	  cp += dn_skipname(cp, eom) + QFIXEDSZ;
#else	/* !defined(BIND_VER) || (BIND_VER < 473) */
	  cp += dn_skip(cp) + QFIXEDSZ;
#endif	/* defined(BIND_VER) && (BIND_VER >= 473) */
	}
	realname[0] = '\0';
	maxpref = 66000;
	while (ancount > 0 && cp < eom && nmx < maxmx-1) {
	  n = dn_expand((msgdata *)&answer, eom, cp, (void*)buf, sizeof buf);
	  if (n < 0)
	    break;
	  cp += n;
	  if (cp+10 > eom) { cp = eom; break; }

	  NS_GET16(type,  cp);
	  NS_GET16(class, cp);
	  NS_GET32(ttl,   cp);	/* TTL */
	  NS_GET16(n, cp);	/* dlen */

	  mx[nmx].expiry = now + ttl;

	  if (cp + n > eom) { cp = eom; break; }

	  if (class != C_IN) {
	    cp += n;
	    if (cp > eom) break;
	    --ancount;
	    continue;
	  }
	  if (type == T_CNAME) {
	    cp += dn_expand((msgdata *)&answer, eom, cp,
			    (void*)realname, realnamesize);
	    if (cp > eom) break;
	    saw_cname = 1;
	    --ancount;
	    if (SS->verboselog)
	      fprintf(SS->verboselog, " -> CNAME: '%s'\n", realname);
	    if (ttl < *realnamettlp) *realnamettlp = ttl;
	    continue;
	  } else if (type != T_MX)  {
	    cp += n;
	    if (cp > eom) break;
	    --ancount;
	    continue;
	  }
	  if (cp + n /* dlen */ > eom) { cp = eom; break; }

	  NS_GET16(mx[nmx].pref, cp);  /* MX preference value */

	  n = dn_expand((msgdata *)&answer, eom, cp, (void*)buf, sizeof buf);
	  if (n < 0) break;
	  cp += n;
	  if (cp > eom) break;

	  mx[nmx].ai   = NULL;
	  mx[nmx].host = (char *)strdup((void*)buf);
	  if (mx[nmx].host == NULL) {
	    fprintf(stderr, "Out of virtual memory!\n");
	    exit(EX_OSERR);
	  }
	  if (SS->verboselog)
	    fprintf(SS->verboselog, " -> (%lds) MX[%d] pref=%d host=%s\n",
		    (long)(mx[nmx].expiry - now), nmx, mx[nmx].pref, buf);
	  if (maxmx > 2)
	    rmsgappend(SS, 1, "\r-> %lds MX[%d] p=%d '%s'",
		       mx[nmx].expiry - now, nmx, mx[nmx].pref, mx[nmx].host);
	  mxtype[nmx] = 0;
	  ++nmx;
	  --ancount;
	} /* Gone thru all answers */

	if (nmx >= maxmx-1 && ancount > 0) {

	  /* If the MAXFORWARDERS count has been exceeded
	     (quite a feat!)  skip over the rest of the
	     answers, as long as we have them, and the
	     reply-buffer has not been exhausted...

	     These are in fact extremely pathological cases of
	     the DNS datasets, and most MTA systems will simply
	     barf at this scale of things far before ZMailer... */

	  if (SS->verboselog)
	    fprintf(SS->verboselog, "  collected MX count matches maximum supported (%d) with still some (%d) answers left to pick, we discard them.\n",
		    maxmx, ancount);

	  while (ancount > 0 && cp < eom) {
#if	defined(BIND_VER) && (BIND_VER >= 473)
	    n = dn_skipname(cp, eom);
#else	/* !defined(BIND_VER) || (BIND_VER < 473) */
	    n = dn_skip(cp);
#endif	/* defined(BIND_VER) && (BIND_VER >= 473) */
	    if (n < 0)
	      break;
	    cp += n;
	    if (cp+10 > eom) { cp = eom; break; }

	    cp += NS_INT16SZ; /* type */
	    cp += NS_INT16SZ; /* class */
	    cp += NS_INT32SZ; /* ttl */
	    NS_GET16(n, cp); /* dlen */

	    cp += n;
	    --ancount;
	  } /* Skipped thru all remaining answers */
	}

	if (ancount > 0) {
	  /* URGH!!!!   Still answers left over, WHAT ?!?!?! */
	  for (i = 0; i < nmx; ++i) {
	    if (mx[i].host) free(mx[i].host);
	    mx[i].host = NULL;
	  }
	  if (hp->tc) {
	    /* Yes, it is TRUNCATED reply!   Must retry with e.g.
	       by using TCP! */
	    /* FIXME: FIXME! FIXME! Truncated reply handling! */
	    if (maxmx > 2)
	      rmsgappend(SS, 1, "\r   TRUNCATED REPLY!");
	  }

	  if (SS->verboselog)
	    fprintf(SS->verboselog,"  left-over ANCOUNT=%d != 0! TC=%d\n",
		    ancount, hp->tc);

	  if (maxmx > 2)
	    rmsgappend(SS, 1, "\r   AnswerCount  %d > 0!!", ancount);

	  return EX_DEFERALL; /* FIXME?? FIXME?? */
	}

	if (nmx == 0 && realname[0] != 0) {
	  /* do it recursively for the real name */
	  n = getmxrr(SS, (char *)realname, mx, maxmx, depth+1,
		      realname, realnamesize, realnamettlp);

	  if (had_eai_again)
	    return EX_DEFERALL;
	  return n;
	} else if (nmx == 0) {
	  /* "give it the benefit of doubt" */
	  mx[0].host = NULL;
	  mx[0].ai   = NULL;
	  SS->mxcount = 0;
	  if (had_eai_again)
	    return EX_DEFERALL;
	  return EX_OK;
	}

	while (nscount > 0 && cp < eom) {
#if	defined(BIND_VER) && (BIND_VER >= 473)
	  n = dn_skipname(cp, eom);
#else	/* !defined(BIND_VER) || (BIND_VER < 473) */
	  n = dn_skip(cp);
#endif	/* defined(BIND_VER) && (BIND_VER >= 473) */
	  if (n < 0)
	    break;
	  cp += n;
	  if (cp+10 > eom) { cp = eom; break; }

	  cp += NS_INT16SZ; /* type  */
	  cp += NS_INT16SZ; /* class */
	  cp += NS_INT32SZ; /* ttl   */
	  NS_GET16(n, cp);  /* dlen  */

	  cp += n; /* We simply skip this data.. */
	  if (cp <= eom)
	    --nscount;
	}


	/* =========================================================== */
#if 0 /* Bloody Linux vs. FreeBSD implementation differences... (addrinfo internal handling) */
#include "getmxrr-removed.txt"

#endif /* Linux vs. FreeBSD implementation difference... */
	/* =========================================================== */

	/* Collect addresses for all those who don't have them from
	   the ADDITIONAL SECTION data */

	for (i = 0; i < nmx; ++i) {

	  struct addrinfo req, *ai, **aip;

	  if (SS->verboselog)
	    fprintf(SS->verboselog, "  mx[%d] mxtype=%s%s(%d) host='%s'\n",
		    i, (mxtype[i]&1)?"4":"-", (mxtype[i]&2)?"6":"-",
		    mxtype[i], mx[i].host);

#if defined(AF_INET6) && defined(INET6)
	  /* If not IPv6 speaker, and already have A, skip it. */
	  if (!use_ipv6 && (mxtype[i] & 1))
	    continue;

	  if (mxtype[i] == 3)
	    continue; /* Have both A and AAAA */
#endif /* INET6 */
	  
	  memset(&req, 0, sizeof(req));
	  req.ai_socktype = SOCK_STREAM;
	  req.ai_protocol = IPPROTO_TCP;
	  req.ai_flags    = AI_CANONNAME;
	  req.ai_family   = PF_INET;
	  ai = NULL;
	  n = 0;

	  if (! (mxtype[i] & 1)) {  /* Not have A */

	    /* This resolves CNAME, it should not happen in case
	       of MX server, though..    */
#ifdef HAVE_GETADDRINFO
	    n = getaddrinfo((const char*)mx[i].host, "0", &req, &ai);
#else
	    n = _getaddrinfo_((const char*)mx[i].host, "0", &req, &ai, SS->verboselog);
#endif /* HAVE_GETADDRINFO */
	    if (SS->verboselog)
	      fprintf(SS->verboselog,"  getaddrinfo('%s','0') (PF_INET) -> r=%d (%s), ai=%p\n",
		      mx[i].host, n, gai_strerror(n), ai);
	    if (n != 0)
	      if (maxmx > 2)
		rmsgappend(SS, 1, "\r-> getaddrinfo(INET, '%s','0') -> r=%d (%s); ai=%p",
			   mx[i].host, n, gai_strerror(n), ai);
#if 0
	    if (n) {
	      zsyslog((LOG_INFO,"getmxrr('%s') mx[%d]='%s' getaddrinfo(INET) rc=%d",
		       host, i, mx[i].host, n));
	    }
#endif /* .. 0 */
	    switch (n) {
	    case 0:
	      break;
	    case EAI_AGAIN:
	      had_eai_again = 1;
	      break;
	    case EAI_MEMORY:
	      exit(EX_OSERR);
	      break;
	    case EAI_NONAME:
	    case EAI_FAIL:
#ifdef EAI_NODATA
	    case EAI_NODATA:
#endif
	    case EAI_SERVICE:
	    default:
	      break;
	    }
	  }

#if defined(AF_INET6) && defined(INET6)
	  if (use_ipv6 && !(mxtype[i] & 2) ) {

	    /* Want, but not have AAAA, ask for it. */

	    int n2;
	    struct addrinfo *ai2 = NULL;

	    memset(&req, 0, sizeof(req));
	    req.ai_socktype = SOCK_STREAM;
	    req.ai_protocol = IPPROTO_TCP;
	    req.ai_flags    = AI_CANONNAME;
	    req.ai_family   = PF_INET6;

	  /* This resolves CNAME, it should not happen in case
	     of MX server, though..    */
#ifdef HAVE_GETADDRINFO
	    n2 = getaddrinfo((const char *)mx[i].host, "0", &req, &ai2);
#else
	    n2 = _getaddrinfo_((const char *)mx[i].host, "0", &req, &ai2,
			       SS->verboselog);
#endif /* HAVE_GETADDRINFO */
	    if (SS->verboselog)
	      fprintf(SS->verboselog,"  getaddrinfo('%s','0') (PF_INET6) -> r=%d (%s), ai=%p\n",
		      mx[i].host, n2, gai_strerror(n2), ai2);

	    if (n2 != 0)
	      if (maxmx > 2)
		rmsgappend(SS, 1, "\r-> getaddrinfo(INET6,'%s') -> r=%d (%s); ai=%p",
			   mx[i].host, n2, gai_strerror(n2), ai2);
#if 0
	    if (n) {
	      zsyslog((LOG_INFO,"getmxrr('%s') mx[%d]='%s' getaddrinfo(INET6) rc=%d",
		       host, i, mx[i].host, n));
	    }
#endif /* .. 0 */
	    switch (n2) {
	    case 0:
	      break;
	    case EAI_AGAIN:
	      had_eai_again = 1;
	      break;
	    case EAI_MEMORY:
	      exit(EX_OSERR);
	      break;
	    case EAI_NONAME:
	    case EAI_FAIL:
#ifdef EAI_NODATA
	    case EAI_NODATA:
#endif
	    case EAI_SERVICE:
	    default:
	      break;
	    }

	    if (n != 0 && n2 == 0) {
	      /* IPv6 address, no IPv4 (or error..) */
	      n = n2;
	      ai = ai2; ai2 = NULL;
	    }
	    if (ai2 && ai) {
	      /* BOTH ?!  Catenate them! */
	      aip = &ai->ai_next;
	      while (*aip) aip = &((*aip)->ai_next);
	      *aip = ai2;
	    }
	  }
#endif /* INET6 */

	  /* Catenate new stuff into the tail of the old ... */
	  aip = &(mx[i].ai);
	  while (*aip) aip = &((*aip)->ai_next);
	  *aip = ai;

	  if (n != 0) {
	    if (n == EAI_AGAIN) {
	      sprintf(SS->remotemsg, "smtp; 500 (DNS: getaddrinfo<%.200s> got EAI_AGAIN)", buf);
	      endtime = now;
#ifndef TEST
	      notary_setxdelay((int)(endtime-starttime));
	      notaryreport(NULL,FAILED,"4.4.4 (DNS lookup report)",SS->remotemsg);
#endif /* .. TEST */

	      had_eai_again = 1;
	    }
	  }
	} /* ... i < nmx ... */


	/* Separate all addresses into their own MXes */

	for (i = 0; i < nmx && nmx < maxmx-1; ++i) {
	  struct addrinfo *ai = mx[i].ai;
	  if (ai) ai = ai->ai_next; /* If more than one.. */
	  while (ai && nmx < maxmx-1) {
	    memcpy(&mx[nmx], &mx[i], sizeof(mx[0]));
	    mx[nmx].ai = ai;
	    ai         = ai->ai_next;
	    mx[nmx].ai->ai_next = NULL;
	    mx[nmx].host = (char *)strdup((const char *)mx[i].host);
	    if (mx[nmx].host == NULL) {
	      fprintf(stderr, "Out of virtual memory!\n");
	      exit(EX_OSERR);
	    }
	    ++nmx;
	  }

	  /* If there was something, it has been split out..
	     Hmm.. except if nmx >= maxmx-1, which is pathological
	     anyway...  100+ addressed server entities.. */

	  if (mx[i].ai) mx[i].ai->ai_next = NULL;
	}

	for (i = 0; i < nmx; ++i) {
	  if (mx[i].ai == NULL && mx[i].host != NULL) {
	    if (maxmx > 2)
	      rmsgappend(SS, 1, "\r-> No addresses for '%s'[%d]", mx[i].host, i);
	    free(mx[i].host);
	    mx[i].host = NULL;
	    continue;
	  }
	  if (CISTREQ(mx[i].host, myhostname) ||
	      (mx[i].ai->ai_canonname &&
	       CISTREQ(mx[i].ai->ai_canonname, myhostname)) ||
	      matchmyaddresses(mx[i].ai) == 1) {

	    if (maxmx > 2)
	      rmsgappend(SS, 1, "\r-> Selfmatch '%s'(%s)[%d]",
			 mx[i].host, ((mx[i].ai->ai_canonname) ?
				      mx[i].ai->ai_canonname : "-"), i);

	    if (SS->verboselog)
	      fprintf(SS->verboselog,"  matchmyaddresses(): matched!  canon='%s', myname='%s'\n", mx[i].ai->ai_canonname ? mx[i].ai->ai_canonname : "<NIL>", myhostname);
	    if (maxpref > (int)mx[i].pref)
	      maxpref = mx[i].pref;
	  }
	} /* ... i < nmx ... */

	SS->mxcount = nmx;

	if (SS->verboselog)
	  fprintf(SS->verboselog,"  getmxrr('%s') -> nmx=%d, maxpref=%d, realname='%s'\n", host, nmx, maxpref, realname);
	if (maxmx > 2)
	  rmsgappend(SS, 1, "\r-> nmx=%d maxpref=%d realname='%s'",
		     nmx, maxpref, realname);

	/* discard MX RRs with a value >= that of  myhost */
	for (n = i = 0; n < nmx; ++n) {
	  if ((int)mx[n].pref >= maxpref && mx[n].host) {
	    free(mx[n].host);
	    freeaddrinfo(mx[n].ai);
	    mx[n].host = NULL;
	    mx[n].ai   = NULL;
	    ++i; /* discard count */
	  }
	}
	if (i /* discard count */ == nmx) {
	  /* All discarded, we are the best MX :-( */
	  mx[0].host = NULL;
	  SS->mxcount = 0;
	  if (had_eai_again)
	    return EX_DEFERALL;
	  return EX_OK;
	}
#ifndef TEST
#ifdef	RFC974
	/* discard MX's that do not support SMTP service */
	if (checkwks)
	  for (n = 0; n < nmx; ++n) {
	    int ttl;
	    if (mx[n].host == NULL)
	      continue;
	    strncpy((char*)buf, (char*)mx[n].host, sizeof(buf));
	    buf[sizeof(buf)-1] = 0;
	    /* It is an MX, it CAN'T have CNAME ! */
	    if (!getrrtype((void*)buf, &ttl, sizeof buf, T_WKS,
			   0, SS->verboselog)) {
	      free(mx[n].host);
	      mx[n].host = NULL;
	      freeaddrinfo(mx[n].ai);
	      mx[n].ai   = NULL;
	    }
	  }
#endif	/* RFC974 */
#endif /* TEST */
	/* determine how many are left */
	for (i = 0, n = 0; i < nmx; ++i) {
	  if (mx[i].host == NULL)
	    continue;
	  if (n < i) {
	    memcpy(&mx[n], &mx[i], sizeof(mx[0]));
	    memset(&mx[i], 0, sizeof(mx[0]));
	  }
	  ++n;			/* found one! */
	}

	nmx = n;
	SS->mxcount = nmx;

	if (n == 0 && had_eai_again)
	  return EX_DEFERALL;

	if (n == 0) {/* MX's exist, but their WKS's show no TCP smtp service */
	  if (maxmx > 2) {
	    rmsgappend(SS, 1, "\r=> NONE of MXes support SMTP!");
	    time(&endtime);
#ifndef TEST
	    notary_setxdelay((int)(endtime-starttime));
	    notaryreport(NULL,FAILED,"5.4.4 (DNS lookup report)",SS->remotemsg);
#endif
	  }
	  return EX_UNAVAILABLE;
	}

	/* sort the records per preferrence value */
	for (i = 0; i < nmx; i++) {
	  for (j = i + 1; j < nmx; j++) {
	    if (mx[i].pref > mx[j].pref) {
	      memcpy(&mxtemp, &mx[i],  sizeof(mxtemp));
	      memcpy(&mx[i],  &mx[j],  sizeof(mxtemp));
	      memcpy(&mx[j],  &mxtemp, sizeof(mxtemp));
	    }
	  }
	}

	/* Randomize the order of those of same preferrence.
	   This will do some sort of load-balancing on large sites
	   which have multiple mail-servers at the same priority.  */
	for (i = 0, maxpref = mx[0].pref; i < nmx; ++i) {
	  /* They are in numerical order, now we can
	     detect when a new preferrence group steps in */
	  j = i;
	  while (j < nmx && maxpref == mx[j].pref) ++j;
	  if ((j-i) > 1) {
	    /* At least two of the same preferrence */
	    int k, len = j-i;
	    for (k = 0; k < len; ++k) {
	      int l = ranny(len-1);
	      memcpy(&mxtemp,  &mx[i+k], sizeof(mxtemp));
	      memcpy(&mx[i+k], &mx[i+l], sizeof(mxtemp));
	      memcpy(&mx[i+l], &mxtemp,  sizeof(mxtemp));
	    }
#if defined(AF_INET6) && defined(INET6)
	    if (prefer_ip6) {
	      int l; /* Bring IPv6 addresses before IPv4 ones */
	      for (l = 0, k = 1; k < len; ++k) {
		if (mx[l].ai->ai_family == PF_INET &&
		    mx[k].ai->ai_family == PF_INET6) {
		  memcpy(&mxtemp,  &mx[k],  sizeof(mxtemp));
		  memcpy(&mx[k],   &mx[l],  sizeof(mxtemp));
		  memcpy(&mx[l],   &mxtemp, sizeof(mxtemp));
		  ++l;
		}
	      }
	    }
#endif
	  }
	  /* Processed that preference, now next */
	  i = j-1;
	  if (j < nmx)		/* If within the array */
	    maxpref = mx[j].pref;
	}
	if (SS->verboselog) {
	  fprintf(SS->verboselog,"Target has following MXes (cnt=%d):\n",nmx);
	  for (i=0; i<nmx; ++i) {
	    struct addrinfo *ai = mx[i].ai;
	    for (n = 0; ai; ai = ai->ai_next) ++n;
	    fprintf(SS->verboselog,"  MX %3d %-30.200s  (%d %saddrs)\n",
		    mx[i].pref, mx[i].host, n,
		    n == 1 ?
		    (mx[i].ai->ai_family == PF_INET ? "AF_INET ":"AF_INET6 "):
		    "");
	  }
	}
	mx[nmx].host = NULL;
	SS->mxcount = nmx;
	/* Even with errors in data retrieval, if we get
	   ANY   MX entries, we are a happy camper! */
	if (had_eai_again && nmx == 0)
	  return EX_DEFERALL;
	return EX_OK;
}


/* rmsgappend() is here because of getmxrr() test facility */

#ifdef HAVE_STDARG_H
#ifdef __STDC__
void
rmsgappend(SmtpState *SS, int append, const char *fmt, ...)
#else /* Not ANSI-C */
void
rmsgappend(SS, append, fmt)
	SmtpState *SS;
	int append;
	const char *fmt;
#endif
#else
void
rmsgappend(va_alist)
	va_dcl
#endif
{
	va_list ap;
	char *args;
	long  argi;
	char *cp, *cpend;
	char ibuf[15];
	int  long_flg = 0;
#ifdef HAVE_STDARG_H
	va_start(ap,fmt);
#else
	const char *fmt;
	SmtpState *SS;
	int append;
	va_start(ap);
	SS     = va_arg(ap, SmtpState *);
	append = va_arg(ap, int);
	fmt    = va_arg(ap, char *);
#endif

	cp    = SS->remotemsg + strlen(SS->remotemsg);
	cpend = SS->remotemsg + sizeof(SS->remotemsg) -1;

	if (SS->prevcmdstate >= SMTPSTATE99) /* magic limit.. */
	  SS->remotemsgs[(int)SS->cmdstate] = cp = SS->remotemsg;
	if (SS->cmdstate > SS->prevcmdstate)
	  SS->remotemsgs[(int)SS->cmdstate] = cp;

	if (!append)
	  cp = SS->remotemsgs[(int)SS->cmdstate];

	SS->prevcmdstate = SS->cmdstate;

	if (!fmt) fmt="(NULL)";
	for (; *fmt != 0; ++fmt) {
	  if (*fmt == '%') {
	    int c = *++fmt;
	    long_flg = 0;
	    if (c == 'l') {
	      long_flg = 1;
	      c = *++fmt;
	    }
	    switch (c) {
	    case 's':
	      args = va_arg(ap, char *);
	      if (!args) args = "(null)";
	      while (*args && cp < cpend) *cp++ = *args ++;
	      break;
	    case 'd':
	      if (long_flg) {
		argi = va_arg(ap, long);
		sprintf(ibuf, "%ld", argi);
	      } else {
		argi = va_arg(ap, int);
		sprintf(ibuf, "%d", (int)argi);
	      }
	      args = ibuf;
	      while (*args && cp < cpend) *cp++ = *args ++;
	      break;
	    default:
	      if (cp < cpend) *cp++ = '%';
	      if (cp < cpend) *cp++ = c;
	      break;
	    }
	  } else
	    if (cp < cpend)
	      *cp++ = *fmt;
	}
	*cp = 0;
	va_end(ap);
}


#ifdef TEST

time_t endtime, starttime, now;
const char *FAILED = "failed";

int use_ipv6 = 1;
int prefer_ip6 = 1;
int checkwks = 0;

char myhostname[512] = "my.host.name";
const char *progname;
char errormsg[ZBUFSIZ]; /* Global for the use of  dnsgetrr.c */

int main(argc, argv)
     int argc;
     char *argv[];
{
	int rc;
	SmtpState SS;
	char *host, *s;
	char realname[1024];
	time_t realnamettl;

	progname = argv[0];

	memset(&SS, 0, sizeof(SS));
	SS.verboselog = stdout;

	host = argv[1];

	if (argc != 2) {
	  printf("Usage: getmxrr-test domain.name\n");
	  printf("\n");
	  printf("Std tests:\n");
	  printf("     getmxrr-test  timeout-mx.zmailer.org\n");
	  printf("  -> GETMXRR() rc=100 EX_DEFERALL; mxcount=0\n");
	  printf("  -> NO SUCCESSFULLY COLLECTED MX DATA, LOOKING FOR A/AAAA DATA:\n");
	  printf("  -> .. getaddrinfo() (INET*) r=-2 (no address)\n");
	  printf("  ->  GOT NO ADDRESSES!\n");
	  printf("\n");
	  printf("     getmxrr-test  timeout-zone.zmailer.org\n");
	  printf("  -> GETMXRR() rc=100 EX_DEFERALL; mxcount=0\n");
	  printf("  -> NO SUCCESSFULLY COLLECTED MX DATA, LOOKING FOR A/AAAA DATA:\n");
	  printf("  -> .. getaddrinfo() (INET*) r=-3 (temporary failure in name resolution)\n");
	  printf("  ->  GOT NO ADDRESSES!\n");
	  exit(EX_USAGE);
	}

	printf("ZMAILER GETMXRR() TEST HARNESS\n");

	realname[0] = 0;
	realnamettl = 84600;

	rc = getmxrr(&SS, host, SS.mxh, MAXFORWARDERS, 0, realname, sizeof(realname), &realnamettl);

	switch (rc) {
	case EX_OK:
	  s = "EX_OK";
	  break;
	case EX_USAGE:
	  s = "EX_USAGE";
	  break;
	case EX_DATAERR:
	  s = "EX_DATAERR";
	  break;
	case EX_NOINPUT:
	  s = "EX_NOINPUT";
	  break;
	case EX_NOUSER:
	  s = "EX_NOUSER";
	  break;
	case EX_NOHOST:
	  s = "EX_NOHOST";
	  break;
	case EX_UNAVAILABLE:
	  s = "EX_UNAVAILABLE";
	  break;
	case EX_SOFTWARE:
	  s = "EX_SOFTWARE";
	  break;
	case EX_OSERR:
	  s = "EX_OSERR";
	  break;
	case EX_OSFILE:
	  s = "EX_OSFILE";
	  break;
	case EX_CANTCREAT:
	  s = "EX_CANTCREAT";
	  break;
	case EX_IOERR:
	  s = "EX_IOERR";
	  break;
	case EX_TEMPFAIL:
	  s = "EX_TEMPFAIL";
	  break;
	case EX_PROTOCOL:
	  s = "EX_PROTOCOL";
	  break;
	case EX_NOPERM:
	  s = "EX_NOPERM";
	  break;
	case EX_DEFERALL:
	  s = "EX_DEFERALL";
	  break;
	default:
	  s = "UNKNOWN!";
	}


	printf("GETMXRR() rc=%d %s; mxcount=%d\n", rc, s, SS.mxcount);


	if (SS.mxcount == 0 || SS.mxh[0].host == NULL) {

	  struct addrinfo req, *ai;
	  int r;

	  printf("NO SUCCESSFULLY COLLECTED MX DATA, LOOKING FOR A/AAAA DATA:\n");

	  memset(&req, 0, sizeof(req));
	  req.ai_socktype = SOCK_STREAM;
	  req.ai_protocol = IPPROTO_TCP;
	  req.ai_flags    = AI_CANONNAME;
	  req.ai_family   = PF_INET;

	  ai = NULL;

	  errno = 0;
	  /* Either forbidden MX usage, or does not have MX entries! */

#ifdef HAVE__GETADDRINFO_
	  r = _getaddrinfo_(host, "0", &req, &ai, SS.verboselog);
#else
	  r = getaddrinfo(host, "0", &req, &ai);
#endif

	  if (SS.verboselog)
	    fprintf(SS.verboselog,
		    "getaddrinfo('%s','0' (INET)) -> r=%d (%s), ai=%p\n",
		    host, r, gai_strerror(r), ai);
#if defined(AF_INET6) && defined(INET6)
	  if (use_ipv6) {
	    struct addrinfo *ai2 = NULL, **aip;
	    int i2;

	    memset(&req, 0, sizeof(req));
	    req.ai_socktype = SOCK_STREAM;
	    req.ai_protocol = IPPROTO_TCP;
	    req.ai_flags    = AI_CANONNAME;
	    req.ai_family   = PF_INET6;

	    /* This resolves CNAME, it should not happen in case
	       of MX server, though..    */
#ifdef HAVE__GETADDRINFO_
	    i2 = _getaddrinfo_(host, "0", &req, &ai2, SS.verboselog);
#else
	    i2 = getaddrinfo(host, "0", &req, &ai2);
#endif
	    if (SS.verboselog)
	      fprintf(SS.verboselog,
		      "getaddrinfo('%s','0' (INET6)) -> r=%d (%s), ai=%p\n",
		      host,i2, gai_strerror(i2), ai2);

	    if (r != 0 && i2 == 0) {
	      /* IPv6 address, no IPv4 (or error..) */
	      r = i2;
	      ai = ai2; ai2 = NULL;
	    }
	    if (ai2 && ai) {
	      /* BOTH ?!  Catenate them! */
	      aip = &(ai->ai_next);
	      while (*aip) aip = &((*aip)->ai_next);
	      *aip = ai2;
	    }
	  }
#endif
	  if (ai)
	    printf("  GOT SOME ADDRESS, SUCCESS!\n");
	  else
	    printf("  GOT NO ADDRESSES!\n");
	}

	return 0;
}

const char * getzenv(name) 
     const char *name;
{
   return NULL;
}

#if 0 /* Deep testing .. */
int _getaddrinfo_ (host, port, req, ai, logfp)
  const char *host;
  const char *port;
  const struct addrinfo *req;
  struct addrinfo **ai;
  FILE *logfp;
{
  return getaddrinfo(host, port, req, ai);
}
#endif

#endif
