mbox-lock.c revision a9a3687619f38c862849f38db45506cc1d2f8f75
/* Copyright (c) 2002-2014 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "eacces-error.h"
#include "restrict-access.h"
#include "nfs-workarounds.h"
#include "ipwd.h"
#include "mail-index-private.h"
#include "mbox-storage.h"
#include "istream-raw-mbox.h"
#include "mbox-file.h"
#include "mbox-lock.h"
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#ifdef HAVE_FLOCK
#endif
/* 0.1 .. 0.2msec */
enum mbox_lock_type {
};
enum mbox_dotlock_op {
};
struct mbox_lock_context {
struct mbox_mailbox *mbox;
int lock_status[MBOX_LOCK_COUNT];
bool checked_file;
int lock_type;
bool dotlock_last_stale;
bool fcntl_locked;
bool using_privileges;
};
struct mbox_lock_data {
enum mbox_lock_type type;
const char *name;
};
#ifdef HAVE_FLOCK
#else
# define mbox_lock_flock NULL
#endif
#ifdef HAVE_LOCKF
#else
# define mbox_lock_lockf NULL
#endif
static struct mbox_lock_data lock_data[] = {
};
static int ATTR_NOWARN_UNUSED_RESULT
static int ATTR_NOWARN_UNUSED_RESULT
enum mbox_lock_type *locks)
{
enum mbox_lock_type type;
const char *const *lock;
int i, dest;
break;
}
}
i_fatal("%s: Support for lock type %s "
}
for (i = 0; i < dest; i++) {
}
/* @UNSAFE */
}
}
{
int r, w;
"mbox_read_locks", read_locks);
"mbox_write_locks", write_locks);
at least read_locks and possibly more. */
break;
if (read_locks[r] == write_locks[w])
r++;
}
"Lock ordering must be the same with both, "
"and write locks must contain all read locks "
"(and possibly more)");
}
}
{
return 0;
/* we could flush NFS file handle cache here if we wanted to
be sure that the file is latest, but mbox files get rarely
deleted and the flushing might cause errors (e.g. EBUSY for
else
return -1;
}
}
if (mbox_file_open(mbox) < 0)
return -1;
}
return 0;
}
{
enum mbox_lock_type *lock_types;
int i;
if (ctx->using_privileges)
/* get next index we wish to try locking. it's the one after
dotlocking. */
if (lock_types[i] == MBOX_LOCK_DOTLOCK)
break;
}
i++;
/* we couldn't get fd lock -
it's really locked */
return FALSE;
}
}
}
if (ctx->using_privileges) {
if (restrict_access_use_priv_gid() < 0) {
/* shouldn't get here */
return FALSE;
}
}
return TRUE;
}
struct dotlock_settings *set,
enum mbox_dotlock_op op)
{
if (orig_dir_fd == -1) {
"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
*/
/* already relative */
} else {
"chdir(%s) failed: %m", dir);
return -1;
}
fname++;
}
if (op == MBOX_DOTLOCK_OP_LOCK) {
"access(%s) failed: %m", box_path);
return -1;
}
}
if (restrict_access_use_priv_gid() < 0) {
return -1;
}
switch (op) {
case MBOX_DOTLOCK_OP_LOCK:
/* we're now privileged - avoid doing as much as possible */
if (ret > 0)
const char *errmsg =
eacces_error_get_creating("file_dotlock_create",
fname);
"%s", errmsg);
} else {
}
break;
case MBOX_DOTLOCK_OP_UNLOCK:
/* we're now privileged - avoid doing as much as possible */
if (ret < 0)
break;
case MBOX_DOTLOCK_OP_TOUCH:
if (ret < 0)
break;
}
orig_errno = errno;
if (fchdir(orig_dir_fd) < 0) {
"fchdir() failed: %m");
}
errno = orig_errno;
return ret;
}
static void
{
int orig_errno = errno;
/* allow privileged locking for
a) user's own INBOX,
b) another user's shared INBOX, and
c) anything called INBOX (in inbox=no namespace) */
"%s (not INBOX -> no privileged locking)", errmsg);
} else if (!mbox->mbox_privileged_locking) {
"%s (under root dir %s -> no privileged locking)",
else
} else {
}
errno = orig_errno;
}
static int
{
struct dotlock_settings set;
int ret;
if (!mbox->mbox_dotlocked)
return 1;
if (!mbox->mbox_used_privileges) {
"file_dotlock_delete()");
}
} else {
}
return 1;
}
if (mbox->mbox_dotlocked)
return 1;
&mbox->mbox_dotlock);
if (ret >= 0) {
/* success / timeout */
/* try again, this time with extra privileges */
else
if (ret < 0) {
return 1;
return -1;
}
if (ret == 0) {
return 0;
}
return -1;
return 1;
}
{
}
{
}
#ifdef HAVE_FLOCK
{
unsigned int next_alarm;
return -1;
return 1;
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 */
} else {
if (now >= max_wait_time)
alarm(1);
else
}
/* non-blocking lock trying failed */
return 0;
}
alarm(0);
return -1;
}
if (now >= max_wait_time) {
alarm(0);
return 0;
}
/* notify locks once every 5 seconds.
try to use rounded values. */
if (next_alarm == 0)
next_alarm = 5;
max_wait_time - now);
}
alarm(0);
return 1;
}
#endif
#ifdef HAVE_LOCKF
{
unsigned int next_alarm;
return -1;
return 1;
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 */
} else {
if (now >= max_wait_time)
alarm(1);
else
}
max_wait_time == 0) {
/* non-blocking lock trying failed */
return 0;
}
alarm(0);
return -1;
}
if (now >= max_wait_time) {
alarm(0);
return 0;
}
/* notify locks once every 5 seconds.
try to use rounded values. */
if (next_alarm == 0)
next_alarm = 5;
max_wait_time - now);
}
alarm(0);
return 1;
}
#endif
{
unsigned int next_alarm;
int wait_type;
return -1;
return 1;
if (max_wait_time == 0) {
/* usually we're waiting here, but if we came from
mbox_lock_dotlock(), we just want to try locking */
} else {
if (now >= max_wait_time)
alarm(1);
else
}
/* non-blocking lock trying failed */
return 0;
}
alarm(0);
return -1;
}
"fcntl() failed with mbox file %s: "
"File is locked by another process (EACCES)",
return -1;
}
if (now >= max_wait_time) {
alarm(0);
return 0;
}
/* notify locks once every 5 seconds.
try to use rounded values. */
if (next_alarm == 0)
next_alarm = 5;
max_wait_time - now);
}
alarm(0);
return 1;
}
static int ATTR_NOWARN_UNUSED_RESULT
{
enum mbox_lock_type *lock_types;
enum mbox_lock_type type;
int i, ret = 0, lock_status;
type = lock_types[i];
continue;
if (ret <= 0)
break;
}
return ret;
}
bool *fcntl_locked_r)
{
struct mbox_lock_context ctx;
int ret, i;
bool drop_locks;
*fcntl_locked_r = FALSE;
/* read-only mbox stream. no need to lock. */
return 1;
}
/* dropping to shared lock. first drop those that we
don't remove completely. */
const enum mbox_lock_type *read_locks =
for (i = 0; i < MBOX_LOCK_COUNT; i++)
drop_locks = TRUE;
} else {
drop_locks = FALSE;
}
if (ret <= 0) {
if (!drop_locks)
if (ret == 0) {
}
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 =
const enum mbox_lock_type *write_locks =
}
return 1;
}
unsigned int *lock_id_r)
{
bool fcntl_locked;
int ret;
/* 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. */
}
/* mbox must be locked before index (the NULL check is for
MAILBOX_FLAG_KEEP_LOCKED) */
if (ret <= 0)
return ret;
if (fcntl_locked) {
} else {
}
}
}
} else {
mbox->mbox_excl_locks++;
}
return 1;
}
{
int ret = 0;
ret = -1;
return ret;
}
{
struct mbox_lock_context ctx;
bool fcntl_locked;
int i;
if (lock_id & 1) {
/* dropping exclusive lock */
if (--mbox->mbox_excl_locks > 0)
return 0;
if (mbox->mbox_shared_locks > 0) {
/* drop to shared lock */
&fcntl_locked) < 0)
return -1;
return 0;
}
} else {
/* dropping shared lock */
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 */
for (i = 0; i < MBOX_LOCK_COUNT; i++)
return mbox_unlock_files(&ctx);
}
{
return mbox->mbox_lock_id +
}
{
return;
if (!mbox->mbox_used_privileges)
else {
}
}