Example #1
0
void *
dbmdb_open(uschar *filename, uschar **errmsg)
{
EXIM_DB *yield;
EXIM_DBOPEN(filename, O_RDONLY, 0, &yield);
if (yield == NULL)
  {
  int save_errno = errno;
  *errmsg = string_open_failed(errno, "%s as a %s file", filename, EXIM_DBTYPE);
  errno = save_errno;
  }
return yield;
}
Example #2
0
BOOL
autoreply_transport_entry(
  transport_instance *tblock,      /* data for this instantiation */
  address_item *addr)              /* address we are working on */
{
int fd, pid, rc;
int cache_fd = -1;
int log_fd = -1;
int cache_size = 0;
int add_size = 0;
EXIM_DB *dbm_file = NULL;
BOOL file_expand, return_message;
uschar *from, *reply_to, *to, *cc, *bcc, *subject, *headers, *text, *file;
uschar *logfile, *oncelog;
uschar *cache_buff = NULL;
uschar *cache_time = NULL;
uschar *message_id = NULL;
header_line *h;
time_t now = time(NULL);
time_t once_repeat_sec = 0;
FILE *f;
FILE *ff = NULL;

autoreply_transport_options_block *ob =
  (autoreply_transport_options_block *)(tblock->options_block);

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

/* Set up for the good case */

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

/* If the address is pointing to a reply block, then take all the data
from that block. It has typically been set up by a mail filter processing
router. Otherwise, the data must be supplied by this transport, and
it has to be expanded here. */

if (addr->reply != NULL)
  {
  DEBUG(D_transport) debug_printf("taking data from address\n");
  from = addr->reply->from;
  reply_to = addr->reply->reply_to;
  to = addr->reply->to;
  cc = addr->reply->cc;
  bcc = addr->reply->bcc;
  subject = addr->reply->subject;
  headers = addr->reply->headers;
  text = addr->reply->text;
  file = addr->reply->file;
  logfile = addr->reply->logfile;
  oncelog = addr->reply->oncelog;
  once_repeat_sec = addr->reply->once_repeat;
  file_expand = addr->reply->file_expand;
  expand_forbid = addr->reply->expand_forbid;
  return_message = addr->reply->return_message;
  }
else
  {
  uschar *oncerepeat = ob->once_repeat;

  DEBUG(D_transport) debug_printf("taking data from transport\n");
  from = ob->from;
  reply_to = ob->reply_to;
  to = ob->to;
  cc = ob->cc;
  bcc = ob->bcc;
  subject = ob->subject;
  headers = ob->headers;
  text = ob->text;
  file = ob->file;
  logfile = ob->logfile;
  oncelog = ob->oncelog;
  file_expand = ob->file_expand;
  return_message = ob->return_message;

  if ((from  != NULL &&
        (from = checkexpand(from, addr, tblock->name, cke_hdr)) == NULL) ||
      (reply_to    != NULL &&
        (reply_to = checkexpand(reply_to, addr, tblock->name, cke_hdr)) == NULL) ||
      (to    != NULL &&
        (to = checkexpand(to, addr, tblock->name, cke_hdr)) == NULL) ||
      (cc    != NULL &&
        (cc = checkexpand(cc, addr, tblock->name, cke_hdr)) == NULL) ||
      (bcc   != NULL &&
        (bcc = checkexpand(bcc, addr, tblock->name, cke_hdr)) == NULL) ||
      (subject   != NULL &&
        (subject = checkexpand(subject, addr, tblock->name, cke_hdr)) == NULL) ||
      (headers != NULL &&
        (headers = checkexpand(headers, addr, tblock->name, cke_text)) == NULL) ||
      (text  != NULL &&
        (text = checkexpand(text, addr, tblock->name, cke_text)) == NULL) ||
      (file  != NULL &&
        (file = checkexpand(file, addr, tblock->name, cke_file)) == NULL) ||
      (logfile != NULL &&
        (logfile = checkexpand(logfile, addr, tblock->name, cke_file)) == NULL) ||
      (oncelog != NULL &&
        (oncelog = checkexpand(oncelog, addr, tblock->name, cke_file)) == NULL) ||
      (oncerepeat != NULL &&
        (oncerepeat = checkexpand(oncerepeat, addr, tblock->name, cke_file)) == NULL))
    return FALSE;

  if (oncerepeat != NULL)
    {
    once_repeat_sec = readconf_readtime(oncerepeat, 0, FALSE);
    if (once_repeat_sec < 0)
      {
      addr->transport_return = FAIL;
      addr->message = string_sprintf("Invalid time value \"%s\" for "
        "\"once_repeat\" in %s transport", oncerepeat, tblock->name);
      return FALSE;
      }
    }
  }

/* If the never_mail option is set, we have to scan all the recipients and
remove those that match. */

if (ob->never_mail != NULL)
  {
  uschar *never_mail = expand_string(ob->never_mail);

  if (never_mail == NULL)
    {
    addr->transport_return = FAIL;
    addr->message = string_sprintf("Failed to expand \"%s\" for "
      "\"never_mail\" in %s transport", ob->never_mail, tblock->name);
    return FALSE;
    }

  if (to != NULL) check_never_mail(&to, never_mail);
  if (cc != NULL) check_never_mail(&cc, never_mail);
  if (bcc != NULL) check_never_mail(&bcc, never_mail);

  if (to == NULL && cc == NULL && bcc == NULL)
    {
    DEBUG(D_transport)
      debug_printf("*** all recipients removed by never_mail\n");
    return OK;
    }
  }

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

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


/* If the oncelog field is set, we send want to send only one message to the
given recipient(s). This works only on the "To" field. If there is no "To"
field, the message is always sent. If the To: field contains more than one
recipient, the effect might not be quite as envisaged. If once_file_size is
set, instead of a dbm file, we use a regular file containing a circular buffer
recipient cache. */

if (oncelog != NULL && *oncelog != 0 && to != NULL)
  {
  time_t then = 0;

  /* Handle fixed-size cache file. */

  if (ob->once_file_size > 0)
    {
    uschar *p;
    struct stat statbuf;
    cache_fd = Uopen(oncelog, O_CREAT|O_RDWR, ob->mode);

    if (cache_fd < 0 || fstat(cache_fd, &statbuf) != 0)
      {
      addr->transport_return = DEFER;
      addr->message = string_sprintf("Failed to %s \"once\" file %s when "
        "sending message from %s transport: %s",
        (cache_fd < 0)? "open" : "stat", oncelog, tblock->name,
          strerror(errno));
      goto END_OFF;
      }

    /* Get store in the temporary pool and read the entire file into it. We get
    an amount of store that is big enough to add the new entry on the end if we
    need to do that. */

    cache_size = statbuf.st_size;
    add_size = sizeof(time_t) + Ustrlen(to) + 1;
    cache_buff = store_get(cache_size + add_size);

    if (read(cache_fd, cache_buff, cache_size) != cache_size)
      {
      addr->transport_return = DEFER;
      addr->basic_errno = errno;
      addr->message = US"error while reading \"once\" file";
      goto END_OFF;
      }

    DEBUG(D_transport) debug_printf("%d bytes read from %s\n", cache_size, oncelog);

    /* Scan the data for this recipient. Each entry in the file starts with
    a time_t sized time value, followed by the address, followed by a binary
    zero. If we find a match, put the time into "then", and the place where it
    was found into "cache_time". Otherwise, "then" is left at zero. */

    p = cache_buff;
    while (p < cache_buff + cache_size)
      {
      uschar *s = p + sizeof(time_t);
      uschar *nextp = s + Ustrlen(s) + 1;
      if (Ustrcmp(to, s) == 0)
        {
        memcpy(&then, p, sizeof(time_t));
        cache_time = p;
        break;
        }
      p = nextp;
      }
    }

  /* Use a DBM file for the list of previous recipients. */

  else
    {
    EXIM_DATUM key_datum, result_datum;
    EXIM_DBOPEN(oncelog, O_RDWR|O_CREAT, ob->mode, &dbm_file);
    if (dbm_file == NULL)
      {
      addr->transport_return = DEFER;
      addr->message = string_sprintf("Failed to open %s file %s when sending "
        "message from %s transport: %s", EXIM_DBTYPE, oncelog, tblock->name,
        strerror(errno));
      goto END_OFF;
      }

    EXIM_DATUM_INIT(key_datum);        /* Some DBM libraries need datums */
    EXIM_DATUM_INIT(result_datum);     /* to be cleared */
    EXIM_DATUM_DATA(key_datum) = CS to;
    EXIM_DATUM_SIZE(key_datum) = Ustrlen(to) + 1;

    if (EXIM_DBGET(dbm_file, key_datum, result_datum))
      {
      /* If the datum size is that of a binary time, we are in the new world
      where messages are sent periodically. Otherwise the file is an old one,
      where the datum was filled with a tod_log time, which is assumed to be
      different in size. For that, only one message is ever sent. This change
      introduced at Exim 3.00. In a couple of years' time the test on the size
      can be abolished. */

      if (EXIM_DATUM_SIZE(result_datum) == sizeof(time_t))
        {
        memcpy(&then, EXIM_DATUM_DATA(result_datum), sizeof(time_t));
        }
      else then = now;
      }
    }

  /* Either "then" is set zero, if no message has yet been sent, or it
  is set to the time of the last sending. */

  if (then != 0 && (once_repeat_sec <= 0 || now - then < once_repeat_sec))
    {
    DEBUG(D_transport) debug_printf("message previously sent to %s%s\n", to,
      (once_repeat_sec > 0)? " and repeat time not reached" : "");
    log_fd = Uopen(logfile, O_WRONLY|O_APPEND|O_CREAT, ob->mode);
    if (log_fd >= 0)
      {
      uschar *ptr = log_buffer;
      sprintf(CS ptr, "%s\n  previously sent to %.200s\n", tod_stamp(tod_log), to);
      while(*ptr) ptr++;
      if(write(log_fd, log_buffer, ptr - log_buffer) != ptr-log_buffer
        || close(log_fd))
        DEBUG(D_transport) debug_printf("Problem writing log file %s for %s "
          "transport\n", logfile, tblock->name);
      }
    goto END_OFF;
    }

  DEBUG(D_transport) debug_printf("%s %s\n", (then <= 0)?
    "no previous message sent to" : "repeat time reached for", to);
  }

/* We are going to send a message. Ensure any requested file is available. */

if (file != NULL)
  {
  ff = Ufopen(file, "rb");
  if (ff == NULL && !ob->file_optional)
    {
    addr->transport_return = DEFER;
    addr->message = string_sprintf("Failed to open file %s when sending "
      "message from %s transport: %s", file, tblock->name, strerror(errno));
    return FALSE;
    }
  }

/* Make a subprocess to send the message */

pid = child_open_exim(&fd);

/* Creation of child failed; defer this delivery. */

if (pid < 0)
  {
  addr->transport_return = DEFER;
  addr->message = string_sprintf("Failed to create child process to send "
    "message from %s transport: %s", tblock->name, strerror(errno));
  DEBUG(D_transport) debug_printf("%s\n", addr->message);
  return FALSE;
  }

/* Create the message to be sent - recipients are taken from the headers,
as the -t option is used. The "headers" stuff *must* be last in case there
are newlines in it which might, if placed earlier, screw up other headers. */

f = fdopen(fd, "wb");

if (from != NULL) fprintf(f, "From: %s\n", from);
if (reply_to != NULL) fprintf(f, "Reply-To: %s\n", reply_to);
if (to != NULL) fprintf(f, "To: %s\n", to);
if (cc != NULL) fprintf(f, "Cc: %s\n", cc);
if (bcc != NULL) fprintf(f, "Bcc: %s\n", bcc);
if (subject != NULL) fprintf(f, "Subject: %s\n", subject);

/* Generate In-Reply-To from the message_id header; there should
always be one, but code defensively. */

for (h = header_list; h != NULL; h = h->next)
  if (h->type == htype_id) break;

if (h != NULL)
  {
  message_id = Ustrchr(h->text, ':') + 1;
  while (isspace(*message_id)) message_id++;
  fprintf(f, "In-Reply-To: %s", message_id);
  }

/* Generate a References header if there is at least one of Message-ID:,
References:, or In-Reply-To: (see RFC 2822). */

for (h = header_list; h != NULL; h = h->next)
  if (h->type != htype_old && strncmpic(US"References:", h->text, 11) == 0)
    break;

if (h == NULL)
  for (h = header_list; h != NULL; h = h->next)
    if (h->type != htype_old && strncmpic(US"In-Reply-To:", h->text, 12) == 0)
      break;

/* We limit the total length of references.  Although there is no fixed
limit, some systems do not like headers growing beyond recognition.
Keep the first message ID for the thread root and the last few for
the position inside the thread, up to a maximum of 12 altogether. */

if (h != NULL || message_id != NULL)
  {
  fprintf(f, "References:");
  if (h != NULL)
    {
    uschar *s, *id, *error;
    uschar *referenced_ids[12];
    int reference_count = 0;
    int i;

    s = Ustrchr(h->text, ':') + 1;
    parse_allow_group = FALSE;
    while (*s != 0 && (s = parse_message_id(s, &id, &error)) != NULL)
      {
      if (reference_count == sizeof(referenced_ids)/sizeof(uschar *))
        {
        memmove(referenced_ids + 1, referenced_ids + 2,
           sizeof(referenced_ids) - 2*sizeof(uschar *));
        referenced_ids[reference_count - 1] = id;
        }
      else referenced_ids[reference_count++] = id;
      }
    for (i = 0; i < reference_count; ++i) fprintf(f, " %s", referenced_ids[i]);
    }

  /* The message id will have a newline on the end of it. */

  if (message_id != NULL) fprintf(f, " %s", message_id);
    else fprintf(f, "\n");
  }

/* Add an Auto-Submitted: header */

fprintf(f, "Auto-Submitted: auto-replied\n");

/* Add any specially requested headers */

if (headers != NULL) fprintf(f, "%s\n", headers);
fprintf(f, "\n");

if (text != NULL)
  {
  fprintf(f, "%s", CS text);
  if (text[Ustrlen(text)-1] != '\n') fprintf(f, "\n");
  }

if (ff != NULL)
  {
  while (Ufgets(big_buffer, big_buffer_size, ff) != NULL)
    {
    if (file_expand)
      {
      uschar *s = expand_string(big_buffer);
      DEBUG(D_transport)
        {
        if (s == NULL)
          debug_printf("error while expanding line from file:\n  %s\n  %s\n",
            big_buffer, expand_string_message);
        }
      fprintf(f, "%s", (s == NULL)? CS big_buffer : CS s);
      }
    else fprintf(f, "%s", CS big_buffer);
    }
  }
Example #3
0
open_db *
dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof)
{
int rc, save_errno;
BOOL read_only = flags == O_RDONLY;
BOOL created = FALSE;
flock_t lock_data;
uschar buffer[256];

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

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

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

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

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

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

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

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

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

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

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

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

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

save_errno = errno;

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

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

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

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

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

  closedir(dd);
  }

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

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

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

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

return dbblock;
}
Example #4
0
static open_db *
dbfn_open(uschar *spool, uschar *name, int flags, open_db *dbblock)
{
int rc;
struct flock lock_data;
BOOL read_only = flags == O_RDONLY;
uschar buffer[256];

/* The first thing to do is to open a separate file on which to lock. This
ensures that Exim has exclusive use of the database before it even tries to
open it. If there is a database, there should be a lock file in existence. */

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

dbblock->lockfd = Uopen(buffer, flags, 0);
if (dbblock->lockfd < 0)
  {
  printf("** Failed to open database lock file %s: %s\n", buffer,
    strerror(errno));
  return NULL;
  }

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

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

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

if (sigalrm_seen) errno = ETIMEDOUT;
if (rc < 0)
  {
  printf("** Failed to get %s lock for %s: %s",
    ((flags & O_RDONLY) != 0)? "read" : "write", buffer,
    (errno == ETIMEDOUT)? "timed out" : strerror(errno));
  (void)close(dbblock->lockfd);
  return NULL;
  }

/* At this point we have an opened and locked separate lock file, that is,
exclusive access to the database, so we can go ahead and open it. */

sprintf(CS buffer, "%s/db/%s", spool, name);
EXIM_DBOPEN(buffer, flags, 0, &(dbblock->dbptr));

if (dbblock->dbptr == NULL)
  {
  printf("** Failed to open DBM file %s for %s:\n   %s%s\n", buffer,
    read_only? "reading" : "writing", strerror(errno),
    #ifdef USE_DB
    " (or Berkeley DB error while opening)"
    #else
    ""
    #endif
    );
  (void)close(dbblock->lockfd);
  return NULL;
  }

return dbblock;
}