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; }
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; }