示例#1
0
int
auth_call_pwcheck(uschar *s, uschar **errptr)
{
uschar *reply = NULL;
uschar *pw = Ustrrchr(s, ':');

if (pw == NULL)
  {
  *errptr = US"pwcheck: malformed input - missing colon";
  return ERROR;
  }

*pw++ = 0;   /* Separate user and password */

DEBUG(D_auth)
  debug_printf("Running pwcheck authentication for user \"%s\"\n", s);

switch (pwcheck_verify_password(CS s, CS pw, (const char **)(&reply)))
  {
  case PWCHECK_OK:
  DEBUG(D_auth) debug_printf("pwcheck: success (%s)\n", reply);
  return OK;

  case PWCHECK_NO:
  DEBUG(D_auth) debug_printf("pwcheck: access denied (%s)\n", reply);
  return FAIL;

  default:
  DEBUG(D_auth) debug_printf("pwcheck: query failed (%s)\n", reply);
  *errptr = reply;
  return ERROR;
  }
}
示例#2
0
void
rf_change_domain(address_item *addr, uschar *domain, BOOL rewrite,
  address_item **addr_new)
{
address_item *parent = store_get(sizeof(address_item));
uschar *at = Ustrrchr(addr->address, '@');
uschar *address = string_sprintf("%.*s@%s", at - addr->address, addr->address,
  domain);

DEBUG(D_route) debug_printf("domain changed to %s\n", domain);

/* The current address item is made into the parent, and a new address is set
up in the old space. */

*parent = *addr;

/* First copy in initializing values, to wipe out stuff such as the named
domain cache. Then copy over the propagating fields from the parent. Then set
up the new fields. */

*addr = address_defaults;
addr->p = parent->p;

addr->address = address;
addr->unique = string_copy(address);
addr->parent = parent;

addr->next = *addr_new;
*addr_new = addr;

/* Rewrite header lines if requested */

if (rewrite)
  {
  header_line *h;
  DEBUG(D_route|D_rewrite) debug_printf("rewriting header lines\n");
  for (h = header_list; h != NULL; h = h->next)
    {
    header_line *newh =
      rewrite_header(h, parent->domain, domain,
        global_rewrite_rules, rewrite_existflags, TRUE);
    if (newh != NULL)
      {
      h = newh;
      header_rewritten = TRUE;
      }
    }
  }
}
示例#3
0
文件: rda.c 项目: toddr/exim
static int
rda_exists(uschar *filename, uschar **error)
{
int rc, saved_errno;
uschar *slash;
struct stat statbuf;

if ((rc = Ustat(filename, &statbuf)) >= 0) return FILE_EXIST;
saved_errno = errno;

Ustrncpy(big_buffer, filename, big_buffer_size - 3);
sigalrm_seen = FALSE;

if (saved_errno == ENOENT)
  {
  slash = Ustrrchr(big_buffer, '/');
  Ustrcpy(slash+1, ".");

  alarm(30);
  rc = Ustat(big_buffer, &statbuf);
  if (rc != 0 && errno == EACCES && !sigalrm_seen)
    {
    *slash = 0;
    rc = Ustat(big_buffer, &statbuf);
    }
  saved_errno = errno;
  alarm(0);

  DEBUG(D_route) debug_printf("stat(%s)=%d\n", big_buffer, rc);
  }

if (sigalrm_seen || rc != 0)
  {
  *error = string_sprintf("failed to stat %s (%s)", big_buffer,
    sigalrm_seen? "timeout" : strerror(saved_errno));
  return FILE_EXIST_UNCLEAR;
  }

*error = string_sprintf("%s does not exist", filename);
DEBUG(D_route) debug_printf("%s\n", *error);
return FILE_NOT_EXIST;
}
示例#4
0
static int
perform_pgsql_search(uschar *query, uschar *server, uschar **resultptr,
  uschar **errmsg, BOOL *defer_break, BOOL *do_cache)
{
PGconn *pg_conn = NULL;
PGresult *pg_result = NULL;

int i;
int ssize = 0;
int offset = 0;
int yield = DEFER;
unsigned int num_fields, num_tuples;
uschar *result = NULL;
pgsql_connection *cn;
uschar *server_copy = NULL;
uschar *sdata[3];

/* Disaggregate the parameters from the server argument. The order is host or
path, 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 = 2; i >= 0; i--)
  {
  uschar *pp = Ustrrchr(server, '/');
  if (pp == NULL)
    {
    *errmsg = string_sprintf("incomplete pgSQL 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 */
  }

/* The total server string has now been truncated so that what is left at the
start is the identification of the server (host or path). See if we have a
cached connection to the server. */

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

/* If there is no cached connection, we must set one up. */

if (cn == NULL)
  {
  uschar *port = US"";

  /* For a Unix domain socket connection, the path is in parentheses */

  if (*server == '(')
    {
    uschar *last_slash, *last_dot, *p;

    p = ++server;
    while (*p != 0 && *p != ')') p++;
    *p = 0;

    last_slash = Ustrrchr(server, '/');
    last_dot = Ustrrchr(server, '.');

    DEBUG(D_lookup) debug_printf("PGSQL new connection: socket=%s "
      "database=%s user=%s\n", server, sdata[0], sdata[1]);

    /* A valid socket name looks like this: /var/run/postgresql/.s.PGSQL.5432
    We have to call PQsetdbLogin with '/var/run/postgresql' as the hostname
    argument and put '5432' into the port variable. */

    if (last_slash == NULL || last_dot == NULL)
      {
      *errmsg = string_sprintf("PGSQL invalid filename for socket: %s",
        server);
      *defer_break = TRUE;
      return DEFER;
      }

    /* Terminate the path name and set up the port: we'll have something like
    server = "/var/run/postgresql" and port = "5432". */

    *last_slash = 0;
    port = last_dot + 1;
    }

  /* Host connection; sort out the port */

  else
    {
    uschar *p;
    if ((p = Ustrchr(server, ':')) != NULL)
      {
      *p++ = 0;
      port = p;
      }

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

    DEBUG(D_lookup) debug_printf("PGSQL new connection: host=%s port=%s "
      "database=%s user=%s\n", server, port, sdata[0], sdata[1]);
    }

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

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

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

  pg_conn=PQsetdbLogin(
    /*  host      port  options tty   database       user       passwd */
    CS server, CS port,  NULL, NULL, CS sdata[0], CS sdata[1], CS sdata[2]);

  if(PQstatus(pg_conn) == CONNECTION_BAD)
    {
    store_reset(server_copy);
    *errmsg = string_sprintf("PGSQL connection failed: %s",
      PQerrorMessage(pg_conn));
    PQfinish(pg_conn);
    goto PGSQL_EXIT;
    }

  /* Set the client encoding to SQL_ASCII, which means that the server will
  not try to interpret the query as being in any fancy encoding such as UTF-8
  or other multibyte code that might cause problems with escaping. */

  PQsetClientEncoding(pg_conn, "SQL_ASCII");

  /* Set the notice processor to prevent notices from being written to stderr
  (which is what the default does). Our function (above) just produces debug
  output. */

  PQsetNoticeProcessor(pg_conn, notice_processor, NULL);

  /* Add the connection to the cache */

  cn = store_get(sizeof(pgsql_connection));
  cn->server = server_copy;
  cn->handle = pg_conn;
  cn->next = pgsql_connections;
  pgsql_connections = cn;
  }

/* Else use a previously cached connection */

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

/* Run the query */

  pg_result = PQexec(pg_conn, CS query);
  switch(PQresultStatus(pg_result))
    {
    case PGRES_EMPTY_QUERY:
    case PGRES_COMMAND_OK:
    /* The command was successful but did not return any data since it was
     * not SELECT but either an INSERT, UPDATE or DELETE statement. Tell the
     * high level code to not cache this query, and clean the current cache for
     * this handle by setting *do_cache FALSE. */
    result = string_copy(US PQcmdTuples(pg_result));
    offset = Ustrlen(result);
    *do_cache = FALSE;
    DEBUG(D_lookup) debug_printf("PGSQL: command does not return any data "
      "but was successful. Rows affected: %s\n", result);

    case PGRES_TUPLES_OK:
    break;

    default:
    /* This was the original code:
    *errmsg = string_sprintf("PGSQL: query failed: %s\n",
                             PQresultErrorMessage(pg_result));
    This was suggested by a user:
    */

    *errmsg = string_sprintf("PGSQL: query failed: %s (%s) (%s)\n",
                             PQresultErrorMessage(pg_result),
                             PQresStatus(PQresultStatus(pg_result)), query);
    goto PGSQL_EXIT;
    }

/* Result is in pg_result. Find the number of fields returned. If this is one,
we don't add field names to the data. Otherwise we do. If the query did not
return anything we skip the for loop; this also applies to the case
PGRES_COMMAND_OK. */

num_fields = PQnfields(pg_result);
num_tuples = PQntuples(pg_result);

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

for (i = 0; i < num_tuples; i++)
  {
  if (result != NULL)
    result = string_cat(result, &ssize, &offset, US"\n", 1);

   if (num_fields == 1)
    {
    result = string_cat(result, &ssize, &offset,
      US PQgetvalue(pg_result, i, 0), PQgetlength(pg_result, i, 0));
    }

   else
    {
    int j;
    for (j = 0; j < num_fields; j++)
      {
      uschar *tmp = US PQgetvalue(pg_result, i, j);
      result = lf_quote(US PQfname(pg_result, j), tmp, Ustrlen(tmp), result,
        &ssize, &offset);
      }
    }
  }

/* 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"PGSQL: no data found";
  }
else
  {
  result[offset] = 0;
  store_reset(result + offset + 1);
  }

/* Get here by goto from various error checks. */

PGSQL_EXIT:

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

if (pg_result != NULL) PQclear(pg_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 */
  }
}
示例#5
0
文件: redis.c 项目: usmanjsonar/exim
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 */
  }
}
示例#6
0
文件: dnslookup.c 项目: fanf2/exim
int
dnslookup_router_entry(
  router_instance *rblock,        /* data for this instantiation */
  address_item *addr,             /* address we are working on */
  struct passwd *pw,              /* passwd entry after check_local_user */
  int verify,                     /* v_none/v_recipient/v_sender/v_expn */
  address_item **addr_local,      /* add it to this if it's local */
  address_item **addr_remote,     /* add it to this if it's remote */
  address_item **addr_new,        /* put new addresses on here */
  address_item **addr_succeed)    /* put old address here on success */
{
host_item h;
int rc;
int widen_sep = 0;
int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A;
dnslookup_router_options_block *ob =
  (dnslookup_router_options_block *)(rblock->options_block);
uschar *srv_service = NULL;
uschar *widen = NULL;
uschar *pre_widen = addr->domain;
uschar *post_widen = NULL;
uschar *fully_qualified_name;
uschar *listptr;
uschar widen_buffer[256];

addr_new = addr_new;          /* Keep picky compilers happy */
addr_succeed = addr_succeed;

DEBUG(D_route)
  debug_printf("%s router called for %s\n  domain = %s\n",
    rblock->name, addr->address, addr->domain);

/* If an SRV check is required, expand the service name */

if (ob->check_srv != NULL)
  {
  srv_service = expand_string(ob->check_srv);
  if (srv_service == NULL && !expand_string_forcedfail)
    {
    addr->message = string_sprintf("%s router: failed to expand \"%s\": %s",
      rblock->name, ob->check_srv, expand_string_message);
    return DEFER;
    }
  else whichrrs |= HOST_FIND_BY_SRV;
  }

/* Set up the first of any widening domains. The code further down copes with
either pre- or post-widening, but at present there is no way to turn on
pre-widening, as actually doing so seems like a rather bad idea, and nobody has
requested it. Pre-widening would cause local abbreviated names to take
precedence over global names. For example, if the domain is "xxx.ch" it might
be something in the "ch" toplevel domain, but it also might be xxx.ch.xyz.com.
The choice of pre- or post-widening affects which takes precedence. If ever
somebody comes up with some kind of requirement for pre-widening, presumably
with some conditions under which it is done, it can be selected here.

The rewrite_headers option works only when routing an address at transport
time, because the alterations to the headers are not persistent so must be
worked out immediately before they are used. Sender addresses are routed for
verification purposes, but never at transport time, so any header changes that
you might expect as a result of sender domain widening do not occur. Therefore
we do not perform widening when verifying sender addresses; however, widening
sender addresses is OK if we do not have to rewrite the headers. A corollary
of this is that if the current address is not the original address, then it
does not appear in the message header so it is also OK to widen. The
suppression of widening for sender addresses is silent because it is the
normal desirable behaviour. */

if (ob->widen_domains != NULL &&
    (verify != v_sender || !ob->rewrite_headers || addr->parent != NULL))
  {
  listptr = ob->widen_domains;
  widen = string_nextinlist(&listptr, &widen_sep, widen_buffer,
    sizeof(widen_buffer));

/****
  if (some condition requiring pre-widening)
    {
    post_widen = pre_widen;
    pre_widen = NULL;
    }
****/
  }

/* Loop to cope with explicit widening of domains as configured. This code
copes with widening that may happen before or after the original name. The
decision as to which is taken above. */

for (;;)
  {
  int flags = whichrrs;
  BOOL removed = FALSE;

  if (pre_widen != NULL)
    {
    h.name = pre_widen;
    pre_widen = NULL;
    }
  else if (widen != NULL)
    {
    h.name = string_sprintf("%s.%s", addr->domain, widen);
    widen = string_nextinlist(&listptr, &widen_sep, widen_buffer,
      sizeof(widen_buffer));
    DEBUG(D_route) debug_printf("%s router widened %s to %s\n", rblock->name,
      addr->domain, h.name);
    }
  else if (post_widen != NULL)
    {
    h.name = post_widen;
    post_widen = NULL;
    DEBUG(D_route) debug_printf("%s router trying %s after widening failed\n",
      rblock->name, h.name);
    }
  else return DECLINE;

  /* Set up the rest of the initial host item. Others may get chained on if
  there is more than one IP address. We set it up here instead of outside the
  loop so as to re-initialize if a previous try succeeded but was rejected
  because of not having an MX record. */

  h.next = NULL;
  h.address = NULL;
  h.port = PORT_NONE;
  h.mx = MX_NONE;
  h.status = hstatus_unknown;
  h.why = hwhy_unknown;
  h.last_try = 0;

  /* Unfortunately, we cannot set the mx_only option in advance, because the
  DNS lookup may extend an unqualified name. Therefore, we must do the test
  subsequently. We use the same logic as that for widen_domains above to avoid
  requesting a header rewrite that cannot work. */

  if (verify != v_sender || !ob->rewrite_headers || addr->parent != NULL)
    {
    if (ob->qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
    if (ob->search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
    }

  rc = host_find_bydns(&h, rblock->ignore_target_hosts, flags, srv_service,
    ob->srv_fail_domains, ob->mx_fail_domains, &fully_qualified_name, &removed);
  if (removed) setflag(addr, af_local_host_removed);

  /* If host found with only address records, test for the domain's being in
  the mx_domains list. Note that this applies also to SRV records; the name of
  the option is historical. */

  if ((rc == HOST_FOUND || rc == HOST_FOUND_LOCAL) && h.mx < 0 &&
       ob->mx_domains != NULL)
    {
    switch(match_isinlist(fully_qualified_name, &(ob->mx_domains), 0,
          &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL))
      {
      case DEFER:
      addr->message = US"lookup defer for mx_domains";
      return DEFER;

      case OK:
      DEBUG(D_route) debug_printf("%s router rejected %s: no MX record(s)\n",
        rblock->name, fully_qualified_name);
      continue;
      }
    }

  /* Deferral returns forthwith, and anything other than failure breaks the
  loop. */

  if (rc == HOST_FIND_AGAIN)
    {
    if (rblock->pass_on_timeout)
      {
      DEBUG(D_route) debug_printf("%s router timed out, and pass_on_timeout is set\n",
        rblock->name);
      return PASS;
      }
    addr->message = US"host lookup did not complete";
    return DEFER;
    }

  if (rc != HOST_FIND_FAILED) break;

  /* Check to see if the failure is the result of MX records pointing to
  non-existent domains, and if so, set an appropriate error message; the case
  of an MX or SRV record pointing to "." is another special case that we can
  detect. Otherwise "unknown mail domain" is used, which is confusing. Also, in
  this case don't do the widening. We need check only the first host to see if
  its MX has been filled in, but there is no address, because if there were any
  usable addresses returned, we would not have had HOST_FIND_FAILED.

  As a common cause of this problem is MX records with IP addresses on the
  RHS, give a special message in this case. */

  if (h.mx >= 0 && h.address == NULL)
    {
    setflag(addr, af_pass_message);   /* This is not a security risk */
    if (h.name[0] == 0)
      addr->message = US"an MX or SRV record indicated no SMTP service";
    else
      {
      addr->message = US"all relevant MX records point to non-existent hosts";
      if (!allow_mx_to_ip && string_is_ip_address(h.name, NULL) != 0)
        {
        addr->user_message =
          string_sprintf("It appears that the DNS operator for %s\n"
            "has installed an invalid MX record with an IP address\n"
            "instead of a domain name on the right hand side.", addr->domain);
        addr->message = string_sprintf("%s or (invalidly) to IP addresses",
          addr->message);
        }
      }
    return DECLINE;
    }

  /* If there's a syntax error, do not continue with any widening, and note
  the error. */

  if (host_find_failed_syntax)
    {
    addr->message = string_sprintf("mail domain \"%s\" is syntactically "
      "invalid", h.name);
    return DECLINE;
    }
  }

/* If the original domain name has been changed as a result of the host lookup,
set up a child address for rerouting and request header rewrites if so
configured. Then yield REROUTED. However, if the only change is a change of
case in the domain name, which some resolvers yield (others don't), just change
the domain name in the original address so that the official version is used in
RCPT commands. */

if (Ustrcmp(addr->domain, fully_qualified_name) != 0)
  {
  if (strcmpic(addr->domain, fully_qualified_name) == 0)
    {
    uschar *at = Ustrrchr(addr->address, '@');
    memcpy(at+1, fully_qualified_name, Ustrlen(at+1));
    }
  else
    {
    rf_change_domain(addr, fully_qualified_name, ob->rewrite_headers, addr_new);
    return REROUTED;
    }
  }

/* If the yield is HOST_FOUND_LOCAL, the remote domain name either found MX
records with the lowest numbered one pointing to a host with an IP address that
is set on one of the interfaces of this machine, or found A records or got
addresses from gethostbyname() that contain one for this machine. This can
happen quite legitimately if the original name was a shortened form of a
domain, but we will have picked that up already via the name change test above.

Otherwise, the action to be taken can be configured by the self option, the
handling of which is in a separate function, as it is also required for other
routers. */

if (rc == HOST_FOUND_LOCAL)
  {
  rc = rf_self_action(addr, &h, rblock->self_code, rblock->self_rewrite,
    rblock->self, addr_new);
  if (rc != OK) return rc;
  }

/* Otherwise, insist on being a secondary MX if so configured */

else if (ob->check_secondary_mx && !testflag(addr, af_local_host_removed))
  {
  DEBUG(D_route) debug_printf("check_secondary_mx set and local host not secondary\n");
  return DECLINE;
  }

/* Set up the errors address, if any. */

rc = rf_get_errors_address(addr, rblock, verify, &(addr->p.errors_address));
if (rc != OK) return rc;

/* Set up the additional and removeable headers for this address. */

rc = rf_get_munge_headers(addr, rblock, &(addr->p.extra_headers),
  &(addr->p.remove_headers));
if (rc != OK) return rc;

/* Get store in which to preserve the original host item, chained on
to the address. */

addr->host_list = store_get(sizeof(host_item));
addr->host_list[0] = h;

/* Fill in the transport and queue the address for delivery. */

if (!rf_get_transport(rblock->transport_name, &(rblock->transport),
      addr, rblock->name, NULL))
  return DEFER;

addr->transport = rblock->transport;

return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)?
  OK : DEFER;
}
示例#7
0
文件: dns.c 项目: loganaden/exim
int
dns_special_lookup(dns_answer *dnsa, const uschar *name, int type,
  const uschar **fully_qualified_name)
{
switch (type)
  {
  /* The "mx hosts only" type doesn't require any special action here */
  case T_MXH:
    return dns_lookup(dnsa, name, T_MX, fully_qualified_name);

  /* Find nameservers for the domain or the nearest enclosing zone, excluding
  the root servers. */
  case T_ZNS:
    type = T_NS;
    /* FALLTHROUGH */
  case T_SOA:
    {
    const uschar *d = name;
    while (d != 0)
      {
      int rc = dns_lookup(dnsa, d, type, fully_qualified_name);
      if (rc != DNS_NOMATCH && rc != DNS_NODATA) return rc;
      while (*d != 0 && *d != '.') d++;
      if (*d++ == 0) break;
      }
    return DNS_NOMATCH;
    }

  /* Try to look up the Client SMTP Authorization SRV record for the name. If
  there isn't one, search from the top downwards for a CSA record in a parent
  domain, which might be making assertions about subdomains. If we find a record
  we set fully_qualified_name to whichever lookup succeeded, so that the caller
  can tell whether to look at the explicit authorization field or the subdomain
  assertion field. */
  case T_CSA:
    {
    uschar *srvname, *namesuff, *tld, *p;
    int priority, weight, port;
    int limit, rc, i;
    BOOL ipv6;
    dns_record *rr;
    dns_scan dnss;

    DEBUG(D_dns) debug_printf("CSA lookup of %s\n", name);

    srvname = string_sprintf("_client._smtp.%s", name);
    rc = dns_lookup(dnsa, srvname, T_SRV, NULL);
    if (rc == DNS_SUCCEED || rc == DNS_AGAIN)
      {
      if (rc == DNS_SUCCEED) *fully_qualified_name = string_copy(name);
      return rc;
      }

    /* Search for CSA subdomain assertion SRV records from the top downwards,
    starting with the 2nd level domain. This order maximizes cache-friendliness.
    We skip the top level domains to avoid loading their nameservers and because
    we know they'll never have CSA SRV records. */

    namesuff = Ustrrchr(name, '.');
    if (namesuff == NULL) return DNS_NOMATCH;
    tld = namesuff + 1;
    ipv6 = FALSE;
    limit = dns_csa_search_limit;

    /* Use more appropriate search parameters if we are in the reverse DNS. */

    if (strcmpic(namesuff, US".arpa") == 0)
      if (namesuff - 8 > name && strcmpic(namesuff - 8, US".in-addr.arpa") == 0)
	{
	namesuff -= 8;
	tld = namesuff + 1;
	limit = 3;
	}
      else if (namesuff - 4 > name && strcmpic(namesuff - 4, US".ip6.arpa") == 0)
	{
	namesuff -= 4;
	tld = namesuff + 1;
	ipv6 = TRUE;
	limit = 3;
	}

    DEBUG(D_dns) debug_printf("CSA TLD %s\n", tld);

    /* Do not perform the search if the top level or 2nd level domains do not
    exist. This is quite common, and when it occurs all the search queries would
    go to the root or TLD name servers, which is not friendly. So we check the
    AUTHORITY section; if it contains the root's SOA record or the TLD's SOA then
    the TLD or the 2LD (respectively) doesn't exist and we can skip the search.
    If the TLD and the 2LD exist but the explicit CSA record lookup failed, then
    the AUTHORITY SOA will be the 2LD's or a subdomain thereof. */

    if (rc == DNS_NOMATCH)
      {
      /* This is really gross. The successful return value from res_search() is
      the packet length, which is stored in dnsa->answerlen. If we get a
      negative DNS reply then res_search() returns -1, which causes the bounds
      checks for name decompression to fail when it is treated as a packet
      length, which in turn causes the authority search to fail. The correct
      packet length has been lost inside libresolv, so we have to guess a
      replacement value. (The only way to fix this properly would be to
      re-implement res_search() and res_query() so that they don't muddle their
      success and packet length return values.) For added safety we only reset
      the packet length if the packet header looks plausible. */

      HEADER *h = (HEADER *)dnsa->answer;
      if (h->qr == 1 && h->opcode == QUERY && h->tc == 0
	  && (h->rcode == NOERROR || h->rcode == NXDOMAIN)
	  && ntohs(h->qdcount) == 1 && ntohs(h->ancount) == 0
	  && ntohs(h->nscount) >= 1)
	    dnsa->answerlen = MAXPACKET;

      for (rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY);
	   rr;
	   rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
	  )
	if (rr->type != T_SOA) continue;
	else if (strcmpic(rr->name, US"") == 0 ||
		 strcmpic(rr->name, tld) == 0) return DNS_NOMATCH;
	else break;
      }

    for (i = 0; i < limit; i++)
      {
      if (ipv6)
	{
	/* Scan through the IPv6 reverse DNS in chunks of 16 bits worth of IP
	address, i.e. 4 hex chars and 4 dots, i.e. 8 chars. */
	namesuff -= 8;
	if (namesuff <= name) return DNS_NOMATCH;
	}
      else
	/* Find the start of the preceding domain name label. */
	do
	  if (--namesuff <= name) return DNS_NOMATCH;
	while (*namesuff != '.');

      DEBUG(D_dns) debug_printf("CSA parent search at %s\n", namesuff + 1);

      srvname = string_sprintf("_client._smtp.%s", namesuff + 1);
      rc = dns_lookup(dnsa, srvname, T_SRV, NULL);
      if (rc == DNS_AGAIN) return rc;
      if (rc != DNS_SUCCEED) continue;

      /* Check that the SRV record we have found is worth returning. We don't
      just return the first one we find, because some lower level SRV record
      might make stricter assertions than its parent domain. */

      for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
	   rr;
	   rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
	{
	if (rr->type != T_SRV) continue;

	/* Extract the numerical SRV fields (p is incremented) */
	p = rr->data;
	GETSHORT(priority, p);
	GETSHORT(weight, p);	weight = weight; /* compiler quietening */
	GETSHORT(port, p);

	/* Check the CSA version number */
	if (priority != 1) continue;

	/* If it's making an interesting assertion, return this response. */
	if (port & 1)
	  {
	  *fully_qualified_name = namesuff + 1;
	  return DNS_SUCCEED;
	  }
	}
      }
    return DNS_NOMATCH;
    }

  default:
    if (type >= 0)
      return dns_lookup(dnsa, name, type, fully_qualified_name);
  }

/* Control should never reach here */

return DNS_FAIL;
}
示例#8
0
open_db *
dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof)
{
int rc, save_errno;
BOOL read_only = flags == O_RDONLY;
BOOL created = FALSE;
flock_t lock_data;
uschar buffer[256];

/* The first thing to do is to open a separate file on which to lock. This
ensures that Exim has exclusive use of the database before it even tries to
open it. Early versions tried to lock on the open database itself, but that
gave rise to mysterious problems from time to time - it was suspected that some
DB libraries "do things" on their open() calls which break the interlocking.
The lock file is never written to, but we open it for writing so we can get a
write lock if required. If it does not exist, we create it. This is done
separately so we know when we have done it, because when running as root we
need to change the ownership - see the bottom of this function. We also try to
make the directory as well, just in case. We won't be doing this many times
unnecessarily, because usually the lock file will be there. If the directory
exists, there is no error. */

sprintf(CS buffer, "%s/db/%s.lockfile", spool_directory, name);

if ((dbblock->lockfd = Uopen(buffer, O_RDWR, EXIMDB_LOCKFILE_MODE)) < 0)
  {
  created = TRUE;
  (void)directory_make(spool_directory, US"db", EXIMDB_DIRECTORY_MODE, TRUE);
  dbblock->lockfd = Uopen(buffer, O_RDWR|O_CREAT, EXIMDB_LOCKFILE_MODE);
  }

if (dbblock->lockfd < 0)
  {
  log_write(0, LOG_MAIN, "%s",
    string_open_failed(errno, "database lock file %s", buffer));
  errno = 0;      /* Indicates locking failure */
  return NULL;
  }

/* Now we must get a lock on the opened lock file; do this with a blocking
lock that times out. */

lock_data.l_type = read_only? F_RDLCK : F_WRLCK;
lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;

DEBUG(D_hints_lookup|D_retry|D_route|D_deliver)
  debug_printf("locking %s\n", buffer);

sigalrm_seen = FALSE;
alarm(EXIMDB_LOCK_TIMEOUT);
rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
alarm(0);

if (sigalrm_seen) errno = ETIMEDOUT;
if (rc < 0)
  {
  log_write(0, LOG_MAIN|LOG_PANIC, "Failed to get %s lock for %s: %s",
    read_only? "read" : "write", buffer,
    (errno == ETIMEDOUT)? "timed out" : strerror(errno));
  (void)close(dbblock->lockfd);
  errno = 0;       /* Indicates locking failure */
  return NULL;
  }

DEBUG(D_hints_lookup) debug_printf("locked %s\n", buffer);

/* At this point we have an opened and locked separate lock file, that is,
exclusive access to the database, so we can go ahead and open it. If we are
expected to create it, don't do so at first, again so that we can detect
whether we need to change its ownership (see comments about the lock file
above.) There have been regular reports of crashes while opening hints
databases - often this is caused by non-matching db.h and the library. To make
it easy to pin this down, there are now debug statements on either side of the
open call. */

sprintf(CS buffer, "%s/db/%s", spool_directory, name);
DEBUG(D_hints_lookup) debug_printf("EXIM_DBOPEN(%s)\n", buffer);
EXIM_DBOPEN(buffer, flags, EXIMDB_MODE, &(dbblock->dbptr));
DEBUG(D_hints_lookup) debug_printf("returned from EXIM_DBOPEN\n");

if (dbblock->dbptr == NULL && errno == ENOENT && flags == O_RDWR)
  {
  DEBUG(D_hints_lookup)
    debug_printf("%s appears not to exist: trying to create\n", buffer);
  created = TRUE;
  EXIM_DBOPEN(buffer, flags|O_CREAT, EXIMDB_MODE, &(dbblock->dbptr));
  DEBUG(D_hints_lookup) debug_printf("returned from EXIM_DBOPEN\n");
  }

save_errno = errno;

/* If we are running as root and this is the first access to the database, its
files will be owned by root. We want them to be owned by exim. We detect this
situation by noting above when we had to create the lock file or the database
itself. Because the different dbm libraries use different extensions for their
files, I don't know of any easier way of arranging this than scanning the
directory for files with the appropriate base name. At least this deals with
the lock file at the same time. Also, the directory will typically have only
half a dozen files, so the scan will be quick.

This code is placed here, before the test for successful opening, because there
was a case when a file was created, but the DBM library still returned NULL
because of some problem. It also sorts out the lock file if that was created
but creation of the database file failed. */

if (created && geteuid() == root_uid)
  {
  DIR *dd;
  struct dirent *ent;
  uschar *lastname = Ustrrchr(buffer, '/') + 1;
  int namelen = Ustrlen(name);

  *lastname = 0;
  dd = opendir(CS buffer);

  while ((ent = readdir(dd)) != NULL)
    {
    if (Ustrncmp(ent->d_name, name, namelen) == 0)
      {
      struct stat statbuf;
      Ustrcpy(lastname, ent->d_name);
      if (Ustat(buffer, &statbuf) >= 0 && statbuf.st_uid != exim_uid)
        {
        DEBUG(D_hints_lookup) debug_printf("ensuring %s is owned by exim\n", buffer);
        (void)Uchown(buffer, exim_uid, exim_gid);
        }
      }
    }

  closedir(dd);
  }

/* If the open has failed, return NULL, leaving errno set. If lof is TRUE,
log the event - also for debugging - but not if the file just doesn't exist. */

if (dbblock->dbptr == NULL)
  {
  if (save_errno != ENOENT)
    {
    if (lof)
      log_write(0, LOG_MAIN, "%s", string_open_failed(save_errno, "DB file %s",
        buffer));
    else
      DEBUG(D_hints_lookup)
        debug_printf("%s", CS string_open_failed(save_errno, "DB file %s\n",
          buffer));
    }
  (void)close(dbblock->lockfd);
  errno = save_errno;
  return NULL;
  }

DEBUG(D_hints_lookup)
  debug_printf("opened hints database %s: flags=%s\n", buffer,
    (flags == O_RDONLY)? "O_RDONLY" : (flags == O_RDWR)? "O_RDWR" :
    (flags == (O_RDWR|O_CREAT))? "O_RDWR|O_CREAT" : "??");

/* Pass back the block containing the opened database handle and the open fd
for the lock. */

return dbblock;
}
示例#9
0
文件: oracle.c 项目: akissa/exim
static int
perform_oracle_search(uschar *query, uschar *server, uschar **resultptr,
  uschar **errmsg, BOOL *defer_break)
{
Cda_Def *cda = NULL;
struct cda_def *oracle_handle = NULL;
Ora_Describe *desc = NULL;
Ora_Define *def = NULL;
void *hda = NULL;

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

/* 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 ORACLE server data: %s", server);
    *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 */

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

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

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

for (cn = oracle_connections; cn != NULL; cn = cn->next)
  {
  if (strcmp(cn->server, server_copy) == 0)
    {
    oracle_handle = cn->handle;
    hda = cn->hda_mem;
    break;
    }
  }

/* If no cached connection, we must set one up */

if (cn == NULL)
  {
  DEBUG(D_lookup) debug_printf("ORACLE new connection: host=%s database=%s "
    "user=%s\n", sdata[0], sdata[1], sdata[2]);

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

   oracle_handle = store_get(sizeof(struct cda_def));
   hda = store_get(HDA_SIZE);
   memset(hda,'\0',HDA_SIZE);

  /*
   * Perform a default (blocking) login
   *
   * sdata[0] = tnsname (service name - typically host name)
   * sdata[1] = dbname - not used at present
   * sdata[2] = username
   * sdata[3] = passwd
   */

  if(olog(oracle_handle, hda, sdata[2], -1, sdata[3], -1, sdata[0], -1,
         (ub4)OCI_LM_DEF) != 0)
    {
    *errmsg = oracle_error(oracle_handle, oracle_handle->rc,
      US"connection failed");
    *defer_break = FALSE;
    goto ORACLE_EXIT_NO_VALS;
    }

  /* Add the connection to the cache */

  cn = store_get(sizeof(oracle_connection));
  cn->server = server_copy;
  cn->handle = oracle_handle;
  cn->next = oracle_connections;
  cn->hda_mem = hda;
  oracle_connections = cn;
  }

/* Else use a previously cached connection - we can write to the server string
to obliterate the password because it is in a nextinlist temporary buffer. */

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

/* We have a connection. Open a cursor and run the query */

cda = store_get(sizeof(Cda_Def));

if (oopen(cda, oracle_handle, (text *)0, -1, -1, (text *)0, -1) != 0)
  {
  *errmsg = oracle_error(oracle_handle, cda->rc, "failed to open cursor");
  *defer_break = FALSE;
  goto ORACLE_EXIT_NO_VALS;
  }

if (oparse(cda, (text *)query, (sb4) -1,
      (sword)PARSE_NO_DEFER, (ub4)PARSE_V7_LNG) != 0)
  {
  *errmsg = oracle_error(oracle_handle, cda->rc, "query failed");
  *defer_break = FALSE;
  oclose(cda);
  goto ORACLE_EXIT_NO_VALS;
  }

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

def = store_get(sizeof(Ora_Define)*MAX_SELECT_LIST_SIZE);
desc = store_get(sizeof(Ora_Describe)*MAX_SELECT_LIST_SIZE);

if ((num_fields = describe_define(cda,def,desc)) == -1)
  {
  *errmsg = oracle_error(oracle_handle, cda->rc, "describe_define failed");
  *defer_break = FALSE;
  goto ORACLE_EXIT;
  }

if (oexec(cda)!=0)
  {
  *errmsg = oracle_error(oracle_handle, cda->rc, "oexec failed");
  *defer_break = FALSE;
  goto ORACLE_EXIT;
  }

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

while (cda->rc != NO_DATA_FOUND)  /* Loop for each row */
  {
  ofetch(cda);
  if(cda->rc == NO_DATA_FOUND) break;

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

  /* Single field - just add on the data */

  if (num_fields == 1)
    result = string_cat(result, &ssize, &offset, def[0].buf, def[0].col_retlen);

  /* Multiple fields - precede by file name, removing {lead,trail}ing WS */

  else for (i = 0; i < num_fields; i++)
    {
    int slen;
    uschar *s = US desc[i].buf;

    while (*s != 0 && isspace(*s)) s++;
    slen = Ustrlen(s);
    while (slen > 0 && isspace(s[slen-1])) slen--;
    result = string_cat(result, &ssize, &offset, s, slen);
    result = string_cat(result, &ssize, &offset, US"=", 1);

    /* int and float type wont ever need escaping. Otherwise, quote the value
    if it contains spaces or is empty. */

    if (desc[i].dbtype != INT_TYPE && desc[i].dbtype != FLOAT_TYPE &&
       (def[i].buf[0] == 0 || strchr(def[i].buf, ' ') != NULL))
      {
      int j;
      result = string_cat(result, &ssize, &offset, "\"", 1);
      for (j = 0; j < def[i].col_retlen; j++)
        {
        if (def[i].buf[j] == '\"' || def[i].buf[j] == '\\')
          result = string_cat(result, &ssize, &offset, "\\", 1);
        result = string_cat(result, &ssize, &offset, def[i].buf+j, 1);
        }
      result = string_cat(result, &ssize, &offset, "\"", 1);
      }

    else switch(desc[i].dbtype)
      {
      case INT_TYPE:
      sprintf(CS tmp, "%d", def[i].int_buf);
      result = string_cat(result, &ssize, &offset, tmp, Ustrlen(tmp));
      break;

      case FLOAT_TYPE:
      sprintf(CS tmp, "%f", def[i].flt_buf);
      result = string_cat(result, &ssize, &offset, tmp, Ustrlen(tmp));
      break;

      case STRING_TYPE:
      result = string_cat(result, &ssize, &offset, def[i].buf,
        def[i].col_retlen);
      break;

      default:
      *errmsg = string_sprintf("ORACLE: unknown field type %d", desc[i].dbtype);
      *defer_break = FALSE;
      result = NULL;
      goto ORACLE_EXIT;
      }

    result = string_cat(result, &ssize, &offset, " ", 1);
    }
  }

/* 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 = "ORACLE: no data found";
  }
else
  {
  result[offset] = 0;
  store_reset(result + offset + 1);
  }

/* Get here by goto from various error checks. */

ORACLE_EXIT:

/* Close the cursor; don't close the connection, as it is cached. */

oclose(cda);

ORACLE_EXIT_NO_VALS:

/* 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 */
  }
}
示例#10
0
文件: mysql.c 项目: loganaden/exim
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 */
  }
}
示例#11
0
文件: search.c 项目: BHYCHIK/exim
uschar *
search_find(void *handle, uschar *filename, uschar *keystring, int partial,
  const uschar *affix, int affixlen, int starflags, int *expand_setup)
{
tree_node *t = (tree_node *)handle;
BOOL set_null_wild = FALSE;
uschar *yield;

DEBUG(D_lookup)
  {
  if (partial < 0) affixlen = 99;   /* So that "NULL" prints */
  debug_printf("search_find: file=\"%s\"\n  key=\"%s\" "
    "partial=%d affix=%.*s starflags=%x\n",
    (filename == NULL)? US"NULL" : filename,
    keystring, partial, affixlen, affix, starflags);
  }

/* Arrange to put this database at the top of the LRU chain if it is a type
that opens real files. */

if (open_top != (tree_node *)handle &&
    lookup_list[t->name[0]-'0']->type == lookup_absfile)
  {
  search_cache *c = (search_cache *)(t->data.ptr);
  tree_node *up = c->up;
  tree_node *down = c->down;

  /* Cut it out of the list. A newly opened file will a NULL up pointer.
  Otherwise there will be a non-NULL up pointer, since we checked above that
  this block isn't already at the top of the list. */

  if (up != NULL)
    {
    ((search_cache *)(up->data.ptr))->down = down;
    if (down != NULL)
      ((search_cache *)(down->data.ptr))->up = up;
    else open_bot = up;
    }

  /* Now put it at the head of the list. */

  c->up = NULL;
  c->down = open_top;
  if (open_top == NULL) open_bot = t; else
    ((search_cache *)(open_top->data.ptr))->up = t;
  open_top = t;
  }

DEBUG(D_lookup)
  {
  tree_node *t = open_top;
  debug_printf("LRU list:\n");
  while (t != NULL)
    {
    search_cache *c = (search_cache *)(t->data.ptr);
    debug_printf("  %s\n", t->name);
    if (t == open_bot) debug_printf("  End\n");
    t = c->down;
    }
  }

/* First of all, try to match the key string verbatim. If matched a complete
entry but could have been partial, flag to set up variables. */

yield = internal_search_find(handle, filename, keystring);
if (search_find_defer) return NULL;
if (yield != NULL) { if (partial >= 0) set_null_wild = TRUE; }

/* Not matched a complete entry; handle partial lookups, but only if the full
search didn't defer. Don't use string_sprintf() to construct the initial key,
just in case the original key is too long for the string_sprintf() buffer (it
*has* happened!). The case of a zero-length affix has to be treated specially.
*/

else if (partial >= 0)
  {
  int len = Ustrlen(keystring);
  uschar *keystring2;

  /* Try with the affix on the front, except for a zero-length affix */

  if (affixlen == 0) keystring2 = keystring; else
    {
    keystring2 = store_get(len + affixlen + 1);
    Ustrncpy(keystring2, affix, affixlen);
    Ustrcpy(keystring2 + affixlen, keystring);
    DEBUG(D_lookup) debug_printf("trying partial match %s\n", keystring2);
    yield = internal_search_find(handle, filename, keystring2);
    if (search_find_defer) return NULL;
    }

  /* The key in its entirety did not match a wild entry; try chopping off
  leading components. */

  if (yield == NULL)
    {
    int dotcount = 0;
    uschar *keystring3 = keystring2 + affixlen;
    uschar *s = keystring3;
    while (*s != 0) if (*s++ == '.') dotcount++;

    while (dotcount-- >= partial)
      {
      while (*keystring3 != 0 && *keystring3 != '.') keystring3++;

      /* If we get right to the end of the string (which will be the last time
      through this loop), we've failed if the affix is null. Otherwise do one
      last lookup for the affix itself, but if it is longer than 1 character,
      remove the last character if it is ".". */

      if (*keystring3 == 0)
        {
        if (affixlen < 1) break;
        if (affixlen > 1 && affix[affixlen-1] == '.') affixlen--;
        Ustrncpy(keystring2, affix, affixlen);
        keystring2[affixlen] = 0;
        keystring3 = keystring2;
        }
      else
        {
        keystring3 -= affixlen - 1;
        if (affixlen > 0) Ustrncpy(keystring3, affix, affixlen);
        }

      DEBUG(D_lookup) debug_printf("trying partial match %s\n", keystring3);
      yield = internal_search_find(handle, filename, keystring3);
      if (search_find_defer) return NULL;
      if (yield != NULL)
        {
        /* First variable is the wild part; second is the fixed part. Take care
        to get it right when keystring3 is just "*". */

        if (expand_setup != NULL && *expand_setup >= 0)
          {
          int fixedlength = Ustrlen(keystring3) - affixlen;
          int wildlength = Ustrlen(keystring) - fixedlength - 1;
          *expand_setup += 1;
          expand_nstring[*expand_setup] = keystring;
          expand_nlength[*expand_setup] = wildlength;
          *expand_setup += 1;
          expand_nstring[*expand_setup] = keystring + wildlength + 1;
          expand_nlength[*expand_setup] = (fixedlength < 0)? 0 : fixedlength;
          }
        break;
        }
      keystring3 += affixlen;
      }
    }

  else set_null_wild = TRUE; /* Matched a wild entry without any wild part */
  }

/* If nothing has been matched, but the option to look for "*@" is set, try
replacing everthing to the left of @ by *. After a match, the wild part
is set to the string to the left of the @. */

if (yield == NULL && (starflags & SEARCH_STARAT) != 0)
  {
  uschar *atat = Ustrrchr(keystring, '@');
  if (atat != NULL && atat > keystring)
    {
    int savechar;
    savechar = *(--atat);
    *atat = '*';

    DEBUG(D_lookup) debug_printf("trying default match %s\n", atat);
    yield = internal_search_find(handle, filename, atat);
    *atat = savechar;
    if (search_find_defer) return NULL;

    if (yield != NULL && expand_setup != NULL && *expand_setup >= 0)
      {
      *expand_setup += 1;
      expand_nstring[*expand_setup] = keystring;
      expand_nlength[*expand_setup] = atat - keystring + 1;
      *expand_setup += 1;
      expand_nstring[*expand_setup] = keystring;
      expand_nlength[*expand_setup] = 0;
      }
    }
  }

/* If we still haven't matched anything, and the option to look for "*" is set,
try that. If we do match, the first variable (the wild part) is the whole key,
and the second is empty. */

if (yield == NULL && (starflags & (SEARCH_STAR|SEARCH_STARAT)) != 0)
  {
  DEBUG(D_lookup) debug_printf("trying to match *\n");
  yield = internal_search_find(handle, filename, US"*");
  if (yield != NULL && expand_setup != NULL && *expand_setup >= 0)
    {
    *expand_setup += 1;
    expand_nstring[*expand_setup] = keystring;
    expand_nlength[*expand_setup] = Ustrlen(keystring);
    *expand_setup += 1;
    expand_nstring[*expand_setup] = keystring;
    expand_nlength[*expand_setup] = 0;
    }
  }

/* If this was a potentially partial lookup, and we matched either a
complete non-wild domain entry, or we matched a wild-carded entry without
chopping off any of the domain components, set up the expansion variables
(if required) so that the first one is empty, and the second one is the
fixed part of the domain. The set_null_wild flag is set only when yield is not
NULL. */

if (set_null_wild && expand_setup != NULL && *expand_setup >= 0)
  {
  *expand_setup += 1;
  expand_nstring[*expand_setup] = keystring;
  expand_nlength[*expand_setup] = 0;
  *expand_setup += 1;
  expand_nstring[*expand_setup] = keystring;
  expand_nlength[*expand_setup] = Ustrlen(keystring);
  }

return yield;
}
示例#12
0
文件: ibase.c 项目: ulwanski/exim
static int
perform_ibase_search(uschar * query, uschar * server, uschar ** resultptr,
                     uschar ** errmsg, BOOL * defer_break)
{
    isc_stmt_handle stmth = NULL;
    XSQLDA *out_sqlda;
    XSQLVAR *var;

    char buffer[256];
    ISC_STATUS status[20], *statusp = status;

    int i;
    int ssize = 0;
    int offset = 0;
    int yield = DEFER;
    uschar *result = NULL;
    ibase_connection *cn;
    uschar *server_copy = NULL;
    uschar *sdata[3];

/* 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 = 2; i > 0; i--) {
        uschar *pp = Ustrrchr(server, '|');
        if (pp == NULL) {
            *errmsg =
                string_sprintf("incomplete Interbase server data: %s",
                               (i == 3) ? 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 */

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

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

/* Use a previously cached connection ? */

    if (cn != NULL) {
        static char db_info_options[] = { isc_info_base_level };

        /* test if the connection is alive */
        if (isc_database_info
            (status, &cn->dbh, sizeof(db_info_options), db_info_options,
             sizeof(buffer), buffer)) {
            /* error occurred: assume connection is down */
            DEBUG(D_lookup)
                debug_printf
                ("Interbase cleaning up cached connection: %s\n",
                 cn->server);
            isc_detach_database(status, &cn->dbh);
        } else {
            DEBUG(D_lookup)
                debug_printf("Interbase using cached connection for %s\n",
                             server_copy);
        }
    } else {
        cn = store_get(sizeof(ibase_connection));
        cn->server = server_copy;
        cn->dbh = NULL;
        cn->transh = NULL;
        cn->next = ibase_connections;
        ibase_connections = cn;
    }

/* If no cached connection, we must set one up. */

    if (cn->dbh == NULL || cn->transh == NULL) {

        char *dpb, *p;
        short dpb_length;
        static char trans_options[] =
            { isc_tpb_version3, isc_tpb_read, isc_tpb_read_committed,
            isc_tpb_rec_version
        };

        /* Construct the database parameter buffer. */
        dpb = buffer;
        *dpb++ = isc_dpb_version1;
        *dpb++ = isc_dpb_user_name;
        *dpb++ = strlen(sdata[1]);
        for (p = sdata[1]; *p;)
            *dpb++ = *p++;
        *dpb++ = isc_dpb_password;
        *dpb++ = strlen(sdata[2]);
        for (p = sdata[2]; *p;)
            *dpb++ = *p++;
        dpb_length = dpb - buffer;

        DEBUG(D_lookup)
            debug_printf("new Interbase connection: database=%s user=%s\n",
                         sdata[0], sdata[1]);

        /* Connect to the database */
        if (isc_attach_database
            (status, 0, sdata[0], &cn->dbh, dpb_length, buffer)) {
            isc_interprete(buffer, &statusp);
            *errmsg =
                string_sprintf("Interbase attach() failed: %s", buffer);
            *defer_break = FALSE;
            goto IBASE_EXIT;
        }

        /* Now start a read-only read-committed transaction */
        if (isc_start_transaction
            (status, &cn->transh, 1, &cn->dbh, sizeof(trans_options),
             trans_options)) {
            isc_interprete(buffer, &statusp);
            isc_detach_database(status, &cn->dbh);
            *errmsg =
                string_sprintf("Interbase start_transaction() failed: %s",
                               buffer);
            *defer_break = FALSE;
            goto IBASE_EXIT;
        }
    }

/* Run the query */
    if (isc_dsql_allocate_statement(status, &cn->dbh, &stmth)) {
        isc_interprete(buffer, &statusp);
        *errmsg =
            string_sprintf("Interbase alloc_statement() failed: %s",
                           buffer);
        *defer_break = FALSE;
        goto IBASE_EXIT;
    }

    out_sqlda = store_get(XSQLDA_LENGTH(1));
    out_sqlda->version = SQLDA_VERSION1;
    out_sqlda->sqln = 1;

    if (isc_dsql_prepare
        (status, &cn->transh, &stmth, 0, query, 1, out_sqlda)) {
        isc_interprete(buffer, &statusp);
        store_reset(out_sqlda);
        out_sqlda = NULL;
        *errmsg =
            string_sprintf("Interbase prepare_statement() failed: %s",
                           buffer);
        *defer_break = FALSE;
        goto IBASE_EXIT;
    }

/* re-allocate the output structure if there's more than one field */
    if (out_sqlda->sqln < out_sqlda->sqld) {
        XSQLDA *new_sqlda = store_get(XSQLDA_LENGTH(out_sqlda->sqld));
        if (isc_dsql_describe
            (status, &stmth, out_sqlda->version, new_sqlda)) {
            isc_interprete(buffer, &statusp);
            isc_dsql_free_statement(status, &stmth, DSQL_drop);
            store_reset(out_sqlda);
            out_sqlda = NULL;
            *errmsg =
                string_sprintf("Interbase describe_statement() failed: %s",
                               buffer);
            *defer_break = FALSE;
            goto IBASE_EXIT;
        }
        out_sqlda = new_sqlda;
    }

/* allocate storage for every returned field */
    for (i = 0, var = out_sqlda->sqlvar; i < out_sqlda->sqld; i++, var++) {
        switch (var->sqltype & ~1) {
        case SQL_VARYING:
            var->sqldata =
                (char *) store_get(sizeof(char) * var->sqllen + 2);
            break;
        case SQL_TEXT:
            var->sqldata =
                (char *) store_get(sizeof(char) * var->sqllen);
            break;
        case SQL_SHORT:
            var->sqldata = (char *) store_get(sizeof(short));
            break;
        case SQL_LONG:
            var->sqldata = (char *) store_get(sizeof(ISC_LONG));
            break;
#ifdef SQL_INT64
        case SQL_INT64:
            var->sqldata = (char *) store_get(sizeof(ISC_INT64));
            break;
#endif
        case SQL_FLOAT:
            var->sqldata = (char *) store_get(sizeof(float));
            break;
        case SQL_DOUBLE:
            var->sqldata = (char *) store_get(sizeof(double));
            break;
#ifdef SQL_TIMESTAMP
        case SQL_DATE:
            var->sqldata = (char *) store_get(sizeof(ISC_QUAD));
            break;
#else
        case SQL_TIMESTAMP:
            var->sqldata = (char *) store_get(sizeof(ISC_TIMESTAMP));
            break;
        case SQL_TYPE_DATE:
            var->sqldata = (char *) store_get(sizeof(ISC_DATE));
            break;
        case SQL_TYPE_TIME:
            var->sqldata = (char *) store_get(sizeof(ISC_TIME));
            break;
#endif
        }
        if (var->sqltype & 1) {
            var->sqlind = (short *) store_get(sizeof(short));
        }
    }

    /* finally, we're ready to execute the statement */
    if (isc_dsql_execute
        (status, &cn->transh, &stmth, out_sqlda->version, NULL)) {
        isc_interprete(buffer, &statusp);
        *errmsg =
            string_sprintf("Interbase describe_statement() failed: %s",
                           buffer);
        isc_dsql_free_statement(status, &stmth, DSQL_drop);
        *defer_break = FALSE;
        goto IBASE_EXIT;
    }

    while (isc_dsql_fetch(status, &stmth, out_sqlda->version, out_sqlda) !=
           100L) {
        /* check if an error occurred */
        if (status[0] & status[1]) {
            isc_interprete(buffer, &statusp);
            *errmsg =
                string_sprintf("Interbase fetch() failed: %s", buffer);
            isc_dsql_free_statement(status, &stmth, DSQL_drop);
            *defer_break = FALSE;
            goto IBASE_EXIT;
        }

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

        /* Find the number of fields returned. If this is one, we don't add field
           names to the data. Otherwise we do. */
        if (out_sqlda->sqld == 1) {
            if (out_sqlda->sqlvar[0].sqlind == NULL || *out_sqlda->sqlvar[0].sqlind != -1)     /* NULL value yields nothing */
                result =
                    string_catn(result, &ssize, &offset, US buffer,
                               fetch_field(buffer, sizeof(buffer),
                                           &out_sqlda->sqlvar[0]));
        }

        else
            for (i = 0; i < out_sqlda->sqld; i++) {
                int len = fetch_field(buffer, sizeof(buffer),
                                      &out_sqlda->sqlvar[i]);

                result =
                    string_cat(result, &ssize, &offset,
                               US out_sqlda->sqlvar[i].aliasname,
                               out_sqlda->sqlvar[i].aliasname_length);
                result = string_catn(result, &ssize, &offset, US "=", 1);

                /* Quote the value if it contains spaces or is empty */

                if (*out_sqlda->sqlvar[i].sqlind == -1) {       /* NULL value */
                    result =
                        string_catn(result, &ssize, &offset, US "\"\"", 2);
                }

                else if (buffer[0] == 0 || Ustrchr(buffer, ' ') != NULL) {
                    int j;
                    result =
                        string_catn(result, &ssize, &offset, US "\"", 1);
                    for (j = 0; j < len; j++) {
                        if (buffer[j] == '\"' || buffer[j] == '\\')
                            result =
                                string_cat(result, &ssize, &offset,
                                           US "\\", 1);
                        result =
                            string_cat(result, &ssize, &offset,
                                       US buffer + j, 1);
                    }
                    result =
                        string_catn(result, &ssize, &offset, US "\"", 1);
                } else {
                    result =
                        string_catn(result, &ssize, &offset, US buffer, len);
                }
                result = string_catn(result, &ssize, &offset, US " ", 1);
            }
    }

/* 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 "Interbase: no data found";
    } else {
        result[offset] = 0;
        store_reset(result + offset + 1);
    }


/* Get here by goto from various error checks. */

  IBASE_EXIT:

    if (stmth != NULL)
        isc_dsql_free_statement(status, &stmth, DSQL_drop);

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

    if (result != NULL) {
        *resultptr = result;
        return OK;
    } else {
        DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
        return yield;           /* FAIL or DEFER */
    }
}