/*************************************************
*     Exim - an Internet mail transport agent    *
*************************************************/

/* Copyright (c) University of Cambridge 1995 - 1999 */
/* See the file NOTICE for conditions of use and distribution. */

/* Functions concerned with verifying things. */


#include "exim.h"



/*************************************************
*           Local static variables               *
*************************************************/


static char ident_buffer[128];
static BOOL sender_ok;
static BOOL sender_is_local;

/* Values for sender_ok */

#define SENDER_OK_OK          0    /* really and truly OK */
#define SENDER_OK_EXCEPTION   1    /* OK by virtue of the exception list */
#define SENDER_OK_WARNING     2    /* OK by virtue of being in warning mode */
#define SENDER_OK_DEFER       3    /* check didn't complete */
#define SENDER_OK_NOT         4    /* Not OK */



/*************************************************
*           Set up new address                   *
*************************************************/

/* This function is called to set up a new address from what was routed or
directed, so as to include completed domain names, or whatever. If
local_part_quoted is set, the address is local and the local part was quoted in
some way, so we have to re-instate the quoting. We use a flag to control this
to save time doing it on 99.9% of the addresses that don't need it.

Arguments:
  newaddr       points to where to put the new address
  addr          the routed/directed address item

Returns:        nothing
*/

static void
set_new_address(char **newaddress, address_item *addr)
{
char *prefix = (addr->prefix == NULL)? "" : addr->prefix;
char *suffix = (addr->suffix == NULL)? "" : addr->suffix;

if (addr->local_part_quoted)
  {
  int i;
  int size = 64;
  int ptr = 0;
  char *s = store_get(size);
  char *p = prefix;
  s = string_cat(s, &size, &ptr, "\"", 1);
  for (i = 0; i < 3; i++)
    {
    while (*p != 0)
      {
      if (*p == '\"' || *p == '\\')
        s = string_cat(s, &size, &ptr, "\\", 1);
      s = string_cat(s, &size, &ptr, p++, 1);
      }
    p = (i == 0)? addr->local_part : suffix;
    }
  s = string_cat(s, &size, &ptr, "\"@", 2);
  s = string_cat(s, &size, &ptr, addr->domain,
    (int)strlen(addr->domain));
  s[ptr] = 0;
  *newaddress = s;
  }

else
  {
  *newaddress = (addr->local_part[0] == ',' ||
                 addr->local_part[0] == ':')?
     string_sprintf("@%s%s", addr->domain, addr->local_part) :
     string_sprintf("%s%s%s@%s", prefix, addr->local_part,
       suffix, addr->domain);
  }
}



/*************************************************
*            Verify an email address             *
*************************************************/

/* The local flag indicates whether an unqualified address is acceptable or
not. The local_domain flag is set TRUE if the address turns out to be in a
local domain, FALSE if it is in a remote domain. If the file is NULL, don't
print anything. If newaddress is not null, return the address, possibly
modified by the routing process. The yield is OK, FAIL, or DEFER. If there is a
parse error, return FAIL with the parse message in verify_address_parse_error,
which is otherwise set to NULL. If debugging, output fuller information,
provided there is an output file. If log_details is TRUE, write details of
failures to the reject log. If address_test_mode is true, don't skip directors
and routers that have no_verify set - we are doing a test rather than a verify.

Arguments:
  s               address to verify
  f               if not NULL, write the result to this file
  local_domain    if not NULL, set TRUE if address turns out to be in a
                    local domain
  newaddress      if not NULL, return the top-level address, possibly modified
  options         various option bits:
                    vopt_is_recipient => this is a recipient address, otherwise
                      it's a sender address - this affects qualification and
                      rewriting
                    vopt_local => qualify an unqualified address; else error
                    vopt_log_details => write details of failures to the
                      reject log
                    vopt_expn => called from SMTP EXPN command

Returns:          OK
                  FAIL
                  DEFER
*/

int
verify_address(char *s, FILE *f, BOOL *local_domain, char **newaddress,
  int options)
{
int start, end, domain;
BOOL allok = TRUE;
BOOL full_info = (f == NULL)? FALSE : (debug_level > 0);
BOOL is_recipient = (options & vopt_is_recipient) != 0;
BOOL log_details  = (options & vopt_log_details) != 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_orig;
address_item *addr_new;
address_item *addr_remote = NULL;
address_item *addr_local = NULL;
address_item *addr_succeed = NULL;
char *ko_prefix, *cr;

char *receiver =
  parse_extract_address(s, &verify_address_parse_error, &start, &end, &domain,
    FALSE);

if (expn)
  {
  ko_prefix = "553 ";
  cr = "\r";
  }
else ko_prefix = cr = "";

if (receiver == NULL)
  {
  if (f != NULL)
    fprintf(f, "%s%s - bad address: %s%s\n", ko_prefix, s,
      verify_address_parse_error, cr);
  if (log_details)
    log_write(0, LOG_REJECT, "%s - bad address: %s", s,
      verify_address_parse_error);
  return FAIL;
  }
else verify_address_parse_error = NULL;

/* Add qualify domain if permitted. */

if (parse_find_at(receiver) == NULL)
  {
  if ((options & vopt_local) == 0)
    {
    if (f != NULL)
      fprintf(f, "%sA domain is required for \"%s\"%s\n", ko_prefix, receiver,
        cr);
    if (log_details)
      log_write(0, LOG_REJECT, "a domain is required for \"%s\"", receiver);
    return FAIL;
    }
  receiver = rewrite_address_qualify(receiver, is_recipient);
  }

DEBUG(9)
  {
  debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>\n");
  debug_printf("%s %s\n", address_test_mode? "Testing" : "Verifying", receiver);
  }

/* Rewrite and report on it. */

if (rewrite_rules != NULL)
  {
  char *old = receiver;
  receiver = rewrite_address(receiver, is_recipient, FALSE);
  if (receiver != old)
    {
    if (f != NULL && !expn) fprintf(f, "Address rewritten as: %s\n", receiver);
    }
  }

/* Set up an initial address structure. */

addr_new = addr_orig = deliver_make_addr(receiver, FALSE);

/* We need a loop, since a directed address might generate a number of new
addresses. We must also cope with generated pipes and files at the top
level. (See also the code/comment in deliver.c.) However, it is usually
the case that the forwardfile director has its verify flag turned off.

The loop is used after directing, however, only when the verify_actions flag is
set, and this can only be set locally. Remote enquiries just get information
about the top level address, not anything that it generated.

In the case of a router discovering that an apparently remote address is in
fact local, the loop is always re-run. */

while (addr_new != NULL)
  {
  int rc;
  address_item *addr = addr_new;
  addr_new = addr->next;
  addr->next = NULL;

  /* Handle generated pipe, file or reply addresses. We don't get these
  when handling EXPN, as it does only one level of expansion. */

  if (addr->pfr)
    {
    allok = FALSE;
    if (addr->orig[0] == '|')
      {
      if (f != NULL)
        fprintf(f, "%s -> %s %s\n", addr->parent->orig,
          addr->orig, addr->allow_pipe? "" : "*** forbidden ***");
      }
    else if (addr->orig[0] == '/')
      {
      if (f != NULL)
        fprintf(f, "%s -> %s %s\n", addr->parent->orig,
          addr->orig, addr->allow_file? "" : "*** forbidden ***");
      }
    else
      {
      if (f != NULL)
        fprintf(f, "%s -> mail %s %s\n", addr->parent->orig,
          addr->orig+1, addr->allow_reply? "" : "*** forbidden ***");
      }

    if (addr->basic_errno == ERRNO_BADEXTRANSPORT && f != NULL)
      {
      fprintf(f, "*** Error in setting up pipe, file, or autoreply:\n  %s\n",
        addr->message);
      }

    continue;
    }

  /* All addresses should either have been made fully qualified above,
  or been qualified when generated by a director, so panic if we find
  an unqualified one. */

  if (parse_find_at(addr->orig) == NULL)
    log_write(0, LOG_PANIC_DIE, "Unqualified address found: %s", addr->orig);

  /* Determine locality - this sets "local_part", "domain", and "local"
  fields. Pass back the locality if a variable is supplied. */

  deliver_setlocal(addr);

  if (local_domain != NULL) *local_domain = addr->local;

  /* If we are handing an EXPN command and this is the original address,
  and it is not local, we don't proceed any further. (Actually, if it's
  expn, this will always be the original address, since we don't process
  generated addresses any more.) */

  if (expn && addr == addr_orig && !addr->local)
    {
    fprintf(f, "550 Not a local domain\r\n");
    return FALSE;
    }

  /* DEBUG and/or log_details: show what's been done to this address */

  DEBUG(7)
    {
    debug_printf("address %s\n", addr->orig);
    debug_printf("  local_part=%s domain=%s\n  %s\n",
      addr->local_part, addr->domain,
      (addr->local)? "domain is local" : "domain is not local");
    }

  if (log_details)
    log_write(0, LOG_REJECT, "Verifying %s: local part = %s domain = %s "
      "local domain = %s",
      addr->orig, addr->local_part, addr->domain,
      (addr->local)? "true":"false");

  /* Handle a local address with the directors, or a remote address with the
  routers, and output or return the result except when full_info is set, in
  which case continue for other (generated) addresses. When full_info is set,
  f will not be NULL. Don't output anything for success in that case at this
  stage. Note that a director may set up local or remote delivery. */

  verify_forced_errmsg = NULL;

  rc = (addr->local)?
    direct_address(addr, &addr_local, &addr_remote, &addr_new, &addr_succeed,
      verify_type) :
    route_address(addr, &addr_local, &addr_remote, &addr_new, verify_type);

  /* If a director generated a new address as a routing ploy rather than
  as an aliasing or forwarding type of operation, it returns OK_CONTINUE
  instead of OK. This means that verification should always inspect the
  generated addresses, even when short information is required. However,
  we want to pass back any modified top-level address via newaddress,
  not any subsequent one. */

  if (rc == OK_CONTINUE)
    {
    if (!full_info && newaddress != NULL)
      {
      set_new_address(newaddress, addr);
      newaddress = NULL;
      }
    continue;
    }

  /* If a remote address turned out to be local after all, set it up for
  reprocessing and restart the loop. */

  if (rc == ISLOCAL)
    {
    if (log_details)
      log_write(0, LOG_REJECT, "routing %s caused it to become local",
        addr->orig);
    if (addr->local_part[0] == ',' || addr->local_part[0] == ':')
      addr->orig = string_sprintf("@%s%s", addr->domain, addr->local_part);
    else
      addr->orig = string_sprintf("%s@%s", addr->local_part, addr->domain);
    addr->host_list = NULL;
    addr->next = addr_new;
    addr_new = addr;
    continue;
    }

  /* Handle hard failures */

  if (rc == FAIL)
    {
    allok = FALSE;
    if (f != NULL) fprintf(f, "%s%s %s:%s%s%s%s%s\n", ko_prefix,
      addr->orig,
      address_test_mode? "is undeliverable" : "failed to verify",
      (addr->basic_errno <= 0)? "" : strerror(addr->basic_errno),
      (addr->basic_errno <= 0)? "" : ": ",
      expn? " " : "\n  ",
      (addr->message != NULL)? addr->message :
        (addr->basic_errno <= 0)? "unknown error" : "", cr);

    if (log_details) log_write(0, LOG_REJECT,
      "%s is undeliverable: %s%s%s", addr->orig,
      (addr->basic_errno <= 0)? "" : strerror(addr->basic_errno),
      (addr->basic_errno <= 0)? "" : ": ",
      (addr->message != NULL)? addr->message :
        (addr->basic_errno <= 0)? "unknown error" : "");

    if (!full_info) return FAIL; else yield = FAIL;
    }

  /* If the yield is ERROR or PANIC, there has been some cock-up in the
  directors or routers. This doesn't really mean the address is undeliverable
  or unverifyable, so we treat it the same as DEFER. */

  else if (rc == DEFER || rc == ERROR || rc == PANIC)
    {
    allok = FALSE;
    if (f != NULL)
      fprintf(f, "%s%s cannot be resolved at this time:%s%s%s%s%s\n",
      ko_prefix,
      addr->orig,
      expn? " " : "\n  ",
      (addr->basic_errno <= 0)? "" : strerror(addr->basic_errno),
      (addr->basic_errno <= 0)? "" : ": ",
      (addr->message != NULL)? addr->message :
        (addr->basic_errno <= 0)? "unknown error" : "", cr);

    if (log_details)
      log_write(0, LOG_REJECT, "%s cannot be resolved at this time: %s%s%s",
      addr->orig,
      (addr->basic_errno <= 0)? "" : strerror(addr->basic_errno),
      (addr->basic_errno <= 0)? "" : ": ",
      (addr->message != NULL)? addr->message :
        (addr->basic_errno <= 0)? "unknown error" : "");

    if (!full_info) return DEFER;
      else if (yield == OK) yield = DEFER;
    }

  /* If we are handling EXPN, we do not want to continue to route beyond
  the top level. Output what has been generated. */

  else if (expn)
    {
    char *ok_prefix = "250-";
    if (addr_new == NULL)
      {
      if (addr_local == NULL)
        fprintf(f, "250 mail to <%s> is discarded\r\n", s);
      else
        fprintf(f, "250 <%s>\r\n", addr_local->orig);
      }
    else while (addr_new != NULL)
      {
      address_item *addr = addr_new;
      addr_new = addr->next;
      if (addr_new == NULL) ok_prefix = "250 ";
      fprintf(f, "%s<%s>\r\n", ok_prefix, addr->orig);
      }
    return OK;
    }

  /* Handle successful routing or directing when short info wanted */

  else if (!full_info)
    {
    if (f != NULL) fprintf(f, "%s %s\n",
      addr->orig,
      address_test_mode? "is deliverable" : "verified");

    /* Pass back the routed/directed address if requested (so as to get
    completed domain names or whatever). */

    if (newaddress != NULL) set_new_address(newaddress, addr);
    return OK;
    }

  }

/* Display the full results of the successful directing and 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, or as a result of an SMTP EXPN
command (permitted only for specified hosts/nets). For verification, if the
debugging value is greater than 1, show everything; for 1 (which is also -v)
show what "the normal user" might be interested in. For address testing,
always show everything.

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", s);

full_info = debug_level > 1 || address_test_mode;

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->orig);

    while (p != NULL)
      {
      fprintf(f, "\n    <-- %s", p->orig);
      p = p->parent;
      }

    /* Show director and/or router, and transport */

    if (addr->router != NULL)
      {
      if (addr->local_part[0] == ',' || addr->local_part[0] == ':')
        fprintf(f, "\n  deliver to @%s%s", addr->domain,
        addr->local_part);
      else
        fprintf(f, "\n  deliver to %s@%s", addr->local_part,
        addr->domain);

      if (strcmp(addr->domain, addr->route_domain) != 0)
        fprintf(f, " via domain %s", addr->route_domain);
      fprintf(f, "\n");

      if (full_info)
        {
        fprintf(f, "  ");
        if (addr->director != NULL)
          fprintf(f, "director = %s, ", addr->director->name);
        fprintf(f, "router = %s, ", addr->router->name);
        }
      }

    else if (addr->director != NULL)
      {
      fprintf(f, "\n  deliver to %s in domain %s\n",
        addr->local_part, addr->domain);
      if (full_info) fprintf(f, "  director = %s, ", addr->director->name);
      }

    if (full_info)
      fprintf(f, "transport = %s\n", (addr->transport == NULL)? "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 = (int)strlen(h->name);
        if (len > maxlen) maxlen = len;
        len = (h->address != NULL)? (int)strlen(h->address) : 7;
        if (len > maxaddlen) maxaddlen = len;
        }
      for (h = addr->host_list; h != NULL; h = h->next)
        {
        int len = (int)strlen(h->name);
        fprintf(f, "  host %s ", h->name);
        while (len++ < maxlen) fprintf(f, " ");
        if (h->address != NULL)
          {
          fprintf(f, "[%s] ", h->address);
          len = (int)strlen(h->address);
          }
        else
          {
          fprintf(f, "[unknown] ");
          len = 7;
          }
        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 has */
}





/*************************************************
*          Setup host list matching data         *
*************************************************/

/* This function is called for a number of parameters containing lists
of strings of the form [ident@]host, to scan the strings it contains and build
a chain of control blocks of the broken-down or compiled data for subsequent
use. If the host is precisely "@" it refers to the primary hostname. The daemon
calls this at its start if it is listening for SMTP calls. It is also called
from verify_check_host for other kinds of call. The list is then re-used if
necessary, and for that reason, any store that it gets must be got by malloc.

Arguments:
  list      colon-separated list of items
  anchor    where to chain the created blocks

Returns:    nothing
*/

void
verify_setup_hostlist(char *list, host_item **anchor)
{
char *s, *t;
char buffer[1024];

DEBUG(2) debug_printf("verify_setup_hostlist called\n");

for (s = string_nextinlist(&list, ':', buffer, sizeof(buffer));
     s != NULL;
     s = string_nextinlist(&list, ':', buffer, sizeof(buffer)))
  {
  host_item *h = store_malloc(sizeof(host_item));
  h->next = NULL;
  *anchor = h;
  anchor = &(h->next);

  /* If there's an '@' in the string that is not the last character, it starts
  with an ident string; carve that off. */

  if ((t = strchr(s, '@')) != NULL && t[1] != 0)
    {
    *t = 0;
    h->ident_string = string_copy_malloc(s);
    s = t+1;
    }
  else h->ident_string = NULL;

  /* If the host name string is precisely "@", then substitute the primary
  host name. */

  h->name = string_copy_malloc((strcmp(s, "@") == 0)? primary_hostname : s);
  h->compiled_name = NULL;
  h->address = NULL;

  /* If the name is of the form of an IP address, we can set the address
  field immediately. */

  if (string_is_ip_address(h->name)) h->address = h->name;

  /* For non-wildcarded names, the IP address used to be looked up here.
  However, this meant that the daemon hung onto the address for an indefinite
  amount of time, which is Considered Harmful. For non-daemon input, it also
  meant that names that weren't actually going to be tested got looked up.
  So we cut this code out (27-Aug-98). Tidy up the source once this has
  established itself in service. */


  #ifdef NEVER
  /* Else if the name is not wildcarded (starting with * or ^) or a file lookup
  (contains ';') then we can look up the IP address(es) once and for all.
  Ignore failure - this will just cause slower reverse lookups to happen later.
  Move the anchor pointer on to the end for multi-homed hosts. */

  else if (s[0] != '*' && s[0] != '^' && strchr(s, ';') == NULL)
    {
    char *fully_qualified_name;
    (void) host_find_byname(h, &fully_qualified_name, TRUE);
    while (*anchor != NULL) anchor = &((*anchor)->next);  /* Multihomed */
    }
  #endif


  }
}




/*************************************************
*         Check host+ident is in list            *
*************************************************/

/* This function is called from a number of places to test whether the current
calling host (plus ident) is in a list of hosts + idents.

Arguments:
  listptr              pointer to host list for checking
  anchor               anchor for broken down data (may already be done if
                         previously run this check)
  find_failed_return   the value to return if the host name has to be found
                         by gethostbyaddr(), and that fails

Returns:     TRUE if current host+ident is in the list;
             FALSE if definitely not in the list;
             find_failed_return if gethostbyaddr() failed,
             or match_check_string() gave a lookup defer
*/

BOOL
verify_check_host(char **listptr, host_item **anchor, BOOL find_failed_return)
{
host_item *h;
char *list = *listptr;
BOOL ffr = find_failed_return;
char *sender_host_ipv4;
char *ot = string_sprintf("host in %s?", readconf_find_option(listptr));

/* If there's no host address, this is command-line SMTP input, and so no
checking can be done. */

if (sender_host_address == NULL) return FALSE;

DEBUG(9)
  {
  debug_printf("checking %s\n", ot);
  debug_printf("  anchor=%d list=%s\n", *anchor, list);
  }

/* If the list is empty, the answer is no. */

if (list == NULL)
  {
  HDEBUG(2) debug_printf("%s no (option unset)\n", ot);
  return FALSE;
  }

/* Initialize the munged data if not already done so (for calls via the
daemon it should already be set up). */

if (*anchor == NULL) verify_setup_hostlist(list, anchor);

/* If the sender host address starts off ::ffff: it is an IPv6 address in
IPv4-compatible mode. Find the IPv4 part for checking against IPv4 addresses.
*/

sender_host_ipv4 = (strncmp(sender_host_address, "::ffff:", 7) == 0)?
  sender_host_address + 7 : sender_host_address;

/* Scan each listed item, checking the ident string if present, and then
checking the address. If the address isn't already set in the host item, we
can look it up directly only if the name isn't wildcarded. Otherwise we have to
do a reverse lookup of the IP address in order to get the host name, if this
hasn't already been done. Confirm this with a forward lookup afterwards.

If a reverse lookup fails, we normally return find_failed_return. This is set
true for calls to test reject lists and false for calls to test accept lists.
However, there is a requirement to be able to relax this. If any "host name" is
of the form "+allow_unknown", then if a subsequent reverse lookup fails, we
return the negation of find_failed_return. */

for (h = *anchor; h != NULL; h = h->next)
  {
  char *ident;

  /* If the host name is "+allow_unknown", negate the value returned for a
  subsequent failed reverse lookup. */

  if (strcmp(h->name, "+allow_unknown") == 0)
    {
    ffr = ~find_failed_return;
    continue;
    }

  /* Handle ident checking. If the test string starts with \ just ignore that
  character; otherwise if it starts with ! the test is negated. */

  if ((ident = h->ident_string) != NULL)
    {
    BOOL match = TRUE;
    if (sender_ident == NULL) continue;
    if (*ident == '\\') ident++;
      else if (*ident == '!') { ident++; match = FALSE; }
    if ((strcmp(ident, sender_ident) == 0) != match) continue;
    }

  /* Optimize for the special case when the name is "*". */

  if (strcmp(h->name, "*") == 0)
    {
    HDEBUG(2) debug_printf("%s yes (matched *)\n", ot);
    return TRUE;
    }

  /* If we don't already have the IP address (from a previous check on this
  list or as an additional address for a multihomed host), and the name
  contains no wildcards or a lookup, try to find the IP address from a forward
  lookup. Multihomed hosts will cause additional host items to get inserted
  into the chain. That's fine - they will all get scanned by the loop. Ensure
  they go into permanent store, though, by setting the last arg to
  host_find_byname() TRUE. This means they will persist over a number of
  incoming SMTP messages. */

  if (h->address == NULL && h->name[0] != '*' && h->name[0] != '^' &&
      strchr(h->name, ';') == NULL)
    {
    char *fully_qualified_name;
    (void) host_find_byname(h, &fully_qualified_name, TRUE);
    }

  /* If we have the IP address already, test that. If it is an IPv4 address
  ensure that IPv6 compatibility addresses are correctly compared. */

  if (h->address != NULL)
    {
    char *s = (strchr(h->address, ':') == NULL)?
      sender_host_ipv4 : sender_host_address;
    if (strcmp(h->address, s) == 0)
      {
      HDEBUG(2) debug_printf("%s yes (matched fixed IP address)\n", ot);
      return TRUE;
      }
    continue;
    }

  /* Otherwise we are going to need to match on the name. If it hasn't already
  been found, look it up here, ensuring that it goes into permanent store, and
  rebuilt the fullhost values. If we can't find the host name, return the
  value given in find_failed_return. */

  if (sender_host_name == NULL)
    {
    sender_host_name = host_find_byaddr(sender_host_address, TRUE);
    if (sender_host_name == NULL)
      {
      HDEBUG(2) debug_printf("%s %s (failed to get host name \n>>>   for %s)\n",
        ot, (ffr? "yes" : "no"), sender_host_address);
      return ffr;
      }
    host_build_sender_fullhost();
    }

  /* Match on the host name */

  if (match_check_string(sender_host_name, h->name, &(h->compiled_name), -1,
      TRUE, NULL))
    {
    HDEBUG(2) debug_printf("%s yes (host name matched)\n", ot);
    return TRUE;
    }

  if (search_find_defer)
    {
    HDEBUG(2) debug_printf("%s %s (search defer while checking host name)\n",
      ot, (ffr? "yes" : "no"));
    return ffr;
    }
  }

HDEBUG(2) debug_printf("%s no\n", ot);
return FALSE;
}




/*************************************************
*          Setup net list matching data          *
*************************************************/

/* This function is called for a number of parameters containing lists
of strings of the form <ip-address>/<mask>, to scan the strings it contains and
build a chain of control blocks of the broken-down or compiled data for
subsequent use. For IPv4 addresses, the mask is an integer, or in the form of
another v4 address. For IPv6 addresses the mask is always an integer. The
daemon calls this at its start if it is listening for SMTP calls. The string
has already been syntax checked at read-in time. Because the resulting control
blocks are saved for subsequent use, they must be put in malloc'd store.

Arguments:
  list      colon-separated list of items
  anchor    where to chain the created blocks

Returns:    nothing
*/

void
verify_setup_netlist(char *list, ip_net_item **anchor)
{
char *s;
char buffer[256];

DEBUG(2) debug_printf("verify_setup_netlist called\n");

for (s = string_nextinlist(&list, ':', buffer, sizeof(buffer));
     s != NULL;
     s = string_nextinlist(&list, ':', buffer, sizeof(buffer)))
  {
  int i;
  char *error;
  ip_net_item *n = store_malloc(sizeof(ip_net_item));

  n->next = NULL;
  *anchor = n;
  anchor = &(n->next);

  /* If the string starts with a slash, it is taken as a file name, and
  not much more can be done here. */

  if (*s == '/')
    {
    n->filename = string_copy_malloc(s);
    continue;
    }

  /* Else set the filename pointer to null to indicate the presence of
  binary data, use the common function to decode an address and a mask,
  and apply the mask to the saved address. */

  n->filename = NULL;
  n->size = host_amton(s, n->address, n->mask, &error);
  for (i = 0; i < n->size; i++) n->address[i] &= n->mask[i];
  }
}



/*************************************************
*         Check address is in net                *
*************************************************/

/* This function is called from a number of places to test whether the IP
address of the current calling host is in a list of networks.

Arguments:
  listptr    pointer to net list for checking
  anchor     anchor for broken down data (may already be done if via daemon)

Returns:     TRUE if IP address of current host is in the list
*/

BOOL
verify_check_net(char **listptr, ip_net_item **anchor)
{
char *list = *listptr;
char *ot = string_sprintf("host in %s?", readconf_find_option(listptr));

/* If there's no host address, this is command-line SMTP input, and so no
checking can be done. */

if (sender_host_address == NULL) return FALSE;

DEBUG(9)
  {
  debug_printf("checking %s\n", ot);
  debug_printf("  anchor=%d list=%s\n", *anchor, list);
  }

/* If the list is empty, the answer is no. */

if (list == NULL)
  {
  HDEBUG(2) debug_printf("%s no (option unset)\n", ot);
  return FALSE;
  }

/* Initialize the munged data if not already done so. */

if (*anchor == NULL) verify_setup_netlist(list, anchor);

/* Use the generic function that handles both IPv4 and IPv6 addresses */

if (match_net_isinlist(sender_host_address, *anchor))
  {
  HDEBUG(2) debug_printf("%s yes\n", ot);
  return TRUE;
  }
else
  {
  HDEBUG(2) debug_printf("%s no\n", ot);
  return FALSE;
  }
}




/*************************************************
*             Get valid header address           *
*************************************************/

/* Scan the originator headers of the message, looking for an address that
verifies successfully. This function is called from the second verification
function below. 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:
  newaddr        address of pointer to point at the verified address, or
                   NULL, if value of the address not wanted
  header_id      address of pointer to point at name of header used,
                   or NULL if not wanted
  sender_yield   the yield to give if the sender_address is found (saves
                   verifying it again)

Returns:         result of the verification attempt: OK, FAIL, or DEFER;
                 FAIL is given if no appropriate headers are found
*/

static int
verify_get_header_address(char **newaddr, char **header_id, int sender_yield)
{
static int header_types[] = { htype_sender, htype_replyto, htype_from };
static int header_name_offsets[] = { hn_sender, hn_replyto, hn_from };
int senderlen = (int)strlen(sender_address);
int yield = FAIL;
int i;

for (i = 0; i < 3; i++)
  {
  header_line *h;
  for (h = header_list; h != NULL; h = h->next)
    {
    BOOL is_local;
    int terminator, new_ok;
    char *s, *ss;

    if (h->type != header_types[i]) continue;
    s = strchr(h->text, ':') + 1;

    while (isspace(*s)) s++;
    ss = parse_find_address_end(s, FALSE);
    terminator = *ss;
    *ss = 0;

    /* Sender address in header may have whitespace on the end; if this
    test doesn't work, it isn't really harmful other than that a redundant
    verification gets done. */

    new_ok = (strncmp(s, sender_address, senderlen) == 0 &&
              (s[senderlen] == 0 || isspace(s[senderlen])))?
      sender_yield :
      verify_address(s, NULL, &is_local, newaddr,
        sender_verify_log_details? vopt_log_details : 0);

    *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 (new_ok == FAIL && verify_address_parse_error != NULL)
      {
      while (ss > s && isspace(ss[-1])) ss--;
      verify_address_parse_error =
        string_sprintf("syntax error in header address: %s in \"%.*s\"",
          verify_address_parse_error, ss - s, s);
      return FAIL;
      }

    /* Success: set up the name of the header */

    if (new_ok == OK)
      {
      if (header_id != NULL)
        {
        header_name *header_names = (strncmpic(h->text, "resent-", 7) == 0)?
          header_names_resent : header_names_normal;
        *header_id = header_names[header_name_offsets[i]].name;
        }
      return OK;
      }

    if (new_ok == DEFER) yield = DEFER;
    }
  }

return yield;
}




/*************************************************
*            Verify the sender of a message      *
*************************************************/

/* The next two functions operate in tandem, and the second must not be
called without first calling the first one. The reason for this approach is
that some SMTP mailers treat any error returned after the data has been
transmitted as temporary (contrary to RFC821) and keep retrying, even after
they have been sent a 5xx error at the previous attempt. To get round this,
exim keeps a database of failed messages and their hosts, and if the same bad
address is received from the same host soon afterwards, it is rejected at the
preliminary stage (meaning after MAIL FROM for SMTP) in the hope that the far
end might now give up.

The reason for not rejecting at this stage in all cases is that remote
postmasters, when told their systems have been sending out bad messages, always
ask "what were the headers?" and so one needs to have read them in order to log
them. This also helps track down mail forgers. It also makes it possible to
replace bad envelope sender addresses with good ones from inside the message if
that option is configured.

September 1996: Some mailers keep on trying even after getting a 5xx error for
MAIL FROM. If the same bad address is received from the same host for a third
time in a short time, MAIL FROM is accepted, but refuse_all_rcpts is set, and
all subsequent RCPT TO commands get rejected with a 550.

The RFCs imply that the final local-part@domain of a route address should be
intelligible to all parties. It is unfortunately the case that some mailers
abuse this and supply final domain addresses that are meaningful only to
them.

As we are interested only in whether we can route back to this address, we
don't worry about this case, but a possible upgrade would be to make checking
the final address a configurable option. That would then catch

  @valid.domain:junk@junk.domain

which at present gets through. Later: If the new collapse_source_routes option
is set, source routes are collapsed during parsing, so won't appear here, which
is in effect the upgrade mentioned. */




/*************************************************
*        First check on sender address           *
*************************************************/

/* This function is called as soon as a sender address has been received
from an SMTP connection. Unless the host is in the list of those from which no
verification is required, it verifies the address. If it is bad and
sender_verify_reject is FALSE, it gives an OK response with a warning message.

When sender_verify_reject is TRUE and a bad address is received, it checks to
see if the same address has recently been rejected. If not, it sets a flag for
verify_sender() to interrogate. If it has been rejected once recently, an error
return is given for MAIL FROM. If it has been rejected more than once recently,
then MAIL FROM is accepted, but a flag is set to cause all subsequent RCPT TO
commands to be rejected.

Arguments:
  errcode    set this to an SMTP error code on failure, or if there is a
               warning message on success
  errmess    set this to point to an error message on failure

Returns:     TRUE if address verified, or did not fail recently, or host
               is in the exception list, i.e. if is OK to proceed. The
               variable sender_ok indicates why it was allowed to proceed.
*/

BOOL
verify_sender_preliminary(int *errcode, char **errmess)
{
char *newaddr;
open_db dbblock;
open_db *dbm_file;
dbdata_reject *reject;
int rc, rejectlen, yield;
time_t now;
char buffer[SENDER_ADDRESS_MAXLENGTH + 256];

/* If the sender address is empty, it's an error message with, in effect,
no sender, and we can't check anything. */

if (sender_address[0] == 0)
  {
  sender_ok = SENDER_OK_OK;
  return TRUE;
  }

/* See if this is one of the trusted hosts/identd combinations or nets
for which we accept all addresses. If so, do no further checking. */

if (verify_check_host(&sender_verify_except_hosts,
      &sender_verify_except_hostlist, FALSE) ||
    verify_check_net(&sender_verify_except_nets,
      &sender_verify_except_netlist))
  {
  HDEBUG(2) debug_printf("host matched entry in sender_verify_except list\n");
  sender_ok = SENDER_OK_EXCEPTION;
  return TRUE;
  }

/* Try to verify the address */

HDEBUG(2) debug_printf("verifying %s\n", sender_address);

rc = verify_address(sender_address, NULL, &sender_is_local, &newaddr,
    sender_verify_log_details? vopt_log_details : 0);

/* After a successful return, the address may have been changed (typically
a domain will be canonicized or expanded by a router). */

if (rc == OK)
  {
  DEBUG(2) debug_printf("%s verified ok as %s\n", sender_address, newaddr);
  sender_address = newaddr;
  sender_address_rewritten = TRUE;  /* Prevent accept from doing it again */
  sender_ok = SENDER_OK_OK;
  return TRUE;
  }

/* If we are not going to reject permanent failures or if we are going to
accept temporary failures and it is one of them, accept the address, and note
it is a "warning" state. */

if (!sender_verify_reject || (rc == DEFER && sender_try_verify))
  {
  if (rc == DEFER)
    {
    *errmess = "warning: temporarily unable to resolve sender address: "
      "accepted unverified";
    if (sender_verify_log_details)
      log_write(0, LOG_REJECT, "%s verification deferred but sender_try_verify "
        "is true", sender_address);
    }
  else
    {
    *errmess = sender_is_local?
      "warning: unknown local-part in sender address" :
      "warning: cannot route to sender address";
    if (sender_verify_log_details)
      log_write(0, LOG_REJECT, "%s failed verification but "
        "sender_verify_reject is false", sender_address);
    }
  *errcode = 250;
  sender_ok = SENDER_OK_WARNING;
  return TRUE;
  }

/* Set up the key for the reject hints database, and attempt to open it.
If successful, read the record. */

sprintf(buffer, "%s:%.200s", sender_address,
  (sender_host_name != NULL)? sender_host_name :
  (sender_host_address != NULL)? sender_host_address : "");

dbm_file = dbfn_open("reject", O_RDWR, &dbblock);
reject = (dbm_file == NULL)?
  NULL : dbfn_read_with_length(dbm_file, buffer, &rejectlen);

/* Ignore data that is more than 24 hours old */

now = time(NULL);
if (reject != NULL && now - reject->time_stamp > 24*60*60) reject = NULL;

/* Defer is usually a DNS time out. If we have given this temporary rejection
too often recently, convert it into a permanent failure. Once the failure is
permanent, subsequent temporary failures are all made permanent, until the hint
expires (currently 24 hours). Otherwise accept the address for now, leaving
sender_ok set to DEFER, thus postponing possible (temporary) rejection to the
second verification function. */

if (rc == DEFER)
  {
  float temp_rate;

  sender_ok = SENDER_OK_DEFER;

  /* If there's no hints file, or no (recent) record in it, accept the address
  for now. This enables the headers to be read (for logging) and a rejection
  to be given later - or maybe the address can be fixed up from the headers. */

  if (dbm_file == NULL || reject == NULL)
    {
    HDEBUG(2) debug_printf("temporary error while verifying %s - will give "
      "error after DATA\n", sender_address);
    if (dbm_file != NULL) dbfn_close(dbm_file);
    return TRUE;
    }

  /* Control gets here if there has been a temporary or permanent rejection of
  this address recently. If we read a short record, this is old data from a
  previous version of Exim, which did not store temp_rate fields because it
  kept hints for permanent rejection only. Treat as a permanent rejection. This
  temporary code was installed in September 1998. Remove in a couple of years'
  time. */

  temp_rate = (rejectlen < sizeof(dbdata_reject))? 0.0 : reject->temp_rate;

  /* If temp_rate is zero, the previous rejection was permanent, so this one
  must be as well. Otherwise, if the max retry rate is set, we convert to a
  permanent error if it is exceeded. The test here is for continuing: failure
  is falling through. The first failure of an address will have succeeded here
  and given the error later, so as to log the headers. Subsequent failures can
  therefore give the failure here, updating the rate as they do so. */

  if (temp_rate > 0.0 &&
       (sender_verify_max_retry_rate <= 0 ||
        (int)temp_rate < sender_verify_max_retry_rate))
    {
    reject->temp_rate = ((reject->temp_rate + 1.0) * 3600) /
                        (now - reject->time_stamp + 3600);
    dbfn_write(dbm_file, buffer, reject, sizeof(dbdata_reject));
    dbfn_close(dbm_file);
    DEBUG(2) debug_printf("%s verification deferred\n", sender_address);
    *errcode = 451;
    *errmess = "temporarily unable to verify sender address (try again later)";
    return FALSE;
    }

  HDEBUG(2)
    {
    if (temp_rate == 0.0)
      debug_printf("%s verification deferred after previous failure - "
        "treating as permanent error\n", sender_address);
    else
      debug_printf("%s verification deferred too frequently - "
        "forcing a permanent error\n", sender_address);
    }
  }

/* If control gets here it's a hard failure, possibly converted from a
temporary failure that happened too often. */

sender_ok = SENDER_OK_NOT;

/* If there has been no recent rejection, pass now, leaving rejection to the
second function, which will create a DBM entry if necessary (it might not be
necessary if the sender is fixed up from the headers). */

if (dbm_file == NULL || reject == NULL)
  {
  HDEBUG(2)
    debug_printf("%s verification failed - will give error after DATA\n",
      sender_address);
  if (dbm_file != NULL) dbfn_close(dbm_file);
  return TRUE;
  }

/* There's been a recent rejection. If the previous rejection was after DATA,
reject now (i.e. reject the MAIL FROM), and set the flag to say so. */

if (!reject->rejected_mail_from)
  {
  *errcode = 550;
  *errmess = sender_is_local?
    "unknown local-part in sender" : "cannot route to sender";
  reject->rejected_mail_from = TRUE;
  DEBUG(2) debug_printf("%s verification failed after MAIL FROM\n",
    sender_address);
  yield = FALSE;
  }

/* There has been a previous recent rejection after MAIL FROM; the mailer
at the far end is horribly broken. Allow through this MAIL FROM with warning
text, but set refuse_all_rcpts to cause all RCPT TO commands to be failed
with 550 - which seems to be the only thing some mailers understand. We still
need to rewrite the hint record to update the timestamp. */

else
  {
  refuse_all_rcpts = TRUE;
  *errcode = 250;
  *errmess = "reject all recipients: 3 times bad sender";
  HDEBUG(2) debug_printf("%s verification failed - will reject all recipients\n",
    sender_address);
  yield = TRUE;
  }

/* Update the hint and return; reject_rate = 0.0 => permanent error */

reject->temp_rate = 0.0;
dbfn_write(dbm_file, buffer, reject, sizeof(dbdata_reject));
dbfn_close(dbm_file);
return yield;
}




/*************************************************
*        Second check on sender address          *
*************************************************/


/* This function is called when a message has been completely read, but the
headers haven't yet been written to the spool file, if the sender_verify
option is set. The sender check actually took place in the preliminary
function; its result is left in sender_ok. If it is bad, it may (depending on
the configuration) be permitted to replace it with a value taken from one of
the headers (From, Sender) if that address is viable.

Arguments:
  errcode    set this to an SMTP error code on failure
  errmess    set this to point to an error message on failure

Returns:     TRUE if address verified or fixed up, FALSE otherwise
*/

BOOL
verify_sender(int *errcode, char **errmess)
{
open_db dbblock;
open_db *dbm_file;
dbdata_reject newreject;
char buffer[256];

/* The sender verified correctly on the preliminary check. However, sometimes
we are required to inspect the headers as well. */

switch(sender_ok)
  {
  /* Wasn't really OK, but we are running in warning mode. If sender_verify_
  fixup is set, log what it would have done if we weren't running in warning
  mode. */

  case SENDER_OK_WARNING:
  if (sender_verify_fixup)
    {
    char *newaddr, *header_id;
    if (verify_get_header_address(&newaddr, &header_id, FAIL) == OK)
      log_write(0, LOG_MAIN, "return-path %s is rewritable as %s using %s",
        sender_address, newaddr, header_id);
    }

  /* Fall through */

  /* Sender was really, really OK. Verify a sender in an appropriate header
  if configured to do so. */

  case SENDER_OK_OK:
  if (headers_sender_verify ||
      (sender_address[0] == 0 && headers_sender_verify_errmsg))
    {
    char *msg;

    switch (verify_get_header_address(NULL, NULL, OK))
      {
      case DEFER:
      if (headers_checks_fail)
        {
        DEBUG(2) debug_printf("verification of sender from message "
          "headers deferred\n");
        *errcode = 451;
        *errmess = "can't currently verify any sender in message headers "
          "(please try again later): return path is";
        return FALSE;
        }

      log_write(3, LOG_REJECT, "warning: from%s%s <%s>: can't check for "
        "valid sender in headers",
        (sender_fullhost == NULL)? "" : " ",
        (sender_fullhost == NULL)? "" : host_and_ident(),
        sender_address);
      break;

      case FAIL:
      msg = (verify_address_parse_error == NULL)?
        "no valid sender in message headers" :
        verify_address_parse_error;

      if (headers_checks_fail)
        {
        *errcode = 550;
        *errmess = string_sprintf("%s: return path is", msg);
        DEBUG(2) debug_printf("%s <%s>\n", msg, sender_address);
        return FALSE;
        }

      log_write(3, LOG_REJECT, "warning: from%s%s <%s>: %s",
        (sender_fullhost == NULL)? "" : " ",
        (sender_fullhost == NULL)? "" : host_and_ident(),
        sender_address, msg);
      break;
      }
    }

  /* Fall through */

  /* Sender was accepted by exception. Don't verify a sender from the headers
  in this case. */

  case SENDER_OK_EXCEPTION:
  return TRUE;
  }

/* Original sender not OK, or the verification attempt deferred. If configured,
attempt a fix-up by scanning the messages's originator headers for a valid
address. */

if (sender_verify_fixup)
  {
  char *newaddr, *header_id;
  if (verify_get_header_address(&newaddr, &header_id, FAIL) == OK)
    {
    log_write(0, LOG_MAIN, "return-path %s rewritten as %s using %s",
      sender_address, newaddr, header_id);
    header_add(htype_other,
      "X-BadReturnPath: %s rewritten as %s\n  using \"%s\" header\n",
      sender_address, newaddr, header_id);
    sender_address = newaddr;
    return TRUE;
    }
  }

/* Set up for temporary or permanent rejection */

if (sender_ok == SENDER_OK_DEFER)
  {
  DEBUG(2) debug_printf("%s verification deferred\n", sender_address);
  *errcode = 451;
  *errmess = "temporarily unable to verify sender address (try again later)";
  }
else
  {
  DEBUG(2) debug_printf("%s verification failed after data\n", sender_address);
  *errcode = 550;
  *errmess = sender_is_local?
    "unknown local-part in sender" : "cannot route to sender";
  }

/* Set up the key name and open the hints database. O_RDWR (rather than
O_WRONLY) is needed by Berkeley native DB even when reading only. If the
database won't open, we can do no more. */

sprintf(buffer, "%s:%.200s", sender_address,
  (sender_host_name != NULL)? sender_host_name :
  (sender_host_address != NULL)? sender_host_address : "");

dbm_file = dbfn_open("reject", O_RDWR|O_CREAT, &dbblock);

/* Write a new record for this failure. A temporary failure rate of 0.0
indicates a permanent error. Otherwise initialize to "1 per hour". */

if (dbm_file != NULL)
  {
  newreject.rejected_mail_from = FALSE;
  newreject.temp_rate = (sender_ok == SENDER_OK_DEFER)? 1.0 : 0.0;
  dbfn_write(dbm_file, buffer, &newreject, sizeof(dbdata_reject));
  dbfn_close(dbm_file);
  }

return FALSE;
}





/*************************************************
*            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. We copy as many bytes from the result as
will fit into ident_buffer and then free the store that ident_id has got
(several K, I think). 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: the socket of the connection for which the ident value is required
Returns:  nothing

Side effect: any received ident value is put in sender_ident (NULL otherwise)
*/

void
verify_get_ident(int socket)
{
sender_ident = NULL;
if (rfc1413_query_timeout > 0 &&
    (rfc1413_except_hosts == NULL ||
      !verify_check_host(&rfc1413_except_hosts, &rfc1413_except_hostlist, FALSE))
    &&
    (rfc1413_except_nets == NULL ||
      !verify_check_net(&rfc1413_except_nets, &rfc1413_except_netlist)))
  {
  char *ident_ptr = ident_id(socket, rfc1413_query_timeout);
  if (ident_ptr != NULL)
    {
    strncpy(ident_buffer, ident_ptr, sizeof(ident_buffer));
    ident_buffer[sizeof(ident_buffer) - 1] = 0;
    sender_ident = string_printing(ident_buffer);
    /* NB: free() not store_free() as it wasn't got by store_malloc() */
    free(ident_ptr);
    }
  }
}

/* End of verify.c */
