BOOL
spool_move_message(uschar *id, uschar *subdir, uschar *from, uschar *to)
{
/* Create any output directories that do not exist. */

sprintf(CS big_buffer, "%sinput/%s", to, subdir);
(void)directory_make(spool_directory, big_buffer, INPUT_DIRECTORY_MODE, TRUE);
sprintf(CS big_buffer, "%smsglog/%s", to, subdir);
(void)directory_make(spool_directory, big_buffer, INPUT_DIRECTORY_MODE, TRUE);

/* Move the message by first creating new hard links for all the files, and
then removing the old links. When moving messages onto the main spool, the -H
file should be set up last, because that's the one that tells Exim there is a
message to be delivered, so we create its new link last and remove its old link
first. Programs that look at the alternate directories should follow the same
rule of waiting for a -H file before doing anything. When moving messages off
the mail spool, the -D file should be open and locked at the time, thus keeping
Exim's hands off. */

if (!make_link(US"msglog", subdir, id, US"", from, to, TRUE) ||
    !make_link(US"input",  subdir, id, US"-D", from, to, FALSE) ||
    !make_link(US"input",  subdir, id, US"-H", from, to, FALSE))
  return FALSE;

if (!break_link(US"input",  subdir, id, US"-H", from, FALSE) ||
    !break_link(US"input",  subdir, id, US"-D", from, FALSE) ||
    !break_link(US"msglog", subdir, id, US"", from, TRUE))
  return FALSE;

log_write(0, LOG_MAIN, "moved from %sinput, %smsglog to %sinput, %smsglog",
   from, from, to, to);

return TRUE;
}
Esempio n. 2
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;
}