mbox-lock.c revision 4780c036a0f52079068bd23e8a77b67ff457dba6
2454dfa32c93c20a8522c6ed42fe057baaac9f9aStephan Bosch/* Copyright (c) 2002-2013 Dovecot authors, see the included COPYING file */
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen/* 0.1 .. 0.2msec */
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen#define LOCK_RANDOM_USLEEP_TIME (100000 + (unsigned int)rand() % 100000)
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen int (*func)(struct mbox_lock_context *ctx, int lock_type,
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainenstatic int mbox_lock_dotlock(struct mbox_lock_context *ctx, int lock_type,
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainenstatic int mbox_lock_dotlock_try(struct mbox_lock_context *ctx, int lock_type,
46b823ac3bce2c0f9f0fc73911e48d3a77b04fbeTimo Sirainenstatic int mbox_lock_fcntl(struct mbox_lock_context *ctx, int lock_type,
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainenstatic int mbox_lock_flock(struct mbox_lock_context *ctx, int lock_type,
e245fb1302121d2bc2580f61e040c2c8a558ee9eTimo Sirainenstatic int mbox_lock_lockf(struct mbox_lock_context *ctx, int lock_type,
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 Sirainenmbox_lock_list(struct mbox_lock_context *ctx, int lock_type,
d97f081a3de44852197ced772e21560c108895a6Timo Sirainenmbox_unlock_files(struct mbox_lock_context *ctx);
d97f081a3de44852197ced772e21560c108895a6Timo Sirainenstatic void mbox_read_lock_methods(const char *str, const char *env,
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen const char *const *lock;
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) {
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen for (i = 0; i < dest; i++) {
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen i_fatal("%s: Duplicated value %s", env, *lock);
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen /* @UNSAFE */
d97f081a3de44852197ced772e21560c108895a6Timo Sirainenstatic void mbox_init_lock_settings(struct mbox_storage *storage)
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen enum mbox_lock_type read_locks[MBOX_LOCK_COUNT+1];
d97f081a3de44852197ced772e21560c108895a6Timo Sirainen enum mbox_lock_type write_locks[MBOX_LOCK_COUNT+1];
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen mbox_read_lock_methods(storage->set->mbox_read_locks,
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen mbox_read_lock_methods(storage->set->mbox_write_locks,
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 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)");
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen storage->read_locks = p_new(storage->storage.pool,
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen sizeof(*storage->read_locks) * (MBOX_LOCK_COUNT+1));
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen storage->write_locks = p_new(storage->storage.pool,
8a8a3b43987b5ade914f22765e51c9e3de8179d3Timo Sirainen sizeof(*storage->write_locks) * (MBOX_LOCK_COUNT+1));
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainenstatic int mbox_file_open_latest(struct mbox_lock_context *ctx, int lock_type)
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen if (ctx->checked_file || lock_type == F_UNLCK)
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) {
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainenstatic bool dotlock_callback(unsigned int secs_left, bool stale, void *context)
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen /* get next index we wish to try locking. it's the one after
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen dotlocking. */
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen for (i = 0; lock_types[i] != (enum mbox_lock_type)-1; i++) {
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen if (lock_types[i] != (enum mbox_lock_type)-1 &&
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 */
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen index_storage_lock_notify(&ctx->mbox->box, stale ?
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen /* shouldn't get here */
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainenstatic int ATTR_NULL(2) ATTR_NOWARN_UNUSED_RESULT
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainenmbox_dotlock_privileged_op(struct mbox_mailbox *mbox,
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen mail_storage_set_critical(&mbox->storage->storage,
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen "open(.) failed: %m");
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
d5e839aea288aceaddae28a1578cebda3c9e3b58Timo Sirainen /* already relative */
d5e839aea288aceaddae28a1578cebda3c9e3b58Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
153ed0fbca1f5f944b70937dfd71911db172ca97Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
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 const char *errmsg =
8a8a3b43987b5ade914f22765e51c9e3de8179d3Timo Sirainen eacces_error_get_creating("file_dotlock_create",
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen mbox_set_syscall_error(mbox, "file_dotlock_create()");
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen /* we're now privileged - avoid doing as much as possible */
fb176cdc122707cda985ab3c09c02ccf3cec0af1Timo Sirainen ret = file_dotlock_delete(&mbox->mbox_dotlock);
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen mbox_set_syscall_error(mbox, "file_dotlock_delete()");
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen mbox_set_syscall_error(mbox, "file_dotlock_touch()");
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen "fchdir() failed: %m");
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainenmbox_dotlock_log_eacces_error(struct mbox_mailbox *mbox, const char *path)
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen errmsg = eacces_error_get_creating("file_dotlock_create", 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 dir = mailbox_list_get_root_forced(mbox->box.list,
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen mail_storage_set_critical(&mbox->storage->storage,
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen "%s (under root dir %s -> no privileged locking)",
b3f4c31f1533e25380f49f77d5bb1251bf43db2aTimo Sirainen (st.st_mode & 02) == 0 && /* not world-writable */
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen (st.st_mode & 020) != 0) { /* group-writable */
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen mail_storage_set_critical(&mbox->storage->storage,
c096257fbdaf4b9fcf8eb97aae94afdbb4e71ed4Timo Sirainen "%s (set mail_privileged_group=%s)", errmsg, name);
2f4f603d4cebab2cc956c72164efb02da83515c5Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
baf346e71ebd7b44fcba4b48f4d39845453b778bTimo Sirainen "%s (nonstandard permissions in %s)", errmsg, dir);
bace943c67e6cd14ce6c994f533d82a3caad5bf1Timo Sirainenmbox_lock_dotlock_int(struct mbox_lock_context *ctx, int lock_type, bool try)
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen if (file_dotlock_delete(&mbox->mbox_dotlock) <= 0) {
cf636afb3826f0d8e15c248aa1fc04ce72820e08Timo Sirainen "file_dotlock_delete()");
if (ret >= 0) {
if (ret < 0) {
if (ret == 0) {
#ifdef HAVE_FLOCK
unsigned int next_alarm;
if (max_wait_time == 0) {
alarm(0);
alarm(0);
if (next_alarm == 0)
alarm(0);
#ifdef HAVE_LOCKF
unsigned int next_alarm;
else if (max_wait_time == 0) {
max_wait_time == 0) {
alarm(0);
alarm(0);
if (next_alarm == 0)
alarm(0);
unsigned int next_alarm;
int wait_type;
if (max_wait_time == 0) {
alarm(0);
alarm(0);
if (next_alarm == 0)
alarm(0);
static int ATTR_NOWARN_UNUSED_RESULT
if (ret <= 0)
return ret;
bool *fcntl_locked_r)
int ret, i;
bool drop_locks;
for (i = 0; i < MBOX_LOCK_COUNT; i++)
if (ret <= 0) {
if (!drop_locks)
if (ret == 0) {
return ret;
if (drop_locks) {
unsigned int *lock_id_r)
bool fcntl_locked;
int ret;
if (ret <= 0)
return ret;
if (fcntl_locked) {
int ret = 0;
return ret;
bool fcntl_locked;
&fcntl_locked) < 0)
for (i = 0; i < MBOX_LOCK_COUNT; i++)