Esempio n. 1
0
static int
perform_redis_search(const uschar *command, uschar *server, uschar **resultptr,
  uschar **errmsg, BOOL *defer_break, uint *do_cache)
{
redisContext *redis_handle = NULL;        /* Keep compilers happy */
redisReply *redis_reply = NULL;
redisReply *entry = NULL;
redisReply *tentry = NULL;
redis_connection *cn;
int ssize = 0;
int offset = 0;
int yield = DEFER;
int i, j;
uschar *result = NULL;
uschar *server_copy = NULL;
uschar *tmp, *ttmp;
uschar *sdata[3];

/* Disaggregate the parameters from the server argument.
The order is host:port(socket)
We can write to the string, since it is in a nextinlist temporary buffer.
This copy is also used for debugging output.  */

memset(sdata, 0, sizeof(sdata)) /* Set all to NULL */;
for (i = 2; i > 0; i--)
  {
  uschar *pp = Ustrrchr(server, '/');

  if (!pp)
    {
    *errmsg = string_sprintf("incomplete Redis server data: %s",
      i == 2 ? server : server_copy);
    *defer_break = TRUE;
    return DEFER;
    }
  *pp++ = 0;
  sdata[i] = pp;
  if (i == 2) server_copy = string_copy(server);  /* sans password */
  }
sdata[0] = server;   /* What's left at the start */

/* If the database or password is an empty string, set it NULL */
if (sdata[1][0] == 0) sdata[1] = NULL;
if (sdata[2][0] == 0) sdata[2] = NULL;

/* See if we have a cached connection to the server */

for (cn = redis_connections; cn; cn = cn->next)
  if (Ustrcmp(cn->server, server_copy) == 0)
    {
    redis_handle = cn->handle;
    break;
    }

if (!cn)
  {
  uschar *p;
  uschar *socket = NULL;
  int port = 0;
  /* int redis_err = REDIS_OK; */

  if ((p = Ustrchr(sdata[0], '(')))
    {
    *p++ = 0;
    socket = p;
    while (*p != 0 && *p != ')') p++;
    *p = 0;
    }

  if ((p = Ustrchr(sdata[0], ':')))
    {
    *p++ = 0;
    port = Uatoi(p);
    }
  else
    port = Uatoi("6379");

  if (Ustrchr(server, '/'))
    {
    *errmsg = string_sprintf("unexpected slash in Redis server hostname: %s",
      sdata[0]);
    *defer_break = TRUE;
    return DEFER;
    }

  DEBUG(D_lookup)
    debug_printf("REDIS new connection: host=%s port=%d socket=%s database=%s\n",
      sdata[0], port, socket, sdata[1]);

  /* Get store for a new handle, initialize it, and connect to the server */
  /* XXX: Use timeouts ? */
  redis_handle =
    socket ? redisConnectUnix(CCS socket) : redisConnect(CCS server, port);
  if (!redis_handle)
    {
    *errmsg = string_sprintf("REDIS connection failed");
    *defer_break = FALSE;
    goto REDIS_EXIT;
    }

  /* Add the connection to the cache */
  cn = store_get(sizeof(redis_connection));
  cn->server = server_copy;
  cn->handle = redis_handle;
  cn->next = redis_connections;
  redis_connections = cn;
  }
else
  {
  DEBUG(D_lookup)
    debug_printf("REDIS using cached connection for %s\n", server_copy);
}

/* Authenticate if there is a password */
if(sdata[2])
  if (!(redis_reply = redisCommand(redis_handle, "AUTH %s", sdata[2])))
    {
    *errmsg = string_sprintf("REDIS Authentication failed: %s\n", redis_handle->errstr);
    *defer_break = FALSE;
    goto REDIS_EXIT;
    }

/* Select the database if there is a dbnumber passed */
if(sdata[1])
  {
  if (!(redis_reply = redisCommand(redis_handle, "SELECT %s", sdata[1])))
    {
    *errmsg = string_sprintf("REDIS: Selecting database=%s failed: %s\n", sdata[1], redis_handle->errstr);
    *defer_break = FALSE;
    goto REDIS_EXIT;
    }
  DEBUG(D_lookup) debug_printf("REDIS: Selecting database=%s\n", sdata[1]);
  }

/* split string on whitespace into argv */
  {
  uschar * argv[32];
  int i;
  const uschar * s = command;
  int siz, ptr;
  uschar c;

  while (isspace(*s)) s++;

  for (i = 0; *s && i < nele(argv); i++)
    {
    for (argv[i] = NULL, siz = ptr = 0; (c = *s) && !isspace(c); s++)
      if (c != '\\' || *++s)		/* backslash protects next char */
	argv[i] = string_cat(argv[i], &siz, &ptr, s, 1);
    *(argv[i]+ptr) = '\0';
    DEBUG(D_lookup) debug_printf("REDIS: argv[%d] '%s'\n", i, argv[i]);
    while (isspace(*s)) s++;
    }

  /* Run the command. We use the argv form rather than plain as that parses
  into args by whitespace yet has no escaping mechanism. */

  redis_reply = redisCommandArgv(redis_handle, i, (const char **) argv, NULL);
  if (!redis_reply)
    {
    *errmsg = string_sprintf("REDIS: query failed: %s\n", redis_handle->errstr);
    *defer_break = FALSE;
    goto REDIS_EXIT;
    }
  }

switch (redis_reply->type)
  {
  case REDIS_REPLY_ERROR:
    *errmsg = string_sprintf("REDIS: lookup result failed: %s\n", redis_reply->str);
    *defer_break = FALSE;
    *do_cache = 0;
    goto REDIS_EXIT;
    /* NOTREACHED */

  case REDIS_REPLY_NIL:
    DEBUG(D_lookup)
      debug_printf("REDIS: query was not one that returned any data\n");
    result = string_sprintf("");
    *do_cache = 0;
    goto REDIS_EXIT;
    /* NOTREACHED */

  case REDIS_REPLY_INTEGER:
    ttmp = (redis_reply->integer != 0) ? US"true" : US"false";
    result = string_cat(result, &ssize, &offset, US ttmp, Ustrlen(ttmp));
    break;

  case REDIS_REPLY_STRING:
  case REDIS_REPLY_STATUS:
    result = string_cat(result, &ssize, &offset,
			US redis_reply->str, redis_reply->len);
    break;

  case REDIS_REPLY_ARRAY:
 
    /* NOTE: For now support 1 nested array result. If needed a limitless
    result can be parsed */

    for (i = 0; i < redis_reply->elements; i++)
      {
      entry = redis_reply->element[i];

      if (result)
	result = string_cat(result, &ssize, &offset, US"\n", 1);

      switch (entry->type)
	{
	case REDIS_REPLY_INTEGER:
	  tmp = string_sprintf("%d", entry->integer);
	  result = string_cat(result, &ssize, &offset, US tmp, Ustrlen(tmp));
	  break;
	case REDIS_REPLY_STRING:
	  result = string_cat(result, &ssize, &offset,
			      US entry->str, entry->len);
	  break;
	case REDIS_REPLY_ARRAY:
	  for (j = 0; j < entry->elements; j++)
	    {
	    tentry = entry->element[j];

	    if (result)
	      result = string_cat(result, &ssize, &offset, US"\n", 1);

	    switch (tentry->type)
	      {
	      case REDIS_REPLY_INTEGER:
		ttmp = string_sprintf("%d", tentry->integer);
		result = string_cat(result, &ssize, &offset,
				    US ttmp, Ustrlen(ttmp));
		break;
	      case REDIS_REPLY_STRING:
		result = string_cat(result, &ssize, &offset,
				    US tentry->str, tentry->len);
		break;
	      case REDIS_REPLY_ARRAY:
		DEBUG(D_lookup)
		  debug_printf("REDIS: result has nesting of arrays which"
		    " is not supported. Ignoring!\n");
		break;
	      default:
		DEBUG(D_lookup) debug_printf(
			  "REDIS: result has unsupported type. Ignoring!\n");
		break;
	      }
	    }
	    break;
	  default:
	    DEBUG(D_lookup) debug_printf("REDIS: query returned unsupported type\n");
	    break;
	  }
	}
      break;
  }


if (result)
  {
  result[offset] = 0;
  store_reset(result + offset + 1);
  }
else
  {
  yield = FAIL;
  *errmsg = US"REDIS: no data found";
  }

REDIS_EXIT:

/* Free store for any result that was got; don't close the connection,
as it is cached. */

if (redis_reply) freeReplyObject(redis_reply);

/* Non-NULL result indicates a sucessful result */

if (result)
  {
  *resultptr = result;
  return OK;
  }
else
  {
  DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
  /* NOTE: Required to close connection since it needs to be reopened */
  return yield;      /* FAIL or DEFER */
  }
}
Esempio n. 2
0
File: dns.c Progetto: Chaohua/exim
static int
fakens_search(const uschar *domain, int type, uschar *answerptr, int size)
{
int len = Ustrlen(domain);
int asize = size;                  /* Locally modified */
uschar *endname;
uschar name[256];
uschar utilname[256];
uschar *aptr = answerptr;          /* Locally modified */
struct stat statbuf;

/* Remove terminating dot. */

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

return res_search(CS domain, C_IN, type, answerptr, size);
}
Esempio n. 3
0
int
spool_read_header(uschar *name, BOOL read_headers, BOOL subdir_set)
{
FILE *f = NULL;
int n;
int rcount = 0;
long int uid, gid;
BOOL inheader = FALSE;
uschar *p;

/* Reset all the global variables to their default values. However, there is
one exception. DO NOT change the default value of dont_deliver, because it may
be forced by an external setting. */

acl_var_c = acl_var_m = NULL;
authenticated_id = NULL;
authenticated_sender = NULL;
allow_unqualified_recipient = FALSE;
allow_unqualified_sender = FALSE;
body_linecount = 0;
body_zerocount = 0;
deliver_firsttime = FALSE;
deliver_freeze = FALSE;
deliver_frozen_at = 0;
deliver_manual_thaw = FALSE;
/* dont_deliver must NOT be reset */
header_list = header_last = NULL;
host_lookup_deferred = FALSE;
host_lookup_failed = FALSE;
interface_address = NULL;
interface_port = 0;
local_error_message = FALSE;
local_scan_data = NULL;
max_received_linelength = 0;
message_linecount = 0;
received_protocol = NULL;
received_count = 0;
recipients_list = NULL;
sender_address = NULL;
sender_fullhost = NULL;
sender_helo_name = NULL;
sender_host_address = NULL;
sender_host_name = NULL;
sender_host_port = 0;
sender_host_authenticated = NULL;
sender_ident = NULL;
sender_local = FALSE;
sender_set_untrusted = FALSE;
smtp_active_hostname = primary_hostname;
tree_nonrecipients = NULL;

#ifdef EXPERIMENTAL_BRIGHTMAIL
bmi_run = 0;
bmi_verdicts = NULL;
#endif

#ifndef DISABLE_DKIM
dkim_signers = NULL;
dkim_disable_verify = FALSE;
dkim_collect_input = FALSE;
#endif

#ifdef SUPPORT_TLS
tls_in.certificate_verified = FALSE;
# ifdef EXPERIMENTAL_DANE
tls_in.dane_verified = FALSE;
# endif
tls_in.cipher = NULL;
# ifndef COMPILE_UTILITY	/* tls support fns not built in */
tls_free_cert(&tls_in.ourcert);
tls_free_cert(&tls_in.peercert);
# endif
tls_in.peerdn = NULL;
tls_in.sni = NULL;
tls_in.ocsp = OCSP_NOT_REQ;
#endif

#ifdef WITH_CONTENT_SCAN
spam_bar = NULL;
spam_score = NULL;
spam_score_int = NULL;
#endif

#if defined(SUPPORT_I18N) && !defined(COMPILE_UTILITY)
message_smtputf8 = FALSE;
message_utf8_downconvert = 0;
#endif

dsn_ret = 0;
dsn_envid = NULL;

/* Generate the full name and open the file. If message_subdir is already
set, just look in the given directory. Otherwise, look in both the split
and unsplit directories, as for the data file above. */

for (n = 0; n < 2; n++)
  {
  if (!subdir_set)
    message_subdir[0] = (split_spool_directory == (n == 0))? name[5] : 0;
  sprintf(CS big_buffer, "%s/input/%s/%s", spool_directory, message_subdir,
    name);
  f = Ufopen(big_buffer, "rb");
  if (f != NULL) break;
  if (n != 0 || subdir_set || errno != ENOENT) return spool_read_notopen;
  }

errno = 0;

#ifndef COMPILE_UTILITY
DEBUG(D_deliver) debug_printf("reading spool file %s\n", name);
#endif  /* COMPILE_UTILITY */

/* The first line of a spool file contains the message id followed by -H (i.e.
the file name), in order to make the file self-identifying. */

if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
if (Ustrlen(big_buffer) != MESSAGE_ID_LENGTH + 3 ||
    Ustrncmp(big_buffer, name, MESSAGE_ID_LENGTH + 2) != 0)
  goto SPOOL_FORMAT_ERROR;

/* The next three lines in the header file are in a fixed format. The first
contains the login, uid, and gid of the user who caused the file to be written.
There are known cases where a negative gid is used, so we allow for both
negative uids and gids. The second contains the mail address of the message's
sender, enclosed in <>. The third contains the time the message was received,
and the number of warning messages for delivery delays that have been sent. */

if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;

p = big_buffer + Ustrlen(big_buffer);
while (p > big_buffer && isspace(p[-1])) p--;
*p = 0;
if (!isdigit(p[-1])) goto SPOOL_FORMAT_ERROR;
while (p > big_buffer && (isdigit(p[-1]) || '-' == p[-1])) p--;
gid = Uatoi(p);
if (p <= big_buffer || *(--p) != ' ') goto SPOOL_FORMAT_ERROR;
*p = 0;
if (!isdigit(p[-1])) goto SPOOL_FORMAT_ERROR;
while (p > big_buffer && (isdigit(p[-1]) || '-' == p[-1])) p--;
uid = Uatoi(p);
if (p <= big_buffer || *(--p) != ' ') goto SPOOL_FORMAT_ERROR;
*p = 0;

originator_login = string_copy(big_buffer);
originator_uid = (uid_t)uid;
originator_gid = (gid_t)gid;

/* envelope from */
if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
n = Ustrlen(big_buffer);
if (n < 3 || big_buffer[0] != '<' || big_buffer[n-2] != '>')
  goto SPOOL_FORMAT_ERROR;

sender_address = store_get(n-2);
Ustrncpy(sender_address, big_buffer+1, n-3);
sender_address[n-3] = 0;

/* time */
if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
if (sscanf(CS big_buffer, "%d %d", &received_time, &warning_count) != 2)
  goto SPOOL_FORMAT_ERROR;

message_age = time(NULL) - received_time;

#ifndef COMPILE_UTILITY
DEBUG(D_deliver) debug_printf("user=%s uid=%ld gid=%ld sender=%s\n",
  originator_login, (long int)originator_uid, (long int)originator_gid,
  sender_address);
#endif  /* COMPILE_UTILITY */

/* Now there may be a number of optional lines, each starting with "-". If you
add a new setting here, make sure you set the default above.

Because there are now quite a number of different possibilities, we use a
switch on the first character to avoid too many failing tests. Thanks to Nico
Erfurth for the patch that implemented this. I have made it even more efficient
by not re-scanning the first two characters.

To allow new versions of Exim that add additional flags to interwork with older
versions that do not understand them, just ignore any lines starting with "-"
that we don't recognize. Otherwise it wouldn't be possible to back off a new
version that left new-style flags written on the spool. */

p = big_buffer + 2;
for (;;)
  {
  int len;
  if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
  if (big_buffer[0] != '-') break;
  while (  (len = Ustrlen(big_buffer)) == big_buffer_size-1
	&& big_buffer[len-1] != '\n'
	)
    {	/* buffer not big enough for line; certs make this possible */
    uschar * buf;
    if (big_buffer_size >= BIG_BUFFER_SIZE*4) goto SPOOL_READ_ERROR;
    buf = store_get_perm(big_buffer_size *= 2);
    memcpy(buf, big_buffer, --len);
    big_buffer = buf;
    if (Ufgets(big_buffer+len, big_buffer_size-len, f) == NULL)
      goto SPOOL_READ_ERROR;
    }
  big_buffer[len-1] = 0;

  switch(big_buffer[1])
    {
    case 'a':

    /* Nowadays we use "-aclc" and "-aclm" for the different types of ACL
    variable, because Exim allows any number of them, with arbitrary names.
    The line in the spool file is "-acl[cm] <name> <length>". The name excludes
    the c or m. */

    if (Ustrncmp(p, "clc ", 4) == 0 ||
        Ustrncmp(p, "clm ", 4) == 0)
      {
      uschar *name, *endptr;
      int count;
      tree_node *node;
      endptr = Ustrchr(big_buffer + 6, ' ');
      if (endptr == NULL) goto SPOOL_FORMAT_ERROR;
      name = string_sprintf("%c%.*s", big_buffer[4], endptr - big_buffer - 6,
        big_buffer + 6);
      if (sscanf(CS endptr, " %d", &count) != 1) goto SPOOL_FORMAT_ERROR;
      node = acl_var_create(name);
      node->data.ptr = store_get(count + 1);
      if (fread(node->data.ptr, 1, count+1, f) < count) goto SPOOL_READ_ERROR;
      ((uschar*)node->data.ptr)[count] = 0;
      }

    else if (Ustrcmp(p, "llow_unqualified_recipient") == 0)
      allow_unqualified_recipient = TRUE;
    else if (Ustrcmp(p, "llow_unqualified_sender") == 0)
      allow_unqualified_sender = TRUE;

    else if (Ustrncmp(p, "uth_id", 6) == 0)
      authenticated_id = string_copy(big_buffer + 9);
    else if (Ustrncmp(p, "uth_sender", 10) == 0)
      authenticated_sender = string_copy(big_buffer + 13);
    else if (Ustrncmp(p, "ctive_hostname", 14) == 0)
      smtp_active_hostname = string_copy(big_buffer + 17);

    /* For long-term backward compatibility, we recognize "-acl", which was
    used before the number of ACL variables changed from 10 to 20. This was
    before the subsequent change to an arbitrary number of named variables.
    This code is retained so that upgrades from very old versions can still
    handle old-format spool files. The value given after "-acl" is a number
    that is 0-9 for connection variables, and 10-19 for message variables. */

    else if (Ustrncmp(p, "cl ", 3) == 0)
      {
      int index, count;
      uschar name[20];   /* Need plenty of space for %d format */
      tree_node *node;
      if (  sscanf(CS big_buffer + 5, "%d %d", &index, &count) != 2
	 || index >= 20
         )
        goto SPOOL_FORMAT_ERROR;
      if (index < 10)
        (void) string_format(name, sizeof(name), "%c%d", 'c', index);
      else
        (void) string_format(name, sizeof(name), "%c%d", 'm', index - 10);
      node = acl_var_create(name);
      node->data.ptr = store_get(count + 1);
      if (fread(node->data.ptr, 1, count+1, f) < count) goto SPOOL_READ_ERROR;
      ((uschar*)node->data.ptr)[count] = 0;
      }
    break;

    case 'b':
    if (Ustrncmp(p, "ody_linecount", 13) == 0)
      body_linecount = Uatoi(big_buffer + 15);
    else if (Ustrncmp(p, "ody_zerocount", 13) == 0)
      body_zerocount = Uatoi(big_buffer + 15);
#ifdef EXPERIMENTAL_BRIGHTMAIL
    else if (Ustrncmp(p, "mi_verdicts ", 12) == 0)
      bmi_verdicts = string_copy(big_buffer + 14);
#endif
    break;

    case 'd':
    if (Ustrcmp(p, "eliver_firsttime") == 0)
      deliver_firsttime = TRUE;
    /* Check if the dsn flags have been set in the header file */
    else if (Ustrncmp(p, "sn_ret", 6) == 0)
      dsn_ret= atoi(CS big_buffer + 8);
    else if (Ustrncmp(p, "sn_envid", 8) == 0)
      dsn_envid = string_copy(big_buffer + 11);
    break;

    case 'f':
    if (Ustrncmp(p, "rozen", 5) == 0)
      {
      deliver_freeze = TRUE;
      if (sscanf(CS big_buffer+7, TIME_T_FMT, &deliver_frozen_at) != 1)
	goto SPOOL_READ_ERROR;
      }
    break;

    case 'h':
    if (Ustrcmp(p, "ost_lookup_deferred") == 0)
      host_lookup_deferred = TRUE;
    else if (Ustrcmp(p, "ost_lookup_failed") == 0)
      host_lookup_failed = TRUE;
    else if (Ustrncmp(p, "ost_auth", 8) == 0)
      sender_host_authenticated = string_copy(big_buffer + 11);
    else if (Ustrncmp(p, "ost_name", 8) == 0)
      sender_host_name = string_copy(big_buffer + 11);
    else if (Ustrncmp(p, "elo_name", 8) == 0)
      sender_helo_name = string_copy(big_buffer + 11);

    /* We now record the port number after the address, separated by a
    dot. For compatibility during upgrading, do nothing if there
    isn't a value (it gets left at zero). */

    else if (Ustrncmp(p, "ost_address", 11) == 0)
      {
      sender_host_port = host_address_extract_port(big_buffer + 14);
      sender_host_address = string_copy(big_buffer + 14);
      }
    break;

    case 'i':
    if (Ustrncmp(p, "nterface_address", 16) == 0)
      {
      interface_port = host_address_extract_port(big_buffer + 19);
      interface_address = string_copy(big_buffer + 19);
      }
    else if (Ustrncmp(p, "dent", 4) == 0)
      sender_ident = string_copy(big_buffer + 7);
    break;

    case 'l':
    if (Ustrcmp(p, "ocal") == 0) sender_local = TRUE;
    else if (Ustrcmp(big_buffer, "-localerror") == 0)
      local_error_message = TRUE;
    else if (Ustrncmp(p, "ocal_scan ", 10) == 0)
      local_scan_data = string_copy(big_buffer + 12);
    break;

    case 'm':
    if (Ustrcmp(p, "anual_thaw") == 0) deliver_manual_thaw = TRUE;
    else if (Ustrncmp(p, "ax_received_linelength", 22) == 0)
      max_received_linelength = Uatoi(big_buffer + 24);
    break;

    case 'N':
    if (*p == 0) dont_deliver = TRUE;   /* -N */
    break;

    case 'r':
    if (Ustrncmp(p, "eceived_protocol", 16) == 0)
      received_protocol = string_copy(big_buffer + 19);
    break;

    case 's':
    if (Ustrncmp(p, "ender_set_untrusted", 19) == 0)
      sender_set_untrusted = TRUE;
#ifdef WITH_CONTENT_SCAN
    else if (Ustrncmp(p, "pam_bar ", 8) == 0)
      spam_bar = string_copy(big_buffer + 10);
    else if (Ustrncmp(p, "pam_score ", 10) == 0)
      spam_score = string_copy(big_buffer + 12);
    else if (Ustrncmp(p, "pam_score_int ", 14) == 0)
      spam_score_int = string_copy(big_buffer + 16);
#endif
#if defined(SUPPORT_I18N) && !defined(COMPILE_UTILITY)
    else if (Ustrncmp(p, "mtputf8", 7) == 0)
      message_smtputf8 = TRUE;
#endif
    break;

#ifdef SUPPORT_TLS
    case 't':
    if (Ustrncmp(p, "ls_certificate_verified", 23) == 0)
      tls_in.certificate_verified = TRUE;
    else if (Ustrncmp(p, "ls_cipher", 9) == 0)
      tls_in.cipher = string_copy(big_buffer + 12);
# ifndef COMPILE_UTILITY	/* tls support fns not built in */
    else if (Ustrncmp(p, "ls_ourcert", 10) == 0)
      (void) tls_import_cert(big_buffer + 13, &tls_in.ourcert);
    else if (Ustrncmp(p, "ls_peercert", 11) == 0)
      (void) tls_import_cert(big_buffer + 14, &tls_in.peercert);
# endif
    else if (Ustrncmp(p, "ls_peerdn", 9) == 0)
      tls_in.peerdn = string_unprinting(string_copy(big_buffer + 12));
    else if (Ustrncmp(p, "ls_sni", 6) == 0)
      tls_in.sni = string_unprinting(string_copy(big_buffer + 9));
    else if (Ustrncmp(p, "ls_ocsp", 7) == 0)
      tls_in.ocsp = big_buffer[10] - '0';
    break;
#endif

#if defined(SUPPORT_I18N) && !defined(COMPILE_UTILITY)
    case 'u':
    if (Ustrncmp(p, "tf8_downcvt", 11) == 0)
      message_utf8_downconvert = 1;
    else if (Ustrncmp(p, "tf8_optdowncvt", 15) == 0)
      message_utf8_downconvert = -1;
    break;
#endif

    default:    /* Present because some compilers complain if all */
    break;      /* possibilities are not covered. */
    }
  }

/* Build sender_fullhost if required */

#ifndef COMPILE_UTILITY
host_build_sender_fullhost();
#endif  /* COMPILE_UTILITY */

#ifndef COMPILE_UTILITY
DEBUG(D_deliver)
  debug_printf("sender_local=%d ident=%s\n", sender_local,
    (sender_ident == NULL)? US"unset" : sender_ident);
#endif  /* COMPILE_UTILITY */

/* We now have the tree of addresses NOT to deliver to, or a line
containing "XX", indicating no tree. */

if (Ustrncmp(big_buffer, "XX\n", 3) != 0 &&
  !read_nonrecipients_tree(&tree_nonrecipients, f, big_buffer, big_buffer_size))
    goto SPOOL_FORMAT_ERROR;

#ifndef COMPILE_UTILITY
DEBUG(D_deliver)
  {
  debug_printf("Non-recipients:\n");
  debug_print_tree(tree_nonrecipients);
  }
#endif  /* COMPILE_UTILITY */

/* After reading the tree, the next line has not yet been read into the
buffer. It contains the count of recipients which follow on separate lines. */

if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
if (sscanf(CS big_buffer, "%d", &rcount) != 1) goto SPOOL_FORMAT_ERROR;

#ifndef COMPILE_UTILITY
DEBUG(D_deliver) debug_printf("recipients_count=%d\n", rcount);
#endif  /* COMPILE_UTILITY */

recipients_list_max = rcount;
recipients_list = store_get(rcount * sizeof(recipient_item));

for (recipients_count = 0; recipients_count < rcount; recipients_count++)
  {
  int nn;
  int pno = -1;
  int dsn_flags = 0;
  uschar *orcpt = NULL;
  uschar *errors_to = NULL;
  uschar *p;

  if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
  nn = Ustrlen(big_buffer);
  if (nn < 2) goto SPOOL_FORMAT_ERROR;

  /* Remove the newline; this terminates the address if there is no additional
  data on the line. */

  p = big_buffer + nn - 1;
  *p-- = 0;

  /* Look back from the end of the line for digits and special terminators.
  Since an address must end with a domain, we can tell that extra data is
  present by the presence of the terminator, which is always some character
  that cannot exist in a domain. (If I'd thought of the need for additional
  data early on, I'd have put it at the start, with the address at the end. As
  it is, we have to operate backwards. Addresses are permitted to contain
  spaces, you see.)

  This code has to cope with various versions of this data that have evolved
  over time. In all cases, the line might just contain an address, with no
  additional data. Otherwise, the possibilities are as follows:

  Exim 3 type:       <address><space><digits>,<digits>,<digits>

    The second set of digits is the parent number for one_time addresses. The
    other values were remnants of earlier experiments that were abandoned.

  Exim 4 first type: <address><space><digits>

    The digits are the parent number for one_time addresses.

  Exim 4 new type:   <address><space><data>#<type bits>

    The type bits indicate what the contents of the data are.

    Bit 01 indicates that, reading from right to left, the data
      ends with <errors_to address><space><len>,<pno> where pno is
      the parent number for one_time addresses, and len is the length
      of the errors_to address (zero meaning none).

    Bit 02 indicates that, again reading from right to left, the data continues
     with orcpt len(orcpt),dsn_flags
   */

  while (isdigit(*p)) p--;

  /* Handle Exim 3 spool files */

  if (*p == ',')
    {
    int dummy;
    while (isdigit(*(--p)) || *p == ',');
    if (*p == ' ')
      {
      *p++ = 0;
      (void)sscanf(CS p, "%d,%d", &dummy, &pno);
      }
    }

  /* Handle early Exim 4 spool files */

  else if (*p == ' ')
    {
    *p++ = 0;
    (void)sscanf(CS p, "%d", &pno);
    }

  /* Handle current format Exim 4 spool files */

  else if (*p == '#')
    {
    int flags;

#if !defined (COMPILE_UTILITY)
    DEBUG(D_deliver) debug_printf("**** SPOOL_IN - Exim 4 standard format spoolfile\n");
#endif

    (void)sscanf(CS p+1, "%d", &flags);

    if ((flags & 0x01) != 0)      /* one_time data exists */
      {
      int len;
      while (isdigit(*(--p)) || *p == ',' || *p == '-');
      (void)sscanf(CS p+1, "%d,%d", &len, &pno);
      *p = 0;
      if (len > 0)
        {
        p -= len;
        errors_to = string_copy(p);
        }
      }

    *(--p) = 0;   /* Terminate address */
    if ((flags & 0x02) != 0)      /* one_time data exists */
      {
      int len;
      while (isdigit(*(--p)) || *p == ',' || *p == '-');
      (void)sscanf(CS p+1, "%d,%d", &len, &dsn_flags);
      *p = 0;
      if (len > 0)
        {
        p -= len;
        orcpt = string_copy(p);
        }
      }

    *(--p) = 0;   /* Terminate address */
    }
#if !defined(COMPILE_UTILITY)
  else
    { DEBUG(D_deliver) debug_printf("**** SPOOL_IN - No additional fields\n"); }

  if ((orcpt != NULL) || (dsn_flags != 0))
    {
    DEBUG(D_deliver) debug_printf("**** SPOOL_IN - address: |%s| orcpt: |%s| dsn_flags: %d\n",
      big_buffer, orcpt, dsn_flags);
    }
  if (errors_to != NULL)
    {
    DEBUG(D_deliver) debug_printf("**** SPOOL_IN - address: |%s| errorsto: |%s|\n",
      big_buffer, errors_to);
    }
#endif

  recipients_list[recipients_count].address = string_copy(big_buffer);
  recipients_list[recipients_count].pno = pno;
  recipients_list[recipients_count].errors_to = errors_to;
  recipients_list[recipients_count].orcpt = orcpt;
  recipients_list[recipients_count].dsn_flags = dsn_flags;
  }

/* The remainder of the spool header file contains the headers for the message,
separated off from the previous data by a blank line. Each header is preceded
by a count of its length and either a certain letter (for various identified
headers), space (for a miscellaneous live header) or an asterisk (for a header
that has been rewritten). Count the Received: headers. We read the headers
always, in order to check on the format of the file, but only create a header
list if requested to do so. */

inheader = TRUE;
if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
if (big_buffer[0] != '\n') goto SPOOL_FORMAT_ERROR;

while ((n = fgetc(f)) != EOF)
  {
  header_line *h;
  uschar flag[4];
  int i;

  if (!isdigit(n)) goto SPOOL_FORMAT_ERROR;
  if(ungetc(n, f) == EOF  ||  fscanf(f, "%d%c ", &n, flag) == EOF)
    goto SPOOL_READ_ERROR;
  if (flag[0] != '*') message_size += n;  /* Omit non-transmitted headers */

  if (read_headers)
    {
    h = store_get(sizeof(header_line));
    h->next = NULL;
    h->type = flag[0];
    h->slen = n;
    h->text = store_get(n+1);

    if (h->type == htype_received) received_count++;

    if (header_list == NULL) header_list = h;
      else header_last->next = h;
    header_last = h;

    for (i = 0; i < n; i++)
      {
      int c = fgetc(f);
      if (c == 0 || c == EOF) goto SPOOL_FORMAT_ERROR;
      if (c == '\n' && h->type != htype_old) message_linecount++;
      h->text[i] = c;
      }
    h->text[i] = 0;
    }

  /* Not requiring header data, just skip through the bytes */

  else for (i = 0; i < n; i++)
    {
    int c = fgetc(f);
    if (c == 0 || c == EOF) goto SPOOL_FORMAT_ERROR;
    }
  }

/* We have successfully read the data in the header file. Update the message
line count by adding the body linecount to the header linecount. Close the file
and give a positive response. */

#ifndef COMPILE_UTILITY
DEBUG(D_deliver) debug_printf("body_linecount=%d message_linecount=%d\n",
  body_linecount, message_linecount);
#endif  /* COMPILE_UTILITY */

message_linecount += body_linecount;

fclose(f);
return spool_read_OK;


/* There was an error reading the spool or there was missing data,
or there was a format error. A "read error" with no errno means an
unexpected EOF, which we treat as a format error. */

SPOOL_READ_ERROR:
if (errno != 0)
  {
  n = errno;

#ifndef COMPILE_UTILITY
  DEBUG(D_any) debug_printf("Error while reading spool file %s\n", name);
#endif  /* COMPILE_UTILITY */

  fclose(f);
  errno = n;
  return inheader? spool_read_hdrerror : spool_read_enverror;
  }

SPOOL_FORMAT_ERROR:

#ifndef COMPILE_UTILITY
DEBUG(D_any) debug_printf("Format error in spool file %s\n", name);
#endif  /* COMPILE_UTILITY */

fclose(f);
errno = ERRNO_SPOOLFORMAT;
return inheader? spool_read_hdrerror : spool_read_enverror;
}
Esempio n. 4
0
File: pipe.c Progetto: akissa/exim
BOOL
pipe_transport_entry(
  transport_instance *tblock,      /* data for this instantiation */
  address_item *addr)              /* address(es) we are working on */
{
pid_t pid, outpid;
int fd_in, fd_out, rc;
int envcount = 0;
int envsep = 0;
int expand_fail;
pipe_transport_options_block *ob =
  (pipe_transport_options_block *)(tblock->options_block);
int timeout = ob->timeout;
BOOL written_ok = FALSE;
BOOL expand_arguments;
const uschar **argv;
uschar *envp[50];
const uschar *envlist = ob->environment;
uschar *cmd, *ss;
uschar *eol = (ob->use_crlf)? US"\r\n" : US"\n";

DEBUG(D_transport) debug_printf("%s transport entered\n", tblock->name);

/* Set up for the good case */

addr->transport_return = OK;
addr->basic_errno = 0;

/* Pipes are not accepted as general addresses, but they can be generated from
.forward files or alias files. In those cases, the pfr flag is set, and the
command to be obeyed is pointed to by addr->local_part; it starts with the pipe
symbol. In other cases, the command is supplied as one of the pipe transport's
options. */

if (testflag(addr, af_pfr) && addr->local_part[0] == '|')
  {
  if (ob->force_command)
    {
    /* Enables expansion of $address_pipe into seperate arguments */
    setflag(addr, af_force_command);
    cmd = ob->cmd;
    expand_arguments = TRUE;
    expand_fail = PANIC;
    }
  else
    {
    cmd = addr->local_part + 1;
    while (isspace(*cmd)) cmd++;
    expand_arguments = testflag(addr, af_expand_pipe);
    expand_fail = FAIL;
    }
  }
else
  {
  cmd = ob->cmd;
  expand_arguments = TRUE;
  expand_fail = PANIC;
  }

/* If no command has been supplied, we are in trouble.
 * We also check for an empty string since it may be
 * coming from addr->local_part[0] == '|'
 */

if (cmd == NULL || *cmd == '\0')
  {
  addr->transport_return = DEFER;
  addr->message = string_sprintf("no command specified for %s transport",
    tblock->name);
  return FALSE;
  }

/* When a pipe is set up by a filter file, there may be values for $thisaddress
and numerical the variables in existence. These are passed in
addr->pipe_expandn for use here. */

if (expand_arguments && addr->pipe_expandn != NULL)
  {
  uschar **ss = addr->pipe_expandn;
  expand_nmax = -1;
  if (*ss != NULL) filter_thisaddress = *ss++;
  while (*ss != NULL)
    {
    expand_nstring[++expand_nmax] = *ss;
    expand_nlength[expand_nmax] = Ustrlen(*ss++);
    }
  }

/* The default way of processing the command is to split it up into arguments
here, and run it directly. This offers some security advantages. However, there
are installations that want by default to run commands under /bin/sh always, so
there is an option to do that. */

if (ob->use_shell)
  {
  if (!set_up_shell_command(&argv, cmd, expand_arguments, expand_fail, addr,
    tblock->name)) return FALSE;
  }
else if (!set_up_direct_command(&argv, cmd, expand_arguments, expand_fail, addr,
  tblock->name, ob)) return FALSE;

expand_nmax = -1;           /* Reset */
filter_thisaddress = NULL;

/* Set up the environment for the command. */

envp[envcount++] = string_sprintf("LOCAL_PART=%s", deliver_localpart);
envp[envcount++] = string_sprintf("LOGNAME=%s", deliver_localpart);
envp[envcount++] = string_sprintf("USER=%s", deliver_localpart);
envp[envcount++] = string_sprintf("LOCAL_PART_PREFIX=%#s",
  deliver_localpart_prefix);
envp[envcount++] = string_sprintf("LOCAL_PART_SUFFIX=%#s",
  deliver_localpart_suffix);
envp[envcount++] = string_sprintf("DOMAIN=%s", deliver_domain);
envp[envcount++] = string_sprintf("HOME=%#s", deliver_home);
envp[envcount++] = string_sprintf("MESSAGE_ID=%s", message_id);
envp[envcount++] = string_sprintf("PATH=%s", ob->path);
envp[envcount++] = string_sprintf("RECIPIENT=%#s%#s%#s@%#s",
  deliver_localpart_prefix, deliver_localpart, deliver_localpart_suffix,
  deliver_domain);
envp[envcount++] = string_sprintf("QUALIFY_DOMAIN=%s", qualify_domain_sender);
envp[envcount++] = string_sprintf("SENDER=%s", sender_address);
envp[envcount++] = US"SHELL=/bin/sh";

if (addr->host_list != NULL)
  envp[envcount++] = string_sprintf("HOST=%s", addr->host_list->name);

if (timestamps_utc) envp[envcount++] = US"TZ=UTC";
else if (timezone_string != NULL && timezone_string[0] != 0)
  envp[envcount++] = string_sprintf("TZ=%s", timezone_string);

/* Add any requested items */

if (envlist)
  {
  envlist = expand_cstring(envlist);
  if (envlist == NULL)
    {
    addr->transport_return = DEFER;
    addr->message = string_sprintf("failed to expand string \"%s\" "
      "for %s transport: %s", ob->environment, tblock->name,
      expand_string_message);
    return FALSE;
    }
  }

while ((ss = string_nextinlist(&envlist, &envsep, big_buffer, big_buffer_size))
       != NULL)
   {
   if (envcount > sizeof(envp)/sizeof(uschar *) - 2)
     {
     addr->transport_return = DEFER;
     addr->message = string_sprintf("too many environment settings for "
       "%s transport", tblock->name);
     return FALSE;
     }
   envp[envcount++] = string_copy(ss);
   }

envp[envcount] = NULL;

/* If the -N option is set, can't do any more. */

if (dont_deliver)
  {
  DEBUG(D_transport)
    debug_printf("*** delivery by %s transport bypassed by -N option",
      tblock->name);
  return FALSE;
  }


/* Handling the output from the pipe is tricky. If a file for catching this
output is provided, we could in theory just hand that fd over to the process,
but this isn't very safe because it might loop and carry on writing for
ever (which is exactly what happened in early versions of Exim). Therefore we
use the standard child_open() function, which creates pipes. We can then read
our end of the output pipe and count the number of bytes that come through,
chopping the sub-process if it exceeds some limit.

However, this means we want to run a sub-process with both its input and output
attached to pipes. We can't handle that easily from a single parent process
using straightforward code such as the transport_write_message() function
because the subprocess might not be reading its input because it is trying to
write to a full output pipe. The complication of redesigning the world to
handle this is too great - simpler just to run another process to do the
reading of the output pipe. */


/* 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 a timeout. */

if ((pid = child_open(USS argv, envp, ob->umask, &fd_in, &fd_out, TRUE)) < 0)
  {
  addr->transport_return = DEFER;
  addr->message = string_sprintf(
    "Failed to create child process for %s transport: %s", tblock->name,
      strerror(errno));
  return FALSE;
  }

/* Now fork a process to handle the output that comes down the pipe. */

if ((outpid = fork()) < 0)
  {
  addr->basic_errno = errno;
  addr->transport_return = DEFER;
  addr->message = string_sprintf(
    "Failed to create process for handling output in %s transport",
      tblock->name);
  (void)close(fd_in);
  (void)close(fd_out);
  return FALSE;
  }

/* This is the code for the output-handling subprocess. Read from the pipe
in chunks, and write to the return file if one is provided. Keep track of
the number of bytes handled. If the limit is exceeded, try to kill the
subprocess group, and in any case close the pipe and exit, which should cause
the subprocess to fail. */

if (outpid == 0)
  {
  int count = 0;
  (void)close(fd_in);
  set_process_info("reading output from |%s", cmd);
  while ((rc = read(fd_out, big_buffer, big_buffer_size)) > 0)
    {
    if (addr->return_file >= 0)
      if(write(addr->return_file, big_buffer, rc) != rc)
        DEBUG(D_transport) debug_printf("Problem writing to return_file\n");
    count += rc;
    if (count > ob->max_output)
      {
      DEBUG(D_transport) debug_printf("Too much output from pipe - killed\n");
      if (addr->return_file >= 0)
	{
        uschar *message = US"\n\n*** Too much output - remainder discarded ***\n";
        rc = Ustrlen(message);
        if(write(addr->return_file, message, rc) != rc)
          DEBUG(D_transport) debug_printf("Problem writing to return_file\n");
	}
      killpg(pid, SIGKILL);
      break;
      }
    }
  (void)close(fd_out);
  _exit(0);
  }

(void)close(fd_out);  /* Not used in this process */


/* Carrying on now with the main parent process. Attempt to write the message
to it down the pipe. It is a fallacy to think that you can detect write errors
when the sub-process fails to read the pipe. The parent process may complete
writing and close the pipe before the sub-process completes. We could sleep a
bit here to let the sub-process get going, but it may still not complete. So we
ignore all writing errors. (When in the test harness, we do do a short sleep so
any debugging output is likely to be in the same order.) */

if (running_in_test_harness) millisleep(500);

DEBUG(D_transport) debug_printf("Writing message to pipe\n");

/* Arrange to time out writes if there is a timeout set. */

if (timeout > 0)
  {
  sigalrm_seen = FALSE;
  transport_write_timeout = timeout;
  }

/* Reset the counter of bytes written */

transport_count = 0;

/* First write any configured prefix information */

if (ob->message_prefix != NULL)
  {
  uschar *prefix = expand_string(ob->message_prefix);
  if (prefix == NULL)
    {
    addr->transport_return = search_find_defer? DEFER : PANIC;
    addr->message = string_sprintf("Expansion of \"%s\" (prefix for %s "
      "transport) failed: %s", ob->message_prefix, tblock->name,
      expand_string_message);
    return FALSE;
    }
  if (!transport_write_block(fd_in, prefix, Ustrlen(prefix)))
    goto END_WRITE;
  }

/* If the use_bsmtp option is set, we need to write SMTP prefix information.
The various different values for batching are handled outside; if there is more
than one address available here, all must be included. Force SMTP dot-handling.
*/

if (ob->use_bsmtp)
  {
  address_item *a;

  if (!transport_write_string(fd_in, "MAIL FROM:<%s>%s", return_path, eol))
    goto END_WRITE;

  for (a = addr; a != NULL; a = a->next)
    {
    if (!transport_write_string(fd_in,
        "RCPT TO:<%s>%s",
        transport_rcpt_address(a, tblock->rcpt_include_affixes),
        eol))
      goto END_WRITE;
    }

  if (!transport_write_string(fd_in, "DATA%s", eol)) goto END_WRITE;
  }

/* Now the actual message - the options were set at initialization time */

if (!transport_write_message(addr, fd_in, ob->options, 0, tblock->add_headers,
  tblock->remove_headers, ob->check_string, ob->escape_string,
  tblock->rewrite_rules, tblock->rewrite_existflags))
    goto END_WRITE;

/* Now any configured suffix */

if (ob->message_suffix != NULL)
  {
  uschar *suffix = expand_string(ob->message_suffix);
  if (suffix == NULL)
    {
    addr->transport_return = search_find_defer? DEFER : PANIC;
    addr->message = string_sprintf("Expansion of \"%s\" (suffix for %s "
      "transport) failed: %s", ob->message_suffix, tblock->name,
      expand_string_message);
    return FALSE;
    }
  if (!transport_write_block(fd_in, suffix, Ustrlen(suffix)))
    goto END_WRITE;
  }

/* If local_smtp, write the terminating dot. */

if (ob->use_bsmtp && !transport_write_string(fd_in, ".%s", eol))
  goto END_WRITE;

/* Flag all writing completed successfully. */

written_ok = TRUE;

/* Come here if there are errors during writing. */

END_WRITE:

/* OK, the writing is now all done. Close the pipe. */

(void) close(fd_in);

/* Handle errors during writing. For timeouts, set the timeout for waiting for
the child process to 1 second. If the process at the far end of the pipe died
without reading all of it, we expect an EPIPE error, which should be ignored.
We used also to ignore WRITEINCOMPLETE but the writing function is now cleverer
at handling OS where the death of a pipe doesn't give EPIPE immediately. See
comments therein. */

if (!written_ok)
  {
  if (errno == ETIMEDOUT)
    {
    addr->message = string_sprintf("%stimeout while writing to pipe",
      transport_filter_timed_out? "transport filter " : "");
    addr->transport_return = ob->timeout_defer? DEFER : FAIL;
    timeout = 1;
    }
  else if (errno == EPIPE)
    {
    debug_printf("transport error EPIPE ignored\n");
    }
  else
    {
    addr->transport_return = PANIC;
    addr->basic_errno = errno;
    if (errno == ERRNO_CHHEADER_FAIL)
      addr->message =
        string_sprintf("Failed to expand headers_add or headers_remove: %s",
          expand_string_message);
    else if (errno == ERRNO_FILTER_FAIL)
      addr->message = string_sprintf("Transport filter process failed (%d)%s",
      addr->more_errno,
      (addr->more_errno == EX_EXECFAILED)? ": unable to execute command" : "");
    else if (errno == ERRNO_WRITEINCOMPLETE)
      addr->message = string_sprintf("Failed repeatedly to write data");
    else
      addr->message = string_sprintf("Error %d", errno);
    return FALSE;
    }
  }

/* Wait for the child process to complete and take action if the returned
status is nonzero. The timeout will be just 1 second if any of the writes
above timed out. */

if ((rc = child_close(pid, timeout)) != 0)
  {
  uschar *tmsg = (addr->message == NULL)? US"" :
    string_sprintf(" (preceded by %s)", addr->message);

  /* The process did not complete in time; kill its process group and fail
  the delivery. It appears to be necessary to kill the output process too, as
  otherwise it hangs on for some time if the actual pipe process is sleeping.
  (At least, that's what I observed on Solaris 2.5.1.) Since we are failing
  the delivery, that shouldn't cause any problem. */

  if (rc == -256)
    {
    killpg(pid, SIGKILL);
    kill(outpid, SIGKILL);
    addr->transport_return = ob->timeout_defer? DEFER : FAIL;
    addr->message = string_sprintf("pipe delivery process timed out%s", tmsg);
    }

  /* Wait() failed. */

  else if (rc == -257)
    {
    addr->transport_return = PANIC;
    addr->message = string_sprintf("Wait() failed for child process of %s "
      "transport: %s%s", tblock->name, strerror(errno), tmsg);
    }

  /* Since the transport_filter timed out we assume it has sent the child process
  a malformed or incomplete data stream.  Kill off the child process
  and prevent checking its exit status as it will has probably exited in error.
  This prevents the transport_filter timeout message from getting overwritten
  by the exit error which is not the cause of the problem. */

  else if (transport_filter_timed_out)
    {
    killpg(pid, SIGKILL);
    kill(outpid, SIGKILL);
    }

  /* Either the process completed, but yielded a non-zero (necessarily
  positive) status, or the process was terminated by a signal (rc will contain
  the negation of the signal number). Treat killing by signal as failure unless
  status is being ignored. By default, the message is bounced back, unless
  freeze_signal is set, in which case it is frozen instead. */

  else if (rc < 0)
    {
    if (ob->freeze_signal)
      {
      addr->transport_return = DEFER;
      addr->special_action = SPECIAL_FREEZE;
      addr->message = string_sprintf("Child process of %s transport (running "
        "command \"%s\") was terminated by signal %d (%s)%s", tblock->name, cmd,
        -rc, os_strsignal(-rc), tmsg);
      }
    else if (!ob->ignore_status)
      {
      addr->transport_return = FAIL;
      addr->message = string_sprintf("Child process of %s transport (running "
        "command \"%s\") was terminated by signal %d (%s)%s", tblock->name, cmd,
        -rc, os_strsignal(-rc), tmsg);
      }
    }

  /* For positive values (process terminated with non-zero status), we need a
  status code to request deferral. A number of systems contain the following
  line in sysexits.h:

      #define EX_TEMPFAIL 75

  with the description

      EX_TEMPFAIL -- temporary failure, indicating something that
         is not really an error.  In sendmail, this means
         that a mailer (e.g.) could not create a connection,
         and the request should be reattempted later.

  Based on this, we use exit code EX_TEMPFAIL as a default to mean "defer" when
  not ignoring the returned status. However, there is now an option that
  contains a list of temporary codes, with TEMPFAIL and CANTCREAT as defaults.

  Another case that needs special treatment is if execve() failed (typically
  the command that was given is a non-existent path). By default this is
  treated as just another failure, but if freeze_exec_fail is set, the reaction
  is to freeze the message rather than bounce the address. Exim used to signal
  this failure with EX_UNAVAILABLE, which is definined in many systems as

      #define EX_UNAVAILABLE  69

  with the description

      EX_UNAVAILABLE -- A service is unavailable.  This can occur
            if a support program or file does not exist.  This
            can also be used as a catchall message when something
            you wanted to do doesn't work, but you don't know why.

  However, this can be confused with a command that actually returns 69 because
  something *it* wanted is unavailable. At release 4.21, Exim was changed to
  use return code 127 instead, because this is what the shell returns when it
  is unable to exec a command. We define it as EX_EXECFAILED, and use it in
  child.c to signal execve() failure and other unexpected failures such as
  setuid() not working - though that won't be the case here because we aren't
  changing uid. */

  else
    {
    /* Always handle execve() failure specially if requested to */

    if (ob->freeze_exec_fail && (rc == EX_EXECFAILED))
      {
      addr->transport_return = DEFER;
      addr->special_action = SPECIAL_FREEZE;
      addr->message = string_sprintf("pipe process failed to exec \"%s\"%s",
        cmd, tmsg);
      }

    /* Otherwise take action only if not ignoring status */

    else if (!ob->ignore_status)
      {
      uschar *ss;
      int size, ptr, i;

      /* If temp_errors is "*" all codes are temporary. Initializion checks
      that it's either "*" or a list of numbers. If not "*", scan the list of
      temporary failure codes; if any match, the result is DEFER. */

      if (ob->temp_errors[0] == '*')
        addr->transport_return = DEFER;

      else
        {
        const uschar *s = ob->temp_errors;
        uschar *p;
        uschar buffer[64];
        int sep = 0;

        addr->transport_return = FAIL;
        while ((p = string_nextinlist(&s,&sep,buffer,sizeof(buffer))))
          if (rc == Uatoi(p)) { addr->transport_return = DEFER; break; }
        }

      /* Ensure the message contains the expanded command and arguments. This
      doesn't have to be brilliantly efficient - it is an error situation. */

      addr->message = string_sprintf("Child process of %s transport returned "
        "%d", tblock->name, rc);

      ptr = Ustrlen(addr->message);
      size = ptr + 1;

      /* If the return code is > 128, it often means that a shell command
      was terminated by a signal. */

      ss = (rc > 128)?
        string_sprintf("(could mean shell command ended by signal %d (%s))",
          rc-128, os_strsignal(rc-128)) :
        US os_strexit(rc);

      if (*ss != 0)
        {
        addr->message = string_cat(addr->message, &size, &ptr, US" ", 1);
        addr->message = string_cat(addr->message, &size, &ptr,
          ss, Ustrlen(ss));
        }

      /* Now add the command and arguments */

      addr->message = string_cat(addr->message, &size, &ptr,
        US" from command:", 14);

      for (i = 0; i < sizeof(argv)/sizeof(int *) && argv[i] != NULL; i++)
        {
        BOOL quote = FALSE;
        addr->message = string_cat(addr->message, &size, &ptr, US" ", 1);
        if (Ustrpbrk(argv[i], " \t") != NULL)
          {
          quote = TRUE;
          addr->message = string_cat(addr->message, &size, &ptr, US"\"", 1);
          }
        addr->message = string_cat(addr->message, &size, &ptr, argv[i],
          Ustrlen(argv[i]));
        if (quote)
          addr->message = string_cat(addr->message, &size, &ptr, US"\"", 1);
        }

      /* Add previous filter timeout message, if present. */

      if (*tmsg != 0)
        addr->message = string_cat(addr->message, &size, &ptr, tmsg,
          Ustrlen(tmsg));

      addr->message[ptr] = 0;  /* Ensure concatenated string terminated */
      }
    }
  }

/* Ensure all subprocesses (in particular, the output handling process)
are complete before we pass this point. */

while (wait(&rc) >= 0);

DEBUG(D_transport) debug_printf("%s transport yielded %d\n", tblock->name,
  addr->transport_return);

/* If there has been a problem, the message in addr->message contains details
of the pipe command. We don't want to expose these to the world, so we set up
something bland to return to the sender. */

if (addr->transport_return != OK)
  addr->user_message = US"local delivery failed";

return FALSE;
}
Esempio n. 5
0
File: dovecot.c Progetto: Exim/exim
int
auth_dovecot_server(auth_instance * ablock, uschar * data)
{
auth_dovecot_options_block *ob =
       (auth_dovecot_options_block *) ablock->options_block;
struct sockaddr_un sa;
uschar buffer[DOVECOT_AUTH_MAXLINELEN];
uschar *args[DOVECOT_AUTH_MAXFIELDCOUNT];
uschar *auth_command;
uschar *auth_extra_data = US"";
uschar *p;
int nargs, tmp;
int crequid = 1, cont = 1, fd = -1, ret = DEFER;
BOOL found = FALSE, have_mech_line = FALSE;

HDEBUG(D_auth) debug_printf("dovecot authentication\n");

if (!data)
  {
  ret = FAIL;
  goto out;
  }

memset(&sa, 0, sizeof(sa));
sa.sun_family = AF_UNIX;

/* This was the original code here: it is nonsense because strncpy()
does not return an integer. I have converted this to use the function
that formats and checks length. PH */

/*
if (strncpy(sa.sun_path, ob->server_socket, sizeof(sa.sun_path)) < 0) {
}
*/

if (!string_format(US sa.sun_path, sizeof(sa.sun_path), "%s",
		  ob->server_socket))
  {
  auth_defer_msg = US"authentication socket path too long";
  return DEFER;
  }

auth_defer_msg = US"authentication socket connection error";

if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
  return DEFER;

if (connect(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0)
  goto out;

auth_defer_msg = US"authentication socket protocol error";

socket_buffer_left = 0;  /* Global, used to read more than a line but return by line */
while (cont)
  {
  if (dc_gets(buffer, sizeof(buffer), fd) == NULL)
    OUT("authentication socket read error or premature eof");
  p = buffer + Ustrlen(buffer) - 1;
  if (*p != '\n')
    OUT("authentication socket protocol line too long");

  *p = '\0';
  HDEBUG(D_auth) debug_printf("received: %s\n", buffer);

  nargs = strcut(buffer, args, sizeof(args) / sizeof(args[0]));

  /* HDEBUG(D_auth) debug_strcut(args, nargs, sizeof(args) / sizeof(args[0])); */

  /* Code below rewritten by Kirill Miazine ([email protected]). Only check commands that
    Exim will need. Original code also failed if Dovecot server sent unknown
    command. E.g. COOKIE in version 1.1 of the protocol would cause troubles. */
  /* pdp: note that CUID is a per-connection identifier sent by the server,
    which increments at server discretion.
    By contrast, the "id" field of the protocol is a connection-specific request
    identifier, which needs to be unique per request from the client and is not
    connected to the CUID value, so we ignore CUID from server.  It's purely for
    diagnostics. */

  if (Ustrcmp(args[0], US"VERSION") == 0)
    {
    CHECK_COMMAND("VERSION", 2, 2);
    if (Uatoi(args[1]) != VERSION_MAJOR)
      OUT("authentication socket protocol version mismatch");
    }
  else if (Ustrcmp(args[0], US"MECH") == 0)
    {
    CHECK_COMMAND("MECH", 1, INT_MAX);
    have_mech_line = TRUE;
    if (strcmpic(US args[1], ablock->public_name) == 0)
      found = TRUE;
    }
  else if (Ustrcmp(args[0], US"SPID") == 0)
    {
    /* Unfortunately the auth protocol handshake wasn't designed well
    to differentiate between auth-client/userdb/master. auth-userdb
    and auth-master send VERSION + SPID lines only and nothing
    afterwards, while auth-client sends VERSION + MECH + SPID +
    CUID + more. The simplest way that we can determine if we've
    connected to the correct socket is to see if MECH line exists or
    not (alternatively we'd have to have a small timeout after SPID
    to see if CUID is sent or not). */

    if (!have_mech_line)
      OUT("authentication socket type mismatch"
	" (connected to auth-master instead of auth-client)");
    }
  else if (Ustrcmp(args[0], US"DONE") == 0)
    {
    CHECK_COMMAND("DONE", 0, 0);
    cont = 0;
    }
  }

if (!found)
  {
  auth_defer_msg = string_sprintf(
    "Dovecot did not advertise mechanism \"%s\" to us", ablock->public_name);
  goto out;
  }

/* Added by PH: data must not contain tab (as it is
b64 it shouldn't, but check for safety). */

if (Ustrchr(data, '\t') != NULL)
  {
  ret = FAIL;
  goto out;
  }

/* Added by PH: extra fields when TLS is in use or if the TCP/IP
connection is local. */

if (tls_in.cipher != NULL)
  auth_extra_data = string_sprintf("secured\t%s%s",
     tls_in.certificate_verified? "valid-client-cert" : "",
     tls_in.certificate_verified? "\t" : "");

else if (  interface_address != NULL
        && Ustrcmp(sender_host_address, interface_address) == 0)
  auth_extra_data = US"secured\t";


/****************************************************************************
The code below was the original code here. It didn't work. A reading of the
file auth-protocol.txt.gz that came with Dovecot 1.0_beta8 indicated that
this was not right. Maybe something changed. I changed it to move the
service indication into the AUTH command, and it seems to be better. PH

fprintf(f, "VERSION\t%d\t%d\r\nSERVICE\tSMTP\r\nCPID\t%d\r\n"
       "AUTH\t%d\t%s\trip=%s\tlip=%s\tresp=%s\r\n",
       VERSION_MAJOR, VERSION_MINOR, getpid(), cuid,
       ablock->public_name, sender_host_address, interface_address,
       data ? CS  data : "");

Subsequently, the command was modified to add "secured" and "valid-client-
cert" when relevant.
****************************************************************************/

auth_command = string_sprintf("VERSION\t%d\t%d\nCPID\t%d\n"
       "AUTH\t%d\t%s\tservice=smtp\t%srip=%s\tlip=%s\tnologin\tresp=%s\n",
       VERSION_MAJOR, VERSION_MINOR, getpid(), crequid,
       ablock->public_name, auth_extra_data, sender_host_address,
       interface_address, data);

if (write(fd, auth_command, Ustrlen(auth_command)) < 0)
  HDEBUG(D_auth) debug_printf("error sending auth_command: %s\n",
    strerror(errno));

HDEBUG(D_auth) debug_printf("sent: %s", auth_command);

while (1)
  {
  uschar *temp;
  uschar *auth_id_pre = NULL;
  int i;

  if (dc_gets(buffer, sizeof(buffer), fd) == NULL)
    {
    auth_defer_msg = US"authentication socket read error or premature eof";
    goto out;
    }

  buffer[Ustrlen(buffer) - 1] = 0;
  HDEBUG(D_auth) debug_printf("received: %s\n", buffer);
  nargs = strcut(buffer, args, sizeof(args) / sizeof(args[0]));

  if (Uatoi(args[1]) != crequid)
    OUT("authentication socket connection id mismatch");

  switch (toupper(*args[0]))
    {
    case 'C':
      CHECK_COMMAND("CONT", 1, 2);

      if ((tmp = auth_get_no64_data(&data, US args[2])) != OK)
	{
	ret = tmp;
	goto out;
	}

      /* Added by PH: data must not contain tab (as it is
      b64 it shouldn't, but check for safety). */

      if (Ustrchr(data, '\t') != NULL)
        {
	ret = FAIL;
	goto out;
	}

      temp = string_sprintf("CONT\t%d\t%s\n", crequid, data);
      if (write(fd, temp, Ustrlen(temp)) < 0)
	OUT("authentication socket write error");
      break;

    case 'F':
      CHECK_COMMAND("FAIL", 1, -1);

      for (i=2; (i<nargs) && (auth_id_pre == NULL); i++)
	{
	if ( Ustrncmp(args[i], US"user="******"OK", 2, -1);

      /* Search for the "user=$USER" string in the args array
      and return the proper value.  */

      for (i=2; (i<nargs) && (auth_id_pre == NULL); i++)
	{
	if ( Ustrncmp(args[i], US"user="******"authentication socket protocol error, username missing");

      ret = OK;
      /* fallthrough */

    default:
      goto out;
    }
  }

out:
/* close the socket used by dovecot */
if (fd >= 0)
  close(fd);

/* Expand server_condition as an authorization check */
return ret == OK ? auth_check_serv_cond(ablock) : ret;
}
Esempio n. 6
0
int
main(int argc, char **cargv)
{
open_db dbblock[8];
int max_db = sizeof(dbblock)/sizeof(open_db);
int current = -1;
int showtime = 0;
int i;
dbdata_wait *dbwait = NULL;
uschar **argv = USS cargv;
uschar buffer[256];
uschar structbuffer[1024];

if (argc != 2)
  {
  printf("Usage: test_dbfn directory\n");
  printf("The subdirectory called \"db\" in the given directory is used for\n");
  printf("the files used in this test program.\n");
  return 1;
  }

/* Initialize */

spool_directory = argv[1];
debug_selector = D_all - D_memory;
debug_file = stderr;
big_buffer = malloc(big_buffer_size);

for (i = 0; i < max_db; i++) dbblock[i].dbptr = NULL;

printf("\nExim's db functions tester: interface type is %s\n", EXIM_DBTYPE);
printf("DBM library: ");

#ifdef DB_VERSION_STRING
printf("Berkeley DB: %s\n", DB_VERSION_STRING);
#elif defined(BTREEVERSION) && defined(HASHVERSION)
  #ifdef USE_DB
  printf("probably Berkeley DB version 1.8x (native mode)\n");
  #else
  printf("probably Berkeley DB version 1.8x (compatibility mode)\n");
  #endif
#elif defined(_DBM_RDONLY) || defined(dbm_dirfno)
printf("probably ndbm\n");
#elif defined(USE_TDB)
printf("using tdb\n");
#else
  #ifdef USE_GDBM
  printf("probably GDBM (native mode)\n");
  #else
  printf("probably GDBM (compatibility mode)\n");
  #endif
#endif

/* Test the functions */

printf("\nTest the functions\n> ");

while (Ufgets(buffer, 256, stdin) != NULL)
  {
  int len = Ustrlen(buffer);
  int count = 1;
  clock_t start = 1;
  clock_t stop = 0;
  uschar *cmd = buffer;
  while (len > 0 && isspace((uschar)buffer[len-1])) len--;
  buffer[len] = 0;

  if (isdigit((uschar)*cmd))
    {
    count = Uatoi(cmd);
    while (isdigit((uschar)*cmd)) cmd++;
    while (isspace((uschar)*cmd)) cmd++;
    }

  if (Ustrncmp(cmd, "open", 4) == 0)
    {
    int i;
    open_db *odb;
    uschar *s = cmd + 4;
    while (isspace((uschar)*s)) s++;

    for (i = 0; i < max_db; i++)
      if (dbblock[i].dbptr == NULL) break;

    if (i >= max_db)
      {
      printf("Too many open databases\n> ");
      continue;
      }

    start = clock();
    odb = dbfn_open(s, O_RDWR, dbblock + i, TRUE);
    stop = clock();

    if (odb != NULL)
      {
      current = i;
      printf("opened %d\n", current);
      }
    /* Other error cases will have written messages */
    else if (errno == ENOENT)
      {
      printf("open failed: %s%s\n", strerror(errno),
        #ifdef USE_DB
        " (or other Berkeley DB error)"
        #else
        ""
        #endif
        );
      }
    }

  else if (Ustrncmp(cmd, "write", 5) == 0)
    {
    int rc = 0;
    uschar *key = cmd + 5;
    uschar *data;

    if (current < 0)
      {
      printf("No current database\n");
      continue;
      }

    while (isspace((uschar)*key)) key++;
    data = key;
    while (*data != 0 && !isspace((uschar)*data)) data++;
    *data++ = 0;
    while (isspace((uschar)*data)) data++;

    dbwait = (dbdata_wait *)(&structbuffer);
    Ustrcpy(dbwait->text, data);

    start = clock();
    while (count-- > 0)
      rc = dbfn_write(dbblock + current, key, dbwait,
        Ustrlen(data) + sizeof(dbdata_wait));
    stop = clock();
    if (rc != 0) printf("Failed: %s\n", strerror(errno));
    }

  else if (Ustrncmp(cmd, "read", 4) == 0)
    {
    uschar *key = cmd + 4;
    if (current < 0)
      {
      printf("No current database\n");
      continue;
      }
    while (isspace((uschar)*key)) key++;
    start = clock();
    while (count-- > 0)
      dbwait = (dbdata_wait *)dbfn_read_with_length(dbblock+ current, key, NULL);
    stop = clock();
    printf("%s\n", (dbwait == NULL)? "<not found>" : CS dbwait->text);
    }

  else if (Ustrncmp(cmd, "delete", 6) == 0)
    {
    uschar *key = cmd + 6;
    if (current < 0)
      {
      printf("No current database\n");
      continue;
      }
    while (isspace((uschar)*key)) key++;
    dbfn_delete(dbblock + current, key);
    }

  else if (Ustrncmp(cmd, "scan", 4) == 0)
    {
    EXIM_CURSOR *cursor;
    BOOL startflag = TRUE;
    uschar *key;
    uschar keybuffer[256];
    if (current < 0)
      {
      printf("No current database\n");
      continue;
      }
    start = clock();
    while ((key = dbfn_scan(dbblock + current, startflag, &cursor)) != NULL)
      {
      startflag = FALSE;
      Ustrcpy(keybuffer, key);
      dbwait = (dbdata_wait *)dbfn_read_with_length(dbblock + current,
        keybuffer, NULL);
      printf("%s: %s\n", keybuffer, dbwait->text);
      }
    stop = clock();
    printf("End of scan\n");
    }

  else if (Ustrncmp(cmd, "close", 5) == 0)
    {
    uschar *s = cmd + 5;
    while (isspace((uschar)*s)) s++;
    i = Uatoi(s);
    if (i >= max_db || dbblock[i].dbptr == NULL) printf("Not open\n"); else
      {
      start = clock();
      dbfn_close(dbblock + i);
      stop = clock();
      dbblock[i].dbptr = NULL;
      if (i == current) current = -1;
      }
    }

  else if (Ustrncmp(cmd, "file", 4) == 0)
    {
    uschar *s = cmd + 4;
    while (isspace((uschar)*s)) s++;
    i = Uatoi(s);
    if (i >= max_db || dbblock[i].dbptr == NULL) printf("Not open\n");
      else current = i;
    }

  else if (Ustrncmp(cmd, "time", 4) == 0)
    {
    showtime = ~showtime;
    printf("Timing %s\n", showtime? "on" : "off");
    }

  else if (Ustrcmp(cmd, "q") == 0 || Ustrncmp(cmd, "quit", 4) == 0) break;

  else if (Ustrncmp(cmd, "help", 4) == 0)
    {
    printf("close  [<number>]              close file [<number>]\n");
    printf("delete <key>                   remove record from current file\n");
    printf("file   <number>                make file <number> current\n");
    printf("open   <name>                  open db file\n");
    printf("q[uit]                         exit program\n");
    printf("read   <key>                   read record from current file\n");
    printf("scan                           scan current file\n");
    printf("time                           time display on/off\n");
    printf("write  <key> <rest-of-line>    write record to current file\n");
    }

  else printf("Eh?\n");

  if (showtime && stop >= start)
    printf("start=%d stop=%d difference=%d\n", (int)start, (int)stop,
     (int)(stop - start));

  printf("> ");
  }

for (i = 0; i < max_db; i++)
  {
  if (dbblock[i].dbptr != NULL)
    {
    printf("\nClosing %d", i);
    dbfn_close(dbblock + i);
    }
  }

printf("\n");
return 0;
}
Esempio n. 7
0
int main(int argc, char **cargv)
{
int dbdata_type;
uschar **argv = USS cargv;
uschar buffer[256];
uschar name[256];
void *reset_point = store_get(0);

name[0] = 0;  /* No name set */

/* Sort out the database type, verify what we are working on and then process
user requests */

dbdata_type = check_args(argc, argv, US"fixdb", US"");
printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);

for(;;)
  {
  open_db dbblock;
  open_db *dbm;
  void *record;
  dbdata_retry *retry;
  dbdata_wait *wait;
  dbdata_callout_cache *callout;
  dbdata_ratelimit *ratelimit;
  int i, oldlength;
  uschar *t;
  uschar field[256], value[256];

  store_reset(reset_point);

  printf("> ");
  if (Ufgets(buffer, 256, stdin) == NULL) break;

  buffer[Ustrlen(buffer)-1] = 0;
  field[0] = value[0] = 0;

  /* If the buffer contains just one digit, or just consists of "d", use the
  previous name for an update. */

  if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
       || Ustrcmp(buffer, "d") == 0)
    {
    if (name[0] == 0)
      {
      printf("No previous record name is set\n");
      continue;
      }
    (void)sscanf(CS buffer, "%s %s", field, value);
    }
  else
    {
    name[0] = 0;
    (void)sscanf(CS buffer, "%s %s %s", name, field, value);
    }

  /* Handle an update request */

  if (field[0] != 0)
    {
    int verify = 1;
    dbm = dbfn_open(argv[1], argv[2], O_RDWR, &dbblock);
    if (dbm == NULL) continue;

    if (Ustrcmp(field, "d") == 0)
      {
      if (value[0] != 0) printf("unexpected value after \"d\"\n");
        else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
          "not found" : "deleted");
      dbfn_close(dbm);
      continue;
      }

    else if (isdigit((uschar)field[0]))
      {
      int fieldno = Uatoi(field);
      if (value[0] == 0)
        {
        printf("value missing\n");
        dbfn_close(dbm);
        continue;
        }
      else
        {
        record = dbfn_read_with_length(dbm, name, &oldlength);
        if (record == NULL) printf("not found\n"); else
          {
          time_t tt;
          int length = 0;     /* Stops compiler warning */

          switch(dbdata_type)
            {
            case type_retry:
            retry = (dbdata_retry *)record;
            length = sizeof(dbdata_retry) + Ustrlen(retry->text);

            switch(fieldno)
              {
              case 0:
              retry->basic_errno = Uatoi(value);
              break;

              case 1:
              retry->more_errno = Uatoi(value);
              break;

              case 2:
              if ((tt = read_time(value)) > 0) retry->first_failed = tt;
                else printf("bad time value\n");
              break;

              case 3:
              if ((tt = read_time(value)) > 0) retry->last_try = tt;
                else printf("bad time value\n");
              break;

              case 4:
              if ((tt = read_time(value)) > 0) retry->next_try = tt;
                else printf("bad time value\n");
              break;

              case 5:
              if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
              else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
              else printf("\"yes\" or \"no\" expected=n");
              break;

              default:
              printf("unknown field number\n");
              verify = 0;
              break;
              }
            break;

            case type_wait:
            printf("Can't change contents of wait database record\n");
            break;

            case type_misc:
            printf("Can't change contents of misc database record\n");
            break;

            case type_callout:
            callout = (dbdata_callout_cache *)record;
            length = sizeof(dbdata_callout_cache);
            switch(fieldno)
              {
              case 0:
              callout->result = Uatoi(value);
              break;

              case 1:
              callout->postmaster_result = Uatoi(value);
              break;

              case 2:
              callout->random_result = Uatoi(value);
              break;

              default:
              printf("unknown field number\n");
              verify = 0;
              break;
              }
            break;

            case type_ratelimit:
            ratelimit = (dbdata_ratelimit *)record;
            length = sizeof(dbdata_ratelimit);
            switch(fieldno)
              {
              case 0:
              if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
                else printf("bad time value\n");
              break;

              case 1:
              ratelimit->time_usec = Uatoi(value);
              break;

              case 2:
              ratelimit->rate = Ustrtod(value, NULL);
              break;

              default:
              printf("unknown field number\n");
              verify = 0;
              break;
              }
            break;
            }

          dbfn_write(dbm, name, record, length);
          }
        }
      }

    else
      {
      printf("field number or d expected\n");
      verify = 0;
      }

    dbfn_close(dbm);
    if (!verify) continue;
    }

  /* The "name" q causes an exit */

  else if (Ustrcmp(name, "q") == 0) return 0;

  /* Handle a read request, or verify after an update. */

  dbm = dbfn_open(argv[1], argv[2], O_RDONLY, &dbblock);
  if (dbm == NULL) continue;

  record = dbfn_read_with_length(dbm, name, &oldlength);
  if (record == NULL)
    {
    printf("record %s not found\n", name);
    name[0] = 0;
    }
  else
    {
    int count_bad = 0;
    printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
    switch(dbdata_type)
      {
      case type_retry:
      retry = (dbdata_retry *)record;
      printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
      printf("1 extra data:   %d\n", retry->more_errno);
      printf("2 first failed: %s\n", print_time(retry->first_failed));
      printf("3 last try:     %s\n", print_time(retry->last_try));
      printf("4 next try:     %s\n", print_time(retry->next_try));
      printf("5 expired:      %s\n", (retry->expired)? "yes" : "no");
      break;

      case type_wait:
      wait = (dbdata_wait *)record;
      t = wait->text;
      printf("Sequence: %d\n", wait->sequence);
      if (wait->count > WAIT_NAME_MAX)
        {
        printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
          wait->count, WAIT_NAME_MAX);
        wait->count = WAIT_NAME_MAX;
        count_bad = 1;
        }
      for (i = 1; i <= wait->count; i++)
        {
        Ustrncpy(value, t, MESSAGE_ID_LENGTH);
        value[MESSAGE_ID_LENGTH] = 0;
        if (count_bad && value[0] == 0) break;
        if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
            Ustrspn(value, "0123456789"
                          "abcdefghijklmnopqrstuvwxyz"
                          "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
          {
          int j;
          printf("\n**** Data corrupted: bad character in message id ****\n");
          for (j = 0; j < MESSAGE_ID_LENGTH; j++)
            printf("%02x ", value[j]);
          printf("\n");
          break;
          }
        printf("%s ", value);
        t += MESSAGE_ID_LENGTH;
        }
      printf("\n");
      break;

      case type_misc:
      break;

      case type_callout:
      callout = (dbdata_callout_cache *)record;
      printf("0 callout:    %s (%d)\n", print_cache(callout->result),
          callout->result);
      if (oldlength > sizeof(dbdata_callout_cache_address))
        {
        printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
            callout->postmaster_result);
        printf("2 random:     %s (%d)\n", print_cache(callout->random_result),
            callout->random_result);
        }
      break;

      case type_ratelimit:
      ratelimit = (dbdata_ratelimit *)record;
      printf("0 time stamp:  %s\n", print_time(ratelimit->time_stamp));
      printf("1 fract. time: .%06d\n", ratelimit->time_usec);
      printf("2 sender rate: % .3f\n", ratelimit->rate);
      break;
      }
    }

  /* The database is closed after each request */

  dbfn_close(dbm);
  }

printf("\n");
return 0;
}
Esempio n. 8
0
static int
perform_mysql_search(const uschar *query, uschar *server, uschar **resultptr,
  uschar **errmsg, BOOL *defer_break, BOOL *do_cache)
{
MYSQL *mysql_handle = NULL;        /* Keep compilers happy */
MYSQL_RES *mysql_result = NULL;
MYSQL_ROW mysql_row_data;
MYSQL_FIELD *fields;

int i;
int ssize = 0;
int offset = 0;
int yield = DEFER;
unsigned int num_fields;
uschar *result = NULL;
mysql_connection *cn;
uschar *server_copy = NULL;
uschar *sdata[4];

/* Disaggregate the parameters from the server argument. The order is host,
database, user, password. We can write to the string, since it is in a
nextinlist temporary buffer. The copy of the string that is used for caching
has the password removed. This copy is also used for debugging output. */

for (i = 3; i > 0; i--)
  {
  uschar *pp = Ustrrchr(server, '/');
  if (pp == NULL)
    {
    *errmsg = string_sprintf("incomplete MySQL server data: %s",
      (i == 3)? server : server_copy);
    *defer_break = TRUE;
    return DEFER;
    }
  *pp++ = 0;
  sdata[i] = pp;
  if (i == 3) server_copy = string_copy(server);  /* sans password */
  }
sdata[0] = server;   /* What's left at the start */

/* See if we have a cached connection to the server */

for (cn = mysql_connections; cn != NULL; cn = cn->next)
  {
  if (Ustrcmp(cn->server, server_copy) == 0)
    {
    mysql_handle = cn->handle;
    break;
    }
  }

/* If no cached connection, we must set one up. Mysql allows for a host name
and port to be specified. It also allows the name of a Unix socket to be used.
Unfortunately, this contains slashes, but its use is expected to be rare, so
the rather cumbersome syntax shouldn't inconvenience too many people. We use
this:  host:port(socket)  where all the parts are optional. */

if (cn == NULL)
  {
  uschar *p;
  uschar *socket = NULL;
  int port = 0;

  if ((p = Ustrchr(sdata[0], '(')) != NULL)
    {
    *p++ = 0;
    socket = p;
    while (*p != 0 && *p != ')') p++;
    *p = 0;
    }

  if ((p = Ustrchr(sdata[0], ':')) != NULL)
    {
    *p++ = 0;
    port = Uatoi(p);
    }

  if (Ustrchr(sdata[0], '/') != NULL)
    {
    *errmsg = string_sprintf("unexpected slash in MySQL server hostname: %s",
      sdata[0]);
    *defer_break = TRUE;
    return DEFER;
    }

  /* If the database is the empty string, set it NULL - the query must then
  define it. */

  if (sdata[1][0] == 0) sdata[1] = NULL;

  DEBUG(D_lookup)
    debug_printf("MYSQL new connection: host=%s port=%d socket=%s "
      "database=%s user=%s\n", sdata[0], port, socket, sdata[1], sdata[2]);

  /* Get store for a new handle, initialize it, and connect to the server */

  mysql_handle = store_get(sizeof(MYSQL));
  mysql_init(mysql_handle);
  if (mysql_real_connect(mysql_handle,
      /*  host        user         passwd     database */
      CS sdata[0], CS sdata[2], CS sdata[3], CS sdata[1],
      port, CS socket, CLIENT_MULTI_RESULTS) == NULL)
    {
    *errmsg = string_sprintf("MYSQL connection failed: %s",
      mysql_error(mysql_handle));
    *defer_break = FALSE;
    goto MYSQL_EXIT;
    }

  /* Add the connection to the cache */

  cn = store_get(sizeof(mysql_connection));
  cn->server = server_copy;
  cn->handle = mysql_handle;
  cn->next = mysql_connections;
  mysql_connections = cn;
  }

/* Else use a previously cached connection */

else
  {
  DEBUG(D_lookup)
    debug_printf("MYSQL using cached connection for %s\n", server_copy);
  }

/* Run the query */

if (mysql_query(mysql_handle, CS query) != 0)
  {
  *errmsg = string_sprintf("MYSQL: query failed: %s\n",
    mysql_error(mysql_handle));
  *defer_break = FALSE;
  goto MYSQL_EXIT;
  }

/* Pick up the result. If the query was not of the type that returns data,
namely INSERT, UPDATE, or DELETE, an error occurs here. However, this situation
can be detected by calling mysql_field_count(). If its result is zero, no data
was expected (this is all explained clearly in the MySQL manual). In this case,
we return the number of rows affected by the command. In this event, we do NOT
want to cache the result; also the whole cache for the handle must be cleaned
up. Setting do_cache FALSE requests this. */

if ((mysql_result = mysql_use_result(mysql_handle)) == NULL)
  {
  if ( mysql_field_count(mysql_handle) == 0 )
    {
    DEBUG(D_lookup) debug_printf("MYSQL: query was not one that returns data\n");
    result = string_sprintf("%d", mysql_affected_rows(mysql_handle));
    *do_cache = FALSE;
    goto MYSQL_EXIT;
    }
  *errmsg = string_sprintf("MYSQL: lookup result failed: %s\n",
    mysql_error(mysql_handle));
  *defer_break = FALSE;
  goto MYSQL_EXIT;
  }

/* Find the number of fields returned. If this is one, we don't add field
names to the data. Otherwise we do. */

num_fields = mysql_num_fields(mysql_result);

/* Get the fields and construct the result string. If there is more than one
row, we insert '\n' between them. */

fields = mysql_fetch_fields(mysql_result);

while ((mysql_row_data = mysql_fetch_row(mysql_result)) != NULL)
  {
  unsigned long *lengths = mysql_fetch_lengths(mysql_result);

  if (result != NULL)
      result = string_cat(result, &ssize, &offset, US"\n", 1);

  if (num_fields == 1)
    {
    if (mysql_row_data[0] != NULL)    /* NULL value yields nothing */
      result = string_cat(result, &ssize, &offset, US mysql_row_data[0],
        lengths[0]);
    }

  else for (i = 0; i < num_fields; i++)
    {
    result = lf_quote(US fields[i].name, US mysql_row_data[i], lengths[i],
      result, &ssize, &offset);
    }
  }

/* more results? -1 = no, >0 = error, 0 = yes (keep looping)
   This is needed because of the CLIENT_MULTI_RESULTS on mysql_real_connect(),
   we don't expect any more results. */

while((i = mysql_next_result(mysql_handle)) >= 0) {
   if(i == 0) {   /* Just ignore more results */
     DEBUG(D_lookup) debug_printf("MYSQL: got unexpected more results\n");
     continue;
   }

   *errmsg = string_sprintf("MYSQL: lookup result error when checking for more results: %s\n",
       mysql_error(mysql_handle));
   goto MYSQL_EXIT;
}

/* If result is NULL then no data has been found and so we return FAIL.
Otherwise, we must terminate the string which has been built; string_cat()
always leaves enough room for a terminating zero. */

if (result == NULL)
  {
  yield = FAIL;
  *errmsg = US"MYSQL: no data found";
  }
else
  {
  result[offset] = 0;
  store_reset(result + offset + 1);
  }

/* Get here by goto from various error checks and from the case where no data
was read (e.g. an update query). */

MYSQL_EXIT:

/* Free mysal store for any result that was got; don't close the connection, as
it is cached. */

if (mysql_result != NULL) mysql_free_result(mysql_result);

/* Non-NULL result indicates a sucessful result */

if (result != NULL)
  {
  *resultptr = result;
  return OK;
  }
else
  {
  DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
  return yield;      /* FAIL or DEFER */
  }
}
Esempio n. 9
0
File: ldap.c Progetto: fanf2/exim
static int
control_ldap_search(uschar *ldap_url, int search_type, uschar **res,
  uschar **errmsg)
{
BOOL defer_break = FALSE;
int timelimit = LDAP_NO_LIMIT;
int sizelimit = LDAP_NO_LIMIT;
int tcplimit = 0;
int sep = 0;
int dereference = LDAP_DEREF_NEVER;
void* referrals = LDAP_OPT_ON;
uschar *url = ldap_url;
uschar *p;
uschar *user = NULL;
uschar *password = NULL;
uschar *server, *list;
uschar buffer[512];

while (isspace(*url)) url++;

/* Until the string begins "ldap", search for the other parameter settings that
are recognized. They are of the form NAME=VALUE, with the value being
optionally double-quoted. There must still be a space after it, however. No
NAME has the value "ldap". */

while (strncmpic(url, US"ldap", 4) != 0)
  {
  uschar *name = url;
  while (*url != 0 && *url != '=') url++;
  if (*url == '=')
    {
    int namelen;
    uschar *value;
    namelen = ++url - name;
    value = string_dequote(&url);
    if (isspace(*url))
      {
      if (strncmpic(name, US"USER="******"PASS="******"SIZE=", namelen) == 0) sizelimit = Uatoi(value);
      else if (strncmpic(name, US"TIME=", namelen) == 0) timelimit = Uatoi(value);
      else if (strncmpic(name, US"CONNECT=", namelen) == 0) tcplimit = Uatoi(value);
      else if (strncmpic(name, US"NETTIME=", namelen) == 0) tcplimit = Uatoi(value);

      /* Don't know if all LDAP libraries have LDAP_OPT_DEREF */

      #ifdef LDAP_OPT_DEREF
      else if (strncmpic(name, US"DEREFERENCE=", namelen) == 0)
        {
        if (strcmpic(value, US"never") == 0) dereference = LDAP_DEREF_NEVER;
        else if (strcmpic(value, US"searching") == 0)
          dereference = LDAP_DEREF_SEARCHING;
        else if (strcmpic(value, US"finding") == 0)
          dereference = LDAP_DEREF_FINDING;
        if (strcmpic(value, US"always") == 0) dereference = LDAP_DEREF_ALWAYS;
        }
      #else
      else if (strncmpic(name, US"DEREFERENCE=", namelen) == 0)
        {
        *errmsg = string_sprintf("LDAP_OP_DEREF not defined in this LDAP "
          "library - cannot use \"dereference\"");
        DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
        return DEFER;
        }
      #endif

      #ifdef LDAP_OPT_REFERRALS
      else if (strncmpic(name, US"REFERRALS=", namelen) == 0)
        {
        if (strcmpic(value, US"follow") == 0) referrals = LDAP_OPT_ON;
        else if (strcmpic(value, US"nofollow") == 0) referrals = LDAP_OPT_OFF;
        else
          {
          *errmsg = string_sprintf("LDAP option REFERRALS is not \"follow\" "
            "or \"nofollow\"");
          DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
          return DEFER;
          }
        }
      #else
      else if (strncmpic(name, US"REFERRALS=", namelen) == 0)
        {
        *errmsg = string_sprintf("LDAP_OP_REFERRALS not defined in this LDAP "
          "library - cannot use \"referrals\"");
        DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
        return DEFER;
        }
      #endif

      else
        {
        *errmsg =
          string_sprintf("unknown parameter \"%.*s\" precedes LDAP URL",
            namelen, name);
        DEBUG(D_lookup) debug_printf("LDAP query error: %s\n", *errmsg);
        return DEFER;
        }
      while (isspace(*url)) url++;
      continue;
      }
    }
  *errmsg = US"malformed parameter setting precedes LDAP URL";
  DEBUG(D_lookup) debug_printf("LDAP query error: %s\n", *errmsg);
  return DEFER;
  }

/* If user is set, de-URL-quote it. Some LDAP libraries do this for themselves,
but it seems that not all behave like this. The DN for the user is often the
result of ${quote_ldap_dn:...} quoting, which does apply URL quoting, because
that is needed when the DN is used as a base DN in a query. Sigh. This is all
far too complicated. */

if (user != NULL)
  {
  uschar *s;
  uschar *t = user;
  for (s = user; *s != 0; s++)
    {
    int c, d;
    if (*s == '%' && isxdigit(c=s[1]) && isxdigit(d=s[2]))
      {
      c = tolower(c);
      d = tolower(d);
      *t++ =
        (((c >= 'a')? (10 + c - 'a') : c - '0') << 4) |
         ((d >= 'a')? (10 + d - 'a') : d - '0');
      s += 2;
      }
    else *t++ = *s;
    }
  *t = 0;
  }

DEBUG(D_lookup)
  debug_printf("LDAP parameters: user=%s pass=%s size=%d time=%d connect=%d "
    "dereference=%d referrals=%s\n", user, password, sizelimit, timelimit,
    tcplimit, dereference, (referrals == LDAP_OPT_ON)? "on" : "off");

/* If the request is just to check authentication, some credentials must
be given. The password must not be empty because LDAP binds with an empty
password are considered anonymous, and will succeed on most installations. */

if (search_type == SEARCH_LDAP_AUTH)
  {
  if (user == NULL || password == NULL)
    {
    *errmsg = US"ldapauth lookups must specify the username and password";
    return DEFER;
    }
  if (password[0] == 0)
    {
    DEBUG(D_lookup) debug_printf("Empty password: ldapauth returns FAIL\n");
    return FAIL;
    }
  }

/* Check for valid ldap url starters */

p = url + 4;
if (tolower(*p) == 's' || tolower(*p) == 'i') p++;
if (Ustrncmp(p, "://", 3) != 0)
  {
  *errmsg = string_sprintf("LDAP URL does not start with \"ldap://\", "
    "\"ldaps://\", or \"ldapi://\" (it starts with \"%.16s...\")", url);
  DEBUG(D_lookup) debug_printf("LDAP query error: %s\n", *errmsg);
  return DEFER;
  }

/* No default servers, or URL contains a server name: just one attempt */

if (eldap_default_servers == NULL || p[3] != '/')
  {
  return perform_ldap_search(url, NULL, 0, search_type, res, errmsg,
    &defer_break, user, password, sizelimit, timelimit, tcplimit, dereference,
    referrals);
  }

/* Loop through the default servers until OK or FAIL */

list = eldap_default_servers;
while ((server = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
  {
  int rc;
  int port = 0;
  uschar *colon = Ustrchr(server, ':');
  if (colon != NULL)
    {
    *colon = 0;
    port = Uatoi(colon+1);
    }
  rc = perform_ldap_search(url, server, port, search_type, res, errmsg,
    &defer_break, user, password, sizelimit, timelimit, tcplimit, dereference,
    referrals);
  if (rc != DEFER || defer_break) return rc;
  }

return DEFER;
}
Esempio n. 10
0
File: dovecot.c Progetto: fanf2/exim
int auth_dovecot_server(auth_instance *ablock, uschar *data)
{
       auth_dovecot_options_block *ob =
               (auth_dovecot_options_block *)(ablock->options_block);
       struct sockaddr_un sa;
       uschar buffer[4096];
       uschar *args[8];
       uschar *auth_command;
       uschar *auth_extra_data = US"";
       int nargs, tmp;
       int cuid = 0, cont = 1, found = 0, fd, ret = DEFER;

       HDEBUG(D_auth) debug_printf("dovecot authentication\n");

       memset(&sa, 0, sizeof(sa));
       sa.sun_family = AF_UNIX;

       /* This was the original code here: it is nonsense because strncpy()
       does not return an integer. I have converted this to use the function
       that formats and checks length. PH */

       /*
       if (strncpy(sa.sun_path, ob->server_socket, sizeof(sa.sun_path)) < 0) {
       */

       if (!string_format(US sa.sun_path, sizeof(sa.sun_path), "%s",
                          ob->server_socket)) {
               auth_defer_msg = US"authentication socket path too long";
               return DEFER;
       }

       auth_defer_msg = US"authentication socket connection error";

       fd = socket(PF_UNIX, SOCK_STREAM, 0);
       if (fd < 0)
               return DEFER;

       if (connect(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0)
               goto out;

       auth_defer_msg = US"authentication socket protocol error";

       sbp = 0;  /* Socket buffer pointer */
       while (cont) {
               if (dc_gets(buffer, sizeof(buffer), fd) == NULL)
                       OUT("authentication socket read error or premature eof");

               buffer[Ustrlen(buffer) - 1] = 0;
               HDEBUG(D_auth) debug_printf("received: %s\n", buffer);
               nargs = strcut(buffer, args, sizeof(args) / sizeof(args[0]));

               /* Code below rewritten by Kirill Miazine ([email protected]). Only check commands that
                  Exim will need. Original code also failed if Dovecot server sent unknown
                  command. E.g. COOKIE in version 1.1 of the protocol would cause troubles. */
               if (Ustrcmp(args[0], US"CUID") == 0) {
                       CHECK_COMMAND("CUID", 1, 1);
                       cuid = Uatoi(args[1]);
               } else if (Ustrcmp(args[0], US"VERSION") == 0) {
                       CHECK_COMMAND("VERSION", 2, 2);
                       if (Uatoi(args[1]) != VERSION_MAJOR)
                               OUT("authentication socket protocol version mismatch");
               } else if (Ustrcmp(args[0], US"MECH") == 0) {
                       CHECK_COMMAND("MECH", 1, INT_MAX);
                       if (strcmpic(US args[1], ablock->public_name) == 0)
                               found = 1;
               } else if (Ustrcmp(args[0], US"DONE") == 0) {
                       CHECK_COMMAND("DONE", 0, 0);
                       cont = 0;
               }
       }

       if (!found)
               goto out;

       /* Added by PH: data must not contain tab (as it is
       b64 it shouldn't, but check for safety). */

       if (Ustrchr(data, '\t') != NULL) {
               ret = FAIL;
               goto out;
       }

       /* Added by PH: extra fields when TLS is in use or if the TCP/IP
       connection is local. */

       if (tls_cipher != NULL)
               auth_extra_data = string_sprintf("secured\t%s%s",
                   tls_certificate_verified? "valid-client-cert" : "",
                   tls_certificate_verified? "\t" : "");
       else if (interface_address != NULL &&
                Ustrcmp(sender_host_address, interface_address) == 0)
               auth_extra_data = US"secured\t";


/****************************************************************************
   The code below was the original code here. It didn't work. A reading of the
   file auth-protocol.txt.gz that came with Dovecot 1.0_beta8 indicated that
   this was not right. Maybe something changed. I changed it to move the
   service indication into the AUTH command, and it seems to be better. PH

       fprintf(f, "VERSION\t%d\t%d\r\nSERVICE\tSMTP\r\nCPID\t%d\r\n"
               "AUTH\t%d\t%s\trip=%s\tlip=%s\tresp=%s\r\n",
               VERSION_MAJOR, VERSION_MINOR, getpid(), cuid,
               ablock->public_name, sender_host_address, interface_address,
               data ? (char *) data : "");

   Subsequently, the command was modified to add "secured" and "valid-client-
   cert" when relevant.

   The auth protocol is documented here:
        http://wiki.dovecot.org/Authentication_Protocol
****************************************************************************/

       auth_command = string_sprintf("VERSION\t%d\t%d\nCPID\t%d\n"
               "AUTH\t%d\t%s\tservice=smtp\t%srip=%s\tlip=%s\tnologin\tresp=%s\n",
               VERSION_MAJOR, VERSION_MINOR, getpid(), cuid,
               ablock->public_name, auth_extra_data, sender_host_address,
               interface_address, data ? (char *) data : "");

       if (write(fd, auth_command, Ustrlen(auth_command)) < 0)
              HDEBUG(D_auth) debug_printf("error sending auth_command: %s\n",
                strerror(errno));

       HDEBUG(D_auth) debug_printf("sent: %s", auth_command);

       while (1) {
               uschar *temp;
               uschar *auth_id_pre = NULL;
               int i;

               if (dc_gets(buffer, sizeof(buffer), fd) == NULL) {
                       auth_defer_msg = US"authentication socket read error or premature eof";
                       goto out;
               }

               buffer[Ustrlen(buffer) - 1] = 0;
               HDEBUG(D_auth) debug_printf("received: %s\n", buffer);
               nargs = strcut(buffer, args, sizeof(args) / sizeof(args[0]));

               if (Uatoi(args[1]) != cuid)
                       OUT("authentication socket connection id mismatch");

               switch (toupper(*args[0])) {
               case 'C':
                       CHECK_COMMAND("CONT", 1, 2);

                       tmp = auth_get_no64_data(&data, US args[2]);
                       if (tmp != OK) {
                               ret = tmp;
                               goto out;
                       }

                       /* Added by PH: data must not contain tab (as it is
                       b64 it shouldn't, but check for safety). */

                       if (Ustrchr(data, '\t') != NULL) {
                               ret = FAIL;
                               goto out;
                       }

                       temp = string_sprintf("CONT\t%d\t%s\n", cuid, data);
                       if (write(fd, temp, Ustrlen(temp)) < 0)
                               OUT("authentication socket write error");
                       break;

               case 'F':
                       CHECK_COMMAND("FAIL", 1, -1);

                       for (i=2; (i<nargs) && (auth_id_pre == NULL); i++)
                       {
                               if ( Ustrncmp(args[i], US"user="******"OK", 2, -1);

                       /*
                        * Search for the "user=$USER" string in the args array
                        * and return the proper value.
                        */
                       for (i=2; (i<nargs) && (auth_id_pre == NULL); i++)
                       {
                               if ( Ustrncmp(args[i], US"user="******"authentication socket protocol error, username missing");

                       ret = OK;
                       /* fallthrough */

               default:
                       goto out;
               }
       }

out:
       /* close the socket used by dovecot */
       if (fd >= 0)
              close(fd);

       /* Expand server_condition as an authorization check */
       return (ret == OK)? auth_check_serv_cond(ablock) : ret;
}