mbox-lock.c revision 35077ff71c0082c1370d9c296abce3153f645958
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen/* Copyright (c) 2002-2009 Dovecot authors, see the included COPYING file */
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#include "lib.h"
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#include "restrict-access.h"
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#include "nfs-workarounds.h"
e074ffeaee1ce283bd42f167c6810e3d013f8218Timo Sirainen#include "mail-index-private.h"
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#include "mbox-storage.h"
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#include "istream-raw-mbox.h"
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#include "mbox-file.h"
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen#include "mbox-lock.h"
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen
0536ccb51d41e3078c3a9fa33e509fb4b2420f95Timo Sirainen#include <time.h>
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen#include <stdlib.h>
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#include <unistd.h>
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#include <fcntl.h>
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#include <sys/stat.h>
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#ifdef HAVE_FLOCK
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen# include <sys/file.h>
797de45dcf6e24642ab347d5033beb92034b779dTimo Sirainen#endif
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen/* 0.1 .. 0.2msec */
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen#define LOCK_RANDOM_USLEEP_TIME (100000 + (unsigned int)rand() % 100000)
4ee00532a265bdfb38539d811fcd12d51210ac35Timo Sirainen
4ee00532a265bdfb38539d811fcd12d51210ac35Timo Sirainen/* lock methods to use in wanted order */
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#define DEFAULT_READ_LOCK_METHODS "fcntl"
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#define DEFAULT_WRITE_LOCK_METHODS "dotlock fcntl"
dac0b2e5e0f38c6d95ef1a842d891480db580236Timo Sirainen/* lock timeout */
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#define MBOX_DEFAULT_LOCK_TIMEOUT (5*60)
797de45dcf6e24642ab347d5033beb92034b779dTimo Sirainen/* assume stale dotlock if mbox file hasn't changed for n seconds */
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#define DEFAULT_DOTLOCK_CHANGE_TIMEOUT (120)
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainenenum mbox_lock_type {
dac0b2e5e0f38c6d95ef1a842d891480db580236Timo Sirainen MBOX_LOCK_DOTLOCK,
dac0b2e5e0f38c6d95ef1a842d891480db580236Timo Sirainen MBOX_LOCK_DOTLOCK_TRY,
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen MBOX_LOCK_FCNTL,
dac0b2e5e0f38c6d95ef1a842d891480db580236Timo Sirainen MBOX_LOCK_FLOCK,
717a444a466280a84a468220f647fdcb9f3b546fTimo Sirainen MBOX_LOCK_LOCKF,
717a444a466280a84a468220f647fdcb9f3b546fTimo Sirainen
717a444a466280a84a468220f647fdcb9f3b546fTimo Sirainen MBOX_LOCK_COUNT
717a444a466280a84a468220f647fdcb9f3b546fTimo Sirainen};
717a444a466280a84a468220f647fdcb9f3b546fTimo Sirainen
717a444a466280a84a468220f647fdcb9f3b546fTimo Sirainenenum mbox_dotlock_op {
717a444a466280a84a468220f647fdcb9f3b546fTimo Sirainen MBOX_DOTLOCK_OP_LOCK,
717a444a466280a84a468220f647fdcb9f3b546fTimo Sirainen MBOX_DOTLOCK_OP_UNLOCK,
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen MBOX_DOTLOCK_OP_TOUCH
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen};
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainenstruct mbox_lock_context {
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen struct mbox_mailbox *mbox;
797de45dcf6e24642ab347d5033beb92034b779dTimo Sirainen int lock_status[MBOX_LOCK_COUNT];
720692523ece4a549f7c589508d5693ee310f6b3Timo Sirainen bool checked_file;
720692523ece4a549f7c589508d5693ee310f6b3Timo Sirainen
720692523ece4a549f7c589508d5693ee310f6b3Timo Sirainen int lock_type;
4b8459c6c24b79d4ed5974ab6e3289a3f2b701c0Timo Sirainen bool dotlock_last_stale;
720692523ece4a549f7c589508d5693ee310f6b3Timo Sirainen bool fcntl_locked;
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen bool using_privileges;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen};
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainenstruct mbox_lock_data {
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen enum mbox_lock_type type;
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen const char *name;
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen int (*func)(struct mbox_lock_context *ctx, int lock_type,
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen time_t max_wait_time);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen};
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainen
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainenstatic int mbox_lock_dotlock(struct mbox_lock_context *ctx, int lock_type,
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainen time_t max_wait_time);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainenstatic int mbox_lock_dotlock_try(struct mbox_lock_context *ctx, int lock_type,
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen time_t max_wait_time);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainenstatic int mbox_lock_fcntl(struct mbox_lock_context *ctx, int lock_type,
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen time_t max_wait_time);
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen#ifdef HAVE_FLOCK
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainenstatic int mbox_lock_flock(struct mbox_lock_context *ctx, int lock_type,
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen time_t max_wait_time);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#else
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen# define mbox_lock_flock NULL
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#endif
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#ifdef HAVE_LOCKF
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainenstatic int mbox_lock_lockf(struct mbox_lock_context *ctx, int lock_type,
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen time_t max_wait_time);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#else
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen# define mbox_lock_lockf NULL
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen#endif
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainenstatic struct mbox_lock_data lock_data[] = {
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen { MBOX_LOCK_DOTLOCK, "dotlock", mbox_lock_dotlock },
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen { MBOX_LOCK_DOTLOCK_TRY, "dotlock_try", mbox_lock_dotlock_try },
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen { MBOX_LOCK_FCNTL, "fcntl", mbox_lock_fcntl },
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen { MBOX_LOCK_FLOCK, "flock", mbox_lock_flock },
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen { MBOX_LOCK_LOCKF, "lockf", mbox_lock_lockf },
193f5296d2a6b847970c222d8a261b89aae46331Timo Sirainen { 0, NULL, NULL }
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen};
193f5296d2a6b847970c222d8a261b89aae46331Timo Sirainen
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainenstatic bool lock_settings_initialized = FALSE;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainenstatic enum mbox_lock_type read_locks[MBOX_LOCK_COUNT+1];
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainenstatic enum mbox_lock_type write_locks[MBOX_LOCK_COUNT+1];
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainenstatic int lock_timeout, dotlock_change_timeout;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainenstatic int mbox_lock_list(struct mbox_lock_context *ctx, int lock_type,
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen time_t max_wait_time, int idx);
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainenstatic int mbox_unlock_files(struct mbox_lock_context *ctx);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainenstatic void mbox_read_lock_methods(const char *str, const char *env,
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen enum mbox_lock_type *locks)
965e13eea8dc7f1da3769ab0c4667e36d0f33192Timo Sirainen{
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen enum mbox_lock_type type;
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainen const char *const *lock;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen int i, dest;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen for (lock = t_strsplit(str, " "), dest = 0; *lock != NULL; lock++) {
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen for (type = 0; lock_data[type].name != NULL; type++) {
debb28d107fa06d26fd194fdac523cfb32809199Timo Sirainen if (strcasecmp(*lock, lock_data[type].name) == 0) {
debb28d107fa06d26fd194fdac523cfb32809199Timo Sirainen type = lock_data[type].type;
4d4cd9cde9e01d4ad9354e6e30ac2f90d13042b2Timo Sirainen break;
4d4cd9cde9e01d4ad9354e6e30ac2f90d13042b2Timo Sirainen }
4d4cd9cde9e01d4ad9354e6e30ac2f90d13042b2Timo Sirainen }
debb28d107fa06d26fd194fdac523cfb32809199Timo Sirainen if (lock_data[type].name == NULL)
debb28d107fa06d26fd194fdac523cfb32809199Timo Sirainen i_fatal("%s: Invalid value %s", env, *lock);
debb28d107fa06d26fd194fdac523cfb32809199Timo Sirainen if (lock_data[type].func == NULL) {
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen i_fatal("%s: Support for lock type %s "
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen "not compiled into binary", env, *lock);
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen }
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen for (i = 0; i < dest; i++) {
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen if (locks[i] == type)
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen i_fatal("%s: Duplicated value %s", env, *lock);
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen }
31a574fda352ef4f71dbff9c30e15e4744e132c0Timo Sirainen
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen /* @UNSAFE */
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen locks[dest++] = type;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen }
193f5296d2a6b847970c222d8a261b89aae46331Timo Sirainen locks[dest] = (enum mbox_lock_type)-1;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen}
193f5296d2a6b847970c222d8a261b89aae46331Timo Sirainen
08f24237ccc177f5b3a09b24d8a725fa47e1ee32Timo Sirainenstatic void mbox_init_lock_settings(void)
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen{
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen const char *str;
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen int r, w;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen str = getenv("MBOX_READ_LOCKS");
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen if (str == NULL) str = DEFAULT_READ_LOCK_METHODS;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen mbox_read_lock_methods(str, "MBOX_READ_LOCKS", read_locks);
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen str = getenv("MBOX_WRITE_LOCKS");
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen if (str == NULL) str = DEFAULT_WRITE_LOCK_METHODS;
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen mbox_read_lock_methods(str, "MBOX_WRITE_LOCKS", write_locks);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen /* check that read/write list orders match. write_locks must contain
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen at least read_locks and possibly more. */
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen for (r = w = 0; write_locks[w] != (enum mbox_lock_type)-1; w++) {
debb28d107fa06d26fd194fdac523cfb32809199Timo Sirainen if (read_locks[r] == (enum mbox_lock_type)-1)
debb28d107fa06d26fd194fdac523cfb32809199Timo Sirainen break;
745f2c7424d88e368eff0a3a7650b352a9d1f0ddTimo Sirainen if (read_locks[r] == write_locks[w])
f05b9dd37f830576ca7d32ec7071bf87906df3d2Timo Sirainen r++;
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen }
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen if (read_locks[r] != (enum mbox_lock_type)-1) {
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen i_fatal("mbox read/write lock list settings are invalid. "
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen "Lock ordering must be the same with both, "
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen "and write locks must contain all read locks "
35565557e05721a761132cec2ba1d93acacb6c14Timo Sirainen "(and possibly more)");
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen }
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen str = getenv("MBOX_LOCK_TIMEOUT");
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen lock_timeout = str == NULL ? MBOX_DEFAULT_LOCK_TIMEOUT : atoi(str);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
797de45dcf6e24642ab347d5033beb92034b779dTimo Sirainen str = getenv("MBOX_DOTLOCK_CHANGE_TIMEOUT");
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen dotlock_change_timeout = str == NULL ?
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen DEFAULT_DOTLOCK_CHANGE_TIMEOUT : atoi(str);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen lock_settings_initialized = TRUE;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen}
b9a13c136b7c5803a8271878fcbbf5328f6e7f2aTimo Sirainen
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainenstatic int mbox_file_open_latest(struct mbox_lock_context *ctx, int lock_type)
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen{
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen struct mbox_mailbox *mbox = ctx->mbox;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen struct stat st;
6795f542ed816a3c977085d4f74df1d62a37b690Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen if (ctx->checked_file || lock_type == F_UNLCK)
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen return 0;
eff0f02f2c8320c1bd4df72a281a92051d78b2b1Timo Sirainen
eff0f02f2c8320c1bd4df72a281a92051d78b2b1Timo Sirainen if (mbox->mbox_fd != -1) {
eff0f02f2c8320c1bd4df72a281a92051d78b2b1Timo Sirainen /* we could flush NFS file handle cache here if we wanted to
eff0f02f2c8320c1bd4df72a281a92051d78b2b1Timo Sirainen be sure that the file is latest, but mbox files get rarely
eff0f02f2c8320c1bd4df72a281a92051d78b2b1Timo Sirainen deleted and the flushing might cause errors (e.g. EBUSY for
eff0f02f2c8320c1bd4df72a281a92051d78b2b1Timo Sirainen trying to flush a /var/mail mountpoint) */
eff0f02f2c8320c1bd4df72a281a92051d78b2b1Timo Sirainen if (nfs_safe_stat(mbox->path, &st) < 0) {
eff0f02f2c8320c1bd4df72a281a92051d78b2b1Timo Sirainen if (errno == ENOENT)
eff0f02f2c8320c1bd4df72a281a92051d78b2b1Timo Sirainen mailbox_set_deleted(&mbox->ibox.box);
637f9883a385abb03fd1211e79cc68df696cc387Timo Sirainen else
eff0f02f2c8320c1bd4df72a281a92051d78b2b1Timo Sirainen mbox_set_syscall_error(mbox, "stat()");
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen return -1;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen }
20a3870db4f78717574ee94bca1512994391b2abTimo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen if (st.st_ino != mbox->mbox_ino ||
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen !CMP_DEV_T(st.st_dev, mbox->mbox_dev))
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen mbox_file_close(mbox);
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen }
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen if (mbox->mbox_fd == -1) {
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen if (mbox_file_open(mbox) < 0)
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen return -1;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen }
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen ctx->checked_file = TRUE;
dba5754de32284b3149ddd5c9bb1701b05707752Timo Sirainen return 0;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen}
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainenstatic bool dotlock_callback(unsigned int secs_left, bool stale, void *context)
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen{
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainen struct mbox_lock_context *ctx = context;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen enum mbox_lock_type *lock_types;
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen int i;
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainen
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen if (ctx->using_privileges)
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainen restrict_access_drop_priv_gid();
dcc76bb1e1bb287e3e71e6a39a7ca207fab0eaa8Timo Sirainen
8d59f06c9422fa49b538e23ffb06eddb23c6add2Timo Sirainen if (stale && !ctx->dotlock_last_stale) {
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen /* get next index we wish to try locking. it's the one after
b9a13c136b7c5803a8271878fcbbf5328f6e7f2aTimo Sirainen dotlocking. */
b9a13c136b7c5803a8271878fcbbf5328f6e7f2aTimo Sirainen lock_types = ctx->lock_type == F_WRLCK ||
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen (ctx->lock_type == F_UNLCK &&
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen ctx->mbox->mbox_lock_type == F_WRLCK) ?
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen write_locks : read_locks;
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen
c014f12e8268bf37ca2997e632ad7c22b8d04a84Timo Sirainen for (i = 0; lock_types[i] != (enum mbox_lock_type)-1; i++) {
if (lock_types[i] == MBOX_LOCK_DOTLOCK)
break;
}
if (lock_types[i] != (enum mbox_lock_type)-1 &&
lock_types[i+1] != (enum mbox_lock_type)-1) {
i++;
if (mbox_lock_list(ctx, ctx->lock_type, 0, i) <= 0) {
/* we couldn't get fd lock -
it's really locked */
ctx->dotlock_last_stale = TRUE;
return FALSE;
}
(void)mbox_lock_list(ctx, F_UNLCK, 0, i);
}
}
ctx->dotlock_last_stale = stale;
index_storage_lock_notify(&ctx->mbox->ibox, stale ?
MAILBOX_LOCK_NOTIFY_MAILBOX_OVERRIDE :
MAILBOX_LOCK_NOTIFY_MAILBOX_ABORT,
secs_left);
if (ctx->using_privileges) {
if (restrict_access_use_priv_gid() < 0) {
/* shouldn't get here */
return FALSE;
}
}
return TRUE;
}
static int mbox_dotlock_privileged_op(struct mbox_mailbox *mbox,
struct dotlock_settings *set,
enum mbox_dotlock_op op)
{
const char *dir, *fname;
int ret = -1, orig_dir_fd;
orig_dir_fd = open(".", O_RDONLY);
if (orig_dir_fd == -1) {
mail_storage_set_critical(&mbox->storage->storage,
"open(.) failed: %m");
return -1;
}
/* allow dotlocks to be created only for files we can read while we're
unprivileged. to make sure there are no race conditions we first
have to chdir to the mbox file's directory and then use relative
paths. unless this is done, users could:
- create *.lock files to any directory writable by the
privileged group
- DoS other users by dotlocking their mailboxes infinitely
*/
fname = strrchr(mbox->path, '/');
if (fname == NULL) {
/* already relative */
fname = mbox->path;
} else {
dir = t_strdup_until(mbox->path, fname);
if (chdir(dir) < 0) {
mail_storage_set_critical(&mbox->storage->storage,
"chdir(%s) failed: %m", dir);
(void)close(orig_dir_fd);
return -1;
}
fname++;
}
if (op == MBOX_DOTLOCK_OP_LOCK) {
if (access(fname, R_OK) < 0) {
mail_storage_set_critical(&mbox->storage->storage,
"access(%s) failed: %m", mbox->path);
return -1;
}
}
if (restrict_access_use_priv_gid() < 0) {
(void)close(orig_dir_fd);
return -1;
}
switch (op) {
case MBOX_DOTLOCK_OP_LOCK:
/* we're now privileged - avoid doing as much as possible */
ret = file_dotlock_create(set, fname, 0, &mbox->mbox_dotlock);
if (ret > 0)
mbox->mbox_used_privileges = TRUE;
break;
case MBOX_DOTLOCK_OP_UNLOCK:
/* we're now privileged - avoid doing as much as possible */
ret = file_dotlock_delete(&mbox->mbox_dotlock);
mbox->mbox_used_privileges = FALSE;
break;
case MBOX_DOTLOCK_OP_TOUCH:
if (!file_dotlock_is_locked(mbox->mbox_dotlock)) {
file_dotlock_delete(&mbox->mbox_dotlock);
mbox->mbox_used_privileges = TRUE;
ret = -1;
} else {
ret = file_dotlock_touch(mbox->mbox_dotlock);
}
break;
}
restrict_access_drop_priv_gid();
if (fchdir(orig_dir_fd) < 0) {
mail_storage_set_critical(&mbox->storage->storage,
"fchdir() failed: %m");
}
(void)close(orig_dir_fd);
return ret;
}
static int
mbox_lock_dotlock_int(struct mbox_lock_context *ctx, int lock_type, bool try)
{
struct mbox_mailbox *mbox = ctx->mbox;
struct dotlock_settings set;
int ret;
if (lock_type == F_UNLCK) {
if (!mbox->mbox_dotlocked)
return 1;
if (!mbox->mbox_used_privileges)
ret = file_dotlock_delete(&mbox->mbox_dotlock);
else {
ctx->using_privileges = TRUE;
ret = mbox_dotlock_privileged_op(mbox, NULL,
MBOX_DOTLOCK_OP_UNLOCK);
ctx->using_privileges = FALSE;
}
if (ret <= 0) {
mbox_set_syscall_error(mbox, "file_dotlock_delete()");
ret = -1;
}
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.flags &
MAIL_STORAGE_FLAG_DOTLOCK_USE_EXCL) != 0;
set.nfs_flush = (mbox->storage->storage.flags &
MAIL_STORAGE_FLAG_NFS_FLUSH_STORAGE) != 0;
set.timeout = lock_timeout;
set.stale_timeout = dotlock_change_timeout;
set.callback = dotlock_callback;
set.context = ctx;
ret = file_dotlock_create(&set, mbox->path, 0, &mbox->mbox_dotlock);
if (ret < 0 && 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);
}
if (ret < 0) {
if ((ENOSPACE(errno) || errno == EACCES) && try)
return 1;
mbox_set_syscall_error(mbox, "file_lock_dotlock()");
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, last_notify;
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));
}
last_notify = 0;
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->ibox,
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, last_notify;
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;
}
last_notify = 0;
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->ibox,
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)",
ctx->mbox->path);
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->ibox,
MAILBOX_LOCK_NOTIFY_MAILBOX_ABORT,
max_wait_time - now);
}
alarm(0);
ctx->fcntl_locked = TRUE;
return 1;
}
static int 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) ?
write_locks : 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->ibox);
if (!lock_settings_initialized)
mbox_init_lock_settings();
if (mbox->mbox_fd == -1 && mbox->mbox_file_stream != NULL) {
/* read-only mbox stream. no need to lock. */
i_assert(mbox->ibox.backend_readonly);
mbox->mbox_lock_type = lock_type;
return 1;
}
max_wait_time = time(NULL) + 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. */
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)
(void)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 */
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;
(void)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)
{
bool fcntl_locked;
int ret;
/* 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 */
i_assert(mbox->ibox.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.flags &
MAIL_STORAGE_FLAG_NFS_FLUSH_STORAGE) != 0) {
if (fcntl_locked) {
nfs_flush_attr_cache_fd_locked(mbox->path,
mbox->mbox_fd);
nfs_flush_read_cache_locked(mbox->path,
mbox->mbox_fd);
} else {
nfs_flush_attr_cache_unlocked(mbox->path);
nfs_flush_read_cache_unlocked(mbox->path,
mbox->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);
}
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 {
(void)mbox_dotlock_privileged_op(mbox, NULL,
MBOX_DOTLOCK_OP_TOUCH);
}
}