mbox-lock.c revision 62461eb609e1d852e027cf4e07d30d51288678a2
02c335c23bf5fa225a467c19f2c063fb0dc7b8c3Timo Sirainen/* Copyright (c) 2002-2017 Dovecot authors, see the included COPYING file */
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen/* 0.1 .. 0.2msec */
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainen#define LOCK_RANDOM_USLEEP_TIME (100000 + (unsigned int)i_rand() % 100000)
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen int (*func)(struct mbox_lock_context *ctx, int lock_type,
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainenstatic int mbox_lock_dotlock(struct mbox_lock_context *ctx, int lock_type,
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainenstatic int mbox_lock_dotlock_try(struct mbox_lock_context *ctx, int lock_type,
ce1a6c9b82117d253df9acd77e54ac84dd8a247eTimo Sirainenstatic int mbox_lock_fcntl(struct mbox_lock_context *ctx, int lock_type,
ce1a6c9b82117d253df9acd77e54ac84dd8a247eTimo Sirainenstatic int mbox_lock_flock(struct mbox_lock_context *ctx, int lock_type,
ce1a6c9b82117d253df9acd77e54ac84dd8a247eTimo Sirainenstatic int mbox_lock_lockf(struct mbox_lock_context *ctx, int lock_type,
ce1a6c9b82117d253df9acd77e54ac84dd8a247eTimo Sirainen { MBOX_LOCK_DOTLOCK, "dotlock", mbox_lock_dotlock },
ce1a6c9b82117d253df9acd77e54ac84dd8a247eTimo Sirainen { MBOX_LOCK_DOTLOCK_TRY, "dotlock_try", mbox_lock_dotlock_try },
ce1a6c9b82117d253df9acd77e54ac84dd8a247eTimo Sirainen { MBOX_LOCK_FCNTL, "fcntl", mbox_lock_fcntl },
ce1a6c9b82117d253df9acd77e54ac84dd8a247eTimo Sirainen { MBOX_LOCK_FLOCK, "flock", mbox_lock_flock },
ce1a6c9b82117d253df9acd77e54ac84dd8a247eTimo Sirainen { MBOX_LOCK_LOCKF, "lockf", mbox_lock_lockf },
ce1a6c9b82117d253df9acd77e54ac84dd8a247eTimo Sirainenmbox_lock_list(struct mbox_lock_context *ctx, int lock_type,
ce1a6c9b82117d253df9acd77e54ac84dd8a247eTimo Sirainenmbox_unlock_files(struct mbox_lock_context *ctx);
ce1a6c9b82117d253df9acd77e54ac84dd8a247eTimo Sirainenstatic void mbox_read_lock_methods(const char *str, const char *env,
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen const char *const *lock;
ce1a6c9b82117d253df9acd77e54ac84dd8a247eTimo Sirainen for (lock = t_strsplit(str, " "), dest = 0; *lock != NULL; lock++) {
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen for (type = 0; lock_data[type].name != NULL; type++) {
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen if (strcasecmp(*lock, lock_data[type].name) == 0) {
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen for (i = 0; i < dest; i++) {
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen i_fatal("%s: Duplicated value %s", env, *lock);
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen /* @UNSAFE */
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainenstatic void mbox_init_lock_settings(struct mbox_storage *storage)
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen enum mbox_lock_type read_locks[MBOX_LOCK_COUNT+1];
e7dd5065d21c569e5e6ddd713bd345dadd1cf51dTimo Sirainen enum mbox_lock_type write_locks[MBOX_LOCK_COUNT+1];
d694a52bce62c52080c3f87a56dcf77030fd2712Timo Sirainen mbox_read_lock_methods(storage->set->mbox_read_locks,
009217abb57a24a4076092e8e4e165545747839eStephan Bosch mbox_read_lock_methods(storage->set->mbox_write_locks,
009217abb57a24a4076092e8e4e165545747839eStephan Bosch /* check that read/write list orders match. write_locks must contain
009217abb57a24a4076092e8e4e165545747839eStephan Bosch at least read_locks and possibly more. */
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen for (r = w = 0; write_locks[w] != (enum mbox_lock_type)-1; w++) {
c93aca832ee532010ead91b85fa9f614132e1be2Stephan Bosch if (read_locks[r] != (enum mbox_lock_type)-1) {
c93aca832ee532010ead91b85fa9f614132e1be2Stephan Bosch i_fatal("mbox read/write lock list settings are invalid. "
c93aca832ee532010ead91b85fa9f614132e1be2Stephan Bosch "Lock ordering must be the same with both, "
c93aca832ee532010ead91b85fa9f614132e1be2Stephan Bosch "and write locks must contain all read locks "
c93aca832ee532010ead91b85fa9f614132e1be2Stephan Bosch "(and possibly more)");
c93aca832ee532010ead91b85fa9f614132e1be2Stephan Bosch storage->read_locks = p_new(storage->storage.pool,
ab281fc992907b6cf6c730f672dc3aa4c6685015Timo Sirainen sizeof(*storage->read_locks) * (MBOX_LOCK_COUNT+1));
6d24551e169c0808695db35d7a228f1970a84c75Timo Sirainen storage->write_locks = p_new(storage->storage.pool,
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen sizeof(*storage->write_locks) * (MBOX_LOCK_COUNT+1));
1bf5c6c20f3d51f13d3240cfb46e471074c86276Timo Sirainenstatic int mbox_file_open_latest(struct mbox_lock_context *ctx, int lock_type)
1bf5c6c20f3d51f13d3240cfb46e471074c86276Timo Sirainen if (ctx->checked_file || lock_type == F_UNLCK)
8ce3071e80b9973230048ecadfcb073fb82cc69fTimo Sirainen /* we could flush NFS file handle cache here if we wanted to
ce1a6c9b82117d253df9acd77e54ac84dd8a247eTimo Sirainen be sure that the file is latest, but mbox files get rarely
ce1a6c9b82117d253df9acd77e54ac84dd8a247eTimo Sirainen deleted and the flushing might cause errors (e.g. EBUSY for
ce1a6c9b82117d253df9acd77e54ac84dd8a247eTimo Sirainen trying to flush a /var/mail mountpoint) */
ce1a6c9b82117d253df9acd77e54ac84dd8a247eTimo Sirainen if (nfs_safe_stat(mailbox_get_path(&mbox->box), &st) < 0) {
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainenstatic bool dotlock_callback(unsigned int secs_left, bool stale, void *context)
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen /* get next index we wish to try locking. it's the one after
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen dotlocking. */
b8afdaa1bffe2f27cd4b02bf3bfbd2d297c8e648Timo Sirainen for (i = 0; lock_types[i] != (enum mbox_lock_type)-1; i++) {
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen if (lock_types[i] != (enum mbox_lock_type)-1 &&
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen if (mbox_lock_list(ctx, ctx->lock_type, 0, i) <= 0) {
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen /* we couldn't get fd lock -
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen it's really locked */
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainen index_storage_lock_notify(&ctx->mbox->box, stale ?
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen /* shouldn't get here */
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainenstatic int ATTR_NULL(2) ATTR_NOWARN_UNUSED_RESULT
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainenmbox_dotlock_privileged_op(struct mbox_mailbox *mbox,
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen "open(.) failed: %m");
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainen /* allow dotlocks to be created only for files we can read while we're
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen unprivileged. to make sure there are no race conditions we first
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen have to chdir to the mbox file's directory and then use relative
1093de32efb2a231949566d4bd8aa55a8f43fb70Timo Sirainen paths. unless this is done, users could:
de754cb78f75e8b3b994cddafe41c9ed1467c33dTimo Sirainen - create *.lock files to any directory writable by the
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen privileged group
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen - DoS other users by dotlocking their mailboxes infinitely
660b99a7059824676b2b8d6f79b8e15d47df25a2Timo Sirainen /* already relative */
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen /* we're now privileged - avoid doing as much as possible */
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen ret = file_dotlock_create(set, fname, 0, &mbox->mbox_dotlock);
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen const char *errmsg =
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen eacces_error_get_creating("file_dotlock_create",
77f1da4b5e2b800197d8db548235497d5e9d6a4fTimo Sirainen mail_storage_set_critical(&mbox->storage->storage,
18f1bbf05980d3c53ecae81b62574212f0891522Timo Sirainen mbox_set_syscall_error(mbox, "file_dotlock_create()");
18f1bbf05980d3c53ecae81b62574212f0891522Timo Sirainen /* we're now privileged - avoid doing as much as possible */
18f1bbf05980d3c53ecae81b62574212f0891522Timo Sirainen ret = file_dotlock_delete(&mbox->mbox_dotlock);
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen mbox_set_syscall_error(mbox, "file_dotlock_delete()");
e911b23f3e05308df9b98b1a3fdaf72e4302d8fdTimo Sirainen mbox_set_syscall_error(mbox, "file_dotlock_touch()");
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen "fchdir() failed: %m");
1093de32efb2a231949566d4bd8aa55a8f43fb70Timo Sirainenmbox_dotlock_log_eacces_error(struct mbox_mailbox *mbox, const char *path)
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainen errmsg = eacces_error_get_creating("file_dotlock_create", path);
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen dir = dir == NULL ? "." : t_strdup_until(path, dir);
04b7dc631f33bf61f273138c679da9bd0910fb6dTimo Sirainen /* allow privileged locking for
04b7dc631f33bf61f273138c679da9bd0910fb6dTimo Sirainen a) user's own INBOX,
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainen b) another user's shared INBOX, and
04b7dc631f33bf61f273138c679da9bd0910fb6dTimo Sirainen c) anything called INBOX (in inbox=no namespace) */
04b7dc631f33bf61f273138c679da9bd0910fb6dTimo Sirainen if (!mbox->box.inbox_any && strcmp(mbox->box.name, "INBOX") != 0) {
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
1093de32efb2a231949566d4bd8aa55a8f43fb70Timo Sirainen "%s (not INBOX -> no privileged locking)", errmsg);
1093de32efb2a231949566d4bd8aa55a8f43fb70Timo Sirainen dir = mailbox_list_get_root_forced(mbox->box.list,
1093de32efb2a231949566d4bd8aa55a8f43fb70Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
1093de32efb2a231949566d4bd8aa55a8f43fb70Timo Sirainen "%s (under root dir %s -> no privileged locking)",
1093de32efb2a231949566d4bd8aa55a8f43fb70Timo Sirainen (st.st_mode & 02) == 0 && /* not world-writable */
1093de32efb2a231949566d4bd8aa55a8f43fb70Timo Sirainen (st.st_mode & 020) != 0) { /* group-writable */
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen "%s (set mail_privileged_group=%s)", errmsg, name);
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen "%s (nonstandard permissions in %s)", errmsg, dir);
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainenmbox_lock_dotlock_int(struct mbox_lock_context *ctx, int lock_type, bool try)
c51644e9e04effbbc9c415cadcfbcb4d9465855cTimo Sirainen if (file_dotlock_delete(&mbox->mbox_dotlock) <= 0) {
c51644e9e04effbbc9c415cadcfbcb4d9465855cTimo Sirainen "file_dotlock_delete()");
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen set.use_excl_lock = mbox->storage->storage.set->dotlock_use_excl;
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen set.nfs_flush = mbox->storage->storage.set->mail_nfs_storage;
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen set.timeout = mail_storage_get_lock_timeout(&mbox->storage->storage,
6d24551e169c0808695db35d7a228f1970a84c75Timo Sirainen set.stale_timeout = mbox->storage->set->mbox_dotlock_change_timeout;
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen ret = file_dotlock_create(&set, mailbox_get_path(&mbox->box), 0,
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen /* success / timeout */
a10ed8c47534b4c6b6bf2711ccfe577e720a47b4Timo Sirainen } else if (errno == EACCES && restrict_access_have_priv_gid() &&
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainen /* try again, this time with extra privileges */
280503e88a6b2f72a32a8fbe363794abaaa845d6Timo Sirainen mbox_dotlock_log_eacces_error(mbox, mailbox_get_path(&mbox->box));
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainen mbox_set_syscall_error(mbox, "file_dotlock_create()");
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen if ((ENOSPACE(errno) || errno == EACCES) && try)
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen mail_storage_set_error(&mbox->storage->storage,
660b99a7059824676b2b8d6f79b8e15d47df25a2Timo Sirainen if (mbox_file_open_latest(ctx, lock_type) < 0)
42fcc708268a89aa9640693e71d13a2bb76e19c8Timo Sirainenstatic int mbox_lock_dotlock(struct mbox_lock_context *ctx, int lock_type,
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen return mbox_lock_dotlock_int(ctx, lock_type, FALSE);
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainenstatic int mbox_lock_dotlock_try(struct mbox_lock_context *ctx, int lock_type,
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainen return mbox_lock_dotlock_int(ctx, lock_type, TRUE);
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainenstatic int mbox_lock_flock(struct mbox_lock_context *ctx, int lock_type,
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainen if (mbox_file_open_latest(ctx, lock_type) < 0)
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainen if (lock_type == F_UNLCK && ctx->mbox->mbox_fd == -1)
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainen /* usually we're waiting here, but if we came from
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainen mbox_lock_dotlock(), we just want to try locking */
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainen while (flock(ctx->mbox->mbox_fd, lock_type) < 0) {
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainen if (errno == EWOULDBLOCK && max_wait_time == 0) {
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainen /* non-blocking lock trying failed */
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainen /* notify locks once every 5 seconds.
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainen try to use rounded values. */
decb23442f9e6cd5c4845a9cb162029b8c6d5f0fTimo Sirainenstatic int mbox_lock_lockf(struct mbox_lock_context *ctx, int lock_type,
a75907609d7c410c9e17beedfafbf28b4439fa8aTimo Sirainen if (lock_type == F_UNLCK && ctx->mbox->mbox_fd == -1)
a75907609d7c410c9e17beedfafbf28b4439fa8aTimo Sirainen else if (max_wait_time == 0) {
a75907609d7c410c9e17beedfafbf28b4439fa8aTimo Sirainen /* usually we're waiting here, but if we came from
decb23442f9e6cd5c4845a9cb162029b8c6d5f0fTimo Sirainen mbox_lock_dotlock(), we just want to try locking */
a75907609d7c410c9e17beedfafbf28b4439fa8aTimo Sirainen while (lockf(ctx->mbox->mbox_fd, lock_type, 0) < 0) {
a75907609d7c410c9e17beedfafbf28b4439fa8aTimo Sirainen /* non-blocking lock trying failed */
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainen /* notify locks once every 5 seconds.
46ec792dd4ccf6c34706c4774228301fafde6aa9Timo Sirainen try to use rounded values. */
4c6ddf2491104f917d00e6900e833e80ea02c7b6Timo Sirainenstatic int mbox_lock_fcntl(struct mbox_lock_context *ctx, int lock_type,
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen if (mbox_file_open_latest(ctx, lock_type) < 0)
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen if (lock_type == F_UNLCK && ctx->mbox->mbox_fd == -1)
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen /* usually we're waiting here, but if we came from
ce1a6c9b82117d253df9acd77e54ac84dd8a247eTimo Sirainen mbox_lock_dotlock(), we just want to try locking */
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen while (fcntl(ctx->mbox->mbox_fd, wait_type, &fl) < 0) {
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen /* non-blocking lock trying failed */
6b107c647b2120d3906ff7d368f8a16900f6833fTimo Sirainen mail_storage_set_critical(&ctx->mbox->storage->storage,
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen "fcntl() failed with mbox file %s: "
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainen "File is locked by another process (EACCES)",
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen /* notify locks once every 5 seconds.
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen try to use rounded values. */
3b22894b8805b186c73d8b754001e8d7e944be85Timo Sirainenmbox_lock_list(struct mbox_lock_context *ctx, int lock_type,
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen (lock_type == F_UNLCK && ctx->mbox->mbox_lock_type == F_WRLCK) ?
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen for (i = idx; lock_types[i] != (enum mbox_lock_type)-1; i++) {
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen if (ctx->locked_status[type] == locked_status)
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen ret = lock_data[type].func(ctx, lock_type, max_wait_time);
d3a7d023b47d2a137f01109e7b38702dca3f11d3Timo Sirainenstatic int mbox_update_locking(struct mbox_mailbox *mbox, int lock_type,
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen if (!mbox->storage->lock_settings_initialized)
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen if (mbox->mbox_fd == -1 && mbox->mbox_file_stream != NULL) {
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen /* read-only mbox stream. no need to lock. */
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen mail_storage_get_lock_timeout(&mbox->storage->storage,
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen /* dropping to shared lock. first drop those that we
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen don't remove completely. */
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen for (i = 0; i < MBOX_LOCK_COUNT; i++)
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen for (i = 0; read_locks[i] != (enum mbox_lock_type)-1; i++)
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen ret = mbox_lock_list(&ctx, lock_type, max_wait_time, 0);
80e461e945dd4d5b0d08535413176cf1756e8523Timo Sirainen mail_storage_set_error(&mbox->storage->storage,
9f627b360ed38fdc54cb02ec5e67246c3f0d5b0fTimo Sirainen /* dropping to shared lock: drop the locks that are only
e248fe370c4047cee921a91b48edc37944ab0526Timo Sirainen in write list */
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++)