mbox-lock.c revision 4780c036a0f52079068bd23e8a77b67ff457dba6
2454dfa32c93c20a8522c6ed42fe057baaac9f9aStephan Bosch/* Copyright (c) 2002-2013 Dovecot authors, see the included COPYING file */
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen#include "lib.h"
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen#include "eacces-error.h"
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen#include "restrict-access.h"
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen#include "nfs-workarounds.h"
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen#include "ipwd.h"
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen#include "mail-index-private.h"
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen#include "mbox-storage.h"
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen#include "istream-raw-mbox.h"
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen#include "mbox-file.h"
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen#include "mbox-lock.h"
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen
8a8a3b43987b5ade914f22765e51c9e3de8179d3Timo Sirainen#include <time.h>
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen#include <stdlib.h>
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen#include <unistd.h>
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen#include <fcntl.h>
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen#include <sys/stat.h>
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen#ifdef HAVE_FLOCK
8a8a3b43987b5ade914f22765e51c9e3de8179d3Timo Sirainen# include <sys/file.h>
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen#endif
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen/* 0.1 .. 0.2msec */
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen#define LOCK_RANDOM_USLEEP_TIME (100000 + (unsigned int)rand() % 100000)
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainenenum mbox_lock_type {
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen MBOX_LOCK_DOTLOCK,
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen MBOX_LOCK_DOTLOCK_TRY,
d5e839aea288aceaddae28a1578cebda3c9e3b58Timo Sirainen MBOX_LOCK_FCNTL,
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen MBOX_LOCK_FLOCK,
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen MBOX_LOCK_LOCKF,
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen MBOX_LOCK_COUNT
25ec868bd8b5375e1c1c4c3331d761667ddfe26cTimo Sirainen};
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainenenum mbox_dotlock_op {
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen MBOX_DOTLOCK_OP_LOCK,
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen MBOX_DOTLOCK_OP_UNLOCK,
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen MBOX_DOTLOCK_OP_TOUCH
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen};
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainenstruct mbox_lock_context {
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen struct mbox_mailbox *mbox;
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen int lock_status[MBOX_LOCK_COUNT];
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen bool checked_file;
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen int lock_type;
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen bool dotlock_last_stale;
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen bool fcntl_locked;
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen bool using_privileges;
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen};
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainenstruct mbox_lock_data {
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen enum mbox_lock_type type;
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen const char *name;
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen int (*func)(struct mbox_lock_context *ctx, int lock_type,
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen time_t max_wait_time);
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen};
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainenstatic int mbox_lock_dotlock(struct mbox_lock_context *ctx, int lock_type,
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen time_t max_wait_time);
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainenstatic int mbox_lock_dotlock_try(struct mbox_lock_context *ctx, int lock_type,
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen time_t max_wait_time);
46b823ac3bce2c0f9f0fc73911e48d3a77b04fbeTimo Sirainenstatic int mbox_lock_fcntl(struct mbox_lock_context *ctx, int lock_type,
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen time_t max_wait_time);
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen#ifdef HAVE_FLOCK
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainenstatic int mbox_lock_flock(struct mbox_lock_context *ctx, int lock_type,
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen time_t max_wait_time);
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen#else
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen# define mbox_lock_flock NULL
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen#endif
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen#ifdef HAVE_LOCKF
e245fb1302121d2bc2580f61e040c2c8a558ee9eTimo Sirainenstatic int mbox_lock_lockf(struct mbox_lock_context *ctx, int lock_type,
e245fb1302121d2bc2580f61e040c2c8a558ee9eTimo Sirainen time_t max_wait_time);
e245fb1302121d2bc2580f61e040c2c8a558ee9eTimo Sirainen#else
d5e839aea288aceaddae28a1578cebda3c9e3b58Timo Sirainen# define mbox_lock_lockf NULL
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen#endif
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainenstatic struct mbox_lock_data lock_data[] = {
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen { MBOX_LOCK_DOTLOCK, "dotlock", mbox_lock_dotlock },
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen { MBOX_LOCK_DOTLOCK_TRY, "dotlock_try", mbox_lock_dotlock_try },
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen { MBOX_LOCK_FCNTL, "fcntl", mbox_lock_fcntl },
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen { MBOX_LOCK_FLOCK, "flock", mbox_lock_flock },
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen { MBOX_LOCK_LOCKF, "lockf", mbox_lock_lockf },
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen { 0, NULL, NULL }
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen};
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen
d97f081a3de44852197ced772e21560c108895a6Timo Sirainenstatic int ATTR_NOWARN_UNUSED_RESULT
d97f081a3de44852197ced772e21560c108895a6Timo Sirainenmbox_lock_list(struct mbox_lock_context *ctx, int lock_type,
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen time_t max_wait_time, int idx);
d97f081a3de44852197ced772e21560c108895a6Timo Sirainenstatic int ATTR_NOWARN_UNUSED_RESULT
d97f081a3de44852197ced772e21560c108895a6Timo Sirainenmbox_unlock_files(struct mbox_lock_context *ctx);
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen
d97f081a3de44852197ced772e21560c108895a6Timo Sirainenstatic void mbox_read_lock_methods(const char *str, const char *env,
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen enum mbox_lock_type *locks)
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen{
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen enum mbox_lock_type type;
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen const char *const *lock;
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen int i, dest;
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen for (lock = t_strsplit(str, " "), dest = 0; *lock != NULL; lock++) {
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen for (type = 0; lock_data[type].name != NULL; type++) {
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen if (strcasecmp(*lock, lock_data[type].name) == 0) {
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen type = lock_data[type].type;
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen break;
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen }
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen }
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen if (lock_data[type].name == NULL)
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen i_fatal("%s: Invalid value %s", env, *lock);
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen if (lock_data[type].func == NULL) {
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen i_fatal("%s: Support for lock type %s "
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen "not compiled into binary", env, *lock);
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen }
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen for (i = 0; i < dest; i++) {
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen if (locks[i] == type)
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen i_fatal("%s: Duplicated value %s", env, *lock);
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen }
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen /* @UNSAFE */
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen locks[dest++] = type;
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen }
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen locks[dest] = (enum mbox_lock_type)-1;
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen}
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen
d97f081a3de44852197ced772e21560c108895a6Timo Sirainenstatic void mbox_init_lock_settings(struct mbox_storage *storage)
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen{
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen enum mbox_lock_type read_locks[MBOX_LOCK_COUNT+1];
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen enum mbox_lock_type write_locks[MBOX_LOCK_COUNT+1];
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen int r, w;
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen mbox_read_lock_methods(storage->set->mbox_read_locks,
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen "mbox_read_locks", read_locks);
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen mbox_read_lock_methods(storage->set->mbox_write_locks,
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen "mbox_write_locks", write_locks);
d5e839aea288aceaddae28a1578cebda3c9e3b58Timo Sirainen
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen /* check that read/write list orders match. write_locks must contain
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen at least read_locks and possibly more. */
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen for (r = w = 0; write_locks[w] != (enum mbox_lock_type)-1; w++) {
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen if (read_locks[r] == (enum mbox_lock_type)-1)
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen break;
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen if (read_locks[r] == write_locks[w])
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen r++;
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen }
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen if (read_locks[r] != (enum mbox_lock_type)-1) {
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen i_fatal("mbox read/write lock list settings are invalid. "
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen "Lock ordering must be the same with both, "
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen "and write locks must contain all read locks "
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen "(and possibly more)");
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen }
25ec868bd8b5375e1c1c4c3331d761667ddfe26cTimo Sirainen
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen storage->read_locks = p_new(storage->storage.pool,
25ec868bd8b5375e1c1c4c3331d761667ddfe26cTimo Sirainen enum mbox_lock_type, MBOX_LOCK_COUNT+1);
25ec868bd8b5375e1c1c4c3331d761667ddfe26cTimo Sirainen memcpy(storage->read_locks, read_locks,
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen sizeof(*storage->read_locks) * (MBOX_LOCK_COUNT+1));
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen storage->write_locks = p_new(storage->storage.pool,
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen enum mbox_lock_type, MBOX_LOCK_COUNT+1);
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen memcpy(storage->write_locks, write_locks,
8a8a3b43987b5ade914f22765e51c9e3de8179d3Timo Sirainen sizeof(*storage->write_locks) * (MBOX_LOCK_COUNT+1));
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen
adea69875046ece77dc36abd3f88a241a3f17ad9Timo Sirainen storage->lock_settings_initialized = TRUE;
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen}
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainenstatic int mbox_file_open_latest(struct mbox_lock_context *ctx, int lock_type)
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen{
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen struct mbox_mailbox *mbox = ctx->mbox;
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen struct stat st;
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen if (ctx->checked_file || lock_type == F_UNLCK)
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen return 0;
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen if (mbox->mbox_fd != -1) {
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen /* we could flush NFS file handle cache here if we wanted to
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen be sure that the file is latest, but mbox files get rarely
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen deleted and the flushing might cause errors (e.g. EBUSY for
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen trying to flush a /var/mail mountpoint) */
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen if (nfs_safe_stat(mailbox_get_path(&mbox->box), &st) < 0) {
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen if (errno == ENOENT)
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen mailbox_set_deleted(&mbox->box);
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen else
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen mbox_set_syscall_error(mbox, "stat()");
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen return -1;
c076ad69e28e7d41af83ada84e12019793ffcfa2Timo Sirainen }
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen if (st.st_ino != mbox->mbox_ino ||
c076ad69e28e7d41af83ada84e12019793ffcfa2Timo Sirainen !CMP_DEV_T(st.st_dev, mbox->mbox_dev))
c076ad69e28e7d41af83ada84e12019793ffcfa2Timo Sirainen mbox_file_close(mbox);
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen }
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen if (mbox->mbox_fd == -1) {
6adf683655750bcb809275cd65dc75fd12214198Timo Sirainen if (mbox_file_open(mbox) < 0)
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen return -1;
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen }
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen ctx->checked_file = TRUE;
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen return 0;
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen}
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainenstatic bool dotlock_callback(unsigned int secs_left, bool stale, void *context)
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen{
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen struct mbox_lock_context *ctx = context;
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen enum mbox_lock_type *lock_types;
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen int i;
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen if (ctx->using_privileges)
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen restrict_access_drop_priv_gid();
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen
6adf683655750bcb809275cd65dc75fd12214198Timo Sirainen if (stale && !ctx->dotlock_last_stale) {
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen /* get next index we wish to try locking. it's the one after
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen dotlocking. */
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen lock_types = ctx->lock_type == F_WRLCK ||
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen (ctx->lock_type == F_UNLCK &&
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen ctx->mbox->mbox_lock_type == F_WRLCK) ?
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen ctx->mbox->storage->write_locks :
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen ctx->mbox->storage->read_locks;
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen for (i = 0; lock_types[i] != (enum mbox_lock_type)-1; i++) {
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen if (lock_types[i] == MBOX_LOCK_DOTLOCK)
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen break;
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen }
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen if (lock_types[i] != (enum mbox_lock_type)-1 &&
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen lock_types[i+1] != (enum mbox_lock_type)-1) {
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen i++;
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen if (mbox_lock_list(ctx, ctx->lock_type, 0, i) <= 0) {
61d57efe9dee0dd38b0f726ef85e3c710cb655fcTimo Sirainen /* we couldn't get fd lock -
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen it's really locked */
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen ctx->dotlock_last_stale = TRUE;
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen return FALSE;
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen }
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen mbox_lock_list(ctx, F_UNLCK, 0, i);
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen }
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen }
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen ctx->dotlock_last_stale = stale;
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen index_storage_lock_notify(&ctx->mbox->box, stale ?
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen MAILBOX_LOCK_NOTIFY_MAILBOX_OVERRIDE :
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen MAILBOX_LOCK_NOTIFY_MAILBOX_ABORT,
c076ad69e28e7d41af83ada84e12019793ffcfa2Timo Sirainen secs_left);
c076ad69e28e7d41af83ada84e12019793ffcfa2Timo Sirainen if (ctx->using_privileges) {
c076ad69e28e7d41af83ada84e12019793ffcfa2Timo Sirainen if (restrict_access_use_priv_gid() < 0) {
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen /* shouldn't get here */
c076ad69e28e7d41af83ada84e12019793ffcfa2Timo Sirainen return FALSE;
c076ad69e28e7d41af83ada84e12019793ffcfa2Timo Sirainen }
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen }
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen return TRUE;
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen}
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainenstatic int ATTR_NULL(2) ATTR_NOWARN_UNUSED_RESULT
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainenmbox_dotlock_privileged_op(struct mbox_mailbox *mbox,
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen struct dotlock_settings *set,
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen enum mbox_dotlock_op op)
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen{
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen const char *box_path, *dir, *fname;
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen int ret = -1, orig_dir_fd, orig_errno;
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen
e20f5920759215f328ce12dcca071b4e7dda3d48Timo Sirainen orig_dir_fd = open(".", O_RDONLY);
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen if (orig_dir_fd == -1) {
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen mail_storage_set_critical(&mbox->storage->storage,
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen "open(.) failed: %m");
46631c1d903c409444b1b1c4a1d41a033c09ee37Timo Sirainen return -1;
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen }
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen
fb176cdc122707cda985ab3c09c02ccf3cec0af1Timo Sirainen /* allow dotlocks to be created only for files we can read while we're
fb176cdc122707cda985ab3c09c02ccf3cec0af1Timo Sirainen unprivileged. to make sure there are no race conditions we first
fb176cdc122707cda985ab3c09c02ccf3cec0af1Timo Sirainen have to chdir to the mbox file's directory and then use relative
fb176cdc122707cda985ab3c09c02ccf3cec0af1Timo Sirainen paths. unless this is done, users could:
fb176cdc122707cda985ab3c09c02ccf3cec0af1Timo Sirainen - create *.lock files to any directory writable by the
fb176cdc122707cda985ab3c09c02ccf3cec0af1Timo Sirainen privileged group
fb176cdc122707cda985ab3c09c02ccf3cec0af1Timo Sirainen - DoS other users by dotlocking their mailboxes infinitely
fb176cdc122707cda985ab3c09c02ccf3cec0af1Timo Sirainen */
fb176cdc122707cda985ab3c09c02ccf3cec0af1Timo Sirainen box_path = mailbox_get_path(&mbox->box);
fb176cdc122707cda985ab3c09c02ccf3cec0af1Timo Sirainen fname = strrchr(box_path, '/');
d5e839aea288aceaddae28a1578cebda3c9e3b58Timo Sirainen if (fname == NULL) {
d5e839aea288aceaddae28a1578cebda3c9e3b58Timo Sirainen /* already relative */
d5e839aea288aceaddae28a1578cebda3c9e3b58Timo Sirainen fname = box_path;
d5e839aea288aceaddae28a1578cebda3c9e3b58Timo Sirainen } else {
d5e839aea288aceaddae28a1578cebda3c9e3b58Timo Sirainen dir = t_strdup_until(box_path, fname);
d5e839aea288aceaddae28a1578cebda3c9e3b58Timo Sirainen if (chdir(dir) < 0) {
d5e839aea288aceaddae28a1578cebda3c9e3b58Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
d5e839aea288aceaddae28a1578cebda3c9e3b58Timo Sirainen "chdir(%s) failed: %m", dir);
fb176cdc122707cda985ab3c09c02ccf3cec0af1Timo Sirainen i_close_fd(&orig_dir_fd);
fb176cdc122707cda985ab3c09c02ccf3cec0af1Timo Sirainen return -1;
fb176cdc122707cda985ab3c09c02ccf3cec0af1Timo Sirainen }
fb176cdc122707cda985ab3c09c02ccf3cec0af1Timo Sirainen fname++;
153ed0fbca1f5f944b70937dfd71911db172ca97Timo Sirainen }
153ed0fbca1f5f944b70937dfd71911db172ca97Timo Sirainen if (op == MBOX_DOTLOCK_OP_LOCK) {
153ed0fbca1f5f944b70937dfd71911db172ca97Timo Sirainen if (access(fname, R_OK) < 0) {
153ed0fbca1f5f944b70937dfd71911db172ca97Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
153ed0fbca1f5f944b70937dfd71911db172ca97Timo Sirainen "access(%s) failed: %m", box_path);
153ed0fbca1f5f944b70937dfd71911db172ca97Timo Sirainen return -1;
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen }
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen }
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen if (restrict_access_use_priv_gid() < 0) {
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen i_close_fd(&orig_dir_fd);
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen return -1;
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen }
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen switch (op) {
8a8a3b43987b5ade914f22765e51c9e3de8179d3Timo Sirainen case MBOX_DOTLOCK_OP_LOCK:
8a8a3b43987b5ade914f22765e51c9e3de8179d3Timo Sirainen /* we're now privileged - avoid doing as much as possible */
8a8a3b43987b5ade914f22765e51c9e3de8179d3Timo Sirainen ret = file_dotlock_create(set, fname, 0, &mbox->mbox_dotlock);
8a8a3b43987b5ade914f22765e51c9e3de8179d3Timo Sirainen if (ret > 0)
8a8a3b43987b5ade914f22765e51c9e3de8179d3Timo Sirainen mbox->mbox_used_privileges = TRUE;
8a8a3b43987b5ade914f22765e51c9e3de8179d3Timo Sirainen else if (ret < 0 && errno == EACCES) {
8a8a3b43987b5ade914f22765e51c9e3de8179d3Timo Sirainen const char *errmsg =
8a8a3b43987b5ade914f22765e51c9e3de8179d3Timo Sirainen eacces_error_get_creating("file_dotlock_create",
8a8a3b43987b5ade914f22765e51c9e3de8179d3Timo Sirainen fname);
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen "%s", errmsg);
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen } else {
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen mbox_set_syscall_error(mbox, "file_dotlock_create()");
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen }
da574ef2db96f258d24bc4c89a77833036d13a95Timo Sirainen break;
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen case MBOX_DOTLOCK_OP_UNLOCK:
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen /* we're now privileged - avoid doing as much as possible */
fb176cdc122707cda985ab3c09c02ccf3cec0af1Timo Sirainen ret = file_dotlock_delete(&mbox->mbox_dotlock);
153ed0fbca1f5f944b70937dfd71911db172ca97Timo Sirainen if (ret < 0)
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen mbox_set_syscall_error(mbox, "file_dotlock_delete()");
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen mbox->mbox_used_privileges = FALSE;
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen break;
8a8a3b43987b5ade914f22765e51c9e3de8179d3Timo Sirainen case MBOX_DOTLOCK_OP_TOUCH:
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen ret = file_dotlock_touch(mbox->mbox_dotlock);
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen if (ret < 0)
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen mbox_set_syscall_error(mbox, "file_dotlock_touch()");
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen break;
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen }
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen orig_errno = errno;
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen restrict_access_drop_priv_gid();
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen if (fchdir(orig_dir_fd) < 0) {
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen "fchdir() failed: %m");
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen }
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen i_close_fd(&orig_dir_fd);
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen errno = orig_errno;
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen return ret;
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen}
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainenstatic void
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainenmbox_dotlock_log_eacces_error(struct mbox_mailbox *mbox, const char *path)
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen{
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen const char *dir, *errmsg, *name;
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen struct stat st;
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen struct group group;
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen int orig_errno = errno;
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen errmsg = eacces_error_get_creating("file_dotlock_create", path);
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen dir = strrchr(path, '/');
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen dir = dir == NULL ? "." : t_strdup_until(path, dir);
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen /* allow privileged locking for
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen a) user's own INBOX,
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen b) another user's shared INBOX, and
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen c) anything called INBOX (in inbox=no namespace) */
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen if (!mbox->box.inbox_any && strcmp(mbox->box.name, "INBOX") != 0) {
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
e44028b5df7045dd9e7f324175e73e3ff490cb5dTimo Sirainen "%s (not INBOX -> no privileged locking)", errmsg);
d28179fd78550a58be44dcb1e3e830ab7d33172dTimo Sirainen } else if (!mbox->mbox_privileged_locking) {
d28179fd78550a58be44dcb1e3e830ab7d33172dTimo Sirainen dir = mailbox_list_get_root_forced(mbox->box.list,
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen MAILBOX_LIST_PATH_TYPE_DIR);
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen mail_storage_set_critical(&mbox->storage->storage,
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen "%s (under root dir %s -> no privileged locking)",
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen errmsg, dir);
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen } else if (stat(dir, &st) == 0 &&
b3f4c31f1533e25380f49f77d5bb1251bf43db2aTimo Sirainen (st.st_mode & 02) == 0 && /* not world-writable */
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen (st.st_mode & 020) != 0) { /* group-writable */
bace943c67e6cd14ce6c994f533d82a3caad5bf1Timo Sirainen if (i_getgrgid(st.st_gid, &group) <= 0)
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen name = dec2str(st.st_gid);
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen else
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen name = group.gr_name;
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen mail_storage_set_critical(&mbox->storage->storage,
c096257fbdaf4b9fcf8eb97aae94afdbb4e71ed4Timo Sirainen "%s (set mail_privileged_group=%s)", errmsg, name);
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen } else {
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen "%s (nonstandard permissions in %s)", errmsg, dir);
e44028b5df7045dd9e7f324175e73e3ff490cb5dTimo Sirainen }
e44028b5df7045dd9e7f324175e73e3ff490cb5dTimo Sirainen errno = orig_errno;
e44028b5df7045dd9e7f324175e73e3ff490cb5dTimo Sirainen}
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen
bace943c67e6cd14ce6c994f533d82a3caad5bf1Timo Sirainenstatic int
bace943c67e6cd14ce6c994f533d82a3caad5bf1Timo Sirainenmbox_lock_dotlock_int(struct mbox_lock_context *ctx, int lock_type, bool try)
c096257fbdaf4b9fcf8eb97aae94afdbb4e71ed4Timo Sirainen{
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen struct mbox_mailbox *mbox = ctx->mbox;
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen struct dotlock_settings set;
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen int ret;
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen if (lock_type == F_UNLCK) {
c096257fbdaf4b9fcf8eb97aae94afdbb4e71ed4Timo Sirainen if (!mbox->mbox_dotlocked)
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen return 1;
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen if (!mbox->mbox_used_privileges) {
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen if (file_dotlock_delete(&mbox->mbox_dotlock) <= 0) {
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen mbox_set_syscall_error(mbox,
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen "file_dotlock_delete()");
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen }
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen } else {
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen ctx->using_privileges = TRUE;
mbox_dotlock_privileged_op(mbox, NULL,
MBOX_DOTLOCK_OP_UNLOCK);
ctx->using_privileges = FALSE;
}
mbox->mbox_dotlocked = FALSE;
return 1;
}
if (mbox->mbox_dotlocked)
return 1;
ctx->dotlock_last_stale = -1;
memset(&set, 0, sizeof(set));
set.use_excl_lock = mbox->storage->storage.set->dotlock_use_excl;
set.nfs_flush = mbox->storage->storage.set->mail_nfs_storage;
set.timeout = mail_storage_get_lock_timeout(&mbox->storage->storage,
mbox->storage->set->mbox_lock_timeout);
set.stale_timeout = mbox->storage->set->mbox_dotlock_change_timeout;
set.callback = dotlock_callback;
set.context = ctx;
ret = file_dotlock_create(&set, mailbox_get_path(&mbox->box), 0,
&mbox->mbox_dotlock);
if (ret >= 0) {
/* success / timeout */
} else if (errno == EACCES && restrict_access_have_priv_gid() &&
mbox->mbox_privileged_locking) {
/* try again, this time with extra privileges */
ret = mbox_dotlock_privileged_op(mbox, &set,
MBOX_DOTLOCK_OP_LOCK);
} else if (errno == EACCES)
mbox_dotlock_log_eacces_error(mbox, mailbox_get_path(&mbox->box));
else
mbox_set_syscall_error(mbox, "file_dotlock_create()");
if (ret < 0) {
if ((ENOSPACE(errno) || errno == EACCES) && try)
return 1;
return -1;
}
if (ret == 0) {
mail_storage_set_error(&mbox->storage->storage,
MAIL_ERROR_TEMP, MAIL_ERRSTR_LOCK_TIMEOUT);
return 0;
}
mbox->mbox_dotlocked = TRUE;
if (mbox_file_open_latest(ctx, lock_type) < 0)
return -1;
return 1;
}
static int mbox_lock_dotlock(struct mbox_lock_context *ctx, int lock_type,
time_t max_wait_time ATTR_UNUSED)
{
return mbox_lock_dotlock_int(ctx, lock_type, FALSE);
}
static int mbox_lock_dotlock_try(struct mbox_lock_context *ctx, int lock_type,
time_t max_wait_time ATTR_UNUSED)
{
return mbox_lock_dotlock_int(ctx, lock_type, TRUE);
}
#ifdef HAVE_FLOCK
static int mbox_lock_flock(struct mbox_lock_context *ctx, int lock_type,
time_t max_wait_time)
{
time_t now;
unsigned int next_alarm;
if (mbox_file_open_latest(ctx, lock_type) < 0)
return -1;
if (lock_type == F_UNLCK && ctx->mbox->mbox_fd == -1)
return 1;
if (lock_type == F_WRLCK)
lock_type = LOCK_EX;
else if (lock_type == F_RDLCK)
lock_type = LOCK_SH;
else
lock_type = LOCK_UN;
if (max_wait_time == 0) {
/* usually we're waiting here, but if we came from
mbox_lock_dotlock(), we just want to try locking */
lock_type |= LOCK_NB;
} else {
now = time(NULL);
if (now >= max_wait_time)
alarm(1);
else
alarm(I_MIN(max_wait_time - now, 5));
}
while (flock(ctx->mbox->mbox_fd, lock_type) < 0) {
if (errno != EINTR) {
if (errno == EWOULDBLOCK && max_wait_time == 0) {
/* non-blocking lock trying failed */
return 0;
}
alarm(0);
mbox_set_syscall_error(ctx->mbox, "flock()");
return -1;
}
now = time(NULL);
if (now >= max_wait_time) {
alarm(0);
return 0;
}
/* notify locks once every 5 seconds.
try to use rounded values. */
next_alarm = (max_wait_time - now) % 5;
if (next_alarm == 0)
next_alarm = 5;
alarm(next_alarm);
index_storage_lock_notify(&ctx->mbox->box,
MAILBOX_LOCK_NOTIFY_MAILBOX_ABORT,
max_wait_time - now);
}
alarm(0);
return 1;
}
#endif
#ifdef HAVE_LOCKF
static int mbox_lock_lockf(struct mbox_lock_context *ctx, int lock_type,
time_t max_wait_time)
{
time_t now;
unsigned int next_alarm;
if (mbox_file_open_latest(ctx, lock_type) < 0)
return -1;
if (lock_type == F_UNLCK && ctx->mbox->mbox_fd == -1)
return 1;
if (lock_type == F_UNLCK)
lock_type = F_ULOCK;
else if (max_wait_time == 0) {
/* usually we're waiting here, but if we came from
mbox_lock_dotlock(), we just want to try locking */
lock_type = F_TLOCK;
} else {
now = time(NULL);
if (now >= max_wait_time)
alarm(1);
else
alarm(I_MIN(max_wait_time - now, 5));
lock_type = F_LOCK;
}
while (lockf(ctx->mbox->mbox_fd, lock_type, 0) < 0) {
if (errno != EINTR) {
if ((errno == EACCES || errno == EAGAIN) &&
max_wait_time == 0) {
/* non-blocking lock trying failed */
return 0;
}
alarm(0);
mbox_set_syscall_error(ctx->mbox, "lockf()");
return -1;
}
now = time(NULL);
if (now >= max_wait_time) {
alarm(0);
return 0;
}
/* notify locks once every 5 seconds.
try to use rounded values. */
next_alarm = (max_wait_time - now) % 5;
if (next_alarm == 0)
next_alarm = 5;
alarm(next_alarm);
index_storage_lock_notify(&ctx->mbox->box,
MAILBOX_LOCK_NOTIFY_MAILBOX_ABORT,
max_wait_time - now);
}
alarm(0);
return 1;
}
#endif
static int mbox_lock_fcntl(struct mbox_lock_context *ctx, int lock_type,
time_t max_wait_time)
{
struct flock fl;
time_t now;
unsigned int next_alarm;
int wait_type;
if (mbox_file_open_latest(ctx, lock_type) < 0)
return -1;
if (lock_type == F_UNLCK && ctx->mbox->mbox_fd == -1)
return 1;
memset(&fl, 0, sizeof(fl));
fl.l_type = lock_type;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
if (max_wait_time == 0) {
/* usually we're waiting here, but if we came from
mbox_lock_dotlock(), we just want to try locking */
wait_type = F_SETLK;
} else {
wait_type = F_SETLKW;
now = time(NULL);
if (now >= max_wait_time)
alarm(1);
else
alarm(I_MIN(max_wait_time - now, 5));
}
while (fcntl(ctx->mbox->mbox_fd, wait_type, &fl) < 0) {
if (errno != EINTR) {
if ((errno == EACCES || errno == EAGAIN) &&
wait_type == F_SETLK) {
/* non-blocking lock trying failed */
return 0;
}
alarm(0);
if (errno != EACCES) {
mbox_set_syscall_error(ctx->mbox, "fcntl()");
return -1;
}
mail_storage_set_critical(&ctx->mbox->storage->storage,
"fcntl() failed with mbox file %s: "
"File is locked by another process (EACCES)",
mailbox_get_path(&ctx->mbox->box));
return -1;
}
now = time(NULL);
if (now >= max_wait_time) {
alarm(0);
return 0;
}
/* notify locks once every 5 seconds.
try to use rounded values. */
next_alarm = (max_wait_time - now) % 5;
if (next_alarm == 0)
next_alarm = 5;
alarm(next_alarm);
index_storage_lock_notify(&ctx->mbox->box,
MAILBOX_LOCK_NOTIFY_MAILBOX_ABORT,
max_wait_time - now);
}
alarm(0);
ctx->fcntl_locked = TRUE;
return 1;
}
static int ATTR_NOWARN_UNUSED_RESULT
mbox_lock_list(struct mbox_lock_context *ctx, int lock_type,
time_t max_wait_time, int idx)
{
enum mbox_lock_type *lock_types;
enum mbox_lock_type type;
int i, ret = 0, lock_status;
ctx->lock_type = lock_type;
lock_types = lock_type == F_WRLCK ||
(lock_type == F_UNLCK && ctx->mbox->mbox_lock_type == F_WRLCK) ?
ctx->mbox->storage->write_locks :
ctx->mbox->storage->read_locks;
for (i = idx; lock_types[i] != (enum mbox_lock_type)-1; i++) {
type = lock_types[i];
lock_status = lock_type != F_UNLCK;
if (ctx->lock_status[type] == lock_status)
continue;
ctx->lock_status[type] = lock_status;
ret = lock_data[type].func(ctx, lock_type, max_wait_time);
if (ret <= 0)
break;
}
return ret;
}
static int mbox_update_locking(struct mbox_mailbox *mbox, int lock_type,
bool *fcntl_locked_r)
{
struct mbox_lock_context ctx;
time_t max_wait_time;
int ret, i;
bool drop_locks;
*fcntl_locked_r = FALSE;
index_storage_lock_notify_reset(&mbox->box);
if (!mbox->storage->lock_settings_initialized)
mbox_init_lock_settings(mbox->storage);
if (mbox->mbox_fd == -1 && mbox->mbox_file_stream != NULL) {
/* read-only mbox stream. no need to lock. */
i_assert(mbox_is_backend_readonly(mbox));
mbox->mbox_lock_type = lock_type;
return 1;
}
max_wait_time = time(NULL) +
mail_storage_get_lock_timeout(&mbox->storage->storage,
mbox->storage->set->mbox_lock_timeout);
memset(&ctx, 0, sizeof(ctx));
ctx.mbox = mbox;
if (mbox->mbox_lock_type == F_WRLCK) {
/* dropping to shared lock. first drop those that we
don't remove completely. */
const enum mbox_lock_type *read_locks =
mbox->storage->read_locks;
for (i = 0; i < MBOX_LOCK_COUNT; i++)
ctx.lock_status[i] = 1;
for (i = 0; read_locks[i] != (enum mbox_lock_type)-1; i++)
ctx.lock_status[read_locks[i]] = 0;
drop_locks = TRUE;
} else {
drop_locks = FALSE;
}
mbox->mbox_lock_type = lock_type;
ret = mbox_lock_list(&ctx, lock_type, max_wait_time, 0);
if (ret <= 0) {
if (!drop_locks)
mbox_unlock_files(&ctx);
if (ret == 0) {
mail_storage_set_error(&mbox->storage->storage,
MAIL_ERROR_TEMP, MAIL_ERRSTR_LOCK_TIMEOUT);
}
return ret;
}
if (drop_locks) {
/* dropping to shared lock: drop the locks that are only
in write list */
const enum mbox_lock_type *read_locks =
mbox->storage->read_locks;
const enum mbox_lock_type *write_locks =
mbox->storage->write_locks;
memset(ctx.lock_status, 0, sizeof(ctx.lock_status));
for (i = 0; write_locks[i] != (enum mbox_lock_type)-1; i++)
ctx.lock_status[write_locks[i]] = 1;
for (i = 0; read_locks[i] != (enum mbox_lock_type)-1; i++)
ctx.lock_status[read_locks[i]] = 0;
mbox->mbox_lock_type = F_WRLCK;
mbox_lock_list(&ctx, F_UNLCK, 0, 0);
mbox->mbox_lock_type = F_RDLCK;
}
*fcntl_locked_r = ctx.fcntl_locked;
return 1;
}
int mbox_lock(struct mbox_mailbox *mbox, int lock_type,
unsigned int *lock_id_r)
{
const char *path = mailbox_get_path(&mbox->box);
int mbox_fd = mbox->mbox_fd;
bool fcntl_locked;
int ret;
if (lock_type == F_RDLCK && mbox->external_transactions > 0 &&
mbox->mbox_lock_type != F_RDLCK) {
/* we have a transaction open that is going to save mails
and apparently also wants to read from the same mailbox
(copy, move, catenate). we need to write lock the mailbox,
since we can't later upgrade a read lock to write lock. */
lock_type = F_WRLCK;
}
/* allow only unlock -> shared/exclusive or exclusive -> shared */
i_assert(lock_type == F_RDLCK || lock_type == F_WRLCK);
i_assert(lock_type == F_RDLCK || mbox->mbox_lock_type != F_RDLCK);
/* mbox must be locked before index (the NULL check is for
MAILBOX_FLAG_KEEP_LOCKED) */
i_assert(mbox->box.index == NULL ||
mbox->box.index->lock_type != F_WRLCK);
if (mbox->mbox_lock_type == F_UNLCK) {
ret = mbox_update_locking(mbox, lock_type, &fcntl_locked);
if (ret <= 0)
return ret;
if (mbox->storage->storage.set->mail_nfs_storage) {
if (fcntl_locked) {
nfs_flush_attr_cache_fd_locked(path, mbox_fd);
nfs_flush_read_cache_locked(path, mbox_fd);
} else {
nfs_flush_attr_cache_unlocked(path);
nfs_flush_read_cache_unlocked(path, mbox_fd);
}
}
mbox->mbox_lock_id += 2;
}
if (lock_type == F_RDLCK) {
mbox->mbox_shared_locks++;
*lock_id_r = mbox->mbox_lock_id;
} else {
mbox->mbox_excl_locks++;
*lock_id_r = mbox->mbox_lock_id + 1;
}
if (mbox->mbox_stream != NULL)
istream_raw_mbox_set_locked(mbox->mbox_stream);
return 1;
}
static int mbox_unlock_files(struct mbox_lock_context *ctx)
{
int ret = 0;
if (mbox_lock_list(ctx, F_UNLCK, 0, 0) < 0)
ret = -1;
ctx->mbox->mbox_lock_id += 2;
ctx->mbox->mbox_lock_type = F_UNLCK;
return ret;
}
int mbox_unlock(struct mbox_mailbox *mbox, unsigned int lock_id)
{
struct mbox_lock_context ctx;
bool fcntl_locked;
int i;
i_assert(mbox->mbox_lock_id == (lock_id & ~1));
if (lock_id & 1) {
/* dropping exclusive lock */
i_assert(mbox->mbox_excl_locks > 0);
if (--mbox->mbox_excl_locks > 0)
return 0;
if (mbox->mbox_shared_locks > 0) {
/* drop to shared lock */
if (mbox_update_locking(mbox, F_RDLCK,
&fcntl_locked) < 0)
return -1;
return 0;
}
} else {
/* dropping shared lock */
i_assert(mbox->mbox_shared_locks > 0);
if (--mbox->mbox_shared_locks > 0)
return 0;
if (mbox->mbox_excl_locks > 0)
return 0;
}
/* all locks gone */
/* make sure we don't read the stream while unlocked */
if (mbox->mbox_stream != NULL)
istream_raw_mbox_set_unlocked(mbox->mbox_stream);
memset(&ctx, 0, sizeof(ctx));
ctx.mbox = mbox;
for (i = 0; i < MBOX_LOCK_COUNT; i++)
ctx.lock_status[i] = 1;
return mbox_unlock_files(&ctx);
}
unsigned int mbox_get_cur_lock_id(struct mbox_mailbox *mbox)
{
return mbox->mbox_lock_id +
(mbox->mbox_excl_locks > 0 ? 1 : 0);
}
void mbox_dotlock_touch(struct mbox_mailbox *mbox)
{
if (mbox->mbox_dotlock == NULL)
return;
if (!mbox->mbox_used_privileges)
(void)file_dotlock_touch(mbox->mbox_dotlock);
else {
mbox_dotlock_privileged_op(mbox, NULL,
MBOX_DOTLOCK_OP_TOUCH);
}
}