Commit message: Recast more internal string routines to use growable-strings
CVE: CVE-2019-16928
Vulnerability description: Exim 4.92 through 4.92.2 allows remote code execution, a different vulnerability than CVE-2019-15846. There is a heap-based buffer overflow in string_vformat in string.c involving a long EHLO command.

Project: exim

transport_do_pass_socket(const uschar *transport_name, const uschar *hostname,
  const uschar *hostaddress, uschar *id, int socket_fd)
{
int i = 20;
const uschar **argv;

/* Set up the calling arguments; use the standard function for the basics,
but we have a number of extras that may be added. */

argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, &i, FALSE, 0);

if (f.smtp_authenticated)			argv[i++] = US"-MCA";
if (smtp_peer_options & OPTION_CHUNKING)	argv[i++] = US"-MCK";
if (smtp_peer_options & OPTION_DSN)		argv[i++] = US"-MCD";
if (smtp_peer_options & OPTION_PIPE)		argv[i++] = US"-MCP";
if (smtp_peer_options & OPTION_SIZE)		argv[i++] = US"-MCS";
#ifdef SUPPORT_TLS
if (smtp_peer_options & OPTION_TLS)
  if (tls_out.active.sock >= 0 || continue_proxy_cipher)
    {
    argv[i++] = US"-MCt";
    argv[i++] = sending_ip_address;
    argv[i++] = string_sprintf("%d", sending_port);
    argv[i++] = tls_out.active.sock >= 0 ? tls_out.cipher : continue_proxy_cipher;
    }
  else
    argv[i++] = US"-MCT";
#endif

if (queue_run_pid != (pid_t)0)
  {
  argv[i++] = US"-MCQ";
  argv[i++] = string_sprintf("%d", queue_run_pid);
  argv[i++] = string_sprintf("%d", queue_run_pipe);
  }

argv[i++] = US"-MC";
argv[i++] = US transport_name;
argv[i++] = US hostname;
argv[i++] = US hostaddress;
argv[i++] = string_sprintf("%d", continue_sequence + 1);
argv[i++] = id;
argv[i++] = NULL;

/* Arrange for the channel to be on stdin. */

if (socket_fd != 0)
  {
  (void)dup2(socket_fd, 0);
  (void)close(socket_fd);
  }

DEBUG(D_exec) debug_print_argv(argv);
exim_nullstd();                          /* Ensure std{out,err} exist */
execv(CS argv[0], (char *const *)argv);

DEBUG(D_any) debug_printf("execv failed: %s\n", strerror(errno));
_exit(errno);         /* Note: must be _exit(), NOT exit() */
}

Project: exim

write_to_fd_buf(int fd, const uschar *buf, size_t length)
{
ssize_t wrote;
size_t total_written = 0;
const uschar *p = buf;
size_t left = length;

while (1)
  {
  wrote = write(fd, p, left);
  if (wrote == (ssize_t)-1)
    {
    if (errno == EINTR) continue;
    return wrote;
    }
  total_written += wrote;
  if (wrote == left)
    break;
  else
    {
    p += wrote;
    left -= wrote;
    }
  }
return total_written;
}

Project: exim

debug_printf_indent(const char * format, ...)
{
va_list ap;
va_start(ap, format);
debug_vprintf(acl_level + expand_level, format, ap);
va_end(ap);
}

Project: exim

find_control(const uschar * name, control_def * ol, int last)
{
int first = 0;
while (last > first)
  {
  int middle = (first + last)/2;
  uschar * s =  ol[middle].name;
  int c = Ustrncmp(name, s, Ustrlen(s));
  if (c == 0) return middle;
  else if (c > 0) first = middle + 1;
  else last = middle;
  }
return -1;
}

Project: exim
File: deliver.c

par_reduce(int max, BOOL fallback)
{
while (parcount > max)
  {
  address_item *doneaddr = par_wait();
  if (!doneaddr)
    {
    log_write(0, LOG_MAIN|LOG_PANIC,
      "remote delivery process count got out of step");
    parcount = 0;
    }
  else
    {
    transport_instance * tp = doneaddr->transport;
    if (tp->max_parallel)
      enq_end(string_sprintf("tpt-serialize-%s", tp->name));

    remote_post_process(doneaddr, LOG_MAIN, NULL, fallback);
    }
  }
}

Project: exim

host_is_in_net(const uschar *host, const uschar *net, int maskoffset)
{
int i;
int address[4];
int incoming[4];
int mlen;
int size = host_aton(net, address);
int insize;

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

if (maskoffset == 0) mlen = 99999;    /* Big number */
  else mlen = Uatoi(net + maskoffset + 1);

/* Convert the incoming address to binary. */

insize = host_aton(host, 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 FALSE;

/* Else do the masked comparison. */

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

return TRUE;
}

Project: exim

exim_usage(uschar *progname)
{

/* Handle specific program invocation variants */
if (Ustrcmp(progname, US"-mailq") == 0)
  exim_fail(
    "mailq - list the contents of the mail queue\n\n"
    "For a list of options, see the Exim documentation.\n");

/* Generic usage - we output this whatever happens */
exim_fail(
  "Exim is a Mail Transfer Agent. It is normally called by Mail User Agents,\n"
  "not directly from a shell command line. Options and/or arguments control\n"
  "what it does when called. For a list of options, see the Exim documentation.\n");
}

Project: exim

transport_write_block_fd(transport_ctx * tctx, uschar *block, int len, BOOL more)
{
int i, rc, save_errno;
int local_timeout = transport_write_timeout;
int fd = tctx->u.fd;

/* This loop is for handling incomplete writes and other retries. In most
normal cases, it is only ever executed once. */

for (i = 0; i < 100; i++)
  {
  DEBUG(D_transport)
    debug_printf("writing data block fd=%d size=%d timeout=%d%s\n",
      fd, len, local_timeout, more ? " (more expected)" : "");

  /* This code makes use of alarm() in order to implement the timeout. This
  isn't a very tidy way of doing things. Using non-blocking I/O with select()
  provides a neater approach. However, I don't know how to do this when TLS is
  in use. */

  if (transport_write_timeout <= 0)   /* No timeout wanted */
    {
    rc =
#ifdef SUPPORT_TLS
	tls_out.active.sock == fd ? tls_write(tls_out.active.tls_ctx, block, len, more) :
#endif
#ifdef MSG_MORE
	more && !(tctx->options & topt_not_socket)
	  ? send(fd, block, len, MSG_MORE) :
#endif
	write(fd, block, len);
    save_errno = errno;
    }

  /* Timeout wanted. */

  else
    {
    ALARM(local_timeout);

    rc =
#ifdef SUPPORT_TLS
	tls_out.active.sock == fd ? tls_write(tls_out.active.tls_ctx, block, len, more) :
#endif
#ifdef MSG_MORE
	more && !(tctx->options & topt_not_socket)
	  ? send(fd, block, len, MSG_MORE) :
#endif
	write(fd, block, len);

    save_errno = errno;
    local_timeout = ALARM_CLR(0);
    if (sigalrm_seen)
      {
      errno = ETIMEDOUT;
      return FALSE;
      }
    }

  /* Hopefully, the most common case is success, so test that first. */

  if (rc == len) { transport_count += len; return TRUE; }

  /* A non-negative return code is an incomplete write. Try again for the rest
  of the block. If we have exactly hit the timeout, give up. */

  if (rc >= 0)
    {
    len -= rc;
    block += rc;
    transport_count += rc;
    DEBUG(D_transport) debug_printf("write incomplete (%d)\n", rc);
    goto CHECK_TIMEOUT;   /* A few lines below */
    }

  /* A negative return code with an EINTR error is another form of
  incomplete write, zero bytes having been written */

  if (save_errno == EINTR)
    {
    DEBUG(D_transport)
      debug_printf("write interrupted before anything written\n");
    goto CHECK_TIMEOUT;   /* A few lines below */
    }

  /* A response of EAGAIN from write() is likely only in the case of writing
  to a FIFO that is not swallowing the data as fast as Exim is writing it. */

  if (save_errno == EAGAIN)
    {
    DEBUG(D_transport)
      debug_printf("write temporarily locked out, waiting 1 sec\n");
    sleep(1);

    /* Before continuing to try another write, check that we haven't run out of
    time. */

    CHECK_TIMEOUT:
    if (transport_write_timeout > 0 && local_timeout <= 0)
      {
      errno = ETIMEDOUT;
      return FALSE;
      }
    continue;
    }

  /* Otherwise there's been an error */

  DEBUG(D_transport) debug_printf("writing error %d: %s\n", save_errno,
    strerror(save_errno));
  errno = save_errno;
  return FALSE;
  }

/* We've tried and tried and tried but still failed */

errno = ERRNO_WRITEINCOMPLETE;
return FALSE;
}

Project: exim

unlink_log(int type)
{
if (type == lt_debug) unlink(CS debuglog_name);
}

Project: exim

set_address_from_dns(host_item *host, host_item **lastptr,
  const uschar *ignore_target_hosts, BOOL allow_ip,
  const uschar **fully_qualified_name,
  BOOL dnssec_request, BOOL dnssec_require, int whichrrs)
{
dns_record *rr;
host_item *thishostlast = NULL;    /* Indicates not yet filled in anything */
BOOL v6_find_again = FALSE;
BOOL dnssec_fail = FALSE;
int i;

/* If allow_ip is set, a name which is an IP address returns that value
as its address. This is used for MX records when allow_mx_to_ip is set, for
those sites that feel they have to flaunt the RFC rules. */

if (allow_ip && string_is_ip_address(host->name, NULL) != 0)
  {
  #ifndef STAND_ALONE
  if (  ignore_target_hosts
     && verify_check_this_host(&ignore_target_hosts, NULL, host->name,
        host->name, NULL) == OK)
    return HOST_IGNORED;
  #endif

  host->address = host->name;
  return HOST_FOUND;
  }

/* On an IPv6 system, unless IPv6 is disabled, go round the loop up to twice,
looking for AAAA records the first time. However, unless doing standalone
testing, we force an IPv4 lookup if the domain matches dns_ipv4_lookup global.
On an IPv4 system, go round the loop once only, looking only for A records. */

#if HAVE_IPV6
  #ifndef STAND_ALONE
    if (  disable_ipv6
       || !(whichrrs & HOST_FIND_BY_AAAA)
       || (dns_ipv4_lookup
          && match_isinlist(host->name, CUSS &dns_ipv4_lookup, 0, NULL, NULL,
	      MCL_DOMAIN, TRUE, NULL) == OK)
       )
      i = 0;    /* look up A records only */
    else
  #endif        /* STAND_ALONE */

  i = 1;        /* look up AAAA and A records */

/* The IPv4 world */

#else           /* HAVE_IPV6 */
  i = 0;        /* look up A records only */
#endif          /* HAVE_IPV6 */

for (; i >= 0; i--)
  {
  static int types[] = { T_A, T_AAAA };
  int type = types[i];
  int randoffset = i == (whichrrs & HOST_FIND_IPV4_FIRST ? 1 : 0)
    ? 500 : 0;  /* Ensures v6/4 sort order */
  dns_answer dnsa;
  dns_scan dnss;

  int rc = dns_lookup_timerwrap(&dnsa, host->name, type, fully_qualified_name);
  lookup_dnssec_authenticated = !dnssec_request ? NULL
    : dns_is_secure(&dnsa) ? US"yes" : US"no";

  DEBUG(D_dns)
    if (  (dnssec_request || dnssec_require)
       && !dns_is_secure(&dnsa)
       && dns_is_aa(&dnsa)
       )
      debug_printf("DNS lookup of %.256s (A/AAAA) requested AD, but got AA\n", host->name);

  /* We want to return HOST_FIND_AGAIN if one of the A or AAAA lookups
  fails or times out, but not if another one succeeds. (In the early
  IPv6 days there are name servers that always fail on AAAA, but are happy
  to give out an A record. We want to proceed with that A record.) */

  if (rc != DNS_SUCCEED)
    {
    if (i == 0)  /* Just tried for an A record, i.e. end of loop */
      {
      if (host->address != NULL) return HOST_FOUND;  /* AAAA was found */
      if (rc == DNS_AGAIN || rc == DNS_FAIL || v6_find_again)
        return HOST_FIND_AGAIN;
      return HOST_FIND_FAILED;    /* DNS_NOMATCH or DNS_NODATA */
      }

    /* Tried for an AAAA record: remember if this was a temporary
    error, and look for the next record type. */

    if (rc != DNS_NOMATCH && rc != DNS_NODATA) v6_find_again = TRUE;
    continue;
    }

  if (dnssec_request)
    {
    if (dns_is_secure(&dnsa))
      {
      DEBUG(D_host_lookup) debug_printf("%s A DNSSEC\n", host->name);
      if (host->dnssec == DS_UNK) /* set in host_find_bydns() */
	host->dnssec = DS_YES;
      }
    else
      {
      if (dnssec_require)
	{
	dnssec_fail = TRUE;
	DEBUG(D_host_lookup) debug_printf("dnssec fail on %s for %.256s",
		i>0 ? "AAAA" : "A", host->name);
	continue;
	}
      if (host->dnssec == DS_YES) /* set in host_find_bydns() */
	{
	DEBUG(D_host_lookup) debug_printf("%s A cancel DNSSEC\n", host->name);
	host->dnssec = DS_NO;
	lookup_dnssec_authenticated = US"no";
	}
      }
    }

  /* Lookup succeeded: fill in the given host item with the first non-ignored
  address found; create additional items for any others. A single A6 record
  may generate more than one address.  The lookup had a chance to update the
  fqdn; we do not want any later times round the loop to do so. */

  fully_qualified_name = NULL;

  for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
       rr;
       rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
    {
    if (rr->type == type)
      {
      dns_address *da = dns_address_from_rr(&dnsa, rr);

      DEBUG(D_host_lookup)
        if (!da) debug_printf("no addresses extracted from A6 RR for %s\n",
            host->name);

      /* This loop runs only once for A and AAAA records, but may run
      several times for an A6 record that generated multiple addresses. */

      for (; da; da = da->next)
        {
        #ifndef STAND_ALONE
        if (ignore_target_hosts != NULL &&
              verify_check_this_host(&ignore_target_hosts, NULL,
                host->name, da->address, NULL) == OK)
          {
          DEBUG(D_host_lookup)
            debug_printf("ignored host %s [%s]\n", host->name, da->address);
          continue;
          }
        #endif

        /* If this is the first address, stick it in the given host block,
        and change the name if the returned RR has a different name. */

        if (thishostlast == NULL)
          {
          if (strcmpic(host->name, rr->name) != 0)
            host->name = string_copy_dnsdomain(rr->name);
          host->address = da->address;
          host->sort_key = host->mx * 1000 + random_number(500) + randoffset;
          host->status = hstatus_unknown;
          host->why = hwhy_unknown;
          thishostlast = host;
          }

        /* Not the first address. Check for, and ignore, duplicates. Then
        insert in the chain at a random point. */

        else
          {
          int new_sort_key;
          host_item *next;

          /* End of our local chain is specified by "thishostlast". */

          for (next = host;; next = next->next)
            {
            if (Ustrcmp(CS da->address, next->address) == 0) break;
            if (next == thishostlast) { next = NULL; break; }
            }
          if (next != NULL) continue;  /* With loop for next address */

          /* Not a duplicate */

          new_sort_key = host->mx * 1000 + random_number(500) + randoffset;
          next = store_get(sizeof(host_item));

          /* New address goes first: insert the new block after the first one
          (so as not to disturb the original pointer) but put the new address
          in the original block. */

          if (new_sort_key < host->sort_key)
            {
            *next = *host;                                  /* Copies port */
            host->next = next;
            host->address = da->address;
            host->sort_key = new_sort_key;
            if (thishostlast == host) thishostlast = next;  /* Local last */
            if (*lastptr == host) *lastptr = next;          /* Global last */
            }

          /* Otherwise scan down the addresses for this host to find the
          one to insert after. */

          else
            {
            host_item *h = host;
            while (h != thishostlast)
              {
              if (new_sort_key < h->next->sort_key) break;
              h = h->next;
              }
            *next = *h;                                 /* Copies port */
            h->next = next;
            next->address = da->address;
            next->sort_key = new_sort_key;
            if (h == thishostlast) thishostlast = next; /* Local last */
            if (h == *lastptr) *lastptr = next;         /* Global last */
            }
          }
        }
      }
    }
  }

/* Control gets here only if the second lookup (the A record) succeeded.
However, the address may not be filled in if it was ignored. */

return host->address
  ? HOST_FOUND
  : dnssec_fail
  ? HOST_FIND_SECURITY
  : HOST_IGNORED;
}

Project: exim

ipv6_nmtoa(int * binary, uschar * buffer)
{
int i, j, k;
uschar * c = buffer;
uschar * d = NULL;	/* shut insufficiently "clever" compiler up */

for (i = 0; i < 4; i++)
  {			/* expand to text */
  j = binary[i];
  c += sprintf(CS c, "%x:%x:", (j >> 16) & 0xffff, j & 0xffff);
  }

for (c = buffer, k = -1, i = 0; i < 8; i++)
  {			/* find longest 0-group sequence */
  if (*c == '0')	/* must be "0:" */
    {
    uschar * s = c;
    j = i;
    while (c[2] == '0') i++, c += 2;
    if (i-j > k)
      {
      k = i-j;		/* length of sequence */
      d = s;		/* start of sequence */
      }
    }
  while (*++c != ':') ;
  c++;
  }

c[-1] = '\0';	/* drop trailing colon */

/* debug_printf("%s: D k %d <%s> <%s>\n", __FUNCTION__, k, d, d + 2*(k+1)); */
if (k >= 0)
  {			/* collapse */
  c = d + 2*(k+1);
  if (d == buffer) c--;	/* need extra colon */
  *d++ = ':';	/* 1st 0 */
  while ((*d++ = *c++)) ;
  }
else
  d = c;

return d - buffer;
}

Project: exim

smtp_verify_helo(void)
{
BOOL yield = TRUE;

HDEBUG(D_receive) debug_printf("verifying EHLO/HELO argument \"%s\"\n",
  sender_helo_name);

if (sender_helo_name == NULL)
  {
  HDEBUG(D_receive) debug_printf("no EHLO/HELO command was issued\n");
  }

/* Deal with the case of -bs without an IP address */

else if (sender_host_address == NULL)
  {
  HDEBUG(D_receive) debug_printf("no client IP address: assume success\n");
  f.helo_verified = TRUE;
  }

/* Deal with the more common case when there is a sending IP address */

else if (sender_helo_name[0] == '[')
  {
  f.helo_verified = Ustrncmp(sender_helo_name+1, sender_host_address,
    Ustrlen(sender_host_address)) == 0;

#if HAVE_IPV6
  if (!f.helo_verified)
    {
    if (strncmpic(sender_host_address, US"::ffff:", 7) == 0)
      f.helo_verified = Ustrncmp(sender_helo_name + 1,
        sender_host_address + 7, Ustrlen(sender_host_address) - 7) == 0;
    }
#endif

  HDEBUG(D_receive)
    { if (f.helo_verified) debug_printf("matched host address\n"); }
  }

/* Do a reverse lookup if one hasn't already given a positive or negative
response. If that fails, or the name doesn't match, try checking with a forward
lookup. */

else
  {
  if (sender_host_name == NULL && !host_lookup_failed)
    yield = host_name_lookup() != DEFER;

  /* If a host name is known, check it and all its aliases. */

  if (sender_host_name)
    if ((f.helo_verified = strcmpic(sender_host_name, sender_helo_name) == 0))
      {
      sender_helo_dnssec = sender_host_dnssec;
      HDEBUG(D_receive) debug_printf("matched host name\n");
      }
    else
      {
      uschar **aliases = sender_host_aliases;
      while (*aliases)
        if ((f.helo_verified = strcmpic(*aliases++, sender_helo_name) == 0))
	  {
	  sender_helo_dnssec = sender_host_dnssec;
	  break;
	  }

      HDEBUG(D_receive) if (f.helo_verified)
          debug_printf("matched alias %s\n", *(--aliases));
      }

  /* Final attempt: try a forward lookup of the helo name */

  if (!f.helo_verified)
    {
    int rc;
    host_item h;
    dnssec_domains d;
    host_item *hh;

    h.name = sender_helo_name;
    h.address = NULL;
    h.mx = MX_NONE;
    h.next = NULL;
    d.request = US"*";
    d.require = US"";

    HDEBUG(D_receive) debug_printf("getting IP address for %s\n",
      sender_helo_name);
    rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A | HOST_FIND_BY_AAAA,
			  NULL, NULL, NULL, &d, NULL, NULL);
    if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL)
      for (hh = &h; hh; hh = hh->next)
        if (Ustrcmp(hh->address, sender_host_address) == 0)
          {
          f.helo_verified = TRUE;
	  if (h.dnssec == DS_YES) sender_helo_dnssec = TRUE;
          HDEBUG(D_receive)
	    {
            debug_printf("IP address for %s matches calling address\n"
	      "Forward DNS security status: %sverified\n",
              sender_helo_name, sender_helo_dnssec ? "" : "un");
	    }
          break;
          }
    }
  }

if (!f.helo_verified) f.helo_verify_failed = TRUE;  /* We've tried ... */
return yield;
}

Project: exim
File: deliver.c

check_message_size(transport_instance *tp, address_item *addr)
{
int rc = OK;
int size_limit;

deliver_set_expansions(addr);
size_limit = expand_string_integer(tp->message_size_limit, TRUE);
deliver_set_expansions(NULL);

if (expand_string_message)
  {
  rc = DEFER;
  addr->message = size_limit == -1
    ? string_sprintf("failed to expand message_size_limit "
      "in %s transport: %s", tp->name, expand_string_message)
    : string_sprintf("invalid message_size_limit "
      "in %s transport: %s", tp->name, expand_string_message);
  }
else if (size_limit > 0 && message_size > size_limit)
  {
  rc = FAIL;
  addr->message =
    string_sprintf("message is too big (transport limit = %d)",
      size_limit);
  }

return rc;
}

Project: exim

swallow_until_crlf(int fd, uschar *base, int already, int capacity)
{
uschar *to = base + already;
uschar *cr;
int have = 0;
int ret;
int last = 0;

/* For "PROXY UNKNOWN\r\n" we, at time of writing, expect to have read
up through the \r; for the _normal_ case, we haven't yet seen the \r. */

cr = memchr(base, '\r', already);
if (cr != NULL)
  {
  if ((cr - base) < already - 1)
    {
    /* \r and presumed \n already within what we have; probably not
    actually proxy protocol, but abort cleanly. */
    return 0;
    }
  /* \r is last character read, just need one more. */
  last = 1;
  }

while (capacity > 0)
  {
  do { ret = recv(fd, to, 1, 0); } while (ret == -1 && errno == EINTR);
  if (ret == -1)
    return -1;
  have++;
  if (last)
    return have;
  if (*to == '\r')
    last = 1;
  capacity--;
  to++;
  }

/* reached end without having room for a final newline, abort */
errno = EOVERFLOW;
return -1;
}

Project: exim

smtp_handle_acl_fail(int where, int rc, uschar *user_msg, uschar *log_msg)
{
BOOL drop = rc == FAIL_DROP;
int codelen = 3;
uschar *smtp_code;
uschar *lognl;
uschar *sender_info = US"";
uschar *what =
#ifdef WITH_CONTENT_SCAN
  where == ACL_WHERE_MIME ? US"during MIME ACL checks" :
#endif
  where == ACL_WHERE_PREDATA ? US"DATA" :
  where == ACL_WHERE_DATA ? US"after DATA" :
#ifndef DISABLE_PRDR
  where == ACL_WHERE_PRDR ? US"after DATA PRDR" :
#endif
  smtp_cmd_data ?
    string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_data) :
    string_sprintf("%s in \"connect\" ACL", acl_wherenames[where]);

if (drop) rc = FAIL;

/* Set the default SMTP code, and allow a user message to change it. */

smtp_code = rc == FAIL ? acl_wherecodes[where] : US"451";
smtp_message_code(&smtp_code, &codelen, &user_msg, &log_msg,
  where != ACL_WHERE_VRFY);

/* We used to have sender_address here; however, there was a bug that was not
updating sender_address after a rewrite during a verify. When this bug was
fixed, sender_address at this point became the rewritten address. I'm not sure
this is what should be logged, so I've changed to logging the unrewritten
address to retain backward compatibility. */

#ifndef WITH_CONTENT_SCAN
if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA)
#else
if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA || where == ACL_WHERE_MIME)
#endif
  {
  sender_info = string_sprintf("F=<%s>%s%s%s%s ",
    sender_address_unrewritten ? sender_address_unrewritten : sender_address,
    sender_host_authenticated ? US" A="                                    : US"",
    sender_host_authenticated ? sender_host_authenticated                  : US"",
    sender_host_authenticated && authenticated_id ? US":"                  : US"",
    sender_host_authenticated && authenticated_id ? authenticated_id       : US""
    );
  }

/* If there's been a sender verification failure with a specific message, and
we have not sent a response about it yet, do so now, as a preliminary line for
failures, but not defers. However, always log it for defer, and log it for fail
unless the sender_verify_fail log selector has been turned off. */

if (sender_verified_failed &&
    !testflag(sender_verified_failed, af_sverify_told))
  {
  BOOL save_rcpt_in_progress = fl.rcpt_in_progress;
  fl.rcpt_in_progress = FALSE;  /* So as not to treat these as the error */

  setflag(sender_verified_failed, af_sverify_told);

  if (rc != FAIL || LOGGING(sender_verify_fail))
    log_write(0, LOG_MAIN|LOG_REJECT, "%s sender verify %s for <%s>%s",
      host_and_ident(TRUE),
      ((sender_verified_failed->special_action & 255) == DEFER)? "defer":"fail",
      sender_verified_failed->address,
      (sender_verified_failed->message == NULL)? US"" :
      string_sprintf(": %s", sender_verified_failed->message));

  if (rc == FAIL && sender_verified_failed->user_message)
    smtp_respond(smtp_code, codelen, FALSE, string_sprintf(
        testflag(sender_verified_failed, af_verify_pmfail)?
          "Postmaster verification failed while checking <%s>\n%s\n"
          "Several RFCs state that you are required to have a postmaster\n"
          "mailbox for each mail domain. This host does not accept mail\n"
          "from domains whose servers reject the postmaster address."
          :
        testflag(sender_verified_failed, af_verify_nsfail)?
          "Callback setup failed while verifying <%s>\n%s\n"
          "The initial connection, or a HELO or MAIL FROM:<> command was\n"
          "rejected. Refusing MAIL FROM:<> does not help fight spam, disregards\n"
          "RFC requirements, and stops you from receiving standard bounce\n"
          "messages. This host does not accept mail from domains whose servers\n"
          "refuse bounces."
          :
          "Verification failed for <%s>\n%s",
        sender_verified_failed->address,
        sender_verified_failed->user_message));

  fl.rcpt_in_progress = save_rcpt_in_progress;
  }

/* Sort out text for logging */

log_msg = log_msg ? string_sprintf(": %s", log_msg) : US"";
if ((lognl = Ustrchr(log_msg, '\n'))) *lognl = 0;

/* Send permanent failure response to the command, but the code used isn't
always a 5xx one - see comments at the start of this function. If the original
rc was FAIL_DROP we drop the connection and yield 2. */

if (rc == FAIL)
  smtp_respond(smtp_code, codelen, TRUE,
    user_msg ? user_msg : US"Administrative prohibition");

/* Send temporary failure response to the command. Don't give any details,
unless acl_temp_details is set. This is TRUE for a callout defer, a "defer"
verb, and for a header verify when smtp_return_error_details is set.

This conditional logic is all somewhat of a mess because of the odd
interactions between temp_details and return_error_details. One day it should
be re-implemented in a tidier fashion. */

else
  if (f.acl_temp_details && user_msg)
    {
    if (  smtp_return_error_details
       && sender_verified_failed
       && sender_verified_failed->message
       )
      smtp_respond(smtp_code, codelen, FALSE, sender_verified_failed->message);

    smtp_respond(smtp_code, codelen, TRUE, user_msg);
    }
  else
    smtp_respond(smtp_code, codelen, TRUE,
      US"Temporary local problem - please try later");

/* Log the incident to the logs that are specified by log_reject_target
(default main, reject). This can be empty to suppress logging of rejections. If
the connection is not forcibly to be dropped, return 0. Otherwise, log why it
is closing if required and return 2.  */

if (log_reject_target != 0)
  {
#ifdef SUPPORT_TLS
  gstring * g = s_tlslog(NULL);
  uschar * tls = string_from_gstring(g);
  if (!tls) tls = US"";
#else
  uschar * tls = US"";
#endif
  log_write(where == ACL_WHERE_CONNECT ? L_connection_reject : 0,
    log_reject_target, "%s%s%s %s%srejected %s%s",
    LOGGING(dnssec) && sender_host_dnssec ? US" DS" : US"",
    host_and_ident(TRUE),
    tls,
    sender_info,
    rc == FAIL ? US"" : US"temporarily ",
    what, log_msg);
  }

if (!drop) return 0;

log_write(L_smtp_connection, LOG_MAIN, "%s closed by DROP in ACL",
  smtp_get_connection_info());

/* Run the not-quit ACL, but without any custom messages. This should not be a
problem, because we get here only if some other ACL has issued "drop", and
in that case, *its* custom messages will have been used above. */

smtp_notquit_exit(US"acl-drop", NULL, NULL);
return 2;
}

Project: exim
File: host.c

log_long_lookup(const uschar * type, const uschar * data, unsigned long msec)
{
log_write(0, LOG_MAIN, "Long %s lookup for '%s': %lu msec",
  type, data, msec);
}

Project: exim

d_hostlog(gstring * g, address_item * addr)
{
host_item * h = addr->host_used;

g = string_append(g, 2, US" H=", h->name);

if (LOGGING(dnssec) && h->dnssec == DS_YES)
  g = string_catn(g, US" DS", 3);

g = string_append(g, 3, US" [", h->address, US"]");

if (LOGGING(outgoing_port))
  g = string_fmt_append(g, ":%d", h->port);

#ifdef SUPPORT_SOCKS
if (LOGGING(proxy) && proxy_local_address)
  {
  g = string_append(g, 3, US" PRX=[", proxy_local_address, US"]");
  if (LOGGING(outgoing_port))
    g = string_fmt_append(g, ":%d", proxy_local_port);
  }
#endif

g = d_log_interface(g);

if (testflag(addr, af_tcp_fastopen))
  g = string_catn(g, US" TFO*", testflag(addr, af_tcp_fastopen_data) ? 5 : 4);

return g;
}

Project: exim
File: deliver.c

same_strings(uschar *one, uschar *two)
{
if (one == two) return TRUE;   /* Includes the case where both NULL */
if (!one || !two) return FALSE;
return (Ustrcmp(one, two) == 0);
}

Project: exim

dns_lookup_timerwrap(dns_answer *dnsa, const uschar *name, int type,
  const uschar **fully_qualified_name)
{
int retval;
unsigned long time_msec;

if (!slow_lookup_log)
  return dns_lookup(dnsa, name, type, fully_qualified_name);

time_msec = get_time_in_ms();
retval = dns_lookup(dnsa, name, type, fully_qualified_name);
if ((time_msec = get_time_in_ms() - time_msec) > slow_lookup_log)
  log_long_lookup(US"name", name, time_msec);
return retval;
}

Project: exim

smtp_message_code(uschar **code, int *codelen, uschar **msg, uschar **log_msg,
  BOOL check_valid)
{
int n;
int ovector[3];

if (!msg || !*msg) return;

if ((n = pcre_exec(regex_smtp_code, NULL, CS *msg, Ustrlen(*msg), 0,
  PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int))) < 0) return;

if (check_valid && (*msg)[0] != (*code)[0])
  {
  log_write(0, LOG_MAIN|LOG_PANIC, "configured error code starts with "
    "incorrect digit (expected %c) in \"%s\"", (*code)[0], *msg);
  if (log_msg != NULL && *log_msg == *msg)
    *log_msg = string_sprintf("%s %s", *code, *log_msg + ovector[1]);
  }
else
  {
  *code = *msg;
  *codelen = ovector[1];    /* Includes final space */
  }
*msg += ovector[1];         /* Chop the code off the message */
return;
}

Project: exim

host_build_sender_fullhost(void) { }

Project: exim

millisleep(int msec)
{
struct itimerval itval;
itval.it_interval.tv_sec = 0;
itval.it_interval.tv_usec = 0;
itval.it_value.tv_sec = msec/1000;
itval.it_value.tv_usec = (msec % 1000) * 1000;
milliwait(&itval);
}

Project: exim
File: deliver.c

replicate_status(address_item *addr)
{
address_item *addr2;
for (addr2 = addr->next; addr2; addr2 = addr2->next)
  {
  addr2->transport =	    addr->transport;
  addr2->transport_return = addr->transport_return;
  addr2->basic_errno =	    addr->basic_errno;
  addr2->more_errno =	    addr->more_errno;
  addr2->delivery_usec =    addr->delivery_usec;
  addr2->special_action =   addr->special_action;
  addr2->message =	    addr->message;
  addr2->user_message =	    addr->user_message;
  }
}

Project: exim

regex_must_compile(const uschar *pattern, BOOL caseless, BOOL use_malloc)
{
int offset;
int options = PCRE_COPT;
const pcre *yield;
const uschar *error;
if (use_malloc)
  {
  pcre_malloc = function_store_malloc;
  pcre_free = function_store_free;
  }
if (caseless) options |= PCRE_CASELESS;
yield = pcre_compile(CCS pattern, options, (const char **)&error, &offset, NULL);
pcre_malloc = function_store_get;
pcre_free = function_dummy_free;
if (yield == NULL)
  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "regular expression error: "
    "%s at offset %d while compiling %s", error, offset, pattern);
return yield;
}

Project: exim

acl_checkname(uschar *name, uschar **list, int end)
{
int start = 0;

while (start < end)
  {
  int mid = (start + end)/2;
  int c = Ustrcmp(name, list[mid]);
  if (c == 0) return mid;
  if (c < 0) end = mid; else start = mid + 1;
  }

return -1;
}

Project: exim

par_read_pipe(int poffset, BOOL eop)
{
host_item *h;
pardata *p = parlist + poffset;
address_item *addrlist = p->addrlist;
address_item *addr = p->addr;
pid_t pid = p->pid;
int fd = p->fd;

uschar *msg = p->msg;
BOOL done = p->done;

/* Loop through all items, reading from the pipe when necessary. The pipe
used to be non-blocking. But I do not see a reason for using non-blocking I/O
here, as the preceding select() tells us, if data is available for reading.

A read() on a "selected" handle should never block, but(!) it may return
less data then we expected. (The buffer size we pass to read() shouldn't be
understood as a "request", but as a "limit".)

Each separate item is written to the pipe in a timely manner. But, especially for
larger items, the read(2) may already return partial data from the write(2).

The write is atomic mostly (depending on the amount written), but atomic does
not imply "all or noting", it just is "not intermixed" with other writes on the
same channel (pipe).

*/

DEBUG(D_deliver) debug_printf("reading pipe for subprocess %d (%s)\n",
  (int)p->pid, eop? "ended" : "not ended yet");

while (!done)
  {
  retry_item *r, **rp;
  uschar pipeheader[PIPE_HEADER_SIZE+1];
  uschar *id = &pipeheader[0];
  uschar *subid = &pipeheader[1];
  uschar *ptr = big_buffer;
  size_t required = PIPE_HEADER_SIZE; /* first the pipehaeder, later the data */
  ssize_t got;

  DEBUG(D_deliver) debug_printf(
    "expect %lu bytes (pipeheader) from tpt process %d\n", (u_long)required, pid);

  /* We require(!) all the PIPE_HEADER_SIZE bytes here, as we know,
  they're written in a timely manner, so waiting for the write shouldn't hurt a lot.
  If we get less, we can assume the subprocess do be done and do not expect any further
  information from it. */

  if ((got = readn(fd, pipeheader, required)) != required)
    {
    msg = string_sprintf("got " SSIZE_T_FMT " of %d bytes (pipeheader) "
      "from transport process %d for transport %s",
      got, PIPE_HEADER_SIZE, pid, addr->transport->driver_name);
    done = TRUE;
    break;
    }

  pipeheader[PIPE_HEADER_SIZE] = '\0';
  DEBUG(D_deliver)
    debug_printf("got %ld bytes (pipeheader) from transport process %d\n",
      (long) got, pid);

  {
  /* If we can't decode the pipeheader, the subprocess seems to have a
  problem, we do not expect any furher information from it. */
  char *endc;
  required = Ustrtol(pipeheader+2, &endc, 10);
  if (*endc)
    {
    msg = string_sprintf("failed to read pipe "
      "from transport process %d for transport %s: error decoding size from header",
      pid, addr->transport->driver_name);
    done = TRUE;
    break;
    }
  }

  DEBUG(D_deliver)
    debug_printf("expect %lu bytes (pipedata) from transport process %d\n",
      (u_long)required, pid);

  /* Same as above, the transport process will write the bytes announced
  in a timely manner, so we can just wait for the bytes, getting less than expected
  is considered a problem of the subprocess, we do not expect anything else from it. */
  if ((got = readn(fd, big_buffer, required)) != required)
    {
    msg = string_sprintf("got only " SSIZE_T_FMT " of " SIZE_T_FMT
      " bytes (pipedata) from transport process %d for transport %s",
      got, required, pid, addr->transport->driver_name);
    done = TRUE;
    break;
    }

  /* Handle each possible type of item, assuming the complete item is
  available in store. */

  switch (*id)
    {
    /* Host items exist only if any hosts were marked unusable. Match
    up by checking the IP address. */

    case 'H':
      for (h = addrlist->host_list; h; h = h->next)
	{
	if (!h->address || Ustrcmp(h->address, ptr+2) != 0) continue;
	h->status = ptr[0];
	h->why = ptr[1];
	}
      ptr += 2;
      while (*ptr++);
      break;

    /* Retry items are sent in a preceding R item for each address. This is
    kept separate to keep each message short enough to guarantee it won't
    be split in the pipe. Hopefully, in the majority of cases, there won't in
    fact be any retry items at all.

    The complete set of retry items might include an item to delete a
    routing retry if there was a previous routing delay. However, routing
    retries are also used when a remote transport identifies an address error.
    In that case, there may also be an "add" item for the same key. Arrange
    that a "delete" item is dropped in favour of an "add" item. */

    case 'R':
      if (!addr) goto ADDR_MISMATCH;

      DEBUG(D_deliver|D_retry)
	debug_printf("reading retry information for %s from subprocess\n",
	  ptr+1);

      /* Cut out any "delete" items on the list. */

      for (rp = &addr->retries; (r = *rp); rp = &r->next)
	if (Ustrcmp(r->key, ptr+1) == 0)           /* Found item with same key */
	  {
	  if (!(r->flags & rf_delete)) break;	   /* It was not "delete" */
	  *rp = r->next;                           /* Excise a delete item */
	  DEBUG(D_deliver|D_retry)
	    debug_printf("  existing delete item dropped\n");
	  }

      /* We want to add a delete item only if there is no non-delete item;
      however we still have to step ptr through the data. */

      if (!r || !(*ptr & rf_delete))
	{
	r = store_get(sizeof(retry_item));
	r->next = addr->retries;
	addr->retries = r;
	r->flags = *ptr++;
	r->key = string_copy(ptr);
	while (*ptr++);
	memcpy(&r->basic_errno, ptr, sizeof(r->basic_errno));
	ptr += sizeof(r->basic_errno);
	memcpy(&r->more_errno, ptr, sizeof(r->more_errno));
	ptr += sizeof(r->more_errno);
	r->message = *ptr ? string_copy(ptr) : NULL;
	DEBUG(D_deliver|D_retry) debug_printf("  added %s item\n",
	    r->flags & rf_delete ? "delete" : "retry");
	}

      else
	{
	DEBUG(D_deliver|D_retry)
	  debug_printf("  delete item not added: non-delete item exists\n");
	ptr++;
	while(*ptr++);
	ptr += sizeof(r->basic_errno) + sizeof(r->more_errno);
	}

      while(*ptr++);
      break;

    /* Put the amount of data written into the parlist block */

    case 'S':
      memcpy(&(p->transport_count), ptr, sizeof(transport_count));
      ptr += sizeof(transport_count);
      break;

    /* Address items are in the order of items on the address chain. We
    remember the current address value in case this function is called
    several times to empty the pipe in stages. Information about delivery
    over TLS is sent in a preceding X item for each address. We don't put
    it in with the other info, in order to keep each message short enough to
    guarantee it won't be split in the pipe. */

#ifdef SUPPORT_TLS
    case 'X':
      if (!addr) goto ADDR_MISMATCH;          /* Below, in 'A' handler */
      switch (*subid)
	{
	case '1':
	  addr->cipher = NULL;
	  addr->peerdn = NULL;

	  if (*ptr)
	    addr->cipher = string_copy(ptr);
	  while (*ptr++);
	  if (*ptr)
	    addr->peerdn = string_copy(ptr);
	  break;

	case '2':
	  if (*ptr)
	    (void) tls_import_cert(ptr, &addr->peercert);
	  else
	    addr->peercert = NULL;
	  break;

	case '3':
	  if (*ptr)
	    (void) tls_import_cert(ptr, &addr->ourcert);
	  else
	    addr->ourcert = NULL;
	  break;

# ifndef DISABLE_OCSP
	case '4':
	  addr->ocsp = *ptr ? *ptr - '0' : OCSP_NOT_REQ;
	  break;
# endif
	}
      while (*ptr++);
      break;
#endif	/*SUPPORT_TLS*/

    case 'C':	/* client authenticator information */
      switch (*subid)
	{
	case '1': addr->authenticator = *ptr ? string_copy(ptr) : NULL; break;
	case '2': addr->auth_id = *ptr ? string_copy(ptr) : NULL;	break;
	case '3': addr->auth_sndr = *ptr ? string_copy(ptr) : NULL;	break;
	}
      while (*ptr++);
      break;

#ifndef DISABLE_PRDR
    case 'P':
      setflag(addr, af_prdr_used);
      break;
#endif

    case 'L':
      switch (*subid)
	{
#ifdef EXPERIMENTAL_PIPE_CONNECT
	case 2: setflag(addr, af_early_pipe);	/*FALLTHROUGH*/
#endif
	case 1: setflag(addr, af_pipelining); break;
	}
      break;

    case 'K':
      setflag(addr, af_chunking_used);
      break;

    case 'T':
      setflag(addr, af_tcp_fastopen_conn);
      if (*subid > '0') setflag(addr, af_tcp_fastopen);
      if (*subid > '1') setflag(addr, af_tcp_fastopen_data);
      break;

    case 'D':
      if (!addr) goto ADDR_MISMATCH;
      memcpy(&(addr->dsn_aware), ptr, sizeof(addr->dsn_aware));
      ptr += sizeof(addr->dsn_aware);
      DEBUG(D_deliver) debug_printf("DSN read: addr->dsn_aware = %d\n", addr->dsn_aware);
      break;

    case 'A':
      if (!addr)
	{
	ADDR_MISMATCH:
	msg = string_sprintf("address count mismatch for data read from pipe "
	  "for transport process %d for transport %s", pid,
	    addrlist->transport->driver_name);
	done = TRUE;
	break;
	}

      switch (*subid)
	{
  #ifdef SUPPORT_SOCKS
	case '2':	/* proxy information; must arrive before A0 and applies to that addr XXX oops*/
	  proxy_session = TRUE;	/*XXX should this be cleared somewhere? */
	  if (*ptr == 0)
	    ptr++;
	  else
	    {
	    proxy_local_address = string_copy(ptr);
	    while(*ptr++);
	    memcpy(&proxy_local_port, ptr, sizeof(proxy_local_port));
	    ptr += sizeof(proxy_local_port);
	    }
	  break;
  #endif

  #ifdef EXPERIMENTAL_DSN_INFO
	case '1':	/* must arrive before A0, and applies to that addr */
			/* Two strings: smtp_greeting and helo_response */
	  addr->smtp_greeting = string_copy(ptr);
	  while(*ptr++);
	  addr->helo_response = string_copy(ptr);
	  while(*ptr++);
	  break;
  #endif

	case '0':
	  DEBUG(D_deliver) debug_printf("A0 %s tret %d\n", addr->address, *ptr);
	  addr->transport_return = *ptr++;
	  addr->special_action = *ptr++;
	  memcpy(&addr->basic_errno, ptr, sizeof(addr->basic_errno));
	  ptr += sizeof(addr->basic_errno);
	  memcpy(&addr->more_errno, ptr, sizeof(addr->more_errno));
	  ptr += sizeof(addr->more_errno);
	  memcpy(&addr->delivery_usec, ptr, sizeof(addr->delivery_usec));
	  ptr += sizeof(addr->delivery_usec);
	  memcpy(&addr->flags, ptr, sizeof(addr->flags));
	  ptr += sizeof(addr->flags);
	  addr->message = *ptr ? string_copy(ptr) : NULL;
	  while(*ptr++);
	  addr->user_message = *ptr ? string_copy(ptr) : NULL;
	  while(*ptr++);

	  /* Always two strings for host information, followed by the port number and DNSSEC mark */

	  if (*ptr)
	    {
	    h = store_get(sizeof(host_item));
	    h->name = string_copy(ptr);
	    while (*ptr++);
	    h->address = string_copy(ptr);
	    while(*ptr++);
	    memcpy(&h->port, ptr, sizeof(h->port));
	    ptr += sizeof(h->port);
	    h->dnssec = *ptr == '2' ? DS_YES
		      : *ptr == '1' ? DS_NO
		      : DS_UNK;
	    ptr++;
	    addr->host_used = h;
	    }
	  else ptr++;

	  /* Finished with this address */

	  addr = addr->next;
	  break;
	}
      break;

    /* Local interface address/port */
    case 'I':
      if (*ptr) sending_ip_address = string_copy(ptr);
      while (*ptr++) ;
      if (*ptr) sending_port = atoi(CS ptr);
      while (*ptr++) ;
      break;

    /* Z marks the logical end of the data. It is followed by '0' if
    continue_transport was NULL at the end of transporting, otherwise '1'.
    We need to know when it becomes NULL during a delivery down a passed SMTP
    channel so that we don't try to pass anything more down it. Of course, for
    most normal messages it will remain NULL all the time. */

    case 'Z':
      if (*ptr == '0')
	{
	continue_transport = NULL;
	continue_hostname = NULL;
	}
      done = TRUE;
      DEBUG(D_deliver) debug_printf("Z0%c item read\n", *ptr);
      break;

    /* Anything else is a disaster. */

    default:
      msg = string_sprintf("malformed data (%d) read from pipe for transport "
	"process %d for transport %s", ptr[-1], pid,
	  addr->transport->driver_name);
      done = TRUE;
      break;
    }
  }

/* The done flag is inspected externally, to determine whether or not to
call the function again when the process finishes. */

p->done = done;

/* If the process hadn't finished, and we haven't seen the end of the data
or if we suffered a disaster, update the rest of the state, and return FALSE to
indicate "not finished". */

if (!eop && !done)
  {
  p->addr = addr;
  p->msg = msg;
  return FALSE;
  }

/* Close our end of the pipe, to prevent deadlock if the far end is still
pushing stuff into it. */

(void)close(fd);
p->fd = -1;

/* If we have finished without error, but haven't had data for every address,
something is wrong. */

if (!msg && addr)
  msg = string_sprintf("insufficient address data read from pipe "
    "for transport process %d for transport %s", pid,
      addr->transport->driver_name);

/* If an error message is set, something has gone wrong in getting back
the delivery data. Put the message into each address and freeze it. */

if (msg)
  for (addr = addrlist; addr; addr = addr->next)
    {
    addr->transport_return = DEFER;
    addr->special_action = SPECIAL_FREEZE;
    addr->message = msg;
    log_write(0, LOG_MAIN|LOG_PANIC, "Delivery status for %s: %s\n", addr->address, addr->message);
    }

/* Return TRUE to indicate we have got all we need from this process, even
if it hasn't actually finished yet. */

return TRUE;
}

Project: exim

command_sigterm_handler(int sig)
{
had_command_sigterm = sig;
}

Project: exim

void auth_heimdal_gssapi_version_report(FILE *f) {}

Project: exim

acl_getline(void)
{
uschar *yield;

/* This loop handles leading blank lines and comments. */

for(;;)
  {
  while (isspace(*acl_text)) acl_text++;   /* Leading spaces/empty lines */
  if (*acl_text == 0) return NULL;         /* No more data */
  yield = acl_text;                        /* Potential data line */

  while (*acl_text != 0 && *acl_text != '\n') acl_text++;

  /* If we hit the end before a newline, we have the whole logical line. If
  it's a comment, there's no more data to be given. Otherwise, yield it. */

  if (*acl_text == 0) return (*yield == '#')? NULL : yield;

  /* After reaching a newline, end this loop if the physical line does not
  start with '#'. If it does, it's a comment, and the loop continues. */

  if (*yield != '#') break;
  }

/* This loop handles continuations. We know we have some real data, ending in
newline. See if there is a continuation marker at the end (ignoring trailing
white space). We know that *yield is not white space, so no need to test for
cont > yield in the backwards scanning loop. */

for(;;)
  {
  uschar *cont;
  for (cont = acl_text - 1; isspace(*cont); cont--);

  /* If no continuation follows, we are done. Mark the end of the line and
  return it. */

  if (*cont != '\\')
    {
    *acl_text++ = 0;
    return yield;
    }

  /* We have encountered a continuation. Skip over whitespace at the start of
  the next line, and indeed the whole of the next line or lines if they are
  comment lines. */

  for (;;)
    {
    while (*(++acl_text) == ' ' || *acl_text == '\t');
    if (*acl_text != '#') break;
    while (*(++acl_text) != 0 && *acl_text != '\n');
    }

  /* We have the start of a continuation line. Move all the rest of the data
  to join onto the previous line, and then find its end. If the end is not a
  newline, we are done. Otherwise loop to look for another continuation. */

  memmove(cont, acl_text, acl_text_end - acl_text);
  acl_text_end -= acl_text - cont;
  acl_text = cont;
  while (*acl_text != 0 && *acl_text != '\n') acl_text++;
  if (*acl_text == 0) return yield;
  }

/* Control does not reach here */
}

Project: exim

acl_udpsend(const uschar *arg, uschar **log_msgptr)
{
int sep = 0;
uschar *hostname;
uschar *portstr;
uschar *portend;
host_item *h;
int portnum;
int len;
int r, s;
uschar * errstr;

hostname = string_nextinlist(&arg, &sep, NULL, 0);
portstr = string_nextinlist(&arg, &sep, NULL, 0);

if (!hostname)
  {
  *log_msgptr = US"missing destination host in \"udpsend\" modifier";
  return ERROR;
  }
if (!portstr)
  {
  *log_msgptr = US"missing destination port in \"udpsend\" modifier";
  return ERROR;
  }
if (!arg)
  {
  *log_msgptr = US"missing datagram payload in \"udpsend\" modifier";
  return ERROR;
  }
portnum = Ustrtol(portstr, &portend, 10);
if (*portend != '\0')
  {
  *log_msgptr = US"bad destination port in \"udpsend\" modifier";
  return ERROR;
  }

/* Make a single-item host list. */
h = store_get(sizeof(host_item));
memset(h, 0, sizeof(host_item));
h->name = hostname;
h->port = portnum;
h->mx = MX_NONE;

if (string_is_ip_address(hostname, NULL))
  h->address = hostname, r = HOST_FOUND;
else
  r = host_find_byname(h, NULL, 0, NULL, FALSE);
if (r == HOST_FIND_FAILED || r == HOST_FIND_AGAIN)
  {
  *log_msgptr = US"DNS lookup failed in \"udpsend\" modifier";
  return DEFER;
  }

HDEBUG(D_acl)
  debug_printf_indent("udpsend [%s]:%d %s\n", h->address, portnum, arg);

/*XXX this could better use sendto */
r = s = ip_connectedsocket(SOCK_DGRAM, h->address, portnum, portnum,
		1, NULL, &errstr, NULL);
if (r < 0) goto defer;
len = Ustrlen(arg);
r = send(s, arg, len, 0);
if (r < 0)
  {
  errstr = US strerror(errno);
  close(s);
  goto defer;
  }
close(s);
if (r < len)
  {
  *log_msgptr =
    string_sprintf("\"udpsend\" truncated from %d to %d octets", len, r);
  return DEFER;
  }

HDEBUG(D_acl)
  debug_printf_indent("udpsend %d bytes\n", r);

return OK;

defer:
*log_msgptr = string_sprintf("\"udpsend\" failed: %s", errstr);
return DEFER;
}

Project: exim

local_part_quote(uschar *lpart)
{
BOOL needs_quote = FALSE;
gstring * g;
uschar *t;

for (t = lpart; !needs_quote && *t != 0; t++)
  {
  needs_quote = !isalnum(*t) && strchr("!#$%&'*+-/=?^_`{|}~", *t) == NULL &&
    (*t != '.' || t == lpart || t[1] == 0);
  }

if (!needs_quote) return lpart;

g = string_catn(NULL, US"\"", 1);

for (;;)
  {
  uschar *nq = US Ustrpbrk(lpart, "\\\"");
  if (nq == NULL)
    {
    g = string_cat(g, lpart);
    break;
    }
  g = string_catn(g, lpart, nq - lpart);
  g = string_catn(g, US"\\", 1);
  g = string_catn(g, nq, 1);
  lpart = nq + 1;
  }

g = string_catn(g, US"\"", 1);
return string_from_gstring(g);
}

Project: exim

exim_errstr(int err)
{
return err < 0 ? exim_errstrings[-err] : CUS strerror(err);
}

Project: exim

flush_buffer(smtp_outblock * outblock, int mode)
{
int rc;
int n = outblock->ptr - outblock->buffer;
BOOL more = mode == SCMD_MORE;

HDEBUG(D_transport|D_acl) debug_printf_indent("cmd buf flush %d bytes%s\n", n,
  more ? " (more expected)" : "");

#ifdef SUPPORT_TLS
if (outblock->cctx->tls_ctx)
  rc = tls_write(outblock->cctx->tls_ctx, outblock->buffer, n, more);
else
#endif

  {
  if (outblock->conn_args)
    {
    blob early_data = { .data = outblock->buffer, .len = n };

    /* We ignore the more-flag if we're doing a connect with early-data, which
    means we won't get BDAT+data. A pity, but wise due to the idempotency
    requirement: TFO with data can, in rare cases, replay the data to the
    receiver. */

    if (  (outblock->cctx->sock = smtp_connect(outblock->conn_args, &early_data))
       < 0)
      return FALSE;
    outblock->conn_args = NULL;
    rc = n;
    }
  else

    rc = send(outblock->cctx->sock, outblock->buffer, n,
#ifdef MSG_MORE
	      more ? MSG_MORE : 0
#else
	      0
#endif
	     );
  }

if (rc <= 0)
  {
  HDEBUG(D_transport|D_acl) debug_printf_indent("send failed: %s\n", strerror(errno));
  return FALSE;
  }

outblock->ptr = outblock->buffer;
outblock->cmd_count = 0;
return TRUE;
}

Project: exim
File: deliver.c

do_local_deliveries(void)
{
open_db dbblock;
open_db *dbm_file = NULL;
time_t now = time(NULL);

/* Loop until we have exhausted the supply of local deliveries */

while (addr_local)
  {
  struct timeval delivery_start;
  struct timeval deliver_time;
  address_item *addr2, *addr3, *nextaddr;
  int logflags = LOG_MAIN;
  int logchar = f.dont_deliver? '*' : '=';
  transport_instance *tp;
  uschar * serialize_key = NULL;

  /* Pick the first undelivered address off the chain */

  address_item *addr = addr_local;
  addr_local = addr->next;
  addr->next = NULL;

  DEBUG(D_deliver|D_transport)
    debug_printf("--------> %s <--------\n", addr->address);

  /* An internal disaster if there is no transport. Should not occur! */

  if (!(tp = addr->transport))
    {
    logflags |= LOG_PANIC;
    f.disable_logging = FALSE;  /* Jic */
    addr->message = addr->router
      ? string_sprintf("No transport set by %s router", addr->router->name)
      : string_sprintf("No transport set by system filter");
    post_process_one(addr, DEFER, logflags, EXIM_DTYPE_TRANSPORT, 0);
    continue;
    }

  /* Check that this base address hasn't previously been delivered to this
  transport. The check is necessary at this point to handle homonymic addresses
  correctly in cases where the pattern of redirection changes between delivery
  attempts. Non-homonymic previous delivery is detected earlier, at routing
  time. */

  if (previously_transported(addr, FALSE)) continue;

  /* There are weird cases where logging is disabled */

  f.disable_logging = tp->disable_logging;

  /* Check for batched addresses and possible amalgamation. Skip all the work
  if either batch_max <= 1 or there aren't any other addresses for local
  delivery. */

  if (tp->batch_max > 1 && addr_local)
    {
    int batch_count = 1;
    BOOL uses_dom = readconf_depends((driver_instance *)tp, US"domain");
    BOOL uses_lp = (  testflag(addr, af_pfr)
		   && (testflag(addr, af_file) || addr->local_part[0] == '|')
		   )
		|| readconf_depends((driver_instance *)tp, US"local_part");
    uschar *batch_id = NULL;
    address_item **anchor = &addr_local;
    address_item *last = addr;
    address_item *next;

    /* Expand the batch_id string for comparison with other addresses.
    Expansion failure suppresses batching. */

    if (tp->batch_id)
      {
      deliver_set_expansions(addr);
      batch_id = expand_string(tp->batch_id);
      deliver_set_expansions(NULL);
      if (!batch_id)
        {
        log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand batch_id option "
          "in %s transport (%s): %s", tp->name, addr->address,
          expand_string_message);
        batch_count = tp->batch_max;
        }
      }

    /* Until we reach the batch_max limit, pick off addresses which have the
    same characteristics. These are:

      same transport
      not previously delivered (see comment about 50 lines above)
      same local part if the transport's configuration contains $local_part
        or if this is a file or pipe delivery from a redirection
      same domain if the transport's configuration contains $domain
      same errors address
      same additional headers
      same headers to be removed
      same uid/gid for running the transport
      same first host if a host list is set
    */

    while ((next = *anchor) && batch_count < tp->batch_max)
      {
      BOOL ok =
           tp == next->transport
	&& !previously_transported(next, TRUE)
	&& testflag(addr, af_pfr) == testflag(next, af_pfr)
	&& testflag(addr, af_file) == testflag(next, af_file)
	&& (!uses_lp  || Ustrcmp(next->local_part, addr->local_part) == 0)
	&& (!uses_dom || Ustrcmp(next->domain, addr->domain) == 0)
	&& same_strings(next->prop.errors_address, addr->prop.errors_address)
	&& same_headers(next->prop.extra_headers, addr->prop.extra_headers)
	&& same_strings(next->prop.remove_headers, addr->prop.remove_headers)
	&& same_ugid(tp, addr, next)
	&& (  !addr->host_list && !next->host_list
	   ||    addr->host_list
	      && next->host_list
	      && Ustrcmp(addr->host_list->name, next->host_list->name) == 0
	   );

      /* If the transport has a batch_id setting, batch_id will be non-NULL
      from the expansion outside the loop. Expand for this address and compare.
      Expansion failure makes this address ineligible for batching. */

      if (ok && batch_id)
        {
        uschar *bid;
        address_item *save_nextnext = next->next;
        next->next = NULL;            /* Expansion for a single address */
        deliver_set_expansions(next);
        next->next = save_nextnext;
        bid = expand_string(tp->batch_id);
        deliver_set_expansions(NULL);
        if (!bid)
          {
          log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand batch_id option "
            "in %s transport (%s): %s", tp->name, next->address,
            expand_string_message);
          ok = FALSE;
          }
        else ok = (Ustrcmp(batch_id, bid) == 0);
        }

      /* Take address into batch if OK. */

      if (ok)
        {
        *anchor = next->next;           /* Include the address */
        next->next = NULL;
        last->next = next;
        last = next;
        batch_count++;
        }
      else anchor = &next->next;        /* Skip the address */
      }
    }

  /* We now have one or more addresses that can be delivered in a batch. Check
  whether the transport is prepared to accept a message of this size. If not,
  fail them all forthwith. If the expansion fails, or does not yield an
  integer, defer delivery. */

  if (tp->message_size_limit)
    {
    int rc = check_message_size(tp, addr);
    if (rc != OK)
      {
      replicate_status(addr);
      while (addr)
        {
        addr2 = addr->next;
        post_process_one(addr, rc, logflags, EXIM_DTYPE_TRANSPORT, 0);
        addr = addr2;
        }
      continue;    /* With next batch of addresses */
      }
    }

  /* If we are not running the queue, or if forcing, all deliveries will be
  attempted. Otherwise, we must respect the retry times for each address. Even
  when not doing this, we need to set up the retry key string, and determine
  whether a retry record exists, because after a successful delivery, a delete
  retry item must be set up. Keep the retry database open only for the duration
  of these checks, rather than for all local deliveries, because some local
  deliveries (e.g. to pipes) can take a substantial time. */

  if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE)))
    {
    DEBUG(D_deliver|D_retry|D_hints_lookup)
      debug_printf("no retry data available\n");
    }

  addr2 = addr;
  addr3 = NULL;
  while (addr2)
    {
    BOOL ok = TRUE;   /* to deliver this address */
    uschar *retry_key;

    /* Set up the retry key to include the domain or not, and change its
    leading character from "R" to "T". Must make a copy before doing this,
    because the old key may be pointed to from a "delete" retry item after
    a routing delay. */

    retry_key = string_copy(
      tp->retry_use_local_part ? addr2->address_retry_key :
        addr2->domain_retry_key);
    *retry_key = 'T';

    /* Inspect the retry data. If there is no hints file, delivery happens. */

    if (dbm_file)
      {
      dbdata_retry *retry_record = dbfn_read(dbm_file, retry_key);

      /* If there is no retry record, delivery happens. If there is,
      remember it exists so it can be deleted after a successful delivery. */

      if (retry_record)
        {
        setflag(addr2, af_lt_retry_exists);

        /* A retry record exists for this address. If queue running and not
        forcing, inspect its contents. If the record is too old, or if its
        retry time has come, or if it has passed its cutoff time, delivery
        will go ahead. */

        DEBUG(D_retry)
          {
          debug_printf("retry record exists: age=%s ",
            readconf_printtime(now - retry_record->time_stamp));
          debug_printf("(max %s)\n", readconf_printtime(retry_data_expire));
          debug_printf("  time to retry = %s expired = %d\n",
            readconf_printtime(retry_record->next_try - now),
            retry_record->expired);
          }

        if (f.queue_running && !f.deliver_force)
          {
          ok = (now - retry_record->time_stamp > retry_data_expire)
	    || (now >= retry_record->next_try)
	    || retry_record->expired;

          /* If we haven't reached the retry time, there is one more check
          to do, which is for the ultimate address timeout. */

          if (!ok)
            ok = retry_ultimate_address_timeout(retry_key, addr2->domain,
                retry_record, now);
          }
        }
      else DEBUG(D_retry) debug_printf("no retry record exists\n");
      }

    /* This address is to be delivered. Leave it on the chain. */

    if (ok)
      {
      addr3 = addr2;
      addr2 = addr2->next;
      }

    /* This address is to be deferred. Take it out of the chain, and
    post-process it as complete. Must take it out of the chain first,
    because post processing puts it on another chain. */

    else
      {
      address_item *this = addr2;
      this->message = US"Retry time not yet reached";
      this->basic_errno = ERRNO_LRETRY;
      addr2 = addr3 ? (addr3->next = addr2->next)
		    : (addr = addr2->next);
      post_process_one(this, DEFER, logflags, EXIM_DTYPE_TRANSPORT, 0);
      }
    }

  if (dbm_file) dbfn_close(dbm_file);

  /* If there are no addresses left on the chain, they all deferred. Loop
  for the next set of addresses. */

  if (!addr) continue;

  /* If the transport is limited for parallellism, enforce that here.
  We use a hints DB entry, incremented here and decremented after
  the transport (and any shadow transport) completes. */

  if (tpt_parallel_check(tp, addr, &serialize_key))
    {
    if (expand_string_message)
      {
      logflags |= LOG_PANIC;
      do
	{
	addr = addr->next;
	post_process_one(addr, DEFER, logflags, EXIM_DTYPE_TRANSPORT, 0);
	} while ((addr = addr2));
      }
    continue;			/* Loop for the next set of addresses. */
    }


  /* So, finally, we do have some addresses that can be passed to the
  transport. Before doing so, set up variables that are relevant to a
  single delivery. */

  deliver_set_expansions(addr);

  gettimeofday(&delivery_start, NULL);
  deliver_local(addr, FALSE);
  timesince(&deliver_time, &delivery_start);

  /* If a shadow transport (which must perforce be another local transport), is
  defined, and its condition is met, we must pass the message to the shadow
  too, but only those addresses that succeeded. We do this by making a new
  chain of addresses - also to keep the original chain uncontaminated. We must
  use a chain rather than doing it one by one, because the shadow transport may
  batch.

  NOTE: if the condition fails because of a lookup defer, there is nothing we
  can do! */

  if (  tp->shadow
     && (  !tp->shadow_condition
        || expand_check_condition(tp->shadow_condition, tp->name, US"transport")
     )  )
    {
    transport_instance *stp;
    address_item *shadow_addr = NULL;
    address_item **last = &shadow_addr;

    for (stp = transports; stp; stp = stp->next)
      if (Ustrcmp(stp->name, tp->shadow) == 0) break;

    if (!stp)
      log_write(0, LOG_MAIN|LOG_PANIC, "shadow transport \"%s\" not found ",
        tp->shadow);

    /* Pick off the addresses that have succeeded, and make clones. Put into
    the shadow_message field a pointer to the shadow_message field of the real
    address. */

    else for (addr2 = addr; addr2; addr2 = addr2->next)
      if (addr2->transport_return == OK)
	{
	addr3 = store_get(sizeof(address_item));
	*addr3 = *addr2;
	addr3->next = NULL;
	addr3->shadow_message = US &addr2->shadow_message;
	addr3->transport = stp;
	addr3->transport_return = DEFER;
	addr3->return_filename = NULL;
	addr3->return_file = -1;
	*last = addr3;
	last = &addr3->next;
	}

    /* If we found any addresses to shadow, run the delivery, and stick any
    message back into the shadow_message field in the original. */

    if (shadow_addr)
      {
      int save_count = transport_count;

      DEBUG(D_deliver|D_transport)
        debug_printf(">>>>>>>>>>>>>>>> Shadow delivery >>>>>>>>>>>>>>>>\n");
      deliver_local(shadow_addr, TRUE);

      for(; shadow_addr; shadow_addr = shadow_addr->next)
        {
        int sresult = shadow_addr->transport_return;
        *(uschar **)shadow_addr->shadow_message =
	  sresult == OK
	  ? string_sprintf(" ST=%s", stp->name)
	  : string_sprintf(" ST=%s (%s%s%s)", stp->name,
	      shadow_addr->basic_errno <= 0
	      ? US""
	      : US strerror(shadow_addr->basic_errno),
	      shadow_addr->basic_errno <= 0 || !shadow_addr->message
	      ? US""
	      : US": ",
	      shadow_addr->message
	      ? shadow_addr->message
	      : shadow_addr->basic_errno <= 0
	      ? US"unknown error"
	      : US"");

        DEBUG(D_deliver|D_transport)
          debug_printf("%s shadow transport returned %s for %s\n",
            stp->name,
            sresult == OK ?    "OK" :
            sresult == DEFER ? "DEFER" :
            sresult == FAIL ?  "FAIL" :
            sresult == PANIC ? "PANIC" : "?",
            shadow_addr->address);
        }

      DEBUG(D_deliver|D_transport)
        debug_printf(">>>>>>>>>>>>>>>> End shadow delivery >>>>>>>>>>>>>>>>\n");

      transport_count = save_count;   /* Restore original transport count */
      }
    }

  /* Cancel the expansions that were set up for the delivery. */

  deliver_set_expansions(NULL);

  /* If the transport was parallelism-limited, decrement the hints DB record. */

  if (serialize_key) enq_end(serialize_key);

  /* Now we can process the results of the real transport. We must take each
  address off the chain first, because post_process_one() puts it on another
  chain. */

  for (addr2 = addr; addr2; addr2 = nextaddr)
    {
    int result = addr2->transport_return;
    nextaddr = addr2->next;

    DEBUG(D_deliver|D_transport)
      debug_printf("%s transport returned %s for %s\n",
        tp->name,
        result == OK ?    "OK" :
        result == DEFER ? "DEFER" :
        result == FAIL ?  "FAIL" :
        result == PANIC ? "PANIC" : "?",
        addr2->address);

    /* If there is a retry_record, or if delivery is deferred, build a retry
    item for setting a new retry time or deleting the old retry record from
    the database. These items are handled all together after all addresses
    have been handled (so the database is open just for a short time for
    updating). */

    if (result == DEFER || testflag(addr2, af_lt_retry_exists))
      {
      int flags = result == DEFER ? 0 : rf_delete;
      uschar *retry_key = string_copy(tp->retry_use_local_part
	? addr2->address_retry_key : addr2->domain_retry_key);
      *retry_key = 'T';
      retry_add_item(addr2, retry_key, flags);
      }

    /* Done with this address */

    if (result == OK)
      {
      addr2->more_errno = deliver_time.tv_sec;
      addr2->delivery_usec = deliver_time.tv_usec;
      }
    post_process_one(addr2, result, logflags, EXIM_DTYPE_TRANSPORT, logchar);

    /* If a pipe delivery generated text to be sent back, the result may be
    changed to FAIL, and we must copy this for subsequent addresses in the
    batch. */

    if (addr2->transport_return != result)
      {
      for (addr3 = nextaddr; addr3; addr3 = addr3->next)
        {
        addr3->transport_return = addr2->transport_return;
        addr3->basic_errno = addr2->basic_errno;
        addr3->message = addr2->message;
        }
      result = addr2->transport_return;
      }

    /* Whether or not the result was changed to FAIL, we need to copy the
    return_file value from the first address into all the addresses of the
    batch, so they are all listed in the error message. */

    addr2->return_file = addr->return_file;

    /* Change log character for recording successful deliveries. */

    if (result == OK) logchar = '-';
    }
  }        /* Loop back for next batch of addresses */
}

Project: exim

smtp_fflush(void)
{
if (tls_in.active.sock < 0 && fflush(smtp_out) != 0) smtp_write_error = -1;
return smtp_write_error;
}

Project: exim
File: deliver.c

previously_transported(address_item *addr, BOOL testing)
{
(void)string_format(big_buffer, big_buffer_size, "%s/%s",
  addr->unique + (testflag(addr, af_homonym)? 3:0), addr->transport->name);

if (tree_search(tree_nonrecipients, big_buffer) != 0)
  {
  DEBUG(D_deliver|D_route|D_transport)
    debug_printf("%s was previously delivered (%s transport): discarded\n",
    addr->address, addr->transport->name);
  if (!testing) child_done(addr, tod_stamp(tod_log));
  return TRUE;
  }

return FALSE;
}

Project: exim

smtp_vprintf(const char *format, BOOL more, va_list ap)
{
gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
BOOL yield;

yield = !! string_vformat(&gs, FALSE, format, ap);
string_from_gstring(&gs);

DEBUG(D_receive)
  {
  void *reset_point = store_get(0);
  uschar *msg_copy, *cr, *end;
  msg_copy = string_copy(gs.s);
  end = msg_copy + gs.ptr;
  while ((cr = Ustrchr(msg_copy, '\r')) != NULL)   /* lose CRs */
    memmove(cr, cr + 1, (end--) - cr);
  debug_printf("SMTP>> %s", msg_copy);
  store_reset(reset_point);
  }

if (!yield)
  {
  log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_printf()");
  smtp_closedown(US"Unexpected error");
  exim_exit(EXIT_FAILURE, NULL);
  }

/* If this is the first output for a (non-batch) RCPT command, see if all RCPTs
have had the same. Note: this code is also present in smtp_respond(). It would
be tidier to have it only in one place, but when it was added, it was easier to
do it that way, so as not to have to mess with the code for the RCPT command,
which sometimes uses smtp_printf() and sometimes smtp_respond(). */

if (fl.rcpt_in_progress)
  {
  if (rcpt_smtp_response == NULL)
    rcpt_smtp_response = string_copy(big_buffer);
  else if (fl.rcpt_smtp_response_same &&
           Ustrcmp(rcpt_smtp_response, big_buffer) != 0)
    fl.rcpt_smtp_response_same = FALSE;
  fl.rcpt_in_progress = FALSE;
  }

/* Now write the string */

#ifdef SUPPORT_TLS
if (tls_in.active.sock >= 0)
  {
  if (tls_write(NULL, gs.s, gs.ptr, more) < 0)
    smtp_write_error = -1;
  }
else
#endif

if (fprintf(smtp_out, "%s", gs.s) < 0) smtp_write_error = -1;
}

Project: exim
File: deliver.c

deliver_local(address_item *addr, BOOL shadowing)
{
BOOL use_initgroups;
uid_t uid;
gid_t gid;
int status, len, rc;
int pfd[2];
pid_t pid;
uschar *working_directory;
address_item *addr2;
transport_instance *tp = addr->transport;

/* Set up the return path from the errors or sender address. If the transport
has its own return path setting, expand it and replace the existing value. */

if(addr->prop.errors_address)
  return_path = addr->prop.errors_address;
#ifdef EXPERIMENTAL_SRS
else if (addr->prop.srs_sender)
  return_path = addr->prop.srs_sender;
#endif
else
  return_path = sender_address;

if (tp->return_path)
  {
  uschar *new_return_path = expand_string(tp->return_path);
  if (!new_return_path)
    {
    if (!f.expand_string_forcedfail)
      {
      common_error(TRUE, addr, ERRNO_EXPANDFAIL,
        US"Failed to expand return path \"%s\" in %s transport: %s",
        tp->return_path, tp->name, expand_string_message);
      return;
      }
    }
  else return_path = new_return_path;
  }

/* For local deliveries, one at a time, the value used for logging can just be
set directly, once and for all. */

used_return_path = return_path;

/* Sort out the uid, gid, and initgroups flag. If an error occurs, the message
gets put into the address(es), and the expansions are unset, so we can just
return. */

if (!findugid(addr, tp, &uid, &gid, &use_initgroups)) return;

/* See if either the transport or the address specifies a home directory. A
home directory set in the address may already be expanded; a flag is set to
indicate that. In other cases we must expand it. */

if (  (deliver_home = tp->home_dir)		/* Set in transport, or */
   || (  (deliver_home = addr->home_dir)	/* Set in address and */
      && !testflag(addr, af_home_expanded)	/*   not expanded */
   )  )
  {
  uschar *rawhome = deliver_home;
  deliver_home = NULL;                      /* in case it contains $home */
  if (!(deliver_home = expand_string(rawhome)))
    {
    common_error(TRUE, addr, ERRNO_EXPANDFAIL, US"home directory \"%s\" failed "
      "to expand for %s transport: %s", rawhome, tp->name,
      expand_string_message);
    return;
    }
  if (*deliver_home != '/')
    {
    common_error(TRUE, addr, ERRNO_NOTABSOLUTE, US"home directory path \"%s\" "
      "is not absolute for %s transport", deliver_home, tp->name);
    return;
    }
  }

/* See if either the transport or the address specifies a current directory,
and if so, expand it. If nothing is set, use the home directory, unless it is
also unset in which case use "/", which is assumed to be a directory to which
all users have access. It is necessary to be in a visible directory for some
operating systems when running pipes, as some commands (e.g. "rm" under Solaris
2.5) require this. */

working_directory = tp->current_dir ? tp->current_dir : addr->current_dir;
if (working_directory)
  {
  uschar *raw = working_directory;
  if (!(working_directory = expand_string(raw)))
    {
    common_error(TRUE, addr, ERRNO_EXPANDFAIL, US"current directory \"%s\" "
      "failed to expand for %s transport: %s", raw, tp->name,
      expand_string_message);
    return;
    }
  if (*working_directory != '/')
    {
    common_error(TRUE, addr, ERRNO_NOTABSOLUTE, US"current directory path "
      "\"%s\" is not absolute for %s transport", working_directory, tp->name);
    return;
    }
  }
else working_directory = deliver_home ? deliver_home : US"/";

/* If one of the return_output flags is set on the transport, create and open a
file in the message log directory for the transport to write its output onto.
This is mainly used by pipe transports. The file needs to be unique to the
address. This feature is not available for shadow transports. */

if (  !shadowing
   && (  tp->return_output || tp->return_fail_output
      || tp->log_output || tp->log_fail_output || tp->log_defer_output
   )  )
  {
  uschar * error;

  addr->return_filename =
    spool_fname(US"msglog", message_subdir, message_id,
      string_sprintf("-%d-%d", getpid(), return_count++));

  if ((addr->return_file = open_msglog_file(addr->return_filename, 0400, &error)) < 0)
    {
    common_error(TRUE, addr, errno, US"Unable to %s file for %s transport "
      "to return message: %s", error, tp->name, strerror(errno));
    return;
    }
  }

/* Create the pipe for inter-process communication. */

if (pipe(pfd) != 0)
  {
  common_error(TRUE, addr, ERRNO_PIPEFAIL, US"Creation of pipe failed: %s",
    strerror(errno));
  return;
  }

/* Now fork the process to do the real work in the subprocess, but first
ensure that all cached resources are freed so that the subprocess starts with
a clean slate and doesn't interfere with the parent process. */

search_tidyup();

if ((pid = fork()) == 0)
  {
  BOOL replicate = TRUE;

  /* Prevent core dumps, as we don't want them in users' home directories.
  HP-UX doesn't have RLIMIT_CORE; I don't know how to do this in that
  system. Some experimental/developing systems (e.g. GNU/Hurd) may define
  RLIMIT_CORE but not support it in setrlimit(). For such systems, do not
  complain if the error is "not supported".

  There are two scenarios where changing the max limit has an effect.  In one,
  the user is using a .forward and invoking a command of their choice via pipe;
  for these, we do need the max limit to be 0 unless the admin chooses to
  permit an increased limit.  In the other, the command is invoked directly by
  the transport and is under administrator control, thus being able to raise
  the limit aids in debugging.  So there's no general always-right answer.

  Thus we inhibit core-dumps completely but let individual transports, while
  still root, re-raise the limits back up to aid debugging.  We make the
  default be no core-dumps -- few enough people can use core dumps in
  diagnosis that it's reasonable to make them something that has to be explicitly requested.
  */

#ifdef RLIMIT_CORE
  struct rlimit rl;
  rl.rlim_cur = 0;
  rl.rlim_max = 0;
  if (setrlimit(RLIMIT_CORE, &rl) < 0)
    {
# ifdef SETRLIMIT_NOT_SUPPORTED
    if (errno != ENOSYS && errno != ENOTSUP)
# endif
      log_write(0, LOG_MAIN|LOG_PANIC, "setrlimit(RLIMIT_CORE) failed: %s",
        strerror(errno));
    }
#endif

  /* Reset the random number generator, so different processes don't all
  have the same sequence. */

  random_seed = 0;

  /* If the transport has a setup entry, call this first, while still
  privileged. (Appendfile uses this to expand quota, for example, while
  able to read private files.) */

  if (addr->transport->setup)
    switch((addr->transport->setup)(addr->transport, addr, NULL, uid, gid,
           &(addr->message)))
      {
      case DEFER:
	addr->transport_return = DEFER;
	goto PASS_BACK;

      case FAIL:
	addr->transport_return = PANIC;
	goto PASS_BACK;
      }

  /* Ignore SIGINT and SIGTERM during delivery. Also ignore SIGUSR1, as
  when the process becomes unprivileged, it won't be able to write to the
  process log. SIGHUP is ignored throughout exim, except when it is being
  run as a daemon. */

  signal(SIGINT, SIG_IGN);
  signal(SIGTERM, SIG_IGN);
  signal(SIGUSR1, SIG_IGN);

  /* Close the unwanted half of the pipe, and set close-on-exec for the other
  half - for transports that exec things (e.g. pipe). Then set the required
  gid/uid. */

  (void)close(pfd[pipe_read]);
  (void)fcntl(pfd[pipe_write], F_SETFD, fcntl(pfd[pipe_write], F_GETFD) |
    FD_CLOEXEC);
  exim_setugid(uid, gid, use_initgroups,
    string_sprintf("local delivery to %s <%s> transport=%s", addr->local_part,
      addr->address, addr->transport->name));

  DEBUG(D_deliver)
    {
    address_item *batched;
    debug_printf("  home=%s current=%s\n", deliver_home, working_directory);
    for (batched = addr->next; batched; batched = batched->next)
      debug_printf("additional batched address: %s\n", batched->address);
    }

  /* Set an appropriate working directory. */

  if (Uchdir(working_directory) < 0)
    {
    addr->transport_return = DEFER;
    addr->basic_errno = errno;
    addr->message = string_sprintf("failed to chdir to %s", working_directory);
    }

  /* If successful, call the transport */

  else
    {
    BOOL ok = TRUE;
    set_process_info("delivering %s to %s using %s", message_id,
     addr->local_part, addr->transport->name);

    /* Setting this global in the subprocess means we need never clear it */
    transport_name = addr->transport->name;

    /* If a transport filter has been specified, set up its argument list.
    Any errors will get put into the address, and FALSE yielded. */

    if (addr->transport->filter_command)
      {
      ok = transport_set_up_command(&transport_filter_argv,
        addr->transport->filter_command,
        TRUE, PANIC, addr, US"transport filter", NULL);
      transport_filter_timeout = addr->transport->filter_timeout;
      }
    else transport_filter_argv = NULL;

    if (ok)
      {
      debug_print_string(addr->transport->debug_string);
      replicate = !(addr->transport->info->code)(addr->transport, addr);
      }
    }

  /* Pass the results back down the pipe. If necessary, first replicate the
  status in the top address to the others in the batch. The label is the
  subject of a goto when a call to the transport's setup function fails. We
  pass the pointer to the transport back in case it got changed as a result of
  file_format in appendfile. */

  PASS_BACK:

  if (replicate) replicate_status(addr);
  for (addr2 = addr; addr2; addr2 = addr2->next)
    {
    int i;
    int local_part_length = Ustrlen(addr2->local_part);
    uschar *s;
    int ret;

    if(  (ret = write(pfd[pipe_write], &addr2->transport_return, sizeof(int))) != sizeof(int)
      || (ret = write(pfd[pipe_write], &transport_count, sizeof(transport_count))) != sizeof(transport_count)
      || (ret = write(pfd[pipe_write], &addr2->flags, sizeof(addr2->flags))) != sizeof(addr2->flags)
      || (ret = write(pfd[pipe_write], &addr2->basic_errno,    sizeof(int))) != sizeof(int)
      || (ret = write(pfd[pipe_write], &addr2->more_errno,     sizeof(int))) != sizeof(int)
      || (ret = write(pfd[pipe_write], &addr2->delivery_usec,  sizeof(int))) != sizeof(int)
      || (ret = write(pfd[pipe_write], &addr2->special_action, sizeof(int))) != sizeof(int)
      || (ret = write(pfd[pipe_write], &addr2->transport,
        sizeof(transport_instance *))) != sizeof(transport_instance *)

    /* For a file delivery, pass back the local part, in case the original
    was only part of the final delivery path. This gives more complete
    logging. */

      || (testflag(addr2, af_file)
          && (  (ret = write(pfd[pipe_write], &local_part_length, sizeof(int))) != sizeof(int)
             || (ret = write(pfd[pipe_write], addr2->local_part, local_part_length)) != local_part_length
	     )
	 )
      )
      log_write(0, LOG_MAIN|LOG_PANIC, "Failed writing transport results to pipe: %s",
	ret == -1 ? strerror(errno) : "short write");

    /* Now any messages */

    for (i = 0, s = addr2->message; i < 2; i++, s = addr2->user_message)
      {
      int message_length = s ? Ustrlen(s) + 1 : 0;
      if(  (ret = write(pfd[pipe_write], &message_length, sizeof(int))) != sizeof(int)
        || message_length > 0  && (ret = write(pfd[pipe_write], s, message_length)) != message_length
	)
        log_write(0, LOG_MAIN|LOG_PANIC, "Failed writing transport results to pipe: %s",
	  ret == -1 ? strerror(errno) : "short write");
      }
    }

  /* OK, this process is now done. Free any cached resources that it opened,
  and close the pipe we were writing down before exiting. */

  (void)close(pfd[pipe_write]);
  search_tidyup();
  exit(EXIT_SUCCESS);
  }

/* Back in the main process: panic if the fork did not succeed. This seems
better than returning an error - if forking is failing it is probably best
not to try other deliveries for this message. */

if (pid < 0)
  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Fork failed for local delivery to %s",
    addr->address);

/* Read the pipe to get the delivery status codes and error messages. Our copy
of the writing end must be closed first, as otherwise read() won't return zero
on an empty pipe. We check that a status exists for each address before
overwriting the address structure. If data is missing, the default DEFER status
will remain. Afterwards, close the reading end. */

(void)close(pfd[pipe_write]);

for (addr2 = addr; addr2; addr2 = addr2->next)
  {
  if ((len = read(pfd[pipe_read], &status, sizeof(int))) > 0)
    {
    int i;
    uschar **sptr;

    addr2->transport_return = status;
    len = read(pfd[pipe_read], &transport_count,
      sizeof(transport_count));
    len = read(pfd[pipe_read], &addr2->flags, sizeof(addr2->flags));
    len = read(pfd[pipe_read], &addr2->basic_errno,    sizeof(int));
    len = read(pfd[pipe_read], &addr2->more_errno,     sizeof(int));
    len = read(pfd[pipe_read], &addr2->delivery_usec,  sizeof(int));
    len = read(pfd[pipe_read], &addr2->special_action, sizeof(int));
    len = read(pfd[pipe_read], &addr2->transport,
      sizeof(transport_instance *));

    if (testflag(addr2, af_file))
      {
      int llen;
      if (  read(pfd[pipe_read], &llen, sizeof(int)) != sizeof(int)
	 || llen > 64*4	/* limit from rfc 5821, times I18N factor */
         )
	{
	log_write(0, LOG_MAIN|LOG_PANIC, "bad local_part length read"
	  " from delivery subprocess");
	break;
	}
      /* sanity-checked llen so disable the Coverity error */
      /* coverity[tainted_data] */
      if (read(pfd[pipe_read], big_buffer, llen) != llen)
	{
	log_write(0, LOG_MAIN|LOG_PANIC, "bad local_part read"
	  " from delivery subprocess");
	break;
	}
      big_buffer[llen] = 0;
      addr2->local_part = string_copy(big_buffer);
      }

    for (i = 0, sptr = &addr2->message; i < 2; i++, sptr = &addr2->user_message)
      {
      int message_length;
      len = read(pfd[pipe_read], &message_length, sizeof(int));
      if (message_length > 0)
        {
        len = read(pfd[pipe_read], big_buffer, message_length);
	big_buffer[big_buffer_size-1] = '\0';		/* guard byte */
        if (len > 0) *sptr = string_copy(big_buffer);
        }
      }
    }

  else
    {
    log_write(0, LOG_MAIN|LOG_PANIC, "failed to read delivery status for %s "
      "from delivery subprocess", addr2->unique);
    break;
    }
  }

(void)close(pfd[pipe_read]);

/* Unless shadowing, write all successful addresses immediately to the journal
file, to ensure they are recorded asap. For homonymic addresses, use the base
address plus the transport name. Failure to write the journal is panic-worthy,
but don't stop, as it may prove possible subsequently to update the spool file
in order to record the delivery. */

if (!shadowing)
  {
  for (addr2 = addr; addr2; addr2 = addr2->next)
    if (addr2->transport_return == OK)
      {
      if (testflag(addr2, af_homonym))
	sprintf(CS big_buffer, "%.500s/%s\n", addr2->unique + 3, tp->name);
      else
	sprintf(CS big_buffer, "%.500s\n", addr2->unique);

      /* In the test harness, wait just a bit to let the subprocess finish off
      any debug output etc first. */

      if (f.running_in_test_harness) millisleep(300);

      DEBUG(D_deliver) debug_printf("journalling %s", big_buffer);
      len = Ustrlen(big_buffer);
      if (write(journal_fd, big_buffer, len) != len)
	log_write(0, LOG_MAIN|LOG_PANIC, "failed to update journal for %s: %s",
	  big_buffer, strerror(errno));
      }

  /* Ensure the journal file is pushed out to disk. */

  if (EXIMfsync(journal_fd) < 0)
    log_write(0, LOG_MAIN|LOG_PANIC, "failed to fsync journal: %s",
      strerror(errno));
  }

/* Wait for the process to finish. If it terminates with a non-zero code,
freeze the message (except for SIGTERM, SIGKILL and SIGQUIT), but leave the
status values of all the addresses as they are. Take care to handle the case
when the subprocess doesn't seem to exist. This has been seen on one system
when Exim was called from an MUA that set SIGCHLD to SIG_IGN. When that
happens, wait() doesn't recognize the termination of child processes. Exim now
resets SIGCHLD to SIG_DFL, but this code should still be robust. */

while ((rc = wait(&status)) != pid)
  if (rc < 0 && errno == ECHILD)      /* Process has vanished */
    {
    log_write(0, LOG_MAIN, "%s transport process vanished unexpectedly",
      addr->transport->driver_name);
    status = 0;
    break;
    }

if ((status & 0xffff) != 0)
  {
  int msb = (status >> 8) & 255;
  int lsb = status & 255;
  int code = (msb == 0)? (lsb & 0x7f) : msb;
  if (msb != 0 || (code != SIGTERM && code != SIGKILL && code != SIGQUIT))
    addr->special_action = SPECIAL_FREEZE;
  log_write(0, LOG_MAIN|LOG_PANIC, "%s transport process returned non-zero "
    "status 0x%04x: %s %d",
    addr->transport->driver_name,
    status,
    msb == 0 ? "terminated by signal" : "exit code",
    code);
  }

/* If SPECIAL_WARN is set in the top address, send a warning message. */

if (addr->special_action == SPECIAL_WARN && addr->transport->warn_message)
  {
  int fd;
  uschar *warn_message;
  pid_t pid;

  DEBUG(D_deliver) debug_printf("Warning message requested by transport\n");

  if (!(warn_message = expand_string(addr->transport->warn_message)))
    log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand \"%s\" (warning "
      "message for %s transport): %s", addr->transport->warn_message,
      addr->transport->name, expand_string_message);

  else if ((pid = child_open_exim(&fd)) > 0)
    {
    FILE *f = fdopen(fd, "wb");
    if (errors_reply_to && !contains_header(US"Reply-To", warn_message))
      fprintf(f, "Reply-To: %s\n", errors_reply_to);
    fprintf(f, "Auto-Submitted: auto-replied\n");
    if (!contains_header(US"From", warn_message))
      moan_write_from(f);
    fprintf(f, "%s", CS warn_message);

    /* Close and wait for child process to complete, without a timeout. */

    (void)fclose(f);
    (void)child_close(pid, 0);
    }

  addr->special_action = SPECIAL_NONE;
  }
}

Project: exim

host_build_ifacelist(const uschar *list, uschar *name)
{
int sep = 0;
uschar *s;
ip_address_item * yield = NULL, * last = NULL, * next;

while ((s = string_nextinlist(&list, &sep, NULL, 0)))
  {
  int ipv;
  int port = host_address_extract_port(s);            /* Leaves just the IP address */

  if (!(ipv = string_is_ip_address(s, NULL)))
    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Malformed IP address \"%s\" in %s",
      s, name);

  /* Skip IPv6 addresses if IPv6 is disabled. */

  if (disable_ipv6 && ipv == 6) continue;

  /* This use of strcpy() is OK because we have checked that s is a valid IP
  address above. The field in the ip_address_item is large enough to hold an
  IPv6 address. */

  next = store_get(sizeof(ip_address_item));
  next->next = NULL;
  Ustrcpy(next->address, s);
  next->port = port;
  next->v6_include_v4 = FALSE;

  if (!yield)
    yield = last = next;
  else
    {
    last->next = next;
    last = next;
    }
  }

return yield;
}

Project: exim
File: deliver.c

contains_header(uschar *hdr, uschar *hstring)
{
int len = Ustrlen(hdr);
uschar *p = hstring;
while (*p != 0)
  {
  if (strncmpic(p, hdr, len) == 0)
    {
    p += len;
    while (*p == ' ' || *p == '\t') p++;
    if (*p == ':') return TRUE;
    }
  while (*p != 0 && *p != '\n') p++;
  if (*p == '\n') p++;
  }
return FALSE;
}

Project: exim

debug_logging_activate(uschar *tag_name, uschar *opts)
{
int fd = -1;

if (debug_file)
  {
  debug_printf("DEBUGGING ACTIVATED FROM WITHIN CONFIG.\n"
      "DEBUG: Tag=\"%s\" opts=\"%s\"\n", tag_name, opts ? opts : US"");
  return;
  }

if (tag_name != NULL && (Ustrchr(tag_name, '/') != NULL))
  {
  log_write(0, LOG_MAIN|LOG_PANIC, "debug tag may not contain a '/' in: %s",
      tag_name);
  return;
  }

debug_selector = D_default;
if (opts)
  decode_bits(&debug_selector, 1, debug_notall, opts,
      debug_options, debug_options_count, US"debug", DEBUG_FROM_CONFIG);

/* When activating from a transport process we may never have logged at all
resulting in certain setup not having been done.  Hack this for now so we
do not segfault; note that nondefault log locations will not work */

if (!*file_path) set_file_path();

open_log(&fd, lt_debug, tag_name);

if (fd != -1)
  debug_file = fdopen(fd, "w");
else
  log_write(0, LOG_MAIN|LOG_PANIC, "unable to open debug log");
}

Project: exim
File: deliver.c

event_raise(uschar * action, const uschar * event, uschar * ev_data)
{
uschar * s;
if (action)
  {
  DEBUG(D_deliver)
    debug_printf("Event(%s): event_action=|%s| delivery_IP=%s\n",
      event,
      action, deliver_host_address);

  event_name = event;
  event_data = ev_data;

  if (!(s = expand_string(action)) && *expand_string_message)
    log_write(0, LOG_MAIN|LOG_PANIC,
      "failed to expand event_action %s in %s: %s\n",
      event, transport_name ? transport_name : US"main", expand_string_message);

  event_name = event_data = NULL;

  /* If the expansion returns anything but an empty string, flag for
  the caller to modify his normal processing
  */
  if (s && *s)
    {
    DEBUG(D_deliver)
      debug_printf("Event(%s): event_action returned \"%s\"\n", event, s);
    return s;
    }
  }
return NULL;
}

Project: exim

smtp_refill(unsigned lim)
{
int rc, save_errno;
if (!smtp_out) return FALSE;
fflush(smtp_out);
if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);

/* Limit amount read, so non-message data is not fed to DKIM.
Take care to not touch the safety NUL at the end of the buffer. */

rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE-1, lim));
save_errno = errno;
if (smtp_receive_timeout > 0) ALARM_CLR(0);
if (rc <= 0)
  {
  /* Must put the error text in fixed store, because this might be during
  header reading, where it releases unused store above the header. */
  if (rc < 0)
    {
    if (had_command_timeout)		/* set by signal handler */
      smtp_command_timeout_exit();	/* does not return */
    if (had_command_sigterm)
      smtp_command_sigterm_exit();
    if (had_data_timeout)
      smtp_data_timeout_exit();
    if (had_data_sigint)
      smtp_data_sigint_exit();

    smtp_had_error = save_errno;
    smtp_read_error = string_copy_malloc(
      string_sprintf(" (error: %s)", strerror(save_errno)));
    }
  else
    smtp_had_eof = 1;
  return FALSE;
  }
#ifndef DISABLE_DKIM
dkim_exim_verify_feed(smtp_inbuffer, rc);
#endif
smtp_inend = smtp_inbuffer + rc;
smtp_inptr = smtp_inbuffer;
return TRUE;
}

Project: exim

debug_print_string(uschar *debug_string)
{
if (!debug_string) return;
HDEBUG(D_any|D_v)
  {
  uschar *s = expand_string(debug_string);
  if (!s)
    debug_printf("failed to expand debug_output \"%s\": %s\n", debug_string,
      expand_string_message);
  else if (s[0] != 0)
    debug_printf("%s%s", s, (s[Ustrlen(s)-1] == '\n')? "" : "\n");
  }
}

Project: exim

fn_hdrs_added(void)
{
gstring * g = NULL;
header_line * h;

for (h = acl_added_headers; h; h = h->next)
  {
  int i = h->slen;
  if (h->text[i-1] == '\n') i--;
  g = string_append_listele_n(g, '\n', h->text, i);
  }

return g ? g->s : NULL;
}

Project: exim

host_build_sender_fullhost(void)
{
BOOL show_helo = TRUE;
uschar * address, * fullhost, * rcvhost, * reset_point;
int len;

if (!sender_host_address) return;

reset_point = store_get(0);

/* Set up address, with or without the port. After discussion, it seems that
the only format that doesn't cause trouble is [aaaa]:pppp. However, we can't
use this directly as the first item for Received: because it ain't an RFC 2822
domain. Sigh. */

address = string_sprintf("[%s]:%d", sender_host_address, sender_host_port);
if (!LOGGING(incoming_port) || sender_host_port <= 0)
  *(Ustrrchr(address, ':')) = 0;

/* If there's no EHLO/HELO data, we can't show it. */

if (!sender_helo_name) show_helo = FALSE;

/* If HELO/EHLO was followed by an IP literal, it's messy because of two
features of IPv6. Firstly, there's the "IPv6:" prefix (Exim is liberal and
doesn't require this, for historical reasons). Secondly, IPv6 addresses may not
be given in canonical form, so we have to canonicalize them before comparing. As
it happens, the code works for both IPv4 and IPv6. */

else if (sender_helo_name[0] == '[' &&
         sender_helo_name[(len=Ustrlen(sender_helo_name))-1] == ']')
  {
  int offset = 1;
  uschar *helo_ip;

  if (strncmpic(sender_helo_name + 1, US"IPv6:", 5) == 0) offset += 5;
  if (strncmpic(sender_helo_name + 1, US"IPv4:", 5) == 0) offset += 5;

  helo_ip = string_copyn(sender_helo_name + offset, len - offset - 1);

  if (string_is_ip_address(helo_ip, NULL) != 0)
    {
    int x[4], y[4];
    int sizex, sizey;
    uschar ipx[48], ipy[48];    /* large enough for full IPv6 */

    sizex = host_aton(helo_ip, x);
    sizey = host_aton(sender_host_address, y);

    (void)host_nmtoa(sizex, x, -1, ipx, ':');
    (void)host_nmtoa(sizey, y, -1, ipy, ':');

    if (strcmpic(ipx, ipy) == 0) show_helo = FALSE;
    }
  }

/* Host name is not verified */

if (!sender_host_name)
  {
  uschar *portptr = Ustrstr(address, "]:");
  gstring * g;
  int adlen;    /* Sun compiler doesn't like ++ in initializers */

  adlen = portptr ? (++portptr - address) : Ustrlen(address);
  fullhost = sender_helo_name
    ? string_sprintf("(%s) %s", sender_helo_name, address)
    : address;

  g = string_catn(NULL, address, adlen);

  if (sender_ident || show_helo || portptr)
    {
    int firstptr;
    g = string_catn(g, US" (", 2);
    firstptr = g->ptr;

    if (portptr)
      g = string_append(g, 2, US"port=", portptr + 1);

    if (show_helo)
      g = string_append(g, 2,
        firstptr == g->ptr ? US"helo=" : US" helo=", sender_helo_name);

    if (sender_ident)
      g = string_append(g, 2,
        firstptr == g->ptr ? US"ident=" : US" ident=", sender_ident);

    g = string_catn(g, US")", 1);
    }

  rcvhost = string_from_gstring(g);
  }

/* Host name is known and verified. Unless we've already found that the HELO
data matches the IP address, compare it with the name. */

else
  {
  if (show_helo && strcmpic(sender_host_name, sender_helo_name) == 0)
    show_helo = FALSE;

  if (show_helo)
    {
    fullhost = string_sprintf("%s (%s) %s", sender_host_name,
      sender_helo_name, address);
    rcvhost = sender_ident
      ?  string_sprintf("%s\n\t(%s helo=%s ident=%s)", sender_host_name,
        address, sender_helo_name, sender_ident)
      : string_sprintf("%s (%s helo=%s)", sender_host_name,
        address, sender_helo_name);
    }
  else
    {
    fullhost = string_sprintf("%s %s", sender_host_name, address);
    rcvhost = sender_ident
      ?  string_sprintf("%s (%s ident=%s)", sender_host_name, address,
        sender_ident)
      : string_sprintf("%s (%s)", sender_host_name, address);
    }
  }

if (sender_fullhost) store_free(sender_fullhost);
sender_fullhost = string_copy_malloc(fullhost);
if (sender_rcvhost) store_free(sender_rcvhost);
sender_rcvhost = string_copy_malloc(rcvhost);

store_reset(reset_point);

DEBUG(D_host_lookup) debug_printf("sender_fullhost = %s\n", sender_fullhost);
DEBUG(D_host_lookup) debug_printf("sender_rcvhost = %s\n", sender_rcvhost);
}

Project: exim

void lmtp_transport_init(transport_instance *tblock) {}

Project: exim

authres_smtpauth(gstring * g)
{
if (!sender_host_authenticated)
  return g;

g = string_append(g, 2, US";\n\tauth=pass (", sender_host_auth_pubname);

if (Ustrcmp(sender_host_auth_pubname, "tls") != 0)
  g = string_append(g, 2, US") smtp.auth=", authenticated_id);
else if (authenticated_id)
  g = string_append(g, 2, US") x509.auth=", authenticated_id);
else
  g = string_catn(g, US") reason=x509.auth", 17);

if (authenticated_sender)
  g = string_append(g, 2, US" smtp.mailfrom=", authenticated_sender);
return g;
}

Project: exim
File: deliver.c

next_emf(FILE *f, uschar *which)
{
uschar *yield;
gstring * para;
uschar buffer[256];

if (!f) return NULL;

if (!Ufgets(buffer, sizeof(buffer), f) || Ustrcmp(buffer, "****\n") == 0)
  return NULL;

para = string_get(256);
for (;;)
  {
  para = string_cat(para, buffer);
  if (!Ufgets(buffer, sizeof(buffer), f) || Ustrcmp(buffer, "****\n") == 0)
    break;
  }
if ((yield = expand_string(string_from_gstring(para))))
  return yield;

log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand string from "
  "bounce_message_file or warn_message_file (%s): %s", which,
  expand_string_message);
return NULL;
}

Project: exim
File: deliver.c

print_address_information(address_item *addr, FILE *f, uschar *si, uschar *sc,
  uschar *se)
{
BOOL yield = TRUE;
uschar *printed = US"";
address_item *ancestor = addr;
while (ancestor->parent) ancestor = ancestor->parent;

fprintf(f, "%s", CS si);

if (addr->parent && testflag(addr, af_hide_child))
  {
  printed = US"an undisclosed address";
  yield = FALSE;
  }
else if (!testflag(addr, af_pfr) || !addr->parent)
  printed = addr->address;

else
  {
  uschar *s = addr->address;
  uschar *ss;

  if (addr->address[0] == '>') { ss = US"mail"; s++; }
  else if (addr->address[0] == '|') ss = US"pipe";
  else ss = US"save";

  fprintf(f, "%s to %s%sgenerated by ", ss, s, sc);
  printed = addr->parent->address;
  }

fprintf(f, "%s", CS string_printing(printed));

if (ancestor != addr)
  {
  uschar *original = ancestor->onetime_parent;
  if (!original) original= ancestor->address;
  if (strcmpic(original, printed) != 0)
    fprintf(f, "%s(%sgenerated from %s)", sc,
      ancestor != addr->parent ? "ultimately " : "",
      string_printing(original));
  }

if (addr->host_used)
  fprintf(f, "\n    host %s [%s]",
	  addr->host_used->name, addr->host_used->address);

fprintf(f, "%s", CS se);
return yield;
}

Project: exim

host_nmtoa(int count, int *binary, int mask, uschar *buffer, int sep)
{
int i, j;
uschar *tt = buffer;

if (count == 1)
  {
  j = binary[0];
  for (i = 24; i >= 0; i -= 8)
    tt += sprintf(CS tt, "%d.", (j >> i) & 255);
  }
else
  for (i = 0; i < 4; i++)
    {
    j = binary[i];
    tt += sprintf(CS tt, "%04x%c%04x%c", (j >> 16) & 0xffff, sep, j & 0xffff, sep);
    }

tt--;   /* lose final separator */

if (mask < 0)
  *tt = 0;
else
  tt += sprintf(CS tt, "/%d", mask);

return tt - buffer;
}

Project: exim

debug_print_tree(tree_node *p)
{
int i;
for (i = 0; i < tree_printlinesize; i++) tree_printline[i] = ' ';
if (!p) debug_printf("Empty Tree\n"); else tree_printsub(p, 0, 0);
debug_printf("---- End of tree ----\n");
}

Project: exim

d_log_interface(gstring * g)
{
if (LOGGING(incoming_interface) && LOGGING(outgoing_interface)
    && sending_ip_address)
  {
  g = string_fmt_append(g, " I=[%s]", sending_ip_address);
  if (LOGGING(outgoing_port))
    g = string_fmt_append(g, "%d", sending_port);
  }
return g;
}

Project: exim

decode_bits(unsigned int *selector, size_t selsize, int *notall,
  uschar *string, bit_table *options, int count, uschar *which, int flags)
{
uschar *errmsg;
if (string == NULL) return;

if (*string == '=')
  {
  char *end;    /* Not uschar */
  memset(selector, 0, sizeof(*selector)*selsize);
  *selector = strtoul(CS string+1, &end, 0);
  if (*end == 0) return;
  errmsg = string_sprintf("malformed numeric %s_selector setting: %s", which,
    string);
  goto ERROR_RETURN;
  }

/* Handle symbolic setting */

else for(;;)
  {
  BOOL adding;
  uschar *s;
  int len;
  bit_table *start, *end;

  while (isspace(*string)) string++;
  if (*string == 0) return;

  if (*string != '+' && *string != '-')
    {
    errmsg = string_sprintf("malformed %s_selector setting: "
      "+ or - expected but found \"%s\"", which, string);
    goto ERROR_RETURN;
    }

  adding = *string++ == '+';
  s = string;
  while (isalnum(*string) || *string == '_') string++;
  len = string - s;

  start = options;
  end = options + count;

  while (start < end)
    {
    bit_table *middle = start + (end - start)/2;
    int c = Ustrncmp(s, middle->name, len);
    if (c == 0)
      {
      if (middle->name[len] != 0) c = -1; else
        {
        unsigned int bit = middle->bit;

	if (bit == -1)
	  {
	  if (adding)
	    {
	    memset(selector, -1, sizeof(*selector)*selsize);
	    bits_clear(selector, selsize, notall);
	    }
	  else
	    memset(selector, 0, sizeof(*selector)*selsize);
	  }
	else if (adding)
	  BIT_SET(selector, selsize, bit);
	else
	  BIT_CLEAR(selector, selsize, bit);

        break;  /* Out of loop to match selector name */
        }
      }
    if (c < 0) end = middle; else start = middle + 1;
    }  /* Loop to match selector name */

  if (start >= end)
    {
    errmsg = string_sprintf("unknown %s_selector setting: %c%.*s", which,
      adding? '+' : '-', len, s);
    goto ERROR_RETURN;
    }
  }    /* Loop for selector names */

/* Handle disasters */

ERROR_RETURN:
if (Ustrcmp(which, "debug") == 0)
  {
  if (flags & DEBUG_FROM_CONFIG)
    {
    log_write(0, LOG_CONFIG|LOG_PANIC, "%s", errmsg);
    return;
    }
  fprintf(stderr, "exim: %s\n", errmsg);
  exit(EXIT_FAILURE);
  }
else log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "%s", errmsg);
}

Project: exim
File: deliver.c

do_duplicate_check(address_item **anchor)
{
address_item *addr;
while ((addr = *anchor))
  {
  tree_node *tnode;
  if (testflag(addr, af_pfr))
    {
    anchor = &(addr->next);
    }
  else if ((tnode = tree_search(tree_duplicates, addr->unique)))
    {
    DEBUG(D_deliver|D_route)
      debug_printf("%s is a duplicate address: discarded\n", addr->unique);
    *anchor = addr->next;
    addr->dupof = tnode->data.ptr;
    addr->next = addr_duplicate;
    addr_duplicate = addr;
    }
  else
    {
    tree_add_duplicate(addr->unique, addr);
    anchor = &(addr->next);
    }
  }
}

Project: exim

int main(int argc, char **cargv)
{
host_item h;
int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
BOOL byname = FALSE;
BOOL qualify_single = TRUE;
BOOL search_parents = FALSE;
BOOL request_dnssec = FALSE;
BOOL require_dnssec = FALSE;
uschar **argv = USS cargv;
uschar buffer[256];

disable_ipv6 = FALSE;
primary_hostname = US"";
store_pool = POOL_MAIN;
debug_selector = D_host_lookup|D_interface;
debug_file = stdout;
debug_fd = fileno(debug_file);

printf("Exim stand-alone host functions test\n");

host_find_interfaces();
debug_selector = D_host_lookup | D_dns;

if (argc > 1) primary_hostname = argv[1];

/* So that debug level changes can be done first */

dns_init(qualify_single, search_parents, FALSE);

printf("Testing host lookup\n");
printf("> ");
while (Ufgets(buffer, 256, stdin) != NULL)
  {
  int rc;
  int len = Ustrlen(buffer);
  uschar *fully_qualified_name;

  while (len > 0 && isspace(buffer[len-1])) len--;
  buffer[len] = 0;

  if (Ustrcmp(buffer, "q") == 0) break;

  if (Ustrcmp(buffer, "byname") == 0) byname = TRUE;
  else if (Ustrcmp(buffer, "no_byname") == 0) byname = FALSE;
  else if (Ustrcmp(buffer, "a_only") == 0) whichrrs = HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
  else if (Ustrcmp(buffer, "mx_only") == 0) whichrrs = HOST_FIND_BY_MX;
  else if (Ustrcmp(buffer, "srv_only") == 0) whichrrs = HOST_FIND_BY_SRV;
  else if (Ustrcmp(buffer, "srv+a") == 0)
    whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
  else if (Ustrcmp(buffer, "srv+mx") == 0)
    whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_MX;
  else if (Ustrcmp(buffer, "srv+mx+a") == 0)
    whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_MX | HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
  else if (Ustrcmp(buffer, "qualify_single")    == 0) qualify_single = TRUE;
  else if (Ustrcmp(buffer, "no_qualify_single") == 0) qualify_single = FALSE;
  else if (Ustrcmp(buffer, "search_parents")    == 0) search_parents = TRUE;
  else if (Ustrcmp(buffer, "no_search_parents") == 0) search_parents = FALSE;
  else if (Ustrcmp(buffer, "request_dnssec")    == 0) request_dnssec = TRUE;
  else if (Ustrcmp(buffer, "no_request_dnssec") == 0) request_dnssec = FALSE;
  else if (Ustrcmp(buffer, "require_dnssec")    == 0) require_dnssec = TRUE;
  else if (Ustrcmp(buffer, "no_require_dnssec") == 0) require_dnssec = FALSE;
  else if (Ustrcmp(buffer, "test_harness") == 0)
    f.running_in_test_harness = !f.running_in_test_harness;
  else if (Ustrcmp(buffer, "ipv6") == 0) disable_ipv6 = !disable_ipv6;
  else if (Ustrcmp(buffer, "res_debug") == 0)
    {
    _res.options ^= RES_DEBUG;
    }
  else if (Ustrncmp(buffer, "retrans", 7) == 0)
    {
    (void)sscanf(CS(buffer+8), "%d", &dns_retrans);
    _res.retrans = dns_retrans;
    }
  else if (Ustrncmp(buffer, "retry", 5) == 0)
    {
    (void)sscanf(CS(buffer+6), "%d", &dns_retry);
    _res.retry = dns_retry;
    }
  else
    {
    int flags = whichrrs;
    dnssec_domains d;

    h.name = buffer;
    h.next = NULL;
    h.mx = MX_NONE;
    h.port = PORT_NONE;
    h.status = hstatus_unknown;
    h.why = hwhy_unknown;
    h.address = NULL;

    if (qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
    if (search_parents) flags |= HOST_FIND_SEARCH_PARENTS;

    d.request = request_dnssec ? &h.name : NULL;
    d.require = require_dnssec ? &h.name : NULL;

    rc = byname
      ? host_find_byname(&h, NULL, flags, &fully_qualified_name, TRUE)
      : host_find_bydns(&h, NULL, flags, US"smtp", NULL, NULL,
			&d, &fully_qualified_name, NULL);

    switch (rc)
      {
      case HOST_FIND_FAILED:	printf("Failed\n");	break;
      case HOST_FIND_AGAIN:	printf("Again\n");	break;
      case HOST_FIND_SECURITY:	printf("Security\n");	break;
      case HOST_FOUND_LOCAL:	printf("Local\n");	break;
      }
    }

  printf("\n> ");
  }

printf("Testing host_aton\n");
printf("> ");
while (Ufgets(buffer, 256, stdin) != NULL)
  {
  int i;
  int x[4];
  int len = Ustrlen(buffer);

  while (len > 0 && isspace(buffer[len-1])) len--;
  buffer[len] = 0;

  if (Ustrcmp(buffer, "q") == 0) break;

  len = host_aton(buffer, x);
  printf("length = %d ", len);
  for (i = 0; i < len; i++)
    {
    printf("%04x ", (x[i] >> 16) & 0xffff);
    printf("%04x ", x[i] & 0xffff);
    }
  printf("\n> ");
  }

printf("\n");

printf("Testing host_name_lookup\n");
printf("> ");
while (Ufgets(buffer, 256, stdin) != NULL)
  {
  int len = Ustrlen(buffer);
  while (len > 0 && isspace(buffer[len-1])) len--;
  buffer[len] = 0;
  if (Ustrcmp(buffer, "q") == 0) break;
  sender_host_address = buffer;
  sender_host_name = NULL;
  sender_host_aliases = NULL;
  host_lookup_msg = US"";
  host_lookup_failed = FALSE;
  if (host_name_lookup() == FAIL)  /* Debug causes printing */
    printf("Lookup failed:%s\n", host_lookup_msg);
  printf("\n> ");
  }

printf("\n");

return 0;
}

Project: exim

function_store_free(void *block)
{
store_free(block);
}

Project: exim

smtp_write_command(void * sx, int mode, const char *format, ...)
{
smtp_outblock * outblock = &((smtp_context *)sx)->outblock;
int rc = 0;

if (format)
  {
  gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
  va_list ap;

  va_start(ap, format);
  if (!string_vformat(&gs, FALSE, CS format, ap))
    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing "
      "SMTP");
  va_end(ap);
  string_from_gstring(&gs);

  if (gs.ptr > outblock->buffersize)
    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing "
      "SMTP");

  if (gs.ptr > outblock->buffersize - (outblock->ptr - outblock->buffer))
    {
    rc = outblock->cmd_count;                 /* flush resets */
    if (!flush_buffer(outblock, SCMD_FLUSH)) return -1;
    }

  Ustrncpy(CS outblock->ptr, gs.s, gs.ptr);
  outblock->ptr += gs.ptr;
  outblock->cmd_count++;
  gs.ptr -= 2; string_from_gstring(&gs); /* remove \r\n for error message */

  /* We want to hide the actual data sent in AUTH transactions from reflections
  and logs. While authenticating, a flag is set in the outblock to enable this.
  The AUTH command itself gets any data flattened. Other lines are flattened
  completely. */

  if (outblock->authenticating)
    {
    uschar *p = big_buffer;
    if (Ustrncmp(big_buffer, "AUTH ", 5) == 0)
      {
      p += 5;
      while (isspace(*p)) p++;
      while (!isspace(*p)) p++;
      while (isspace(*p)) p++;
      }
    while (*p != 0) *p++ = '*';
    }

  HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> %s\n", big_buffer);
  }

if (mode != SCMD_BUFFER)
  {
  rc += outblock->cmd_count;                /* flush resets */
  if (!flush_buffer(outblock, mode)) return -1;
  }

return rc;
}

Project: exim

usr1_handler(int sig)
{
int fd;

os_restarting_signal(sig, usr1_handler);

if ((fd = Uopen(process_log_path, O_APPEND|O_WRONLY, LOG_MODE)) < 0)
  {
  /* If we are already running as the Exim user, try to create it in the
  current process (assuming spool_directory exists). Otherwise, if we are
  root, do the creation in an exim:exim subprocess. */

  int euid = geteuid();
  if (euid == exim_uid)
    fd = Uopen(process_log_path, O_CREAT|O_APPEND|O_WRONLY, LOG_MODE);
  else if (euid == root_uid)
    fd = log_create_as_exim(process_log_path);
  }

/* If we are neither exim nor root, or if we failed to create the log file,
give up. There is not much useful we can do with errors, since we don't want
to disrupt whatever is going on outside the signal handler. */

if (fd < 0) return;

(void)write(fd, process_info, process_info_len);
(void)close(fd);
}

Project: exim
File: deliver.c

d_tlslog(gstring * s, address_item * addr)
{
if (LOGGING(tls_cipher) && addr->cipher)
  s = string_append(s, 2, US" X=", addr->cipher);
if (LOGGING(tls_certificate_verified) && addr->cipher)
  s = string_append(s, 2, US" CV=",
    testflag(addr, af_cert_verified)
    ?
#ifdef SUPPORT_DANE
      testflag(addr, af_dane_verified)
    ? "dane"
    :
#endif
      "yes"
    : "no");
if (LOGGING(tls_peerdn) && addr->peerdn)
  s = string_append(s, 3, US" DN=\"", string_printing(addr->peerdn), US"\"");
return s;
}

Project: exim
File: deliver.c

par_wait(void)
{
int poffset, status;
address_item *addr, *addrlist;
pid_t pid;

set_process_info("delivering %s: waiting for a remote delivery subprocess "
  "to finish", message_id);

/* Loop until either a subprocess completes, or there are no subprocesses in
existence - in which case give an error return. We cannot proceed just by
waiting for a completion, because a subprocess may have filled up its pipe, and
be waiting for it to be emptied. Therefore, if no processes have finished, we
wait for one of the pipes to acquire some data by calling select(), with a
timeout just in case.

The simple approach is just to iterate after reading data from a ready pipe.
This leads to non-ideal behaviour when the subprocess has written its final Z
item, closed the pipe, and is in the process of exiting (the common case). A
call to waitpid() yields nothing completed, but select() shows the pipe ready -
reading it yields EOF, so you end up with busy-waiting until the subprocess has
actually finished.

To avoid this, if all the data that is needed has been read from a subprocess
after select(), an explicit wait() for it is done. We know that all it is doing
is writing to the pipe and then exiting, so the wait should not be long.

The non-blocking waitpid() is to some extent just insurance; if we could
reliably detect end-of-file on the pipe, we could always know when to do a
blocking wait() for a completed process. However, because some systems use
NDELAY, which doesn't distinguish between EOF and pipe empty, it is easier to
use code that functions without the need to recognize EOF.

There's a double loop here just in case we end up with a process that is not in
the list of remote delivery processes. Something has obviously gone wrong if
this is the case. (For example, a process that is incorrectly left over from
routing or local deliveries might be found.) The damage can be minimized by
looping back and looking for another process. If there aren't any, the error
return will happen. */

for (;;)   /* Normally we do not repeat this loop */
  {
  while ((pid = waitpid(-1, &status, WNOHANG)) <= 0)
    {
    struct timeval tv;
    fd_set select_pipes;
    int maxpipe, readycount;

    /* A return value of -1 can mean several things. If errno != ECHILD, it
    either means invalid options (which we discount), or that this process was
    interrupted by a signal. Just loop to try the waitpid() again.

    If errno == ECHILD, waitpid() is telling us that there are no subprocesses
    in existence. This should never happen, and is an unexpected error.
    However, there is a nasty complication when running under Linux. If "strace
    -f" is being used under Linux to trace this process and its children,
    subprocesses are "stolen" from their parents and become the children of the
    tracing process. A general wait such as the one we've just obeyed returns
    as if there are no children while subprocesses are running. Once a
    subprocess completes, it is restored to the parent, and waitpid(-1) finds
    it. Thanks to Joachim Wieland for finding all this out and suggesting a
    palliative.

    This does not happen using "truss" on Solaris, nor (I think) with other
    tracing facilities on other OS. It seems to be specific to Linux.

    What we do to get round this is to use kill() to see if any of our
    subprocesses are still in existence. If kill() gives an OK return, we know
    it must be for one of our processes - it can't be for a re-use of the pid,
    because if our process had finished, waitpid() would have found it. If any
    of our subprocesses are in existence, we proceed to use select() as if
    waitpid() had returned zero. I think this is safe. */

    if (pid < 0)
      {
      if (errno != ECHILD) continue;   /* Repeats the waitpid() */

      DEBUG(D_deliver)
        debug_printf("waitpid() returned -1/ECHILD: checking explicitly "
          "for process existence\n");

      for (poffset = 0; poffset < remote_max_parallel; poffset++)
        {
        if ((pid = parlist[poffset].pid) != 0 && kill(pid, 0) == 0)
          {
          DEBUG(D_deliver) debug_printf("process %d still exists: assume "
            "stolen by strace\n", (int)pid);
          break;   /* With poffset set */
          }
        }

      if (poffset >= remote_max_parallel)
        {
        DEBUG(D_deliver) debug_printf("*** no delivery children found\n");
        return NULL;   /* This is the error return */
        }
      }

    /* A pid value greater than 0 breaks the "while" loop. A negative value has
    been handled above. A return value of zero means that there is at least one
    subprocess, but there are no completed subprocesses. See if any pipes are
    ready with any data for reading. */

    DEBUG(D_deliver) debug_printf("selecting on subprocess pipes\n");

    maxpipe = 0;
    FD_ZERO(&select_pipes);
    for (poffset = 0; poffset < remote_max_parallel; poffset++)
      if (parlist[poffset].pid != 0)
        {
        int fd = parlist[poffset].fd;
        FD_SET(fd, &select_pipes);
        if (fd > maxpipe) maxpipe = fd;
        }

    /* Stick in a 60-second timeout, just in case. */

    tv.tv_sec = 60;
    tv.tv_usec = 0;

    readycount = select(maxpipe + 1, (SELECT_ARG2_TYPE *)&select_pipes,
         NULL, NULL, &tv);

    /* Scan through the pipes and read any that are ready; use the count
    returned by select() to stop when there are no more. Select() can return
    with no processes (e.g. if interrupted). This shouldn't matter.

    If par_read_pipe() returns TRUE, it means that either the terminating Z was
    read, or there was a disaster. In either case, we are finished with this
    process. Do an explicit wait() for the process and break the main loop if
    it succeeds.

    It turns out that we have to deal with the case of an interrupted system
    call, which can happen on some operating systems if the signal handling is
    set up to do that by default. */

    for (poffset = 0;
         readycount > 0 && poffset < remote_max_parallel;
         poffset++)
      {
      if (  (pid = parlist[poffset].pid) != 0
         && FD_ISSET(parlist[poffset].fd, &select_pipes)
	 )
        {
        readycount--;
        if (par_read_pipe(poffset, FALSE))    /* Finished with this pipe */
          for (;;)                            /* Loop for signals */
            {
            pid_t endedpid = waitpid(pid, &status, 0);
            if (endedpid == pid) goto PROCESS_DONE;
            if (endedpid != (pid_t)(-1) || errno != EINTR)
              log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Unexpected error return "
                "%d (errno = %d) from waitpid() for process %d",
                (int)endedpid, errno, (int)pid);
            }
        }
      }

    /* Now go back and look for a completed subprocess again. */
    }

  /* A completed process was detected by the non-blocking waitpid(). Find the
  data block that corresponds to this subprocess. */

  for (poffset = 0; poffset < remote_max_parallel; poffset++)
    if (pid == parlist[poffset].pid) break;

  /* Found the data block; this is a known remote delivery process. We don't
  need to repeat the outer loop. This should be what normally happens. */

  if (poffset < remote_max_parallel) break;

  /* This situation is an error, but it's probably better to carry on looking
  for another process than to give up (as we used to do). */

  log_write(0, LOG_MAIN|LOG_PANIC, "Process %d finished: not found in remote "
    "transport process list", pid);
  }  /* End of the "for" loop */

/* Come here when all the data was completely read after a select(), and
the process in pid has been wait()ed for. */

PROCESS_DONE:

DEBUG(D_deliver)
  {
  if (status == 0)
    debug_printf("remote delivery process %d ended\n", (int)pid);
  else
    debug_printf("remote delivery process %d ended: status=%04x\n", (int)pid,
      status);
  }

set_process_info("delivering %s", message_id);

/* Get the chain of processed addresses */

addrlist = parlist[poffset].addrlist;

/* If the process did not finish cleanly, record an error and freeze (except
for SIGTERM, SIGKILL and SIGQUIT), and also ensure the journal is not removed,
in case the delivery did actually happen. */

if ((status & 0xffff) != 0)
  {
  uschar *msg;
  int msb = (status >> 8) & 255;
  int lsb = status & 255;
  int code = (msb == 0)? (lsb & 0x7f) : msb;

  msg = string_sprintf("%s transport process returned non-zero status 0x%04x: "
    "%s %d",
    addrlist->transport->driver_name,
    status,
    (msb == 0)? "terminated by signal" : "exit code",
    code);

  if (msb != 0 || (code != SIGTERM && code != SIGKILL && code != SIGQUIT))
    addrlist->special_action = SPECIAL_FREEZE;

  for (addr = addrlist; addr; addr = addr->next)
    {
    addr->transport_return = DEFER;
    addr->message = msg;
    }

  remove_journal = FALSE;
  }

/* Else complete reading the pipe to get the result of the delivery, if all
the data has not yet been obtained. */

else if (!parlist[poffset].done) (void)par_read_pipe(poffset, TRUE);

/* Put the data count and return path into globals, mark the data slot unused,
decrement the count of subprocesses, and return the address chain. */

transport_count = parlist[poffset].transport_count;
used_return_path = parlist[poffset].return_path;
parlist[poffset].pid = 0;
parcount--;
return addrlist;
}

Project: exim

s_tlslog(gstring * g)
{
if (LOGGING(tls_cipher) && tls_in.cipher)
  g = string_append(g, 2, US" X=", tls_in.cipher);
if (LOGGING(tls_certificate_verified) && tls_in.cipher)
  g = string_append(g, 2, US" CV=", tls_in.certificate_verified? "yes":"no");
if (LOGGING(tls_peerdn) && tls_in.peerdn)
  g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\"");
if (LOGGING(tls_sni) && tls_in.sni)
  g = string_append(g, 3, US" SNI=\"", string_printing(tls_in.sni), US"\"");
return g;
}

Project: exim

delivery_log(int flags, address_item * addr, int logchar, uschar * msg)
{
gstring * g; /* Used for a temporary, expanding buffer, for building log lines  */
void * reset_point;     /* released afterwards.  */

/* Log the delivery on the main log. We use an extensible string to build up
the log line, and reset the store afterwards. Remote deliveries should always
have a pointer to the host item that succeeded; local deliveries can have a
pointer to a single host item in their host list, for use by the transport. */

#ifndef DISABLE_EVENT
  /* presume no successful remote delivery */
  lookup_dnssec_authenticated = NULL;
#endif

g = reset_point = string_get(256);

if (msg)
  g = string_append(g, 2, host_and_ident(TRUE), US" ");
else
  {
  g->s[0] = logchar; g->ptr = 1;
  g = string_catn(g, US"> ", 2);
  }
g = string_log_address(g, addr, LOGGING(all_parents), TRUE);

if (LOGGING(sender_on_delivery) || msg)
  g = string_append(g, 3, US" F=<",
#ifdef SUPPORT_I18N
    testflag(addr, af_utf8_downcvt)
    ? string_address_utf8_to_alabel(sender_address, NULL)
    :
#endif
      sender_address,
  US">");

if (*queue_name)
  g = string_append(g, 2, US" Q=", queue_name);

#ifdef EXPERIMENTAL_SRS
if(addr->prop.srs_sender)
  g = string_append(g, 3, US" SRS=<", addr->prop.srs_sender, US">");
#endif

/* You might think that the return path must always be set for a successful
delivery; indeed, I did for some time, until this statement crashed. The case
when it is not set is for a delivery to /dev/null which is optimised by not
being run at all. */

if (used_return_path && LOGGING(return_path_on_delivery))
  g = string_append(g, 3, US" P=<", used_return_path, US">");

if (msg)
  g = string_append(g, 2, US" ", msg);

/* For a delivery from a system filter, there may not be a router */
if (addr->router)
  g = string_append(g, 2, US" R=", addr->router->name);

g = string_append(g, 2, US" T=", addr->transport->name);

if (LOGGING(delivery_size))
  g = string_fmt_append(g, " S=%d", transport_count);

/* Local delivery */

if (addr->transport->info->local)
  {
  if (addr->host_list)
    g = string_append(g, 2, US" H=", addr->host_list->name);
  g = d_log_interface(g);
  if (addr->shadow_message)
    g = string_cat(g, addr->shadow_message);
  }

/* Remote delivery */

else
  {
  if (addr->host_used)
    {
    g = d_hostlog(g, addr);
    if (continue_sequence > 1)
      g = string_catn(g, US"*", 1);

#ifndef DISABLE_EVENT
    deliver_host_address = addr->host_used->address;
    deliver_host_port =    addr->host_used->port;
    deliver_host =         addr->host_used->name;

    /* DNS lookup status */
    lookup_dnssec_authenticated = addr->host_used->dnssec==DS_YES ? US"yes"
			      : addr->host_used->dnssec==DS_NO ? US"no"
			      : NULL;
#endif
    }

#ifdef SUPPORT_TLS
  g = d_tlslog(g, addr);
#endif

  if (addr->authenticator)
    {
    g = string_append(g, 2, US" A=", addr->authenticator);
    if (addr->auth_id)
      {
      g = string_append(g, 2, US":", addr->auth_id);
      if (LOGGING(smtp_mailauth) && addr->auth_sndr)
        g = string_append(g, 2, US":", addr->auth_sndr);
      }
    }

  if (LOGGING(pipelining))
    {
    if (testflag(addr, af_pipelining))
      g = string_catn(g, US" L", 2);
#ifdef EXPERIMENTAL_PIPE_CONNECT
    if (testflag(addr, af_early_pipe))
      g = string_catn(g, US"*", 1);
#endif
    }

#ifndef DISABLE_PRDR
  if (testflag(addr, af_prdr_used))
    g = string_catn(g, US" PRDR", 5);
#endif

  if (testflag(addr, af_chunking_used))
    g = string_catn(g, US" K", 2);
  }

/* confirmation message (SMTP (host_used) and LMTP (driver_name)) */

if (  LOGGING(smtp_confirmation)
   && addr->message
   && (addr->host_used || Ustrcmp(addr->transport->driver_name, "lmtp") == 0)
   )
  {
  unsigned i;
  unsigned lim = big_buffer_size < 1024 ? big_buffer_size : 1024;
  uschar *p = big_buffer;
  uschar *ss = addr->message;
  *p++ = '\"';
  for (i = 0; i < lim && ss[i] != 0; i++)	/* limit logged amount */
    {
    if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\'; /* quote \ and " */
    *p++ = ss[i];
    }
  *p++ = '\"';
  *p = 0;
  g = string_append(g, 2, US" C=", big_buffer);
  }

/* Time on queue and actual time taken to deliver */

if (LOGGING(queue_time))
  g = string_append(g, 2, US" QT=",
    string_timesince(&received_time));

if (LOGGING(deliver_time))
  {
  struct timeval diff = {.tv_sec = addr->more_errno, .tv_usec = addr->delivery_usec};
  g = string_append(g, 2, US" DT=", string_timediff(&diff));
  }

/* string_cat() always leaves room for the terminator. Release the
store we used to build the line after writing it. */

log_write(0, flags, "%s", string_from_gstring(g));

#ifndef DISABLE_EVENT
if (!msg) msg_event_raise(US"msg:delivery", addr);
#endif

store_reset(reset_point);
return;
}

Project: exim

incomplete_transaction_log(uschar *what)
{
if (sender_address == NULL ||                 /* No transaction in progress */
    !LOGGING(smtp_incomplete_transaction))
  return;

/* Build list of recipients for logging */

if (recipients_count > 0)
  {
  int i;
  raw_recipients = store_get(recipients_count * sizeof(uschar *));
  for (i = 0; i < recipients_count; i++)
    raw_recipients[i] = recipients_list[i].address;
  raw_recipients_count = recipients_count;
  }

log_write(L_smtp_incomplete_transaction, LOG_MAIN|LOG_SENDER|LOG_RECIPIENTS,
  "%s incomplete transaction (%s)", host_and_ident(TRUE), what);
}

Project: exim

acl_verify_reverse(uschar **user_msgptr, uschar **log_msgptr)
{
int rc;

user_msgptr = user_msgptr;  /* stop compiler warning */

/* Previous success */

if (sender_host_name != NULL) return OK;

/* Previous failure */

if (host_lookup_failed)
  {
  *log_msgptr = string_sprintf("host lookup failed%s", host_lookup_msg);
  return FAIL;
  }

/* Need to do a lookup */

HDEBUG(D_acl)
  debug_printf_indent("looking up host name to force name/address consistency check\n");

if ((rc = host_name_lookup()) != OK)
  {
  *log_msgptr = (rc == DEFER)?
    US"host lookup deferred for reverse lookup check"
    :
    string_sprintf("host lookup failed for reverse lookup check%s",
      host_lookup_msg);
  return rc;    /* DEFER or FAIL */
  }

host_build_sender_fullhost();
return OK;
}

Project: exim
File: host.c

random_number(int limit)
{
if (limit < 1)
  return 0;
if (random_seed == 0)
  {
  if (f.running_in_test_harness) random_seed = 42; else
    {
    int p = (int)getpid();
    random_seed = (int)time(NULL) ^ ((p << 16) | p);
    }
  }
random_seed = 1103515245 * random_seed + 12345;
return (unsigned int)(random_seed >> 16) % limit;
}

Project: exim

log_create(uschar *name)
{
int fd = Uopen(name,
#ifdef O_CLOEXEC
	O_CLOEXEC |
#endif
	O_CREAT|O_APPEND|O_WRONLY, LOG_MODE);

/* If creation failed, attempt to build a log directory in case that is the
problem. */

if (fd < 0 && errno == ENOENT)
  {
  BOOL created;
  uschar *lastslash = Ustrrchr(name, '/');
  *lastslash = 0;
  created = directory_make(NULL, name, LOG_DIRECTORY_MODE, FALSE);
  DEBUG(D_any) debug_printf("%s log directory %s\n",
    created ? "created" : "failed to create", name);
  *lastslash = '/';
  if (created) fd = Uopen(name,
#ifdef O_CLOEXEC
			O_CLOEXEC |
#endif
		       	O_CREAT|O_APPEND|O_WRONLY, LOG_MODE);
  }

return fd;
}

Project: exim

acl_verify(int where, address_item *addr, const uschar *arg,
  uschar **user_msgptr, uschar **log_msgptr, int *basic_errno)
{
int sep = '/';
int callout = -1;
int callout_overall = -1;
int callout_connect = -1;
int verify_options = 0;
int rc;
BOOL verify_header_sender = FALSE;
BOOL defer_ok = FALSE;
BOOL callout_defer_ok = FALSE;
BOOL no_details = FALSE;
BOOL success_on_redirect = FALSE;
address_item *sender_vaddr = NULL;
uschar *verify_sender_address = NULL;
uschar *pm_mailfrom = NULL;
uschar *se_mailfrom = NULL;

/* Some of the verify items have slash-separated options; some do not. Diagnose
an error if options are given for items that don't expect them.
*/

uschar *slash = Ustrchr(arg, '/');
const uschar *list = arg;
uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
verify_type_t * vp;

if (!ss) goto BAD_VERIFY;

/* Handle name/address consistency verification in a separate function. */

for (vp= verify_type_list;
     CS vp < CS verify_type_list + sizeof(verify_type_list);
     vp++
    )
  if (vp->alt_opt_sep ? strncmpic(ss, vp->name, vp->alt_opt_sep) == 0
                      : strcmpic (ss, vp->name) == 0)
   break;
if (CS vp >= CS verify_type_list + sizeof(verify_type_list))
  goto BAD_VERIFY;

if (vp->no_options && slash)
  {
  *log_msgptr = string_sprintf("unexpected '/' found in \"%s\" "
    "(this verify item has no options)", arg);
  return ERROR;
  }
if (!(vp->where_allowed & BIT(where)))
  {
  *log_msgptr = string_sprintf("cannot verify %s in ACL for %s",
		  vp->name, acl_wherenames[where]);
  return ERROR;
  }
switch(vp->value)
  {
  case VERIFY_REV_HOST_LKUP:
    if (!sender_host_address) return OK;
    if ((rc = acl_verify_reverse(user_msgptr, log_msgptr)) == DEFER)
      while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
	if (strcmpic(ss, US"defer_ok") == 0)
	  return OK;
    return rc;

  case VERIFY_CERT:
    /* TLS certificate verification is done at STARTTLS time; here we just
    test whether it was successful or not. (This is for optional verification; for
    mandatory verification, the connection doesn't last this long.) */

    if (tls_in.certificate_verified) return OK;
    *user_msgptr = US"no verified certificate";
    return FAIL;

  case VERIFY_HELO:
    /* We can test the result of optional HELO verification that might have
    occurred earlier. If not, we can attempt the verification now. */

    if (!f.helo_verified && !f.helo_verify_failed) smtp_verify_helo();
    return f.helo_verified ? OK : FAIL;

  case VERIFY_CSA:
    /* Do Client SMTP Authorization checks in a separate function, and turn the
    result code into user-friendly strings. */

    rc = acl_verify_csa(list);
    *log_msgptr = *user_msgptr = string_sprintf("client SMTP authorization %s",
                                              csa_reason_string[rc]);
    csa_status = csa_status_string[rc];
    DEBUG(D_acl) debug_printf_indent("CSA result %s\n", csa_status);
    return csa_return_code[rc];

#ifdef EXPERIMENTAL_ARC
  case VERIFY_ARC:
    {	/* Do Authenticated Received Chain checks in a separate function. */
    const uschar * condlist = CUS string_nextinlist(&list, &sep, NULL, 0);
    int csep = 0;
    uschar * cond;

    if (!(arc_state = acl_verify_arc())) return DEFER;
    DEBUG(D_acl) debug_printf_indent("ARC verify result %s %s%s%s\n", arc_state,
      arc_state_reason ? "(":"", arc_state_reason, arc_state_reason ? ")":"");

    if (!condlist) condlist = US"none:pass";
    while ((cond = string_nextinlist(&condlist, &csep, NULL, 0)))
      if (Ustrcmp(arc_state, cond) == 0) return OK;
    return FAIL;
    }
#endif

  case VERIFY_HDR_SYNTAX:
    /* Check that all relevant header lines have the correct 5322-syntax. If there is
    a syntax error, we return details of the error to the sender if configured to
    send out full details. (But a "message" setting on the ACL can override, as
    always). */

    rc = verify_check_headers(log_msgptr);
    if (rc != OK && *log_msgptr)
      if (smtp_return_error_details)
	*user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
      else
	acl_verify_message = *log_msgptr;
    return rc;

  case VERIFY_HDR_NAMES_ASCII:
    /* Check that all header names are true 7 bit strings
    See RFC 5322, 2.2. and RFC 6532, 3. */

    rc = verify_check_header_names_ascii(log_msgptr);
    if (rc != OK && smtp_return_error_details && *log_msgptr)
      *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
    return rc;

  case VERIFY_NOT_BLIND:
    /* Check that no recipient of this message is "blind", that is, every envelope
    recipient must be mentioned in either To: or Cc:. */

    if ((rc = verify_check_notblind()) != OK)
      {
      *log_msgptr = string_sprintf("bcc recipient detected");
      if (smtp_return_error_details)
        *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
      }
    return rc;

  /* The remaining verification tests check recipient and sender addresses,
  either from the envelope or from the header. There are a number of
  slash-separated options that are common to all of them. */

  case VERIFY_HDR_SNDR:
    verify_header_sender = TRUE;
    break;

  case VERIFY_SNDR:
    /* In the case of a sender, this can optionally be followed by an address to use
    in place of the actual sender (rare special-case requirement). */
    {
    uschar *s = ss + 6;
    if (*s == 0)
      verify_sender_address = sender_address;
    else
      {
      while (isspace(*s)) s++;
      if (*s++ != '=') goto BAD_VERIFY;
      while (isspace(*s)) s++;
      verify_sender_address = string_copy(s);
      }
    }
    break;

  case VERIFY_RCPT:
    break;
  }



/* Remaining items are optional; they apply to sender and recipient
verification, including "header sender" verification. */

while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))
      != NULL)
  {
  if (strcmpic(ss, US"defer_ok") == 0) defer_ok = TRUE;
  else if (strcmpic(ss, US"no_details") == 0) no_details = TRUE;
  else if (strcmpic(ss, US"success_on_redirect") == 0) success_on_redirect = TRUE;

  /* These two old options are left for backwards compatibility */

  else if (strcmpic(ss, US"callout_defer_ok") == 0)
    {
    callout_defer_ok = TRUE;
    if (callout == -1) callout = CALLOUT_TIMEOUT_DEFAULT;
    }

  else if (strcmpic(ss, US"check_postmaster") == 0)
     {
     pm_mailfrom = US"";
     if (callout == -1) callout = CALLOUT_TIMEOUT_DEFAULT;
     }

  /* The callout option has a number of sub-options, comma separated */

  else if (strncmpic(ss, US"callout", 7) == 0)
    {
    callout = CALLOUT_TIMEOUT_DEFAULT;
    ss += 7;
    if (*ss != 0)
      {
      while (isspace(*ss)) ss++;
      if (*ss++ == '=')
        {
	const uschar * sublist = ss;
        int optsep = ',';
        uschar *opt;
        uschar buffer[256];
        while (isspace(*sublist)) sublist++;

        while ((opt = string_nextinlist(&sublist, &optsep, buffer, sizeof(buffer))))
          {
	  callout_opt_t * op;
	  double period = 1.0F;

	  for (op= callout_opt_list; op->name; op++)
	    if (strncmpic(opt, op->name, Ustrlen(op->name)) == 0)
	      break;

	  verify_options |= op->flag;
	  if (op->has_option)
	    {
	    opt += Ustrlen(op->name);
            while (isspace(*opt)) opt++;
            if (*opt++ != '=')
              {
              *log_msgptr = string_sprintf("'=' expected after "
                "\"%s\" in ACL verify condition \"%s\"", op->name, arg);
              return ERROR;
              }
            while (isspace(*opt)) opt++;
	    }
	  if (op->timeval && (period = readconf_readtime(opt, 0, FALSE)) < 0)
	    {
	    *log_msgptr = string_sprintf("bad time value in ACL condition "
	      "\"verify %s\"", arg);
	    return ERROR;
	    }

	  switch(op->value)
	    {
            case CALLOUT_DEFER_OK:		callout_defer_ok = TRUE; break;
            case CALLOUT_POSTMASTER:		pm_mailfrom = US"";	break;
            case CALLOUT_FULLPOSTMASTER:	pm_mailfrom = US"";	break;
            case CALLOUT_MAILFROM:
              if (!verify_header_sender)
                {
                *log_msgptr = string_sprintf("\"mailfrom\" is allowed as a "
                  "callout option only for verify=header_sender (detected in ACL "
                  "condition \"%s\")", arg);
                return ERROR;
                }
              se_mailfrom = string_copy(opt);
  	      break;
            case CALLOUT_POSTMASTER_MAILFROM:	pm_mailfrom = string_copy(opt); break;
            case CALLOUT_MAXWAIT:		callout_overall = period;	break;
            case CALLOUT_CONNECT:		callout_connect = period;	break;
            case CALLOUT_TIME:			callout = period;		break;
	    }
          }
        }
      else
        {
        *log_msgptr = string_sprintf("'=' expected after \"callout\" in "
          "ACL condition \"%s\"", arg);
        return ERROR;
        }
      }
    }

  /* Option not recognized */

  else
    {
    *log_msgptr = string_sprintf("unknown option \"%s\" in ACL "
      "condition \"verify %s\"", ss, arg);
    return ERROR;
    }
  }

if ((verify_options & (vopt_callout_recipsender|vopt_callout_recippmaster)) ==
      (vopt_callout_recipsender|vopt_callout_recippmaster))
  {
  *log_msgptr = US"only one of use_sender and use_postmaster can be set "
    "for a recipient callout";
  return ERROR;
  }

/* Handle sender-in-header verification. Default the user message to the log
message if giving out verification details. */

if (verify_header_sender)
  {
  int verrno;

  if ((rc = verify_check_header_address(user_msgptr, log_msgptr, callout,
    callout_overall, callout_connect, se_mailfrom, pm_mailfrom, verify_options,
    &verrno)) != OK)
    {
    *basic_errno = verrno;
    if (smtp_return_error_details)
      {
      if (!*user_msgptr && *log_msgptr)
        *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
      if (rc == DEFER) f.acl_temp_details = TRUE;
      }
    }
  }

/* Handle a sender address. The default is to verify *the* sender address, but
optionally a different address can be given, for special requirements. If the
address is empty, we are dealing with a bounce message that has no sender, so
we cannot do any checking. If the real sender address gets rewritten during
verification (e.g. DNS widening), set the flag to stop it being rewritten again
during message reception.

A list of verified "sender" addresses is kept to try to avoid doing to much
work repetitively when there are multiple recipients in a message and they all
require sender verification. However, when callouts are involved, it gets too
complicated because different recipients may require different callout options.
Therefore, we always do a full sender verify when any kind of callout is
specified. Caching elsewhere, for instance in the DNS resolver and in the
callout handling, should ensure that this is not terribly inefficient. */

else if (verify_sender_address)
  {
  if ((verify_options & (vopt_callout_recipsender|vopt_callout_recippmaster)))
    {
    *log_msgptr = US"use_sender or use_postmaster cannot be used for a "
      "sender verify callout";
    return ERROR;
    }

  sender_vaddr = verify_checked_sender(verify_sender_address);
  if (sender_vaddr != NULL &&               /* Previously checked */
      callout <= 0)                         /* No callout needed this time */
    {
    /* If the "routed" flag is set, it means that routing worked before, so
    this check can give OK (the saved return code value, if set, belongs to a
    callout that was done previously). If the "routed" flag is not set, routing
    must have failed, so we use the saved return code. */

    if (testflag(sender_vaddr, af_verify_routed))
      rc = OK;
    else
      {
      rc = sender_vaddr->special_action;
      *basic_errno = sender_vaddr->basic_errno;
      }
    HDEBUG(D_acl) debug_printf_indent("using cached sender verify result\n");
    }

  /* Do a new verification, and cache the result. The cache is used to avoid
  verifying the sender multiple times for multiple RCPTs when callouts are not
  specified (see comments above).

  The cache is also used on failure to give details in response to the first
  RCPT that gets bounced for this reason. However, this can be suppressed by
  the no_details option, which sets the flag that says "this detail has already
  been sent". The cache normally contains just one address, but there may be
  more in esoteric circumstances. */

  else
    {
    BOOL routed = TRUE;
    uschar *save_address_data = deliver_address_data;

    sender_vaddr = deliver_make_addr(verify_sender_address, TRUE);
#ifdef SUPPORT_I18N
    if ((sender_vaddr->prop.utf8_msg = message_smtputf8))
      {
      sender_vaddr->prop.utf8_downcvt =       message_utf8_downconvert == 1;
      sender_vaddr->prop.utf8_downcvt_maybe = message_utf8_downconvert == -1;
      }
#endif
    if (no_details) setflag(sender_vaddr, af_sverify_told);
    if (verify_sender_address[0] != 0)
      {
      /* If this is the real sender address, save the unrewritten version
      for use later in receive. Otherwise, set a flag so that rewriting the
      sender in verify_address() does not update sender_address. */

      if (verify_sender_address == sender_address)
        sender_address_unrewritten = sender_address;
      else
        verify_options |= vopt_fake_sender;

      if (success_on_redirect)
        verify_options |= vopt_success_on_redirect;

      /* The recipient, qualify, and expn options are never set in
      verify_options. */

      rc = verify_address(sender_vaddr, NULL, verify_options, callout,
        callout_overall, callout_connect, se_mailfrom, pm_mailfrom, &routed);

      HDEBUG(D_acl) debug_printf_indent("----------- end verify ------------\n");

      if (rc != OK)
        *basic_errno = sender_vaddr->basic_errno;
      else
	DEBUG(D_acl)
	  {
	  if (Ustrcmp(sender_vaddr->address, verify_sender_address) != 0)
	    debug_printf_indent("sender %s verified ok as %s\n",
	      verify_sender_address, sender_vaddr->address);
	  else
	    debug_printf_indent("sender %s verified ok\n",
	      verify_sender_address);
	  }
      }
    else
      rc = OK;  /* Null sender */

    /* Cache the result code */

    if (routed) setflag(sender_vaddr, af_verify_routed);
    if (callout > 0) setflag(sender_vaddr, af_verify_callout);
    sender_vaddr->special_action = rc;
    sender_vaddr->next = sender_verified_list;
    sender_verified_list = sender_vaddr;

    /* Restore the recipient address data, which might have been clobbered by
    the sender verification. */

    deliver_address_data = save_address_data;
    }

  /* Put the sender address_data value into $sender_address_data */

  sender_address_data = sender_vaddr->prop.address_data;
  }

/* A recipient address just gets a straightforward verify; again we must handle
the DEFER overrides. */

else
  {
  address_item addr2;

  if (success_on_redirect)
    verify_options |= vopt_success_on_redirect;

  /* We must use a copy of the address for verification, because it might
  get rewritten. */

  addr2 = *addr;
  rc = verify_address(&addr2, NULL, verify_options|vopt_is_recipient, callout,
    callout_overall, callout_connect, se_mailfrom, pm_mailfrom, NULL);
  HDEBUG(D_acl) debug_printf_indent("----------- end verify ------------\n");

  *basic_errno = addr2.basic_errno;
  *log_msgptr = addr2.message;
  *user_msgptr = (addr2.user_message != NULL)?
    addr2.user_message : addr2.message;

  /* Allow details for temporary error if the address is so flagged. */
  if (testflag((&addr2), af_pass_message)) f.acl_temp_details = TRUE;

  /* Make $address_data visible */
  deliver_address_data = addr2.prop.address_data;
  }

/* We have a result from the relevant test. Handle defer overrides first. */

if (rc == DEFER && (defer_ok ||
   (callout_defer_ok && *basic_errno == ERRNO_CALLOUTDEFER)))
  {
  HDEBUG(D_acl) debug_printf_indent("verify defer overridden by %s\n",
    defer_ok? "defer_ok" : "callout_defer_ok");
  rc = OK;
  }

/* If we've failed a sender, set up a recipient message, and point
sender_verified_failed to the address item that actually failed. */

if (rc != OK && verify_sender_address != NULL)
  {
  if (rc != DEFER)
    *log_msgptr = *user_msgptr = US"Sender verify failed";
  else if (*basic_errno != ERRNO_CALLOUTDEFER)
    *log_msgptr = *user_msgptr = US"Could not complete sender verify";
  else
    {
    *log_msgptr = US"Could not complete sender verify callout";
    *user_msgptr = smtp_return_error_details? sender_vaddr->user_message :
      *log_msgptr;
    }

  sender_verified_failed = sender_vaddr;
  }

/* Verifying an address messes up the values of $domain and $local_part,
so reset them before returning if this is a RCPT ACL. */

if (addr != NULL)
  {
  deliver_domain = addr->domain;
  deliver_localpart = addr->local_part;
  }
return rc;

/* Syntax errors in the verify argument come here. */

BAD_VERIFY:
*log_msgptr = string_sprintf("expected \"sender[=address]\", \"recipient\", "
  "\"helo\", \"header_syntax\", \"header_sender\", \"header_names_ascii\" "
  "or \"reverse_host_lookup\" at start of ACL condition "
  "\"verify %s\"", arg);
return ERROR;
}

Project: exim

acl_verify_csa(const uschar *domain)
{
tree_node *t;
const uschar *found;
int priority, weight, port;
dns_answer dnsa;
dns_scan dnss;
dns_record *rr;
int rc, type;
uschar target[256];

/* Work out the domain we are using for the CSA lookup. The default is the
client's HELO domain. If the client has not said HELO, use its IP address
instead. If it's a local client (exim -bs), CSA isn't applicable. */

while (isspace(*domain) && *domain != '\0') ++domain;
if (*domain == '\0') domain = sender_helo_name;
if (domain == NULL) domain = sender_host_address;
if (sender_host_address == NULL) return CSA_UNKNOWN;

/* If we have an address literal, strip off the framing ready for turning it
into a domain. The framing consists of matched square brackets possibly
containing a keyword and a colon before the actual IP address. */

if (domain[0] == '[')
  {
  const uschar *start = Ustrchr(domain, ':');
  if (start == NULL) start = domain;
  domain = string_copyn(start + 1, Ustrlen(start) - 2);
  }

/* Turn domains that look like bare IP addresses into domains in the reverse
DNS. This code also deals with address literals and $sender_host_address. It's
not quite kosher to treat bare domains such as EHLO 192.0.2.57 the same as
address literals, but it's probably the most friendly thing to do. This is an
extension to CSA, so we allow it to be turned off for proper conformance. */

if (string_is_ip_address(domain, NULL) != 0)
  {
  if (!dns_csa_use_reverse) return CSA_UNKNOWN;
  dns_build_reverse(domain, target);
  domain = target;
  }

/* Find out if we've already done the CSA check for this domain. If we have,
return the same result again. Otherwise build a new cached result structure
for this domain. The name is filled in now, and the value is filled in when
we return from this function. */

t = tree_search(csa_cache, domain);
if (t != NULL) return t->data.val;

t = store_get_perm(sizeof(tree_node) + Ustrlen(domain));
Ustrcpy(t->name, domain);
(void)tree_insertnode(&csa_cache, t);

/* Now we are ready to do the actual DNS lookup(s). */

found = domain;
switch (dns_special_lookup(&dnsa, domain, T_CSA, &found))
  {
  /* If something bad happened (most commonly DNS_AGAIN), defer. */

  default:
  return t->data.val = CSA_DEFER_SRV;

  /* If we found nothing, the client's authorization is unknown. */

  case DNS_NOMATCH:
  case DNS_NODATA:
  return t->data.val = CSA_UNKNOWN;

  /* We got something! Go on to look at the reply in more detail. */

  case DNS_SUCCEED:
  break;
  }

/* Scan the reply for well-formed CSA SRV records. */

for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
     rr;
     rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT)) if (rr->type == T_SRV)
  {
  const uschar * p = rr->data;

  /* Extract the numerical SRV fields (p is incremented) */

  GETSHORT(priority, p);
  GETSHORT(weight, p);
  GETSHORT(port, p);

  DEBUG(D_acl)
    debug_printf_indent("CSA priority=%d weight=%d port=%d\n", priority, weight, port);

  /* Check the CSA version number */

  if (priority != 1) continue;

  /* If the domain does not have a CSA SRV record of its own (i.e. the domain
  found by dns_special_lookup() is a parent of the one we asked for), we check
  the subdomain assertions in the port field. At the moment there's only one
  assertion: legitimate SMTP clients are all explicitly authorized with CSA
  SRV records of their own. */

  if (Ustrcmp(found, domain) != 0)
    return t->data.val = port & 1 ? CSA_FAIL_EXPLICIT : CSA_UNKNOWN;

  /* This CSA SRV record refers directly to our domain, so we check the value
  in the weight field to work out the domain's authorization. 0 and 1 are
  unauthorized; 3 means the client is authorized but we can't check the IP
  address in order to authenticate it, so we treat it as unknown; values
  greater than 3 are undefined. */

  if (weight < 2) return t->data.val = CSA_FAIL_DOMAIN;

  if (weight > 2) continue;

  /* Weight == 2, which means the domain is authorized. We must check that the
  client's IP address is listed as one of the SRV target addresses. Save the
  target hostname then break to scan the additional data for its addresses. */

  (void)dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p,
    (DN_EXPAND_ARG4_TYPE)target, sizeof(target));

  DEBUG(D_acl) debug_printf_indent("CSA target is %s\n", target);

  break;
  }

/* If we didn't break the loop then no appropriate records were found. */

if (rr == NULL) return t->data.val = CSA_UNKNOWN;

/* Do not check addresses if the target is ".", in accordance with RFC 2782.
A target of "." indicates there are no valid addresses, so the client cannot
be authorized. (This is an odd configuration because weight=2 target=. is
equivalent to weight=1, but we check for it in order to keep load off the
root name servers.) Note that dn_expand() turns "." into "". */

if (Ustrcmp(target, "") == 0) return t->data.val = CSA_FAIL_NOADDR;

/* Scan the additional section of the CSA SRV reply for addresses belonging
to the target. If the name server didn't return any additional data (e.g.
because it does not fully support SRV records), we need to do another lookup
to obtain the target addresses; otherwise we have a definitive result. */

rc = acl_verify_csa_address(&dnsa, &dnss, RESET_ADDITIONAL, target);
if (rc != CSA_FAIL_NOADDR) return t->data.val = rc;

/* The DNS lookup type corresponds to the IP version used by the client. */

#if HAVE_IPV6
if (Ustrchr(sender_host_address, ':') != NULL)
  type = T_AAAA;
else
#endif /* HAVE_IPV6 */
  type = T_A;


lookup_dnssec_authenticated = NULL;
switch (dns_lookup(&dnsa, target, type, NULL))
  {
  /* If something bad happened (most commonly DNS_AGAIN), defer. */

  default:
    return t->data.val = CSA_DEFER_ADDR;

  /* If the query succeeded, scan the addresses and return the result. */

  case DNS_SUCCEED:
    rc = acl_verify_csa_address(&dnsa, &dnss, RESET_ANSWERS, target);
    if (rc != CSA_FAIL_NOADDR) return t->data.val = rc;
    /* else fall through */

  /* If the target has no IP addresses, the client cannot have an authorized
  IP address. However, if the target site uses A6 records (not AAAA records)
  we have to do yet another lookup in order to check them. */

  case DNS_NOMATCH:
  case DNS_NODATA:
    return t->data.val = CSA_FAIL_NOADDR;
  }
}

Project: exim

acl_check(int where, uschar *recipient, uschar *s, uschar **user_msgptr,
  uschar **log_msgptr)
{
int rc;
address_item adb;
address_item *addr = NULL;

*user_msgptr = *log_msgptr = NULL;
sender_verified_failed = NULL;
ratelimiters_cmd = NULL;
log_reject_target = LOG_MAIN|LOG_REJECT;

#ifndef DISABLE_PRDR
if (where==ACL_WHERE_RCPT || where==ACL_WHERE_VRFY || where==ACL_WHERE_PRDR)
#else
if (where==ACL_WHERE_RCPT || where==ACL_WHERE_VRFY)
#endif
  {
  adb = address_defaults;
  addr = &adb;
  addr->address = recipient;
  if (deliver_split_address(addr) == DEFER)
    {
    *log_msgptr = US"defer in percent_hack_domains check";
    return DEFER;
    }
#ifdef SUPPORT_I18N
  if ((addr->prop.utf8_msg = message_smtputf8))
    {
    addr->prop.utf8_downcvt =       message_utf8_downconvert == 1;
    addr->prop.utf8_downcvt_maybe = message_utf8_downconvert == -1;
    }
#endif
  deliver_domain = addr->domain;
  deliver_localpart = addr->local_part;
  }

acl_where = where;
acl_level = 0;
rc = acl_check_internal(where, addr, s, user_msgptr, log_msgptr);
acl_level = 0;
acl_where = ACL_WHERE_UNKNOWN;

/* Cutthrough - if requested,
and WHERE_RCPT and not yet opened conn as result of recipient-verify,
and rcpt acl returned accept,
and first recipient (cancel on any subsequents)
open one now and run it up to RCPT acceptance.
A failed verify should cancel cutthrough request,
and will pass the fail to the originator.
Initial implementation:  dual-write to spool.
Assume the rxd datastream is now being copied byte-for-byte to an open cutthrough connection.

Cease cutthrough copy on rxd final dot; do not send one.

On a data acl, if not accept and a cutthrough conn is open, hard-close it (no SMTP niceness).

On data acl accept, terminate the dataphase on an open cutthrough conn.  If accepted or
perm-rejected, reflect that to the original sender - and dump the spooled copy.
If temp-reject, close the conn (and keep the spooled copy).
If conn-failure, no action (and keep the spooled copy).
*/
switch (where)
  {
  case ACL_WHERE_RCPT:
#ifndef DISABLE_PRDR
  case ACL_WHERE_PRDR:
#endif

    if (f.host_checking_callout)	/* -bhc mode */
      cancel_cutthrough_connection(TRUE, US"host-checking mode");

    else if (  rc == OK
	    && cutthrough.delivery
	    && rcpt_count > cutthrough.nrcpt
	    )
      {
      if ((rc = open_cutthrough_connection(addr)) == DEFER)
	if (cutthrough.defer_pass)
	  {
	  uschar * s = addr->message;
	  /* Horrid kludge to recover target's SMTP message */
	  while (*s) s++;
	  do --s; while (!isdigit(*s));
	  if (*--s && isdigit(*s) && *--s && isdigit(*s)) *user_msgptr = s;
	  f.acl_temp_details = TRUE;
	  }
	else
	  {
	  HDEBUG(D_acl) debug_printf_indent("cutthrough defer; will spool\n");
	  rc = OK;
	  }
      }
    else HDEBUG(D_acl) if (cutthrough.delivery)
      if (rcpt_count <= cutthrough.nrcpt)
	debug_printf_indent("ignore cutthrough request; nonfirst message\n");
      else if (rc != OK)
	debug_printf_indent("ignore cutthrough request; ACL did not accept\n");
    break;

  case ACL_WHERE_PREDATA:
    if (rc == OK)
      cutthrough_predata();
    else
      cancel_cutthrough_connection(TRUE, US"predata acl not ok");
    break;

  case ACL_WHERE_QUIT:
  case ACL_WHERE_NOTQUIT:
    /* Drop cutthrough conns, and drop heldopen verify conns if
    the previous was not DATA */
    {
    uschar prev = smtp_connection_had[smtp_ch_index-2];
    BOOL dropverify = !(prev == SCH_DATA || prev == SCH_BDAT);

    cancel_cutthrough_connection(dropverify, US"quit or conndrop");
    break;
    }

  default:
    break;
  }

deliver_domain = deliver_localpart = deliver_address_data =
  deliver_domain_data = sender_address_data = NULL;

/* A DISCARD response is permitted only for message ACLs, excluding the PREDATA
ACL, which is really in the middle of an SMTP command. */

if (rc == DISCARD)
  {
  if (where > ACL_WHERE_NOTSMTP || where == ACL_WHERE_PREDATA)
    {
    log_write(0, LOG_MAIN|LOG_PANIC, "\"discard\" verb not allowed in %s "
      "ACL", acl_wherenames[where]);
    return ERROR;
    }
  return DISCARD;
  }

/* A DROP response is not permitted from MAILAUTH */

if (rc == FAIL_DROP && where == ACL_WHERE_MAILAUTH)
  {
  log_write(0, LOG_MAIN|LOG_PANIC, "\"drop\" verb not allowed in %s "
    "ACL", acl_wherenames[where]);
  return ERROR;
  }

/* Before giving a response, take a look at the length of any user message, and
split it up into multiple lines if possible. */

*user_msgptr = string_split_message(*user_msgptr);
if (fake_response != OK)
  fake_response_text = string_split_message(fake_response_text);

return rc;
}

Project: exim

bdat_ungetc(int ch)
{
chunking_data_left++;
return lwr_receive_ungetc(ch);
}

Project: exim

debug_vprintf(int indent, const char *format, va_list ap)
{
int save_errno = errno;

if (!debug_file) return;

/* Various things can be inserted at the start of a line. Don't use the
tod_stamp() function for the timestamp, because that will overwrite the
timestamp buffer, which may contain something useful. (This was a bug fix: the
+memory debugging with +timestamp did cause a problem.) */

if (debug_ptr == debug_buffer)
  {
  DEBUG(D_timestamp)
    {
    struct timeval now;
    time_t tmp;
    struct tm * t;

    gettimeofday(&now, NULL);
    tmp = now.tv_sec;
    t = f.timestamps_utc ? gmtime(&tmp) : localtime(&tmp);
    debug_ptr += sprintf(CS debug_ptr,
      LOGGING(millisec) ? "%02d:%02d:%02d.%03d " : "%02d:%02d:%02d ",
      t->tm_hour, t->tm_min, t->tm_sec, (int)(now.tv_usec/1000));
    }

  DEBUG(D_pid)
    debug_ptr += sprintf(CS debug_ptr, "%5d ", (int)getpid());

  /* Set up prefix if outputting for host checking and not debugging */

  if (host_checking && debug_selector == 0)
    {
    Ustrcpy(debug_ptr, ">>> ");
    debug_ptr += 4;
    }

  debug_prefix_length = debug_ptr - debug_buffer;
  }

if (indent > 0)
  {
  int i;
  for (i = indent >> 2; i > 0; i--)
    DEBUG(D_noutf8)
      {
      Ustrcpy(debug_ptr, "   !");
      debug_ptr += 4;	/* 3 spaces + shriek */
      debug_prefix_length += 4;
      }
    else
      {
      Ustrcpy(debug_ptr, "   " UTF8_VERT_2DASH);
      debug_ptr += 6;	/* 3 spaces + 3 UTF-8 octets */
      debug_prefix_length += 6;
      }

  Ustrncpy(debug_ptr, "   ", indent &= 3);
  debug_ptr += indent;
  debug_prefix_length += indent;
  }

/* Use the checked formatting routine to ensure that the buffer
does not overflow. Ensure there's space for a newline at the end. */

  {
  gstring gs = { .size = (int)sizeof(debug_buffer) - 1,
		.ptr = debug_ptr - debug_buffer,
		.s = debug_buffer };
  if (!string_vformat(&gs, FALSE, format, ap))
    {
    uschar * s = US"**** debug string too long - truncated ****\n";
    uschar * p = gs.s + gs.ptr;
    int maxlen = gs.size - Ustrlen(s) - 2;
    if (p > gs.s + maxlen) p = gs.s + maxlen;
    if (p > gs.s && p[-1] != '\n') *p++ = '\n';
    Ustrcpy(p, s);
    while(*debug_ptr) debug_ptr++;
    }
  else
    {
    string_from_gstring(&gs);
    debug_ptr = gs.s + gs.ptr;
    }
  }

/* Output the line if it is complete. If we added any prefix data and there
are internal newlines, make sure the prefix is on the continuation lines,
as long as there is room in the buffer. We want to do just a single fprintf()
so as to avoid interleaving. */

if (debug_ptr[-1] == '\n')
  {
  if (debug_prefix_length > 0)
    {
    uschar *p = debug_buffer;
    int left = sizeof(debug_buffer) - (debug_ptr - debug_buffer) - 1;
    while ((p = Ustrchr(p, '\n') + 1) != debug_ptr &&
           left >= debug_prefix_length)
      {
      int len = debug_ptr - p;
      memmove(p + debug_prefix_length, p, len + 1);
      memmove(p, debug_buffer, debug_prefix_length);
      debug_ptr += debug_prefix_length;
      left -= debug_prefix_length;
      }
    }

  fprintf(debug_file, "%s", CS debug_buffer);
  fflush(debug_file);
  debug_ptr = debug_buffer;
  debug_prefix_length = 0;
  }
errno = save_errno;
}

Project: exim
File: host.c

host_item_get_port(host_item *h)
{
const uschar *p;
int port, x;
int len = Ustrlen(h->name);

if (len < 3 || (p = h->name + len - 1, !isdigit(*p))) return PORT_NONE;

/* Extract potential port number */

port = *p-- - '0';
x = 10;

while (p > h->name + 1 && isdigit(*p))
  {
  port += (*p-- - '0') * x;
  x *= 10;
  }

/* The smallest value of p at this point is h->name + 1. */

if (*p != ':') return PORT_NONE;

if (p[-1] == ']' && h->name[0] == '[')
  h->name = string_copyn(h->name + 1, p - h->name - 2);
else if (Ustrchr(h->name, ':') == p)
  h->name = string_copyn(h->name, p - h->name);
else return PORT_NONE;

DEBUG(D_route|D_host_lookup) debug_printf("host=%s port=%d\n", h->name, port);
return port;
}

Project: exim

header_add(int type, const char *format, ...)
{
va_list ap;
va_start(ap, format);
header_add_backend(TRUE, NULL, FALSE, type, format, ap);
va_end(ap);
}

Project: exim

smtp_closedown(uschar *message)
{
if (!smtp_in || smtp_batched_input) return;
receive_swallow_smtp();
smtp_printf("421 %s\r\n", FALSE, message);

for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
  {
  case EOF_CMD:
    return;

  case QUIT_CMD:
    smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
    mac_smtp_fflush();
    return;

  case RSET_CMD:
    smtp_printf("250 Reset OK\r\n", FALSE);
    break;

  default:
    smtp_printf("421 %s\r\n", FALSE, message);
    break;
  }
}

Project: exim

smtp_read_response(void * sx0, uschar *buffer, int size, int okdigit,
   int timeout)
{
smtp_context * sx = sx0;
uschar *ptr = buffer;
int count = 0;

errno = 0;  /* Ensure errno starts out zero */

#ifdef EXPERIMENTAL_PIPE_CONNECT
if (sx->pending_BANNER || sx->pending_EHLO)
  if (smtp_reap_early_pipe(sx, &count) != OK)
    {
    DEBUG(D_transport) debug_printf("failed reaping pipelined cmd responsess\n");
    return FALSE;
    }
#endif

/* This is a loop to read and concatenate the lines that make up a multi-line
response. */

for (;;)
  {
  if ((count = read_response_line(&sx->inblock, ptr, size, timeout)) < 0)
    return FALSE;

  HDEBUG(D_transport|D_acl|D_v)
    debug_printf_indent("  %s %s\n", ptr == buffer ? "SMTP<<" : "      ", ptr);

  /* Check the format of the response: it must start with three digits; if
  these are followed by a space or end of line, the response is complete. If
  they are followed by '-' this is a multi-line response and we must look for
  another line until the final line is reached. The only use made of multi-line
  responses is to pass them back as error messages. We therefore just
  concatenate them all within the buffer, which should be large enough to
  accept any reasonable number of lines. */

  if (count < 3 ||
     !isdigit(ptr[0]) ||
     !isdigit(ptr[1]) ||
     !isdigit(ptr[2]) ||
     (ptr[3] != '-' && ptr[3] != ' ' && ptr[3] != 0))
    {
    errno = ERRNO_SMTPFORMAT;    /* format error */
    return FALSE;
    }

  /* If the line we have just read is a terminal line, line, we are done.
  Otherwise more data has to be read. */

  if (ptr[3] != '-') break;

  /* Move the reading pointer upwards in the buffer and insert \n between the
  components of a multiline response. Space is left for this by read_response_
  line(). */

  ptr += count;
  *ptr++ = '\n';
  size -= count + 1;
  }

#ifdef TCP_FASTOPEN
  tfo_out_check(sx->cctx.sock);
#endif

/* Return a value that depends on the SMTP return code. On some systems a
non-zero value of errno has been seen at this point, so ensure it is zero,
because the caller of this function looks at errno when FALSE is returned, to
distinguish between an unexpected return code and other errors such as
timeouts, lost connections, etc. */

errno = 0;
return buffer[0] == okdigit;
}

Project: exim

log_write(unsigned int selector, int flags, char *format, ...)
{
va_list ap;
va_start(ap, format);
vfprintf(stderr, format, ap);
fprintf(stderr, "\n");
va_end(ap);
selector = selector;     /* Keep picky compilers happy */
flags = flags;
}

Project: exim

synprot_error(int type, int code, uschar *data, uschar *errmess)
{
int yield = -1;

log_write(type, LOG_MAIN, "SMTP %s error in \"%s\" %s %s",
  (type == L_smtp_syntax_error)? "syntax" : "protocol",
  string_printing(smtp_cmd_buffer), host_and_ident(TRUE), errmess);

if (++synprot_error_count > smtp_max_synprot_errors)
  {
  yield = 1;
  log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
    "syntax or protocol errors (last command was \"%s\")",
    host_and_ident(FALSE), string_printing(smtp_cmd_buffer));
  }

if (code > 0)
  {
  smtp_printf("%d%c%s%s%s\r\n", FALSE, code, yield == 1 ? '-' : ' ',
    data ? data : US"", data ? US": " : US"", errmess);
  if (yield == 1)
    smtp_printf("%d Too many syntax or protocol errors\r\n", FALSE, code);
  }

return yield;
}

Project: exim

internal_transport_write_message(transport_ctx * tctx, int size_limit)
{
int len, size = 0;

/* Initialize pointer in output buffer. */

transport_write_reset(tctx->options);

/* Set up the data for start-of-line data checking and escaping */

if (tctx->check_string && tctx->escape_string)
  {
  nl_check = tctx->check_string;
  nl_check_length = Ustrlen(nl_check);
  nl_escape = tctx->escape_string;
  nl_escape_length = Ustrlen(nl_escape);
  }

/* Whether the escaping mechanism is applied to headers or not is controlled by
an option (set for SMTP, not otherwise). Negate the length if not wanted till
after the headers. */

if (!(tctx->options & topt_escape_headers))
  nl_check_length = -nl_check_length;

/* Write the headers if required, including any that have to be added. If there
are header rewriting rules, apply them.  The datasource is not the -D spoolfile
so temporarily hide the global that adjusts for its format. */

if (!(tctx->options & topt_no_headers))
  {
  BOOL save_wireformat = f.spool_file_wireformat;
  f.spool_file_wireformat = FALSE;

  /* Add return-path: if requested. */

  if (tctx->options & topt_add_return_path)
    {
    uschar buffer[ADDRESS_MAXLENGTH + 20];
    int n = sprintf(CS buffer, "Return-path: <%.*s>\n", ADDRESS_MAXLENGTH,
      return_path);
    if (!write_chunk(tctx, buffer, n)) goto bad;
    }

  /* Add envelope-to: if requested */

  if (tctx->options & topt_add_envelope_to)
    {
    BOOL first = TRUE;
    address_item *p;
    struct aci *plist = NULL;
    struct aci *dlist = NULL;
    void *reset_point = store_get(0);

    if (!write_chunk(tctx, US"Envelope-to: ", 13)) goto bad;

    /* Pick up from all the addresses. The plist and dlist variables are
    anchors for lists of addresses already handled; they have to be defined at
    this level because write_env_to() calls itself recursively. */

    for (p = tctx->addr; p; p = p->next)
      if (!write_env_to(p, &plist, &dlist, &first, tctx)) goto bad;

    /* Add a final newline and reset the store used for tracking duplicates */

    if (!write_chunk(tctx, US"\n", 1)) goto bad;
    store_reset(reset_point);
    }

  /* Add delivery-date: if requested. */

  if (tctx->options & topt_add_delivery_date)
    {
    uschar * s = tod_stamp(tod_full);

    if (  !write_chunk(tctx, US"Delivery-date: ", 15)
       || !write_chunk(tctx, s, Ustrlen(s))
       || !write_chunk(tctx, US"\n", 1)) goto bad;
    }

  /* Then the message's headers. Don't write any that are flagged as "old";
  that means they were rewritten, or are a record of envelope rewriting, or
  were removed (e.g. Bcc). If remove_headers is not null, skip any headers that
  match any entries therein. Then check addr->prop.remove_headers too, provided that
  addr is not NULL. */

  if (!transport_headers_send(tctx, &write_chunk))
    {
bad:
    f.spool_file_wireformat = save_wireformat;
    return FALSE;
    }

  f.spool_file_wireformat = save_wireformat;
  }

/* When doing RFC3030 CHUNKING output, work out how much data would be in a
last-BDAT, consisting of the current write_chunk() output buffer fill
(optimally, all of the headers - but it does not matter if we already had to
flush that buffer with non-last BDAT prependix) plus the amount of body data
(as expanded for CRLF lines).  Then create and write BDAT(s), and ensure
that further use of write_chunk() will not prepend BDATs.
The first BDAT written will also first flush any outstanding MAIL and RCPT
commands which were buffered thans to PIPELINING.
Commands go out (using a send()) from a different buffer to data (using a
write()).  They might not end up in the same TCP segment, which is
suboptimal. */

if (tctx->options & topt_use_bdat)
  {
  off_t fsize;
  int hsize;

  if ((hsize = chunk_ptr - deliver_out_buffer) < 0)
    hsize = 0;
  if (!(tctx->options & topt_no_body))
    {
    if ((fsize = lseek(deliver_datafile, 0, SEEK_END)) < 0) return FALSE;
    fsize -= SPOOL_DATA_START_OFFSET;
    if (size_limit > 0  &&  fsize > size_limit)
      fsize = size_limit;
    size = hsize + fsize;
    if (tctx->options & topt_use_crlf  &&  !f.spool_file_wireformat)
      size += body_linecount;	/* account for CRLF-expansion */

    /* With topt_use_bdat we never do dot-stuffing; no need to
    account for any expansion due to that. */
    }

  /* If the message is large, emit first a non-LAST chunk with just the
  headers, and reap the command responses.  This lets us error out early
  on RCPT rejects rather than sending megabytes of data.  Include headers
  on the assumption they are cheap enough and some clever implementations
  might errorcheck them too, on-the-fly, and reject that chunk. */

  if (size > DELIVER_OUT_BUFFER_SIZE && hsize > 0)
    {
    DEBUG(D_transport)
      debug_printf("sending small initial BDAT; hsize=%d\n", hsize);
    if (  tctx->chunk_cb(tctx, hsize, 0) != OK
       || !transport_write_block(tctx, deliver_out_buffer, hsize, FALSE)
       || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
       )
      return FALSE;
    chunk_ptr = deliver_out_buffer;
    size -= hsize;
    }

  /* Emit a LAST datachunk command, and unmark the context for further
  BDAT commands. */

  if (tctx->chunk_cb(tctx, size, tc_chunk_last) != OK)
    return FALSE;
  tctx->options &= ~topt_use_bdat;
  }

/* If the body is required, ensure that the data for check strings (formerly
the "from hack") is enabled by negating the length if necessary. (It will be
negative in cases where it isn't to apply to the headers). Then ensure the body
is positioned at the start of its file (following the message id), then write
it, applying the size limit if required. */

/* If we have a wireformat -D file (CRNL lines, non-dotstuffed, no ending dot)
and we want to send a body without dotstuffing or ending-dot, in-clear,
then we can just dump it using sendfile.
This should get used for CHUNKING output and also for writing the -K file for
dkim signing,  when we had CHUNKING input.  */

#ifdef OS_SENDFILE
if (  f.spool_file_wireformat
   && !(tctx->options & (topt_no_body | topt_end_dot))
   && !nl_check_length
   && tls_out.active.sock != tctx->u.fd
   )
  {
  ssize_t copied = 0;
  off_t offset = SPOOL_DATA_START_OFFSET;

  /* Write out any header data in the buffer */

  if ((len = chunk_ptr - deliver_out_buffer) > 0)
    {
    if (!transport_write_block(tctx, deliver_out_buffer, len, TRUE))
      return FALSE;
    size -= len;
    }

  DEBUG(D_transport) debug_printf("using sendfile for body\n");

  while(size > 0)
    {
    if ((copied = os_sendfile(tctx->u.fd, deliver_datafile, &offset, size)) <= 0) break;
    size -= copied;
    }
  return copied >= 0;
  }
#else
DEBUG(D_transport) debug_printf("cannot use sendfile for body: no support\n");
#endif

DEBUG(D_transport)
  if (!(tctx->options & topt_no_body))
    debug_printf("cannot use sendfile for body: %s\n",
      !f.spool_file_wireformat ? "spoolfile not wireformat"
      : tctx->options & topt_end_dot ? "terminating dot wanted"
      : nl_check_length ? "dot- or From-stuffing wanted"
      : "TLS output wanted");

if (!(tctx->options & topt_no_body))
  {
  int size = size_limit;

  nl_check_length = abs(nl_check_length);
  nl_partial_match = 0;
  if (lseek(deliver_datafile, SPOOL_DATA_START_OFFSET, SEEK_SET) < 0)
    return FALSE;
  while (  (len = MAX(DELIVER_IN_BUFFER_SIZE, size)) > 0
	&& (len = read(deliver_datafile, deliver_in_buffer, len)) > 0)
    {
    if (!write_chunk(tctx, deliver_in_buffer, len))
      return FALSE;
    size -= len;
    }

  /* A read error on the body will have left len == -1 and errno set. */

  if (len != 0) return FALSE;
  }

/* Finished with the check string, and spool-format consideration */

nl_check_length = nl_escape_length = 0;
f.spool_file_wireformat = FALSE;

/* If requested, add a terminating "." line (SMTP output). */

if (tctx->options & topt_end_dot && !write_chunk(tctx, US".\n", 2))
  return FALSE;

/* Write out any remaining data in the buffer before returning. */

return (len = chunk_ptr - deliver_out_buffer) <= 0 ||
  transport_write_block(tctx, deliver_out_buffer, len, FALSE);
}

Project: exim

show_db_version(FILE * f)
{
#ifdef DB_VERSION_STRING
DEBUG(D_any)
  {
  fprintf(f, "Library version: BDB: Compile: %s\n", DB_VERSION_STRING);
  fprintf(f, "                      Runtime: %s\n",
    db_version(NULL, NULL, NULL));
  }
else
  fprintf(f, "Berkeley DB: %s\n", DB_VERSION_STRING);

#elif defined(BTREEVERSION) && defined(HASHVERSION)
  #ifdef USE_DB
  fprintf(f, "Probably Berkeley DB version 1.8x (native mode)\n");
  #else
  fprintf(f, "Probably Berkeley DB version 1.8x (compatibility mode)\n");
  #endif

#elif defined(_DBM_RDONLY) || defined(dbm_dirfno)
fprintf(f, "Probably ndbm\n");
#elif defined(USE_TDB)
fprintf(f, "Using tdb\n");
#else
  #ifdef USE_GDBM
  fprintf(f, "Probably GDBM (native mode)\n");
  #else
  fprintf(f, "Probably GDBM (compatibility mode)\n");
  #endif
#endif
}

Project: exim

host_is_tls_on_connect_port(int port)
{
int sep = 0;
uschar buffer[32];
const uschar *list = tls_in.on_connect_ports;
uschar *s;
uschar *end;

if (tls_in.on_connect) return TRUE;

while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
  if (Ustrtol(s, &end, 10) == port)
    return TRUE;

return FALSE;
}

Project: exim

debug_printf(char *format, ...)
{
va_list ap;
gstring * g = string_get(1024);
void * reset_point = g;

va_start(ap, format);

if (!string_vformat(g, FALSE, format, ap))
  {
  char * s = "**** debug string overflowed buffer ****\n";
  char * p = CS g->s + g->ptr;
  int maxlen = g->size - (int)strlen(s) - 3;
  if (p > g->s + maxlen) p = g->s + maxlen;
  if (p > g->s && p[-1] != '\n') *p++ = '\n';
  strcpy(p, s);
  }

fprintf(stderr, "%s", string_from_gstring(g));
fflush(stderr);
store_reset(reset_point);
va_end(ap);
}

Project: exim

get_stdinput(char *(*fn_readline)(const char *), void(*fn_addhist)(const char *))
{
int i;
gstring * g = NULL;

if (!fn_readline) { printf("> "); fflush(stdout); }

for (i = 0;; i++)
  {
  uschar buffer[1024];
  uschar *p, *ss;

  #ifdef USE_READLINE
  char *readline_line = NULL;
  if (fn_readline != NULL)
    {
    if ((readline_line = fn_readline((i > 0)? "":"> ")) == NULL) break;
    if (*readline_line != 0 && fn_addhist != NULL) fn_addhist(readline_line);
    p = US readline_line;
    }
  else
  #endif

  /* readline() not in use */

    {
    if (Ufgets(buffer, sizeof(buffer), stdin) == NULL) break;
    p = buffer;
    }

  /* Handle the line */

  ss = p + (int)Ustrlen(p);
  while (ss > p && isspace(ss[-1])) ss--;

  if (i > 0)
    {
    while (p < ss && isspace(*p)) p++;   /* leading space after cont */
    }

  g = string_catn(g, p, ss - p);

  #ifdef USE_READLINE
  if (fn_readline) free(readline_line);
  #endif

  /* g can only be NULL if ss==p */
  if (ss == p || g->s[g->ptr-1] != '\\')
    break;

  --g->ptr;
  (void) string_from_gstring(g);
  }

if (!g) printf("\n");
return string_from_gstring(g);
}

Project: exim

smtp_log_tls_fail(uschar * errstr)
{
uschar * conn_info = smtp_get_connection_info();

if (Ustrncmp(conn_info, US"SMTP ", 5) == 0) conn_info += 5;
/* I'd like to get separated H= here, but too hard for now */

log_write(0, LOG_MAIN, "TLS error on %s %s", conn_info, errstr);
return FALSE;
}

Project: exim

mainlog_close(void)
{
if (mainlogfd < 0) return;
(void)close(mainlogfd);
mainlogfd = -1;
mainlog_inode = 0;
}

Project: exim

close_unwanted(void)
{
if (smtp_input)
  {
#ifdef SUPPORT_TLS
  tls_close(NULL, TLS_NO_SHUTDOWN);      /* Shut down the TLS library */
#endif
  (void)close(fileno(smtp_in));
  (void)close(fileno(smtp_out));
  smtp_in = NULL;
  }
else
  {
  (void)close(0);                                          /* stdin */
  if ((debug_selector & D_resolver) == 0) (void)close(1);  /* stdout */
  if (debug_selector == 0)                                 /* stderr */
    {
    if (!f.synchronous_delivery)
      {
      (void)close(2);
      log_stderr = NULL;
      }
    (void)setsid();
    }
  }
}

Project: exim

acl_ratelimit(const uschar *arg, int where, uschar **log_msgptr)
{
double limit, period, count;
uschar *ss;
uschar *key = NULL;
uschar *unique = NULL;
int sep = '/';
BOOL leaky = FALSE, strict = FALSE, readonly = FALSE;
BOOL noupdate = FALSE, badacl = FALSE;
int mode = RATE_PER_WHAT;
int old_pool, rc;
tree_node **anchor, *t;
open_db dbblock, *dbm;
int dbdb_size;
dbdata_ratelimit *dbd;
dbdata_ratelimit_unique *dbdb;
struct timeval tv;

/* Parse the first two options and record their values in expansion
variables. These variables allow the configuration to have informative
error messages based on rate limits obtained from a table lookup. */

/* First is the maximum number of messages per period / maximum burst
size, which must be greater than or equal to zero. Zero is useful for
rate measurement as opposed to rate limiting. */

if (!(sender_rate_limit = string_nextinlist(&arg, &sep, NULL, 0)))
  return ratelimit_error(log_msgptr, "sender rate limit not set");

limit = Ustrtod(sender_rate_limit, &ss);
if      (tolower(*ss) == 'k') { limit *= 1024.0; ss++; }
else if (tolower(*ss) == 'm') { limit *= 1024.0*1024.0; ss++; }
else if (tolower(*ss) == 'g') { limit *= 1024.0*1024.0*1024.0; ss++; }

if (limit < 0.0 || *ss != '\0')
  return ratelimit_error(log_msgptr,
    "\"%s\" is not a positive number", sender_rate_limit);

/* Second is the rate measurement period / exponential smoothing time
constant. This must be strictly greater than zero, because zero leads to
run-time division errors. */

period = !(sender_rate_period = string_nextinlist(&arg, &sep, NULL, 0))
  ? -1.0 : readconf_readtime(sender_rate_period, 0, FALSE);
if (period <= 0.0)
  return ratelimit_error(log_msgptr,
    "\"%s\" is not a time value", sender_rate_period);

/* By default we are counting one of something, but the per_rcpt,
per_byte, and count options can change this. */

count = 1.0;

/* Parse the other options. */

while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size)))
  {
  if (strcmpic(ss, US"leaky") == 0) leaky = TRUE;
  else if (strcmpic(ss, US"strict") == 0) strict = TRUE;
  else if (strcmpic(ss, US"noupdate") == 0) noupdate = TRUE;
  else if (strcmpic(ss, US"readonly") == 0) readonly = TRUE;
  else if (strcmpic(ss, US"per_cmd") == 0) RATE_SET(mode, PER_CMD);
  else if (strcmpic(ss, US"per_conn") == 0)
    {
    RATE_SET(mode, PER_CONN);
    if (where == ACL_WHERE_NOTSMTP || where == ACL_WHERE_NOTSMTP_START)
      badacl = TRUE;
    }
  else if (strcmpic(ss, US"per_mail") == 0)
    {
    RATE_SET(mode, PER_MAIL);
    if (where > ACL_WHERE_NOTSMTP) badacl = TRUE;
    }
  else if (strcmpic(ss, US"per_rcpt") == 0)
    {
    /* If we are running in the RCPT ACL, then we'll count the recipients
    one by one, but if we are running when we have accumulated the whole
    list then we'll add them all in one batch. */
    if (where == ACL_WHERE_RCPT)
      RATE_SET(mode, PER_RCPT);
    else if (where >= ACL_WHERE_PREDATA && where <= ACL_WHERE_NOTSMTP)
      RATE_SET(mode, PER_ALLRCPTS), count = (double)recipients_count;
    else if (where == ACL_WHERE_MAIL || where > ACL_WHERE_NOTSMTP)
      RATE_SET(mode, PER_RCPT), badacl = TRUE;
    }
  else if (strcmpic(ss, US"per_byte") == 0)
    {
    /* If we have not yet received the message data and there was no SIZE
    declaration on the MAIL command, then it's safe to just use a value of
    zero and let the recorded rate decay as if nothing happened. */
    RATE_SET(mode, PER_MAIL);
    if (where > ACL_WHERE_NOTSMTP) badacl = TRUE;
    else count = message_size < 0 ? 0.0 : (double)message_size;
    }
  else if (strcmpic(ss, US"per_addr") == 0)
    {
    RATE_SET(mode, PER_RCPT);
    if (where != ACL_WHERE_RCPT) badacl = TRUE, unique = US"*";
    else unique = string_sprintf("%s@%s", deliver_localpart, deliver_domain);
    }
  else if (strncmpic(ss, US"count=", 6) == 0)
    {
    uschar *e;
    count = Ustrtod(ss+6, &e);
    if (count < 0.0 || *e != '\0')
      return ratelimit_error(log_msgptr, "\"%s\" is not a positive number", ss);
    }
  else if (strncmpic(ss, US"unique=", 7) == 0)
    unique = string_copy(ss + 7);
  else if (!key)
    key = string_copy(ss);
  else
    key = string_sprintf("%s/%s", key, ss);
  }

/* Sanity check. When the badacl flag is set the update mode must either
be readonly (which is the default if it is omitted) or, for backwards
compatibility, a combination of noupdate and strict or leaky. */

if (mode == RATE_PER_CLASH)
  return ratelimit_error(log_msgptr, "conflicting per_* options");
if (leaky + strict + readonly > 1)
  return ratelimit_error(log_msgptr, "conflicting update modes");
if (badacl && (leaky || strict) && !noupdate)
  return ratelimit_error(log_msgptr,
    "\"%s\" must not have /leaky or /strict option, or cannot be used in %s ACL",
    ratelimit_option_string[mode], acl_wherenames[where]);

/* Set the default values of any unset options. In readonly mode we
perform the rate computation without any increment so that its value
decays to eventually allow over-limit senders through. */

if (noupdate) readonly = TRUE, leaky = strict = FALSE;
if (badacl) readonly = TRUE;
if (readonly) count = 0.0;
if (!strict && !readonly) leaky = TRUE;
if (mode == RATE_PER_WHAT) mode = RATE_PER_MAIL;

/* Create the lookup key. If there is no explicit key, use sender_host_address.
If there is no sender_host_address (e.g. -bs or acl_not_smtp) then we simply
omit it. The smoothing constant (sender_rate_period) and the per_xxx options
are added to the key because they alter the meaning of the stored data. */

if (!key)
  key = !sender_host_address ? US"" : sender_host_address;

key = string_sprintf("%s/%s/%s%s",
  sender_rate_period,
  ratelimit_option_string[mode],
  unique == NULL ? "" : "unique/",
  key);

HDEBUG(D_acl)
  debug_printf_indent("ratelimit condition count=%.0f %.1f/%s\n", count, limit, key);

/* See if we have already computed the rate by looking in the relevant tree.
For per-connection rate limiting, store tree nodes and dbdata in the permanent
pool so that they survive across resets. In readonly mode we only remember the
result for the rest of this command in case a later command changes it. After
this bit of logic the code is independent of the per_* mode. */

old_pool = store_pool;

if (readonly)
  anchor = &ratelimiters_cmd;
else switch(mode)
  {
  case RATE_PER_CONN:
    anchor = &ratelimiters_conn;
    store_pool = POOL_PERM;
    break;
  case RATE_PER_BYTE:
  case RATE_PER_MAIL:
  case RATE_PER_ALLRCPTS:
    anchor = &ratelimiters_mail;
    break;
  case RATE_PER_ADDR:
  case RATE_PER_CMD:
  case RATE_PER_RCPT:
    anchor = &ratelimiters_cmd;
    break;
  default:
    anchor = NULL; /* silence an "unused" complaint */
    log_write(0, LOG_MAIN|LOG_PANIC_DIE,
      "internal ACL error: unknown ratelimit mode %d", mode);
    break;
  }

if ((t = tree_search(*anchor, key)))
  {
  dbd = t->data.ptr;
  /* The following few lines duplicate some of the code below. */
  rc = (dbd->rate < limit)? FAIL : OK;
  store_pool = old_pool;
  sender_rate = string_sprintf("%.1f", dbd->rate);
  HDEBUG(D_acl)
    debug_printf_indent("ratelimit found pre-computed rate %s\n", sender_rate);
  return rc;
  }

/* We aren't using a pre-computed rate, so get a previously recorded rate
from the database, which will be updated and written back if required. */

if (!(dbm = dbfn_open(US"ratelimit", O_RDWR, &dbblock, TRUE)))
  {
  store_pool = old_pool;
  sender_rate = NULL;
  HDEBUG(D_acl) debug_printf_indent("ratelimit database not available\n");
  *log_msgptr = US"ratelimit database not available";
  return DEFER;
  }
dbdb = dbfn_read_with_length(dbm, key, &dbdb_size);
dbd = NULL;

gettimeofday(&tv, NULL);

if (dbdb)
  {
  /* Locate the basic ratelimit block inside the DB data. */
  HDEBUG(D_acl) debug_printf_indent("ratelimit found key in database\n");
  dbd = &dbdb->dbd;

  /* Forget the old Bloom filter if it is too old, so that we count each
  repeating event once per period. We don't simply clear and re-use the old
  filter because we want its size to change if the limit changes. Note that
  we keep the dbd pointer for copying the rate into the new data block. */

  if(unique && tv.tv_sec > dbdb->bloom_epoch + period)
    {
    HDEBUG(D_acl) debug_printf_indent("ratelimit discarding old Bloom filter\n");
    dbdb = NULL;
    }

  /* Sanity check. */

  if(unique && dbdb_size < sizeof(*dbdb))
    {
    HDEBUG(D_acl) debug_printf_indent("ratelimit discarding undersize Bloom filter\n");
    dbdb = NULL;
    }
  }

/* Allocate a new data block if the database lookup failed
or the Bloom filter passed its age limit. */

if (!dbdb)
  {
  if (!unique)
    {
    /* No Bloom filter. This basic ratelimit block is initialized below. */
    HDEBUG(D_acl) debug_printf_indent("ratelimit creating new rate data block\n");
    dbdb_size = sizeof(*dbd);
    dbdb = store_get(dbdb_size);
    }
  else
    {
    int extra;
    HDEBUG(D_acl) debug_printf_indent("ratelimit creating new Bloom filter\n");

    /* See the long comment below for an explanation of the magic number 2.
    The filter has a minimum size in case the rate limit is very small;
    this is determined by the definition of dbdata_ratelimit_unique. */

    extra = (int)limit * 2 - sizeof(dbdb->bloom);
    if (extra < 0) extra = 0;
    dbdb_size = sizeof(*dbdb) + extra;
    dbdb = store_get(dbdb_size);
    dbdb->bloom_epoch = tv.tv_sec;
    dbdb->bloom_size = sizeof(dbdb->bloom) + extra;
    memset(dbdb->bloom, 0, dbdb->bloom_size);

    /* Preserve any basic ratelimit data (which is our longer-term memory)
    by copying it from the discarded block. */

    if (dbd)
      {
      dbdb->dbd = *dbd;
      dbd = &dbdb->dbd;
      }
    }
  }

/* If we are counting unique events, find out if this event is new or not.
If the client repeats the event during the current period then it should be
counted. We skip this code in readonly mode for efficiency, because any
changes to the filter will be discarded and because count is already set to
zero. */

if (unique && !readonly)
  {
  /* We identify unique events using a Bloom filter. (You can find my
  notes on Bloom filters at http://fanf.livejournal.com/81696.html)
  With the per_addr option, an "event" is a recipient address, though the
  user can use the unique option to define their own events. We only count
  an event if we have not seen it before.

  We size the filter according to the rate limit, which (in leaky mode)
  is the limit on the population of the filter. We allow 16 bits of space
  per entry (see the construction code above) and we set (up to) 8 of them
  when inserting an element (see the loop below). The probability of a false
  positive (an event we have not seen before but which we fail to count) is

    size    = limit * 16
    numhash = 8
    allzero = exp(-numhash * pop / size)
            = exp(-0.5 * pop / limit)
    fpr     = pow(1 - allzero, numhash)

  For senders at the limit the fpr is      0.06%    or  1 in 1700
  and for senders at half the limit it is  0.0006%  or  1 in 170000

  In strict mode the Bloom filter can fill up beyond the normal limit, in
  which case the false positive rate will rise. This means that the
  measured rate for very fast senders can bogusly drop off after a while.

  At twice the limit, the fpr is  2.5%  or  1 in 40
  At four times the limit, it is  31%   or  1 in 3.2

  It takes ln(pop/limit) periods for an over-limit burst of pop events to
  decay below the limit, and if this is more than one then the Bloom filter
  will be discarded before the decay gets that far. The false positive rate
  at this threshold is 9.3% or 1 in 10.7. */

  BOOL seen;
  unsigned n, hash, hinc;
  uschar md5sum[16];
  md5 md5info;

  /* Instead of using eight independent hash values, we combine two values
  using the formula h1 + n * h2. This does not harm the Bloom filter's
  performance, and means the amount of hash we need is independent of the
  number of bits we set in the filter. */

  md5_start(&md5info);
  md5_end(&md5info, unique, Ustrlen(unique), md5sum);
  hash = md5sum[0] | md5sum[1] << 8 | md5sum[2] << 16 | md5sum[3] << 24;
  hinc = md5sum[4] | md5sum[5] << 8 | md5sum[6] << 16 | md5sum[7] << 24;

  /* Scan the bits corresponding to this event. A zero bit means we have
  not seen it before. Ensure all bits are set to record this event. */

  HDEBUG(D_acl) debug_printf_indent("ratelimit checking uniqueness of %s\n", unique);

  seen = TRUE;
  for (n = 0; n < 8; n++, hash += hinc)
    {
    int bit = 1 << (hash % 8);
    int byte = (hash / 8) % dbdb->bloom_size;
    if ((dbdb->bloom[byte] & bit) == 0)
      {
      dbdb->bloom[byte] |= bit;
      seen = FALSE;
      }
    }

  /* If this event has occurred before, do not count it. */

  if (seen)
    {
    HDEBUG(D_acl) debug_printf_indent("ratelimit event found in Bloom filter\n");
    count = 0.0;
    }
  else
    HDEBUG(D_acl) debug_printf_indent("ratelimit event added to Bloom filter\n");
  }

/* If there was no previous ratelimit data block for this key, initialize
the new one, otherwise update the block from the database. The initial rate
is what would be computed by the code below for an infinite interval. */

if (!dbd)
  {
  HDEBUG(D_acl) debug_printf_indent("ratelimit initializing new key's rate data\n");
  dbd = &dbdb->dbd;
  dbd->time_stamp = tv.tv_sec;
  dbd->time_usec = tv.tv_usec;
  dbd->rate = count;
  }
else
  {
  /* The smoothed rate is computed using an exponentially weighted moving
  average adjusted for variable sampling intervals. The standard EWMA for
  a fixed sampling interval is:  f'(t) = (1 - a) * f(t) + a * f'(t - 1)
  where f() is the measured value and f'() is the smoothed value.

  Old data decays out of the smoothed value exponentially, such that data n
  samples old is multiplied by a^n. The exponential decay time constant p
  is defined such that data p samples old is multiplied by 1/e, which means
  that a = exp(-1/p). We can maintain the same time constant for a variable
  sampling interval i by using a = exp(-i/p).

  The rate we are measuring is messages per period, suitable for directly
  comparing with the limit. The average rate between now and the previous
  message is period / interval, which we feed into the EWMA as the sample.

  It turns out that the number of messages required for the smoothed rate
  to reach the limit when they are sent in a burst is equal to the limit.
  This can be seen by analysing the value of the smoothed rate after N
  messages sent at even intervals. Let k = (1 - a) * p/i

    rate_1 = (1 - a) * p/i + a * rate_0
           = k + a * rate_0
    rate_2 = k + a * rate_1
           = k + a * k + a^2 * rate_0
    rate_3 = k + a * k + a^2 * k + a^3 * rate_0
    rate_N = rate_0 * a^N + k * SUM(x=0..N-1)(a^x)
           = rate_0 * a^N + k * (1 - a^N) / (1 - a)
           = rate_0 * a^N + p/i * (1 - a^N)

  When N is large, a^N -> 0 so rate_N -> p/i as desired.

    rate_N = p/i + (rate_0 - p/i) * a^N
    a^N = (rate_N - p/i) / (rate_0 - p/i)
    N * -i/p = log((rate_N - p/i) / (rate_0 - p/i))
    N = p/i * log((rate_0 - p/i) / (rate_N - p/i))

  Numerical analysis of the above equation, setting the computed rate to
  increase from rate_0 = 0 to rate_N = limit, shows that for large sending
  rates, p/i, the number of messages N = limit. So limit serves as both the
  maximum rate measured in messages per period, and the maximum number of
  messages that can be sent in a fast burst. */

  double this_time = (double)tv.tv_sec
                   + (double)tv.tv_usec / 1000000.0;
  double prev_time = (double)dbd->time_stamp
                   + (double)dbd->time_usec / 1000000.0;

  /* We must avoid division by zero, and deal gracefully with the clock going
  backwards. If we blunder ahead when time is in reverse then the computed
  rate will be bogus. To be safe we clamp interval to a very small number. */

  double interval = this_time - prev_time <= 0.0 ? 1e-9
                  : this_time - prev_time;

  double i_over_p = interval / period;
  double a = exp(-i_over_p);

  /* Combine the instantaneous rate (period / interval) with the previous rate
  using the smoothing factor a. In order to measure sized events, multiply the
  instantaneous rate by the count of bytes or recipients etc. */

  dbd->time_stamp = tv.tv_sec;
  dbd->time_usec = tv.tv_usec;
  dbd->rate = (1 - a) * count / i_over_p + a * dbd->rate;

  /* When events are very widely spaced the computed rate tends towards zero.
  Although this is accurate it turns out not to be useful for our purposes,
  especially when the first event after a long silence is the start of a spam
  run. A more useful model is that the rate for an isolated event should be the
  size of the event per the period size, ignoring the lack of events outside
  the current period and regardless of where the event falls in the period. So,
  if the interval was so long that the calculated rate is unhelpfully small, we
  re-initialize the rate. In the absence of higher-rate bursts, the condition
  below is true if the interval is greater than the period. */

  if (dbd->rate < count) dbd->rate = count;
  }

/* Clients sending at the limit are considered to be over the limit.
This matters for edge cases such as a limit of zero, when the client
should be completely blocked. */

rc = dbd->rate < limit ? FAIL : OK;

/* Update the state if the rate is low or if we are being strict. If we
are in leaky mode and the sender's rate is too high, we do not update
the recorded rate in order to avoid an over-aggressive sender's retry
rate preventing them from getting any email through. If readonly is set,
neither leaky nor strict are set, so we do not do any updates. */

if ((rc == FAIL && leaky) || strict)
  {
  dbfn_write(dbm, key, dbdb, dbdb_size);
  HDEBUG(D_acl) debug_printf_indent("ratelimit db updated\n");
  }
else
  {
  HDEBUG(D_acl) debug_printf_indent("ratelimit db not updated: %s\n",
    readonly? "readonly mode" : "over the limit, but leaky");
  }

dbfn_close(dbm);

/* Store the result in the tree for future reference. */

t = store_get(sizeof(tree_node) + Ustrlen(key));
t->data.ptr = dbd;
Ustrcpy(t->name, key);
(void)tree_insertnode(anchor, t);

/* We create the formatted version of the sender's rate very late in
order to ensure that it is done using the correct storage pool. */

store_pool = old_pool;
sender_rate = string_sprintf("%.1f", dbd->rate);

HDEBUG(D_acl)
  debug_printf_indent("ratelimit computed rate %s\n", sender_rate);

return rc;
}

Project: exim
File: deliver.c

child_done(address_item *addr, uschar *now)
{
address_item *aa;
while (addr->parent)
  {
  addr = addr->parent;
  if (--addr->child_count > 0) return;   /* Incomplete parent */
  address_done(addr, now);

  /* Log the completion of all descendents only when there is no ancestor with
  the same original address. */

  for (aa = addr->parent; aa; aa = aa->parent)
    if (Ustrcmp(aa->address, addr->address) == 0) break;
  if (aa) continue;

  deliver_msglog("%s %s: children all complete\n", now, addr->address);
  DEBUG(D_deliver) debug_printf("%s: children all complete\n", addr->address);
  }
}

Project: exim

sigalrm_handler(int sig)
{
sig = sig;            /* Keep picky compilers happy */
sigalrm_seen = TRUE;
}

Project: exim

check_sync(void)
{
if (!smtp_enforce_sync || !sender_host_address || f.sender_host_notsocket)
  return TRUE;

return wouldblock_reading();
}

Project: exim

header_testname_incomplete(header_line *h, const uschar *name,
    int len, BOOL notdel)
{
if (h->type == '*' && notdel) return FALSE;
if (h->text == NULL || strncmpic(h->text, name, len) != 0) return FALSE;
return TRUE;
}

Project: exim

exim_exit(int rc, const uschar * process)
{
search_tidyup();
DEBUG(D_any)
  debug_printf(">>>>>>>>>>>>>>>> Exim pid=%d %s%s%sterminating with rc=%d "
    ">>>>>>>>>>>>>>>>\n", (int)getpid(),
    process ? "(" : "", process, process ? ") " : "", rc);
exit(rc);
}

Project: exim

log_create_as_exim(uschar *name)
{
pid_t pid = fork();
int status = 1;
int fd = -1;

/* In the subprocess, change uid/gid and do the creation. Return 0 from the
subprocess on success. If we don't check for setuid failures, then the file
can be created as root, so vulnerabilities which cause setuid to fail mean
that the Exim user can use symlinks to cause a file to be opened/created as
root. We always open for append, so can't nuke existing content but it would
still be Rather Bad. */

if (pid == 0)
  {
  if (setgid(exim_gid) < 0)
    die(US"exim: setgid for log-file creation failed, aborting",
      US"Unexpected log failure, please try later");
  if (setuid(exim_uid) < 0)
    die(US"exim: setuid for log-file creation failed, aborting",
      US"Unexpected log failure, please try later");
  _exit((log_create(name) < 0)? 1 : 0);
  }

/* If we created a subprocess, wait for it. If it succeeded, try the open. */

while (pid > 0 && waitpid(pid, &status, 0) != pid);
if (status == 0) fd = Uopen(name,
#ifdef O_CLOEXEC
			O_CLOEXEC |
#endif
		       	O_APPEND|O_WRONLY, LOG_MODE);

/* If we failed to create a subprocess, we are in a bad way. We return
with fd still < 0, and errno set, letting the caller handle the error. */

return fd;
}