/*************************************************
*     Exim - an Internet mail transport agent    *
*************************************************/

/* Copyright (c) University of Cambridge 1995 - 2002 */
/* See the file NOTICE for conditions of use and distribution. */

/* Functions concerned with verifying things. The original code for callout
caching was contributed by Kevin Fleming (but I hacked it around a bit). */


#include "exim.h"


/* Structure for passing arguments to check_host() */

typedef struct check_host_block {
  uschar *host_name;
  uschar *host_address;
  uschar *host_ipv4;
  uschar **valueptr;
  BOOL   negative;
} check_host_block;


/* Structure for caching DNSBL lookups */

typedef struct dnsbl_cache_block {
  dns_address *rhs;
  uschar *text;
  int rc;
  BOOL text_set;
} dnsbl_cache_block;


/* Anchor for DNSBL cache */

static tree_node *dnsbl_cache = NULL;



/*************************************************
*          Retrieve a callout cache record       *
*************************************************/

/* If a record exists, check whether it has expired, and if so remove it from
the cache.

Arguments:
  dbm_file          an open hints file
  key               the record key
  type              "address" or "domain"
  positive_expire   expire time for positive records
  negative_expire   expire time for negative records

Returns:            the cache record if a non-expired one exists, else NULL
*/

static dbdata_callout_cache *
get_callout_cache_record(open_db *dbm_file, uschar *key, uschar *type,
  int positive_expire, int negative_expire)
{
BOOL negative;
dbdata_callout_cache *cache_record = dbfn_read(dbm_file, key);

if (cache_record == NULL)
  {
  DEBUG(D_verify) debug_printf("callout cache: no %s record found\n", type);
  return NULL;
  }

/* We treat a record as "negative" if its result field is not positive, or if
it is a domain record and the postmaster field is negative. */

negative = cache_record->result != ccache_accept ||
  (type[0] == 'd' && cache_record->postmaster_accept == ccache_reject);

if ((time(NULL) - cache_record->time_stamp) >
      (negative? negative_expire : positive_expire))
  {
  DEBUG(D_verify) debug_printf("callout cache: %s record expired\n", type);
  return NULL;
  }

DEBUG(D_verify) debug_printf("callout cache: found %s record\n", type);
return cache_record;
}



/*************************************************
*      Do callout verification for an address    *
*************************************************/

/* This function is called from verify_address() when the address has routed to
a host list, and a callout has been requested. Callouts are expensive; that is
why a cache is used to improve the efficiency.

Arguments:
  addr         the address that's been routed
  callout      the callout timeout
  options      the verification options -these bits are used:
                 vopt_is_recipient => this is a recipient address
                 vopt_callout_postmaster => do postmaster check
                 vopt_callout_no_cache => don't use callout cache
                 vopt_callout_random => do the "random" thing

Returns:       OK/FAIL/DEFER
*/

static int
do_callout(address_item *addr, int callout, int options)
{
BOOL is_recipient = (options & vopt_is_recipient) != 0;
BOOL callout_postmaster = (options & vopt_callout_postmaster) != 0;
BOOL callout_no_cache = (options & vopt_callout_no_cache) != 0;
BOOL callout_random = (options & vopt_callout_random) != 0;

int yield = OK;
host_item *host;
BOOL done = FALSE;
BOOL random_ok = FALSE;
uschar *random_local_part = NULL;
open_db dbblock;
open_db *dbm_file = NULL;
dbdata_callout_cache new_domain_record;

/* Open the callout cache database, it it exists, for reading only at this
stage, unless caching has been disabled. */

if (callout_no_cache)
  {
  DEBUG(D_verify) debug_printf("callout cache: disabled by no_cache\n");
  }
else if ((dbm_file = dbfn_open(US"callout", O_RDWR, &dbblock, FALSE)) == NULL)
  {
  DEBUG(D_verify) debug_printf("callout cache: not available\n");
  }

/* If a cache database is available see if we can avoid the need to do an
actual callout by making use of previously-obtained data. */

if (dbm_file != NULL)
  {
  dbdata_callout_cache *cache_record = get_callout_cache_record(dbm_file,
    addr->domain, US"domain",
    callout_cache_domain_positive_expire,
    callout_cache_domain_negative_expire);

  /* If an unexpired cache record was found for this domain, see if the callout
  process can be short-circuited. */

  if (cache_record != NULL)
    {
    /* If an early command (up to and including MAIL FROM:<>) was rejected,
    there is no point carrying on. The callout fails. */

    if (cache_record->result == ccache_reject)
      {
      setflag(addr, af_verify_nsfail);
      DEBUG(D_verify)
        debug_printf("callout cache: domain gave initial rejection, or "
          "does not accept HELO or MAIL FROM:<>\n");
      yield = FAIL;
      goto END_CALLOUT;
      }

    /* If a postmaster check is requested, but there was a previous failure,
    there is again no point in carrying on. If a postmaster check is required,
    but has not been done before, we are going to have to do a callout, so skip
    remaining cache processing. */

    if (callout_postmaster)
      {
      if (cache_record->postmaster_accept == ccache_reject)
        {
        setflag(addr, af_verify_pmfail);
        DEBUG(D_verify)
          debug_printf("callout cache: domain does not accept "
            "RCPT TO:<postmaster@domain>\n");
        yield = FAIL;
        goto END_CALLOUT;
        }
      if (cache_record->postmaster_accept == ccache_unknown) goto END_CACHE;
      }

    /* If a previous check on a "random" local part was accepted, we assume
    that the server does not do any checking on local parts. There is therefore
    no point in doing the callout, because it will always be successful. Again,
    if a random check is required but hasn't been done, skip the remaining
    cache processing. */

    if (callout_random)
      {
      if (cache_record->random_accept == ccache_accept)
        {
        DEBUG(D_verify)
	  debug_printf("callout cache: domain accepts random addresses\n");
        goto END_CALLOUT;     /* Default yield is OK */
        }
      if (cache_record->random_accept == ccache_unknown) goto END_CACHE;
      }
    }

  /* We can't give a result based on information about the domain. See if there
  is an unexpired cache record for this specific address. */

  cache_record = get_callout_cache_record(dbm_file,
    addr->address, US"address",
    callout_cache_positive_expire,
    callout_cache_negative_expire);

  if (cache_record != NULL)
    {
    if (cache_record->result == ccache_accept)
      {
      DEBUG(D_verify)
        debug_printf("callout cache: address record is positive\n");
      }
    else
      {
      DEBUG(D_verify)
        debug_printf("callout cache: address record is negative\n");
      yield = FAIL;
      }
    goto END_CALLOUT;
    }

  /* Close the cache database while we actually do the callout for real. */

  END_CACHE:
  dbfn_close(dbm_file);
  dbm_file = NULL;
  }


/* The information wasn't available in the cache, so we have to do a real
callout and save the result in the cache for next time, unless no_cache is set.
Set up a domain record with default settings. */

new_domain_record.result = ccache_unknown;
new_domain_record.postmaster_accept = ccache_unknown;
new_domain_record.random_accept = ccache_unknown;

/* If we are to test with a random local part, ensure that such a local part is
available. If not, log the fact, but carry on without randomming. */

if (callout_random && callout_random_local_part != NULL)
  {
  random_local_part = expand_string(callout_random_local_part);
  if (random_local_part == NULL)
    log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
      "callout_random_local_part: %s", expand_string_message);
  }


/* Once we've connected to a host, we do the complete set of requested tests,
even though there might be previous knowledge about some of them, because the
expensive bit is making the connection, and anyway, this gets the domain
record's timestamp updated. */

for (host = addr->host_list; host != NULL && !done; host = host->next)
  {
  smtp_inblock inblock;
  smtp_outblock outblock;
  int host_af;
  int port = 25;
  uschar *helo = US"HELO";
  uschar *interface = NULL;  /* Outgoing interface to use; NULL => any */
  uschar inbuffer[1024];
  uschar outbuffer[1024];
  uschar responsebuffer[1024];

  clearflag(addr, af_verify_pmfail);  /* postmaster callout flag */
  clearflag(addr, af_verify_nsfail);  /* null sender callout flag */

  /* Skip this host if we don't have an IP address for it. */

  if (host->address == NULL) continue;

  /* Set IPv4 or IPv6 */

  host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET:AF_INET6;

  /* If verification yielded a remote transport, we want to use that
  transport's interface, port, and protocol options, so as to mimic what
  would happen if we were really sending a message to this address. If
  there is a problem getting the values, log a panic message, but carry on
  with the default values. */

  if (addr->transport != NULL && !addr->transport->info->local)
    {
    uschar *ifstring = NULL;
    uschar *portstring = NULL;
    uschar *protocolstring = NULL;

    (void)(addr->transport->setup)(addr->transport, addr, &ifstring,
      &portstring, &protocolstring, NULL);
    deliver_host = host->name;
    deliver_host_address = host->address;
    if (!smtp_get_interface(ifstring, host_af, addr, NULL, &interface,
            US"callout") ||
        !smtp_get_port(portstring, addr, &port, US"callout"))
      log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
        addr->message);
    if (Ustrcmp(protocolstring, "lmtp") == 0) helo = US"LHLO";
    deliver_host = deliver_host_address = NULL;
    }

  HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", interface, port);

  /* Set up the buffer for reading SMTP response packets. */

  inblock.buffer = inbuffer;
  inblock.buffersize = sizeof(inbuffer);
  inblock.ptr = inbuffer;
  inblock.ptrend = inbuffer;

  /* Set up the buffer for holding SMTP commands while pipelining */

  outblock.buffer = outbuffer;
  outblock.buffersize = sizeof(outbuffer);
  outblock.ptr = outbuffer;
  outblock.cmd_count = 0;
  outblock.authenticating = FALSE;

  /* Connect to the host; on failure, just loop for the next one */

  inblock.sock = outblock.sock =
    smtp_connect(host, host_af, port, interface, callout, TRUE);
  if (inblock.sock < 0) continue;

  /* Wait for initial response, and then run the initial SMTP commands. The
  smtp_write_command() function leaves its command in big_buffer. This is
  used in error responses. Initialize it in case the connection is
  rejected. */

  Ustrcpy(big_buffer, "initial connection");

  done =
    smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
      '2', callout) &&

    smtp_write_command(&outblock, FALSE, "%s %s\r\n", helo,
      primary_hostname) >= 0 &&
    smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
      '2', callout) &&

    smtp_write_command(&outblock, FALSE, "MAIL FROM:<>\r\n") >= 0 &&
    smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
      '2', callout);

  /* If the host gave an initial error, or does not accept HELO or MAIL
  FROM:<>, arrange to cache this information, but don't record anything for an
  I/O error or a defer. */

  if (!done)
    {
    if (errno == 0 && responsebuffer[0] == '5')
      {
      setflag(addr, af_verify_nsfail);
      new_domain_record.result = ccache_reject;
      }
    }

  /* Otherwise, proceed to check the given address, and if successful, follow
  with other checks if configured. In each case, issue RSET first, because
  some server accept only one recipient after MAIL FROM:<>. */

  else
    {
    new_domain_record.result = ccache_accept;

    done =
      smtp_write_command(&outblock, FALSE, "RCPT TO:<%.1000s>\r\n",
        addr->address) >= 0 &&
      smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
        '2', callout);

    /* Do postmaster check if requested */

    if (done && callout_postmaster)
      {
      done =
        smtp_write_command(&outblock, FALSE, "RSET\r\n") >= 0 &&
        smtp_read_response(&inblock, responsebuffer,
          sizeof(responsebuffer), '2', callout) &&

        smtp_write_command(&outblock, FALSE, "MAIL FROM:<>\r\n") >= 0 &&
        smtp_read_response(&inblock, responsebuffer,
          sizeof(responsebuffer), '2', callout) &&

        smtp_write_command(&outblock, FALSE,
          "RCPT TO:<postmaster@%.1000s>\r\n", addr->domain) >= 0 &&
        smtp_read_response(&inblock, responsebuffer,
          sizeof(responsebuffer), '2', callout);

      if (done)
        new_domain_record.postmaster_accept = ccache_accept;
      else if (errno == 0 && responsebuffer[0] == '5')
        {
        setflag(addr, af_verify_pmfail);
        new_domain_record.postmaster_accept = ccache_reject;
        }
      }

    /* Random local part check */

    if (done && random_local_part != NULL)
      {
      uschar randombuffer[1024];
      random_ok =
        smtp_write_command(&outblock, FALSE, "RSET\r\n") >= 0 &&
        smtp_read_response(&inblock, randombuffer,
          sizeof(randombuffer), '2', callout) &&

        smtp_write_command(&outblock, FALSE, "MAIL FROM:<>\r\n") >= 0 &&
        smtp_read_response(&inblock, randombuffer,
          sizeof(randombuffer), '2', callout) &&

        smtp_write_command(&outblock, FALSE,
          "RCPT TO:<%.1000s@%.1000s>\r\n", random_local_part,
          addr->domain) >= 0 &&
        smtp_read_response(&inblock, randombuffer,
          sizeof(randombuffer), '2', callout);

      if (random_ok)
        new_domain_record.random_accept = ccache_accept;
      else if (errno == 0 && randombuffer[0] == '5')
        new_domain_record.random_accept = ccache_reject;
      }		  /* Random check */
    }             /* MAIL FROM:<> accepted */

  /* For any failure of the main check, other than a negative response, we just
  close the connection and carry on. We can identify a negative response by the
  fact that errno is zero. For I/O errors it will be non-zero

  Set up different error texts for logging and for sending back to the caller
  as an SMTP response. Log in all cases, using a one-line format. For sender
  callouts, give a full response to the caller, but for recipient callouts,
  don't give the IP address because this may be an internal host whose identity
  is not to be widely broadcast. */

  if (!done && errno == 0)
    {
    addr->message =
      string_sprintf("response to \"%s\" from %s [%s] was %s",
        big_buffer, host->name, host->address,
        string_printing(responsebuffer));

    addr->user_message = is_recipient?
      string_sprintf("Callout verification failed:\n%s", responsebuffer)
      :
      string_sprintf("Called:   %s\nSent:     %s\nResponse: %s",
        host->address, big_buffer, responsebuffer);

    /* Hard rejection ends the process */

    if (responsebuffer[0] == '5')   /* Address rejected */
      {
      yield = FAIL;
      done = TRUE;
      }
    }

  /* End the SMTP conversation and close the connection. */

  smtp_write_command(&outblock, FALSE, "QUIT\r\n");
  close(inblock.sock);
  }    /* Loop through all hosts, while !done */


/* If we get here with done == TRUE, a successful callout happened, and yield
will be set OK or FAIL according to the response to the RCPT command.
Otherwise, we looped through the hosts but couldn't complete the business.
However, there may be domain-specific information to cache in both cases.

The value of the result field in the new_domain record is ccache_unknown if
there was an error before or with MAIL FROM:<>, and errno was not zero,
implying some kind of I/O error. We don't want to write the cache in that case.
Otherwise the value is ccache_accept or ccache_reject. */

if (!callout_no_cache && new_domain_record.result != ccache_unknown)
  {
  if ((dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE))
       == NULL)
    {
    DEBUG(D_verify) debug_printf("callout cache: not available\n");
    }
  else
    {
    (void)dbfn_write(dbm_file, addr->domain, &new_domain_record,
      (int)sizeof(dbdata_callout_cache));
    DEBUG(D_verify) debug_printf("created callout cache domain record:\n"
      "  result=%d postmaster=%d random=%d\n",
      new_domain_record.result,
      new_domain_record.postmaster_accept,
      new_domain_record.random_accept);
    }
  }

/* If a definite result was obtained for the callout, cache it unless caching
is disabled, or we had a negative domain record. */

if (done)
  {
  if (!callout_no_cache && new_domain_record.result != ccache_reject)
    {
    if (dbm_file == NULL)
      dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE);
    if (dbm_file == NULL)
      {
      DEBUG(D_verify) debug_printf("no callout cache available\n");
      }
    else
      {
      dbdata_callout_cache new_record;
      memset(&new_record, 0, sizeof(new_record));
      new_record.result = (yield == FAIL)? ccache_reject : ccache_accept;
      (void)dbfn_write(dbm_file, addr->address, &new_record,
        (int)sizeof(dbdata_callout_cache));
      DEBUG(D_verify) debug_printf("wrote %s callout cache record\n",
        (new_record.result == ccache_accept)? "positive" : "negative");
      }
    }
  }    /* done */

/* Failure to connect to any host, or any response other than 2xx or 5xx is a
temporary error. If there was only one host, and a response was received, leave
it alone. Otherwise, give a generic response. */

else   /* !done */
  {
  uschar *dullmsg = string_sprintf("Could not complete %s verify callout",
    is_recipient? "recipient" : "sender");
  yield = DEFER;
  if (addr->host_list->next != NULL || addr->message == NULL)
    {
    addr->user_message = addr->message = dullmsg;
    if (smtp_return_error_details)
      addr->user_message = string_sprintf("%s for <%s>.\n"
        "The mail server(s) for the domain may be temporarily unreachable, or\n"
        "they may be permanently unreachable from this server. In the latter case,\n%s",
        addr->message, addr->address,
        is_recipient?
          "the address will never be accepted."
          :
          "you need to change the address or create an MX record for its domain\n"
          "if it is supposed to be generally accessible from the Internet.\n"
          "Talk to your mail administrator for details.");
    }
  else if (!smtp_return_error_details) addr->user_message = dullmsg;
  addr->basic_errno = ERRNO_CALLOUTDEFER;
  }

/* Come here from within the cache-reading code on fast-track exit. */

END_CALLOUT:
if (dbm_file != NULL) dbfn_close(dbm_file);
return yield;
}



/*************************************************
*           Copy error to toplevel address       *
*************************************************/

/* This function is used when a verify fails or defers, to ensure that the
failure or defer information is in the original toplevel address. This applies
when an address is redirected to a single new address, and the failure or
deferral happens to the child address.

Arguments:
  vaddr       the verify address item
  addr        the final address item
  yield       FAIL or DEFER

Returns:      the value of YIELD
*/

static int
copy_error(address_item *vaddr, address_item *addr, int yield)
{
if (addr != vaddr)
  {
  vaddr->message = addr->message;
  vaddr->user_message = addr->user_message;
  vaddr->basic_errno = addr->basic_errno;
  vaddr->more_errno = addr->more_errno;
  }
return yield;
}




/*************************************************
*            Verify an email address             *
*************************************************/

/* This function is used both for verification (-bv and at other times) and
address testing (-bt), which is indicated by address_test_mode being set.

Arguments:
  vaddr           contains the address to verify
  f               if not NULL, write the result to this file
  options         various option bits:
                    vopt_is_recipient => this is a recipient address, otherwise
                      it's a sender address - this affects qualification and
                      rewriting and messages from callouts
                    vopt_qualify => qualify an unqualified address; else error
                    vopt_expn => called from SMTP EXPN command
                    vopt_callout_postmaster => do postmaster check
                    vopt_callout_no_cache => don't use callout cache
                    vopt_callout_random => do the "random" thing
                  the last three are passed on to do_callout()

  callout         if > 0, specifies that callout is required, and gives timeout

Returns:          OK      address verified
                  FAIL    address failed to verify
                  DEFER   can't tell at present
*/

int
verify_address(address_item *vaddr, FILE *f, int options, int callout)
{
BOOL allok = TRUE;
BOOL full_info = (f == NULL)? FALSE : (debug_selector != 0);
BOOL is_recipient = (options & vopt_is_recipient) != 0;
BOOL expn         = (options & vopt_expn) != 0;

int i;
int yield = OK;
int verify_type = expn? v_expn :
     address_test_mode? v_none :
          is_recipient? v_recipient : v_sender;
address_item *addr_list;
address_item *addr_new = NULL;
address_item *addr_remote = NULL;
address_item *addr_local = NULL;
address_item *addr_succeed = NULL;
uschar *ko_prefix, *cr;
uschar *address = vaddr->address;

/* Set up a prefix and suffix for error message which allow us to use the same
output statements both in EXPN mode (where an SMTP response is needed) and when
debugging with an output file. */

if (expn)
  {
  ko_prefix = US"553 ";
  cr = US"\r";
  }
else ko_prefix = cr = US"";

/* Add qualify domain if permitted; otherwise an unqualified address fails. */

if (parse_find_at(address) == NULL)
  {
  if ((options & vopt_qualify) == 0)
    {
    if (f != NULL)
      fprintf(f, "%sA domain is required for \"%s\"%s\n", ko_prefix, address,
        cr);
    return FAIL;
    }
  address = rewrite_address_qualify(address, is_recipient);
  }

DEBUG(D_verify)
  {
  debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
  debug_printf("%s %s\n", address_test_mode? "Testing" : "Verifying", address);
  }

/* Rewrite and report on it. Clear the domain and local part caches - these
may have been set by domains and local part tests during an ACL. */

if (global_rewrite_rules != NULL)
  {
  uschar *old = address;
  address = rewrite_address(address, is_recipient, FALSE,
    global_rewrite_rules, rewrite_existflags);
  if (address != old)
    {
    for (i = 0; i < (MAX_NAMED_LIST * 2)/32; i++) vaddr->localpart_cache[i] = 0;
    for (i = 0; i < (MAX_NAMED_LIST * 2)/32; i++) vaddr->domain_cache[i] = 0;
    if (f != NULL && !expn) fprintf(f, "Address rewritten as: %s\n", address);
    }
  }

/* Update the address structure with the possibly qualified and rewritten
address. Set it up as the starting address on the chain of new addresses. */

vaddr->address = address;
addr_new = vaddr;

/* We need a loop, because an address can generate new addresses. We must also
cope with generated pipes and files at the top level. (See also the code and
comment in deliver.c.) However, it is usually the case that the router for
user's .forward files has its verify flag turned off.

If an address generates more than one child, the loop is used only when
full_info is set, and this can only be set locally. Remote enquiries just get
information about the top level address, not anything that it generated. */

while (addr_new != NULL)
  {
  int rc;
  address_item *addr = addr_new;

  addr_new = addr->next;
  addr->next = NULL;

  DEBUG(D_verify)
    {
    debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
    debug_printf("Considering %s\n", addr->address);
    }

  /* Handle generated pipe, file or reply addresses. We don't get these
  when handling EXPN, as it does only one level of expansion. */

  if (testflag(addr, af_pfr))
    {
    allok = FALSE;
    if (f != NULL)
      {
      BOOL allow;

      if (addr->address[0] == '>')
        {
        allow = testflag(addr, af_allow_reply);
        fprintf(f, "%s -> mail %s", addr->parent->address, addr->address + 1);
        }
      else
        {
        allow = (addr->address[0] == '|')?
          testflag(addr, af_allow_pipe) : testflag(addr, af_allow_file);
        fprintf(f, "%s -> %s", addr->parent->address, addr->address);
        }

      if (addr->basic_errno == ERRNO_BADTRANSPORT)
        fprintf(f, "\n*** Error in setting up pipe, file, or autoreply:\n"
          "%s\n", addr->message);
      else if (allow)
        fprintf(f, "\n  transport = %s\n", addr->transport->name);
      else
        fprintf(f, " *** forbidden ***\n");
      }
    continue;
    }

  /* Split the address into domain and local part, handling the %-hack if
  necessary, and then route it. */

  if ((rc = deliver_split_address(addr)) == OK)
    rc = route_address(addr, &addr_local, &addr_remote, &addr_new,
      &addr_succeed, verify_type);

  /* If routing an address succeeded and set up a list of hosts, and the
  callout option is set, and we aren't in a host checking run, do the callout
  verification. */

  if (rc == OK && addr->host_list != NULL && callout > 0)
    {
    HDEBUG(D_verify) debug_printf("Attempting full verification using callout\n");
    if (host_checking)
      {
      HDEBUG(D_verify) debug_printf("... callout omitted when host testing\n");
      }
    else rc = do_callout(addr, callout, options);
    }

  /* A router may return REROUTED if it has set up a child address as a result
  of a change of domain name (typically from widening). In this case we always
  want to continue to verify the new child. */

  if (rc == REROUTED) continue;

  /* Handle hard failures */

  if (rc == FAIL)
    {
    allok = FALSE;
    if (f != NULL)
      {
      fprintf(f, "%s%s %s", ko_prefix, address,
        address_test_mode? "is undeliverable" : "failed to verify");
      if (!expn && admin_user)
        {
        if (addr->basic_errno > 0)
          fprintf(f, ": %s", strerror(addr->basic_errno));
        if (addr->message != NULL)
          fprintf(f, ":\n  %s", addr->message);
        }
      fprintf(f, "%s\n", cr);
      }

    if (!full_info) return copy_error(vaddr, addr, FAIL);
      else yield = FAIL;
    }

  /* Soft failure */

  else if (rc == DEFER)
    {
    allok = FALSE;
    if (f != NULL)
      {
      fprintf(f, "%s%s cannot be resolved at this time", ko_prefix, address);
      if (!expn && admin_user)
        {
        if (addr->basic_errno > 0)
          fprintf(f, ":\n  %s", strerror(addr->basic_errno));
        if (addr->message != NULL)
          fprintf(f, ":\n  %s", addr->message);
        else if (addr->basic_errno <= 0)
          fprintf(f, ":\n  unknown error");
        }

      fprintf(f, "%s\n", cr);
      }
    if (!full_info) return copy_error(vaddr, addr, DEFER);
      else if (yield == OK) yield = DEFER;
    }

  /* If we are handling EXPN, we do not want to continue to route beyond
  the top level. */

  else if (expn)
    {
    uschar *ok_prefix = US"250-";
    if (addr_new == NULL)
      {
      if (addr_local == NULL && addr_remote == NULL)
        fprintf(f, "250 mail to <%s> is discarded\r\n", address);
      else
        fprintf(f, "250 <%s>\r\n", address);
      }
    else while (addr_new != NULL)
      {
      address_item *addr2 = addr_new;
      addr_new = addr2->next;
      if (addr_new == NULL) ok_prefix = US"250 ";
      fprintf(f, "%s<%s>\r\n", ok_prefix, addr2->address);
      }
    return OK;
    }

  /* Successful routing other than EXPN. */

  else
    {
    /* Handle successful routing when short info wanted. Otherwise continue for
    other (generated) addresses.

    There is a conflict between the use of aliasing as an alternate email
    address, and as a sort of mailing list. If an alias turns the incoming
    address into just one address (e.g. J.Caesar->jc44) you may well want to
    carry on verifying the generated address to ensure it is valid when
    checking incoming mail. If aliasing generates multiple addresses, you
    probably don't want to do this. Exim therefore treats the generation of
    just a single new address as a special case, and continues on to verify the
    generated address. */

    if (!full_info &&                    /* Stop if short info wanted AND */
         (addr_new == NULL ||            /* No new address OR */
          addr_new->next != NULL ||      /* More than one new address OR */
          testflag(addr_new, af_pfr)))   /* New address is pfr */
      {
      if (f != NULL) fprintf(f, "%s %s\n", address,
        address_test_mode? "is deliverable" : "verified");
      return OK;
      }
    }
  }     /* Loop for generated addresses */

/* Display the full results of the successful routing, including any generated
addresses. Control gets here only when full_info is set, which requires f not
to be NULL, and this occurs only when a top-level verify is called with the
debugging switch on.

If there are no local and no remote addresses, and there were no pipes, files,
or autoreplies, and there were no errors or deferments, the message is to be
discarded, usually because of the use of :blackhole: in an alias file. */

if (allok && addr_local == NULL && addr_remote == NULL)
  fprintf(f, "mail to %s is discarded\n", address);

else for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++)
  {
  while (addr_list != NULL)
    {
    address_item *addr = addr_list;
    address_item *p = addr->parent;
    addr_list = addr->next;

    fprintf(f, "%s", addr->address);
    while (p != NULL)
      {
      fprintf(f, "\n    <-- %s", p->address);
      p = p->parent;
      }
    fprintf(f, "\n  ");

    /* Show router, and transport */

    fprintf(f, "router = %s, ", addr->router->name);
    fprintf(f, "transport = %s\n", (addr->transport == NULL)? US"unset" :
      addr->transport->name);

    /* Show any hosts that are set up by a router unless the transport
    is going to override them; fiddle a bit to get a nice format. */

    if (addr->host_list != NULL && addr->transport != NULL &&
        !addr->transport->overrides_hosts)
      {
      host_item *h;
      int maxlen = 0;
      int maxaddlen = 0;
      for (h = addr->host_list; h != NULL; h = h->next)
        {
        int len = Ustrlen(h->name);
        if (len > maxlen) maxlen = len;
        len = (h->address != NULL)? Ustrlen(h->address) : 7;
        if (len > maxaddlen) maxaddlen = len;
        }
      for (h = addr->host_list; h != NULL; h = h->next)
        {
        int len = Ustrlen(h->name);
        fprintf(f, "  host %s ", h->name);
        while (len++ < maxlen) fprintf(f, " ");
        if (h->address != NULL)
          {
          fprintf(f, "[%s] ", h->address);
          len = Ustrlen(h->address);
          }
        else if (!addr->transport->info->local)  /* Omit [unknown] for local */
          {
          fprintf(f, "[unknown] ");
          len = 7;
          }
        else len = -3;
        while (len++ < maxaddlen) fprintf(f," ");
        if (h->mx >= 0) fprintf(f, "MX=%d", h->mx);
        if (h->status == hstatus_unusable) fprintf(f, " ** unusable **");
        fprintf(f, "\n");
        }
      }
    }
  }

return yield;  /* Will be DEFER or FAIL if any one address has */
}




/*************************************************
*      Check headers for syntax errors           *
*************************************************/

/* This function checks those header lines that contain addresses, and verifies
that all the addresses therein are syntactially correct.

Arguments:
  msgptr     where to put an error message

Returns:     OK
             FAIL
*/

int
verify_check_headers(uschar **msgptr)
{
header_line *h;
uschar *colon, *s;

for (h = header_list; h != NULL; h = h->next)
  {
  if (h->type != htype_from &&
      h->type != htype_reply_to &&
      h->type != htype_sender &&
      h->type != htype_to &&
      h->type != htype_cc &&
      h->type != htype_bcc)
    continue;

  colon = Ustrchr(h->text, ':');
  s = colon + 1;
  while (isspace(*s)) s++;

  parse_allow_group = TRUE;     /* Allow group syntax */

  /* Loop for multiple addresses in the header */

  while (*s != 0)
    {
    uschar *ss = parse_find_address_end(s, FALSE);
    uschar *recipient, *errmess;
    int terminator = *ss;
    int start, end, domain;

    /* Temporarily terminate the string at this point, and extract the
    operative address within. */

    *ss = 0;
    recipient = parse_extract_address(s,&errmess,&start,&end,&domain,FALSE);
    *ss = terminator;

    if (recipient == NULL && Ustrcmp(errmess, "empty address") != 0)
      {
      uschar hname[64];
      uschar *t = h->text;
      uschar *tt = hname;
      uschar *verb = US"is";
      int len;

      while (*t != ':') *tt++ = *t++;
      *tt = 0;

      /* Arrange not to include any white space at the end in the
      error message. */

      t = ss;
      while (t > s && isspace(t[-1])) t--;

      /* Add the address which failed to the error message, since in a
      header with very many addresses it is sometimes hard to spot
      which one is at fault. However, limit the amount of address to
      quote - cases have been seen where, for example, a missing double
      quote in a humungous To: header creates an "address" that is longer
      than string_sprintf can handle. */

      len = t - s;
      if (len > 1024)
        {
        len = 1024;
        verb = US"begins";
        }

      *msgptr = string_printing(
        string_sprintf("%s: failing address in \"%.*s\" header %s: %.*s",
          errmess, colon - h->text, h->text, verb, len, s));

      return FAIL;
      }

    /* Advance to the next address */

    s = ss + (terminator? 1:0);
    while (isspace(*s)) s++;
    }   /* Next address */
  }     /* Next header */

return OK;
}




/*************************************************
*          Find if verified sender               *
*************************************************/

/* Usually, just a single address is verified as the sender of the message.
However, Exim can be made to verify other addresses as well (often related in
some way), and this is useful in some environments. There may therefore be a
chain of such addresses that have previously been tested. This function finds
whether a given address is on the chain.

Arguments:   the address to be verified
Returns:     pointer to an address item, or NULL
*/

address_item *
verify_checked_sender(uschar *sender)
{
address_item *addr;
for (addr = sender_verified_list; addr != NULL; addr = addr->next)
  if (Ustrcmp(sender, addr->address) == 0) break;
return addr;
}





/*************************************************
*             Get valid header address           *
*************************************************/

/* Scan the originator headers of the message, looking for an address that
verifies successfully. RFC 822 says:

    o   The "Sender" field mailbox should be sent  notices  of
        any  problems in transport or delivery of the original
        messages.  If there is no  "Sender"  field,  then  the
        "From" field mailbox should be used.

    o   If the "Reply-To" field exists, then the reply  should
        go to the addresses indicated in that field and not to
        the address(es) indicated in the "From" field.

So we check a Sender field if there is one, else a Reply_to field, else a From
field. As some strange messages may have more than one of these fields,
especially if they are resent- fields, check all of them if there is more than
one.

Arguments:
  user_msgptr   points to where to put a user error message
  log_msgptr    points to where to put a log error message
  callout       timeout for callout check (passed to verify_address())

If log_msgptr is set to something without setting user_msgptr, the caller
normally uses log_msgptr for both things.

Returns:        result of the verification attempt: OK, FAIL, or DEFER;
                FAIL is given if no appropriate headers are found
*/

int
verify_check_header_address(uschar **user_msgptr, uschar **log_msgptr,
  int callout)
{
static int header_types[] = { htype_sender, htype_reply_to, htype_from };
int yield = FAIL;
int i;

for (i = 0; i < 3; i++)
  {
  header_line *h;
  for (h = header_list; h != NULL; h = h->next)
    {
    int terminator, new_ok;
    uschar *s, *ss, *endname;

    if (h->type != header_types[i]) continue;
    s = endname = Ustrchr(h->text, ':') + 1;

    while (*s != 0)
      {
      address_item *vaddr;

      while (isspace(*s) || *s == ',') s++;
      if (*s == 0) break;        /* End of header */

      ss = parse_find_address_end(s, FALSE);

      /* The terminator is a comma or end of header, but there may be white
      space preceding it (including newline for the last address). Move back
      past any white space so we can check against any cached envelope sender
      address verifications. */

      while (isspace(ss[-1])) ss--;
      terminator = *ss;
      *ss = 0;

      HDEBUG(D_verify) debug_printf("verifying %.*s header address %s\n",
        endname - h->text, h->text, s);

      /* See if we have already verified this address as an envelope sender,
      and if so, use the previous answer. */

      vaddr = verify_checked_sender(s);

      if (vaddr != NULL &&                   /* Previously checked */
           (callout <= 0 ||                  /* No callout needed; OR */
            vaddr->special_action > 256))    /* Callout was done */
        {
        new_ok = vaddr->special_action & 255;
        HDEBUG(D_verify) debug_printf("previously checked as envelope sender\n");
        *ss = terminator;  /* Restore shortened string */
        }

      /* Otherwise we run the verification now. We must restore the shortened
      string before running the verification, so the headers are correct, in
      case there is any rewriting. */

      else
        {
        int start, end, domain;
        uschar *address = parse_extract_address(s, log_msgptr, &start,
          &end, &domain, FALSE);

        *ss = terminator;

        /* If verification failed because of a syntax error, fail this
        function, and ensure that the failing address gets added to the error
        message. */

        if (address == NULL)
          {
          new_ok = FAIL;
          if (*log_msgptr != NULL)
            {
            while (ss > s && isspace(ss[-1])) ss--;
            *log_msgptr = string_sprintf("syntax error in '%.*s' header when "
              "scanning for sender: %s in \"%.*s\"",
              endname - h->text, h->text, *log_msgptr, ss - s, s);
            return FAIL;
            }
          }

        /* Else go ahead with the sender verification. */

        else
          {
          vaddr = deliver_make_addr(address, FALSE);
          new_ok = verify_address(vaddr, NULL, 0, callout);
          }
        }

      /* We now have the result, either newly found, or cached. If we are
      giving out error details, set a specific user error. This means that the
      last of these will be returned to the user if all three fail. We do not
      set a log message - the generic one below will be used. */

      if (new_ok != OK && smtp_return_error_details)
        {
        *user_msgptr = string_sprintf("Rejected after DATA: "
          "could not verify \"%.*s\" header address\n%s: %s",
          endname - h->text, h->text, vaddr->address, vaddr->message);
        }

      /* Success or defer */

      if (new_ok == OK) return OK;
      if (new_ok == DEFER) yield = DEFER;

      /* Move on to any more addresses in the header */

      s = ss;
      }
    }
  }

if (yield == FAIL && *log_msgptr == NULL)
  *log_msgptr = US"there is no valid sender in any header line";

if (yield == DEFER && *log_msgptr == NULL)
  *log_msgptr = US"all attempts to verify a sender in a header line deferred";

return yield;
}




/*************************************************
*            Get RFC 1413 identification         *
*************************************************/

/* Attempt to get an id from the sending machine via the RFC 1413 protocol. If
the timeout is set to zero, then the query is not done. There may also be lists
of hosts and nets which are exempt. To guard against malefactors sending
non-printing characters which could, for example, disrupt a message's headers,
make sure the string consists of printing characters only.

Argument:
  port    the port to connect to; usually this is IDENT_PORT (113), but when
          running in the test harness with -bh a different value is used.

Returns:  nothing

Side effect: any received ident value is put in sender_ident (NULL otherwise)
*/

void
verify_get_ident(int port)
{
int sock, host_af, qlen;
int received_sender_port, received_interface_port, n;
uschar *p;
uschar buffer[2048];

/* Default is no ident. Check whether we want to do an ident check for this
host. */

sender_ident = NULL;
if (rfc1413_query_timeout <= 0 || verify_check_host(&rfc1413_hosts) != OK)
  return;

DEBUG(D_ident) debug_printf("doing ident callback\n");

/* Set up a connection to the ident port of the remote host. Bind the local end
to the incoming interface address. If the sender host address is an IPv6
address, the incoming interface address will also be IPv6. */

host_af = (Ustrchr(sender_host_address, ':') == NULL)? AF_INET : AF_INET6;
sock = ip_socket(SOCK_STREAM, host_af);
if (sock < 0) return;

if (ip_bind(sock, host_af, interface_address, 0) < 0)
  {
  DEBUG(D_ident) debug_printf("bind socket for ident failed: %s\n",
    strerror(errno));
  goto END_OFF;
  }

if (ip_connect(sock, host_af, sender_host_address, port, rfc1413_query_timeout)
     < 0)
  {
  DEBUG(D_ident) debug_printf("ident connection to %s failed: %s\n",
    sender_host_address, strerror(errno));
  goto END_OFF;
  }

/* Construct and send the query. */

sprintf(CS buffer, "%d , %d\r\n", sender_host_port, interface_port);
qlen = Ustrlen(buffer);
if (send(sock, buffer, qlen, 0) < 0)
  {
  DEBUG(D_ident) debug_printf("ident send failed: %s\n", strerror(errno));
  goto END_OFF;
  }

/* Read a response line. We put it into the rest of the buffer, using several
recv() calls if necessary. */

p = buffer + qlen;

for (;;)
  {
  uschar *pp;
  int count;
  int size = sizeof(buffer) - (p - buffer);

  if (size <= 0) goto END_OFF;   /* Buffer filled without seeing \n. */
  count = ip_recv(sock, p, size, rfc1413_query_timeout);
  if (count <= 0) goto END_OFF;  /* Read error or EOF */

  /* Scan what we just read, to see if we have reached the terminating \r\n. Be
  generous, and accept a plain \n terminator as well. The only illegal
  character is 0. */

  for (pp = p; pp < p + count; pp++)
    {
    if (*pp == 0) goto END_OFF;   /* Zero octet not allowed */
    if (*pp == '\n')
      {
      if (pp[-1] == '\r') pp--;
      *pp = 0;
      goto GOT_DATA;             /* Break out of both loops */
      }
    }

  /* Reached the end of the data without finding \n. Let the loop continue to
  read some more, if there is room. */

  p = pp;
  }

GOT_DATA:

/* We have received a line of data. Check it carefully. It must start with the
same two port numbers that we sent, followed by data as defined by the RFC. For
example,

  12345 , 25 : USERID : UNIX :root

However, the amount of white space may be different to what we sent. In the
"osname" field there may be several sub-fields, comma separated. The data we
actually want to save follows the third colon. Some systems put leading spaces
in it - we discard those. */

if (sscanf(CS buffer + qlen, "%d , %d%n", &received_sender_port,
      &received_interface_port, &n) != 2 ||
    received_sender_port != sender_host_port ||
    received_interface_port != interface_port)
  return;

p = buffer + qlen + n;
while(isspace(*p)) p++;
if (*p++ != ':') return;
while(isspace(*p)) p++;
if (Ustrncmp(p, "USERID", 6) != 0) return;
p += 6;
while(isspace(*p)) p++;
if (*p++ != ':') return;
while (*p != 0 && *p != ':') p++;
if (*p++ == 0) return;
while(isspace(*p)) p++;
if (*p == 0) return;

/* The rest of the line is the data we want. We turn it into printing
characters when we save it, so that it cannot mess up the format of any logging
or Received: lines into which it gets inserted. We keep a maximum of 127
characters. */

sender_ident = string_printing(string_copyn(p, 127));
DEBUG(D_ident) debug_printf("sender_ident = %s\n", sender_ident);

END_OFF:
close(sock);
return;
}




/*************************************************
*      Match host to a single host-list item     *
*************************************************/

/* This function compares a host (name or address) against a single item
from a host list. The host name gets looked up if it is needed and is not
already known. The function is called from verify_check_this_host() via
match_check_list(), which is why most of its arguments are in a single block.

Arguments:
  arg            the argument block (see below)
  ss             the host-list item

The block contains:
  host_name      the host name or NULL, implying use sender_host_name and
                   sender_host_aliases, looking them up if required
  host_address   the host address
  host_ipv4      the IPv4 address taken from an IPv6 one
  valueptr       where to pass back looked up data, or NULL

Returns:         OK      matched
                 FAIL    did not match
                 DEFER   lookup deferred
                 ERROR   failed to find the host name
*/

static int
check_host(void *arg, uschar *ss)
{
check_host_block *cb = (check_host_block *)arg;
int maskoffset;
uschar *semicolon, *t;
uschar **aliases;

/* Optimize for the special case when the pattern is "*". */

if (*ss == '*' && ss[1] == 0) return OK;

/* If the pattern is empty, it matches only in the case when there is no host -
this can occur in ACL checking for SMTP input using the -bs option. In this
situation, the host address is the empty string. */

if (cb->host_address[0] == 0) return (*ss == 0)? OK : FAIL;
if (*ss == 0) return FAIL;

/* If the pattern is precisely "@" then match against the primary host name;
if it's "@[]" match against the local host's IP addresses. */

if (*ss == '@')
  {
  if (ss[1] == 0) ss = primary_hostname;
  else if (Ustrcmp(ss, "@[]") == 0)
    {
    ip_address_item *ip;
    for (ip = host_find_interfaces(); ip != NULL; ip = ip->next)
      if (Ustrcmp(ip->address, cb->host_address) == 0) return OK;
    return FAIL;
    }
  }

/* If the pattern is an IP address, optionally followed by a bitmask count,
mask it and the current IP address, and do a binary comparison on them.
The function string_is_ip_address(), when given a non-NULL second argument,
puts the offset of a '/' in there and clobbers the '/' to a zero. */

if (string_is_ip_address(ss, &maskoffset))
  {
  int i;
  int address[4];
  int incoming[4];
  int mlen;
  int size = host_aton(ss, address);
  int insize;

  /* No mask => all bits to be checked */

  if (maskoffset == 0) mlen = 99999;    /* Big number */
  else
    {
    mlen = Uatoi(ss + maskoffset + 1);
    ss[maskoffset] = '/';    /* restore the slash */
    }

  /* Convert the incoming address to binary. */

  insize = host_aton(cb->host_address, incoming);

  /* Convert IPv4 addresses given in IPv6 compatible mode, which represent
  connections from IPv4 hosts to IPv6 hosts, that is, addresses of the form
  ::ffff:<v4address>, to IPv4 format. */

  if (insize == 4 && incoming[0] == 0 && incoming[1] == 0 &&
      incoming[2] == 0xffff)
    {
    insize = 1;
    incoming[0] = incoming[3];
    }

  /* No match if the sizes don't agree. */

  if (insize != size) return FAIL;

  /* Else do the masked comparison. */

  for (i = 0; i < size; i++)
    {
    int mask;
    if (mlen == 0) mask = 0;
    else if (mlen < 32)
      {
      mask = (-1) << (32 - mlen);
      mlen = 0;
      }
    else
      {
      mask = -1;
      mlen -= 32;
      }
    if ((incoming[i] & mask) != (address[i] & mask)) return FAIL;
    }
  return OK;
  }

/* If the item is of the form net24-dbm;/some/file then it is a file lookup
on a masked IP network, in textual form. */

if (Ustrncmp(ss, "net", 3) == 0 && (semicolon = Ustrchr(ss, ';')) != NULL)
  {
  int mlen = 0;
  for (t = ss + 3; isdigit(*t); t++) mlen = mlen * 10 + *t - '0';
  if (*t++ == '-')
    {
    int insize;
    int search_type;
    int incoming[4];
    void *handle;
    uschar *filename, *key, *result;
    uschar buffer[64];

    /* If no mask was supplied, set a negative value */

    if (mlen == 0 && t == ss+4) mlen = -1;

    /* Find the search type */

    search_type = search_findtype(t, semicolon - t);

    if (search_type < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s",
      search_error_message);

    /* Adjust parameters for the type of lookup. For a query-style
    lookup, there is no file name, and the "key" is just the query. For
    a single-key lookup, the key is the current IP address, masked
    appropriately, and reconverted to text form, with the mask appended. */

    if (mac_islookup(search_type, lookup_querystyle))
      {
      filename = NULL;
      key = semicolon + 1;
      }
    else
      {
      insize = host_aton(cb->host_address, incoming);
      host_mask(insize, incoming, mlen);
      (void)host_nmtoa(insize, incoming, mlen, buffer);
      key = buffer;
      filename = semicolon + 1;
      }

    /* Now do the actual lookup; note that there is no search_close() because
    of the caching arrangements. */

    handle = search_open(filename, search_type, 0, NULL, NULL);
    if (handle == NULL) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s",
      search_error_message);
    result = search_find(handle, filename, key, -1, NULL, 0, 0, NULL);
    if (cb->valueptr != NULL) *(cb->valueptr) = result;
    return (result != NULL)? OK : search_find_defer? DEFER: FAIL;
    }
  }

/* The pattern is not an IP address or network reference of any kind. That is,
it is a host name pattern. Check the characters of the pattern to see if they
comprise only letters, digits, full stops, and hyphens (the constituents of
domain names). Allow underscores, as they are all too commonly found. Sigh. */

for (t = ss; *t != 0; t++)
  if (!isalnum(*t) && *t != '.' && *t != '-' && *t != '_') break;

/* If the pattern is a complete domain name, with no fancy characters, look up
its IP address and match against that. Note that a multi-homed host will add
items to the chain. */

if (*t == 0)
  {
  int rc;
  host_item h;
  h.next = NULL;
  h.name = ss;
  h.address = NULL;
  h.mx = MX_NONE;
  rc = host_find_byname(&h, NULL, NULL, FALSE);
  if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL)
    {
    host_item *hh;
    for (hh = &h; hh != NULL; hh = hh->next)
      {
      if (Ustrcmp(hh->address, (Ustrchr(hh->address, ':') == NULL)?
        cb->host_ipv4 : cb->host_address) == 0)
          return OK;
      }
    }
  return (rc == HOST_FIND_AGAIN)? DEFER : FAIL;
  }

/* All subsequent comparisons require the host name, and can be done using the
general string matching function. When this function is called for outgoing
hosts, the name is always given explicitly. If it is NULL, it means we must use
sender_host_name and its aliases, looking them up if necessary. */

if (cb->host_name != NULL)   /* Explicit host name given */
  return match_check_string(cb->host_name, ss, -1, TRUE, TRUE, TRUE,
    cb->valueptr);

/* Host name not given; use sender host and its aliases */

if (sender_host_name == NULL)
  {
  HDEBUG(D_host_lookup)
    debug_printf("sender host name required, to match against %s\n", ss);
  if (host_lookup_failed || host_name_lookup() != OK) return ERROR;
  host_build_sender_fullhost();
  }

/* Match on the sender host name, using the general matching function */

switch(match_check_string(sender_host_name, ss, -1, TRUE, TRUE, TRUE,
       cb->valueptr))
  {
  case OK:    return OK;
  case DEFER: return DEFER;
  }

/* If there are aliases, try matching on them. */

aliases = sender_host_aliases;
while (*aliases != NULL)
  {
  switch(match_check_string(*aliases++, ss, -1, TRUE, TRUE, TRUE, cb->valueptr))
    {
    case OK:    return OK;
    case DEFER: return DEFER;
    }
  }
return FAIL;
}




/*************************************************
*    Check a specific host matches a host list   *
*************************************************/

/* This function is passed a host list containing items in a number of
different formats and the identity of a host. Its job is to determine whether
the given host is in the set of hosts defined by the list. The host name is
passed as a pointer so that it can be looked up if needed and not already
known. This is commonly the case when called from verify_check_host() to check
an incoming connection. When called from elsewhere the host name should usually
be set.

This function is now just a front end to match_check_list(), which runs common
code for scanning a list. We pass it the check_host() function to perform a
single test.

Arguments:
  listptr              pointer to the host list
  cache_bits           pointer to cache for named lists, or NULL
  host_name            the host name or NULL, implying use sender_host_name and
                         sender_host_aliases, looking them up if required
  host_address         the IP address
  valueptr             if not NULL, data from a lookup is passed back here

Returns:    OK    if the host is in the defined set
            FAIL  if the host is not in the defined set,
            DEFER if a data lookup deferred (not a host lookup)

If the host name was needed in order to make a comparison, and could not be
determined from the IP address, the result is FAIL unless the item
"+allow_unknown" was met earlier in the list, in which case OK is returned. */

int
verify_check_this_host(uschar **listptr, unsigned int *cache_bits,
  uschar *host_name, uschar *host_address, uschar **valueptr)
{
unsigned int *local_cache_bits = cache_bits;
check_host_block cb;
cb.host_name = host_name;
cb.host_address = host_address;
cb.valueptr = valueptr;

/* If the host address starts off ::ffff: it is an IPv6 address in
IPv4-compatible mode. Find the IPv4 part for checking against IPv4
addresses. */

cb.host_ipv4 = (Ustrncmp(host_address, "::ffff:", 7) == 0)?
  host_address + 7 : host_address;

return match_check_list(listptr, 0, &hostlist_anchor, &local_cache_bits,
  check_host, &cb, MCL_HOST,
  (host_address == sender_host_address)? US"host" : host_address);
}




/*************************************************
*      Check the remote host matches a list      *
*************************************************/

/* This is a front end to verify_check_this_host(), created because checking
the remote host is a common occurrence. With luck, a good compiler will spot
the tail recursion and optimize it. If there's no host address, this is
command-line SMTP input - check against an empty string for the address.

Arguments:
  listptr              pointer to the host list

Returns:               the yield of verify_check_this_host(),
                       i.e. OK, FAIL, or DEFER
*/

int
verify_check_host(uschar **listptr)
{
return verify_check_this_host(listptr, sender_host_cache, NULL,
  (sender_host_address == NULL)? US"" : sender_host_address, NULL);
}





/*************************************************
*    Invert an IP address for a DNS black list   *
*************************************************/

/*
Arguments:
  buffer         where to put the answer
  address        the address to invert
*/

static void
invert_address(uschar *buffer, uschar *address)
{
int bin[4];
uschar *bptr = buffer;

/* If this is an IPv4 address mapped into IPv6 format, adjust the pointer
to the IPv4 part only. */

if (Ustrncmp(address, "::ffff:", 7) == 0) address += 7;

/* Handle IPv4 address: when HAVE_IPV6 is false, the result of host_aton() is
always 1. */

if (host_aton(address, bin) == 1)
  {
  int i;
  int x = bin[0];
  for (i = 0; i < 4; i++)
    {
    sprintf(CS bptr, "%d.", x & 255);
    while (*bptr) bptr++;
    x >>= 8;
    }
  }

/* Handle IPv6 address. Actually, as far as I know, there are no IPv6 addresses
in any DNS black lists, and the format in which they will be looked up is
unknown. This is just a guess. */

#if HAVE_IPV6
else
  {
  int i, j;
  for (j = 3; j >= 0; j--)
    {
    int x = bin[j];
    for (i = 0; i < 8; i++)
      {
      sprintf(CS bptr, "%x.", x & 15);
      while (*bptr) bptr++;
      x >>= 4;
      }
    }
  }
#endif
}



/*************************************************
*        Check host against DNS black lists      *
*************************************************/

/* This function runs checks against a list of DNS black lists, until one
matches. Each item on the list can be of the form

  domain=ip-address/key

The domain is the right-most domain that is used for the query, for example,
blackholes.mail-abuse.org. If the IP address is present, there is a match only
if the DNS lookup returns a matching IP address. Several addresses may be
given, comma-separated, for example: x.y.z=127.0.0.1,127.0.0.2.

If no key is given, what is looked up in the domain is the inverted IP address
of the current client host. If a key is given, it is used to construct the
domain for the lookup. For example,

  dsn.rfc-ignorant.org/$sender_address_domain

After finding a match in the DNS, the domain is placed in $dnslist_domain, and
then we check for a TXT record for an error message, and if found, save its
value in $dnslist_text. We also cache everything in a tree, to optimize
multiple lookups.

Note: an address for testing RBL is 192.203.178.39
Note: an address for testing DUL is 192.203.178.4
Note: a domain for testing RFCI is example.tld.dsn.rfc-ignorant.org

Arguments:
  listptr      the domain/address/data list

Returns:    OK      successful lookup (i.e. the address is on the list), or
                      lookup deferred after +include_unknown
            FAIL    name not found, or no data found for the given type, or
                      lookup deferred after +exclude_uknown (default)
            DEFER   lookup failure, if +defer_unknown was set
*/

int
verify_check_dnsbl(uschar **listptr)
{
int sep = 0;
int defer_return = FAIL;
int old_pool = store_pool;
uschar *list = *listptr;
uschar *domain;
uschar buffer[1024];
uschar query[256];         /* DNS domain max length */
uschar revadd[128];        /* Long enough for IPv6 address */

/* Indicate that the inverted IP address is not yet set up */

revadd[0] = 0;

/* Loop through all the domains supplied, until something matches */

while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
  {
  BOOL frc;
  dns_answer dnsa;
  dns_scan dnss;
  uschar *iplist;
  uschar *key;
  tree_node *t;
  dnsbl_cache_block *cb;

  HDEBUG(D_dnsbl) debug_printf("DNS list check: %s\n", domain);

  /* Deal with special values that change the behaviour on defer */

  if (domain[0] == '+')
    {
    if      (strcmpic(domain, US"+include_unknown") == 0) defer_return = OK;
    else if (strcmpic(domain, US"+exclude_unknown") == 0) defer_return = FAIL;
    else if (strcmpic(domain, US"+defer_unknown") == 0)   defer_return = DEFER;
    else
      log_write(0, LOG_MAIN|LOG_PANIC, "unknown item in dnslist (ignored): %s",
        domain);
    continue;
    }

  /* See if there's explicit data to be looked up */

  key = Ustrchr(domain, '/');
  if (key != NULL) *key++ = 0;

  /* See if there's a list of addresses supplied after the domain name */

  iplist = Ustrchr(domain, '=');
  if (iplist != NULL) *iplist++ = 0;

  /* Construct the query by adding the domain onto either the sending host
  address, or the given key string. */

  if (key == NULL)
    {
    if (sender_host_address == NULL) return FAIL;    /* can never match */
    if (revadd[0] == 0) invert_address(revadd, sender_host_address);
    frc = string_format(query, sizeof(query), "%s%s", revadd, domain);
    }
  else
    {
    frc = string_format(query, sizeof(query), "%s.%s", key, domain);
    }

  if (!frc)
    {
    log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long "
      "(ignored): %s...", query);
    continue;
    }

  /* Look for this query in the cache. */

  t = tree_search(dnsbl_cache, query);

  /* If not cached from a previous lookup, we must do a DNS lookup, and
  cache the result in permanent memory. */

  if (t == NULL)
    {
    store_pool = POOL_PERM;

    /* In case this is the first time the DNS resolver is being used. */

    dns_init(FALSE, FALSE);

    /* Set up a tree entry to cache the lookup */

    t = store_get(sizeof(tree_node) + Ustrlen(query));
    Ustrcpy(t->name, query);
    t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block));
    (void)tree_insertnode(&dnsbl_cache, t);

    /* Do the DNS loopup . */

    HDEBUG(D_dnsbl) debug_printf("new DNS lookup for %s\n", query);
    cb->rc = dns_basic_lookup(&dnsa, query, T_A);
    cb->text_set = FALSE;

    /* If the lookup succeeded, cache the RHS address. The code allows for
    more than one address - this was for complete generality and the possible
    use of A6 records. However, A6 records have been reduced to experimental
    status (August 2001) and may die out. So they may never get used at all,
    let alone in dnsbl records. However, leave the code here, just in case. */

    if (cb->rc == DNS_SUCCEED)
      {
      dns_record *rr;
      for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
	   rr != NULL;
	   rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
	{
        if (rr->type == T_A)
          {
          cb->rhs = dns_address_from_rr(&dnsa, rr);
          break;
          }
	}
      }

    store_pool = old_pool;
    }

  /* Previous lookup was cached */

  else
    {
    HDEBUG(D_dnsbl) debug_printf("using result of previous DNS lookup\n");
    cb = t->data.ptr;
    }

  /* We now have the result of the DNS lookup, either newly done, or cached
  from a previous call. If the lookup succeeded, check against the address
  list. */

  if (cb->rc == DNS_SUCCEED)
    {
    if (iplist != NULL)
      {
      int ipsep = ',';
      uschar ip[46];
      uschar *ptr = iplist;
      dns_address *da = NULL;

      while (string_nextinlist(&ptr, &ipsep, ip, sizeof(ip)) != NULL)
        {
        for (da = cb->rhs; da != NULL; da = da->next)
	  { if (Ustrcmp(CS da->address, ip) == 0) break; }
        if (da != NULL) break;
        }

      /* No IP address matched any of those in the list. In that case,
      behave as if the DNSBL lookup had not succeeded, i.e. the host is
      not on the list. */

      if (da == NULL)
        {
        HDEBUG(D_dnsbl)
	  {
	  debug_printf("DNS lookup for %s suceeded\n", query);
	  debug_printf("=> but we are not accepting this block class because\n");
          debug_printf("=> there was no match for %s\n", iplist);
	  }
        continue;   /* With next DNSBL domain */
        }
      }

    /* Either there was no IP list, or the record matched. Look up a TXT record
    if it hasn't previously been done. */

    if (!cb->text_set)
      {
      cb->text_set = TRUE;
      if (dns_basic_lookup(&dnsa, query, T_TXT) == DNS_SUCCEED)
        {
        dns_record *rr;
        for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
             rr != NULL;
             rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
          if (rr->type == T_TXT) break;
        if (rr != NULL)
          {
          int len = (rr->data)[0];
          if (len > 511) len = 127;
          store_pool = POOL_PERM;
          cb->text = string_sprintf("%.*s", len, (const uschar *)(rr->data+1));
          store_pool = old_pool;
          }
        }
      else cb->text = NULL;
      }

    HDEBUG(D_dnsbl)
      {
      debug_printf("DNS lookup for %s succeeded\n", query);
      debug_printf("=> that means %s is listed at %s\n",
        (key == NULL)? sender_host_address : key, domain);
      }

    dnslist_domain = string_copy(domain);
    dnslist_value = cb->rhs->address;
    dnslist_text = cb->text;
    return OK;
    }

  /* There was a problem with the DNS lookup */

  if (cb->rc != DNS_NOMATCH && cb->rc != DNS_NODATA)
    {
    log_write(L_dnslist_defer, LOG_MAIN,
      "DNS list lookup defer (probably timeout) for %s: %s", query,
      (defer_return == OK)?   US"assumed in list" :
      (defer_return == FAIL)? US"assumed not in list" :
                              US"returned DEFER");
    return defer_return;
    }

  /* No entry was found in the DNS; continue for next domain */

  HDEBUG(D_dnsbl)
    {
    debug_printf("DNS lookup for %s failed\n", query);
    debug_printf("=> that means %s is not listed at %s\n",
      (key == NULL)? sender_host_address : key, domain);
    }
  }      /* Continue with next domain */

return FAIL;
}

/* End of verify.c */
