Exemple #1
0
static int
dns_return(const uschar * name, int type, int rc)
{
res_state resp = os_get_dns_resolver_res();
tree_node *node = store_get_perm(sizeof(tree_node) + 290);
sprintf(CS node->name, "%.255s-%s-%lx", name, dns_text_type(type),
  resp->options);
node->data.val = rc;
(void)tree_insertnode(&tree_dns_fails, node);
return rc;
}
Exemple #2
0
int
dns_basic_lookup(dns_answer *dnsa, const uschar *name, int type)
{
#ifndef STAND_ALONE
int rc = -1;
const uschar *save_domain;
#endif
res_state resp = os_get_dns_resolver_res();

tree_node *previous;
uschar node_name[290];

/* DNS lookup failures of any kind are cached in a tree. This is mainly so that
a timeout on one domain doesn't happen time and time again for messages that
have many addresses in the same domain. We rely on the resolver and name server
caching for successful lookups. */

sprintf(CS node_name, "%.255s-%s-%lx", name, dns_text_type(type),
  resp->options);
previous = tree_search(tree_dns_fails, node_name);
if (previous != NULL)
  {
  DEBUG(D_dns) debug_printf("DNS lookup of %.255s-%s: using cached value %s\n",
    name, dns_text_type(type),
      (previous->data.val == DNS_NOMATCH)? "DNS_NOMATCH" :
      (previous->data.val == DNS_NODATA)? "DNS_NODATA" :
      (previous->data.val == DNS_AGAIN)? "DNS_AGAIN" :
      (previous->data.val == DNS_FAIL)? "DNS_FAIL" : "??");
  return previous->data.val;
  }

#ifdef EXPERIMENTAL_INTERNATIONAL
/* Convert all names to a-label form before doing lookup */
  {
  uschar * alabel;
  uschar * errstr = NULL;
  DEBUG(D_dns) if (string_is_utf8(name))
    debug_printf("convert utf8 '%s' to alabel for for lookup\n", name);
  if ((alabel = string_domain_utf8_to_alabel(name, &errstr)), errstr)
    {
    DEBUG(D_dns)
      debug_printf("DNS name '%s' utf8 conversion to alabel failed: %s\n", name,
        errstr);
    host_find_failed_syntax = TRUE;
    return DNS_NOMATCH;
    }
  name = alabel;
  }
#endif

/* If configured, check the hygene of the name passed to lookup. Otherwise,
although DNS lookups may give REFUSED at the lower level, some resolvers
turn this into TRY_AGAIN, which is silly. Give a NOMATCH return, since such
domains cannot be in the DNS. The check is now done by a regular expression;
give it space for substring storage to save it having to get its own if the
regex has substrings that are used - the default uses a conditional.

This test is omitted for PTR records. These occur only in calls from the dnsdb
lookup, which constructs the names itself, so they should be OK. Besides,
bitstring labels don't conform to normal name syntax. (But the aren't used any
more.)

For SRV records, we omit the initial _smtp._tcp. components at the start. */

#ifndef STAND_ALONE   /* Omit this for stand-alone tests */

if (check_dns_names_pattern[0] != 0 && type != T_PTR && type != T_TXT)
  {
  const uschar *checkname = name;
  int ovector[3*(EXPAND_MAXN+1)];

  dns_pattern_init();

  /* For an SRV lookup, skip over the first two components (the service and
  protocol names, which both start with an underscore). */

  if (type == T_SRV || type == T_TLSA)
    {
    while (*checkname++ != '.');
    while (*checkname++ != '.');
    }

  if (pcre_exec(regex_check_dns_names, NULL, CCS checkname, Ustrlen(checkname),
      0, PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int)) < 0)
    {
    DEBUG(D_dns)
      debug_printf("DNS name syntax check failed: %s (%s)\n", name,
        dns_text_type(type));
    host_find_failed_syntax = TRUE;
    return DNS_NOMATCH;
    }
  }

#endif /* STAND_ALONE */

/* Call the resolver; for an overlong response, res_search() will return the
number of bytes the message would need, so we need to check for this case. The
effect is to truncate overlong data.

On some systems, res_search() will recognize "A-for-A" queries and return
the IP address instead of returning -1 with h_error=HOST_NOT_FOUND. Some
nameservers are also believed to do this. It is, of course, contrary to the
specification of the DNS, so we lock it out. */

if ((type == T_A || type == T_AAAA) && string_is_ip_address(name, NULL) != 0)
  return DNS_NOMATCH;

/* If we are running in the test harness, instead of calling the normal resolver
(res_search), we call fakens_search(), which recognizes certain special
domains, and interfaces to a fake nameserver for certain special zones. */

dnsa->answerlen = running_in_test_harness
  ? fakens_search(name, type, dnsa->answer, MAXPACKET)
  : res_search(CCS name, C_IN, type, dnsa->answer, MAXPACKET);

if (dnsa->answerlen > MAXPACKET)
  {
  DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) resulted in overlong packet (size %d), truncating to %d.\n",
    name, dns_text_type(type), dnsa->answerlen, MAXPACKET);
  dnsa->answerlen = MAXPACKET;
  }

if (dnsa->answerlen < 0) switch (h_errno)
  {
  case HOST_NOT_FOUND:
  DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave HOST_NOT_FOUND\n"
    "returning DNS_NOMATCH\n", name, dns_text_type(type));
  return dns_return(name, type, DNS_NOMATCH);

  case TRY_AGAIN:
  DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave TRY_AGAIN\n",
    name, dns_text_type(type));

  /* Cut this out for various test programs */
#ifndef STAND_ALONE
  save_domain = deliver_domain;
  deliver_domain = string_copy(name);  /* set $domain */
  rc = match_isinlist(name, (const uschar **)&dns_again_means_nonexist, 0, NULL, NULL,
    MCL_DOMAIN, TRUE, NULL);
  deliver_domain = save_domain;
  if (rc != OK)
    {
    DEBUG(D_dns) debug_printf("returning DNS_AGAIN\n");
    return dns_return(name, type, DNS_AGAIN);
    }
  DEBUG(D_dns) debug_printf("%s is in dns_again_means_nonexist: returning "
    "DNS_NOMATCH\n", name);
  return dns_return(name, type, DNS_NOMATCH);

#else   /* For stand-alone tests */
  return dns_return(name, type, DNS_AGAIN);
#endif

  case NO_RECOVERY:
  DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_RECOVERY\n"
    "returning DNS_FAIL\n", name, dns_text_type(type));
  return dns_return(name, type, DNS_FAIL);

  case NO_DATA:
  DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_DATA\n"
    "returning DNS_NODATA\n", name, dns_text_type(type));
  return dns_return(name, type, DNS_NODATA);

  default:
  DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave unknown DNS error %d\n"
    "returning DNS_FAIL\n", name, dns_text_type(type), h_errno);
  return dns_return(name, type, DNS_FAIL);
  }

DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) succeeded\n",
  name, dns_text_type(type));

return DNS_SUCCEED;
}
Exemple #3
0
static int
fakens_search(const uschar *domain, int type, uschar *answerptr, int size)
{
int len = Ustrlen(domain);
int asize = size;                  /* Locally modified */
uschar *endname;
uschar name[256];
uschar utilname[256];
uschar *aptr = answerptr;          /* Locally modified */
struct stat statbuf;

/* Remove terminating dot. */

if (domain[len - 1] == '.') len--;
Ustrncpy(name, domain, len);
name[len] = 0;
endname = name + len;

/* This code, for forcing TRY_AGAIN and NO_RECOVERY, is here so that it works
for the old test suite that uses a real nameserver. When the old test suite is
eventually abandoned, this code could be moved into the fakens utility. */

if (len >= 14 && Ustrcmp(endname - 14, "test.again.dns") == 0)
  {
  int delay = Uatoi(name);  /* digits at the start of the name */
  DEBUG(D_dns) debug_printf("Return from DNS lookup of %s (%s) faked for testing\n",
    name, dns_text_type(type));
  if (delay > 0)
    {
    DEBUG(D_dns) debug_printf("delaying %d seconds\n", delay);
    sleep(delay);
    }
  h_errno = TRY_AGAIN;
  return -1;
  }

if (len >= 13 && Ustrcmp(endname - 13, "test.fail.dns") == 0)
  {
  DEBUG(D_dns) debug_printf("Return from DNS lookup of %s (%s) faked for testing\n",
    name, dns_text_type(type));
  h_errno = NO_RECOVERY;
  return -1;
  }

/* Look for the fakens utility, and if it exists, call it. */

(void)string_format(utilname, sizeof(utilname), "%s/../bin/fakens",
  spool_directory);

if (stat(CS utilname, &statbuf) >= 0)
  {
  pid_t pid;
  int infd, outfd, rc;
  uschar *argv[5];

  DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) using fakens\n",
    name, dns_text_type(type));

  argv[0] = utilname;
  argv[1] = spool_directory;
  argv[2] = name;
  argv[3] = dns_text_type(type);
  argv[4] = NULL;

  pid = child_open(argv, NULL, 0000, &infd, &outfd, FALSE);
  if (pid < 0)
    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to run fakens: %s",
      strerror(errno));

  len = 0;
  rc = -1;
  while (asize > 0 && (rc = read(outfd, aptr, asize)) > 0)
    {
    len += rc;
    aptr += rc;       /* Don't modify the actual arguments, because they */
    asize -= rc;      /* may need to be passed on to res_search(). */
    }

  if (rc < 0)
    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "read from fakens failed: %s",
      strerror(errno));

  switch(child_close(pid, 0))
    {
    case 0: return len;
    case 1: h_errno = HOST_NOT_FOUND; return -1;
    case 2: h_errno = TRY_AGAIN; return -1;
    default:
    case 3: h_errno = NO_RECOVERY; return -1;
    case 4: h_errno = NO_DATA; return -1;
    case 5: /* Pass on to res_search() */
    DEBUG(D_dns) debug_printf("fakens returned PASS_ON\n");
    }
  }

/* fakens utility not found, or it returned "pass on" */

DEBUG(D_dns) debug_printf("passing %s on to res_search()\n", domain);

return res_search(CS domain, C_IN, type, answerptr, size);
}
Exemple #4
0
static int
fakens_search(const uschar *domain, int type, uschar *answerptr, int size)
{
int len = Ustrlen(domain);
int asize = size;                  /* Locally modified */
uschar *endname;
uschar name[256];
uschar utilname[256];
uschar *aptr = answerptr;          /* Locally modified */
struct stat statbuf;

/* Remove terminating dot. */

if (domain[len - 1] == '.') len--;
Ustrncpy(name, domain, len);
name[len] = 0;
endname = name + len;

/* Look for the fakens utility, and if it exists, call it. */

(void)string_format(utilname, sizeof(utilname), "%s/bin/fakens",
  config_main_directory);

if (stat(CS utilname, &statbuf) >= 0)
  {
  pid_t pid;
  int infd, outfd, rc;
  uschar *argv[5];

  DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) using fakens\n", name, dns_text_type(type));

  argv[0] = utilname;
  argv[1] = config_main_directory;
  argv[2] = name;
  argv[3] = dns_text_type(type);
  argv[4] = NULL;

  pid = child_open(argv, NULL, 0000, &infd, &outfd, FALSE);
  if (pid < 0)
    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to run fakens: %s",
      strerror(errno));

  len = 0;
  rc = -1;
  while (asize > 0 && (rc = read(outfd, aptr, asize)) > 0)
    {
    len += rc;
    aptr += rc;       /* Don't modify the actual arguments, because they */
    asize -= rc;      /* may need to be passed on to res_search(). */
    }

  /* If we ran out of output buffer before exhasting the return,
  carry on reading and counting it. */

  if (asize == 0)
    while ((rc = read(outfd, name, sizeof(name))) > 0)
      len += rc;

  if (rc < 0)
    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "read from fakens failed: %s",
      strerror(errno));

  switch(child_close(pid, 0))
    {
    case 0: return len;
    case 1: h_errno = HOST_NOT_FOUND; return -1;
    case 2: h_errno = TRY_AGAIN; return -1;
    default:
    case 3: h_errno = NO_RECOVERY; return -1;
    case 4: h_errno = NO_DATA; return -1;
    case 5: /* Pass on to res_search() */
    DEBUG(D_dns) debug_printf("fakens returned PASS_ON\n");
    }
  }
else
  {
  DEBUG(D_dns) debug_printf("fakens (%s) not found\n", utilname);
  }

/* fakens utility not found, or it returned "pass on" */

DEBUG(D_dns) debug_printf("passing %s on to res_search()\n", domain);

return res_search(CS domain, C_IN, type, answerptr, size);
}
Exemple #5
0
static int
dnsdb_find(void *handle, uschar *filename, const uschar *keystring, int length,
  uschar **result, uschar **errmsg, uint *do_cache)
{
int rc;
int size = 256;
int ptr = 0;
int sep = 0;
int defer_mode = PASS;
int dnssec_mode = OK;
int save_retrans = dns_retrans;
int save_retry =   dns_retry;
int type;
int failrc = FAIL;
const uschar *outsep = CUS"\n";
const uschar *outsep2 = NULL;
uschar *equals, *domain, *found;

/* Because we're the working in the search pool, we try to reclaim as much
store as possible later, so we preallocate the result here */

uschar *yield = store_get(size);

dns_record *rr;
dns_answer dnsa;
dns_scan dnss;

handle = handle;           /* Keep picky compilers happy */
filename = filename;
length = length;
do_cache = do_cache;

/* If the string starts with '>' we change the output separator.
If it's followed by ';' or ',' we set the TXT output separator. */

while (isspace(*keystring)) keystring++;
if (*keystring == '>')
  {
  outsep = keystring + 1;
  keystring += 2;
  if (*keystring == ',')
    {
    outsep2 = keystring + 1;
    keystring += 2;
    }
  else if (*keystring == ';')
    {
    outsep2 = US"";
    keystring++;
    }
  while (isspace(*keystring)) keystring++;
  }

/* Check for a modifier keyword. */

for (;;)
  {
  if (strncmpic(keystring, US"defer_", 6) == 0)
    {
    keystring += 6;
    if (strncmpic(keystring, US"strict", 6) == 0)
      { defer_mode = DEFER; keystring += 6; }
    else if (strncmpic(keystring, US"lax", 3) == 0)
      { defer_mode = PASS; keystring += 3; }
    else if (strncmpic(keystring, US"never", 5) == 0)
      { defer_mode = OK; keystring += 5; }
    else
      {
      *errmsg = US"unsupported dnsdb defer behaviour";
      return DEFER;
      }
    }
  else if (strncmpic(keystring, US"dnssec_", 7) == 0)
    {
    keystring += 7;
    if (strncmpic(keystring, US"strict", 6) == 0)
      { dnssec_mode = DEFER; keystring += 6; }
    else if (strncmpic(keystring, US"lax", 3) == 0)
      { dnssec_mode = PASS; keystring += 3; }
    else if (strncmpic(keystring, US"never", 5) == 0)
      { dnssec_mode = OK; keystring += 5; }
    else
      {
      *errmsg = US"unsupported dnsdb dnssec behaviour";
      return DEFER;
      }
    }
  else if (strncmpic(keystring, US"retrans_", 8) == 0)
    {
    int timeout_sec;
    if ((timeout_sec = readconf_readtime(keystring += 8, ',', FALSE)) <= 0)
      {
      *errmsg = US"unsupported dnsdb timeout value";
      return DEFER;
      }
    dns_retrans = timeout_sec;
    while (*keystring != ',') keystring++;
    }
  else if (strncmpic(keystring, US"retry_", 6) == 0)
    {
    int retries;
    if ((retries = (int)strtol(CCS keystring + 6, CSS &keystring, 0)) < 0)
      {
      *errmsg = US"unsupported dnsdb retry count";
      return DEFER;
      }
    dns_retry = retries;
    }
  else
    break;

  while (isspace(*keystring)) keystring++;
  if (*keystring++ != ',')
    {
    *errmsg = US"dnsdb modifier syntax error";
    return DEFER;
    }
  while (isspace(*keystring)) keystring++;
  }

/* Figure out the "type" value if it is not T_TXT.
If the keystring contains an = this must be preceded by a valid type name. */

type = T_TXT;
if ((equals = Ustrchr(keystring, '=')) != NULL)
  {
  int i, len;
  uschar *tend = equals;

  while (tend > keystring && isspace(tend[-1])) tend--;
  len = tend - keystring;

  for (i = 0; i < nelem(type_names); i++)
    if (len == Ustrlen(type_names[i]) &&
        strncmpic(keystring, US type_names[i], len) == 0)
      {
      type = type_values[i];
      break;
      }

  if (i >= nelem(type_names))
    {
    *errmsg = US"unsupported DNS record type";
    return DEFER;
    }

  keystring = equals + 1;
  while (isspace(*keystring)) keystring++;
  }

/* Initialize the resolver in case this is the first time it has been used. */

dns_init(FALSE, FALSE, dnssec_mode != OK);

/* The remainder of the string must be a list of domains. As long as the lookup
for at least one of them succeeds, we return success. Failure means that none
of them were found.

The original implementation did not support a list of domains. Adding the list
feature is compatible, except in one case: when PTR records are being looked up
for a single IPv6 address. Fortunately, we can hack in a compatibility feature
here: If the type is PTR and no list separator is specified, and the entire
remaining string is valid as an IP address, set an impossible separator so that
it is treated as one item. */

if (type == T_PTR && keystring[0] != '<' &&
    string_is_ip_address(keystring, NULL) != 0)
  sep = -1;

/* SPF strings should be concatenated without a separator, thus make
it the default if not defined (see RFC 4408 section 3.1.3).
Multiple SPF records are forbidden (section 3.1.2) but are currently
not handled specially, thus they are concatenated with \n by default.
MX priority and value are space-separated by default.
SRV and TLSA record parts are space-separated by default. */

if (!outsep2) switch(type)
  {
  case T_SPF:                         outsep2 = US"";  break;
  case T_SRV: case T_MX: case T_TLSA: outsep2 = US" "; break;
  }

/* Now scan the list and do a lookup for each item */

while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
  {
  uschar rbuffer[256];
  int searchtype = (type == T_CSA)? T_SRV :         /* record type we want */
                   (type == T_MXH)? T_MX :
                   (type == T_ZNS)? T_NS : type;

  /* If the type is PTR or CSA, we have to construct the relevant magic lookup
  key if the original is an IP address (some experimental protocols are using
  PTR records for different purposes where the key string is a host name, and
  Exim's extended CSA can be keyed by domains or IP addresses). This code for
  doing the reversal is now in a separate function. */

  if ((type == T_PTR || type == T_CSA) &&
      string_is_ip_address(domain, NULL) != 0)
    {
    dns_build_reverse(domain, rbuffer);
    domain = rbuffer;
    }

  do
    {
    DEBUG(D_lookup) debug_printf("dnsdb key: %s\n", domain);

    /* Do the lookup and sort out the result. There are four special types that
    are handled specially: T_CSA, T_ZNS, T_ADDRESSES and T_MXH.
    The first two are handled in a special lookup function so that the facility
    could be used from other parts of the Exim code. T_ADDRESSES is handled by looping
    over the types of A lookup.  T_MXH affects only what happens later on in
    this function, but for tidiness it is handled by the "special". If the
    lookup fails, continue with the next domain. In the case of DEFER, adjust
    the final "nothing found" result, but carry on to the next domain. */

    found = domain;
#if HAVE_IPV6
    if (type == T_ADDRESSES)		/* NB cannot happen unless HAVE_IPV6 */
      {
      if (searchtype == T_ADDRESSES) searchtype = T_AAAA;
      else if (searchtype == T_AAAA) searchtype = T_A;
      rc = dns_special_lookup(&dnsa, domain, searchtype, CUSS &found);
      }
    else
#endif
      rc = dns_special_lookup(&dnsa, domain, type, CUSS &found);

    lookup_dnssec_authenticated = dnssec_mode==OK ? NULL
      : dns_is_secure(&dnsa) ? US"yes" : US"no";

    if (rc == DNS_NOMATCH || rc == DNS_NODATA) continue;
    if (  rc != DNS_SUCCEED
       || (dnssec_mode == DEFER && !dns_is_secure(&dnsa))
       )
      {
      if (defer_mode == DEFER)
	{
	dns_retrans = save_retrans;
	dns_retry = save_retry;
	dns_init(FALSE, FALSE, FALSE);			/* clr dnssec bit */
	return DEFER;					/* always defer */
	}
      if (defer_mode == PASS) failrc = DEFER;         /* defer only if all do */
      continue;                                       /* treat defer as fail */
      }


    /* Search the returned records */

    for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
         rr != NULL;
         rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
      {
      if (rr->type != searchtype) continue;

      if (*do_cache > rr->ttl)
	*do_cache = rr->ttl;

      if (type == T_A || type == T_AAAA || type == T_ADDRESSES)
        {
        dns_address *da;
        for (da = dns_address_from_rr(&dnsa, rr); da; da = da->next)
          {
          if (ptr != 0) yield = string_catn(yield, &size, &ptr, outsep, 1);
          yield = string_cat(yield, &size, &ptr, da->address);
          }
        continue;
        }

      /* Other kinds of record just have one piece of data each, but there may be
      several of them, of course. */

      if (ptr != 0) yield = string_catn(yield, &size, &ptr, outsep, 1);

      if (type == T_TXT || type == T_SPF)
        {
        if (outsep2 == NULL)
          {
          /* output only the first item of data */
          yield = string_catn(yield, &size, &ptr, (uschar *)(rr->data+1),
            (rr->data)[0]);
          }
        else
          {
          /* output all items */
          int data_offset = 0;
          while (data_offset < rr->size)
            {
            uschar chunk_len = (rr->data)[data_offset++];
            if (outsep2[0] != '\0' && data_offset != 1)
              yield = string_catn(yield, &size, &ptr, outsep2, 1);
            yield = string_catn(yield, &size, &ptr,
                             US ((rr->data)+data_offset), chunk_len);
            data_offset += chunk_len;
            }
          }
        }
      else if (type == T_TLSA)
        {
        uint8_t usage, selector, matching_type;
        uint16_t i, payload_length;
        uschar s[MAX_TLSA_EXPANDED_SIZE];
	uschar * sp = s;
        uschar * p = US rr->data;

        usage = *p++;
        selector = *p++;
        matching_type = *p++;
        /* What's left after removing the first 3 bytes above */
        payload_length = rr->size - 3;
        sp += sprintf(CS s, "%d%c%d%c%d%c", usage, *outsep2,
		selector, *outsep2, matching_type, *outsep2);
        /* Now append the cert/identifier, one hex char at a time */
        for (i=0;
             i < payload_length && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4);
             i++)
          sp += sprintf(CS sp, "%02x", (unsigned char)p[i]);

        yield = string_cat(yield, &size, &ptr, s);
        }
      else   /* T_CNAME, T_CSA, T_MX, T_MXH, T_NS, T_PTR, T_SOA, T_SRV */
        {
        int priority, weight, port;
        uschar s[264];
        uschar * p = US rr->data;

	switch (type)
	  {
	  case T_MXH:
	    /* mxh ignores the priority number and includes only the hostnames */
	    GETSHORT(priority, p);
	    break;

	  case T_MX:
	    GETSHORT(priority, p);
	    sprintf(CS s, "%d%c", priority, *outsep2);
	    yield = string_cat(yield, &size, &ptr, s);
	    break;

	  case T_SRV:
	    GETSHORT(priority, p);
	    GETSHORT(weight, p);
	    GETSHORT(port, p);
	    sprintf(CS s, "%d%c%d%c%d%c", priority, *outsep2,
			      weight, *outsep2, port, *outsep2);
	    yield = string_cat(yield, &size, &ptr, s);
	    break;

	  case T_CSA:
	    /* See acl_verify_csa() for more comments about CSA. */
	    GETSHORT(priority, p);
	    GETSHORT(weight, p);
	    GETSHORT(port, p);

	    if (priority != 1) continue;      /* CSA version must be 1 */

	    /* If the CSA record we found is not the one we asked for, analyse
	    the subdomain assertions in the port field, else analyse the direct
	    authorization status in the weight field. */

	    if (Ustrcmp(found, domain) != 0)
	      {
	      if (port & 1) *s = 'X';         /* explicit authorization required */
	      else *s = '?';                  /* no subdomain assertions here */
	      }
	    else
	      {
	      if (weight < 2) *s = 'N';       /* not authorized */
	      else if (weight == 2) *s = 'Y'; /* authorized */
	      else if (weight == 3) *s = '?'; /* unauthorizable */
	      else continue;                  /* invalid */
	      }

	    s[1] = ' ';
	    yield = string_catn(yield, &size, &ptr, s, 2);
	    break;

	  default:
	    break;
	  }

        /* GETSHORT() has advanced the pointer to the target domain. */

        rc = dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p,
          (DN_EXPAND_ARG4_TYPE)s, sizeof(s));

        /* If an overlong response was received, the data will have been
        truncated and dn_expand may fail. */

        if (rc < 0)
          {
          log_write(0, LOG_MAIN, "host name alias list truncated: type=%s "
            "domain=%s", dns_text_type(type), domain);
          break;
          }
        else yield = string_cat(yield, &size, &ptr, s);

	if (type == T_SOA && outsep2 != NULL)
	  {
	  unsigned long serial, refresh, retry, expire, minimum;

	  p += rc;
	  yield = string_catn(yield, &size, &ptr, outsep2, 1);

	  rc = dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p,
	    (DN_EXPAND_ARG4_TYPE)s, sizeof(s));
	  if (rc < 0)
	    {
	    log_write(0, LOG_MAIN, "responsible-mailbox truncated: type=%s "
	      "domain=%s", dns_text_type(type), domain);
	    break;
	    }
	  else yield = string_cat(yield, &size, &ptr, s);

	  p += rc;
	  GETLONG(serial, p); GETLONG(refresh, p);
	  GETLONG(retry,  p); GETLONG(expire,  p); GETLONG(minimum, p);
	  sprintf(CS s, "%c%lu%c%lu%c%lu%c%lu%c%lu",
	    *outsep2, serial, *outsep2, refresh,
	    *outsep2, retry,  *outsep2, expire,  *outsep2, minimum);
	  yield = string_cat(yield, &size, &ptr, s);
	  }
        }
      }    /* Loop for list of returned records */

           /* Loop for set of A-lookup types */
    } while (type == T_ADDRESSES && searchtype != T_A);

  }        /* Loop for list of domains */

/* Reclaim unused memory */

store_reset(yield + ptr + 1);

/* If ptr == 0 we have not found anything. Otherwise, insert the terminating
zero and return the result. */

dns_retrans = save_retrans;
dns_retry = save_retry;
dns_init(FALSE, FALSE, FALSE);	/* clear the dnssec bit for getaddrbyname */

if (ptr == 0) return failrc;
yield[ptr] = 0;
*result = yield;
return OK;
}