Example #1
0
File: em_menu.c Project: Exim/exim
static void headersAction(Widget w, XtPointer client_data, XtPointer call_data)
{
uschar buffer[256];
header_line *h, *next;
Widget text = text_create(US client_data, text_depth);
void *reset_point;

w = w;      /* Keep picky compilers happy */
call_data = call_data;

/* Remember the point in the dynamic store so we can recover to it afterwards.
Then use Exim's function to read the header. */

reset_point = store_get(0);

sprintf(CS buffer, "%s-H", US client_data);
if (spool_read_header(buffer, TRUE, FALSE) != spool_read_OK)
  {
  if (errno == ERRNO_SPOOLFORMAT)
    {
    struct stat statbuf;
    sprintf(CS big_buffer, "%s/input/%s", spool_directory, buffer);
    if (Ustat(big_buffer, &statbuf) == 0)
      text_showf(text, "Format error in spool file %s: size=%d\n", buffer,
        statbuf.st_size);
    else text_showf(text, "Format error in spool file %s\n", buffer);
    }
  else text_showf(text, "Read error for spool file %s\n", buffer);
  store_reset(reset_point);
  return;
  }

if (sender_address != NULL)
  {
  text_showf(text, "%s sender: <%s>\n", f.sender_local ? "Local" : "Remote",
    sender_address);
  }

if (recipients_list != NULL)
  {
  int i;
  text_show(text, US"Recipients:\n");
  for (i = 0; i < recipients_count; i++)
    {
    text_showf(text, "  %s %s\n",
      (tree_search(tree_nonrecipients, recipients_list[i].address) == NULL)?
        " ":"*", recipients_list[i].address);
    }
  text_show(text, US"\n");
  }

for (h = header_list; h != NULL; h = next)
  {
  next = h->next;
  text_showf(text, "%c ", h->type);   /* Don't push h->text through a %s */
  text_show(text, h->text);           /* expansion as it may be v large */
  }

store_reset(reset_point);
}
Example #2
0
int
tls_import_cert(const uschar * buf, void ** cert)
{
    void * reset_point = store_get(0);
    gnutls_datum_t datum;
    gnutls_x509_crt_t crt = *(gnutls_x509_crt_t *)cert;
    int fail = 0;

    if (crt)
        gnutls_x509_crt_deinit(crt);
    else
        gnutls_global_init();

    gnutls_x509_crt_init(&crt);

    datum.data = string_unprinting(US buf);
    datum.size = Ustrlen(datum.data);
    if ((fail = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM)))
    {
        log_write(0, LOG_MAIN, "TLS error in certificate import: %s",
                  gnutls_strerror(fail));
        fail = 1;
    }
    else
        *cert = (void *)crt;

    store_reset(reset_point);
    return fail;
}
Example #3
0
void
search_tidyup(void)
{
int i;
int old_pool = store_pool;

DEBUG(D_lookup) debug_printf("search_tidyup called\n");

/* Close individually each cached open file. */

store_pool = POOL_SEARCH;
if (search_tree != NULL)
  {
  tidyup_subtree(search_tree);
  search_tree = NULL;
  }
open_top = open_bot = NULL;
open_filecount = 0;

/* Call the general tidyup entry for any drivers that have one. */

for (i = 0; i < lookup_list_count; i++)
  if (lookup_list[i]->tidy != NULL) (lookup_list[i]->tidy)();

if (search_reset_point != NULL) store_reset(search_reset_point);
search_reset_point = NULL;
store_pool = old_pool;
}
Example #4
0
File: tree.c Project: fanf2/exim
void
tree_add_duplicate(uschar *s, address_item *addr)
{
tree_node *node = store_get(sizeof(tree_node) + Ustrlen(s));
Ustrcpy(node->name, s);
node->data.ptr = addr;
if (!tree_insertnode(&tree_duplicates, node)) store_reset(node);
}
Example #5
0
File: tree.c Project: fanf2/exim
void
tree_add_nonrecipient(uschar *s)
{
tree_node *node = store_get(sizeof(tree_node) + Ustrlen(s));
Ustrcpy(node->name, s);
node->data.ptr = NULL;
if (!tree_insertnode(&tree_nonrecipients, node)) store_reset(node);
}
Example #6
0
File: tree.c Project: fanf2/exim
void
tree_add_unusable(host_item *h)
{
tree_node *node;
uschar s[256];
sprintf(CS s, "T:%.200s:%s", h->name, h->address);
node = store_get(sizeof(tree_node) + Ustrlen(s));
Ustrcpy(node->name, s);
node->data.val = h->why;
if (h->status == hstatus_unusable_expired) node->data.val += 256;
if (!tree_insertnode(&tree_unusable, node)) store_reset(node);
}
Example #7
0
static int
exim_gssapi_error_defer(uschar *store_reset_point,
    OM_uint32 major, OM_uint32 minor,
    const char *format, ...)
{
  va_list ap;
  uschar buffer[STRING_SPRINTF_BUFFER_SIZE];
  OM_uint32 maj_stat, min_stat;
  OM_uint32 msgcontext = 0;
  gss_buffer_desc status_string;

  va_start(ap, format);
  if (!string_vformat(buffer, sizeof(buffer), format, ap))
    log_write(0, LOG_MAIN|LOG_PANIC_DIE,
        "exim_gssapi_error_defer expansion larger than %lu",
        sizeof(buffer));
  va_end(ap);

  auth_defer_msg = NULL;

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

    if (auth_defer_msg == NULL) {
      auth_defer_msg = string_copy(US status_string.value);
    }

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

  } while (msgcontext != 0);

  if (store_reset_point)
    store_reset(store_reset_point);
  return DEFER;
}
Example #8
0
/*****************************************************
*  Export/import a certificate, binary/printable
*****************************************************/
int
tls_export_cert(uschar * buf, size_t buflen, void * cert)
{
    size_t sz = buflen;
    void * reset_point = store_get(0);
    int fail;
    const uschar * cp;

    if ((fail = gnutls_x509_crt_export((gnutls_x509_crt_t)cert,
                                       GNUTLS_X509_FMT_PEM, buf, &sz)))
    {
        log_write(0, LOG_MAIN, "TLS error in certificate export: %s",
                  gnutls_strerror(fail));
        return 1;
    }
    if ((cp = string_printing(buf)) != buf)
    {
        Ustrncpy(buf, cp, buflen);
        if (buf[buflen-1])
            fail = 1;
    }
    store_reset(reset_point);
    return fail;
}
Example #9
0
void degenerator_compute (Degenerator_t * degenerator){
	int cur_iter;
	

	pDEBUG("Iterating %d times\n", degenerator->_iterations);
	for (cur_iter = 0; cur_iter < degenerator->_iterations; cur_iter++){
		if ((cur_iter % degenerator->_iterations_offset) == 0){
			// faire des calculs (nombre de composantes connexes)
			store_reset (degenerator->_store, STORE_RESET_ALL ^ STORE_RESET_DEGREE);

			Fifo_t * fifo_idx = fifo_create(degenerator->_size);
			store_connexity (degenerator->_store, fifo_idx);

			fprintf(degenerator->_io->output, "%d %ld\n", cur_iter, fifo_get_size (fifo_idx));
			fifo_destroy(fifo_idx);
			// afficher la courbe...
		}
		// Choisir un noeud selon une certaine méthode...
		nodeindex_t selected_node = (degenerator_select_mode_table[degenerator->_select_mode])(degenerator);
		printf("Node %ld\n", selected_node);

		store_del_node(degenerator->_store, selected_node);
	}
}
Example #10
0
static int
dnsdb_find(void *handle, uschar *filename, const uschar *keystring, int length,
  uschar **result, uschar **errmsg, uint *do_cache)
{
int rc;
int size = 256;
int ptr = 0;
int sep = 0;
int defer_mode = PASS;
int dnssec_mode = OK;
int save_retrans = dns_retrans;
int save_retry =   dns_retry;
int type;
int failrc = FAIL;
const uschar *outsep = CUS"\n";
const uschar *outsep2 = NULL;
uschar *equals, *domain, *found;

/* Because we're the working in the search pool, we try to reclaim as much
store as possible later, so we preallocate the result here */

uschar *yield = store_get(size);

dns_record *rr;
dns_answer dnsa;
dns_scan dnss;

handle = handle;           /* Keep picky compilers happy */
filename = filename;
length = length;
do_cache = do_cache;

/* If the string starts with '>' we change the output separator.
If it's followed by ';' or ',' we set the TXT output separator. */

while (isspace(*keystring)) keystring++;
if (*keystring == '>')
  {
  outsep = keystring + 1;
  keystring += 2;
  if (*keystring == ',')
    {
    outsep2 = keystring + 1;
    keystring += 2;
    }
  else if (*keystring == ';')
    {
    outsep2 = US"";
    keystring++;
    }
  while (isspace(*keystring)) keystring++;
  }

/* Check for a modifier keyword. */

for (;;)
  {
  if (strncmpic(keystring, US"defer_", 6) == 0)
    {
    keystring += 6;
    if (strncmpic(keystring, US"strict", 6) == 0)
      { defer_mode = DEFER; keystring += 6; }
    else if (strncmpic(keystring, US"lax", 3) == 0)
      { defer_mode = PASS; keystring += 3; }
    else if (strncmpic(keystring, US"never", 5) == 0)
      { defer_mode = OK; keystring += 5; }
    else
      {
      *errmsg = US"unsupported dnsdb defer behaviour";
      return DEFER;
      }
    }
  else if (strncmpic(keystring, US"dnssec_", 7) == 0)
    {
    keystring += 7;
    if (strncmpic(keystring, US"strict", 6) == 0)
      { dnssec_mode = DEFER; keystring += 6; }
    else if (strncmpic(keystring, US"lax", 3) == 0)
      { dnssec_mode = PASS; keystring += 3; }
    else if (strncmpic(keystring, US"never", 5) == 0)
      { dnssec_mode = OK; keystring += 5; }
    else
      {
      *errmsg = US"unsupported dnsdb dnssec behaviour";
      return DEFER;
      }
    }
  else if (strncmpic(keystring, US"retrans_", 8) == 0)
    {
    int timeout_sec;
    if ((timeout_sec = readconf_readtime(keystring += 8, ',', FALSE)) <= 0)
      {
      *errmsg = US"unsupported dnsdb timeout value";
      return DEFER;
      }
    dns_retrans = timeout_sec;
    while (*keystring != ',') keystring++;
    }
  else if (strncmpic(keystring, US"retry_", 6) == 0)
    {
    int retries;
    if ((retries = (int)strtol(CCS keystring + 6, CSS &keystring, 0)) < 0)
      {
      *errmsg = US"unsupported dnsdb retry count";
      return DEFER;
      }
    dns_retry = retries;
    }
  else
    break;

  while (isspace(*keystring)) keystring++;
  if (*keystring++ != ',')
    {
    *errmsg = US"dnsdb modifier syntax error";
    return DEFER;
    }
  while (isspace(*keystring)) keystring++;
  }

/* Figure out the "type" value if it is not T_TXT.
If the keystring contains an = this must be preceded by a valid type name. */

type = T_TXT;
if ((equals = Ustrchr(keystring, '=')) != NULL)
  {
  int i, len;
  uschar *tend = equals;

  while (tend > keystring && isspace(tend[-1])) tend--;
  len = tend - keystring;

  for (i = 0; i < nelem(type_names); i++)
    if (len == Ustrlen(type_names[i]) &&
        strncmpic(keystring, US type_names[i], len) == 0)
      {
      type = type_values[i];
      break;
      }

  if (i >= nelem(type_names))
    {
    *errmsg = US"unsupported DNS record type";
    return DEFER;
    }

  keystring = equals + 1;
  while (isspace(*keystring)) keystring++;
  }

/* Initialize the resolver in case this is the first time it has been used. */

dns_init(FALSE, FALSE, dnssec_mode != OK);

/* The remainder of the string must be a list of domains. As long as the lookup
for at least one of them succeeds, we return success. Failure means that none
of them were found.

The original implementation did not support a list of domains. Adding the list
feature is compatible, except in one case: when PTR records are being looked up
for a single IPv6 address. Fortunately, we can hack in a compatibility feature
here: If the type is PTR and no list separator is specified, and the entire
remaining string is valid as an IP address, set an impossible separator so that
it is treated as one item. */

if (type == T_PTR && keystring[0] != '<' &&
    string_is_ip_address(keystring, NULL) != 0)
  sep = -1;

/* SPF strings should be concatenated without a separator, thus make
it the default if not defined (see RFC 4408 section 3.1.3).
Multiple SPF records are forbidden (section 3.1.2) but are currently
not handled specially, thus they are concatenated with \n by default.
MX priority and value are space-separated by default.
SRV and TLSA record parts are space-separated by default. */

if (!outsep2) switch(type)
  {
  case T_SPF:                         outsep2 = US"";  break;
  case T_SRV: case T_MX: case T_TLSA: outsep2 = US" "; break;
  }

/* Now scan the list and do a lookup for each item */

while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
  {
  uschar rbuffer[256];
  int searchtype = (type == T_CSA)? T_SRV :         /* record type we want */
                   (type == T_MXH)? T_MX :
                   (type == T_ZNS)? T_NS : type;

  /* If the type is PTR or CSA, we have to construct the relevant magic lookup
  key if the original is an IP address (some experimental protocols are using
  PTR records for different purposes where the key string is a host name, and
  Exim's extended CSA can be keyed by domains or IP addresses). This code for
  doing the reversal is now in a separate function. */

  if ((type == T_PTR || type == T_CSA) &&
      string_is_ip_address(domain, NULL) != 0)
    {
    dns_build_reverse(domain, rbuffer);
    domain = rbuffer;
    }

  do
    {
    DEBUG(D_lookup) debug_printf("dnsdb key: %s\n", domain);

    /* Do the lookup and sort out the result. There are four special types that
    are handled specially: T_CSA, T_ZNS, T_ADDRESSES and T_MXH.
    The first two are handled in a special lookup function so that the facility
    could be used from other parts of the Exim code. T_ADDRESSES is handled by looping
    over the types of A lookup.  T_MXH affects only what happens later on in
    this function, but for tidiness it is handled by the "special". If the
    lookup fails, continue with the next domain. In the case of DEFER, adjust
    the final "nothing found" result, but carry on to the next domain. */

    found = domain;
#if HAVE_IPV6
    if (type == T_ADDRESSES)		/* NB cannot happen unless HAVE_IPV6 */
      {
      if (searchtype == T_ADDRESSES) searchtype = T_AAAA;
      else if (searchtype == T_AAAA) searchtype = T_A;
      rc = dns_special_lookup(&dnsa, domain, searchtype, CUSS &found);
      }
    else
#endif
      rc = dns_special_lookup(&dnsa, domain, type, CUSS &found);

    lookup_dnssec_authenticated = dnssec_mode==OK ? NULL
      : dns_is_secure(&dnsa) ? US"yes" : US"no";

    if (rc == DNS_NOMATCH || rc == DNS_NODATA) continue;
    if (  rc != DNS_SUCCEED
       || (dnssec_mode == DEFER && !dns_is_secure(&dnsa))
       )
      {
      if (defer_mode == DEFER)
	{
	dns_retrans = save_retrans;
	dns_retry = save_retry;
	dns_init(FALSE, FALSE, FALSE);			/* clr dnssec bit */
	return DEFER;					/* always defer */
	}
      if (defer_mode == PASS) failrc = DEFER;         /* defer only if all do */
      continue;                                       /* treat defer as fail */
      }


    /* Search the returned records */

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

      if (*do_cache > rr->ttl)
	*do_cache = rr->ttl;

      if (type == T_A || type == T_AAAA || type == T_ADDRESSES)
        {
        dns_address *da;
        for (da = dns_address_from_rr(&dnsa, rr); da; da = da->next)
          {
          if (ptr != 0) yield = string_catn(yield, &size, &ptr, outsep, 1);
          yield = string_cat(yield, &size, &ptr, da->address);
          }
        continue;
        }

      /* Other kinds of record just have one piece of data each, but there may be
      several of them, of course. */

      if (ptr != 0) yield = string_catn(yield, &size, &ptr, outsep, 1);

      if (type == T_TXT || type == T_SPF)
        {
        if (outsep2 == NULL)
          {
          /* output only the first item of data */
          yield = string_catn(yield, &size, &ptr, (uschar *)(rr->data+1),
            (rr->data)[0]);
          }
        else
          {
          /* output all items */
          int data_offset = 0;
          while (data_offset < rr->size)
            {
            uschar chunk_len = (rr->data)[data_offset++];
            if (outsep2[0] != '\0' && data_offset != 1)
              yield = string_catn(yield, &size, &ptr, outsep2, 1);
            yield = string_catn(yield, &size, &ptr,
                             US ((rr->data)+data_offset), chunk_len);
            data_offset += chunk_len;
            }
          }
        }
      else if (type == T_TLSA)
        {
        uint8_t usage, selector, matching_type;
        uint16_t i, payload_length;
        uschar s[MAX_TLSA_EXPANDED_SIZE];
	uschar * sp = s;
        uschar * p = US rr->data;

        usage = *p++;
        selector = *p++;
        matching_type = *p++;
        /* What's left after removing the first 3 bytes above */
        payload_length = rr->size - 3;
        sp += sprintf(CS s, "%d%c%d%c%d%c", usage, *outsep2,
		selector, *outsep2, matching_type, *outsep2);
        /* Now append the cert/identifier, one hex char at a time */
        for (i=0;
             i < payload_length && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4);
             i++)
          sp += sprintf(CS sp, "%02x", (unsigned char)p[i]);

        yield = string_cat(yield, &size, &ptr, s);
        }
      else   /* T_CNAME, T_CSA, T_MX, T_MXH, T_NS, T_PTR, T_SOA, T_SRV */
        {
        int priority, weight, port;
        uschar s[264];
        uschar * p = US rr->data;

	switch (type)
	  {
	  case T_MXH:
	    /* mxh ignores the priority number and includes only the hostnames */
	    GETSHORT(priority, p);
	    break;

	  case T_MX:
	    GETSHORT(priority, p);
	    sprintf(CS s, "%d%c", priority, *outsep2);
	    yield = string_cat(yield, &size, &ptr, s);
	    break;

	  case T_SRV:
	    GETSHORT(priority, p);
	    GETSHORT(weight, p);
	    GETSHORT(port, p);
	    sprintf(CS s, "%d%c%d%c%d%c", priority, *outsep2,
			      weight, *outsep2, port, *outsep2);
	    yield = string_cat(yield, &size, &ptr, s);
	    break;

	  case T_CSA:
	    /* See acl_verify_csa() for more comments about CSA. */
	    GETSHORT(priority, p);
	    GETSHORT(weight, p);
	    GETSHORT(port, p);

	    if (priority != 1) continue;      /* CSA version must be 1 */

	    /* If the CSA record we found is not the one we asked for, analyse
	    the subdomain assertions in the port field, else analyse the direct
	    authorization status in the weight field. */

	    if (Ustrcmp(found, domain) != 0)
	      {
	      if (port & 1) *s = 'X';         /* explicit authorization required */
	      else *s = '?';                  /* no subdomain assertions here */
	      }
	    else
	      {
	      if (weight < 2) *s = 'N';       /* not authorized */
	      else if (weight == 2) *s = 'Y'; /* authorized */
	      else if (weight == 3) *s = '?'; /* unauthorizable */
	      else continue;                  /* invalid */
	      }

	    s[1] = ' ';
	    yield = string_catn(yield, &size, &ptr, s, 2);
	    break;

	  default:
	    break;
	  }

        /* GETSHORT() has advanced the pointer to the target domain. */

        rc = dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p,
          (DN_EXPAND_ARG4_TYPE)s, sizeof(s));

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

        if (rc < 0)
          {
          log_write(0, LOG_MAIN, "host name alias list truncated: type=%s "
            "domain=%s", dns_text_type(type), domain);
          break;
          }
        else yield = string_cat(yield, &size, &ptr, s);

	if (type == T_SOA && outsep2 != NULL)
	  {
	  unsigned long serial, refresh, retry, expire, minimum;

	  p += rc;
	  yield = string_catn(yield, &size, &ptr, outsep2, 1);

	  rc = dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p,
	    (DN_EXPAND_ARG4_TYPE)s, sizeof(s));
	  if (rc < 0)
	    {
	    log_write(0, LOG_MAIN, "responsible-mailbox truncated: type=%s "
	      "domain=%s", dns_text_type(type), domain);
	    break;
	    }
	  else yield = string_cat(yield, &size, &ptr, s);

	  p += rc;
	  GETLONG(serial, p); GETLONG(refresh, p);
	  GETLONG(retry,  p); GETLONG(expire,  p); GETLONG(minimum, p);
	  sprintf(CS s, "%c%lu%c%lu%c%lu%c%lu%c%lu",
	    *outsep2, serial, *outsep2, refresh,
	    *outsep2, retry,  *outsep2, expire,  *outsep2, minimum);
	  yield = string_cat(yield, &size, &ptr, s);
	  }
        }
      }    /* Loop for list of returned records */

           /* Loop for set of A-lookup types */
    } while (type == T_ADDRESSES && searchtype != T_A);

  }        /* Loop for list of domains */

/* Reclaim unused memory */

store_reset(yield + ptr + 1);

/* If ptr == 0 we have not found anything. Otherwise, insert the terminating
zero and return the result. */

dns_retrans = save_retrans;
dns_retry = save_retry;
dns_init(FALSE, FALSE, FALSE);	/* clear the dnssec bit for getaddrbyname */

if (ptr == 0) return failrc;
yield[ptr] = 0;
*result = yield;
return OK;
}
Example #11
0
static int
internal_lsearch_find(void *handle, uschar *filename, uschar *keystring,
  int length, uschar **result, uschar **errmsg, int type)
{
FILE *f = (FILE *)handle;
BOOL last_was_eol = TRUE;
BOOL this_is_eol = TRUE;
int old_pool = store_pool;
void *reset_point = NULL;
uschar buffer[4096];

/* Wildcard searches may use up some store, because of expansions. We don't
want them to fill up our search store. What we do is set the pool to the main
pool and get a point to reset to later. Wildcard searches could also issue
lookups, but internal_search_find will take care of that, and the cache will be
safely stored in the search pool again. */

if(type == LSEARCH_WILD || type == LSEARCH_NWILD)
  {
  store_pool = POOL_MAIN;
  reset_point = store_get(0);
  }

filename = filename;  /* Keep picky compilers happy */
errmsg = errmsg;

rewind(f);
for (last_was_eol = TRUE;
     Ufgets(buffer, sizeof(buffer), f) != NULL;
     last_was_eol = this_is_eol)
  {
  int ptr, size;
  int p = Ustrlen(buffer);
  int linekeylength;
  BOOL this_is_comment;
  uschar *yield;
  uschar *s = buffer;

  /* Check whether this the final segment of a line. If it follows an
  incomplete part-line, skip it. */

  this_is_eol = p > 0 && buffer[p-1] == '\n';
  if (!last_was_eol) continue;

  /* We now have the start of a physical line. If this is a final line segment,
  remove trailing white space. */

  if (this_is_eol)
    {
    while (p > 0 && isspace((uschar)buffer[p-1])) p--;
    buffer[p] = 0;
    }

  /* If the buffer is empty it might be (a) a complete empty line, or (b) the
  start of a line that begins with so much white space that it doesn't all fit
  in the buffer. In both cases we want to skip the entire physical line.

  If the buffer begins with # it is a comment line; if it begins with white
  space it is a logical continuation; again, we want to skip the entire
  physical line. */

  if (buffer[0] == 0 || buffer[0] == '#' || isspace(buffer[0])) continue;

  /* We assume that they key will fit in the buffer. If the key starts with ",
  read it as a quoted string. We don't use string_dequote() because that uses
  new store for the result, and we may be doing this many times in a long file.
  We know that the dequoted string must be shorter than the original, because
  we are removing the quotes, and also any escape sequences always turn two or
  more characters into one character. Therefore, we can store the new string in
  the same buffer. */

  if (*s == '\"')
    {
    uschar *t = s++;
    while (*s != 0 && *s != '\"')
      {
      if (*s == '\\') *t++ = string_interpret_escape(&s);
        else *t++ = *s;
      s++;
      }
    if (*s != 0) s++;               /* Past terminating " */
    linekeylength = t - buffer;
    }

  /* Otherwise it is terminated by a colon or white space */

  else
    {
    while (*s != 0 && *s != ':' && !isspace(*s)) s++;
    linekeylength = s - buffer;
    }

  /* The matching test depends on which kind of lsearch we are doing */

  switch(type)
    {
    /* A plain lsearch treats each key as a literal */

    case LSEARCH_PLAIN:
    if (linekeylength != length || strncmpic(buffer, keystring, length) != 0)
      continue;
    break;      /* Key matched */

    /* A wild lsearch treats each key as a possible wildcarded string; no
    expansion is done for nwildlsearch. */

    case LSEARCH_WILD:
    case LSEARCH_NWILD:
      {
      int rc;
      int save = buffer[linekeylength];
      uschar *list = buffer;
      buffer[linekeylength] = 0;
      rc = match_isinlist(keystring,
        &list,
        UCHAR_MAX+1,              /* Single-item list */
        NULL,                     /* No anchor */
        NULL,                     /* No caching */
        MCL_STRING + ((type == LSEARCH_WILD)? 0:MCL_NOEXPAND),
        TRUE,                     /* Caseless */
        NULL);
      buffer[linekeylength] = save;
      if (rc == FAIL) continue;
      if (rc == DEFER) return DEFER;
      }

    /* The key has matched. If the search involved a regular expression, it
    might have caused numerical variables to be set. However, their values will
    be in the wrong storage pool for external use. Copying them to the standard
    pool is not feasible because of the caching of lookup results - a repeated
    lookup will not match the regular expression again. Therefore, we flatten
    all numeric variables at this point. */

    expand_nmax = -1;
    break;

    /* Compare an ip address against a list of network/ip addresses. We have to
    allow for the "*" case specially. */

    case LSEARCH_IP:
    if (linekeylength == 1 && buffer[0] == '*')
      {
      if (length != 1 || keystring[0] != '*') continue;
      }
    else if (length == 1 && keystring[0] == '*') continue;
    else
      {
      int maskoffset;
      int save = buffer[linekeylength];
      buffer[linekeylength] = 0;
      if (string_is_ip_address(buffer, &maskoffset) == 0 ||
          !host_is_in_net(keystring, buffer, maskoffset)) continue;
      buffer[linekeylength] = save;
      }
    break;      /* Key matched */
    }

  /* The key has matched. Skip spaces after the key, and allow an optional
  colon after the spaces. This is an odd specification, but it's for
  compatibility. */

  while (isspace((uschar)*s)) s++;
  if (*s == ':')
    {
    s++;
    while (isspace((uschar)*s)) s++;
    }

  /* Reset dynamic store, if we need to, and revert to the search pool */

  if (reset_point != NULL)
    {
    store_reset(reset_point);
    store_pool = old_pool;
    }

  /* Now we want to build the result string to contain the data. There can be
  two kinds of continuation: (a) the physical line may not all have fitted into
  the buffer, and (b) there may be logical continuation lines, for which we
  must convert all leading white space into a single blank.

  Initialize, and copy the first segment of data. */

  this_is_comment = FALSE;
  size = 100;
  ptr = 0;
  yield = store_get(size);
  if (*s != 0)
    yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));

  /* Now handle continuations */

  for (last_was_eol = this_is_eol;
       Ufgets(buffer, sizeof(buffer), f) != NULL;
       last_was_eol = this_is_eol)
    {
    s = buffer;
    p = Ustrlen(buffer);
    this_is_eol = p > 0 && buffer[p-1] == '\n';

    /* Remove trailing white space from a physical line end */

    if (this_is_eol)
      {
      while (p > 0 && isspace((uschar)buffer[p-1])) p--;
      buffer[p] = 0;
      }

    /* If this is not a physical line continuation, skip it entirely if it's
    empty or starts with #. Otherwise, break the loop if it doesn't start with
    white space. Otherwise, replace leading white space with a single blank. */

    if (last_was_eol)
      {
      this_is_comment = (this_is_comment || (buffer[0] == 0 || buffer[0] == '#'));
      if (this_is_comment) continue;
      if (!isspace((uschar)buffer[0])) break;
      while (isspace((uschar)*s)) s++;
      *(--s) = ' ';
      }
    if (this_is_comment) continue;

    /* Join a physical or logical line continuation onto the result string. */

    yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
    }

  yield[ptr] = 0;
  store_reset(yield + ptr + 1);
  *result = yield;
  return OK;
  }

/* Reset dynamic store, if we need to */

if (reset_point != NULL)
  {
  store_reset(reset_point);
  store_pool = old_pool;
  }

return FAIL;
}
Example #12
0
File: em_log.c Project: fanf2/exim
void read_log(void)
{
struct stat statdata;
uschar buffer[log_buffer_len];

/* If log is not yet open, skip all of this. */

if (LOG != NULL)
  {
  fseek(LOG, log_position, SEEK_SET);

  while (Ufgets(buffer, log_buffer_len, LOG) != NULL)
    {
    uschar *id;
    uschar *p = buffer;
    void *reset_point;
    int length = Ustrlen(buffer);
    int i;

    /* Skip totally blank lines (paranoia: there shouldn't be any) */

    while (*p == ' ' || *p == '\t') p++;
    if (*p == '\n') continue;

    /* We should now have a complete log entry in the buffer; check
    it for various regular expression matches and take appropriate
    action. Get the current store point so we can reset to it. */

    reset_point = store_get(0);

    /* First, update any stripchart data values, noting that the zeroth
    stripchart is the queue length, which is handled elsewhere, and the
    1st may the a size monitor. */

    for (i = stripchart_varstart; i < stripchart_number; i++)
      {
      if (pcre_exec(stripchart_regex[i], NULL, CS buffer, length, 0, PCRE_EOPT,
            NULL, 0) >= 0)
        stripchart_total[i]++;
      }

    /* Munge the log entry and display shortened form on one line.
    We omit the date and show only the time. Remove any time zone offset.
    Take note of the presence of [pid]. */

    if (pcre_exec(yyyymmdd_regex,NULL,CS buffer,length,0,PCRE_EOPT,NULL,0) >= 0)
      {
      int pidlength = 0;
      if ((buffer[20] == '+' || buffer[20] == '-') &&
          isdigit(buffer[21]) && buffer[25] == ' ')
        memmove(buffer + 20, buffer + 26, Ustrlen(buffer + 26) + 1);
      if (buffer[20] == '[')
        {
        while (Ustrchr("[]0123456789", buffer[20+pidlength++]) != NULL);
        }
      id = string_copyn(buffer + 20 + pidlength, MESSAGE_ID_LENGTH);
      show_log("%s", buffer+11);
      }
    else
      {
      id = US"";
      show_log("%s", buffer);
      }

    /* Deal with frozen and unfrozen messages */

    if (strstric(buffer, US"frozen", FALSE) != NULL)
      {
      queue_item *qq = find_queue(id, queue_noop, 0);
      if (qq != NULL)
        {
        if (strstric(buffer, US"unfrozen", FALSE) != NULL)
          qq->frozen = FALSE;
        else qq->frozen = TRUE;
        }
      }

    /* Notice defer messages, and add the destination if it
    isn't already on the list for this message, with a pointer
    to the parent if we can. */

    if ((p = Ustrstr(buffer, "==")) != NULL)
      {
      queue_item *qq = find_queue(id, queue_noop, 0);
      if (qq != NULL)
        {
        dest_item *d;
        uschar *q, *r;
        p += 2;
        while (isspace(*p)) p++;
        q = p;
        while (*p != 0 && !isspace(*p))
          {
          if (*p++ != '\"') continue;
          while (*p != 0)
            {
            if (*p == '\\') p += 2;
              else if (*p++ == '\"') break;
            }
          }
        *p++ = 0;
        if ((r = strstric(q, qualify_domain, FALSE)) != NULL &&
          *(--r) == '@') *r = 0;

        /* If we already have this destination, as tested case-insensitively,
        do not add it to the destinations list. */

        d = find_dest(qq, q, dest_add, TRUE);

        if (d->parent == NULL)
          {
          while (isspace(*p)) p++;
          if (*p == '<')
            {
            dest_item *dd;
            q = ++p;
            while (*p != 0 && *p != '>') p++;
            *p = 0;
            if ((p = strstric(q, qualify_domain, FALSE)) != NULL &&
              *(--p) == '@') *p = 0;
            dd = find_dest(qq, q, dest_noop, FALSE);
            if (dd != NULL && dd != d) d->parent = dd;
            }
          }
        }
      }

    store_reset(reset_point);
    }
  }


/* We have to detect when the log file is changed, and switch to the new file.
In practice, for non-datestamped files, this means that some deliveries might
go unrecorded, since they'll be written to the old file, but this usually
happens in the middle of the night, and I don't think the hassle of keeping
track of two log files is worth it.

First we check the datestamped name of the log file if necessary; if it is
different to the file we currently have open, go for the new file. As happens
in Exim itself, we leave in the following inode check, even when datestamping
because it does no harm and will cope should a file actually be renamed for
some reason.

The test for a changed log file is to look up the inode of the file by name and
compare it with the saved inode of the file we currently are processing. This
accords with the usual interpretation of POSIX and other Unix specs that imply
"one file, one inode". However, it appears that on some Digital systems, if an
open file is unlinked, a new file may be created with the same inode while the
old file remains in existence. This can happen if the old log file is renamed,
processed in some way, and then deleted. To work round this, also test for a
link count of zero on the currently open file. */

if (log_datestamping)
  {
  uschar log_file_wanted[256];
  string_format(log_file_wanted, sizeof(log_file_wanted), CS log_file);
  if (Ustrcmp(log_file_wanted, log_file_open) != 0)
    {
    if (LOG != NULL)
      {
      fclose(LOG);
      LOG = NULL;
      }
    Ustrcpy(log_file_open, log_file_wanted);
    }
  }

if (LOG == NULL ||
    (fstat(fileno(LOG), &statdata) == 0 && statdata.st_nlink == 0) ||
    (Ustat(log_file, &statdata) == 0 && log_inode != statdata.st_ino))
  {
  FILE *TEST;

  /* Experiment shows that sometimes you can't immediately open
  the new log file - presumably immediately after the old one
  is renamed and before the new one exists. Therefore do a
  trial open first to be sure. */

  if ((TEST = fopen(CS log_file_open, "r")) != NULL)
    {
    if (LOG != NULL) fclose(LOG);
    LOG = TEST;
    fstat(fileno(LOG), &statdata);
    log_inode = statdata.st_ino;
    }
  }

/* Save the position we have got to in the log. */

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

  store_reset_point = store_get(0);

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

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

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

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

  maj_stat = gss_release_name(&min_stat, &gserver);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        step += 1;
        break;

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


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

  store_reset(store_reset_point);

  if (error_out != OK)
    return error_out;

  /* Auth succeeded, check server_condition */
  return auth_check_serv_cond(ablock);
}
Example #14
0
static int
nisplus_find(void *handle, uschar *filename, uschar *query, int length,
  uschar **result, uschar **errmsg, uint *do_cache)
{
int i;
int ssize = 0;
int offset = 0;
int error_error = FAIL;
uschar *field_name = NULL;
nis_result *nrt = NULL;
nis_result *nre = NULL;
nis_object *tno, *eno;
struct entry_obj *eo;
struct table_obj *ta;
uschar *p = query + length;
uschar *yield = NULL;

do_cache = do_cache;   /* Placate picky compilers */

/* Search backwards for a colon to see if a result field name
has been given. */

while (p > query && p[-1] != ':') p--;

if (p > query)
  {
  field_name = p;
  p[-1] = 0;
  }
else p = query + length;

/* Now search backwards to find the comma that starts the
table name. */

while (p > query && p[-1] != ',') p--;
if (p <= query)
  {
  *errmsg = US"NIS+ query malformed";
  error_error = DEFER;
  goto NISPLUS_EXIT;
  }

/* Look up the data for the table, in order to get the field names,
check that we got back a table, and set up pointers so the field
names can be scanned. */

nrt = nis_lookup(CS p, EXPAND_NAME | NO_CACHE);
if (nrt->status != NIS_SUCCESS)
  {
  *errmsg = string_sprintf("NIS+ error accessing %s table: %s", p,
    nis_sperrno(nrt->status));
  if (nrt->status != NIS_NOTFOUND && nrt->status != NIS_NOSUCHTABLE)
    error_error = DEFER;
  goto NISPLUS_EXIT;
  }
tno = nrt->objects.objects_val;
if (tno->zo_data.zo_type != TABLE_OBJ)
  {
  *errmsg = string_sprintf("NIS+ error: %s is not a table", p);
  goto NISPLUS_EXIT;
  }
ta = &(tno->zo_data.objdata_u.ta_data);

/* Now look up the entry in the table, check that we got precisely one
object and that it is a table entry. */

nre = nis_list(CS query, EXPAND_NAME, NULL, NULL);
if (nre->status != NIS_SUCCESS)
  {
  *errmsg = string_sprintf("NIS+ error accessing entry %s: %s",
    query, nis_sperrno(nre->status));
  goto NISPLUS_EXIT;
  }
if (nre->objects.objects_len > 1)
  {
  *errmsg = string_sprintf("NIS+ returned more than one object for %s",
    query);
  goto NISPLUS_EXIT;
  }
else if (nre->objects.objects_len < 1)
  {
  *errmsg = string_sprintf("NIS+ returned no data for %s", query);
  goto NISPLUS_EXIT;
  }
eno = nre->objects.objects_val;
if (eno->zo_data.zo_type != ENTRY_OBJ)
  {
  *errmsg = string_sprintf("NIS+ error: %s is not an entry", query);
  goto NISPLUS_EXIT;
  }

/* Scan the columns in the entry and in the table. If a result field
was given, look for that field; otherwise concatenate all the fields
with their names. */

eo = &(eno->zo_data.objdata_u.en_data);
for (i = 0; i < eo->en_cols.en_cols_len; i++)
  {
  table_col *tc = ta->ta_cols.ta_cols_val + i;
  entry_col *ec = eo->en_cols.en_cols_val + i;
  int len = ec->ec_value.ec_value_len;
  uschar *value = US ec->ec_value.ec_value_val;

  /* The value may be NULL for a zero-length field. Turn this into an
  empty string for consistency. Remove trailing whitespace and zero
  bytes. */

  if (value == NULL) value = US""; else
    while (len > 0 && (value[len-1] == 0 || isspace(value[len-1])))
      len--;

  /* Concatenate all fields if no specific one selected */

  if (field_name == NULL)
    {
    yield = string_cat(yield, &ssize, &offset,US  tc->tc_name,
      Ustrlen(tc->tc_name));
    yield = string_cat(yield, &ssize, &offset, US"=", 1);

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

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

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

  /* When the specified field is found, grab its data and finish */

  else if (Ustrcmp(field_name, tc->tc_name) == 0)
    {
    yield = string_copyn(value, len);
    goto NISPLUS_EXIT;
    }
  }

/* Error if a field name was specified and we didn't find it; if no
field name, ensure the concatenated data is zero-terminated. */

if (field_name != NULL)
  *errmsg = string_sprintf("NIS+ field %s not found for %s", field_name,
    query);
else
  {
  yield[offset] = 0;
  store_reset(yield + offset + 1);
  }

/* Restore the colon in the query, and free result store before
finishing. */

NISPLUS_EXIT:
if (field_name != NULL) field_name[-1] = ':';
if (nrt != NULL) nis_freeresult(nrt);
if (nre != NULL) nis_freeresult(nre);

if (yield != NULL)
  {
  *result = yield;
  return OK;
  }

return error_error;      /* FAIL or DEFER */
}
Example #15
0
File: ldap.c Project: fanf2/exim
static int
perform_ldap_search(uschar *ldap_url, uschar *server, int s_port, int search_type,
  uschar **res, uschar **errmsg, BOOL *defer_break, uschar *user, uschar *password,
  int sizelimit, int timelimit, int tcplimit, int dereference, void *referrals)
{
LDAPURLDesc     *ludp = NULL;
LDAPMessage     *result = NULL;
BerElement      *ber;
LDAP_CONNECTION *lcp;

struct timeval timeout;
struct timeval *timeoutptr = NULL;

uschar *attr;
uschar **attrp;
uschar *data = NULL;
uschar *dn = NULL;
uschar *host;
uschar **values;
uschar **firstval;
uschar porttext[16];

uschar *error1 = NULL;   /* string representation of errcode (static) */
uschar *error2 = NULL;   /* error message from the server */
uschar *matched = NULL;  /* partially matched DN */

int    attr_count = 0;
int    error_yield = DEFER;
int    msgid;
int    rc, ldap_rc, ldap_parse_rc;
int    port;
int    ptr = 0;
int    rescount = 0;
int    size = 0;
BOOL   attribute_found = FALSE;
BOOL   ldapi = FALSE;

DEBUG(D_lookup)
  debug_printf("perform_ldap_search: ldap%s URL = \"%s\" server=%s port=%d "
    "sizelimit=%d timelimit=%d tcplimit=%d\n",
    (search_type == SEARCH_LDAP_MULTIPLE)? "m" :
    (search_type == SEARCH_LDAP_DN)? "dn" :
    (search_type == SEARCH_LDAP_AUTH)? "auth" : "",
    ldap_url, server, s_port, sizelimit, timelimit, tcplimit);

/* Check if LDAP thinks the URL is a valid LDAP URL. We assume that if the LDAP
library that is in use doesn't recognize, say, "ldapi", it will barf here. */

if (!ldap_is_ldap_url(CS ldap_url))
  {
  *errmsg = string_sprintf("ldap_is_ldap_url: not an LDAP url \"%s\"\n",
    ldap_url);
  goto RETURN_ERROR_BREAK;
  }

/* Parse the URL */

if ((rc = ldap_url_parse(CS ldap_url, &ludp)) != 0)
  {
  *errmsg = string_sprintf("ldap_url_parse: (error %d) parsing \"%s\"\n", rc,
    ldap_url);
  goto RETURN_ERROR_BREAK;
  }

/* If the host name is empty, take it from the separate argument, if one is
given. OpenLDAP 2.0.6 sets an unset hostname to "" rather than empty, but
expects NULL later in ldap_init() to mean "default", annoyingly. In OpenLDAP
2.0.11 this has changed (it uses NULL). */

if ((ludp->lud_host == NULL || ludp->lud_host[0] == 0) && server != NULL)
  {
  host = server;
  port = s_port;
  }
else
  {
  host = US ludp->lud_host;
  if (host != NULL && host[0] == 0) host = NULL;
  port = ludp->lud_port;
  }

DEBUG(D_lookup) debug_printf("after ldap_url_parse: host=%s port=%d\n",
  host, port);

if (port == 0) port = LDAP_PORT;      /* Default if none given */
sprintf(CS porttext, ":%d", port);    /* For messages */

/* If the "host name" is actually a path, we are going to connect using a Unix
socket, regardless of whether "ldapi" was actually specified or not. This means
that a Unix socket can be declared in eldap_default_servers, and "traditional"
LDAP queries using just "ldap" can be used ("ldaps" is similarly overridden).
The path may start with "/" or it may already be escaped as "%2F" if it was
actually declared that way in eldap_default_servers. (I did it that way the
first time.) If the host name is not a path, the use of "ldapi" causes an
error, except in the default case. (But lud_scheme doesn't seem to exist in
older libraries.) */

if (host != NULL)
  {
  if ((host[0] == '/' || Ustrncmp(host, "%2F", 3) == 0))
    {
    ldapi = TRUE;
    porttext[0] = 0;    /* Remove port from messages */
    }

  #if defined LDAP_LIB_OPENLDAP2
  else if (strncmp(ludp->lud_scheme, "ldapi", 5) == 0)
    {
    *errmsg = string_sprintf("ldapi requires an absolute path (\"%s\" given)",
      host);
    goto RETURN_ERROR;
    }
  #endif
  }

/* Count the attributes; we need this later to tell us how to format results */

for (attrp = USS ludp->lud_attrs; attrp != NULL && *attrp != NULL; attrp++)
  attr_count++;

/* See if we can find a cached connection to this host. The port is not
relevant for ldapi. The host name pointer is set to NULL if no host was given
(implying the library default), rather than to the empty string. Note that in
this case, there is no difference between ldap and ldapi. */

for (lcp = ldap_connections; lcp != NULL; lcp = lcp->next)
  {
  if ((host == NULL) != (lcp->host == NULL) ||
      (host != NULL && strcmpic(lcp->host, host) != 0))
    continue;
  if (ldapi || port == lcp->port) break;
  }

/* Use this network timeout in any requests. */

if (tcplimit > 0)
  {
  timeout.tv_sec = tcplimit;
  timeout.tv_usec = 0;
  timeoutptr = &timeout;
  }

/* If no cached connection found, we must open a connection to the server. If
the server name is actually an absolute path, we set ldapi=TRUE above. This
requests connection via a Unix socket. However, as far as I know, only OpenLDAP
supports the use of sockets, and the use of ldap_initialize(). */

if (lcp == NULL)
  {
  LDAP *ld;


  /* --------------------------- OpenLDAP ------------------------ */

  /* There seems to be a preference under OpenLDAP for ldap_initialize()
  instead of ldap_init(), though I have as yet been unable to find
  documentation that says this. (OpenLDAP documentation is sparse to
  non-existent). So we handle OpenLDAP differently here. Also, support for
  ldapi seems to be OpenLDAP-only at present. */

  #ifdef LDAP_LIB_OPENLDAP2

  /* We now need an empty string for the default host. Get some store in which
  to build a URL for ldap_initialize(). In the ldapi case, it can't be bigger
  than (9 + 3*Ustrlen(shost)), whereas in the other cases it can't be bigger
  than the host name + "ldaps:///" plus : and a port number, say 20 + the
  length of the host name. What we get should accommodate both, easily. */

  uschar *shost = (host == NULL)? US"" : host;
  uschar *init_url = store_get(20 + 3 * Ustrlen(shost));
  uschar *init_ptr;

  /* Handle connection via Unix socket ("ldapi"). We build a basic LDAP URI to
  contain the path name, with slashes escaped as %2F. */

  if (ldapi)
    {
    int ch;
    init_ptr = init_url + 8;
    Ustrcpy(init_url, "ldapi://");
    while ((ch = *shost++) != 0)
      {
      if (ch == '/')
        {
        Ustrncpy(init_ptr, "%2F", 3);
        init_ptr += 3;
        }
      else *init_ptr++ = ch;
      }
    *init_ptr = 0;
    }

  /* This is not an ldapi call. Just build a URI with the protocol type, host
  name, and port. */

  else
    {
    init_ptr = Ustrchr(ldap_url, '/');
    Ustrncpy(init_url, ldap_url, init_ptr - ldap_url);
    init_ptr = init_url + (init_ptr - ldap_url);
    sprintf(CS init_ptr, "//%s:%d/", shost, port);
    }

  /* Call ldap_initialize() and check the result */

  DEBUG(D_lookup) debug_printf("ldap_initialize with URL %s\n", init_url);
  rc = ldap_initialize(&ld, CS init_url);
  if (rc != LDAP_SUCCESS)
    {
    *errmsg = string_sprintf("ldap_initialize: (error %d) URL \"%s\"\n",
      rc, init_url);
    goto RETURN_ERROR;
    }
  store_reset(init_url);   /* Might as well save memory when we can */


  /* ------------------------- Not OpenLDAP ---------------------- */

  /* For libraries other than OpenLDAP, use ldap_init(). */

  #else   /* LDAP_LIB_OPENLDAP2 */
  ld = ldap_init(CS host, port);
  #endif  /* LDAP_LIB_OPENLDAP2 */

  /* -------------------------------------------------------------- */


  /* Handle failure to initialize */

  if (ld == NULL)
    {
    *errmsg = string_sprintf("failed to initialize for LDAP server %s%s - %s",
      host, porttext, strerror(errno));
    goto RETURN_ERROR;
    }

  /* Set the TCP connect time limit if available. This is something that is
  in Netscape SDK v4.1; I don't know about other libraries. */

  #ifdef LDAP_X_OPT_CONNECT_TIMEOUT
  if (tcplimit > 0)
    {
    int timeout1000 = tcplimit*1000;
    ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, (void *)&timeout1000);
    }
  else
    {
    int notimeout = LDAP_X_IO_TIMEOUT_NO_TIMEOUT;
    ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, (void *)&notimeout);
    }
  #endif

  /* Set the TCP connect timeout. This works with OpenLDAP 2.2.14. */

  #ifdef LDAP_OPT_NETWORK_TIMEOUT
  if (tcplimit > 0)
    ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, (void *)timeoutptr);
  #endif

  /* I could not get TLS to work until I set the version to 3. That version
  seems to be the default nowadays. The RFC is dated 1997, so I would hope
  that all the LDAP libraries support it. Therefore, if eldap_version hasn't
  been set, go for v3 if we can. */

  if (eldap_version < 0)
    {
    #ifdef LDAP_VERSION3
    eldap_version = LDAP_VERSION3;
    #else
    eldap_version = 2;
    #endif
    }

  #ifdef LDAP_OPT_PROTOCOL_VERSION
  ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, (void *)&eldap_version);
  #endif

  DEBUG(D_lookup) debug_printf("initialized for LDAP (v%d) server %s%s\n",
    eldap_version, host, porttext);

  /* If not using ldapi and TLS is available, set appropriate TLS options: hard
  for "ldaps" and soft otherwise. */

  #ifdef LDAP_OPT_X_TLS
  if (!ldapi)
    {
    int tls_option;
    if (strncmp(ludp->lud_scheme, "ldaps", 5) == 0)
      {
      tls_option = LDAP_OPT_X_TLS_HARD;
      DEBUG(D_lookup) debug_printf("LDAP_OPT_X_TLS_HARD set\n");
      }
    else
      {
      tls_option = LDAP_OPT_X_TLS_TRY;
      DEBUG(D_lookup) debug_printf("LDAP_OPT_X_TLS_TRY set\n");
      }
    ldap_set_option(ld, LDAP_OPT_X_TLS, (void *)&tls_option);
    }
  #endif  /* LDAP_OPT_X_TLS */

  #ifdef LDAP_OPT_X_TLS_CACERTFILE
  if (eldap_ca_cert_file != NULL)
    {
    ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTFILE, eldap_ca_cert_file);
    }
  #endif
  #ifdef LDAP_OPT_X_TLS_CACERTDIR
  if (eldap_ca_cert_dir != NULL)
    {
    ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTDIR, eldap_ca_cert_dir);
    }
  #endif
  #ifdef LDAP_OPT_X_TLS_CERTFILE
  if (eldap_cert_file != NULL)
    {
    ldap_set_option(ld, LDAP_OPT_X_TLS_CERTFILE, eldap_cert_file);
    }
  #endif
  #ifdef LDAP_OPT_X_TLS_KEYFILE
  if (eldap_cert_key != NULL)
    {
    ldap_set_option(ld, LDAP_OPT_X_TLS_KEYFILE, eldap_cert_key);
    }
  #endif
  #ifdef LDAP_OPT_X_TLS_CIPHER_SUITE
  if (eldap_cipher_suite != NULL)
    {
    ldap_set_option(ld, LDAP_OPT_X_TLS_CIPHER_SUITE, eldap_cipher_suite);
    }
  #endif
  #ifdef LDAP_OPT_X_TLS_REQUIRE_CERT
  if (eldap_require_cert != NULL)
    {
    int cert_option = LDAP_OPT_X_TLS_NEVER;
    if (Ustrcmp(eldap_require_cert, "hard") == 0)
      {
      cert_option = LDAP_OPT_X_TLS_HARD;
      }
    else if (Ustrcmp(eldap_require_cert, "demand") == 0)
      {
      cert_option = LDAP_OPT_X_TLS_DEMAND;
      }
    else if (Ustrcmp(eldap_require_cert, "allow") == 0)
      {
      cert_option = LDAP_OPT_X_TLS_ALLOW;
      }
    else if (Ustrcmp(eldap_require_cert, "try") == 0)
      {
      cert_option = LDAP_OPT_X_TLS_TRY;
      }
    ldap_set_option(ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &cert_option);
    }
  #endif

  /* Now add this connection to the chain of cached connections */

  lcp = store_get(sizeof(LDAP_CONNECTION));
  lcp->host = (host == NULL)? NULL : string_copy(host);
  lcp->bound = FALSE;
  lcp->user = NULL;
  lcp->password = NULL;
  lcp->port = port;
  lcp->ld = ld;
  lcp->next = ldap_connections;
  ldap_connections = lcp;
  }

/* Found cached connection */

else
  {
  DEBUG(D_lookup)
    debug_printf("re-using cached connection to LDAP server %s%s\n",
      host, porttext);
  }

/* Bind with the user/password supplied, or an anonymous bind if these values
are NULL, unless a cached connection is already bound with the same values. */

if (!lcp->bound ||
    (lcp->user == NULL && user != NULL) ||
    (lcp->user != NULL && user == NULL) ||
    (lcp->user != NULL && user != NULL && Ustrcmp(lcp->user, user) != 0) ||
    (lcp->password == NULL && password != NULL) ||
    (lcp->password != NULL && password == NULL) ||
    (lcp->password != NULL && password != NULL &&
      Ustrcmp(lcp->password, password) != 0))
  {
  DEBUG(D_lookup) debug_printf("%sbinding with user=%s password=%s\n",
    (lcp->bound)? "re-" : "", user, password);
#ifdef LDAP_OPT_X_TLS
  /* The Oracle LDAP libraries (LDAP_LIB_TYPE=SOLARIS) don't support this: */
  if (eldap_start_tls)
    {
    ldap_start_tls_s(lcp->ld, NULL, NULL);
    }
#endif
  if ((msgid = ldap_bind(lcp->ld, CS user, CS password, LDAP_AUTH_SIMPLE))
       == -1)
    {
    *errmsg = string_sprintf("failed to bind the LDAP connection to server "
      "%s%s - ldap_bind() returned -1", host, porttext);
    goto RETURN_ERROR;
    }

  if ((rc = ldap_result( lcp->ld, msgid, 1, timeoutptr, &result )) <= 0)
    {
    *errmsg = string_sprintf("failed to bind the LDAP connection to server "
      "%s%s - LDAP error: %s", host, porttext,
      rc == -1 ? "result retrieval failed" : "timeout" );
    result = NULL;
    goto RETURN_ERROR;
    }

  rc = ldap_result2error( lcp->ld, result, 0 );

  /* Invalid credentials when just checking credentials returns FAIL. This
  stops any further servers being tried. */

  if (search_type == SEARCH_LDAP_AUTH && rc == LDAP_INVALID_CREDENTIALS)
    {
    DEBUG(D_lookup)
      debug_printf("Invalid credentials: ldapauth returns FAIL\n");
    error_yield = FAIL;
    goto RETURN_ERROR_NOMSG;
    }

  /* Otherwise we have a problem that doesn't stop further servers from being
  tried. */

  if (rc != LDAP_SUCCESS)
    {
    *errmsg = string_sprintf("failed to bind the LDAP connection to server "
      "%s%s - LDAP error %d: %s", host, porttext, rc, ldap_err2string(rc));
    goto RETURN_ERROR;
    }

  /* Successful bind */

  lcp->bound = TRUE;
  lcp->user = (user == NULL)? NULL : string_copy(user);
  lcp->password = (password == NULL)? NULL : string_copy(password);

  ldap_msgfree(result);
  result = NULL;
  }

/* If we are just checking credentials, return OK. */

if (search_type == SEARCH_LDAP_AUTH)
  {
  DEBUG(D_lookup) debug_printf("Bind succeeded: ldapauth returns OK\n");
  goto RETURN_OK;
  }

/* Before doing the search, set the time and size limits (if given). Here again
the different implementations of LDAP have chosen to do things differently. */

#if defined(LDAP_OPT_SIZELIMIT)
ldap_set_option(lcp->ld, LDAP_OPT_SIZELIMIT, (void *)&sizelimit);
ldap_set_option(lcp->ld, LDAP_OPT_TIMELIMIT, (void *)&timelimit);
#else
lcp->ld->ld_sizelimit = sizelimit;
lcp->ld->ld_timelimit = timelimit;
#endif

/* Similarly for dereferencing aliases. Don't know if this is possible on
an LDAP library without LDAP_OPT_DEREF. */

#if defined(LDAP_OPT_DEREF)
ldap_set_option(lcp->ld, LDAP_OPT_DEREF, (void *)&dereference);
#endif

/* Similarly for the referral setting; should the library follow referrals that
the LDAP server returns? The conditional is just in case someone uses a library
without it. */

#if defined(LDAP_OPT_REFERRALS)
ldap_set_option(lcp->ld, LDAP_OPT_REFERRALS, referrals);
#endif

/* Start the search on the server. */

DEBUG(D_lookup) debug_printf("Start search\n");

msgid = ldap_search(lcp->ld, ludp->lud_dn, ludp->lud_scope, ludp->lud_filter,
  ludp->lud_attrs, 0);

if (msgid == -1)
  {
  #if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
  int err;
  ldap_get_option(lcp->ld, LDAP_OPT_ERROR_NUMBER, &err);
  *errmsg = string_sprintf("ldap_search failed: %d, %s", err,
    ldap_err2string(err));

  #else
  *errmsg = string_sprintf("ldap_search failed");
  #endif

  goto RETURN_ERROR;
  }

/* Loop to pick up results as they come in, setting a timeout if one was
given. */

while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
        LDAP_RES_SEARCH_ENTRY)
  {
  LDAPMessage  *e;

  DEBUG(D_lookup) debug_printf("ldap_result loop\n");

  for(e = ldap_first_entry(lcp->ld, result);
      e != NULL;
      e = ldap_next_entry(lcp->ld, e))
    {
    uschar *new_dn;
    BOOL insert_space = FALSE;

    DEBUG(D_lookup) debug_printf("LDAP entry loop\n");

    rescount++;   /* Count results */

    /* Results for multiple entries values are separated by newlines. */

    if (data != NULL) data = string_cat(data, &size, &ptr, US"\n", 1);

    /* Get the DN from the last result. */

    new_dn = US ldap_get_dn(lcp->ld, e);
    if (new_dn != NULL)
      {
      if (dn != NULL)
        {
        #if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2
        ldap_memfree(dn);
        #else   /* OPENLDAP 1, UMich, Solaris */
        free(dn);
        #endif
        }
      /* Save for later */
      dn = new_dn;
      }

    /* If the data we want is actually the DN rather than any attribute values,
    (an "ldapdn" search) add it to the data string. If there are multiple
    entries, the DNs will be concatenated, but we test for this case below, as
    for SEARCH_LDAP_SINGLE, and give an error. */

    if (search_type == SEARCH_LDAP_DN)   /* Do not amalgamate these into one */
      {                                  /* condition, because of the else */
      if (new_dn != NULL)                /* below, that's for the first only */
        {
        data = string_cat(data, &size, &ptr, new_dn, Ustrlen(new_dn));
        data[ptr] = 0;
        attribute_found = TRUE;
        }
      }

    /* Otherwise, loop through the entry, grabbing attribute values. If there's
    only one attribute being retrieved, no attribute name is given, and the
    result is not quoted. Multiple values are separated by (comma, space).
    If more than one attribute is being retrieved, the data is given as a
    sequence of name=value pairs, with the value always in quotes. If there are
    multiple values, they are given within the quotes, comma separated. */

    else for (attr = US ldap_first_attribute(lcp->ld, e, &ber);
              attr != NULL;
              attr = US ldap_next_attribute(lcp->ld, e, ber))
      {
      if (attr[0] != 0)
        {
        /* Get array of values for this attribute. */

        if ((firstval = values = USS ldap_get_values(lcp->ld, e, CS attr))
             != NULL)
          {
          if (attr_count != 1)
            {
            if (insert_space)
              data = string_cat(data, &size, &ptr, US" ", 1);
            else
              insert_space = TRUE;
            data = string_cat(data, &size, &ptr, attr, Ustrlen(attr));
            data = string_cat(data, &size, &ptr, US"=\"", 2);
            }

          while (*values != NULL)
            {
            uschar *value = *values;
            int len = Ustrlen(value);

            DEBUG(D_lookup) debug_printf("LDAP attr loop %s:%s\n", attr, value);

            if (values != firstval)
              data = string_cat(data, &size, &ptr, US", ", 2);

            /* For multiple attributes, the data is in quotes. We must escape
            internal quotes, backslashes, newlines. */

            if (attr_count != 1)
              {
              int j;
              for (j = 0; j < len; j++)
                {
                if (value[j] == '\n')
                  data = string_cat(data, &size, &ptr, US"\\n", 2);
                else
                  {
                  if (value[j] == '\"' || value[j] == '\\')
                    data = string_cat(data, &size, &ptr, US"\\", 1);
                  data = string_cat(data, &size, &ptr, value+j, 1);
                  }
                }
              }

            /* For single attributes, copy the value verbatim */

            else data = string_cat(data, &size, &ptr, value, len);

            /* Move on to the next value */

            values++;
            attribute_found = TRUE;
            }

          /* Closing quote at the end of the data for a named attribute. */

          if (attr_count != 1)
            data = string_cat(data, &size, &ptr, US"\"", 1);

          /* Free the values */

          ldap_value_free(CSS firstval);
          }
        }

      #if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2

      /* Netscape and OpenLDAP2 LDAP's attrs are dynamically allocated and need
      to be freed. UMich LDAP stores them in static storage and does not require
      this. */

      ldap_memfree(attr);
      #endif
      }        /* End "for" loop for extracting attributes from an entry */
    }          /* End "for" loop for extracting entries from a result */

  /* Free the result */

  ldap_msgfree(result);
  result = NULL;
  }            /* End "while" loop for multiple results */

/* Terminate the dynamic string that we have built and reclaim unused store */

if (data != NULL)
  {
  data[ptr] = 0;
  store_reset(data + ptr + 1);
  }

/* Copy the last dn into eldap_dn */

if (dn != NULL)
  {
  eldap_dn = string_copy(dn);
  #if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2
  ldap_memfree(dn);
  #else   /* OPENLDAP 1, UMich, Solaris */
  free(dn);
  #endif
  }

DEBUG(D_lookup) debug_printf("search ended by ldap_result yielding %d\n",rc);

if (rc == 0)
  {
  *errmsg = US"ldap_result timed out";
  goto RETURN_ERROR;
  }

/* A return code of -1 seems to mean "ldap_result failed internally or couldn't
provide you with a message". Other error states seem to exist where
ldap_result() didn't give us any message from the server at all, leaving result
set to NULL. Apparently, "the error parameters of the LDAP session handle will
be set accordingly". That's the best we can do to retrieve an error status; we
can't use functions like ldap_result2error because they parse a message from
the server, which we didn't get.

Annoyingly, the different implementations of LDAP have gone for different
methods of handling error codes and generating error messages. */

if (rc == -1 || result == NULL)
  {
  int err;
  DEBUG(D_lookup) debug_printf("ldap_result failed\n");

  #if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
    ldap_get_option(lcp->ld, LDAP_OPT_ERROR_NUMBER, &err);
    *errmsg = string_sprintf("ldap_result failed: %d, %s",
      err, ldap_err2string(err));

  #elif defined LDAP_LIB_NETSCAPE
    /* Dubious (surely 'matched' is spurious here?) */
    (void)ldap_get_lderrno(lcp->ld, &matched, &error1);
    *errmsg = string_sprintf("ldap_result failed: %s (%s)", error1, matched);

  #else                             /* UMich LDAP aka OpenLDAP 1.x */
    *errmsg = string_sprintf("ldap_result failed: %d, %s",
      lcp->ld->ld_errno, ldap_err2string(lcp->ld->ld_errno));
  #endif

  goto RETURN_ERROR;
  }

/* A return code that isn't -1 doesn't necessarily mean there were no problems
with the search. The message must be an LDAP_RES_SEARCH_RESULT or
LDAP_RES_SEARCH_REFERENCE or else it's something we can't handle. Some versions
of LDAP do not define LDAP_RES_SEARCH_REFERENCE (LDAP v1 is one, it seems). So
we don't provide that functionality when we can't. :-) */

if (rc != LDAP_RES_SEARCH_RESULT
#ifdef LDAP_RES_SEARCH_REFERENCE
    && rc != LDAP_RES_SEARCH_REFERENCE
#endif
   )
  {
  *errmsg = string_sprintf("ldap_result returned unexpected code %d", rc);
  goto RETURN_ERROR;
  }

/* We have a result message from the server. This doesn't yet mean all is well.
We need to parse the message to find out exactly what's happened. */

#if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
  ldap_rc = rc;
  ldap_parse_rc = ldap_parse_result(lcp->ld, result, &rc, CSS &matched,
    CSS &error2, NULL, NULL, 0);
  DEBUG(D_lookup) debug_printf("ldap_parse_result: %d\n", ldap_parse_rc);
  if (ldap_parse_rc < 0 &&
      (ldap_parse_rc != LDAP_NO_RESULTS_RETURNED
      #ifdef LDAP_RES_SEARCH_REFERENCE
      || ldap_rc != LDAP_RES_SEARCH_REFERENCE
      #endif
     ))
    {
    *errmsg = string_sprintf("ldap_parse_result failed %d", ldap_parse_rc);
    goto RETURN_ERROR;
    }
  error1 = US ldap_err2string(rc);

#elif defined LDAP_LIB_NETSCAPE
  /* Dubious (it doesn't reference 'result' at all!) */
  rc = ldap_get_lderrno(lcp->ld, &matched, &error1);

#else                             /* UMich LDAP aka OpenLDAP 1.x */
  rc = ldap_result2error(lcp->ld, result, 0);
  error1 = ldap_err2string(rc);
  error2 = lcp->ld->ld_error;
  matched = lcp->ld->ld_matched;
#endif

/* Process the status as follows:

  (1) If we get LDAP_SIZELIMIT_EXCEEDED, just carry on, to return the
      truncated result list.

  (2) If we get LDAP_RES_SEARCH_REFERENCE, also just carry on. This was a
      submitted patch that is reported to "do the right thing" with Solaris
      LDAP libraries. (The problem it addresses apparently does not occur with
      Open LDAP.)

  (3) The range of errors defined by LDAP_NAME_ERROR generally mean "that
      object does not, or cannot, exist in the database". For those cases we
      fail the lookup.

  (4) All other non-successes here are treated as some kind of problem with
      the lookup, so return DEFER (which is the default in error_yield).
*/

DEBUG(D_lookup) debug_printf("ldap_parse_result yielded %d: %s\n",
  rc, ldap_err2string(rc));

if (rc != LDAP_SUCCESS && rc != LDAP_SIZELIMIT_EXCEEDED
    #ifdef LDAP_RES_SEARCH_REFERENCE
    && rc != LDAP_RES_SEARCH_REFERENCE
    #endif
    )
  {
  *errmsg = string_sprintf("LDAP search failed - error %d: %s%s%s%s%s",
    rc,
    (error1 != NULL)?                       error1  : US"",
    (error2 != NULL && error2[0] != 0)?     US"/"   : US"",
    (error2 != NULL)?                       error2  : US"",
    (matched != NULL && matched[0] != 0)?   US"/"   : US"",
    (matched != NULL)?                      matched : US"");

  #if defined LDAP_NAME_ERROR
  if (LDAP_NAME_ERROR(rc))
  #elif defined NAME_ERROR    /* OPENLDAP1 calls it this */
  if (NAME_ERROR(rc))
  #else
  if (rc == LDAP_NO_SUCH_OBJECT)
  #endif

    {
    DEBUG(D_lookup) debug_printf("lookup failure forced\n");
    error_yield = FAIL;
    }
  goto RETURN_ERROR;
  }

/* The search succeeded. Check if we have too many results */

if (search_type != SEARCH_LDAP_MULTIPLE && rescount > 1)
  {
  *errmsg = string_sprintf("LDAP search: more than one entry (%d) was returned "
    "(filter not specific enough?)", rescount);
  goto RETURN_ERROR_BREAK;
  }

/* Check if we have too few (zero) entries */

if (rescount < 1)
  {
  *errmsg = string_sprintf("LDAP search: no results");
  error_yield = FAIL;
  goto RETURN_ERROR_BREAK;
  }

/* If an entry was found, but it had no attributes, we behave as if no entries
were found, that is, the lookup failed. */

if (!attribute_found)
  {
  *errmsg = US"LDAP search: found no attributes";
  error_yield = FAIL;
  goto RETURN_ERROR;
  }

/* Otherwise, it's all worked */

DEBUG(D_lookup) debug_printf("LDAP search: returning: %s\n", data);
*res = data;

RETURN_OK:
if (result != NULL) ldap_msgfree(result);
ldap_free_urldesc(ludp);
return OK;

/* Error returns */

RETURN_ERROR_BREAK:
*defer_break = TRUE;

RETURN_ERROR:
DEBUG(D_lookup) debug_printf("%s\n", *errmsg);

RETURN_ERROR_NOMSG:
if (result != NULL) ldap_msgfree(result);
if (ludp != NULL) ldap_free_urldesc(ludp);

#if defined LDAP_LIB_OPENLDAP2
  if (error2 != NULL)  ldap_memfree(error2);
  if (matched != NULL) ldap_memfree(matched);
#endif

return error_yield;
}
Example #16
0
void
auth_cyrus_sasl_init(auth_instance *ablock)
{
auth_cyrus_sasl_options_block *ob =
  (auth_cyrus_sasl_options_block *)(ablock->options_block);
const uschar *list, *listptr, *buffer;
int rc, i;
unsigned int len;
uschar *rs_point, *expanded_hostname;
char *realm_expanded;

sasl_conn_t *conn;
sasl_callback_t cbs[] = {
  {SASL_CB_GETOPT, NULL, NULL },
  {SASL_CB_LIST_END, NULL, NULL}};

/* default the mechanism to our "public name" */
if (ob->server_mech == NULL)
  ob->server_mech = string_copy(ablock->public_name);

expanded_hostname = expand_string(ob->server_hostname);
if (expanded_hostname == NULL)
  log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
      "couldn't expand server_hostname [%s]: %s",
      ablock->name, ob->server_hostname, expand_string_message);

realm_expanded = NULL;
if (ob->server_realm != NULL) {
  realm_expanded = CS expand_string(ob->server_realm);
  if (realm_expanded == NULL)
    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
        "couldn't expand server_realm [%s]: %s",
        ablock->name, ob->server_realm, expand_string_message);
}

/* we're going to initialise the library to check that there is an
 * authenticator of type whatever mechanism we're using
 */

cbs[0].proc = (int(*)(void)) &mysasl_config;
cbs[0].context = ob->server_mech;

if ((rc = sasl_server_init(cbs, "exim")) != SASL_OK )
  log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
      "couldn't initialise Cyrus SASL library.", ablock->name);

if ((rc = sasl_server_new(CS ob->server_service, CS expanded_hostname,
                   realm_expanded, NULL, NULL, NULL, 0, &conn)) != SASL_OK )
  log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
      "couldn't initialise Cyrus SASL server connection.", ablock->name);

if ((rc = sasl_listmech(conn, NULL, "", ":", "", (const char **)&list, &len, &i)) != SASL_OK )
  log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
      "couldn't get Cyrus SASL mechanism list.", ablock->name);

i = ':';
listptr = list;

HDEBUG(D_auth)
  {
  debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
      ob->server_service, expanded_hostname, realm_expanded);
  debug_printf("Cyrus SASL knows mechanisms: %s\n", list);
  }

/* the store_get / store_reset mechanism is hierarchical
 * the hierarchy is stored for us behind our back. This point
 * creates a hierarchy point for this function.
 */
rs_point = store_get(0);

/* loop until either we get to the end of the list, or we match the
 * public name of this authenticator
 */
while ( ( buffer = string_nextinlist(&listptr, &i, NULL, 0) ) &&
       strcmpic(buffer,ob->server_mech) );

if (!buffer)
  log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator:  "
      "Cyrus SASL doesn't know about mechanism %s.", ablock->name, ob->server_mech);

store_reset(rs_point);

HDEBUG(D_auth) debug_printf("Cyrus SASL driver %s: %s initialised\n", ablock->name, ablock->public_name);

/* make sure that if we get here then we're allowed to advertise. */
ablock->server = TRUE;

sasl_dispose(&conn);
sasl_done();
}
Example #17
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 */
  }
}
Example #18
0
File: oracle.c Project: 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 */
  }
}
Example #19
0
int main(int argc, char **cargv)
{
struct stat statbuf;
int maxkeep = 30 * 24 * 60 * 60;
int dbdata_type, i, oldest, path_len;
key_item *keychain = NULL;
void *reset_point;
open_db dbblock;
open_db *dbm;
EXIM_CURSOR *cursor;
uschar **argv = USS cargv;
uschar buffer[256];
uschar *key;

/* Scan the options */

for (i = 1; i < argc; i++)
  {
  if (argv[i][0] != '-') break;
  if (Ustrcmp(argv[i], "-f") == 0) continue;
  if (Ustrcmp(argv[i], "-t") == 0)
    {
    uschar *s;
    s = argv[++i];
    maxkeep = 0;
    while (*s != 0)
      {
      int value, count;
      if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
      (void)sscanf(CS s, "%d%n", &value, &count);
      s += count;
      switch (*s)
        {
        case 'w': value *= 7;
        case 'd': value *= 24;
        case 'h': value *= 60;
        case 'm': value *= 60;
        case 's': s++;
        break;
        default: usage(US"tidydb", US" [-t <time>]");
        }
      maxkeep += value;
      }
    }
  else usage(US"tidydb", US" [-t <time>]");
  }

/* Adjust argument values and process arguments */

argc -= --i;
argv += i;

dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");

/* Compute the oldest keep time, verify what we are doing, and open the
database */

oldest = time(NULL) - maxkeep;
printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);

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

/* Prepare for building file names */

sprintf(CS buffer, "%s/input/", argv[1]);
path_len = Ustrlen(buffer);


/* It appears, by experiment, that it is a bad idea to make changes
to the file while scanning it. Pity the man page doesn't warn you about that.
Therefore, we scan and build a list of all the keys. Then we use that to
read the records and possibly update them. */

key = dbfn_scan(dbm, TRUE, &cursor);
while (key != NULL)
  {
  key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
  k->next = keychain;
  keychain = k;
  Ustrcpy(k->key, key);
  key = dbfn_scan(dbm, FALSE, &cursor);
  }

/* Now scan the collected keys and operate on the records, resetting
the store each time round. */

reset_point = store_get(0);

while (keychain != NULL)
  {
  dbdata_generic *value;

  store_reset(reset_point);
  key = keychain->key;
  keychain = keychain->next;
  value = dbfn_read_with_length(dbm, key, NULL);

  /* A continuation record may have been deleted or renamed already, so
  non-existence is not serious. */

  if (value == NULL) continue;

  /* Delete if too old */

  if (value->time_stamp < oldest)
    {
    printf("deleted %s (too old)\n", key);
    dbfn_delete(dbm, key);
    continue;
    }

  /* Do database-specific tidying for wait databases, and message-
  specific tidying for the retry database. */

  if (dbdata_type == type_wait)
    {
    dbdata_wait *wait = (dbdata_wait *)value;
    BOOL update = FALSE;

    /* Leave corrupt records alone */

    if (wait->count > WAIT_NAME_MAX)
      {
      printf("**** Data for %s corrupted\n  count=%d=0x%x max=%d\n",
        key, wait->count, wait->count, WAIT_NAME_MAX);
      continue;
      }

    /* Loop for renamed continuation records. For each message id,
    check to see if the message exists, and if not, remove its entry
    from the record. Because of the possibility of split input directories,
    we must look in both possible places for a -D file. */

    for (;;)
      {
      int offset;
      int length = wait->count * MESSAGE_ID_LENGTH;

      for (offset = length - MESSAGE_ID_LENGTH;
           offset >= 0; offset -= MESSAGE_ID_LENGTH)
        {
        Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
        sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");

        if (Ustat(buffer, &statbuf) != 0)
          {
          buffer[path_len] = wait->text[offset+5];
          buffer[path_len+1] = '/';
          Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
          sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");

          if (Ustat(buffer, &statbuf) != 0)
            {
            int left = length - offset - MESSAGE_ID_LENGTH;
            if (left > 0) Ustrncpy(wait->text + offset,
              wait->text + offset + MESSAGE_ID_LENGTH, left);
            wait->count--;
            length -= MESSAGE_ID_LENGTH;
            update = TRUE;
            }
          }
        }

      /* If record is empty and the main record, either delete it or rename
      the next continuation, repeating if that is also empty. */

      if (wait->count == 0 && Ustrchr(key, ':') == NULL)
        {
        while (wait->count == 0 && wait->sequence > 0)
          {
          uschar newkey[256];
          dbdata_generic *newvalue;
          sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
          newvalue = dbfn_read_with_length(dbm, newkey, NULL);
          if (newvalue != NULL)
            {
            value = newvalue;
            wait = (dbdata_wait *)newvalue;
            dbfn_delete(dbm, newkey);
            printf("renamed %s\n", newkey);
            update = TRUE;
            }
          else wait->sequence--;
          }

        /* If we have ended up with an empty main record, delete it
        and break the loop. Otherwise the new record will be scanned. */

        if (wait->count == 0 && wait->sequence == 0)
          {
          dbfn_delete(dbm, key);
          printf("deleted %s (empty)\n", key);
          update = FALSE;
          break;
          }
        }

      /* If not an empty main record, break the loop */

      else break;
      }

    /* Re-write the record if required */

    if (update)
      {
      printf("updated %s\n", key);
      dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
        wait->count * MESSAGE_ID_LENGTH);
      }
    }

  /* If a retry record's key ends with a message-id, check that that message
  still exists; if not, remove this record. */

  else if (dbdata_type == type_retry)
    {
    uschar *id;
    int len = Ustrlen(key);

    if (len < MESSAGE_ID_LENGTH + 1) continue;
    id = key + len - MESSAGE_ID_LENGTH - 1;
    if (*id++ != ':') continue;

    for (i = 0; i < MESSAGE_ID_LENGTH; i++)
      {
      if (i == 6 || i == 13)
        { if (id[i] != '-') break; }
      else
        { if (!isalnum(id[i])) break; }
      }
    if (i < MESSAGE_ID_LENGTH) continue;

    Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
    sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");

    if (Ustat(buffer, &statbuf) != 0)
      {
      sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
      if (Ustat(buffer, &statbuf) != 0)
        {
        dbfn_delete(dbm, key);
        printf("deleted %s (no message)\n", key);
        }
      }
    }
  }

dbfn_close(dbm);
printf("Tidying complete\n");
return 0;
}
Example #20
0
int
main(int argc, char **cargv)
{
int dbdata_type = 0;
int yield = 0;
open_db dbblock;
open_db *dbm;
EXIM_CURSOR *cursor;
uschar **argv = USS cargv;
uschar *key;
uschar keybuffer[1024];

/* Check the arguments, and open the database */

dbdata_type = check_args(argc, argv, US"dumpdb", US"");
dbm = dbfn_open(argv[1], argv[2], O_RDONLY, &dbblock);
if (dbm == NULL) exit(1);

/* Scan the file, formatting the information for each entry. Note
that data is returned in a malloc'ed block, in order that it be
correctly aligned. */

key = dbfn_scan(dbm, TRUE, &cursor);
while (key != NULL)
  {
  dbdata_retry *retry;
  dbdata_wait *wait;
  dbdata_callout_cache *callout;
  dbdata_ratelimit *ratelimit;
  int count_bad = 0;
  int i, length;
  uschar *t;
  uschar name[MESSAGE_ID_LENGTH + 1];
  void *value;

  /* Keep a copy of the key separate, as in some DBM's the pointer is into data
  which might change. */

  if (Ustrlen(key) > sizeof(keybuffer) - 1)
    {
    printf("**** Overlong key encountered: %s\n", key);
    return 1;
    }
  Ustrcpy(keybuffer, key);
  value = dbfn_read_with_length(dbm, keybuffer, &length);

  if (value == NULL)
    fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
                    "was not found in the file - something is wrong!\n",
      CS keybuffer);
  else
    {
    /* Note: don't use print_time more than once in one statement, since
    it uses a single buffer. */

    switch(dbdata_type)
      {
      case type_retry:
      retry = (dbdata_retry *)value;
      printf("  %s %d %d %s\n%s  ", keybuffer, retry->basic_errno,
        retry->more_errno, retry->text,
        print_time(retry->first_failed));
      printf("%s  ", print_time(retry->last_try));
      printf("%s %s\n", print_time(retry->next_try),
        (retry->expired)? "*" : "");
      break;

      case type_wait:
      wait = (dbdata_wait *)value;
      printf("%s ", keybuffer);
      t = wait->text;
      name[MESSAGE_ID_LENGTH] = 0;

      if (wait->count > WAIT_NAME_MAX)
        {
        fprintf(stderr,
          "**** Data for %s corrupted\n  count=%d=0x%x max=%d\n",
          CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
        wait->count = WAIT_NAME_MAX;
        yield = count_bad = 1;
        }
      for (i = 1; i <= wait->count; i++)
        {
        Ustrncpy(name, t, MESSAGE_ID_LENGTH);
        if (count_bad && name[0] == 0) break;
        if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
            Ustrspn(name, "0123456789"
                          "abcdefghijklmnopqrstuvwxyz"
                          "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
          {
          int j;
          fprintf(stderr,
            "**** Data for %s corrupted: bad character in message id\n",
            CS keybuffer);
          for (j = 0; j < MESSAGE_ID_LENGTH; j++)
            fprintf(stderr, "%02x ", name[j]);
          fprintf(stderr, "\n");
          yield = 1;
          break;
          }
        printf("%s ", name);
        t += MESSAGE_ID_LENGTH;
        }
      printf("\n");
      break;

      case type_misc:
      printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
        keybuffer);
      break;

      case type_callout:
      callout = (dbdata_callout_cache *)value;

      /* New-style address record */

      if (length == sizeof(dbdata_callout_cache_address))
        {
        printf("%s %s callout=%s\n",
          print_time(((dbdata_generic *)value)->time_stamp),
          keybuffer,
          print_cache(callout->result));
        }

      /* New-style domain record */

      else if (length == sizeof(dbdata_callout_cache))
        {
        printf("%s %s callout=%s postmaster=%s",
          print_time(((dbdata_generic *)value)->time_stamp),
          keybuffer,
          print_cache(callout->result),
          print_cache(callout->postmaster_result));
        if (callout->postmaster_result != ccache_unknown)
          printf(" (%s)", print_time(callout->postmaster_stamp));
        printf(" random=%s", print_cache(callout->random_result));
        if (callout->random_result != ccache_unknown)
          printf(" (%s)", print_time(callout->random_stamp));
        printf("\n");
        }

      /* Old-style domain record, without separate timestamps. This code can
      eventually be thrown away, say in 5 years' time (it's now Feb 2003). */

      else
        {
        printf("%s %s callout=%s postmaster=%s random=%s\n",
          print_time(((dbdata_generic *)value)->time_stamp),
          keybuffer,
          print_cache(callout->result),
          print_cache(callout->postmaster_result),
          print_cache(callout->random_result));
        }

      break;

      case type_ratelimit:
      ratelimit = (dbdata_ratelimit *)value;

      printf("%s.%06d rate: %10.3f key: %s\n",
        print_time(ratelimit->time_stamp), ratelimit->time_usec,
        ratelimit->rate, keybuffer);

      break;
      }
    store_reset(value);
    }
  key = dbfn_scan(dbm, FALSE, &cursor);
  }

dbfn_close(dbm);
return yield;
}
Example #21
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 */
  }
}
Example #22
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;
}
Example #23
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 */
  }
}
Example #24
0
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 */
    }
}