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

add_unique_interface(ip_address_item *list, ip_address_item *ipa)
{
ip_address_item *ipa2;
for (ipa2 = list; ipa2 != NULL; ipa2 = ipa2->next)
  if (Ustrcmp(ipa2->address, ipa->address) == 0) return list;
ipa2 = store_get_perm(sizeof(ip_address_item));
*ipa2 = *ipa;
ipa2->next = list;
return ipa2;
}

Project: exim
File: deliver.c

deliver_make_addr(uschar *address, BOOL copy)
{
address_item *addr = store_get(sizeof(address_item));
*addr = address_defaults;
if (copy) address = string_copy(address);
addr->address = address;
addr->unique = string_copy(address);
return addr;
}

Project: exim

log_write(unsigned int selector, int flags, const char *format, ...)
{
uschar * ptr;
int paniclogfd;
ssize_t written_len;
gstring gs = { .size = LOG_BUFFER_SIZE-1, .ptr = 0, .s = log_buffer };
gstring * g;
va_list ap;

/* If panic_recurseflag is set, we have failed to open the panic log. This is
the ultimate disaster. First try to write the message to a debug file and/or
stderr and also to syslog. If panic_save_buffer is not NULL, it contains the
original log line that caused the problem. Afterwards, expire. */

if (panic_recurseflag)
  {
  uschar *extra = panic_save_buffer ? panic_save_buffer : US"";
  if (debug_file) debug_printf("%s%s", extra, log_buffer);
  if (log_stderr && log_stderr != debug_file)
    fprintf(log_stderr, "%s%s", extra, log_buffer);
  if (*extra) write_syslog(LOG_CRIT, extra);
  write_syslog(LOG_CRIT, log_buffer);
  die(US"exim: could not open panic log - aborting: see message(s) above",
    US"Unexpected log failure, please try later");
  }

/* Ensure we have a buffer (see comment above); this should never be obeyed
when running Exim proper, only when running utilities. */

if (!log_buffer)
  if (!(log_buffer = US malloc(LOG_BUFFER_SIZE)))
    {
    fprintf(stderr, "exim: failed to get store for log buffer\n");
    exim_exit(EXIT_FAILURE, NULL);
    }

/* If we haven't already done so, inspect the setting of log_file_path to
determine whether to log to files and/or to syslog. Bits in logging_mode
control this, and for file logging, the path must end up in file_path. This
variable must be in permanent store because it may be required again later in
the process. */

if (!path_inspected)
  {
  BOOL multiple = FALSE;
  int old_pool = store_pool;

  store_pool = POOL_PERM;

  /* If nothing has been set, don't waste effort... the default values for the
  statics are file_path="" and logging_mode = LOG_MODE_FILE. */

  if (*log_file_path)
    {
    int sep = ':';              /* Fixed separator - outside use */
    uschar *s;
    const uschar *ss = log_file_path;

    logging_mode = 0;
    while ((s = string_nextinlist(&ss, &sep, log_buffer, LOG_BUFFER_SIZE)))
      {
      if (Ustrcmp(s, "syslog") == 0)
        logging_mode |= LOG_MODE_SYSLOG;
      else if (logging_mode & LOG_MODE_FILE)
	multiple = TRUE;
      else
        {
        logging_mode |= LOG_MODE_FILE;

        /* If a non-empty path is given, use it */

        if (*s)
          file_path = string_copy(s);

        /* If the path is empty, we want to use the first non-empty, non-
        syslog item in LOG_FILE_PATH, if there is one, since the value of
        log_file_path may have been set at runtime. If there is no such item,
        use the ultimate default in the spool directory. */

        else
	  set_file_path();  /* Empty item in log_file_path */
        }    /* First non-syslog item in log_file_path */
      }      /* Scan of log_file_path */
    }

  /* If no modes have been selected, it is a major disaster */

  if (logging_mode == 0)
    die(US"Neither syslog nor file logging set in log_file_path",
        US"Unexpected logging failure");

  /* Set up the ultimate default if necessary. Then revert to the old store
  pool, and record that we've sorted out the path. */

  if (logging_mode & LOG_MODE_FILE  &&  !file_path[0])
    file_path = string_sprintf("%s/log/%%slog", spool_directory);
  store_pool = old_pool;
  path_inspected = TRUE;

  /* If more than one file path was given, log a complaint. This recursive call
  should work since we have now set up the routing. */

  if (multiple)
    log_write(0, LOG_MAIN|LOG_PANIC,
      "More than one path given in log_file_path: using %s", file_path);
  }

/* If debugging, show all log entries, but don't show headers. Do it all
in one go so that it doesn't get split when multi-processing. */

DEBUG(D_any|D_v)
  {
  int i;

  g = string_catn(&gs, US"LOG:", 4);

  /* Show the selector that was passed into the call. */

  for (i = 0; i < log_options_count; i++)
    {
    unsigned int bitnum = log_options[i].bit;
    if (bitnum < BITWORDSIZE && selector == BIT(bitnum))
      g = string_fmt_append(g, " %s", log_options[i].name);
    }

  g = string_fmt_append(g, "%s%s%s%s\n  ",
    flags & LOG_MAIN ?    " MAIN"   : "",
    flags & LOG_PANIC ?   " PANIC"  : "",
    (flags & LOG_PANIC_DIE) == LOG_PANIC_DIE ? " DIE" : "",
    flags & LOG_REJECT ?  " REJECT" : "");

  if (flags & LOG_CONFIG) g = log_config_info(g, flags);

  va_start(ap, format);
  i = g->ptr;
  if (!string_vformat(g, FALSE, format, ap))
    {
    g->ptr = i;
    g = string_cat(g, US"**** log string overflowed log buffer ****");
    }
  va_end(ap);

  g->size = LOG_BUFFER_SIZE;
  g = string_catn(g, US"\n", 1);
  debug_printf("%s", string_from_gstring(g));

  gs.size = LOG_BUFFER_SIZE-1;	/* Having used the buffer for debug output, */
  gs.ptr = 0;			/* reset it for the real use. */
  gs.s = log_buffer;
  }
/* If no log file is specified, we are in a mess. */

if (!(flags & (LOG_MAIN|LOG_PANIC|LOG_REJECT)))
  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "log_write called with no log "
    "flags set");

/* There are some weird circumstances in which logging is disabled. */

if (f.disable_logging)
  {
  DEBUG(D_any) debug_printf("log writing disabled\n");
  return;
  }

/* Handle disabled reject log */

if (!write_rejectlog) flags &= ~LOG_REJECT;

/* Create the main message in the log buffer. Do not include the message id
when called by a utility. */

g = string_fmt_append(&gs, "%s ", tod_stamp(tod_log));

if (LOGGING(pid))
  {
  if (!syslog_pid) pid_position[0] = g->ptr;		/* remember begin … */
  g = string_fmt_append(g, "[%d] ", (int)getpid());
  if (!syslog_pid) pid_position[1] = g->ptr;		/*  … and end+1 of the PID */
  }

if (f.really_exim && message_id[0] != 0)
  g = string_fmt_append(g, "%s ", message_id);

if (flags & LOG_CONFIG)
  g = log_config_info(g, flags);

va_start(ap, format);
  {
  int i = g->ptr;
  if (!string_vformat(g, FALSE, format, ap))
    {
    g->ptr = i;
    g = string_cat(g, US"**** log string overflowed log buffer ****\n");
    }
  }
va_end(ap);

/* Add the raw, unrewritten, sender to the message if required. This is done
this way because it kind of fits with LOG_RECIPIENTS. */

if (   flags & LOG_SENDER
   && g->ptr < LOG_BUFFER_SIZE - 10 - Ustrlen(raw_sender))
  g = string_fmt_append(g, " from <%s>", raw_sender);

/* Add list of recipients to the message if required; the raw list,
before rewriting, was saved in raw_recipients. There may be none, if an ACL
discarded them all. */

if (  flags & LOG_RECIPIENTS
   && g->ptr < LOG_BUFFER_SIZE - 6
   && raw_recipients_count > 0)
  {
  int i;
  g = string_fmt_append(g, " for");
  for (i = 0; i < raw_recipients_count; i++)
    {
    uschar * s = raw_recipients[i];
    if (LOG_BUFFER_SIZE - g->ptr < Ustrlen(s) + 3) break;
    g = string_fmt_append(g, " %s", s);
    }
  }

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

/* Handle loggable errors when running a utility, or when address testing.
Write to log_stderr unless debugging (when it will already have been written),
or unless there is no log_stderr (expn called from daemon, for example). */

if (!f.really_exim || f.log_testing_mode)
  {
  if (  !debug_selector
     && log_stderr
     && (selector == 0 || (selector & log_selector[0]) != 0)
    )
    if (host_checking)
      fprintf(log_stderr, "LOG: %s", CS(log_buffer + 20));  /* no timestamp */
    else
      fprintf(log_stderr, "%s", CS log_buffer);

  if ((flags & LOG_PANIC_DIE) == LOG_PANIC_DIE) exim_exit(EXIT_FAILURE, US"");
  return;
  }

/* Handle the main log. We know that either syslog or file logging (or both) is
set up. A real file gets left open during reception or delivery once it has
been opened, but we don't want to keep on writing to it for too long after it
has been renamed. Therefore, do a stat() and see if the inode has changed, and
if so, re-open. */

if (  flags & LOG_MAIN
   && (!selector ||  selector & log_selector[0]))
  {
  if (  logging_mode & LOG_MODE_SYSLOG
     && (syslog_duplication || !(flags & (LOG_REJECT|LOG_PANIC))))
    write_syslog(LOG_INFO, log_buffer);

  if (logging_mode & LOG_MODE_FILE)
    {
    struct stat statbuf;

    /* Check for a change to the mainlog file name when datestamping is in
    operation. This happens at midnight, at which point we want to roll over
    the file. Closing it has the desired effect. */

    if (mainlog_datestamp)
      {
      uschar *nowstamp = tod_stamp(string_datestamp_type);
      if (Ustrncmp (mainlog_datestamp, nowstamp, Ustrlen(nowstamp)) != 0)
        {
        (void)close(mainlogfd);       /* Close the file */
        mainlogfd = -1;               /* Clear the file descriptor */
        mainlog_inode = 0;            /* Unset the inode */
        mainlog_datestamp = NULL;     /* Clear the datestamp */
        }
      }

    /* Otherwise, we want to check whether the file has been renamed by a
    cycling script. This could be "if else", but for safety's sake, leave it as
    "if" so that renaming the log starts a new file even when datestamping is
    happening. */

    if (mainlogfd >= 0)
      if (Ustat(mainlog_name, &statbuf) < 0 || statbuf.st_ino != mainlog_inode)
	mainlog_close();

    /* If the log is closed, open it. Then write the line. */

    if (mainlogfd < 0)
      {
      open_log(&mainlogfd, lt_main, NULL);     /* No return on error */
      if (fstat(mainlogfd, &statbuf) >= 0) mainlog_inode = statbuf.st_ino;
      }

    /* Failing to write to the log is disastrous */

    written_len = write_to_fd_buf(mainlogfd, g->s, g->ptr);
    if (written_len != g->ptr)
      {
      log_write_failed(US"main log", g->ptr, written_len);
      /* That function does not return */
      }
    }
  }

/* Handle the log for rejected messages. This can be globally disabled, in
which case the flags are altered above. If there are any header lines (i.e. if
the rejection is happening after the DATA phase), log the recipients and the
headers. */

if (flags & LOG_REJECT)
  {
  header_line *h;

  if (header_list && LOGGING(rejected_header))
    {
    uschar * p = g->s + g->ptr;
    int i;

    if (recipients_count > 0)
      {
      /* List the sender */

      string_format(p, LOG_BUFFER_SIZE - g->ptr,
        "Envelope-from: <%s>\n", sender_address);
      while (*p) p++;
      g->ptr = p - g->s;

      /* List up to 5 recipients */

      string_format(p, LOG_BUFFER_SIZE - g->ptr,
        "Envelope-to: <%s>\n", recipients_list[0].address);
      while (*p) p++;
      g->ptr = p - g->s;

      for (i = 1; i < recipients_count && i < 5; i++)
        {
        string_format(p, LOG_BUFFER_SIZE - g->ptr, "    <%s>\n",
          recipients_list[i].address);
	while (*p) p++;
	g->ptr = p - g->s;
        }

      if (i < recipients_count)
        {
        string_format(p, LOG_BUFFER_SIZE - g->ptr,
          "    ...\n");
	while (*p) p++;
	g->ptr = p - g->s;
        }
      }

    /* A header with a NULL text is an unfilled in Received: header */

    for (h = header_list; h; h = h->next) if (h->text)
      {
      BOOL fitted = string_format(p, LOG_BUFFER_SIZE - g->ptr,
        "%c %s", h->type, h->text);
      while (*p) p++;
      g->ptr = p - g->s;
      if (!fitted)         /* Buffer is full; truncate */
        {
        g->ptr -= 100;        /* For message and separator */
        if (g->s[g->ptr-1] == '\n') g->ptr--;
        g = string_cat(g, US"\n*** truncated ***\n");
        break;
        }
      }
    }

  /* Write to syslog or to a log file */

  if (  logging_mode & LOG_MODE_SYSLOG
     && (syslog_duplication || !(flags & LOG_PANIC)))
    write_syslog(LOG_NOTICE, string_from_gstring(g));

  /* Check for a change to the rejectlog file name when datestamping is in
  operation. This happens at midnight, at which point we want to roll over
  the file. Closing it has the desired effect. */

  if (logging_mode & LOG_MODE_FILE)
    {
    struct stat statbuf;

    if (rejectlog_datestamp)
      {
      uschar *nowstamp = tod_stamp(string_datestamp_type);
      if (Ustrncmp (rejectlog_datestamp, nowstamp, Ustrlen(nowstamp)) != 0)
        {
        (void)close(rejectlogfd);       /* Close the file */
        rejectlogfd = -1;               /* Clear the file descriptor */
        rejectlog_inode = 0;            /* Unset the inode */
        rejectlog_datestamp = NULL;     /* Clear the datestamp */
        }
      }

    /* Otherwise, we want to check whether the file has been renamed by a
    cycling script. This could be "if else", but for safety's sake, leave it as
    "if" so that renaming the log starts a new file even when datestamping is
    happening. */

    if (rejectlogfd >= 0)
      if (Ustat(rejectlog_name, &statbuf) < 0 ||
           statbuf.st_ino != rejectlog_inode)
        {
        (void)close(rejectlogfd);
        rejectlogfd = -1;
        rejectlog_inode = 0;
        }

    /* Open the file if necessary, and write the data */

    if (rejectlogfd < 0)
      {
      open_log(&rejectlogfd, lt_reject, NULL); /* No return on error */
      if (fstat(rejectlogfd, &statbuf) >= 0) rejectlog_inode = statbuf.st_ino;
      }

    written_len = write_to_fd_buf(rejectlogfd, g->s, g->ptr);
    if (written_len != g->ptr)
      {
      log_write_failed(US"reject log", g->ptr, written_len);
      /* That function does not return */
      }
    }
  }


/* Handle the panic log, which is not kept open like the others. If it fails to
open, there will be a recursive call to log_write(). We detect this above and
attempt to write to the system log as a last-ditch try at telling somebody. In
all cases except mua_wrapper, try to write to log_stderr. */

if (flags & LOG_PANIC)
  {
  if (log_stderr && log_stderr != debug_file && !mua_wrapper)
    fprintf(log_stderr, "%s", CS string_from_gstring(g));

  if (logging_mode & LOG_MODE_SYSLOG)
    write_syslog(LOG_ALERT, log_buffer);

  /* If this panic logging was caused by a failure to open the main log,
  the original log line is in panic_save_buffer. Make an attempt to write it. */

  if (logging_mode & LOG_MODE_FILE)
    {
    panic_recurseflag = TRUE;
    open_log(&paniclogfd, lt_panic, NULL);  /* Won't return on failure */
    panic_recurseflag = FALSE;

    if (panic_save_buffer)
      {
      int i = write(paniclogfd, panic_save_buffer, Ustrlen(panic_save_buffer));
      i = i;	/* compiler quietening */
      }

    written_len = write_to_fd_buf(paniclogfd, g->s, g->ptr);
    if (written_len != g->ptr)
      {
      int save_errno = errno;
      write_syslog(LOG_CRIT, log_buffer);
      sprintf(CS log_buffer, "write failed on panic log: length=%d result=%d "
        "errno=%d (%s)", g->ptr, (int)written_len, save_errno, strerror(save_errno));
      write_syslog(LOG_CRIT, string_from_gstring(g));
      flags |= LOG_PANIC_DIE;
      }

    (void)close(paniclogfd);
    }

  /* Give up if the DIE flag is set */

  if ((flags & LOG_PANIC_DIE) != LOG_PANIC)
    die(NULL, US"Unexpected failure, please try later");
  }
}

Project: exim

int auth_heimdal_gssapi_server(auth_instance *ablock, uschar *data) {return 0;}

Project: exim

header_remove(int occ, const uschar *name)
{
header_line *h;
int hcount = 0;
int len = Ustrlen(name);
for (h = header_list; h != NULL; h = h->next)
  {
  if (header_testname(h, name, len, TRUE) && (occ <= 0 || ++hcount == occ))
    {
    h->type = htype_old;
    if (occ > 0) return;
    }
  }
}

Project: exim

lmtp_read_response(FILE *f, uschar *buffer, int size, int okdigit, int timeout)
{
int count;
uschar *ptr = buffer;
uschar *readptr = buffer;

/* Ensure errno starts out zero */

errno = 0;

/* Loop for handling LMTP responses that do not all come in one line. */

for (;;)
  {
  /* If buffer is too full, something has gone wrong. */

  if (size < 10)
    {
    *readptr = 0;
    errno = ERRNO_SMTPFORMAT;
    return FALSE;
    }

  /* Loop to cover the read getting interrupted. */

  for (;;)
    {
    char *rc;
    int save_errno;

    *readptr = 0;           /* In case nothing gets read */
    sigalrm_seen = FALSE;
    ALARM(timeout);
    rc = Ufgets(readptr, size-1, f);
    save_errno = errno;
    ALARM_CLR(0);
    errno = save_errno;

    if (rc != NULL) break;  /* A line has been read */

    /* Handle timeout; must do this first because it uses EINTR */

    if (sigalrm_seen) errno = ETIMEDOUT;

    /* If some other interrupt arrived, just retry. We presume this to be rare,
    but it can happen (e.g. the SIGUSR1 signal sent by exiwhat causes
    read() to exit). */

    else if (errno == EINTR)
      {
      DEBUG(D_transport) debug_printf("EINTR while reading LMTP response\n");
      continue;
      }

    /* Handle other errors, including EOF; ensure buffer is completely empty. */

    buffer[0] = 0;
    return FALSE;
    }

  /* Adjust size in case we have to read another line, and adjust the
  count to be the length of the line we are about to inspect. */

  count = Ustrlen(readptr);
  size -= count;
  count += readptr - ptr;

  /* See if the final two characters in the buffer are \r\n. If not, we
  have to read some more. At least, that is what we should do on a strict
  interpretation of the RFC. But accept LF as well, as we do for SMTP. */

  if (ptr[count-1] != '\n')
    {
    DEBUG(D_transport)
      {
      int i;
      debug_printf("LMTP input line incomplete in one buffer:\n  ");
      for (i = 0; i < count; i++)
        {
        int c = (ptr[i]);
        if (mac_isprint(c)) debug_printf("%c", c); else debug_printf("<%d>", c);
        }
      debug_printf("\n");
      }
    readptr = ptr + count;
    continue;
    }

  /* Remove any whitespace at the end of the buffer. This gets rid of CR, LF
  etc. at the end. Show it, if debugging, formatting multi-line responses. */

  while (count > 0 && isspace(ptr[count-1])) count--;
  ptr[count] = 0;

  DEBUG(D_transport|D_v)
    {
    uschar *s = ptr;
    uschar *t = ptr;
    while (*t != 0)
      {
      while (*t != 0 && *t != '\n') t++;
      debug_printf("  %s %*s\n", (s == ptr)? "LMTP<<" : "      ",
        (int)(t-s), s);
      if (*t == 0) break;
      s = t = t + 1;
      }
    }

  /* 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. A multiline response may already
  have been read in one go - hence the loop here. */

  for(;;)
    {
    uschar *p;
    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 a single-line response, exit the loop */

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

    /* For a multi-line response see if the next line is already read, and if
    so, stay in this loop to check it. */

    p = ptr + 3;
    while (*(++p) != 0)
      {
      if (*p == '\n')
        {
        ptr = ++p;
        break;
        }
      }
    if (*p == 0) break;   /* No more lines to check */
    }

  /* End of response. If the last of the lines we are looking at is the final
  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 in case this
  is an error message that subsequently gets printed. Set the scanning pointer
  to the reading pointer position. */

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

/* Return a value that depends on the LMTP return code. Ensure that errno 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

transport_write_string(int fd, const char *format, ...)
{
transport_ctx tctx = {{0}};
gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
va_list ap;

va_start(ap, format);
if (!string_vformat(&gs, FALSE, format, ap))
  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong formatted string in transport");
va_end(ap);
tctx.u.fd = fd;
return transport_write_block(&tctx, gs.s, gs.ptr, FALSE);
}

Project: exim

directory_make(char *parent, char *name, int mode, int panic)
{
parent = parent;  /* Keep picky compilers happy */
name = name;
mode = mode;
panic = panic;
}

Project: exim

smtp_respond(uschar* code, int codelen, BOOL final, uschar *msg)
{
int esclen = 0;
uschar *esc = US"";

if (!final && f.no_multiline_responses) return;

if (codelen > 4)
  {
  esc = code + 4;
  esclen = codelen - 4;
  }

/* 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_printf(). 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(msg);
  else if (fl.rcpt_smtp_response_same &&
           Ustrcmp(rcpt_smtp_response, msg) != 0)
    fl.rcpt_smtp_response_same = FALSE;
  fl.rcpt_in_progress = FALSE;
  }

/* Now output the message, splitting it up into multiple lines if necessary.
We only handle pipelining these responses as far as nonfinal/final groups,
not the whole MAIL/RCPT/DATA response set. */

for (;;)
  {
  uschar *nl = Ustrchr(msg, '\n');
  if (nl == NULL)
    {
    smtp_printf("%.3s%c%.*s%s\r\n", !final, code, final ? ' ':'-', esclen, esc, msg);
    return;
    }
  else if (nl[1] == 0 || f.no_multiline_responses)
    {
    smtp_printf("%.3s%c%.*s%.*s\r\n", !final, code, final ? ' ':'-', esclen, esc,
      (int)(nl - msg), msg);
    return;
    }
  else
    {
    smtp_printf("%.3s-%.*s%.*s\r\n", TRUE, code, esclen, esc, (int)(nl - msg), msg);
    msg = nl + 1;
    while (isspace(*msg)) msg++;
    }
  }
}

Project: exim

exim_nullstd(void)
{
int i;
int devnull = -1;
struct stat statbuf;
for (i = 0; i <= 2; i++)
  {
  if (fstat(i, &statbuf) < 0 && errno == EBADF)
    {
    if (devnull < 0) devnull = open("/dev/null", O_RDWR);
    if (devnull < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s",
      string_open_failed(errno, "/dev/null"));
    if (devnull != i) (void)dup2(devnull, i);
    }
  }
if (devnull > 2) (void)close(devnull);
}

Project: exim

bits_clear(unsigned int *selector, size_t selsize, int *bits)
{
for(; *bits != -1; ++bits)
  BIT_CLEAR(selector, selsize, *bits);
}

Project: exim

tfo_in_check(void)
{
# ifdef TCP_INFO
struct tcp_info tinfo;
socklen_t len = sizeof(tinfo);

if (getsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0)
#ifdef TCPI_OPT_SYN_DATA	/* FreeBSD 11 does not seem to have this yet */
  if (tinfo.tcpi_options & TCPI_OPT_SYN_DATA)
    {
    DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (ACKd data-on-SYN)\n");
    f.tcp_in_fastopen_data = f.tcp_in_fastopen = TRUE;
    }
  else
#endif
    if (tinfo.tcpi_state == TCP_SYN_RECV)
    {
    DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (state TCP_SYN_RECV)\n");
    f.tcp_in_fastopen = TRUE;
    }
# endif
}

Project: exim

host_mask(int count, int *binary, int mask)
{
int i;
if (mask < 0) mask = 99999;
for (i = 0; i < count; i++)
  {
  int wordmask;
  if (mask == 0) wordmask = 0;
  else if (mask < 32)
    {
    wordmask = (uint)(-1) << (32 - mask);
    mask = 0;
    }
  else
    {
    wordmask = -1;
    mask -= 32;
    }
  binary[i] &= wordmask;
  }
}

Project: exim

log_config_info(gstring * g, int flags)
{
g = string_cat(g, US"Exim configuration error");

if (flags & (LOG_CONFIG_FOR & ~LOG_CONFIG))
  return string_cat(g, US" for ");

if (flags & (LOG_CONFIG_IN & ~LOG_CONFIG))
  g = string_fmt_append(g, " in line %d of %s", config_lineno, config_filename);

return string_catn(g, US":\n  ", 4);
}

Project: exim

acl_var_create(uschar * name)
{
tree_node * node, ** root = name[0] == 'c' ? &acl_var_c : &acl_var_m;
if (!(node = tree_search(*root, name)))
  {
  node = store_get(sizeof(tree_node) + Ustrlen(name));
  Ustrcpy(node->name, name);
  (void)tree_insertnode(root, node);
  }
node->data.ptr = NULL;
return node;
}

Project: exim

smtp_get_interface(uschar *istring, int host_af, address_item *addr,
  uschar **interface, uschar *msg)
{
const uschar * expint;
uschar *iface;
int sep = 0;

if (!istring) return TRUE;

if (!(expint = expand_string(istring)))
  {
  if (f.expand_string_forcedfail) return TRUE;
  addr->transport_return = PANIC;
  addr->message = string_sprintf("failed to expand \"interface\" "
      "option for %s: %s", msg, expand_string_message);
  return FALSE;
  }

while (isspace(*expint)) expint++;
if (*expint == 0) return TRUE;

while ((iface = string_nextinlist(&expint, &sep, big_buffer,
          big_buffer_size)))
  {
  if (string_is_ip_address(iface, NULL) == 0)
    {
    addr->transport_return = PANIC;
    addr->message = string_sprintf("\"%s\" is not a valid IP "
      "address for the \"interface\" option for %s",
      iface, msg);
    return FALSE;
    }

  if (((Ustrchr(iface, ':') == NULL)? AF_INET:AF_INET6) == host_af)
    break;
  }

if (iface) *interface = string_copy(iface);
return TRUE;
}

Project: exim

show_whats_supported(FILE * fp)
{
auth_info * authi;

DEBUG(D_any) {} else show_db_version(fp);

fprintf(fp, "Support for:");
#ifdef SUPPORT_CRYPTEQ
  fprintf(fp, " crypteq");
#endif
#if HAVE_ICONV
  fprintf(fp, " iconv()");
#endif
#if HAVE_IPV6
  fprintf(fp, " IPv6");
#endif
#ifdef HAVE_SETCLASSRESOURCES
  fprintf(fp, " use_setclassresources");
#endif
#ifdef SUPPORT_PAM
  fprintf(fp, " PAM");
#endif
#ifdef EXIM_PERL
  fprintf(fp, " Perl");
#endif
#ifdef EXPAND_DLFUNC
  fprintf(fp, " Expand_dlfunc");
#endif
#ifdef USE_TCP_WRAPPERS
  fprintf(fp, " TCPwrappers");
#endif
#ifdef SUPPORT_TLS
# ifdef USE_GNUTLS
  fprintf(fp, " GnuTLS");
# else
  fprintf(fp, " OpenSSL");
# endif
#endif
#ifdef SUPPORT_TRANSLATE_IP_ADDRESS
  fprintf(fp, " translate_ip_address");
#endif
#ifdef SUPPORT_MOVE_FROZEN_MESSAGES
  fprintf(fp, " move_frozen_messages");
#endif
#ifdef WITH_CONTENT_SCAN
  fprintf(fp, " Content_Scanning");
#endif
#ifdef SUPPORT_DANE
  fprintf(fp, " DANE");
#endif
#ifndef DISABLE_DKIM
  fprintf(fp, " DKIM");
#endif
#ifndef DISABLE_DNSSEC
  fprintf(fp, " DNSSEC");
#endif
#ifndef DISABLE_EVENT
  fprintf(fp, " Event");
#endif
#ifdef SUPPORT_I18N
  fprintf(fp, " I18N");
#endif
#ifndef DISABLE_OCSP
  fprintf(fp, " OCSP");
#endif
#ifndef DISABLE_PRDR
  fprintf(fp, " PRDR");
#endif
#ifdef SUPPORT_PROXY
  fprintf(fp, " PROXY");
#endif
#ifdef SUPPORT_SOCKS
  fprintf(fp, " SOCKS");
#endif
#ifdef SUPPORT_SPF
  fprintf(fp, " SPF");
#endif
#ifdef TCP_FASTOPEN
  deliver_init();
  if (f.tcp_fastopen_ok) fprintf(fp, " TCP_Fast_Open");
#endif
#ifdef EXPERIMENTAL_LMDB
  fprintf(fp, " Experimental_LMDB");
#endif
#ifdef EXPERIMENTAL_QUEUEFILE
  fprintf(fp, " Experimental_QUEUEFILE");
#endif
#ifdef EXPERIMENTAL_SRS
  fprintf(fp, " Experimental_SRS");
#endif
#ifdef EXPERIMENTAL_ARC
  fprintf(fp, " Experimental_ARC");
#endif
#ifdef EXPERIMENTAL_BRIGHTMAIL
  fprintf(fp, " Experimental_Brightmail");
#endif
#ifdef EXPERIMENTAL_DCC
  fprintf(fp, " Experimental_DCC");
#endif
#ifdef EXPERIMENTAL_DMARC
  fprintf(fp, " Experimental_DMARC");
#endif
#ifdef EXPERIMENTAL_DSN_INFO
  fprintf(fp, " Experimental_DSN_info");
#endif
#ifdef EXPERIMENTAL_REQUIRETLS
  fprintf(fp, " Experimental_REQUIRETLS");
#endif
#ifdef EXPERIMENTAL_PIPE_CONNECT
  fprintf(fp, " Experimental_PIPE_CONNECT");
#endif
fprintf(fp, "\n");

fprintf(fp, "Lookups (built-in):");
#if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2
  fprintf(fp, " lsearch wildlsearch nwildlsearch iplsearch");
#endif
#if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
  fprintf(fp, " cdb");
#endif
#if defined(LOOKUP_DBM) && LOOKUP_DBM!=2
  fprintf(fp, " dbm dbmjz dbmnz");
#endif
#if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2
  fprintf(fp, " dnsdb");
#endif
#if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2
  fprintf(fp, " dsearch");
#endif
#if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2
  fprintf(fp, " ibase");
#endif
#if defined(LOOKUP_LDAP) && LOOKUP_LDAP!=2
  fprintf(fp, " ldap ldapdn ldapm");
#endif
#ifdef EXPERIMENTAL_LMDB
  fprintf(fp, " lmdb");
#endif
#if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
  fprintf(fp, " mysql");
#endif
#if defined(LOOKUP_NIS) && LOOKUP_NIS!=2
  fprintf(fp, " nis nis0");
#endif
#if defined(LOOKUP_NISPLUS) && LOOKUP_NISPLUS!=2
  fprintf(fp, " nisplus");
#endif
#if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2
  fprintf(fp, " oracle");
#endif
#if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2
  fprintf(fp, " passwd");
#endif
#if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
  fprintf(fp, " pgsql");
#endif
#if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2
  fprintf(fp, " redis");
#endif
#if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
  fprintf(fp, " sqlite");
#endif
#if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2
  fprintf(fp, " testdb");
#endif
#if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2
  fprintf(fp, " whoson");
#endif
fprintf(fp, "\n");

auth_show_supported(fp);
route_show_supported(fp);
transport_show_supported(fp);

#ifdef WITH_CONTENT_SCAN
malware_show_supported(fp);
#endif

if (fixed_never_users[0] > 0)
  {
  int i;
  fprintf(fp, "Fixed never_users: ");
  for (i = 1; i <= (int)fixed_never_users[0] - 1; i++)
    fprintf(fp, "%d:", (unsigned int)fixed_never_users[i]);
  fprintf(fp, "%d\n", (unsigned int)fixed_never_users[i]);
  }

fprintf(fp, "Configure owner: %d:%d\n", config_uid, config_gid);

fprintf(fp, "Size of off_t: " SIZE_T_FMT "\n", sizeof(off_t));

/* Everything else is details which are only worth reporting when debugging.
Perhaps the tls_version_report should move into this too. */
DEBUG(D_any) do {

  int i;

/* clang defines __GNUC__ (at least, for me) so test for it first */
#if defined(__clang__)
  fprintf(fp, "Compiler: CLang [%s]\n", __clang_version__);
#elif defined(__GNUC__)
  fprintf(fp, "Compiler: GCC [%s]\n",
# ifdef __VERSION__
      __VERSION__
# else
      "? unknown version ?"
# endif
      );
#else
  fprintf(fp, "Compiler: <unknown>\n");
#endif

#if defined(__GLIBC__) && !defined(__UCLIBC__)
  fprintf(fp, "Library version: Glibc: Compile: %d.%d\n",
	       	__GLIBC__, __GLIBC_MINOR__);
  if (__GLIBC_PREREQ(2, 1))
    fprintf(fp, "                        Runtime: %s\n",
	       	gnu_get_libc_version());
#endif

show_db_version(fp);

#ifdef SUPPORT_TLS
  tls_version_report(fp);
#endif
#ifdef SUPPORT_I18N
  utf8_version_report(fp);
#endif

  for (authi = auths_available; *authi->driver_name != '\0'; ++authi)
    if (authi->version_report)
      (*authi->version_report)(fp);

  /* PCRE_PRERELEASE is either defined and empty or a bare sequence of
  characters; unless it's an ancient version of PCRE in which case it
  is not defined. */
#ifndef PCRE_PRERELEASE
# define PCRE_PRERELEASE
#endif
#define QUOTE(X) #X
#define EXPAND_AND_QUOTE(X) QUOTE(X)
  fprintf(fp, "Library version: PCRE: Compile: %d.%d%s\n"
             "                       Runtime: %s\n",
          PCRE_MAJOR, PCRE_MINOR,
          EXPAND_AND_QUOTE(PCRE_PRERELEASE) "",
          pcre_version());
#undef QUOTE
#undef EXPAND_AND_QUOTE

  init_lookup_list();
  for (i = 0; i < lookup_list_count; i++)
    if (lookup_list[i]->version_report)
      lookup_list[i]->version_report(fp);

#ifdef WHITELIST_D_MACROS
  fprintf(fp, "WHITELIST_D_MACROS: \"%s\"\n", WHITELIST_D_MACROS);
#else
  fprintf(fp, "WHITELIST_D_MACROS unset\n");
#endif
#ifdef TRUSTED_CONFIG_LIST
  fprintf(fp, "TRUSTED_CONFIG_LIST: \"%s\"\n", TRUSTED_CONFIG_LIST);
#else
  fprintf(fp, "TRUSTED_CONFIG_LIST unset\n");
#endif

} while (0);
}

Project: exim

pipeline_response(void)
{
if (  !smtp_enforce_sync || !sender_host_address
   || f.sender_host_notsocket || !f.smtp_in_pipelining_advertised)
  return FALSE;

if (wouldblock_reading()) return FALSE;
f.smtp_in_pipelining_used = TRUE;
return TRUE;
}

Project: exim

auth_heimdal_gssapi_server(auth_instance *ablock, uschar *initial_data)
{
  gss_name_t gclient = GSS_C_NO_NAME;
  gss_name_t gserver = GSS_C_NO_NAME;
  gss_cred_id_t gcred = GSS_C_NO_CREDENTIAL;
  gss_ctx_id_t gcontext = GSS_C_NO_CONTEXT;
  uschar *ex_server_str;
  gss_buffer_desc gbufdesc = GSS_C_EMPTY_BUFFER;
  gss_buffer_desc gbufdesc_in = GSS_C_EMPTY_BUFFER;
  gss_buffer_desc gbufdesc_out = GSS_C_EMPTY_BUFFER;
  gss_OID mech_type;
  OM_uint32 maj_stat, min_stat;
  int step, error_out, i;
  uschar *tmp1, *tmp2, *from_client;
  auth_heimdal_gssapi_options_block *ob =
    (auth_heimdal_gssapi_options_block *)(ablock->options_block);
  BOOL handled_empty_ir;
  uschar *store_reset_point;
  uschar *keytab;
  uschar sasl_config[4];
  uschar requested_qop;

  store_reset_point = store_get(0);

  HDEBUG(D_auth)
    debug_printf("heimdal: initialising auth context for %s\n", ablock->name);

  /* Construct our gss_name_t gserver describing ourselves */
  tmp1 = expand_string(ob->server_service);
  tmp2 = expand_string(ob->server_hostname);
  ex_server_str = string_sprintf("%s@%s", tmp1, tmp2);
  gbufdesc.value = (void *) ex_server_str;
  gbufdesc.length = Ustrlen(ex_server_str);
  maj_stat = gss_import_name(&min_stat,
      &gbufdesc, GSS_C_NT_HOSTBASED_SERVICE, &gserver);
  if (GSS_ERROR(maj_stat))
    return exim_gssapi_error_defer(store_reset_point, maj_stat, min_stat,
        "gss_import_name(%s)", CS gbufdesc.value);

  /* Use a specific keytab, if specified */
  if (ob->server_keytab) {
    keytab = expand_string(ob->server_keytab);
    maj_stat = gsskrb5_register_acceptor_identity(CCS keytab);
    if (GSS_ERROR(maj_stat))
      return exim_gssapi_error_defer(store_reset_point, maj_stat, min_stat,
          "registering keytab \"%s\"", keytab);
    HDEBUG(D_auth)
      debug_printf("heimdal: using keytab \"%s\"\n", keytab);
  }

  /* Acquire our credentials */
  maj_stat = gss_acquire_cred(&min_stat,
      gserver,             /* desired name */
      0,                   /* time */
      GSS_C_NULL_OID_SET,  /* desired mechs */
      GSS_C_ACCEPT,        /* cred usage */
      &gcred,              /* handle */
      NULL                 /* actual mechs */,
      NULL                 /* time rec */);
  if (GSS_ERROR(maj_stat))
    return exim_gssapi_error_defer(store_reset_point, maj_stat, min_stat,
        "gss_acquire_cred(%s)", ex_server_str);

  maj_stat = gss_release_name(&min_stat, &gserver);

  HDEBUG(D_auth) debug_printf("heimdal: have server credentials.\n");

  /* Loop talking to client */
  step = 0;
  from_client = initial_data;
  handled_empty_ir = FALSE;
  error_out = OK;

  /* buffer sizes: auth_get_data() uses big_buffer, which we grow per
  GSSAPI RFC in _init, if needed, to meet the SHOULD size of 64KB.
  (big_buffer starts life at the MUST size of 16KB). */

  /* step values
  0: getting initial data from client to feed into GSSAPI
  1: iterating for as long as GSS_S_CONTINUE_NEEDED
  2: GSS_S_COMPLETE, SASL wrapping for authz and qop to send to client
  3: unpick final auth message from client
  4: break/finish (non-step)
  */
  while (step < 4) {
    switch (step) {
      case 0:
        if (!from_client || *from_client == '\0') {
          if (handled_empty_ir) {
            HDEBUG(D_auth) debug_printf("gssapi: repeated empty input, grr.\n");
            error_out = BAD64;
            goto ERROR_OUT;
          } else {
            HDEBUG(D_auth) debug_printf("gssapi: missing initial response, nudging.\n");
            error_out = auth_get_data(&from_client, US"", 0);
            if (error_out != OK)
              goto ERROR_OUT;
            handled_empty_ir = TRUE;
            continue;
          }
        }
        /* We should now have the opening data from the client, base64-encoded. */
        step += 1;
        HDEBUG(D_auth) debug_printf("heimdal: have initial client data\n");
        break;

      case 1:
        gbufdesc_in.length = b64decode(from_client, USS &gbufdesc_in.value);
        if (gclient) {
          maj_stat = gss_release_name(&min_stat, &gclient);
          gclient = GSS_C_NO_NAME;
        }
        maj_stat = gss_accept_sec_context(&min_stat,
            &gcontext,          /* context handle */
            gcred,              /* acceptor cred handle */
            &gbufdesc_in,       /* input from client */
            GSS_C_NO_CHANNEL_BINDINGS,  /* XXX fixme: use the channel bindings from GnuTLS */
            &gclient,           /* client identifier */
            &mech_type,         /* mechanism in use */
            &gbufdesc_out,      /* output to send to client */
            NULL,               /* return flags */
            NULL,               /* time rec */
            NULL                /* delegated cred_handle */
            );
        if (GSS_ERROR(maj_stat)) {
          exim_gssapi_error_defer(NULL, maj_stat, min_stat,
              "gss_accept_sec_context()");
          error_out = FAIL;
          goto ERROR_OUT;
        }
        if (gbufdesc_out.length != 0) {
          error_out = auth_get_data(&from_client,
              gbufdesc_out.value, gbufdesc_out.length);
          if (error_out != OK)
            goto ERROR_OUT;

          gss_release_buffer(&min_stat, &gbufdesc_out);
          EmptyBuf(gbufdesc_out);
        }
        if (maj_stat == GSS_S_COMPLETE) {
          step += 1;
          HDEBUG(D_auth) debug_printf("heimdal: GSS complete\n");
        } else {
          HDEBUG(D_auth) debug_printf("heimdal: need more data\n");
        }
        break;

      case 2:
        memset(sasl_config, 0xFF, 4);
        /* draft-ietf-sasl-gssapi-06.txt defines bitmasks for first octet
        0x01 No security layer
        0x02 Integrity protection
        0x04 Confidentiality protection

        The remaining three octets are the maximum buffer size for wrapped
        content. */
        sasl_config[0] = 0x01;  /* Exim does not wrap/unwrap SASL layers after auth */
        gbufdesc.value = (void *) sasl_config;
        gbufdesc.length = 4;
        maj_stat = gss_wrap(&min_stat,
            gcontext,
            0,                    /* conf_req_flag: integrity only */
            GSS_C_QOP_DEFAULT,    /* qop requested */
            &gbufdesc,            /* message to protect */
            NULL,                 /* conf_state: no confidentiality applied */
            &gbufdesc_out         /* output buffer */
            );
        if (GSS_ERROR(maj_stat)) {
          exim_gssapi_error_defer(NULL, maj_stat, min_stat,
              "gss_wrap(SASL state after auth)");
          error_out = FAIL;
          goto ERROR_OUT;
        }

        HDEBUG(D_auth) debug_printf("heimdal SASL: requesting QOP with no security layers\n");

        error_out = auth_get_data(&from_client,
            gbufdesc_out.value, gbufdesc_out.length);
        if (error_out != OK)
          goto ERROR_OUT;

        gss_release_buffer(&min_stat, &gbufdesc_out);
        EmptyBuf(gbufdesc_out);
        step += 1;
        break;

      case 3:
        gbufdesc_in.length = b64decode(from_client, USS &gbufdesc_in.value);
        maj_stat = gss_unwrap(&min_stat,
            gcontext,
            &gbufdesc_in,       /* data from client */
            &gbufdesc_out,      /* results */
            NULL,               /* conf state */
            NULL                /* qop state */
            );
        if (GSS_ERROR(maj_stat)) {
          exim_gssapi_error_defer(NULL, maj_stat, min_stat,
              "gss_unwrap(final SASL message from client)");
          error_out = FAIL;
          goto ERROR_OUT;
        }
        if (gbufdesc_out.length < 4) {
          HDEBUG(D_auth)
            debug_printf("gssapi: final message too short; "
                "need flags, buf sizes and optional authzid\n");
          error_out = FAIL;
          goto ERROR_OUT;
        }

        requested_qop = (CS gbufdesc_out.value)[0];
        if ((requested_qop & 0x01) == 0) {
          HDEBUG(D_auth)
            debug_printf("gssapi: client requested security layers (%x)\n",
                (unsigned int) requested_qop);
          error_out = FAIL;
          goto ERROR_OUT;
        }

        for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
        expand_nmax = 0;

        /* Identifiers:
        The SASL provided identifier is an unverified authzid.
        GSSAPI provides us with a verified identifier, but it might be empty
        for some clients.
        */

        /* $auth2 is authzid requested at SASL layer */
        if (gbufdesc_out.length > 4) {
          expand_nlength[2] = gbufdesc_out.length - 4;
          auth_vars[1] = expand_nstring[2] =
            string_copyn((US gbufdesc_out.value) + 4, expand_nlength[2]);
          expand_nmax = 2;
        }

        gss_release_buffer(&min_stat, &gbufdesc_out);
        EmptyBuf(gbufdesc_out);

        /* $auth1 is GSSAPI display name */
        maj_stat = gss_display_name(&min_stat,
            gclient,
            &gbufdesc_out,
            &mech_type);
        if (GSS_ERROR(maj_stat)) {
          auth_vars[1] = expand_nstring[2] = NULL;
          expand_nmax = 0;
          exim_gssapi_error_defer(NULL, maj_stat, min_stat,
              "gss_display_name(client identifier)");
          error_out = FAIL;
          goto ERROR_OUT;
        }

        expand_nlength[1] = gbufdesc_out.length;
        auth_vars[0] = expand_nstring[1] =
          string_copyn(gbufdesc_out.value, gbufdesc_out.length);

        if (expand_nmax == 0) { /* should be: authzid was empty */
          expand_nmax = 2;
          expand_nlength[2] = expand_nlength[1];
          auth_vars[1] = expand_nstring[2] = string_copyn(expand_nstring[1], expand_nlength[1]);
          HDEBUG(D_auth)
            debug_printf("heimdal SASL: empty authzid, set to dup of GSSAPI display name\n");
        }

        HDEBUG(D_auth)
          debug_printf("heimdal SASL: happy with client request\n"
             "  auth1 (verified GSSAPI display-name): \"%s\"\n"
             "  auth2 (unverified SASL requested authzid): \"%s\"\n",
             auth_vars[0], auth_vars[1]);

        step += 1;
        break;

    } /* switch */
  } /* while step */


ERROR_OUT:
  maj_stat = gss_release_cred(&min_stat, &gcred);
  if (gclient) {
    gss_release_name(&min_stat, &gclient);
    gclient = GSS_C_NO_NAME;
  }
  if (gbufdesc_out.length) {
    gss_release_buffer(&min_stat, &gbufdesc_out);
    EmptyBuf(gbufdesc_out);
  }
  if (gcontext != GSS_C_NO_CONTEXT) {
    gss_delete_sec_context(&min_stat, &gcontext, GSS_C_NO_BUFFER);
  }

  store_reset(store_reset_point);

  if (error_out != OK)
    return error_out;

  /* Auth succeeded, check server_condition */
  return auth_check_serv_cond(ablock);
}

Project: exim

options_transports(void)
{
struct transport_info * ti;
uschar buf[64];

options_from_list(optionlist_transports, nelem(optionlist_transports), US"TRANSPORTS", NULL);

for (ti = transports_available; ti->driver_name[0]; ti++)
  {
  spf(buf, sizeof(buf), US"_DRIVER_TRANSPORT_%T", ti->driver_name);
  builtin_macro_create(buf);
  options_from_list(ti->options, (unsigned)*ti->options_count, US"TRANSPORT", ti->driver_name);
  }
}

Project: exim

smtp_get_connection_info(void)
{
const uschar * hostname = sender_fullhost
  ? sender_fullhost : sender_host_address;

if (host_checking)
  return string_sprintf("SMTP connection from %s", hostname);

if (f.sender_host_unknown || f.sender_host_notsocket)
  return string_sprintf("SMTP connection from %s", sender_ident);

if (f.is_inetd)
  return string_sprintf("SMTP connection from %s (via inetd)", hostname);

if (LOGGING(incoming_interface) && interface_address != NULL)
  return string_sprintf("SMTP connection from %s I=[%s]:%d", hostname,
    interface_address, interface_port);

return string_sprintf("SMTP connection from %s", hostname);
}

Project: exim

void auth_heimdal_gssapi_init(auth_instance *ablock) {}

Project: exim

term_handler(int sig)
{
  exit(1);
}

Project: exim

tree_printsub(tree_node *p, int pos, int barswitch)
{
int i;
if (p->right) tree_printsub(p->right, pos+2, 1);
for (i = 0; i <= pos-1; i++) debug_printf("%c", tree_printline[i]);
debug_printf("-->%s [%d]\n", p->name, p->balance);
tree_printline[pos] = barswitch? '|' : ' ';
if (p->left)
  {
  tree_printline[pos+2] = '|';
  tree_printsub(p->left, pos+2, 0);
  }
}

Project: exim

sigalrm_handler(int sig)
{
sig = sig;      /* Keep picky compilers happy */
sigalrm_seen = TRUE;
os_non_restarting_signal(SIGALRM, sigalrm_handler);
}

Project: exim
File: call_pam.c

static void dummy2(int x) { dummy(x-1); }

Project: exim

deferral_log(address_item * addr, uschar * now,
  int logflags, uschar * driver_name, uschar * driver_kind)
{
gstring * g;
void * reset_point;

/* Build up the line that is used for both the message log and the main
log. */

g = reset_point = string_get(256);

/* Create the address string for logging. Must not do this earlier, because
an OK result may be changed to FAIL when a pipe returns text. */

g = string_log_address(g, addr, LOGGING(all_parents), FALSE);

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

/* Either driver_name contains something and driver_kind contains
" router" or " transport" (note the leading space), or driver_name is
a null string and driver_kind contains "routing" without the leading
space, if all routing has been deferred. When a domain has been held,
so nothing has been done at all, both variables contain null strings. */

if (driver_name)
  {
  if (driver_kind[1] == 't' && addr->router)
    g = string_append(g, 2, US" R=", addr->router->name);
  g = string_fmt_append(g, " %c=%s", toupper(driver_kind[1]), driver_name);
  }
else if (driver_kind)
  g = string_append(g, 2, US" ", driver_kind);

g = string_fmt_append(g, " defer (%d)", addr->basic_errno);

if (addr->basic_errno > 0)
  g = string_append(g, 2, US": ",
    US strerror(addr->basic_errno));

if (addr->host_used)
  {
  g = string_append(g, 5,
		    US" H=", addr->host_used->name,
		    US" [",  addr->host_used->address, US"]");
  if (LOGGING(outgoing_port))
    {
    int port = addr->host_used->port;
    g = string_fmt_append(g, ":%d", port == PORT_NONE ? 25 : port);
    }
  }

if (addr->message)
  g = string_append(g, 2, US": ", addr->message);

(void) string_from_gstring(g);

/* Log the deferment in the message log, but don't clutter it
up with retry-time defers after the first delivery attempt. */

if (f.deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
  deliver_msglog("%s %s\n", now, g->s);

/* Write the main log and reset the store.
For errors of the type "retry time not reached" (also remotes skipped
on queue run), logging is controlled by L_retry_defer. Note that this kind
of error number is negative, and all the retry ones are less than any
others. */


log_write(addr->basic_errno <= ERRNO_RETRY_BASE ? L_retry_defer : 0, logflags,
  "== %s", g->s);

store_reset(reset_point);
return;
}

Project: exim

debug_printf(const char *format, ...)
{
va_list ap;
va_start(ap, format);
debug_vprintf(0, format, ap);
va_end(ap);
}

Project: exim
File: deliver.c

string_timesince(struct timeval * then)
{
struct timeval diff;

timesince(&diff, then);
return string_timediff(&diff);
}

Project: exim

modefopen(const uschar *filename, const char *options, mode_t mode)
{
mode_t saved_umask = umask(0777);
FILE *f = Ufopen(filename, options);
(void)umask(saved_umask);
if (f != NULL) (void)fchmod(fileno(f), mode);
return f;
}

Project: exim

smtp_read_command(BOOL check_sync, unsigned buffer_lim)
{
int c;
int ptr = 0;
smtp_cmd_list *p;
BOOL hadnull = FALSE;

had_command_timeout = 0;
os_non_restarting_signal(SIGALRM, command_timeout_handler);

while ((c = (receive_getc)(buffer_lim)) != '\n' && c != EOF)
  {
  if (ptr >= SMTP_CMD_BUFFER_SIZE)
    {
    os_non_restarting_signal(SIGALRM, sigalrm_handler);
    return OTHER_CMD;
    }
  if (c == 0)
    {
    hadnull = TRUE;
    c = '?';
    }
  smtp_cmd_buffer[ptr++] = c;
  }

receive_linecount++;    /* For BSMTP errors */
os_non_restarting_signal(SIGALRM, sigalrm_handler);

/* If hit end of file, return pseudo EOF command. Whether we have a
part-line already read doesn't matter, since this is an error state. */

if (c == EOF) return EOF_CMD;

/* Remove any CR and white space at the end of the line, and terminate the
string. */

while (ptr > 0 && isspace(smtp_cmd_buffer[ptr-1])) ptr--;
smtp_cmd_buffer[ptr] = 0;

DEBUG(D_receive) debug_printf("SMTP<< %s\n", smtp_cmd_buffer);

/* NULLs are not allowed in SMTP commands */

if (hadnull) return BADCHAR_CMD;

/* Scan command list and return identity, having set the data pointer
to the start of the actual data characters. Check for SMTP synchronization
if required. */

for (p = cmd_list; p < cmd_list_end; p++)
  {
#ifdef SUPPORT_PROXY
  /* Only allow QUIT command if Proxy Protocol parsing failed */
  if (proxy_session && f.proxy_session_failed && p->cmd != QUIT_CMD)
    continue;
#endif
  if (  p->len
     && strncmpic(smtp_cmd_buffer, US p->name, p->len) == 0
     && (  smtp_cmd_buffer[p->len-1] == ':'    /* "mail from:" or "rcpt to:" */
        || smtp_cmd_buffer[p->len] == 0
	|| smtp_cmd_buffer[p->len] == ' '
     )  )
    {
    if (smtp_inptr < smtp_inend &&                     /* Outstanding input */
        p->cmd < sync_cmd_limit &&                     /* Command should sync */
        check_sync &&                                  /* Local flag set */
        smtp_enforce_sync &&                           /* Global flag set */
        sender_host_address != NULL &&                 /* Not local input */
        !f.sender_host_notsocket)                        /* Really is a socket */
      return BADSYN_CMD;

    /* The variables $smtp_command and $smtp_command_argument point into the
    unmodified input buffer. A copy of the latter is taken for actual
    processing, so that it can be chopped up into separate parts if necessary,
    for example, when processing a MAIL command options such as SIZE that can
    follow the sender address. */

    smtp_cmd_argument = smtp_cmd_buffer + p->len;
    while (isspace(*smtp_cmd_argument)) smtp_cmd_argument++;
    Ustrcpy(smtp_data_buffer, smtp_cmd_argument);
    smtp_cmd_data = smtp_data_buffer;

    /* Count non-mail commands from those hosts that are controlled in this
    way. The default is all hosts. We don't waste effort checking the list
    until we get a non-mail command, but then cache the result to save checking
    again. If there's a DEFER while checking the host, assume it's in the list.

    Note that one instance of RSET, EHLO/HELO, and STARTTLS is allowed at the
    start of each incoming message by fiddling with the value in the table. */

    if (!p->is_mail_cmd)
      {
      if (count_nonmail == TRUE_UNSET) count_nonmail =
        verify_check_host(&smtp_accept_max_nonmail_hosts) != FAIL;
      if (count_nonmail && ++nonmail_command_count > smtp_accept_max_nonmail)
        return TOO_MANY_NONMAIL_CMD;
      }

    /* If there is data for a command that does not expect it, generate the
    error here. */

    return (p->has_arg || *smtp_cmd_data == 0)? p->cmd : BADARG_CMD;
    }
  }

#ifdef SUPPORT_PROXY
/* Only allow QUIT command if Proxy Protocol parsing failed */
if (proxy_session && f.proxy_session_failed)
  return PROXY_FAIL_IGNORE_CMD;
#endif

/* Enforce synchronization for unknown commands */

if (  smtp_inptr < smtp_inend		/* Outstanding input */
   && check_sync			/* Local flag set */
   && smtp_enforce_sync			/* Global flag set */
   && sender_host_address		/* Not local input */
   && !f.sender_host_notsocket)		/* Really is a socket */
  return BADSYN_CMD;

return OTHER_CMD;
}

Project: exim
File: deliver.c

string_timediff(struct timeval * diff)
{
static uschar buf[sizeof("0.000s")];

if (diff->tv_sec >= 5 || !LOGGING(millisec))
  return readconf_printtime((int)diff->tv_sec);

sprintf(CS buf, "%u.%03us", (uint)diff->tv_sec, (uint)diff->tv_usec/1000);
return buf;
}

Project: exim

decode_control(const uschar *arg, const uschar **pptr, int where, uschar **log_msgptr)
{
int idx, len;
control_def * d;

if (  (idx = find_control(arg, controls_list, nelem(controls_list))) < 0
   || (  arg[len = Ustrlen((d = controls_list+idx)->name)] != 0
      && (!d->has_option || arg[len] != '/')
   )  )
  {
  *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
  return CONTROL_ERROR;
  }

*pptr = arg + len;
return idx;
}

Project: exim
File: deliver.c

string_get_localpart(address_item * addr, gstring * yield)
{
uschar * s;

s = addr->prefix;
if (testflag(addr, af_include_affixes) && s)
  {
#ifdef SUPPORT_I18N
  if (testflag(addr, af_utf8_downcvt))
    s = string_localpart_utf8_to_alabel(s, NULL);
#endif
  yield = string_cat(yield, s);
  }

s = addr->local_part;
#ifdef SUPPORT_I18N
if (testflag(addr, af_utf8_downcvt))
  s = string_localpart_utf8_to_alabel(s, NULL);
#endif
yield = string_cat(yield, s);

s = addr->suffix;
if (testflag(addr, af_include_affixes) && s)
  {
#ifdef SUPPORT_I18N
  if (testflag(addr, af_utf8_downcvt))
    s = string_localpart_utf8_to_alabel(s, NULL);
#endif
  yield = string_cat(yield, s);
  }

return yield;
}

Project: exim

smtp_feof(void)
{
return smtp_had_eof;
}

Project: exim

exim_setugid(uid_t uid, gid_t gid, BOOL igflag, uschar *msg)
{
uid_t euid = geteuid();
gid_t egid = getegid();

if (euid == root_uid || euid != uid || egid != gid || igflag)
  {
  /* At least one OS returns +1 for initgroups failure, so just check for
  non-zero. */

  if (igflag)
    {
    struct passwd *pw = getpwuid(uid);
    if (!pw)
      log_write(0, LOG_MAIN|LOG_PANIC_DIE, "cannot run initgroups(): "
	"no passwd entry for uid=%ld", (long int)uid);

    if (initgroups(pw->pw_name, gid) != 0)
      log_write(0,LOG_MAIN|LOG_PANIC_DIE,"initgroups failed for uid=%ld: %s",
	(long int)uid, strerror(errno));
    }

  if (setgid(gid) < 0 || setuid(uid) < 0)
    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "unable to set gid=%ld or uid=%ld "
      "(euid=%ld): %s", (long int)gid, (long int)uid, (long int)euid, msg);
  }

/* Debugging output included uid/gid and all groups */

DEBUG(D_uid)
  {
  int group_count, save_errno;
  gid_t group_list[EXIM_GROUPLIST_SIZE];
  debug_printf("changed uid/gid: %s\n  uid=%ld gid=%ld pid=%ld\n", msg,
    (long int)geteuid(), (long int)getegid(), (long int)getpid());
  group_count = getgroups(nelem(group_list), group_list);
  save_errno = errno;
  debug_printf("  auxiliary group list:");
  if (group_count > 0)
    {
    int i;
    for (i = 0; i < group_count; i++) debug_printf(" %d", (int)group_list[i]);
    }
  else if (group_count < 0)
    debug_printf(" <error: %s>", strerror(save_errno));
  else debug_printf(" <none>");
  debug_printf("\n");
  }
}

Project: exim

smtp_quit_handler(uschar ** user_msgp, uschar ** log_msgp)
{
HAD(SCH_QUIT);
incomplete_transaction_log(US"QUIT");
if (acl_smtp_quit)
  {
  int rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, user_msgp, log_msgp);
  if (rc == ERROR)
    log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
      *log_msgp);
  }
if (*user_msgp)
  smtp_respond(US"221", 3, TRUE, *user_msgp);
else
  smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);

#ifdef SUPPORT_TLS
tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
#endif

log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
  smtp_get_connection_info());
}

Project: exim
File: deliver.c

check_never_users(uid_t uid, uid_t *nusers)
{
int i;
if (!nusers) return FALSE;
for (i = 1; i <= (int)(nusers[0]); i++) if (nusers[i] == uid) return TRUE;
return FALSE;
}

Project: exim
File: deliver.c

open_msglog_file(uschar *filename, int mode, uschar **error)
{
int fd, i;

for (i = 2; i > 0; i--)
  {
  fd = Uopen(filename,
#ifdef O_CLOEXEC
    O_CLOEXEC |
#endif
#ifdef O_NOFOLLOW
    O_NOFOLLOW |
#endif
		O_WRONLY|O_APPEND|O_CREAT, mode);
  if (fd >= 0)
    {
    /* Set the close-on-exec flag and change the owner to the exim uid/gid (this
    function is called as root). Double check the mode, because the group setting
    doesn't always get set automatically. */

#ifndef O_CLOEXEC
    (void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
#endif
    if (fchown(fd, exim_uid, exim_gid) < 0)
      {
      *error = US"chown";
      return -1;
      }
    if (fchmod(fd, mode) < 0)
      {
      *error = US"chmod";
      return -1;
      }
    return fd;
    }
  if (errno != ENOENT)
    break;

  (void)directory_make(spool_directory,
			spool_sname(US"msglog", message_subdir),
			MSGLOG_DIRECTORY_MODE, TRUE);
  }

*error = US"create";
return -1;
}

Project: exim

acl_verify_csa_address(dns_answer *dnsa, dns_scan *dnss, int reset,
                       uschar *target)
{
dns_record *rr;
dns_address *da;

BOOL target_found = FALSE;

for (rr = dns_next_rr(dnsa, dnss, reset);
     rr != NULL;
     rr = dns_next_rr(dnsa, dnss, RESET_NEXT))
  {
  /* Check this is an address RR for the target hostname. */

  if (rr->type != T_A
    #if HAVE_IPV6
      && rr->type != T_AAAA
    #endif
  ) continue;

  if (strcmpic(target, rr->name) != 0) continue;

  target_found = TRUE;

  /* Turn the target address RR into a list of textual IP addresses and scan
  the list. There may be more than one if it is an A6 RR. */

  for (da = dns_address_from_rr(dnsa, rr); da != NULL; da = da->next)
    {
    /* If the client IP address matches the target IP address, it's good! */

    DEBUG(D_acl) debug_printf_indent("CSA target address is %s\n", da->address);

    if (strcmpic(sender_host_address, da->address) == 0) return CSA_OK;
    }
  }

/* If we found some target addresses but none of them matched, the client is
using an unauthorized IP address, otherwise the target has no authorized IP
addresses. */

if (target_found) return CSA_FAIL_MISMATCH;
else return CSA_FAIL_NOADDR;
}

Project: exim

smtp_get_port(uschar *rstring, address_item *addr, int *port, uschar *msg)
{
uschar *pstring = expand_string(rstring);

if (!pstring)
  {
  addr->transport_return = PANIC;
  addr->message = string_sprintf("failed to expand \"%s\" (\"port\" option) "
    "for %s: %s", rstring, msg, expand_string_message);
  return FALSE;
  }

if (isdigit(*pstring))
  {
  uschar *end;
  *port = Ustrtol(pstring, &end, 0);
  if (end != pstring + Ustrlen(pstring))
    {
    addr->transport_return = PANIC;
    addr->message = string_sprintf("invalid port number for %s: %s", msg,
      pstring);
    return FALSE;
    }
  }

else
  {
  struct servent *smtp_service = getservbyname(CS pstring, "tcp");
  if (!smtp_service)
    {
    addr->transport_return = PANIC;
    addr->message = string_sprintf("TCP port \"%s\" is not defined for %s",
      pstring, msg);
    return FALSE;
    }
  *port = ntohs(smtp_service->s_port);
  }

return TRUE;
}

Project: exim

smtp_data_sigint_exit(void)
{
log_write(0, LOG_MAIN, "%s closed after %s",
  smtp_get_connection_info(), had_data_sigint == SIGTERM ? "SIGTERM":"SIGINT");
receive_bomb_out(US"signal-exit",
  US"Service not available - SIGTERM or SIGINT received");
/* Does not return */
}

Project: exim
File: deliver.c

tpt_parallel_check(transport_instance * tp, address_item * addr, uschar ** key)
{
unsigned max_parallel;

if (!tp->max_parallel) return FALSE;

max_parallel = (unsigned) expand_string_integer(tp->max_parallel, TRUE);
if (expand_string_message)
  {
  log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand max_parallel option "
	"in %s transport (%s): %s", tp->name, addr->address,
	expand_string_message);
  return TRUE;
  }

if (max_parallel > 0)
  {
  uschar * serialize_key = string_sprintf("tpt-serialize-%s", tp->name);
  if (!enq_start(serialize_key, max_parallel))
    {
    address_item * next;
    DEBUG(D_transport)
      debug_printf("skipping tpt %s because concurrency limit %u reached\n",
		  tp->name, max_parallel);
    do
      {
      next = addr->next;
      addr->message = US"concurrency limit reached for transport";
      addr->basic_errno = ERRNO_TRETRY;
      post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_TRANSPORT, 0);
      } while ((addr = next));
    return TRUE;
    }
  *key = serialize_key;
  }
return FALSE;
}

Project: exim

transport_init(void)
{
transport_instance *t;

readconf_driver_init(US"transport",
  (driver_instance **)(&transports),     /* chain anchor */
  (driver_info *)transports_available,   /* available drivers */
  sizeof(transport_info),                /* size of info block */
  &transport_defaults,                   /* default values for generic options */
  sizeof(transport_instance),            /* size of instance block */
  optionlist_transports,                 /* generic options */
  optionlist_transports_size);

/* Now scan the configured transports and check inconsistencies. A shadow
transport is permitted only for local transports. */

for (t = transports; t; t = t->next)
  {
  if (!t->info->local && t->shadow)
    log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
      "shadow transport not allowed on non-local transport %s", t->name);

  if (t->body_only && t->headers_only)
    log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
      "%s transport: body_only and headers_only are mutually exclusive",
      t->name);
  }
}

Project: exim

smtp_ungetc(int ch)
{
*--smtp_inptr = ch;
return ch;
}

Project: exim

bdat_getc(unsigned lim)
{
uschar * user_msg = NULL;
uschar * log_msg;

for(;;)
  {
#ifndef DISABLE_DKIM
  unsigned dkim_save;
#endif

  if (chunking_data_left > 0)
    return lwr_receive_getc(chunking_data_left--);

  receive_getc = lwr_receive_getc;
  receive_getbuf = lwr_receive_getbuf;
  receive_ungetc = lwr_receive_ungetc;
#ifndef DISABLE_DKIM
  dkim_save = dkim_collect_input;
  dkim_collect_input = 0;
#endif

  /* Unless PIPELINING was offered, there should be no next command
  until after we ack that chunk */

  if (!f.smtp_in_pipelining_advertised && !check_sync())
    {
    unsigned n = smtp_inend - smtp_inptr;
    if (n > 32) n = 32;

    incomplete_transaction_log(US"sync failure");
    log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
      "(next input sent too soon: pipelining was not advertised): "
      "rejected \"%s\" %s next input=\"%s\"%s",
      smtp_cmd_buffer, host_and_ident(TRUE),
      string_printing(string_copyn(smtp_inptr, n)),
      smtp_inend - smtp_inptr > n ? "..." : "");
    (void) synprot_error(L_smtp_protocol_error, 554, NULL,
      US"SMTP synchronization error");
    goto repeat_until_rset;
    }

  /* If not the last, ack the received chunk.  The last response is delayed
  until after the data ACL decides on it */

  if (chunking_state == CHUNKING_LAST)
    {
#ifndef DISABLE_DKIM
    dkim_exim_verify_feed(NULL, 0);	/* notify EOD */
#endif
    return EOD;
    }

  smtp_printf("250 %u byte chunk received\r\n", FALSE, chunking_datasize);
  chunking_state = CHUNKING_OFFERED;
  DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);

  /* Expect another BDAT cmd from input. RFC 3030 says nothing about
  QUIT, RSET or NOOP but handling them seems obvious */

next_cmd:
  switch(smtp_read_command(TRUE, 1))
    {
    default:
      (void) synprot_error(L_smtp_protocol_error, 503, NULL,
	US"only BDAT permissible after non-LAST BDAT");

  repeat_until_rset:
      switch(smtp_read_command(TRUE, 1))
	{
	case QUIT_CMD:	smtp_quit_handler(&user_msg, &log_msg);	/*FALLTHROUGH */
	case EOF_CMD:	return EOF;
	case RSET_CMD:	smtp_rset_handler(); return ERR;
	default:	if (synprot_error(L_smtp_protocol_error, 503, NULL,
					  US"only RSET accepted now") > 0)
			  return EOF;
			goto repeat_until_rset;
	}

    case QUIT_CMD:
      smtp_quit_handler(&user_msg, &log_msg);
      /*FALLTHROUGH*/
    case EOF_CMD:
      return EOF;

    case RSET_CMD:
      smtp_rset_handler();
      return ERR;

    case NOOP_CMD:
      HAD(SCH_NOOP);
      smtp_printf("250 OK\r\n", FALSE);
      goto next_cmd;

    case BDAT_CMD:
      {
      int n;

      if (sscanf(CS smtp_cmd_data, "%u %n", &chunking_datasize, &n) < 1)
	{
	(void) synprot_error(L_smtp_protocol_error, 501, NULL,
	  US"missing size for BDAT command");
	return ERR;
	}
      chunking_state = strcmpic(smtp_cmd_data+n, US"LAST") == 0
	? CHUNKING_LAST : CHUNKING_ACTIVE;
      chunking_data_left = chunking_datasize;
      DEBUG(D_receive) debug_printf("chunking state %d, %d bytes\n",
				    (int)chunking_state, chunking_data_left);

      if (chunking_datasize == 0)
	if (chunking_state == CHUNKING_LAST)
	  return EOD;
	else
	  {
	  (void) synprot_error(L_smtp_protocol_error, 504, NULL,
	    US"zero size for BDAT command");
	  goto repeat_until_rset;
	  }

      receive_getc = bdat_getc;
      receive_getbuf = bdat_getbuf;	/* r~getbuf is never actually used */
      receive_ungetc = bdat_ungetc;
#ifndef DISABLE_DKIM
      dkim_collect_input = dkim_save;
#endif
      break;	/* to top of main loop */
      }
    }
  }
}

Project: exim

host_name_lookup(void)
{
int old_pool, rc;
int sep = 0;
uschar *hname, *save_hostname;
uschar **aliases;
uschar buffer[256];
uschar *ordername;
const uschar *list = host_lookup_order;
dns_record *rr;
dns_answer dnsa;
dns_scan dnss;

sender_host_dnssec = host_lookup_deferred = host_lookup_failed = FALSE;

HDEBUG(D_host_lookup)
  debug_printf("looking up host name for %s\n", sender_host_address);

/* For testing the case when a lookup does not complete, we have a special
reserved IP address. */

if (f.running_in_test_harness &&
    Ustrcmp(sender_host_address, "99.99.99.99") == 0)
  {
  HDEBUG(D_host_lookup)
    debug_printf("Test harness: host name lookup returns DEFER\n");
  host_lookup_deferred = TRUE;
  return DEFER;
  }

/* Do lookups directly in the DNS or via gethostbyaddr() (or equivalent), in
the order specified by the host_lookup_order option. */

while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
  {
  if (strcmpic(ordername, US"bydns") == 0)
    {
    dns_init(FALSE, FALSE, FALSE);    /* dnssec ctrl by dns_dnssec_ok glbl */
    dns_build_reverse(sender_host_address, buffer);
    rc = dns_lookup_timerwrap(&dnsa, buffer, T_PTR, NULL);

    /* The first record we come across is used for the name; others are
    considered to be aliases. We have to scan twice, in order to find out the
    number of aliases. However, if all the names are empty, we will behave as
    if failure. (PTR records that yield empty names have been encountered in
    the DNS.) */

    if (rc == DNS_SUCCEED)
      {
      uschar **aptr = NULL;
      int ssize = 264;
      int count = 0;
      int old_pool = store_pool;

      sender_host_dnssec = dns_is_secure(&dnsa);
      DEBUG(D_dns)
        debug_printf("Reverse DNS security status: %s\n",
            sender_host_dnssec ? "DNSSEC verified (AD)" : "unverified");

      store_pool = POOL_PERM;        /* Save names in permanent storage */

      for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
           rr;
           rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
        if (rr->type == T_PTR)
	  count++;

      /* Get store for the list of aliases. For compatibility with
      gethostbyaddr, we make an empty list if there are none. */

      aptr = sender_host_aliases = store_get(count * sizeof(uschar *));

      /* Re-scan and extract the names */

      for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
           rr;
           rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
        {
        uschar *s = NULL;
        if (rr->type != T_PTR) continue;
        s = store_get(ssize);

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

        if (dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen,
             US (rr->data), (DN_EXPAND_ARG4_TYPE)(s), ssize) < 0)
          {
          log_write(0, LOG_MAIN, "host name alias list truncated for %s",
            sender_host_address);
          break;
          }

        store_reset(s + Ustrlen(s) + 1);
        if (s[0] == 0)
          {
          HDEBUG(D_host_lookup) debug_printf("IP address lookup yielded an "
            "empty name: treated as non-existent host name\n");
          continue;
          }
        if (!sender_host_name) sender_host_name = s;
	else *aptr++ = s;
        while (*s != 0) { *s = tolower(*s); s++; }
        }

      *aptr = NULL;            /* End of alias list */
      store_pool = old_pool;   /* Reset store pool */

      /* If we've found a names, break out of the "order" loop */

      if (sender_host_name != NULL) break;
      }

    /* If the DNS lookup deferred, we must also defer. */

    if (rc == DNS_AGAIN)
      {
      HDEBUG(D_host_lookup)
        debug_printf("IP address PTR lookup gave temporary error\n");
      host_lookup_deferred = TRUE;
      return DEFER;
      }
    }

  /* Do a lookup using gethostbyaddr() - or equivalent */

  else if (strcmpic(ordername, US"byaddr") == 0)
    {
    HDEBUG(D_host_lookup)
      debug_printf("IP address lookup using gethostbyaddr()\n");
    rc = host_name_lookup_byaddr();
    if (rc == DEFER)
      {
      host_lookup_deferred = TRUE;
      return rc;                       /* Can't carry on */
      }
    if (rc == OK) break;               /* Found a name */
    }
  }      /* Loop for bydns/byaddr scanning */

/* If we have failed to find a name, return FAIL and log when required.
NB host_lookup_msg must be in permanent store.  */

if (!sender_host_name)
  {
  if (host_checking || !f.log_testing_mode)
    log_write(L_host_lookup_failed, LOG_MAIN, "no host name found for IP "
      "address %s", sender_host_address);
  host_lookup_msg = US" (failed to find host name from IP address)";
  host_lookup_failed = TRUE;
  return FAIL;
  }

HDEBUG(D_host_lookup)
  {
  uschar **aliases = sender_host_aliases;
  debug_printf("IP address lookup yielded \"%s\"\n", sender_host_name);
  while (*aliases != NULL) debug_printf("  alias \"%s\"\n", *aliases++);
  }

/* We need to verify that a forward lookup on the name we found does indeed
correspond to the address. This is for security: in principle a malefactor who
happened to own a reverse zone could set it to point to any names at all.

This code was present in versions of Exim before 3.20. At that point I took it
out because I thought that gethostbyaddr() did the check anyway. It turns out
that this isn't always the case, so it's coming back in at 4.01. This version
is actually better, because it also checks aliases.

The code was made more robust at release 4.21. Prior to that, it accepted all
the names if any of them had the correct IP address. Now the code checks all
the names, and accepts only those that have the correct IP address. */

save_hostname = sender_host_name;   /* Save for error messages */
aliases = sender_host_aliases;
for (hname = sender_host_name; hname; hname = *aliases++)
  {
  int rc;
  BOOL ok = FALSE;
  host_item h = { .next = NULL, .name = hname, .mx = MX_NONE, .address = NULL };
  dnssec_domains d =
    { .request = sender_host_dnssec ? US"*" : NULL, .require = NULL };

  if (  (rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A | HOST_FIND_BY_AAAA,
	  NULL, NULL, NULL, &d, NULL, NULL)) == HOST_FOUND
     || rc == HOST_FOUND_LOCAL
     )
    {
    host_item *hh;
    HDEBUG(D_host_lookup) debug_printf("checking addresses for %s\n", hname);

    /* If the forward lookup was not secure we cancel the is-secure variable */

    DEBUG(D_dns) debug_printf("Forward DNS security status: %s\n",
	  h.dnssec == DS_YES ? "DNSSEC verified (AD)" : "unverified");
    if (h.dnssec != DS_YES) sender_host_dnssec = FALSE;

    for (hh = &h; hh; hh = hh->next)
      if (host_is_in_net(hh->address, sender_host_address, 0))
        {
        HDEBUG(D_host_lookup) debug_printf("  %s OK\n", hh->address);
        ok = TRUE;
        break;
        }
      else
        HDEBUG(D_host_lookup) debug_printf("  %s\n", hh->address);

    if (!ok) HDEBUG(D_host_lookup)
      debug_printf("no IP address for %s matched %s\n", hname,
        sender_host_address);
    }
  else if (rc == HOST_FIND_AGAIN)
    {
    HDEBUG(D_host_lookup) debug_printf("temporary error for host name lookup\n");
    host_lookup_deferred = TRUE;
    sender_host_name = NULL;
    return DEFER;
    }
  else
    HDEBUG(D_host_lookup) debug_printf("no IP addresses found for %s\n", hname);

  /* If this name is no good, and it's the sender name, set it null pro tem;
  if it's an alias, just remove it from the list. */

  if (!ok)
    {
    if (hname == sender_host_name) sender_host_name = NULL; else
      {
      uschar **a;                              /* Don't amalgamate - some */
      a = --aliases;                           /* compilers grumble */
      while (*a != NULL) { *a = a[1]; a++; }
      }
    }
  }

/* If sender_host_name == NULL, it means we didn't like the name. Replace
it with the first alias, if there is one. */

if (sender_host_name == NULL && *sender_host_aliases != NULL)
  sender_host_name = *sender_host_aliases++;

/* If we now have a main name, all is well. */

if (sender_host_name != NULL) return OK;

/* We have failed to find an address that matches. */

HDEBUG(D_host_lookup)
  debug_printf("%s does not match any IP address for %s\n",
    sender_host_address, save_hostname);

/* This message must be in permanent store */

old_pool = store_pool;
store_pool = POOL_PERM;
host_lookup_msg = string_sprintf(" (%s does not match any IP address for %s)",
  sender_host_address, save_hostname);
store_pool = old_pool;
host_lookup_failed = TRUE;
return FAIL;
}

Project: exim

setup_remove_header(const uschar *hnames)
{
if (*hnames)
  acl_removed_headers = acl_removed_headers
    ? string_sprintf("%s : %s", acl_removed_headers, hnames)
    : string_copy(hnames);
}

Project: exim

acl_check_internal(int where, address_item *addr, uschar *s,
  uschar **user_msgptr, uschar **log_msgptr)
{
int fd = -1;
acl_block *acl = NULL;
uschar *acl_name = US"inline ACL";
uschar *ss;

/* Catch configuration loops */

if (acl_level > 20)
  {
  *log_msgptr = US"ACL nested too deep: possible loop";
  return ERROR;
  }

if (!s)
  {
  HDEBUG(D_acl) debug_printf_indent("ACL is NULL: implicit DENY\n");
  return FAIL;
  }

/* At top level, we expand the incoming string. At lower levels, it has already
been expanded as part of condition processing. */

if (acl_level == 0)
  {
  if (!(ss = expand_string(s)))
    {
    if (f.expand_string_forcedfail) return OK;
    *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s", s,
      expand_string_message);
    return ERROR;
    }
  }
else ss = s;

while (isspace(*ss)) ss++;

/* If we can't find a named ACL, the default is to parse it as an inline one.
(Unless it begins with a slash; non-existent files give rise to an error.) */

acl_text = ss;

/* Handle the case of a string that does not contain any spaces. Look for a
named ACL among those read from the configuration, or a previously read file.
It is possible that the pointer to the ACL is NULL if the configuration
contains a name with no data. If not found, and the text begins with '/',
read an ACL from a file, and save it so it can be re-used. */

if (Ustrchr(ss, ' ') == NULL)
  {
  tree_node *t = tree_search(acl_anchor, ss);
  if (t != NULL)
    {
    acl = (acl_block *)(t->data.ptr);
    if (acl == NULL)
      {
      HDEBUG(D_acl) debug_printf_indent("ACL \"%s\" is empty: implicit DENY\n", ss);
      return FAIL;
      }
    acl_name = string_sprintf("ACL \"%s\"", ss);
    HDEBUG(D_acl) debug_printf_indent("using ACL \"%s\"\n", ss);
    }

  else if (*ss == '/')
    {
    struct stat statbuf;
    fd = Uopen(ss, O_RDONLY, 0);
    if (fd < 0)
      {
      *log_msgptr = string_sprintf("failed to open ACL file \"%s\": %s", ss,
        strerror(errno));
      return ERROR;
      }

    if (fstat(fd, &statbuf) != 0)
      {
      *log_msgptr = string_sprintf("failed to fstat ACL file \"%s\": %s", ss,
        strerror(errno));
      return ERROR;
      }

    acl_text = store_get(statbuf.st_size + 1);
    acl_text_end = acl_text + statbuf.st_size + 1;

    if (read(fd, acl_text, statbuf.st_size) != statbuf.st_size)
      {
      *log_msgptr = string_sprintf("failed to read ACL file \"%s\": %s",
        ss, strerror(errno));
      return ERROR;
      }
    acl_text[statbuf.st_size] = 0;
    (void)close(fd);

    acl_name = string_sprintf("ACL \"%s\"", ss);
    HDEBUG(D_acl) debug_printf_indent("read ACL from file %s\n", ss);
    }
  }

/* Parse an ACL that is still in text form. If it came from a file, remember it
in the ACL tree, having read it into the POOL_PERM store pool so that it
persists between multiple messages. */

if (acl == NULL)
  {
  int old_pool = store_pool;
  if (fd >= 0) store_pool = POOL_PERM;
  acl = acl_read(acl_getline, log_msgptr);
  store_pool = old_pool;
  if (acl == NULL && *log_msgptr != NULL) return ERROR;
  if (fd >= 0)
    {
    tree_node *t = store_get_perm(sizeof(tree_node) + Ustrlen(ss));
    Ustrcpy(t->name, ss);
    t->data.ptr = acl;
    (void)tree_insertnode(&acl_anchor, t);
    }
  }

/* Now we have an ACL to use. It's possible it may be NULL. */

while (acl != NULL)
  {
  int cond;
  int basic_errno = 0;
  BOOL endpass_seen = FALSE;
  BOOL acl_quit_check = acl_level == 0
    && (where == ACL_WHERE_QUIT || where == ACL_WHERE_NOTQUIT);

  *log_msgptr = *user_msgptr = NULL;
  f.acl_temp_details = FALSE;

  HDEBUG(D_acl) debug_printf_indent("processing \"%s\"\n", verbs[acl->verb]);

  /* Clear out any search error message from a previous check before testing
  this condition. */

  search_error_message = NULL;
  cond = acl_check_condition(acl->verb, acl->condition, where, addr, acl_level,
    &endpass_seen, user_msgptr, log_msgptr, &basic_errno);

  /* Handle special returns: DEFER causes a return except on a WARN verb;
  ERROR always causes a return. */

  switch (cond)
    {
    case DEFER:
    HDEBUG(D_acl) debug_printf_indent("%s: condition test deferred in %s\n", verbs[acl->verb], acl_name);
    if (basic_errno != ERRNO_CALLOUTDEFER)
      {
      if (search_error_message != NULL && *search_error_message != 0)
        *log_msgptr = search_error_message;
      if (smtp_return_error_details) f.acl_temp_details = TRUE;
      }
    else
      f.acl_temp_details = TRUE;
    if (acl->verb != ACL_WARN) return DEFER;
    break;

    default:      /* Paranoia */
    case ERROR:
    HDEBUG(D_acl) debug_printf_indent("%s: condition test error in %s\n", verbs[acl->verb], acl_name);
    return ERROR;

    case OK:
    HDEBUG(D_acl) debug_printf_indent("%s: condition test succeeded in %s\n",
      verbs[acl->verb], acl_name);
    break;

    case FAIL:
    HDEBUG(D_acl) debug_printf_indent("%s: condition test failed in %s\n", verbs[acl->verb], acl_name);
    break;

    /* DISCARD and DROP can happen only from a nested ACL condition, and
    DISCARD can happen only for an "accept" or "discard" verb. */

    case DISCARD:
    HDEBUG(D_acl) debug_printf_indent("%s: condition test yielded \"discard\" in %s\n",
      verbs[acl->verb], acl_name);
    break;

    case FAIL_DROP:
    HDEBUG(D_acl) debug_printf_indent("%s: condition test yielded \"drop\" in %s\n",
      verbs[acl->verb], acl_name);
    break;
    }

  /* At this point, cond for most verbs is either OK or FAIL or (as a result of
  a nested ACL condition) FAIL_DROP. However, for WARN, cond may be DEFER, and
  for ACCEPT and DISCARD, it may be DISCARD after a nested ACL call. */

  switch(acl->verb)
    {
    case ACL_ACCEPT:
    if (cond == OK || cond == DISCARD)
      {
      HDEBUG(D_acl) debug_printf_indent("end of %s: ACCEPT\n", acl_name);
      return cond;
      }
    if (endpass_seen)
      {
      HDEBUG(D_acl) debug_printf_indent("accept: endpass encountered - denying access\n");
      return cond;
      }
    break;

    case ACL_DEFER:
    if (cond == OK)
      {
      HDEBUG(D_acl) debug_printf_indent("end of %s: DEFER\n", acl_name);
      if (acl_quit_check) goto badquit;
      f.acl_temp_details = TRUE;
      return DEFER;
      }
    break;

    case ACL_DENY:
    if (cond == OK)
      {
      HDEBUG(D_acl) debug_printf_indent("end of %s: DENY\n", acl_name);
      if (acl_quit_check) goto badquit;
      return FAIL;
      }
    break;

    case ACL_DISCARD:
    if (cond == OK || cond == DISCARD)
      {
      HDEBUG(D_acl) debug_printf_indent("end of %s: DISCARD\n", acl_name);
      if (acl_quit_check) goto badquit;
      return DISCARD;
      }
    if (endpass_seen)
      {
      HDEBUG(D_acl) debug_printf_indent("discard: endpass encountered - denying access\n");
      return cond;
      }
    break;

    case ACL_DROP:
    if (cond == OK)
      {
      HDEBUG(D_acl) debug_printf_indent("end of %s: DROP\n", acl_name);
      if (acl_quit_check) goto badquit;
      return FAIL_DROP;
      }
    break;

    case ACL_REQUIRE:
    if (cond != OK)
      {
      HDEBUG(D_acl) debug_printf_indent("end of %s: not OK\n", acl_name);
      if (acl_quit_check) goto badquit;
      return cond;
      }
    break;

    case ACL_WARN:
    if (cond == OK)
      acl_warn(where, *user_msgptr, *log_msgptr);
    else if (cond == DEFER && LOGGING(acl_warn_skipped))
      log_write(0, LOG_MAIN, "%s Warning: ACL \"warn\" statement skipped: "
        "condition test deferred%s%s", host_and_ident(TRUE),
        (*log_msgptr == NULL)? US"" : US": ",
        (*log_msgptr == NULL)? US"" : *log_msgptr);
    *log_msgptr = *user_msgptr = NULL;  /* In case implicit DENY follows */
    break;

    default:
    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "internal ACL error: unknown verb %d",
      acl->verb);
    break;
    }

  /* Pass to the next ACL item */

  acl = acl->next;
  }

/* We have reached the end of the ACL. This is an implicit DENY. */

HDEBUG(D_acl) debug_printf_indent("end of %s: implicit DENY\n", acl_name);
return FAIL;

badquit:
  *log_msgptr = string_sprintf("QUIT or not-QUIT toplevel ACL may not fail "
    "('%s' verb used incorrectly)", verbs[acl->verb]);
  return ERROR;
}

Project: exim

smtp_log_no_mail(void)
{
int i;
uschar * sep, * s;
gstring * g = NULL;

if (smtp_mailcmd_count > 0 || !LOGGING(smtp_no_mail))
  return;

if (sender_host_authenticated)
  {
  g = string_append(g, 2, US" A=", sender_host_authenticated);
  if (authenticated_id) g = string_append(g, 2, US":", authenticated_id);
  }

#ifdef SUPPORT_TLS
g = s_tlslog(g);
#endif

sep = smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE ?  US" C=..." : US" C=";

for (i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++)
  if (smtp_connection_had[i] != SCH_NONE)
    {
    g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]);
    sep = US",";
    }

for (i = 0; i < smtp_ch_index; i++)
  {
  g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]);
  sep = US",";
  }

if (!(s = string_from_gstring(g))) s = US"";

log_write(0, LOG_MAIN, "no MAIL in %sSMTP connection from %s D=%s%s",
  f.tcp_in_fastopen ? f.tcp_in_fastopen_data ? US"TFO* " : US"TFO " : US"",
  host_and_ident(FALSE), string_timesince(&smtp_connection_start), s);
}

Project: exim

transport_headers_send(transport_ctx * tctx,
  BOOL (*sendfn)(transport_ctx * tctx, uschar * s, int len))
{
header_line *h;
const uschar *list;
transport_instance * tblock = tctx ? tctx->tblock : NULL;
address_item * addr = tctx ? tctx->addr : NULL;

/* 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.  It is a colon-sep list; expand the items
separately and squash any empty ones.
Then check addr->prop.remove_headers too, provided that addr is not NULL. */

for (h = header_list; h; h = h->next) if (h->type != htype_old)
  {
  int i;
  BOOL include_header = TRUE;

  list = tblock ? tblock->remove_headers : NULL;
  for (i = 0; i < 2; i++)    /* For remove_headers && addr->prop.remove_headers */
    {
    if (list)
      {
      int sep = ':';         /* This is specified as a colon-separated list */
      uschar *s, *ss;
      while ((s = string_nextinlist(&list, &sep, NULL, 0)))
	{
	int len;

	if (i == 0)
	  if (!(s = expand_string(s)) && !f.expand_string_forcedfail)
	    {
	    errno = ERRNO_CHHEADER_FAIL;
	    return FALSE;
	    }
	len = s ? Ustrlen(s) : 0;
	if (strncmpic(h->text, s, len) != 0) continue;
	ss = h->text + len;
	while (*ss == ' ' || *ss == '\t') ss++;
	if (*ss == ':') break;
	}
      if (s) { include_header = FALSE; break; }
      }
    if (addr) list = addr->prop.remove_headers;
    }

  /* If this header is to be output, try to rewrite it if there are rewriting
  rules. */

  if (include_header)
    {
    if (tblock && tblock->rewrite_rules)
      {
      void *reset_point = store_get(0);
      header_line *hh;

      if ((hh = rewrite_header(h, NULL, NULL, tblock->rewrite_rules,
		  tblock->rewrite_existflags, FALSE)))
	{
	if (!sendfn(tctx, hh->text, hh->slen)) return FALSE;
	store_reset(reset_point);
	continue;     /* With the next header line */
	}
      }

    /* Either no rewriting rules, or it didn't get rewritten */

    if (!sendfn(tctx, h->text, h->slen)) return FALSE;
    }

  /* Header removed */

  else
    DEBUG(D_transport) debug_printf("removed header line:\n%s---\n", h->text);
  }

/* Add on any address-specific headers. If there are multiple addresses,
they will all have the same headers in order to be batched. The headers
are chained in reverse order of adding (so several addresses from the
same alias might share some of them) but we want to output them in the
opposite order. This is a bit tedious, but there shouldn't be very many
of them. We just walk the list twice, reversing the pointers each time,
but on the second time, write out the items.

Headers added to an address by a router are guaranteed to end with a newline.
*/

if (addr)
  {
  int i;
  header_line *hprev = addr->prop.extra_headers;
  header_line *hnext;
  for (i = 0; i < 2; i++)
    for (h = hprev, hprev = NULL; h; h = hnext)
      {
      hnext = h->next;
      h->next = hprev;
      hprev = h;
      if (i == 1)
	{
	if (!sendfn(tctx, h->text, h->slen)) return FALSE;
	DEBUG(D_transport)
	  debug_printf("added header line(s):\n%s---\n", h->text);
	}
      }
  }

/* If a string containing additional headers exists it is a newline-sep
list.  Expand each item and write out the result.  This is done last so that
if it (deliberately or accidentally) isn't in header format, it won't mess
up any other headers. An empty string or a forced expansion failure are
noops. An added header string from a transport may not end with a newline;
add one if it does not. */

if (tblock && (list = CUS tblock->add_headers))
  {
  int sep = '\n';
  uschar * s;

  while ((s = string_nextinlist(&list, &sep, NULL, 0)))
    if ((s = expand_string(s)))
      {
      int len = Ustrlen(s);
      if (len > 0)
	{
	if (!sendfn(tctx, s, len)) return FALSE;
	if (s[len-1] != '\n' && !sendfn(tctx, US"\n", 1))
	  return FALSE;
	DEBUG(D_transport)
	  {
	  debug_printf("added header line:\n%s", s);
	  if (s[len-1] != '\n') debug_printf("\n");
	  debug_printf("---\n");
	  }
	}
      }
    else if (!f.expand_string_forcedfail)
      { errno = ERRNO_CHHEADER_FAIL; return FALSE; }
  }

/* Separate headers from body with a blank line */

return sendfn(tctx, US"\n", 1);
}

Project: exim
File: deliver.c

same_ugid(transport_instance *tp, address_item *addr1, address_item *addr2)
{
if (  !tp->uid_set && !tp->expand_uid
   && !tp->deliver_as_creator
   && (  testflag(addr1, af_uid_set) != testflag(addr2, af_gid_set)
      || (  testflag(addr1, af_uid_set)
         && (  addr1->uid != addr2->uid
	    || testflag(addr1, af_initgroups) != testflag(addr2, af_initgroups)
   )  )  )  )
  return FALSE;

if (  !tp->gid_set && !tp->expand_gid
   && (  testflag(addr1, af_gid_set) != testflag(addr2, af_gid_set)
      || (  testflag(addr1, af_gid_set)
         && addr1->gid != addr2->gid
   )  )  )
  return FALSE;

return TRUE;
}

Project: exim

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

Project: exim
File: deliver.c

print_address_error(address_item *addr, FILE *f, uschar *t)
{
int count = Ustrlen(t);
uschar *s = testflag(addr, af_pass_message)? addr->message : NULL;

if (!s && !(s = addr->user_message))
  return;

fprintf(f, "\n    %s", t);

while (*s)
  if (*s == '\\' && s[1] == 'n')
    {
    fprintf(f, "\n    ");
    s += 2;
    count = 0;
    }
  else
    {
    fputc(*s, f);
    count++;
    if (*s++ == ':' && isspace(*s) && count > 45)
      {
      fprintf(f, "\n   ");  /* sic (because space follows) */
      count = 0;
      }
    }
}

Project: exim

smtp_command_sigterm_exit(void)
{
log_write(0, LOG_MAIN, "%s closed after SIGTERM", smtp_get_connection_info());
if (smtp_batched_input)
  moan_smtp_batch(NULL, "421 SIGTERM received");  /* Does not return */
smtp_notquit_exit(US"signal-exit", US"421",
  US"%s: Service not available - closing connection", smtp_active_hostname);
exim_exit(EXIT_FAILURE, US"receiving");
}

Project: exim

milliwait(struct itimerval *itval)
{
sigset_t sigmask;
sigset_t old_sigmask;

if (itval->it_value.tv_usec < 100 && itval->it_value.tv_sec == 0)
  return;
(void)sigemptyset(&sigmask);                           /* Empty mask */
(void)sigaddset(&sigmask, SIGALRM);                    /* Add SIGALRM */
(void)sigprocmask(SIG_BLOCK, &sigmask, &old_sigmask);  /* Block SIGALRM */
if (setitimer(ITIMER_REAL, itval, NULL) < 0)           /* Start timer */
  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
    "setitimer() failed: %s", strerror(errno));
(void)sigfillset(&sigmask);                            /* All signals */
(void)sigdelset(&sigmask, SIGALRM);                    /* Remove SIGALRM */
(void)sigsuspend(&sigmask);                            /* Until SIGALRM */
(void)sigprocmask(SIG_SETMASK, &old_sigmask, NULL);    /* Restore mask */
}

Project: exim

smtp_get_cache(void)
{
#ifndef DISABLE_DKIM
int n = smtp_inend - smtp_inptr;
if (n > 0)
  dkim_exim_verify_feed(smtp_inptr, n);
#endif
}

Project: exim

pipeline_connect_sends(void)
{
if (!sender_host_address || f.sender_host_notsocket || !fl.pipe_connect_acceptable)
  return FALSE;

if (wouldblock_reading()) return FALSE;
f.smtp_in_early_pipe_used = TRUE;
return TRUE;
}

Project: exim

host_ntoa(int type, const void *arg, char *buffer, int *portptr)
{
type = type;      /* Keep picky compilers happy */
arg = arg;
buffer = buffer;
portptr = portptr;
return NULL;
}

Project: exim

bits_set(unsigned int *selector, size_t selsize, int *bits)
{
for(; *bits != -1; ++bits)
  BIT_SET(selector, selsize, *bits);
}

Project: exim
File: deliver.c

delivery_re_exec(int exec_type)
{
uschar * where;

if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
  {
  int channel_fd = cutthrough.cctx.sock;

  smtp_peer_options = cutthrough.peer_options;
  continue_sequence = 0;

#ifdef SUPPORT_TLS
  if (cutthrough.is_tls)
    {
    int pfd[2], pid;

    smtp_peer_options |= OPTION_TLS;
    sending_ip_address = cutthrough.snd_ip;
    sending_port = cutthrough.snd_port;

    where = US"socketpair";
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) != 0)
      goto fail;

    where = US"fork";
    if ((pid = fork()) < 0)
      goto fail;

    else if (pid == 0)		/* child: fork again to totally disconnect */
      {
      if (f.running_in_test_harness) millisleep(100); /* let parent debug out */
      /* does not return */
      smtp_proxy_tls(cutthrough.cctx.tls_ctx, big_buffer, big_buffer_size,
		      pfd, 5*60);
      }

    DEBUG(D_transport) debug_printf("proxy-proc inter-pid %d\n", pid);
    close(pfd[0]);
    waitpid(pid, NULL, 0);
    (void) close(channel_fd);	/* release the client socket */
    channel_fd = pfd[1];
    }
#endif

  transport_do_pass_socket(cutthrough.transport, cutthrough.host.name,
    cutthrough.host.address, message_id, channel_fd);
  }
else
  {
  cancel_cutthrough_connection(TRUE, US"non-continued delivery");
  (void) child_exec_exim(exec_type, FALSE, NULL, FALSE, 2, US"-Mc", message_id);
  }
return;		/* compiler quietening; control does not reach here. */

#ifdef SUPPORT_TLS
fail:
  log_write(0,
    LOG_MAIN | (exec_type == CEE_EXEC_EXIT ? LOG_PANIC : LOG_PANIC_DIE),
    "delivery re-exec %s failed: %s", where, strerror(errno));

  /* Get here if exec_type == CEE_EXEC_EXIT.
  Note: this must be _exit(), not exit(). */

  _exit(EX_EXECFAILED);
#endif
}

Project: exim

write_chunk(transport_ctx * tctx, uschar *chunk, int len)
{
uschar *start = chunk;
uschar *end = chunk + len;
uschar *ptr;
int mlen = DELIVER_OUT_BUFFER_SIZE - nl_escape_length - 2;

/* The assumption is made that the check string will never stretch over move
than one chunk since the only time there are partial matches is when copying
the body in large buffers. There is always enough room in the buffer for an
escape string, since the loop below ensures this for each character it
processes, and it won't have stuck in the escape string if it left a partial
match. */

if (nl_partial_match >= 0)
  {
  if (nl_check_length > 0 && len >= nl_check_length &&
      Ustrncmp(start, nl_check + nl_partial_match,
        nl_check_length - nl_partial_match) == 0)
    {
    Ustrncpy(chunk_ptr, nl_escape, nl_escape_length);
    chunk_ptr += nl_escape_length;
    start += nl_check_length - nl_partial_match;
    }

  /* The partial match was a false one. Insert the characters carried over
  from the previous chunk. */

  else if (nl_partial_match > 0)
    {
    Ustrncpy(chunk_ptr, nl_check, nl_partial_match);
    chunk_ptr += nl_partial_match;
    }

  nl_partial_match = -1;
  }

/* Now process the characters in the chunk. Whenever we hit a newline we check
for possible escaping. The code for the non-NL route should be as fast as
possible. */

for (ptr = start; ptr < end; ptr++)
  {
  int ch, len;

  /* Flush the buffer if it has reached the threshold - we want to leave enough
  room for the next uschar, plus a possible extra CR for an LF, plus the escape
  string. */

  if ((len = chunk_ptr - deliver_out_buffer) > mlen)
    {
    DEBUG(D_transport) debug_printf("flushing headers buffer\n");

    /* If CHUNKING, prefix with BDAT (size) NON-LAST.  Also, reap responses
    from previous SMTP commands. */

    if (tctx->options & topt_use_bdat  &&  tctx->chunk_cb)
      {
      if (  tctx->chunk_cb(tctx, (unsigned)len, 0) != OK
	 || !transport_write_block(tctx, deliver_out_buffer, len, FALSE)
	 || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
	 )
	return FALSE;
      }
    else
      if (!transport_write_block(tctx, deliver_out_buffer, len, FALSE))
	return FALSE;
    chunk_ptr = deliver_out_buffer;
    }

  /* Remove CR before NL if required */

  if (  *ptr == '\r' && ptr[1] == '\n'
     && !(tctx->options & topt_use_crlf)
     && f.spool_file_wireformat
     )
    ptr++;

  if ((ch = *ptr) == '\n')
    {
    int left = end - ptr - 1;  /* count of chars left after NL */

    /* Insert CR before NL if required */

    if (tctx->options & topt_use_crlf && !f.spool_file_wireformat)
      *chunk_ptr++ = '\r';
    *chunk_ptr++ = '\n';
    transport_newlines++;

    /* The check_string test (formerly "from hack") replaces the specific
    string at the start of a line with an escape string (e.g. "From " becomes
    ">From " or "." becomes "..". It is a case-sensitive test. The length
    check above ensures there is always enough room to insert this string. */

    if (nl_check_length > 0)
      {
      if (left >= nl_check_length &&
          Ustrncmp(ptr+1, nl_check, nl_check_length) == 0)
        {
        Ustrncpy(chunk_ptr, nl_escape, nl_escape_length);
        chunk_ptr += nl_escape_length;
        ptr += nl_check_length;
        }

      /* Handle the case when there isn't enough left to match the whole
      check string, but there may be a partial match. We remember how many
      characters matched, and finish processing this chunk. */

      else if (left <= 0) nl_partial_match = 0;

      else if (Ustrncmp(ptr+1, nl_check, left) == 0)
        {
        nl_partial_match = left;
        ptr = end;
        }
      }
    }

  /* Not a NL character */

  else *chunk_ptr++ = ch;
  }

return TRUE;
}

Project: exim

lmtp_transport_init(transport_instance *tblock)
{
lmtp_transport_options_block *ob =
  (lmtp_transport_options_block *)(tblock->options_block);

/* Either the command field or the socket field must be set */

if ((ob->cmd == NULL) == (ob->skt == NULL))
  log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
    "one (and only one) of command or socket must be set for the %s transport",
    tblock->name);

/* If a fixed uid field is set, then a gid field must also be set. */

if (tblock->uid_set && !tblock->gid_set && tblock->expand_gid == NULL)
  log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
    "user set without group for the %s transport", tblock->name);

/* Set up the bitwise options for transport_write_message from the various
driver options. Only one of body_only and headers_only can be set. */

ob->options |=
  (tblock->body_only? topt_no_headers : 0) |
  (tblock->headers_only? topt_no_body : 0) |
  (tblock->return_path_add? topt_add_return_path : 0) |
  (tblock->delivery_date_add? topt_add_delivery_date : 0) |
  (tblock->envelope_to_add? topt_add_envelope_to : 0) |
  topt_use_crlf | topt_end_dot;
}

Project: exim

auth_heimdal_gssapi_init(auth_instance *ablock)
{
  krb5_context context;
  krb5_keytab keytab;
  krb5_kt_cursor cursor;
  krb5_keytab_entry entry;
  krb5_error_code krc;
  char *principal, *enctype_s;
  const char *k_keytab_typed_name = NULL;
  auth_heimdal_gssapi_options_block *ob =
    (auth_heimdal_gssapi_options_block *)(ablock->options_block);

  ablock->server = FALSE;
  ablock->client = FALSE;

  if (!ob->server_service || !*ob->server_service) {
    HDEBUG(D_auth) debug_printf("heimdal: missing server_service\n");
    return;
  }

  krc = krb5_init_context(&context);
  if (krc != 0) {
    int kerr = errno;
    HDEBUG(D_auth) debug_printf("heimdal: failed to initialise krb5 context: %s\n",
        strerror(kerr));
    return;
  }

  if (ob->server_keytab) {
    k_keytab_typed_name = CCS string_sprintf("file:%s", expand_string(ob->server_keytab));
    HDEBUG(D_auth) debug_printf("heimdal: using keytab %s\n", k_keytab_typed_name);
    krc = krb5_kt_resolve(context, k_keytab_typed_name, &keytab);
    if (krc) {
      HDEBUG(D_auth) exim_heimdal_error_debug("krb5_kt_resolve", context, krc);
      return;
    }
  } else {
    HDEBUG(D_auth) debug_printf("heimdal: using system default keytab\n");
    krc = krb5_kt_default(context, &keytab);
    if (krc) {
      HDEBUG(D_auth) exim_heimdal_error_debug("krb5_kt_default", context, krc);
      return;
    }
  }

  HDEBUG(D_auth) {
    /* http://www.h5l.org/manual/HEAD/krb5/krb5_keytab_intro.html */
    krc = krb5_kt_start_seq_get(context, keytab, &cursor);
    if (krc)
      exim_heimdal_error_debug("krb5_kt_start_seq_get", context, krc);
    else {
      while ((krc = krb5_kt_next_entry(context, keytab, &entry, &cursor)) == 0) {
        principal = enctype_s = NULL;
        krb5_unparse_name(context, entry.principal, &principal);
        krb5_enctype_to_string(context, entry.keyblock.keytype, &enctype_s);
        debug_printf("heimdal: keytab principal: %s  vno=%d  type=%s\n",
            principal ? principal : "??",
            entry.vno,
            enctype_s ? enctype_s : "??");
        free(principal);
        free(enctype_s);
        krb5_kt_free_entry(context, &entry);
      }
      krc = krb5_kt_end_seq_get(context, keytab, &cursor);
      if (krc)
        exim_heimdal_error_debug("krb5_kt_end_seq_get", context, krc);
    }
  }

  krc = krb5_kt_close(context, keytab);
  if (krc)
    HDEBUG(D_auth) exim_heimdal_error_debug("krb5_kt_close", context, krc);

  krb5_free_context(context);

  /* RFC 4121 section 5.2, SHOULD support 64K input buffers */
  if (big_buffer_size < (64 * 1024)) {
    uschar *newbuf;
    big_buffer_size = 64 * 1024;
    newbuf = store_malloc(big_buffer_size);
    store_free(big_buffer);
    big_buffer = newbuf;
  }

  ablock->server = TRUE;
}

Project: exim

smtp_connect(smtp_connect_args * sc, const blob * early_data)
{
int port = sc->host->port;
smtp_transport_options_block * ob = sc->ob;

callout_address = string_sprintf("[%s]:%d", sc->host->address, port);

HDEBUG(D_transport|D_acl|D_v)
  {
  uschar * s = US" ";
  if (sc->interface) s = string_sprintf(" from %s ", sc->interface);
#ifdef SUPPORT_SOCKS
  if (ob->socks_proxy) s = string_sprintf("%svia proxy ", s);
#endif
  debug_printf_indent("Connecting to %s %s%s... ", sc->host->name, callout_address, s);
  }

/* Create and connect the socket */

#ifdef SUPPORT_SOCKS
if (ob->socks_proxy)
  {
  int sock = socks_sock_connect(sc->host, sc->host_af, port, sc->interface,
				sc->tblock, ob->connect_timeout);
  
  if (sock >= 0)
    {
    if (early_data && early_data->data && early_data->len)
      if (send(sock, early_data->data, early_data->len, 0) < 0)
	{
	int save_errno = errno;
	HDEBUG(D_transport|D_acl|D_v)
	  {
	  debug_printf_indent("failed: %s", CUstrerror(save_errno));
	  if (save_errno == ETIMEDOUT)
	    debug_printf(" (timeout=%s)", readconf_printtime(ob->connect_timeout));
	  debug_printf("\n");
	  }
	(void)close(sock);
	sock = -1;
	errno = save_errno;
	}
    }
  return sock;
  }
#endif

return smtp_sock_connect(sc->host, sc->host_af, port, sc->interface,
			  sc->tblock, ob->connect_timeout, early_data);
}

Project: exim

bdat_flush_data(void)
{
while (chunking_data_left)
  {
  unsigned n = chunking_data_left;
  if (!bdat_getbuf(&n)) break;
  }

receive_getc = lwr_receive_getc;
receive_getbuf = lwr_receive_getbuf;
receive_ungetc = lwr_receive_ungetc;

if (chunking_state != CHUNKING_LAST)
  {
  chunking_state = CHUNKING_OFFERED;
  DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);
  }
}

Project: exim
File: host.c

host_address_extract_port(uschar *address)
{
int port = 0;
uschar *endptr;

/* Handle the "bracketed with colon on the end" format */

if (*address == '[')
  {
  uschar *rb = address + 1;
  while (*rb != 0 && *rb != ']') rb++;
  if (*rb++ == 0) return 0;        /* Missing ]; leave invalid address */
  if (*rb == ':')
    {
    port = Ustrtol(rb + 1, &endptr, 10);
    if (*endptr != 0) return 0;    /* Invalid port; leave invalid address */
    }
  else if (*rb != 0) return 0;     /* Bad syntax; leave invalid address */
  memmove(address, address + 1, rb - address - 2);
  rb[-2] = 0;
  }

/* Handle the "dot on the end" format */

else
  {
  int skip = -3;                   /* Skip 3 dots in IPv4 addresses */
  address--;
  while (*(++address) != 0)
    {
    int ch = *address;
    if (ch == ':') skip = 0;       /* Skip 0 dots in IPv6 addresses */
      else if (ch == '.' && skip++ >= 0) break;
    }
  if (*address == 0) return 0;
  port = Ustrtol(address + 1, &endptr, 10);
  if (*endptr != 0) return 0;      /* Invalid port; leave invalid address */
  *address = 0;
  }

return port;
}

Project: exim

command_timeout_handler(int sig)
{
had_command_timeout = sig;
}

Project: exim

header_add_at_position(BOOL after, uschar *name, BOOL topnot, int type,
  const char *format, ...)
{
va_list ap;
va_start(ap, format);
header_add_backend(after, name, topnot, type, format, ap);
va_end(ap);
}

Project: exim

check_helo(uschar *s)
{
uschar *start = s;
uschar *end = s + Ustrlen(s);
BOOL yield = fl.helo_accept_junk;

/* Discard any previous helo name */

if (sender_helo_name)
  {
  store_free(sender_helo_name);
  sender_helo_name = NULL;
  }

/* Skip tests if junk is permitted. */

if (!yield)

  /* Allow the new standard form for IPv6 address literals, namely,
  [IPv6:....], and because someone is bound to use it, allow an equivalent
  IPv4 form. Allow plain addresses as well. */

  if (*s == '[')
    {
    if (end[-1] == ']')
      {
      end[-1] = 0;
      if (strncmpic(s, US"[IPv6:", 6) == 0)
        yield = (string_is_ip_address(s+6, NULL) == 6);
      else if (strncmpic(s, US"[IPv4:", 6) == 0)
        yield = (string_is_ip_address(s+6, NULL) == 4);
      else
        yield = (string_is_ip_address(s+1, NULL) != 0);
      end[-1] = ']';
      }
    }

  /* Non-literals must be alpha, dot, hyphen, plus any non-valid chars
  that have been configured (usually underscore - sigh). */

  else if (*s)
    for (yield = TRUE; *s; s++)
      if (!isalnum(*s) && *s != '.' && *s != '-' &&
          Ustrchr(helo_allow_chars, *s) == NULL)
        {
        yield = FALSE;
        break;
        }

/* Save argument if OK */

if (yield) sender_helo_name = string_copy_malloc(start);
return yield;
}

Project: exim

msg_event_raise(const uschar * event, const address_item * addr)
{
const uschar * save_domain = deliver_domain;
uschar * save_local =  deliver_localpart;
const uschar * save_host = deliver_host;
const uschar * save_address = deliver_host_address;
const int      save_port =   deliver_host_port;

router_name =    addr->router ? addr->router->name : NULL;
deliver_domain = addr->domain;
deliver_localpart = addr->local_part;
deliver_host =   addr->host_used ? addr->host_used->name : NULL;

if (!addr->transport)
  {
  if (Ustrcmp(event, "msg:fail:delivery") == 0)
    {
     /* An address failed with no transport involved. This happens when
     a filter was used which triggered a fail command (in such a case
     a transport isn't needed).  Convert it to an internal fail event. */

    (void) event_raise(event_action, US"msg:fail:internal", addr->message);
    }
  }
else
  {
  transport_name = addr->transport->name;

  (void) event_raise(addr->transport->event_action, event,
	    addr->host_used
	    || Ustrcmp(addr->transport->driver_name, "smtp") == 0
	    || Ustrcmp(addr->transport->driver_name, "lmtp") == 0
	    || Ustrcmp(addr->transport->driver_name, "autoreply") == 0
	   ? addr->message : NULL);
  }

deliver_host_port =    save_port;
deliver_host_address = save_address;
deliver_host =      save_host;
deliver_localpart = save_local;
deliver_domain =    save_domain;
router_name = transport_name = NULL;
}

Project: exim

common_error(BOOL logit, address_item *addr, int code, uschar *format, ...)
{
address_item *addr2;
addr->basic_errno = code;

if (format)
  {
  va_list ap;
  gstring * g;

  va_start(ap, format);
  g = string_vformat(NULL, TRUE, CS format, ap);
  va_end(ap);
  addr->message = string_from_gstring(g);
  }

for (addr2 = addr->next; addr2; addr2 = addr2->next)
  {
  addr2->basic_errno = code;
  addr2->message = addr->message;
  }

if (logit) log_write(0, LOG_MAIN|LOG_PANIC, "%s", addr->message);
deliver_set_expansions(NULL);
}

Project: exim

smtp_ferror(void)
{
errno = smtp_had_error;
return smtp_had_error;
}

Project: exim

smtp_port_for_connect(host_item * host, int port)
{
if (host->port != PORT_NONE)
  {
  HDEBUG(D_transport|D_acl|D_v)
    debug_printf_indent("Transport port=%d replaced by host-specific port=%d\n", port,
      host->port);
  port = host->port;
  }
else host->port = port;    /* Set the port actually used */
}

Project: exim

write_env_to(address_item *p, struct aci **pplist, struct aci **pdlist,
  BOOL *first, transport_ctx * tctx)
{
address_item *pp;
struct aci *ppp;

/* Do nothing if we have already handled this address. If not, remember it
so that we don't handle it again. */

for (ppp = *pdlist; ppp; ppp = ppp->next) if (p == ppp->ptr) return TRUE;

ppp = store_get(sizeof(struct aci));
ppp->next = *pdlist;
*pdlist = ppp;
ppp->ptr = p;

/* Now scan up the ancestry, checking for duplicates at each generation. */

for (pp = p;; pp = pp->parent)
  {
  address_item *dup;
  for (dup = addr_duplicate; dup; dup = dup->next)
    if (dup->dupof == pp)   /* a dup of our address */
      if (!write_env_to(dup, pplist, pdlist, first, tctx))
	return FALSE;
  if (!pp->parent) break;
  }

/* Check to see if we have already output the progenitor. */

for (ppp = *pplist; ppp; ppp = ppp->next) if (pp == ppp->ptr) break;
if (ppp) return TRUE;

/* Remember what we have output, and output it. */

ppp = store_get(sizeof(struct aci));
ppp->next = *pplist;
*pplist = ppp;
ppp->ptr = pp;

if (!*first && !write_chunk(tctx, US",\n ", 3)) return FALSE;
*first = FALSE;
return write_chunk(tctx, pp->address, Ustrlen(pp->address));
}

Project: exim
File: deliver.c

deliver_get_sender_address (uschar * id)
{
int rc;
uschar * new_sender_address,
       * save_sender_address;
BOOL save_qr = f.queue_running;
uschar * spoolname;

/* make spool_open_datafile non-noisy on fail */

f.queue_running = TRUE;

/* Side effect: message_subdir is set for the (possibly split) spool directory */

deliver_datafile = spool_open_datafile(id);
f.queue_running = save_qr;
if (deliver_datafile < 0)
  return NULL;

/* Save and restore the global sender_address.  I'm not sure if we should
not save/restore all the other global variables too, because
spool_read_header() may change all of them. But OTOH, when this
deliver_get_sender_address() gets called, the current message is done
already and nobody needs the globals anymore. (HS12, 2015-08-21) */

spoolname = string_sprintf("%s-H", id);
save_sender_address = sender_address;

rc = spool_read_header(spoolname, TRUE, TRUE);

new_sender_address = sender_address;
sender_address = save_sender_address;

if (rc != spool_read_OK)
  return NULL;

assert(new_sender_address);

(void)close(deliver_datafile);
deliver_datafile = -1;

return new_sender_address;
}

Project: exim
File: receive.c

smtp_user_msg(uschar *code, uschar *user_msg)
{
int len = 3;
smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
smtp_respond(code, len, TRUE, user_msg);
}

Project: exim
File: deliver.c

string_log_address(gstring * g,
  address_item *addr, BOOL all_parents, BOOL success)
{
BOOL add_topaddr = TRUE;
address_item *topaddr;

/* Find the ultimate parent */

for (topaddr = addr; topaddr->parent; topaddr = topaddr->parent) ;

/* We start with just the local part for pipe, file, and reply deliveries, and
for successful local deliveries from routers that have the log_as_local flag
set. File deliveries from filters can be specified as non-absolute paths in
cases where the transport is going to complete the path. If there is an error
before this happens (expansion failure) the local part will not be updated, and
so won't necessarily look like a path. Add extra text for this case. */

if (  testflag(addr, af_pfr)
   || (  success
      && addr->router && addr->router->log_as_local
      && addr->transport && addr->transport->info->local
   )  )
  {
  if (testflag(addr, af_file) && addr->local_part[0] != '/')
    g = string_catn(g, CUS"save ", 5);
  g = string_get_localpart(addr, g);
  }

/* Other deliveries start with the full address. It we have split it into local
part and domain, use those fields. Some early failures can happen before the
splitting is done; in those cases use the original field. */

else
  {
  uschar * cmp = g->s + g->ptr;

  if (addr->local_part)
    {
    const uschar * s;
    g = string_get_localpart(addr, g);
    g = string_catn(g, US"@", 1);
    s = addr->domain;
#ifdef SUPPORT_I18N
    if (testflag(addr, af_utf8_downcvt))
      s = string_localpart_utf8_to_alabel(s, NULL);
#endif
    g = string_cat(g, s);
    }
  else
    g = string_cat(g, addr->address);

  /* If the address we are going to print is the same as the top address,
  and all parents are not being included, don't add on the top address. First
  of all, do a caseless comparison; if this succeeds, do a caseful comparison
  on the local parts. */

  string_from_gstring(g);	/* ensure nul-terminated */
  if (  strcmpic(cmp, topaddr->address) == 0
     && Ustrncmp(cmp, topaddr->address, Ustrchr(cmp, '@') - cmp) == 0
     && !addr->onetime_parent
     && (!all_parents || !addr->parent || addr->parent == topaddr)
     )
    add_topaddr = FALSE;
  }

/* If all parents are requested, or this is a local pipe/file/reply, and
there is at least one intermediate parent, show it in brackets, and continue
with all of them if all are wanted. */

if (  (all_parents || testflag(addr, af_pfr))
   && addr->parent
   && addr->parent != topaddr)
  {
  uschar *s = US" (";
  address_item *addr2;
  for (addr2 = addr->parent; addr2 != topaddr; addr2 = addr2->parent)
    {
    g = string_catn(g, s, 2);
    g = string_cat (g, addr2->address);
    if (!all_parents) break;
    s = US", ";
    }
  g = string_catn(g, US")", 1);
  }

/* Add the top address if it is required */

if (add_topaddr)
  g = string_append(g, 3,
    US" <",
    addr->onetime_parent ? addr->onetime_parent : topaddr->address,
    US">");

return g;
}

Project: exim
File: host.c

get_time_in_ms()
{
struct timeval tmp_time;
unsigned long seconds, microseconds;

gettimeofday(&tmp_time, NULL);
seconds = (unsigned long) tmp_time.tv_sec;
microseconds = (unsigned long) tmp_time.tv_usec;
return seconds*1000 + microseconds/1000;
}

Project: exim

auth_heimdal_gssapi_version_report(FILE *f)
{
  /* No build-time constants available unless we link against libraries at
  build-time and export the result as a string into a header ourselves. */
  fprintf(f, "Library version: Heimdal: Runtime: %s\n"
             " Build Info: %s\n",
          heimdal_version, heimdal_long_version);
}

Project: exim

read_response_line(smtp_inblock *inblock, uschar *buffer, int size, int timeout)
{
uschar *p = buffer;
uschar *ptr = inblock->ptr;
uschar *ptrend = inblock->ptrend;
client_conn_ctx * cctx = inblock->cctx;

/* Loop for reading multiple packets or reading another packet after emptying
a previously-read one. */

for (;;)
  {
  int rc;

  /* If there is data in the input buffer left over from last time, copy
  characters from it until the end of a line, at which point we can return,
  having removed any whitespace (which will include CR) at the end of the line.
  The rules for SMTP say that lines end in CRLF, but there are have been cases
  of hosts using just LF, and other MTAs are reported to handle this, so we
  just look for LF. If we run out of characters before the end of a line,
  carry on to read the next incoming packet. */

  while (ptr < ptrend)
    {
    int c = *ptr++;
    if (c == '\n')
      {
      while (p > buffer && isspace(p[-1])) p--;
      *p = 0;
      inblock->ptr = ptr;
      return p - buffer;
      }
    *p++ = c;
    if (--size < 4)
      {
      *p = 0;                     /* Leave malformed line for error message */
      errno = ERRNO_SMTPFORMAT;
      return -1;
      }
    }

  /* Need to read a new input packet. */

  if((rc = ip_recv(cctx, inblock->buffer, inblock->buffersize, timeout)) <= 0)
    {
    DEBUG(D_deliver|D_transport|D_acl)
      debug_printf_indent(errno ? "  SMTP(%s)<<\n" : "  SMTP(closed)<<\n",
	strerror(errno));
    break;
    }

  /* Another block of data has been successfully read. Set up the pointers
  and let the loop continue. */

  ptrend = inblock->ptrend = inblock->buffer + rc;
  ptr = inblock->buffer;
  DEBUG(D_transport|D_acl) debug_printf_indent("read response data: size=%d\n", rc);
  }

/* Get here if there has been some kind of recv() error; errno is set, but we
ensure that the result buffer is empty before returning. */

*buffer = 0;
return -1;
}

Project: exim
File: deliver.c

same_headers(header_line *one, header_line *two)
{
for (;; one = one->next, two = two->next)
  {
  if (one == two) return TRUE;   /* Includes the case where both NULL */
  if (!one || !two) return FALSE;
  if (Ustrcmp(one->text, two->text) != 0) return FALSE;
  }
}

Project: exim

exim_heimdal_error_debug(const char *label,
    krb5_context context, krb5_error_code err)
{
  const char *kerrsc;
  kerrsc = krb5_get_error_message(context, err);
  debug_printf("heimdal %s: %s\n", label, kerrsc ? kerrsc : "unknown error");
  krb5_free_error_message(context, kerrsc);
}

Project: exim

host_find_bydns(host_item *host, const uschar *ignore_target_hosts, int whichrrs,
  uschar *srv_service, uschar *srv_fail_domains, uschar *mx_fail_domains,
  const dnssec_domains *dnssec_d,
  const uschar **fully_qualified_name, BOOL *removed)
{
host_item *h, *last;
dns_record *rr;
int rc = DNS_FAIL;
int ind_type = 0;
int yield;
dns_answer dnsa;
dns_scan dnss;
BOOL dnssec_require = dnssec_d
		    && match_isinlist(host->name, CUSS &dnssec_d->require,
				    0, NULL, NULL, MCL_DOMAIN, TRUE, NULL) == OK;
BOOL dnssec_request = dnssec_require
		    || (  dnssec_d
		       && match_isinlist(host->name, CUSS &dnssec_d->request,
				    0, NULL, NULL, MCL_DOMAIN, TRUE, NULL) == OK);
dnssec_status_t dnssec;

/* Set the default fully qualified name to the incoming name, initialize the
resolver if necessary, set up the relevant options, and initialize the flag
that gets set for DNS syntax check errors. */

if (fully_qualified_name != NULL) *fully_qualified_name = host->name;
dns_init((whichrrs & HOST_FIND_QUALIFY_SINGLE) != 0,
         (whichrrs & HOST_FIND_SEARCH_PARENTS) != 0,
	 dnssec_request);
f.host_find_failed_syntax = FALSE;

/* First, if requested, look for SRV records. The service name is given; we
assume TCP protocol. DNS domain names are constrained to a maximum of 256
characters, so the code below should be safe. */

if (whichrrs & HOST_FIND_BY_SRV)
  {
  gstring * g;
  uschar * temp_fully_qualified_name;
  int prefix_length;

  g = string_fmt_append(NULL, "_%s._tcp.%n%.256s",
	srv_service, &prefix_length, host->name);
  temp_fully_qualified_name = string_from_gstring(g);
  ind_type = T_SRV;

  /* Search for SRV records. If the fully qualified name is different to
  the input name, pass back the new original domain, without the prepended
  magic. */

  dnssec = DS_UNK;
  lookup_dnssec_authenticated = NULL;
  rc = dns_lookup_timerwrap(&dnsa, temp_fully_qualified_name, ind_type,
	CUSS &temp_fully_qualified_name);

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

  if (dnssec_request)
    {
    if (dns_is_secure(&dnsa))
      { dnssec = DS_YES; lookup_dnssec_authenticated = US"yes"; }
    else
      { dnssec = DS_NO; lookup_dnssec_authenticated = US"no"; }
    }

  if (temp_fully_qualified_name != g->s && fully_qualified_name != NULL)
    *fully_qualified_name = temp_fully_qualified_name + prefix_length;

  /* On DNS failures, we give the "try again" error unless the domain is
  listed as one for which we continue. */

  if (rc == DNS_SUCCEED && dnssec_require && !dns_is_secure(&dnsa))
    {
    log_write(L_host_lookup_failed, LOG_MAIN,
		"dnssec fail on SRV for %.256s", host->name);
    rc = DNS_FAIL;
    }
  if (rc == DNS_FAIL || rc == DNS_AGAIN)
    {
    #ifndef STAND_ALONE
    if (match_isinlist(host->name, CUSS &srv_fail_domains, 0, NULL, NULL,
	MCL_DOMAIN, TRUE, NULL) != OK)
    #endif
      { yield = HOST_FIND_AGAIN; goto out; }
    DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA "
      "(domain in srv_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN");
    }
  }

/* If we did not find any SRV records, search the DNS for MX records, if
requested to do so. If the result is DNS_NOMATCH, it means there is no such
domain, and there's no point in going on to look for address records with the
same domain. The result will be DNS_NODATA if the domain exists but has no MX
records. On DNS failures, we give the "try again" error unless the domain is
listed as one for which we continue. */

if (rc != DNS_SUCCEED  &&  whichrrs & HOST_FIND_BY_MX)
  {
  ind_type = T_MX;
  dnssec = DS_UNK;
  lookup_dnssec_authenticated = NULL;
  rc = dns_lookup_timerwrap(&dnsa, host->name, ind_type, fully_qualified_name);

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

  if (dnssec_request)
    if (dns_is_secure(&dnsa))
      {
      DEBUG(D_host_lookup) debug_printf("%s MX DNSSEC\n", host->name);
      dnssec = DS_YES; lookup_dnssec_authenticated = US"yes";
      }
    else
      {
      dnssec = DS_NO; lookup_dnssec_authenticated = US"no";
      }

  switch (rc)
    {
    case DNS_NOMATCH:
      yield = HOST_FIND_FAILED; goto out;

    case DNS_SUCCEED:
      if (!dnssec_require || dns_is_secure(&dnsa))
	break;
      DEBUG(D_host_lookup)
	debug_printf("dnssec fail on MX for %.256s", host->name);
#ifndef STAND_ALONE
      if (match_isinlist(host->name, CUSS &mx_fail_domains, 0, NULL, NULL,
	  MCL_DOMAIN, TRUE, NULL) != OK)
	{ yield = HOST_FIND_SECURITY; goto out; }
#endif
      rc = DNS_FAIL;
      /*FALLTHROUGH*/

    case DNS_FAIL:
    case DNS_AGAIN:
#ifndef STAND_ALONE
      if (match_isinlist(host->name, CUSS &mx_fail_domains, 0, NULL, NULL,
	  MCL_DOMAIN, TRUE, NULL) != OK)
#endif
	{ yield = HOST_FIND_AGAIN; goto out; }
      DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA "
	"(domain in mx_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN");
      break;
    }
  }

/* If we haven't found anything yet, and we are requested to do so, try for an
A or AAAA record. If we find it (or them) check to see that it isn't the local
host. */

if (rc != DNS_SUCCEED)
  {
  if (!(whichrrs & (HOST_FIND_BY_A | HOST_FIND_BY_AAAA)))
    {
    DEBUG(D_host_lookup) debug_printf("Address records are not being sought\n");
    yield = HOST_FIND_FAILED;
    goto out;
    }

  last = host;        /* End of local chainlet */
  host->mx = MX_NONE;
  host->port = PORT_NONE;
  host->dnssec = DS_UNK;
  lookup_dnssec_authenticated = NULL;
  rc = set_address_from_dns(host, &last, ignore_target_hosts, FALSE,
    fully_qualified_name, dnssec_request, dnssec_require, whichrrs);

  /* If one or more address records have been found, check that none of them
  are local. Since we know the host items all have their IP addresses
  inserted, host_scan_for_local_hosts() can only return HOST_FOUND or
  HOST_FOUND_LOCAL. We do not need to scan for duplicate IP addresses here,
  because set_address_from_dns() removes them. */

  if (rc == HOST_FOUND)
    rc = host_scan_for_local_hosts(host, &last, removed);
  else
    if (rc == HOST_IGNORED) rc = HOST_FIND_FAILED;  /* No special action */

  DEBUG(D_host_lookup)
    {
    host_item *h;
    if (host->address != NULL)
      {
      if (fully_qualified_name != NULL)
        debug_printf("fully qualified name = %s\n", *fully_qualified_name);
      for (h = host; h != last->next; h = h->next)
        debug_printf("%s %s mx=%d sort=%d %s\n", h->name,
          (h->address == NULL)? US"<null>" : h->address, h->mx, h->sort_key,
          (h->status >= hstatus_unusable)? US"*" : US"");
      }
    }

  yield = rc;
  goto out;
  }

/* We have found one or more MX or SRV records. Sort them according to
precedence. Put the data for the first one into the existing host block, and
insert new host_item blocks into the chain for the remainder. For equal
precedences one is supposed to randomize the order. To make this happen, the
sorting is actually done on the MX value * 1000 + a random number. This is put
into a host field called sort_key.

In the case of hosts with both IPv6 and IPv4 addresses, we want to choose the
IPv6 address in preference. At this stage, we don't know what kind of address
the host has. We choose a random number < 500; if later we find an A record
first, we add 500 to the random number. Then for any other address records, we
use random numbers in the range 0-499 for AAAA records and 500-999 for A
records.

At this point we remove any duplicates that point to the same host, retaining
only the one with the lowest precedence. We cannot yet check for precedence
greater than that of the local host, because that test cannot be properly done
until the addresses have been found - an MX record may point to a name for this
host which is not the primary hostname. */

last = NULL;    /* Indicates that not even the first item is filled yet */

for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
     rr;
     rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT)) if (rr->type == ind_type)
  {
  int precedence;
  int weight = 0;        /* For SRV records */
  int port = PORT_NONE;
  const uschar * s = rr->data;	/* MUST be unsigned for GETSHORT */
  uschar data[256];

  GETSHORT(precedence, s);      /* Pointer s is advanced */

  /* For MX records, we use a random "weight" which causes multiple records of
  the same precedence to sort randomly. */

  if (ind_type == T_MX)
    weight = random_number(500);

  /* SRV records are specified with a port and a weight. The weight is used
  in a special algorithm. However, to start with, we just use it to order the
  records of equal priority (precedence). */

  else
    {
    GETSHORT(weight, s);
    GETSHORT(port, s);
    }

  /* Get the name of the host pointed to. */

  (void)dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, s,
    (DN_EXPAND_ARG4_TYPE)data, sizeof(data));

  /* Check that we haven't already got this host on the chain; if we have,
  keep only the lower precedence. This situation shouldn't occur, but you
  never know what junk might get into the DNS (and this case has been seen on
  more than one occasion). */

  if (last != NULL)       /* This is not the first record */
    {
    host_item *prev = NULL;

    for (h = host; h != last->next; prev = h, h = h->next)
      {
      if (strcmpic(h->name, data) == 0)
        {
        DEBUG(D_host_lookup)
          debug_printf("discarded duplicate host %s (MX=%d)\n", data,
            (precedence > h->mx)? precedence : h->mx);
        if (precedence >= h->mx) goto NEXT_MX_RR; /* Skip greater precedence */
        if (h == host)                            /* Override first item */
          {
          h->mx = precedence;
          host->sort_key = precedence * 1000 + weight;
          goto NEXT_MX_RR;
          }

        /* Unwanted host item is not the first in the chain, so we can get
        get rid of it by cutting it out. */

        prev->next = h->next;
        if (h == last) last = prev;
        break;
        }
      }
    }

  /* If this is the first MX or SRV record, put the data into the existing host
  block. Otherwise, add a new block in the correct place; if it has to be
  before the first block, copy the first block's data to a new second block. */

  if (!last)
    {
    host->name = string_copy_dnsdomain(data);
    host->address = NULL;
    host->port = port;
    host->mx = precedence;
    host->sort_key = precedence * 1000 + weight;
    host->status = hstatus_unknown;
    host->why = hwhy_unknown;
    host->dnssec = dnssec;
    last = host;
    }

  /* Make a new host item and seek the correct insertion place */

  else
    {
    int sort_key = precedence * 1000 + weight;
    host_item *next = store_get(sizeof(host_item));
    next->name = string_copy_dnsdomain(data);
    next->address = NULL;
    next->port = port;
    next->mx = precedence;
    next->sort_key = sort_key;
    next->status = hstatus_unknown;
    next->why = hwhy_unknown;
    next->dnssec = dnssec;
    next->last_try = 0;

    /* Handle the case when we have to insert before the first item. */

    if (sort_key < host->sort_key)
      {
      host_item htemp;
      htemp = *host;
      *host = *next;
      *next = htemp;
      host->next = next;
      if (last == host) last = next;
      }

    /* Else scan down the items we have inserted as part of this exercise;
    don't go further. */

    else
      {
      for (h = host; h != last; h = h->next)
        {
        if (sort_key < h->next->sort_key)
          {
          next->next = h->next;
          h->next = next;
          break;
          }
        }

      /* Join on after the last host item that's part of this
      processing if we haven't stopped sooner. */

      if (h == last)
        {
        next->next = last->next;
        last->next = next;
        last = next;
        }
      }
    }

  NEXT_MX_RR: continue;
  }

if (!last)	/* No rr of correct type; give up */
  {
  yield = HOST_FIND_FAILED;
  goto out;
  }

/* If the list of hosts was obtained from SRV records, there are two things to
do. First, if there is only one host, and it's name is ".", it means there is
no SMTP service at this domain. Otherwise, we have to sort the hosts of equal
priority according to their weights, using an algorithm that is defined in RFC
2782. The hosts are currently sorted by priority and weight. For each priority
group we have to pick off one host and put it first, and then repeat for any
remaining in the same priority group. */

if (ind_type == T_SRV)
  {
  host_item **pptr;

  if (host == last && host->name[0] == 0)
    {
    DEBUG(D_host_lookup) debug_printf("the single SRV record is \".\"\n");
    yield = HOST_FIND_FAILED;
    goto out;
    }

  DEBUG(D_host_lookup)
    {
    debug_printf("original ordering of hosts from SRV records:\n");
    for (h = host; h != last->next; h = h->next)
      debug_printf("  %s P=%d W=%d\n", h->name, h->mx, h->sort_key % 1000);
    }

  for (pptr = &host, h = host; h != last; pptr = &h->next, h = h->next)
    {
    int sum = 0;
    host_item *hh;

    /* Find the last following host that has the same precedence. At the same
    time, compute the sum of the weights and the running totals. These can be
    stored in the sort_key field. */

    for (hh = h; hh != last; hh = hh->next)
      {
      int weight = hh->sort_key % 1000;   /* was precedence * 1000 + weight */
      sum += weight;
      hh->sort_key = sum;
      if (hh->mx != hh->next->mx) break;
      }

    /* If there's more than one host at this precedence (priority), we need to
    pick one to go first. */

    if (hh != h)
      {
      host_item *hhh;
      host_item **ppptr;
      int randomizer = random_number(sum + 1);

      for (ppptr = pptr, hhh = h;
           hhh != hh;
           ppptr = &(hhh->next), hhh = hhh->next)
        {
        if (hhh->sort_key >= randomizer) break;
        }

      /* hhh now points to the host that should go first; ppptr points to the
      place that points to it. Unfortunately, if the start of the minilist is
      the start of the entire list, we can't just swap the items over, because
      we must not change the value of host, since it is passed in from outside.
      One day, this could perhaps be changed.

      The special case is fudged by putting the new item *second* in the chain,
      and then transferring the data between the first and second items. We
      can't just swap the first and the chosen item, because that would mean
      that an item with zero weight might no longer be first. */

      if (hhh != h)
        {
        *ppptr = hhh->next;          /* Cuts it out of the chain */

        if (h == host)
          {
          host_item temp = *h;
          *h = *hhh;
          *hhh = temp;
          hhh->next = temp.next;
          h->next = hhh;
          }

        else
          {
          hhh->next = h;               /* The rest of the chain follows it */
          *pptr = hhh;                 /* It takes the place of h */
          h = hhh;                     /* It's now the start of this minilist */
          }
        }
      }

    /* A host has been chosen to be first at this priority and h now points
    to this host. There may be others at the same priority, or others at a
    different priority. Before we leave this host, we need to put back a sort
    key of the traditional MX kind, in case this host is multihomed, because
    the sort key is used for ordering the multiple IP addresses. We do not need
    to ensure that these new sort keys actually reflect the order of the hosts,
    however. */

    h->sort_key = h->mx * 1000 + random_number(500);
    }   /* Move on to the next host */
  }

/* Now we have to find IP addresses for all the hosts. We have ensured above
that the names in all the host items are unique. Before release 4.61 we used to
process records from the additional section in the DNS packet that returned the
MX or SRV records. However, a DNS name server is free to drop any resource
records from the additional section. In theory, this has always been a
potential problem, but it is exacerbated by the advent of IPv6. If a host had
several IPv4 addresses and some were not in the additional section, at least
Exim would try the others. However, if a host had both IPv4 and IPv6 addresses
and all the IPv4 (say) addresses were absent, Exim would try only for a IPv6
connection, and never try an IPv4 address. When there was only IPv4
connectivity, this was a disaster that did in practice occur.

So, from release 4.61 onwards, we always search for A and AAAA records
explicitly. The names shouldn't point to CNAMES, but we use the general lookup
function that handles them, just in case. If any lookup gives a soft error,
change the default yield.

For these DNS lookups, we must disable qualify_single and search_parents;
otherwise invalid host names obtained from MX or SRV records can cause trouble
if they happen to match something local. */

yield = HOST_FIND_FAILED;    /* Default yield */
dns_init(FALSE, FALSE,       /* Disable qualify_single and search_parents */
	 dnssec_request || dnssec_require);

for (h = host; h != last->next; h = h->next)
  {
  if (h->address) continue;  /* Inserted by a multihomed host */

  rc = set_address_from_dns(h, &last, ignore_target_hosts, allow_mx_to_ip,
    NULL, dnssec_request, dnssec_require,
    whichrrs & HOST_FIND_IPV4_ONLY
    ?  HOST_FIND_BY_A  :  HOST_FIND_BY_A | HOST_FIND_BY_AAAA);
  if (rc != HOST_FOUND)
    {
    h->status = hstatus_unusable;
    switch (rc)
      {
      case HOST_FIND_AGAIN:	yield = rc; h->why = hwhy_deferred; break;
      case HOST_FIND_SECURITY:	yield = rc; h->why = hwhy_insecure; break;
      case HOST_IGNORED:	h->why = hwhy_ignored; break;
      default:			h->why = hwhy_failed; break;
      }
    }
  }

/* Scan the list for any hosts that are marked unusable because they have
been explicitly ignored, and remove them from the list, as if they did not
exist. If we end up with just a single, ignored host, flatten its fields as if
nothing was found. */

if (ignore_target_hosts)
  {
  host_item *prev = NULL;
  for (h = host; h != last->next; h = h->next)
    {
    REDO:
    if (h->why != hwhy_ignored)        /* Non ignored host, just continue */
      prev = h;
    else if (prev == NULL)             /* First host is ignored */
      {
      if (h != last)                   /* First is not last */
        {
        if (h->next == last) last = h; /* Overwrite it with next */
        *h = *(h->next);               /* and reprocess it. */
        goto REDO;                     /* C should have redo, like Perl */
        }
      }
    else                               /* Ignored host is not first - */
      {                                /*   cut it out */
      prev->next = h->next;
      if (h == last) last = prev;
      }
    }

  if (host->why == hwhy_ignored) host->address = NULL;
  }

/* There is still one complication in the case of IPv6. Although the code above
arranges that IPv6 addresses take precedence over IPv4 addresses for multihomed
hosts, it doesn't do this for addresses that apply to different hosts with the
same MX precedence, because the sorting on MX precedence happens first. So we
have to make another pass to check for this case. We ensure that, within a
single MX preference value, IPv6 addresses come first. This can separate the
addresses of a multihomed host, but that should not matter. */

#if HAVE_IPV6
if (h != last && !disable_ipv6) for (h = host; h != last; h = h->next)
  {
  host_item temp;
  host_item *next = h->next;

  if (  h->mx != next->mx			/* If next is different MX */
     || !h->address				/* OR this one is unset */
     )
    continue;					/* move on to next */

  if (  whichrrs & HOST_FIND_IPV4_FIRST
     ?     !Ustrchr(h->address, ':')		/* OR this one is IPv4 */
        || next->address
           && Ustrchr(next->address, ':')	/* OR next is IPv6 */

     :     Ustrchr(h->address, ':')		/* OR this one is IPv6 */
        || next->address
           && !Ustrchr(next->address, ':')	/* OR next is IPv4 */
     )
    continue;                                /* move on to next */

  temp = *h;                                 /* otherwise, swap */
  temp.next = next->next;
  *h = *next;
  h->next = next;
  *next = temp;
  }
#endif

/* Remove any duplicate IP addresses and then scan the list of hosts for any
whose IP addresses are on the local host. If any are found, all hosts with the
same or higher MX values are removed. However, if the local host has the lowest
numbered MX, then HOST_FOUND_LOCAL is returned. Otherwise, if at least one host
with an IP address is on the list, HOST_FOUND is returned. Otherwise,
HOST_FIND_FAILED is returned, but in this case do not update the yield, as it
might have been set to HOST_FIND_AGAIN just above here. If not, it will already
be HOST_FIND_FAILED. */

host_remove_duplicates(host, &last);
rc = host_scan_for_local_hosts(host, &last, removed);
if (rc != HOST_FIND_FAILED) yield = rc;

DEBUG(D_host_lookup)
  {
  if (fully_qualified_name != NULL)
    debug_printf("fully qualified name = %s\n", *fully_qualified_name);
  debug_printf("host_find_bydns yield = %s (%d); returned hosts:\n",
    (yield == HOST_FOUND)? "HOST_FOUND" :
    (yield == HOST_FOUND_LOCAL)? "HOST_FOUND_LOCAL" :
    (yield == HOST_FIND_SECURITY)? "HOST_FIND_SECURITY" :
    (yield == HOST_FIND_AGAIN)? "HOST_FIND_AGAIN" :
    (yield == HOST_FIND_FAILED)? "HOST_FIND_FAILED" : "?",
    yield);
  for (h = host; h != last->next; h = h->next)
    {
    debug_printf("  %s %s MX=%d %s", h->name,
      !h->address ? US"<null>" : h->address, h->mx,
      h->dnssec == DS_YES ? US"DNSSEC " : US"");
    if (h->port != PORT_NONE) debug_printf("port=%d ", h->port);
    if (h->status >= hstatus_unusable) debug_printf("*");
    debug_printf("\n");
    }
  }

out:

dns_init(FALSE, FALSE, FALSE);	/* clear the dnssec bit for getaddrbyname */
return yield;
}

Project: exim

regex_match_and_setup(const pcre *re, const uschar *subject, int options, int setup)
{
int ovector[3*(EXPAND_MAXN+1)];
uschar * s = string_copy(subject);	/* de-constifying */
int n = pcre_exec(re, NULL, CS s, Ustrlen(s), 0,
  PCRE_EOPT | options, ovector, nelem(ovector));
BOOL yield = n >= 0;
if (n == 0) n = EXPAND_MAXN + 1;
if (yield)
  {
  int nn;
  expand_nmax = setup < 0 ? 0 : setup + 1;
  for (nn = setup < 0 ? 0 : 2; nn < n*2; nn += 2)
    {
    expand_nstring[expand_nmax] = s + ovector[nn];
    expand_nlength[expand_nmax++] = ovector[nn+1] - ovector[nn];
    }
  expand_nmax--;
  }
return yield;
}

Project: exim

log_write_failed(uschar *name, int length, int rc)
{
int save_errno = errno;

if (!panic_save_buffer)
  if ((panic_save_buffer = US malloc(LOG_BUFFER_SIZE)))
    memcpy(panic_save_buffer, log_buffer, LOG_BUFFER_SIZE);

log_write(0, LOG_PANIC_DIE, "failed to write to %s: length=%d result=%d "
  "errno=%d (%s)", name, length, rc, save_errno,
  (save_errno == 0)? "write incomplete" : strerror(save_errno));
/* Never returns */
}

Project: exim

smtp_start_session(void)
{
int esclen;
uschar *user_msg, *log_msg;
uschar *code, *esc;
uschar *p, *s;
gstring * ss;

gettimeofday(&smtp_connection_start, NULL);
for (smtp_ch_index = 0; smtp_ch_index < SMTP_HBUFF_SIZE; smtp_ch_index++)
  smtp_connection_had[smtp_ch_index] = SCH_NONE;
smtp_ch_index = 0;

/* Default values for certain variables */

fl.helo_seen = fl.esmtp = fl.helo_accept_junk = FALSE;
smtp_mailcmd_count = 0;
count_nonmail = TRUE_UNSET;
synprot_error_count = unknown_command_count = nonmail_command_count = 0;
smtp_delay_mail = smtp_rlm_base;
fl.auth_advertised = FALSE;
f.smtp_in_pipelining_advertised = f.smtp_in_pipelining_used = FALSE;
f.pipelining_enable = TRUE;
sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
fl.smtp_exit_function_called = FALSE;    /* For avoiding loop in not-quit exit */

/* If receiving by -bs from a trusted user, or testing with -bh, we allow
authentication settings from -oMaa to remain in force. */

if (!host_checking && !f.sender_host_notsocket)
  sender_host_auth_pubname = sender_host_authenticated = NULL;
authenticated_by = NULL;

#ifdef SUPPORT_TLS
tls_in.cipher = tls_in.peerdn = NULL;
tls_in.ourcert = tls_in.peercert = NULL;
tls_in.sni = NULL;
tls_in.ocsp = OCSP_NOT_REQ;
fl.tls_advertised = FALSE;
# ifdef EXPERIMENTAL_REQUIRETLS
fl.requiretls_advertised = FALSE;
# endif
#endif
fl.dsn_advertised = FALSE;
#ifdef SUPPORT_I18N
fl.smtputf8_advertised = FALSE;
#endif

/* Reset ACL connection variables */

acl_var_c = NULL;

/* Allow for trailing 0 in the command and data buffers. */

if (!(smtp_cmd_buffer = US malloc(2*SMTP_CMD_BUFFER_SIZE + 2)))
  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
    "malloc() failed for SMTP command buffer");

smtp_cmd_buffer[0] = 0;
smtp_data_buffer = smtp_cmd_buffer + SMTP_CMD_BUFFER_SIZE + 1;

/* For batched input, the protocol setting can be overridden from the
command line by a trusted caller. */

if (smtp_batched_input)
  {
  if (!received_protocol) received_protocol = US"local-bsmtp";
  }

/* For non-batched SMTP input, the protocol setting is forced here. It will be
reset later if any of EHLO/AUTH/STARTTLS are received. */

else
  received_protocol =
    (sender_host_address ? protocols : protocols_local) [pnormal];

/* Set up the buffer for inputting using direct read() calls, and arrange to
call the local functions instead of the standard C ones.  Place a NUL at the
end of the buffer to safety-stop C-string reads from it. */

if (!(smtp_inbuffer = US malloc(IN_BUFFER_SIZE)))
  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "malloc() failed for SMTP input buffer");
smtp_inbuffer[IN_BUFFER_SIZE-1] = '\0';

receive_getc = smtp_getc;
receive_getbuf = smtp_getbuf;
receive_get_cache = smtp_get_cache;
receive_ungetc = smtp_ungetc;
receive_feof = smtp_feof;
receive_ferror = smtp_ferror;
receive_smtp_buffered = smtp_buffered;
smtp_inptr = smtp_inend = smtp_inbuffer;
smtp_had_eof = smtp_had_error = 0;

/* Set up the message size limit; this may be host-specific */

thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
if (expand_string_message)
  {
  if (thismessage_size_limit == -1)
    log_write(0, LOG_MAIN|LOG_PANIC, "unable to expand message_size_limit: "
      "%s", expand_string_message);
  else
    log_write(0, LOG_MAIN|LOG_PANIC, "invalid message_size_limit: "
      "%s", expand_string_message);
  smtp_closedown(US"Temporary local problem - please try later");
  return FALSE;
  }

/* When a message is input locally via the -bs or -bS options, sender_host_
unknown is set unless -oMa was used to force an IP address, in which case it
is checked like a real remote connection. When -bs is used from inetd, this
flag is not set, causing the sending host to be checked. The code that deals
with IP source routing (if configured) is never required for -bs or -bS and
the flag sender_host_notsocket is used to suppress it.

If smtp_accept_max and smtp_accept_reserve are set, keep some connections in
reserve for certain hosts and/or networks. */

if (!f.sender_host_unknown)
  {
  int rc;
  BOOL reserved_host = FALSE;

  /* Look up IP options (source routing info) on the socket if this is not an
  -oMa "host", and if any are found, log them and drop the connection.

  Linux (and others now, see below) is different to everyone else, so there
  has to be some conditional compilation here. Versions of Linux before 2.1.15
  used a structure whose name was "options". Somebody finally realized that
  this name was silly, and it got changed to "ip_options". I use the
  newer name here, but there is a fudge in the script that sets up os.h
  to define a macro in older Linux systems.

  Sigh. Linux is a fast-moving target. Another generation of Linux uses
  glibc 2, which has chosen ip_opts for the structure name. This is now
  really a glibc thing rather than a Linux thing, so the condition name
  has been changed to reflect this. It is relevant also to GNU/Hurd.

  Mac OS 10.x (Darwin) is like the later glibc versions, but without the
  setting of the __GLIBC__ macro, so we can't detect it automatically. There's
  a special macro defined in the os.h file.

  Some DGUX versions on older hardware appear not to support IP options at
  all, so there is now a general macro which can be set to cut out this
  support altogether.

  How to do this properly in IPv6 is not yet known. */

#if !HAVE_IPV6 && !defined(NO_IP_OPTIONS)

  #ifdef GLIBC_IP_OPTIONS
    #if (!defined __GLIBC__) || (__GLIBC__ < 2)
    #define OPTSTYLE 1
    #else
    #define OPTSTYLE 2
    #endif
  #elif defined DARWIN_IP_OPTIONS
    #define OPTSTYLE 2
  #else
    #define OPTSTYLE 3
  #endif

  if (!host_checking && !f.sender_host_notsocket)
    {
    #if OPTSTYLE == 1
    EXIM_SOCKLEN_T optlen = sizeof(struct ip_options) + MAX_IPOPTLEN;
    struct ip_options *ipopt = store_get(optlen);
    #elif OPTSTYLE == 2
    struct ip_opts ipoptblock;
    struct ip_opts *ipopt = &ipoptblock;
    EXIM_SOCKLEN_T optlen = sizeof(ipoptblock);
    #else
    struct ipoption ipoptblock;
    struct ipoption *ipopt = &ipoptblock;
    EXIM_SOCKLEN_T optlen = sizeof(ipoptblock);
    #endif

    /* Occasional genuine failures of getsockopt() have been seen - for
    example, "reset by peer". Therefore, just log and give up on this
    call, unless the error is ENOPROTOOPT. This error is given by systems
    that have the interfaces but not the mechanism - e.g. GNU/Hurd at the time
    of writing. So for that error, carry on - we just can't do an IP options
    check. */

    DEBUG(D_receive) debug_printf("checking for IP options\n");

    if (getsockopt(fileno(smtp_out), IPPROTO_IP, IP_OPTIONS, US (ipopt),
          &optlen) < 0)
      {
      if (errno != ENOPROTOOPT)
        {
        log_write(0, LOG_MAIN, "getsockopt() failed from %s: %s",
          host_and_ident(FALSE), strerror(errno));
        smtp_printf("451 SMTP service not available\r\n", FALSE);
        return FALSE;
        }
      }

    /* Deal with any IP options that are set. On the systems I have looked at,
    the value of MAX_IPOPTLEN has been 40, meaning that there should never be
    more logging data than will fit in big_buffer. Nevertheless, after somebody
    questioned this code, I've added in some paranoid checking. */

    else if (optlen > 0)
      {
      uschar *p = big_buffer;
      uschar *pend = big_buffer + big_buffer_size;
      uschar *opt, *adptr;
      int optcount;
      struct in_addr addr;

      #if OPTSTYLE == 1
      uschar *optstart = US (ipopt->__data);
      #elif OPTSTYLE == 2
      uschar *optstart = US (ipopt->ip_opts);
      #else
      uschar *optstart = US (ipopt->ipopt_list);
      #endif

      DEBUG(D_receive) debug_printf("IP options exist\n");

      Ustrcpy(p, "IP options on incoming call:");
      p += Ustrlen(p);

      for (opt = optstart; opt != NULL &&
           opt < US (ipopt) + optlen;)
        {
        switch (*opt)
          {
          case IPOPT_EOL:
          opt = NULL;
          break;

          case IPOPT_NOP:
          opt++;
          break;

          case IPOPT_SSRR:
          case IPOPT_LSRR:
          if (!string_format(p, pend-p, " %s [@%s",
               (*opt == IPOPT_SSRR)? "SSRR" : "LSRR",
               #if OPTSTYLE == 1
               inet_ntoa(*((struct in_addr *)(&(ipopt->faddr))))))
               #elif OPTSTYLE == 2
               inet_ntoa(ipopt->ip_dst)))
               #else
               inet_ntoa(ipopt->ipopt_dst)))
               #endif
            {
            opt = NULL;
            break;
            }

          p += Ustrlen(p);
          optcount = (opt[1] - 3) / sizeof(struct in_addr);
          adptr = opt + 3;
          while (optcount-- > 0)
            {
            memcpy(&addr, adptr, sizeof(addr));
            if (!string_format(p, pend - p - 1, "%s%s",
                  (optcount == 0)? ":" : "@", inet_ntoa(addr)))
              {
              opt = NULL;
              break;
              }
            p += Ustrlen(p);
            adptr += sizeof(struct in_addr);
            }
          *p++ = ']';
          opt += opt[1];
          break;

          default:
            {
            int i;
            if (pend - p < 4 + 3*opt[1]) { opt = NULL; break; }
            Ustrcat(p, "[ ");
            p += 2;
            for (i = 0; i < opt[1]; i++)
              p += sprintf(CS p, "%2.2x ", opt[i]);
            *p++ = ']';
            }
          opt += opt[1];
          break;
          }
        }

      *p = 0;
      log_write(0, LOG_MAIN, "%s", big_buffer);

      /* Refuse any call with IP options. This is what tcpwrappers 7.5 does. */

      log_write(0, LOG_MAIN|LOG_REJECT,
        "connection from %s refused (IP options)", host_and_ident(FALSE));

      smtp_printf("554 SMTP service not available\r\n", FALSE);
      return FALSE;
      }

    /* Length of options = 0 => there are no options */

    else DEBUG(D_receive) debug_printf("no IP options found\n");
    }
#endif  /* HAVE_IPV6 && !defined(NO_IP_OPTIONS) */

  /* Set keep-alive in socket options. The option is on by default. This
  setting is an attempt to get rid of some hanging connections that stick in
  read() when the remote end (usually a dialup) goes away. */

  if (smtp_accept_keepalive && !f.sender_host_notsocket)
    ip_keepalive(fileno(smtp_out), sender_host_address, FALSE);

  /* If the current host matches host_lookup, set the name by doing a
  reverse lookup. On failure, sender_host_name will be NULL and
  host_lookup_failed will be TRUE. This may or may not be serious - optional
  checks later. */

  if (verify_check_host(&host_lookup) == OK)
    {
    (void)host_name_lookup();
    host_build_sender_fullhost();
    }

  /* Delay this until we have the full name, if it is looked up. */

  set_process_info("handling incoming connection from %s",
    host_and_ident(FALSE));

  /* Expand smtp_receive_timeout, if needed */

  if (smtp_receive_timeout_s)
    {
    uschar * exp;
    if (  !(exp = expand_string(smtp_receive_timeout_s))
       || !(*exp)
       || (smtp_receive_timeout = readconf_readtime(exp, 0, FALSE)) < 0
       )
      log_write(0, LOG_MAIN|LOG_PANIC,
	"bad value for smtp_receive_timeout: '%s'", exp ? exp : US"");
    }

  /* Test for explicit connection rejection */

  if (verify_check_host(&host_reject_connection) == OK)
    {
    log_write(L_connection_reject, LOG_MAIN|LOG_REJECT, "refused connection "
      "from %s (host_reject_connection)", host_and_ident(FALSE));
    smtp_printf("554 SMTP service not available\r\n", FALSE);
    return FALSE;
    }

  /* Test with TCP Wrappers if so configured. There is a problem in that
  hosts_ctl() returns 0 (deny) under a number of system failure circumstances,
  such as disks dying. In these cases, it is desirable to reject with a 4xx
  error instead of a 5xx error. There isn't a "right" way to detect such
  problems. The following kludge is used: errno is zeroed before calling
  hosts_ctl(). If the result is "reject", a 5xx error is given only if the
  value of errno is 0 or ENOENT (which happens if /etc/hosts.{allow,deny} does
  not exist). */

#ifdef USE_TCP_WRAPPERS
  errno = 0;
  if (!(tcp_wrappers_name = expand_string(tcp_wrappers_daemon_name)))
    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" "
      "(tcp_wrappers_name) failed: %s", string_printing(tcp_wrappers_name),
        expand_string_message);

  if (!hosts_ctl(tcp_wrappers_name,
         sender_host_name ? CS sender_host_name : STRING_UNKNOWN,
         sender_host_address ? CS sender_host_address : STRING_UNKNOWN,
         sender_ident ? CS sender_ident : STRING_UNKNOWN))
    {
    if (errno == 0 || errno == ENOENT)
      {
      HDEBUG(D_receive) debug_printf("tcp wrappers rejection\n");
      log_write(L_connection_reject,
                LOG_MAIN|LOG_REJECT, "refused connection from %s "
                "(tcp wrappers)", host_and_ident(FALSE));
      smtp_printf("554 SMTP service not available\r\n", FALSE);
      }
    else
      {
      int save_errno = errno;
      HDEBUG(D_receive) debug_printf("tcp wrappers rejected with unexpected "
        "errno value %d\n", save_errno);
      log_write(L_connection_reject,
                LOG_MAIN|LOG_REJECT, "temporarily refused connection from %s "
                "(tcp wrappers errno=%d)", host_and_ident(FALSE), save_errno);
      smtp_printf("451 Temporary local problem - please try later\r\n", FALSE);
      }
    return FALSE;
    }
#endif

  /* Check for reserved slots. The value of smtp_accept_count has already been
  incremented to include this process. */

  if (smtp_accept_max > 0 &&
      smtp_accept_count > smtp_accept_max - smtp_accept_reserve)
    {
    if ((rc = verify_check_host(&smtp_reserve_hosts)) != OK)
      {
      log_write(L_connection_reject,
        LOG_MAIN, "temporarily refused connection from %s: not in "
        "reserve list: connected=%d max=%d reserve=%d%s",
        host_and_ident(FALSE), smtp_accept_count - 1, smtp_accept_max,
        smtp_accept_reserve, (rc == DEFER)? " (lookup deferred)" : "");
      smtp_printf("421 %s: Too many concurrent SMTP connections; "
        "please try again later\r\n", FALSE, smtp_active_hostname);
      return FALSE;
      }
    reserved_host = TRUE;
    }

  /* If a load level above which only messages from reserved hosts are
  accepted is set, check the load. For incoming calls via the daemon, the
  check is done in the superior process if there are no reserved hosts, to
  save a fork. In all cases, the load average will already be available
  in a global variable at this point. */

  if (smtp_load_reserve >= 0 &&
       load_average > smtp_load_reserve &&
       !reserved_host &&
       verify_check_host(&smtp_reserve_hosts) != OK)
    {
    log_write(L_connection_reject,
      LOG_MAIN, "temporarily refused connection from %s: not in "
      "reserve list and load average = %.2f", host_and_ident(FALSE),
      (double)load_average/1000.0);
    smtp_printf("421 %s: Too much load; please try again later\r\n", FALSE,
      smtp_active_hostname);
    return FALSE;
    }

  /* Determine whether unqualified senders or recipients are permitted
  for this host. Unfortunately, we have to do this every time, in order to
  set the flags so that they can be inspected when considering qualifying
  addresses in the headers. For a site that permits no qualification, this
  won't take long, however. */

  f.allow_unqualified_sender =
    verify_check_host(&sender_unqualified_hosts) == OK;

  f.allow_unqualified_recipient =
    verify_check_host(&recipient_unqualified_hosts) == OK;

  /* Determine whether HELO/EHLO is required for this host. The requirement
  can be hard or soft. */

  fl.helo_required = verify_check_host(&helo_verify_hosts) == OK;
  if (!fl.helo_required)
    fl.helo_verify = verify_check_host(&helo_try_verify_hosts) == OK;

  /* Determine whether this hosts is permitted to send syntactic junk
  after a HELO or EHLO command. */

  fl.helo_accept_junk = verify_check_host(&helo_accept_junk_hosts) == OK;
  }

/* For batch SMTP input we are now done. */

if (smtp_batched_input) return TRUE;

/* If valid Proxy Protocol source is connecting, set up session.
 * Failure will not allow any SMTP function other than QUIT. */

#ifdef SUPPORT_PROXY
proxy_session = FALSE;
f.proxy_session_failed = FALSE;
if (check_proxy_protocol_host())
  setup_proxy_protocol_host();
#endif

  /* Start up TLS if tls_on_connect is set. This is for supporting the legacy
  smtps port for use with older style SSL MTAs. */

#ifdef SUPPORT_TLS
  if (tls_in.on_connect)
    {
    if (tls_server_start(tls_require_ciphers, &user_msg) != OK)
      return smtp_log_tls_fail(user_msg);
    cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
    }
#endif

/* Run the connect ACL if it exists */

user_msg = NULL;
if (acl_smtp_connect)
  {
  int rc;
  if ((rc = acl_check(ACL_WHERE_CONNECT, NULL, acl_smtp_connect, &user_msg,
		      &log_msg)) != OK)
    {
    (void) smtp_handle_acl_fail(ACL_WHERE_CONNECT, rc, user_msg, log_msg);
    return FALSE;
    }
  }

/* Output the initial message for a two-way SMTP connection. It may contain
newlines, which then cause a multi-line response to be given. */

code = US"220";   /* Default status code */
esc = US"";       /* Default extended status code */
esclen = 0;       /* Length of esc */

if (!user_msg)
  {
  if (!(s = expand_string(smtp_banner)))
    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" (smtp_banner) "
      "failed: %s", smtp_banner, expand_string_message);
  }
else
  {
  int codelen = 3;
  s = user_msg;
  smtp_message_code(&code, &codelen, &s, NULL, TRUE);
  if (codelen > 4)
    {
    esc = code + 4;
    esclen = codelen - 4;
    }
  }

/* Remove any terminating newlines; might as well remove trailing space too */

p = s + Ustrlen(s);
while (p > s && isspace(p[-1])) p--;
*p = 0;

/* It seems that CC:Mail is braindead, and assumes that the greeting message
is all contained in a single IP packet. The original code wrote out the
greeting using several calls to fprint/fputc, and on busy servers this could
cause it to be split over more than one packet - which caused CC:Mail to fall
over when it got the second part of the greeting after sending its first
command. Sigh. To try to avoid this, build the complete greeting message
first, and output it in one fell swoop. This gives a better chance of it
ending up as a single packet. */

ss = string_get(256);

p = s;
do       /* At least once, in case we have an empty string */
  {
  int len;
  uschar *linebreak = Ustrchr(p, '\n');
  ss = string_catn(ss, code, 3);
  if (!linebreak)
    {
    len = Ustrlen(p);
    ss = string_catn(ss, US" ", 1);
    }
  else
    {
    len = linebreak - p;
    ss = string_catn(ss, US"-", 1);
    }
  ss = string_catn(ss, esc, esclen);
  ss = string_catn(ss, p, len);
  ss = string_catn(ss, US"\r\n", 2);
  p += len;
  if (linebreak) p++;
  }
while (*p);

/* Before we write the banner, check that there is no input pending, unless
this synchronisation check is disabled. */

#ifdef EXPERIMENTAL_PIPE_CONNECT
fl.pipe_connect_acceptable =
  sender_host_address && verify_check_host(&pipe_connect_advertise_hosts) == OK;

if (!check_sync())
  if (fl.pipe_connect_acceptable)
    f.smtp_in_early_pipe_used = TRUE;
  else
#else
if (!check_sync())
#endif
    {
    unsigned n = smtp_inend - smtp_inptr;
    if (n > 32) n = 32;

    log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol "
      "synchronization error (input sent without waiting for greeting): "
      "rejected connection from %s input=\"%s\"", host_and_ident(TRUE),
      string_printing(string_copyn(smtp_inptr, n)));
    smtp_printf("554 SMTP synchronization error\r\n", FALSE);
    return FALSE;
    }

/* Now output the banner */
/*XXX the ehlo-resp code does its own tls/nontls bit.  Maybe subroutine that? */

smtp_printf("%s",
#ifdef EXPERIMENTAL_PIPE_CONNECT
  fl.pipe_connect_acceptable && pipeline_connect_sends(),
#else
  FALSE,
#endif
  string_from_gstring(ss));

/* Attempt to see if we sent the banner before the last ACK of the 3-way
handshake arrived.  If so we must have managed a TFO. */

#ifdef TCP_FASTOPEN
tfo_in_check();
#endif

return TRUE;
}

Project: exim

exim_gssapi_error_defer(uschar *store_reset_point,
    OM_uint32 major, OM_uint32 minor,
    const char *format, ...)
{
  va_list ap;
  OM_uint32 maj_stat, min_stat;
  OM_uint32 msgcontext = 0;
  gss_buffer_desc status_string;
  gstring * g;

  HDEBUG(D_auth)
    {
    va_start(ap, format);
    g = string_vformat(NULL, TRUE, format, ap);
    va_end(ap);
    }

  auth_defer_msg = NULL;

  do {
    maj_stat = gss_display_status(&min_stat,
        major, GSS_C_GSS_CODE, GSS_C_NO_OID, &msgcontext, &status_string);

    if (!auth_defer_msg)
      auth_defer_msg = string_copy(US status_string.value);

    HDEBUG(D_auth) debug_printf("heimdal %s: %.*s\n",
        string_from_gstring(g), (int)status_string.length,
	CS status_string.value);
    gss_release_buffer(&min_stat, &status_string);

  } while (msgcontext != 0);

  if (store_reset_point)
    store_reset(store_reset_point);
  return DEFER;
}

Project: exim
File: deliver.c

readn(int fd, void * buffer, size_t len)
{
  void * next = buffer;
  void * end = buffer + len;

  while (next < end)
    {
    ssize_t got = read(fd, next, end - next);

    /* I'm not sure if there are signals that can interrupt us,
    for now I assume the worst */
    if (got == -1 && errno == EINTR) continue;
    if (got <= 0) return next - buffer;
    next += got;
    }

  return len;
}

Project: exim

die(uschar *s1, uschar *s2)
{
if (s1)
  {
  write_syslog(LOG_CRIT, s1);
  if (debug_file) debug_printf("%s\n", s1);
  if (log_stderr && log_stderr != debug_file)
    fprintf(log_stderr, "%s\n", s1);
  }
if (f.receive_call_bombout) receive_bomb_out(NULL, s2);  /* does not return */
if (smtp_input) smtp_closedown(s2);
exim_exit(EXIT_FAILURE, NULL);
}

Project: exim

ratelimit_error(uschar **log_msgptr, const char *format, ...)
{
va_list ap;
gstring * g =
  string_cat(NULL, US"error in arguments to \"ratelimit\" condition: ");

va_start(ap, format);
g = string_vformat(g, TRUE, format, ap);
va_end(ap);

gstring_reset_unused(g);
*log_msgptr = string_from_gstring(g);
return ERROR;
}

Project: exim

string_nextinlist(const uschar **listptr, int *separator, uschar *buffer, int buflen)
{
int sep = *separator;
const uschar *s = *listptr;
BOOL sep_is_special;

if (s == NULL) return NULL;

/* This allows for a fixed specified separator to be an iscntrl() character,
but at the time of implementation, this is never the case. However, it's best
to be conservative. */

while (isspace(*s) && *s != sep) s++;

/* A change of separator is permitted, so look for a leading '<' followed by an
allowed character. */

if (sep <= 0)
  {
  if (*s == '<' && (ispunct(s[1]) || iscntrl(s[1])))
    {
    sep = s[1];
    s += 2;
    while (isspace(*s) && *s != sep) s++;
    }
  else
    {
    sep = (sep == 0)? ':' : -sep;
    }
  *separator = sep;
  }

/* An empty string has no list elements */

if (*s == 0) return NULL;

/* Note whether whether or not the separator is an iscntrl() character. */

sep_is_special = iscntrl(sep);

/* Handle the case when a buffer is provided. */

if (buffer)
  {
  int p = 0;
  for (; *s != 0; s++)
    {
    if (*s == sep && (*(++s) != sep || sep_is_special)) break;
    if (p < buflen - 1) buffer[p++] = *s;
    }
  while (p > 0 && isspace(buffer[p-1])) p--;
  buffer[p] = 0;
  }

/* Handle the case when a buffer is not provided. */

else
  {
  const uschar *ss;
  gstring * g = NULL;

  /* We know that *s != 0 at this point. However, it might be pointing to a
  separator, which could indicate an empty string, or (if an ispunct()
  character) could be doubled to indicate a separator character as data at the
  start of a string. Avoid getting working memory for an empty item. */

  if (*s == sep)
    {
    s++;
    if (*s != sep || sep_is_special)
      {
      *listptr = s;
      return string_copy(US"");
      }
    }

  /* Not an empty string; the first character is guaranteed to be a data
  character. */

  for (;;)
    {
    for (ss = s + 1; *ss != 0 && *ss != sep; ss++) ;
    g = string_catn(g, s, ss-s);
    s = ss;
    if (*s == 0 || *(++s) != sep || sep_is_special) break;
    }
  while (g->ptr > 0 && isspace(g->s[g->ptr-1])) g->ptr--;
  buffer = string_from_gstring(g);
  gstring_reset_unused(g);
  }

/* Update the current pointer and return the new string */

*listptr = s;
return buffer;
}

Project: exim
File: deliver.c

findugid(address_item *addr, transport_instance *tp, uid_t *uidp, gid_t *gidp,
  BOOL *igfp)
{
uschar *nuname;
BOOL gid_set = FALSE;

/* Default initgroups flag comes from the transport */

*igfp = tp->initgroups;

/* First see if there's a gid on the transport, either fixed or expandable.
The expanding function always logs failure itself. */

if (tp->gid_set)
  {
  *gidp = tp->gid;
  gid_set = TRUE;
  }
else if (tp->expand_gid)
  {
  if (!route_find_expanded_group(tp->expand_gid, tp->name, US"transport", gidp,
    &(addr->message)))
    {
    common_error(FALSE, addr, ERRNO_GIDFAIL, NULL);
    return FALSE;
    }
  gid_set = TRUE;
  }

/* If the transport did not set a group, see if the router did. */

if (!gid_set && testflag(addr, af_gid_set))
  {
  *gidp = addr->gid;
  gid_set = TRUE;
  }

/* Pick up a uid from the transport if one is set. */

if (tp->uid_set) *uidp = tp->uid;

/* Otherwise, try for an expandable uid field. If it ends up as a numeric id,
it does not provide a passwd value from which a gid can be taken. */

else if (tp->expand_uid)
  {
  struct passwd *pw;
  if (!route_find_expanded_user(tp->expand_uid, tp->name, US"transport", &pw,
       uidp, &(addr->message)))
    {
    common_error(FALSE, addr, ERRNO_UIDFAIL, NULL);
    return FALSE;
    }
  if (!gid_set && pw)
    {
    *gidp = pw->pw_gid;
    gid_set = TRUE;
    }
  }

/* If the transport doesn't set the uid, test the deliver_as_creator flag. */

else if (tp->deliver_as_creator)
  {
  *uidp = originator_uid;
  if (!gid_set)
    {
    *gidp = originator_gid;
    gid_set = TRUE;
    }
  }

/* Otherwise see if the address specifies the uid and if so, take it and its
initgroups flag. */

else if (testflag(addr, af_uid_set))
  {
  *uidp = addr->uid;
  *igfp = testflag(addr, af_initgroups);
  }

/* Nothing has specified the uid - default to the Exim user, and group if the
gid is not set. */

else
  {
  *uidp = exim_uid;
  if (!gid_set)
    {
    *gidp = exim_gid;
    gid_set = TRUE;
    }
  }

/* If no gid is set, it is a disaster. We default to the Exim gid only if
defaulting to the Exim uid. In other words, if the configuration has specified
a uid, it must also provide a gid. */

if (!gid_set)
  {
  common_error(TRUE, addr, ERRNO_GIDFAIL, US"User set without group for "
    "%s transport", tp->name);
  return FALSE;
  }

/* Check that the uid is not on the lists of banned uids that may not be used
for delivery processes. */

nuname = check_never_users(*uidp, never_users)
  ? US"never_users"
  : check_never_users(*uidp, fixed_never_users)
  ? US"fixed_never_users"
  : NULL;
if (nuname)
  {
  common_error(TRUE, addr, ERRNO_UIDFAIL, US"User %ld set for %s transport "
    "is on the %s list", (long int)(*uidp), tp->name, nuname);
  return FALSE;
  }

/* All is well */

return TRUE;
}

Project: exim

smtp_command_timeout_exit(void)
{
log_write(L_lost_incoming_connection,
	  LOG_MAIN, "SMTP command timeout on%s connection from %s",
	  tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE));
if (smtp_batched_input)
  moan_smtp_batch(NULL, "421 SMTP command timeout"); /* Does not return */
smtp_notquit_exit(US"command-timeout", US"421",
  US"%s: SMTP command timeout - closing connection",
  smtp_active_hostname);
exim_exit(EXIT_FAILURE, US"receiving");
}

Project: exim
File: host.c

host_aton(const uschar *address, int *bin)
{
int x[4];
int v4offset = 0;

/* Handle IPv6 address, which may end with an IPv4 address. It may also end
with a "scope", introduced by a percent sign. This code is NOT enclosed in #if
HAVE_IPV6 in order that IPv6 addresses are recognized even if IPv6 is not
supported. */

if (Ustrchr(address, ':') != NULL)
  {
  const uschar *p = address;
  const uschar *component[8];
  BOOL ipv4_ends = FALSE;
  int ci = 0;
  int nulloffset = 0;
  int v6count = 8;
  int i;

  /* If the address starts with a colon, it will start with two colons.
  Just lose the first one, which will leave a null first component. */

  if (*p == ':') p++;

  /* Split the address into components separated by colons. The input address
  is supposed to be checked for syntax. There was a case where this was
  overlooked; to guard against that happening again, check here and crash if
  there are too many components. */

  while (*p != 0 && *p != '%')
    {
    int len = Ustrcspn(p, ":%");
    if (len == 0) nulloffset = ci;
    if (ci > 7) log_write(0, LOG_MAIN|LOG_PANIC_DIE,
      "Internal error: invalid IPv6 address \"%s\" passed to host_aton()",
      address);
    component[ci++] = p;
    p += len;
    if (*p == ':') p++;
    }

  /* If the final component contains a dot, it is a trailing v4 address.
  As the syntax is known to be checked, just set up for a trailing
  v4 address and restrict the v6 part to 6 components. */

  if (Ustrchr(component[ci-1], '.') != NULL)
    {
    address = component[--ci];
    ipv4_ends = TRUE;
    v4offset = 3;
    v6count = 6;
    }

  /* If there are fewer than 6 or 8 components, we have to insert some
  more empty ones in the middle. */

  if (ci < v6count)
    {
    int insert_count = v6count - ci;
    for (i = v6count-1; i > nulloffset + insert_count; i--)
      component[i] = component[i - insert_count];
    while (i > nulloffset) component[i--] = US"";
    }

  /* Now turn the components into binary in pairs and bung them
  into the vector of ints. */

  for (i = 0; i < v6count; i += 2)
    bin[i/2] = (Ustrtol(component[i], NULL, 16) << 16) +
      Ustrtol(component[i+1], NULL, 16);

  /* If there was no terminating v4 component, we are done. */

  if (!ipv4_ends) return 4;
  }

/* Handle IPv4 address */

(void)sscanf(CS address, "%d.%d.%d.%d", x, x+1, x+2, x+3);
bin[v4offset] = ((uint)x[0] << 24) + (x[1] << 16) + (x[2] << 8) + x[3];
return v4offset+1;
}

Project: exim

smtp_printf(const char *format, BOOL more, ...)
{
va_list ap;

va_start(ap, more);
smtp_vprintf(format, more, ap);
va_end(ap);
}

Project: exim

extract_option(uschar **name, uschar **value)
{
uschar *n;
uschar *v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1;
while (isspace(*v)) v--;
v[1] = 0;
while (v > smtp_cmd_data && *v != '=' && !isspace(*v))
  {
  /* Take care to not stop at a space embedded in a quoted local-part */

  if (*v == '"') do v--; while (*v != '"' && v > smtp_cmd_data+1);
  v--;
  }

n = v;
if (*v == '=')
  {
  while(isalpha(n[-1])) n--;
  /* RFC says SP, but TAB seen in wild and other major MTAs accept it */
  if (!isspace(n[-1])) return FALSE;
  n[-1] = 0;
  }
else
  {
  n++;
  if (v == smtp_cmd_data) return FALSE;
  }
*v++ = 0;
*name = n;
*value = v;
return TRUE;
}

Project: exim

auth_heimdal_gssapi_client(
  auth_instance *ablock,                 /* authenticator block */
  void * sx,				 /* connection */
  int timeout,                           /* command timeout */
  uschar *buffer,                        /* buffer for reading response */
  int buffsize)                          /* size of buffer */
{
  HDEBUG(D_auth)
    debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n");
  /* NOT IMPLEMENTED */
  return FAIL;
}

Project: exim

transport_check_waiting(const uschar *transport_name, const uschar *hostname,
  int local_message_max, uschar *new_message_id, BOOL *more, oicf oicf_func, void *oicf_data)
{
dbdata_wait *host_record;
int host_length;
open_db dbblock;
open_db *dbm_file;

int         i;
struct stat statbuf;

*more = FALSE;

DEBUG(D_transport)
  {
  debug_printf("transport_check_waiting entered\n");
  debug_printf("  sequence=%d local_max=%d global_max=%d\n",
    continue_sequence, local_message_max, connection_max_messages);
  }

/* Do nothing if we have hit the maximum number that can be send down one
connection. */

if (connection_max_messages >= 0) local_message_max = connection_max_messages;
if (local_message_max > 0 && continue_sequence >= local_message_max)
  {
  DEBUG(D_transport)
    debug_printf("max messages for one connection reached: returning\n");
  return FALSE;
  }

/* Open the waiting information database. */

if (!(dbm_file = dbfn_open(string_sprintf("wait-%.200s", transport_name),
			  O_RDWR, &dbblock, TRUE)))
  return FALSE;

/* See if there is a record for this host; if not, there's nothing to do. */

if (!(host_record = dbfn_read(dbm_file, hostname)))
  {
  dbfn_close(dbm_file);
  DEBUG(D_transport) debug_printf("no messages waiting for %s\n", hostname);
  return FALSE;
  }

/* If the data in the record looks corrupt, just log something and
don't try to use it. */

if (host_record->count > WAIT_NAME_MAX)
  {
  dbfn_close(dbm_file);
  log_write(0, LOG_MAIN|LOG_PANIC, "smtp-wait database entry for %s has bad "
    "count=%d (max=%d)", hostname, host_record->count, WAIT_NAME_MAX);
  return FALSE;
  }

/* Scan the message ids in the record from the end towards the beginning,
until one is found for which a spool file actually exists. If the record gets
emptied, delete it and continue with any continuation records that may exist.
*/

/* For Bug 1141, I refactored this major portion of the routine, it is risky
but the 1 off will remain without it.  This code now allows me to SKIP over
a message I do not want to send out on this run.  */

host_length = host_record->count * MESSAGE_ID_LENGTH;

while (1)
  {
  msgq_t      *msgq;
  int         msgq_count = 0;
  int         msgq_actual = 0;
  BOOL        bFound = FALSE;
  BOOL        bContinuation = FALSE;

  /* create an array to read entire message queue into memory for processing  */

  msgq = store_malloc(sizeof(msgq_t) * host_record->count);
  msgq_count = host_record->count;
  msgq_actual = msgq_count;

  for (i = 0; i < host_record->count; ++i)
    {
    msgq[i].bKeep = TRUE;

    Ustrncpy(msgq[i].message_id, host_record->text + (i * MESSAGE_ID_LENGTH),
      MESSAGE_ID_LENGTH);
    msgq[i].message_id[MESSAGE_ID_LENGTH] = 0;
    }

  /* first thing remove current message id if it exists */

  for (i = 0; i < msgq_count; ++i)
    if (Ustrcmp(msgq[i].message_id, message_id) == 0)
      {
      msgq[i].bKeep = FALSE;
      break;
      }

  /* now find the next acceptable message_id */

  for (i = msgq_count - 1; i >= 0; --i) if (msgq[i].bKeep)
    {
    uschar subdir[2];

    subdir[0] = split_spool_directory ? msgq[i].message_id[5] : 0;
    subdir[1] = 0;

    if (Ustat(spool_fname(US"input", subdir, msgq[i].message_id, US"-D"),
	      &statbuf) != 0)
      msgq[i].bKeep = FALSE;
    else if (!oicf_func || oicf_func(msgq[i].message_id, oicf_data))
      {
      Ustrcpy(new_message_id, msgq[i].message_id);
      msgq[i].bKeep = FALSE;
      bFound = TRUE;
      break;
      }
    }

  /* re-count */
  for (msgq_actual = 0, i = 0; i < msgq_count; ++i)
    if (msgq[i].bKeep)
      msgq_actual++;

  /* reassemble the host record, based on removed message ids, from in
  memory queue  */

  if (msgq_actual <= 0)
    {
    host_length = 0;
    host_record->count = 0;
    }
  else
    {
    host_length = msgq_actual * MESSAGE_ID_LENGTH;
    host_record->count = msgq_actual;

    if (msgq_actual < msgq_count)
      {
      int new_count;
      for (new_count = 0, i = 0; i < msgq_count; ++i)
	if (msgq[i].bKeep)
	  Ustrncpy(&host_record->text[new_count++ * MESSAGE_ID_LENGTH],
	    msgq[i].message_id, MESSAGE_ID_LENGTH);

      host_record->text[new_count * MESSAGE_ID_LENGTH] = 0;
      }
    }

  /* Check for a continuation record. */

  while (host_length <= 0)
    {
    int i;
    dbdata_wait * newr = NULL;
    uschar buffer[256];

    /* Search for a continuation */

    for (i = host_record->sequence - 1; i >= 0 && !newr; i--)
      {
      sprintf(CS buffer, "%.200s:%d", hostname, i);
      newr = dbfn_read(dbm_file, buffer);
      }

    /* If no continuation, delete the current and break the loop */

    if (!newr)
      {
      dbfn_delete(dbm_file, hostname);
      break;
      }

    /* Else replace the current with the continuation */

    dbfn_delete(dbm_file, buffer);
    host_record = newr;
    host_length = host_record->count * MESSAGE_ID_LENGTH;

    bContinuation = TRUE;
    }

  if (bFound)		/* Usual exit from main loop */
    {
    store_free (msgq);
    break;
    }

  /* If host_length <= 0 we have emptied a record and not found a good message,
  and there are no continuation records. Otherwise there is a continuation
  record to process. */

  if (host_length <= 0)
    {
    dbfn_close(dbm_file);
    DEBUG(D_transport) debug_printf("waiting messages already delivered\n");
    return FALSE;
    }

  /* we were not able to find an acceptable message, nor was there a
   * continuation record.  So bug out, outer logic will clean this up.
   */

  if (!bContinuation)
    {
    Ustrcpy(new_message_id, message_id);
    dbfn_close(dbm_file);
    return FALSE;
    }

  store_free(msgq);
  }		/* we need to process a continuation record */

/* Control gets here when an existing message has been encountered; its
id is in new_message_id, and host_length is the revised length of the
host record. If it is zero, the record has been removed. Update the
record if required, close the database, and return TRUE. */

if (host_length > 0)
  {
  host_record->count = host_length/MESSAGE_ID_LENGTH;

  dbfn_write(dbm_file, hostname, host_record, (int)sizeof(dbdata_wait) + host_length);
  *more = TRUE;
  }

dbfn_close(dbm_file);
return TRUE;
}

Project: exim

transport_write_block(transport_ctx * tctx, uschar *block, int len, BOOL more)
{
if (!(tctx->options & topt_output_string))
  return transport_write_block_fd(tctx, block, len, more);

/* Write to expanding-string.  NOTE: not NUL-terminated */

if (!tctx->u.msg)
  tctx->u.msg = string_get(1024);

tctx->u.msg = string_catn(tctx->u.msg, block, len);
return TRUE;
}

Project: exim
File: deliver.c

post_process_one(address_item *addr, int result, int logflags, int driver_type,
  int logchar)
{
uschar *now = tod_stamp(tod_log);
uschar *driver_kind = NULL;
uschar *driver_name = NULL;

DEBUG(D_deliver) debug_printf("post-process %s (%d)\n", addr->address, result);

/* Set up driver kind and name for logging. Disable logging if the router or
transport has disabled it. */

if (driver_type == EXIM_DTYPE_TRANSPORT)
  {
  if (addr->transport)
    {
    driver_name = addr->transport->name;
    driver_kind = US" transport";
    f.disable_logging = addr->transport->disable_logging;
    }
  else driver_kind = US"transporting";
  }
else if (driver_type == EXIM_DTYPE_ROUTER)
  {
  if (addr->router)
    {
    driver_name = addr->router->name;
    driver_kind = US" router";
    f.disable_logging = addr->router->disable_logging;
    }
  else driver_kind = US"routing";
  }

/* If there's an error message set, ensure that it contains only printing
characters - it should, but occasionally things slip in and this at least
stops the log format from getting wrecked. We also scan the message for an LDAP
expansion item that has a password setting, and flatten the password. This is a
fudge, but I don't know a cleaner way of doing this. (If the item is badly
malformed, it won't ever have gone near LDAP.) */

if (addr->message)
  {
  const uschar * s = string_printing(addr->message);

  /* deconst cast ok as string_printing known to have alloc'n'copied */
  addr->message = expand_hide_passwords(US s);
  }

/* If we used a transport that has one of the "return_output" options set, and
if it did in fact generate some output, then for return_output we treat the
message as failed if it was not already set that way, so that the output gets
returned to the sender, provided there is a sender to send it to. For
return_fail_output, do this only if the delivery failed. Otherwise we just
unlink the file, and remove the name so that if the delivery failed, we don't
try to send back an empty or unwanted file. The log_output options operate only
on a non-empty file.

In any case, we close the message file, because we cannot afford to leave a
file-descriptor for one address while processing (maybe very many) others. */

if (addr->return_file >= 0 && addr->return_filename)
  {
  BOOL return_output = FALSE;
  struct stat statbuf;
  (void)EXIMfsync(addr->return_file);

  /* If there is no output, do nothing. */

  if (fstat(addr->return_file, &statbuf) == 0 && statbuf.st_size > 0)
    {
    transport_instance *tb = addr->transport;

    /* Handle logging options */

    if (  tb->log_output
       || result == FAIL  && tb->log_fail_output
       || result == DEFER && tb->log_defer_output
       )
      {
      uschar *s;
      FILE *f = Ufopen(addr->return_filename, "rb");
      if (!f)
        log_write(0, LOG_MAIN|LOG_PANIC, "failed to open %s to log output "
          "from %s transport: %s", addr->return_filename, tb->name,
          strerror(errno));
      else
        if ((s = US Ufgets(big_buffer, big_buffer_size, f)))
          {
          uschar *p = big_buffer + Ustrlen(big_buffer);
	  const uschar * sp;
          while (p > big_buffer && isspace(p[-1])) p--;
          *p = 0;
          sp = string_printing(big_buffer);
          log_write(0, LOG_MAIN, "<%s>: %s transport output: %s",
            addr->address, tb->name, sp);
          }
      (void)fclose(f);
      }

    /* Handle returning options, but only if there is an address to return
    the text to. */

    if (sender_address[0] != 0 || addr->prop.errors_address)
      if (tb->return_output)
        {
        addr->transport_return = result = FAIL;
        if (addr->basic_errno == 0 && !addr->message)
          addr->message = US"return message generated";
        return_output = TRUE;
        }
      else
        if (tb->return_fail_output && result == FAIL) return_output = TRUE;
    }

  /* Get rid of the file unless it might be returned, but close it in
  all cases. */

  if (!return_output)
    {
    Uunlink(addr->return_filename);
    addr->return_filename = NULL;
    addr->return_file = -1;
    }

  (void)close(addr->return_file);
  }

/* The success case happens only after delivery by a transport. */

if (result == OK)
  {
  addr->next = addr_succeed;
  addr_succeed = addr;

  /* Call address_done() to ensure that we don't deliver to this address again,
  and write appropriate things to the message log. If it is a child address, we
  call child_done() to scan the ancestors and mark them complete if this is the
  last child to complete. */

  address_done(addr, now);
  DEBUG(D_deliver) debug_printf("%s delivered\n", addr->address);

  if (!addr->parent)
    deliver_msglog("%s %s: %s%s succeeded\n", now, addr->address,
      driver_name, driver_kind);
  else
    {
    deliver_msglog("%s %s <%s>: %s%s succeeded\n", now, addr->address,
      addr->parent->address, driver_name, driver_kind);
    child_done(addr, now);
    }

  /* Certificates for logging (via events) */
#ifdef SUPPORT_TLS
  tls_out.ourcert = addr->ourcert;
  addr->ourcert = NULL;
  tls_out.peercert = addr->peercert;
  addr->peercert = NULL;

  tls_out.cipher = addr->cipher;
  tls_out.peerdn = addr->peerdn;
  tls_out.ocsp = addr->ocsp;
# ifdef SUPPORT_DANE
  tls_out.dane_verified = testflag(addr, af_dane_verified);
# endif
#endif

  delivery_log(LOG_MAIN, addr, logchar, NULL);

#ifdef SUPPORT_TLS
  tls_free_cert(&tls_out.ourcert);
  tls_free_cert(&tls_out.peercert);
  tls_out.cipher = NULL;
  tls_out.peerdn = NULL;
  tls_out.ocsp = OCSP_NOT_REQ;
# ifdef SUPPORT_DANE
  tls_out.dane_verified = FALSE;
# endif
#endif
  }


/* Soft failure, or local delivery process failed; freezing may be
requested. */

else if (result == DEFER || result == PANIC)
  {
  if (result == PANIC) logflags |= LOG_PANIC;

  /* This puts them on the chain in reverse order. Do not change this, because
  the code for handling retries assumes that the one with the retry
  information is last. */

  addr->next = addr_defer;
  addr_defer = addr;

  /* The only currently implemented special action is to freeze the
  message. Logging of this is done later, just before the -H file is
  updated. */

  if (addr->special_action == SPECIAL_FREEZE)
    {
    f.deliver_freeze = TRUE;
    deliver_frozen_at = time(NULL);
    update_spool = TRUE;
    }

  /* If doing a 2-stage queue run, we skip writing to either the message
  log or the main log for SMTP defers. */

  if (!f.queue_2stage || addr->basic_errno != 0)
    deferral_log(addr, now, logflags, driver_name, driver_kind);
  }


/* Hard failure. If there is an address to which an error message can be sent,
put this address on the failed list. If not, put it on the deferred list and
freeze the mail message for human attention. The latter action can also be
explicitly requested by a router or transport. */

else
  {
  /* If this is a delivery error, or a message for which no replies are
  wanted, and the message's age is greater than ignore_bounce_errors_after,
  force the af_ignore_error flag. This will cause the address to be discarded
  later (with a log entry). */

  if (!*sender_address && message_age >= ignore_bounce_errors_after)
    addr->prop.ignore_error = TRUE;

  /* Freeze the message if requested, or if this is a bounce message (or other
  message with null sender) and this address does not have its own errors
  address. However, don't freeze if errors are being ignored. The actual code
  to ignore occurs later, instead of sending a message. Logging of freezing
  occurs later, just before writing the -H file. */

  if (  !addr->prop.ignore_error
     && (  addr->special_action == SPECIAL_FREEZE
        || (sender_address[0] == 0 && !addr->prop.errors_address)
     )  )
    {
    frozen_info = addr->special_action == SPECIAL_FREEZE
      ? US""
      : f.sender_local && !f.local_error_message
      ? US" (message created with -f <>)"
      : US" (delivery error message)";
    f.deliver_freeze = TRUE;
    deliver_frozen_at = time(NULL);
    update_spool = TRUE;

    /* The address is put on the defer rather than the failed queue, because
    the message is being retained. */

    addr->next = addr_defer;
    addr_defer = addr;
    }

  /* Don't put the address on the nonrecipients tree yet; wait until an
  error message has been successfully sent. */

  else
    {
    addr->next = addr_failed;
    addr_failed = addr;
    }

  failure_log(addr, driver_name ? NULL : driver_kind, now);
  }

/* Ensure logging is turned on again in all cases */

f.disable_logging = FALSE;
}

Project: exim

lmtp_transport_entry(
  transport_instance *tblock,      /* data for this instantiation */
  address_item *addrlist)          /* address(es) we are working on */
{
pid_t pid = 0;
FILE *out;
lmtp_transport_options_block *ob =
  (lmtp_transport_options_block *)(tblock->options_block);
struct sockaddr_un sockun;         /* don't call this "sun" ! */
int timeout = ob->timeout;
int fd_in = -1, fd_out = -1;
int code, save_errno;
BOOL send_data;
BOOL yield = FALSE;
address_item *addr;
uschar *igquotstr = US"";
uschar *sockname = NULL;
const uschar **argv;
uschar buffer[256];

DEBUG(D_transport) debug_printf("%s transport entered\n", tblock->name);

/* Initialization ensures that either a command or a socket is specified, but
not both. When a command is specified, call the common function for creating an
argument list and expanding the items. */

if (ob->cmd)
  {
  DEBUG(D_transport) debug_printf("using command %s\n", ob->cmd);
  sprintf(CS buffer, "%.50s transport", tblock->name);
  if (!transport_set_up_command(&argv, ob->cmd, TRUE, PANIC, addrlist, buffer,
       NULL))
    return FALSE;

  /* If the -N option is set, can't do any more. Presume all has gone well. */
  if (f.dont_deliver)
    goto MINUS_N;

/* As this is a local transport, we are already running with the required
uid/gid and current directory. Request that the new process be a process group
leader, so we can kill it and all its children on an error. */

  if ((pid = child_open(USS argv, NULL, 0, &fd_in, &fd_out, TRUE)) < 0)
    {
    addrlist->message = string_sprintf(
      "Failed to create child process for %s transport: %s", tblock->name,
        strerror(errno));
    return FALSE;
    }
  }

/* When a socket is specified, expand the string and create a socket. */

else
  {
  DEBUG(D_transport) debug_printf("using socket %s\n", ob->skt);
  sockname = expand_string(ob->skt);
  if (sockname == NULL)
    {
    addrlist->message = string_sprintf("Expansion of \"%s\" (socket setting "
      "for %s transport) failed: %s", ob->skt, tblock->name,
      expand_string_message);
    return FALSE;
    }
  if ((fd_in = fd_out = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
    {
    addrlist->message = string_sprintf(
      "Failed to create socket %s for %s transport: %s",
        ob->skt, tblock->name, strerror(errno));
    return FALSE;
    }

  /* If the -N option is set, can't do any more. Presume all has gone well. */
  if (f.dont_deliver)
    goto MINUS_N;

  sockun.sun_family = AF_UNIX;
  sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1), sockname);
  if(connect(fd_out, (struct sockaddr *)(&sockun), sizeof(sockun)) == -1)
    {
    addrlist->message = string_sprintf(
      "Failed to connect to socket %s for %s transport: %s",
        sockun.sun_path, tblock->name, strerror(errno));
    return FALSE;
    }
  }


/* Make the output we are going to read into a file. */

out = fdopen(fd_out, "rb");

/* Now we must implement the LMTP protocol. It is like SMTP, except that after
the end of the message, a return code for every accepted RCPT TO is sent. This
allows for message+recipient checks after the message has been received. */

/* First thing is to wait for an initial greeting. */

Ustrcpy(big_buffer, "initial connection");
if (!lmtp_read_response(out, buffer, sizeof(buffer), '2',
  timeout)) goto RESPONSE_FAILED;

/* Next, we send a LHLO command, and expect a positive response */

if (!lmtp_write_command(fd_in, "%s %s\r\n", "LHLO",
  primary_hostname)) goto WRITE_FAILED;

if (!lmtp_read_response(out, buffer, sizeof(buffer), '2',
     timeout)) goto RESPONSE_FAILED;

/* If the ignore_quota option is set, note whether the server supports the
IGNOREQUOTA option, and if so, set an appropriate addition for RCPT. */

if (ob->ignore_quota)
  igquotstr = (pcre_exec(regex_IGNOREQUOTA, NULL, CS buffer,
    Ustrlen(CS buffer), 0, PCRE_EOPT, NULL, 0) >= 0)? US" IGNOREQUOTA" : US"";

/* Now the envelope sender */

if (!lmtp_write_command(fd_in, "MAIL FROM:<%s>\r\n", return_path))
  goto WRITE_FAILED;

if (!lmtp_read_response(out, buffer, sizeof(buffer), '2', timeout))
  {
  if (errno == 0 && buffer[0] == '4')
    {
    errno = ERRNO_MAIL4XX;
    addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
    }
  goto RESPONSE_FAILED;
  }

/* Next, we hand over all the recipients. Some may be permanently or
temporarily rejected; others may be accepted, for now. */

send_data = FALSE;
for (addr = addrlist; addr != NULL; addr = addr->next)
  {
  if (!lmtp_write_command(fd_in, "RCPT TO:<%s>%s\r\n",
       transport_rcpt_address(addr, tblock->rcpt_include_affixes), igquotstr))
    goto WRITE_FAILED;
  if (lmtp_read_response(out, buffer, sizeof(buffer), '2', timeout))
    {
    send_data = TRUE;
    addr->transport_return = PENDING_OK;
    }
  else
    {
    if (errno != 0 || buffer[0] == 0) goto RESPONSE_FAILED;
    addr->message = string_sprintf("LMTP error after %s: %s", big_buffer,
      string_printing(buffer));
    setflag(addr, af_pass_message);   /* Allow message to go to user */
    if (buffer[0] == '5') addr->transport_return = FAIL; else
      {
      addr->basic_errno = ERRNO_RCPT4XX;
      addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
      }
    }
  }

/* Now send the text of the message if there were any good recipients. */

if (send_data)
  {
  BOOL ok;
  transport_ctx tctx = {
    {fd_in},
    tblock,
    addrlist,
    US".", US"..",
    ob->options
  };

  if (!lmtp_write_command(fd_in, "DATA\r\n")) goto WRITE_FAILED;
  if (!lmtp_read_response(out, buffer, sizeof(buffer), '3', timeout))
    {
    if (errno == 0 && buffer[0] == '4')
      {
      errno = ERRNO_DATA4XX;
      addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
      }
    goto RESPONSE_FAILED;
    }

  sigalrm_seen = FALSE;
  transport_write_timeout = timeout;
  Ustrcpy(big_buffer, "sending data block");   /* For error messages */
  DEBUG(D_transport|D_v)
    debug_printf("  LMTP>> writing message and terminating \".\"\n");

  transport_count = 0;
  ok = transport_write_message(&tctx, 0);

  /* Failure can either be some kind of I/O disaster (including timeout),
  or the failure of a transport filter or the expansion of added headers. */

  if (!ok)
    {
    buffer[0] = 0;              /* There hasn't been a response */
    goto RESPONSE_FAILED;
    }

  Ustrcpy(big_buffer, "end of data");   /* For error messages */

  /* We now expect a response for every address that was accepted above,
  in the same order. For those that get a response, their status is fixed;
  any that are accepted have been handed over, even if later responses crash -
  at least, that's how I read RFC 2033. */

  for (addr = addrlist; addr != NULL; addr = addr->next)
    {
    if (addr->transport_return != PENDING_OK) continue;

    if (lmtp_read_response(out, buffer, sizeof(buffer), '2', timeout))
      {
      addr->transport_return = OK;
      if (LOGGING(smtp_confirmation))
        {
        const uschar *s = string_printing(buffer);
	/* de-const safe here as string_printing known to have alloc'n'copied */
        addr->message = (s == buffer)? US string_copy(s) : US s;
        }
      }
    /* If the response has failed badly, use it for all the remaining pending
    addresses and give up. */

    else if (errno != 0 || buffer[0] == 0)
      {
      address_item *a;
      save_errno = errno;
      check_response(&save_errno, addr->more_errno, buffer, &code,
        &(addr->message));
      addr->transport_return = (code == '5')? FAIL : DEFER;
      for (a = addr->next; a != NULL; a = a->next)
        {
        if (a->transport_return != PENDING_OK) continue;
        a->basic_errno = addr->basic_errno;
        a->message = addr->message;
        a->transport_return = addr->transport_return;
        }
      break;
      }

    /* Otherwise, it's an LMTP error code return for one address */

    else
      {
      if (buffer[0] == '4')
        {
        addr->basic_errno = ERRNO_DATA4XX;
        addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
        }
      addr->message = string_sprintf("LMTP error after %s: %s", big_buffer,
        string_printing(buffer));
      addr->transport_return = (buffer[0] == '5')? FAIL : DEFER;
      setflag(addr, af_pass_message);   /* Allow message to go to user */
      }
    }
  }

/* The message transaction has completed successfully - this doesn't mean that
all the addresses have necessarily been transferred, but each has its status
set, so we change the yield to TRUE. */

yield = TRUE;
(void) lmtp_write_command(fd_in, "QUIT\r\n");
(void) lmtp_read_response(out, buffer, sizeof(buffer), '2', 1);

goto RETURN;


/* Come here if any call to read_response, other than a response after the data
phase, failed. Put the error in the top address - this will be replicated
because the yield is still FALSE. (But omit ETIMEDOUT, as there will already be
a suitable message.) Analyse the error, and if if isn't too bad, send a QUIT
command. Wait for the response with a short timeout, so we don't wind up this
process before the far end has had time to read the QUIT. */

RESPONSE_FAILED:

save_errno = errno;
if (errno != ETIMEDOUT && errno != 0) addrlist->basic_errno = errno;
addrlist->message = NULL;

if (check_response(&save_errno, addrlist->more_errno,
    buffer, &code, &(addrlist->message)))
  {
  (void) lmtp_write_command(fd_in, "QUIT\r\n");
  (void) lmtp_read_response(out, buffer, sizeof(buffer), '2', 1);
  }

addrlist->transport_return = (code == '5')? FAIL : DEFER;
if (code == '4' && save_errno > 0)
  addrlist->message = string_sprintf("%s: %s", addrlist->message,
    strerror(save_errno));
goto KILL_AND_RETURN;

/* Come here if there are errors during writing of a command or the message
itself. This error will be applied to all the addresses. */

WRITE_FAILED:

addrlist->transport_return = PANIC;
addrlist->basic_errno = errno;
if (errno == ERRNO_CHHEADER_FAIL)
  addrlist->message =
    string_sprintf("Failed to expand headers_add or headers_remove: %s",
      expand_string_message);
else if (errno == ERRNO_FILTER_FAIL)
  addrlist->message = string_sprintf("Filter process failure");
else if (errno == ERRNO_WRITEINCOMPLETE)
  addrlist->message = string_sprintf("Failed repeatedly to write data");
else if (errno == ERRNO_SMTPFORMAT)
  addrlist->message = US"overlong LMTP command generated";
else
  addrlist->message = string_sprintf("Error %d", errno);

/* Come here after errors. Kill off the process. */

KILL_AND_RETURN:

if (pid > 0) killpg(pid, SIGKILL);

/* Come here from all paths after the subprocess is created. Wait for the
process, but with a timeout. */

RETURN:

(void)child_close(pid, timeout);

if (fd_in >= 0) (void)close(fd_in);
if (fd_out >= 0) (void)fclose(out);

DEBUG(D_transport)
  debug_printf("%s transport yields %d\n", tblock->name, yield);

return yield;


MINUS_N:
  DEBUG(D_transport)
    debug_printf("*** delivery by %s transport bypassed by -N option",
      tblock->name);
  addrlist->transport_return = OK;
  return FALSE;
}

Project: exim

set_file_path(void)
{
int sep = ':';              /* Fixed separator - outside use */
uschar *t;
const uschar *tt = US LOG_FILE_PATH;
while ((t = string_nextinlist(&tt, &sep, log_buffer, LOG_BUFFER_SIZE)))
  {
  if (Ustrcmp(t, "syslog") == 0 || t[0] == 0) continue;
  file_path = string_copy(t);
  break;
  }
}