Commit 46f1d7b9 46f1d7b9c07df8a5c331e61521114e08b43c7dec by Sam Roberts

Added lock mode MU_LOCK_EXTERNAL which calls an external program to do

the locking. Default is "dotlock", which is setgid mail so can lock in
a mailspool that users may not have write permissions to.
1 parent 5d3c07d1
......@@ -20,16 +20,16 @@
#endif
#include <stdlib.h>
#ifdef __EXT_QNX
# undef __EXT_QNX
#endif
#include <unistd.h>
#include <argp.h>
#include <mailutils/errno.h>
#include <mailutils/locker.h>
#define MU_DL_EX_OK 0
#define MU_DL_EX_ERROR 1
#define MU_DL_EX_EXIST 3
const char *argp_program_version = "GNU dotlock (" PACKAGE ") " VERSION;
const char *argp_program_bug_address = "<bug-mailutils@gnu.org>";
static char doc[] =
......@@ -54,6 +54,9 @@ static struct argp_option options[] = {
{"debug", 'd', NULL, 0,
"Print details of failure reasons to stderr", 0},
{"test", 'T', "PROGRAM", OPTION_HIDDEN,
"Test external dotlocker", 0},
{NULL, 0, NULL, 0, NULL, 0}
};
......@@ -72,6 +75,7 @@ static int flags;
static int retries;
static int force;
static int debug;
static const char *program;
static error_t
parse_opt (int key, char *arg, struct argp_state *state)
......@@ -86,11 +90,18 @@ parse_opt (int key, char *arg, struct argp_state *state)
unlock = 1;
break;
case 'T':
/* This options exists only to test whether internal and external
locking work correctly/the same. */
flags |= MU_LOCKER_EXTERNAL;
program = arg;
break;
case 'r':
if (arg)
{
retries = atoi (arg);
if (retries == 0)
if (retries <= 0)
argp_error (state, "RETRIES must be greater than 0");
}
flags |= MU_LOCKER_RETRY;
......@@ -100,7 +111,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
if (arg)
{
force = atoi (arg);
if (force == 0)
if (force <= 0)
argp_error (state, "MINUTES must be greater than 0");
force *= 60;
}
......@@ -127,6 +138,13 @@ main (int argc, char *argv[])
{
locker_t locker = 0;
int err = 0;
pid_t usergid = getgid();
pid_t mailgid = getegid();
/* Drop permissions during argument parsing. */
if(setegid(usergid) < 0)
return MU_DL_EX_ERROR;
argp_parse (&argp, argc, argv, 0, NULL, NULL);
......@@ -145,11 +163,19 @@ main (int argc, char *argv[])
if (retries != 0)
locker_set_retries (locker, retries);
if (program != 0)
locker_set_external (locker, program);
if(setegid(mailgid) < 0)
return MU_DL_EX_ERROR;
if (unlock)
err = locker_remove_lock (locker);
else
err = locker_lock (locker);
setegid(usergid);
locker_destroy (&locker);
if (debug && err)
......@@ -161,6 +187,12 @@ main (int argc, char *argv[])
case 0:
err = MU_DL_EX_OK;
break;
case EPERM:
err = MU_DL_EX_PERM;
break;
case MU_ERR_LOCK_NOT_HELD:
err = MU_DL_EX_NEXIST;
break;
case MU_ERR_LOCK_CONFLICT:
err = MU_DL_EX_EXIST;
break;
......@@ -171,3 +203,4 @@ main (int argc, char *argv[])
return err;
}
......
......@@ -18,11 +18,8 @@
#ifndef _MAILUTILS_LOCKER_H
#define _MAILUTILS_LOCKER_H
#include <mailutils/mu_features.h>
#include <mailutils/types.h>
#include <sys/types.h>
#ifdef __cplusplus
extern "C" {
#endif
......@@ -31,12 +28,20 @@ extern "C" {
#define MU_LOCKER_EXPIRE_TIME (10 * 60)
#define MU_LOCKER_RETRIES (10)
#define MU_LOCKER_RETRY_SLEEP (1)
#define MU_LOCKER_EXTERNAL_PROGRAM "dotlock"
/* return codes for the external locker */
#define MU_DL_EX_PERM 4 /* insufficient permissions */
#define MU_DL_EX_EXIST 3 /* lock requested, but file is already locked */
#define MU_DL_EX_NEXIST 2 /* unlock requested, but file is not locked */
#define MU_DL_EX_ERROR 1 /* failed due to some other error */
#define MU_DL_EX_OK 0 /* success */
/* locker_create() flags */
#define MU_LOCKER_NULL 0
/* Special locker type: means no lock. This is to be used with
temporary mailboxes stored in memory. */
#define MU_LOCKER_SIMPLE 0x00
/* Just try and dotlock the file, not the default because its usually
better to retry. */
#define MU_LOCKER_RETRY 0x01
/* This requests that we loop retries times, sleeping retry_sleep
seconds in between trying to obtain the lock before failing with
......@@ -51,6 +56,13 @@ extern "C" {
an external dotlocker, non-setgid programs will use a dotlocker,
which locks and exits imediately. This is a protection against
a server crashing, it's not generally useful. */
#define MU_LOCKER_EXTERNAL 0x08
/* Use an external program to lock the file. This is necessary
for programs having permission to access a file, but do not
have write permission on the directory that contains that file. */
#define MU_LOCKER_NULL 0x10
/* Special locker type: means no lock. This is to be used with
temporary mailboxes stored in memory. */
#define MU_LOCKER_DEFAULT (MU_LOCKER_RETRY)
......@@ -63,11 +75,13 @@ extern int locker_set_flags __P ((locker_t, int));
extern int locker_set_expire_time __P ((locker_t, int));
extern int locker_set_retries __P ((locker_t, int));
extern int locker_set_retry_sleep __P ((locker_t, int));
extern int locker_set_external __P ((locker_t, const char* program));
extern int locker_get_flags __P ((locker_t, int*));
extern int locker_get_expire_time __P ((locker_t, int*));
extern int locker_get_retries __P ((locker_t, int*));
extern int locker_get_retry_sleep __P ((locker_t, int*));
extern int locker_get_external __P ((locker_t, char**));
extern int locker_lock __P ((locker_t));
extern int locker_touchlock __P ((locker_t));
......
......@@ -35,8 +35,9 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <mailutils/locker.h>
#include <mailutils/errno.h>
#include <mailutils/locker.h>
#include <mailutils/mutil.h>
#define LOCKFILE_ATTR 0444
......@@ -45,7 +46,6 @@
struct _locker
{
int fd;
int refcnt;
char *file;
......@@ -58,18 +58,19 @@ struct _locker
int expire_time;
int retries;
int retry_sleep;
char *external;
};
/* Assert that we're managing the refcnt and fd correctly, either
* we have a lock, and the fd is valid, or refcnt is 0 and fd is -1.
* And refcnt can never be less than 0.
*/
#define INVARIANT(l) \
assert((l)->refcnt >= 0); \
assert( \
(((l)->refcnt > 0) && ((l)->fd != -1)) || \
(((l)->refcnt == 0) && ((l)->fd == -1)) \
);
#define INVARIANT(l) assert((l)->refcnt >= 0);
static void expire_stale_lock (locker_t lock);
static int stat_check (const char *file, int fd, int links);
static int check_file_permissions (const char *file);
static int lock_external(locker_t l, int lock);
int
locker_create (locker_t *plocker, const char *filename, int flags)
......@@ -112,11 +113,19 @@ locker_create (locker_t *plocker, const char *filename, int flags)
else
l->flags = MU_LOCKER_DEFAULT;
l->fd = -1;
l->expire_time = MU_LOCKER_EXPIRE_TIME;
l->retries = MU_LOCKER_RETRIES;
l->retry_sleep = MU_LOCKER_RETRY_SLEEP;
if(flags & MU_LOCKER_EXTERNAL)
{
if(!(l->external = strdup(MU_LOCKER_EXTERNAL_PROGRAM)))
{
locker_destroy(&l);
return ENOMEM;
}
}
INVARIANT(l);
*plocker = l;
......@@ -129,10 +138,10 @@ locker_destroy (locker_t *plocker)
{
if (plocker && *plocker)
{
close((*plocker)->fd);
free ((*plocker)->file);
free ((*plocker)->dotlock);
free ((*plocker)->nfslock);
free ((*plocker)->external);
free (*plocker);
*plocker = NULL;
}
......@@ -190,6 +199,28 @@ locker_set_retry_sleep (locker_t locker, int retry_sleep)
return 0;
}
int
locker_set_external (locker_t locker, const char* program)
{
char* p = NULL;
if (!locker)
return MU_ERR_LOCKER_NULL;
/* program can be NULL */
if(program != 0)
{
p = strdup(program);
if(!p)
return ENOMEM;
}
free(locker->external);
locker->external = p;
return 0;
}
int locker_get_flags (locker_t locker, int *flags)
{
if (!locker)
......@@ -245,97 +276,6 @@ locker_get_retry_sleep (locker_t locker, int *retry_sleep)
return 0;
}
/* expire a stale lock (if MU_LOCKER_PID or MU_LOCKER_TIME) */
static void
expire_stale_lock (locker_t lock)
{
int stale = 0;
int fd = open (lock->dotlock, O_RDONLY);
if (fd == -1)
return;
/* Check to see if this process is still running. */
if (lock->flags & MU_LOCKER_PID)
{
char buf[16];
pid_t pid;
int nread = read (fd, buf, sizeof (buf) - 1);
if (nread > 0)
{
buf[nread] = '\0';
pid = strtol (buf, NULL, 10);
if (pid > 0)
{
/* Process is gone so we try to remove the lock. */
if (kill (pid, 0) == -1)
stale = 1;
}
else
stale = 1; /* Corrupted file, remove the lock. */
}
}
/* Check to see if the lock expired. */
if (lock->flags & MU_LOCKER_TIME)
{
struct stat stbuf;
fstat (fd, &stbuf);
/* The lock has expired. */
if ((time (NULL) - stbuf.st_mtime) > lock->expire_time)
stale = 1;
}
close (fd);
if (stale)
unlink (lock->dotlock);
}
static int
stat_check (const char *file, int fd, int links)
{
struct stat fn_stat;
struct stat fd_stat;
int err = 0;
int localfd = -1;
if(fd == -1)
{
localfd = open(file, O_RDONLY);
if(localfd == -1)
return errno;
fd = localfd;
}
/* We should always be able to stat a valid fd, so this
is an error condition. */
if (lstat (file, &fn_stat) || fstat (fd, &fd_stat))
err = errno;
else
{
/* If the link and stat don't report the same info, or the
file is a symlink, fail the locking. */
#define CHK(X) if(X) err = EINVAL
CHK (!S_ISREG (fn_stat.st_mode));
CHK (!S_ISREG (fd_stat.st_mode));
CHK (fn_stat.st_nlink != links);
CHK (fn_stat.st_dev != fd_stat.st_dev);
CHK (fn_stat.st_ino != fd_stat.st_ino);
CHK (fn_stat.st_mode != fd_stat.st_mode);
CHK (fn_stat.st_nlink != fd_stat.st_nlink);
CHK (fn_stat.st_uid != fd_stat.st_uid);
CHK (fn_stat.st_gid != fd_stat.st_gid);
CHK (fn_stat.st_rdev != fd_stat.st_rdev);
#undef CHK
}
if(localfd != -1)
close(localfd);
return err;
}
int
locker_lock (locker_t lock)
{
......@@ -353,31 +293,20 @@ locker_lock (locker_t lock)
/* Is the lock already applied? */
if (lock->refcnt > 0)
{
assert (lock->fd != -1);
lock->refcnt++;
return 0;
}
assert (lock->fd == -1);
/* Do the lock with an external program, if requested. */
if (lock->flags & MU_LOCKER_EXTERNAL)
{
return lock_external(lock, 1);
}
/* Check we are trying to lock a regular file, with a link count
of 1, that we have permission to read, etc., or don't lock it. */
if ((fd = open (lock->file, O_RDONLY)) == -1)
{
return errno;
}
else
{
err = stat_check (lock->file, fd, 1);
close (fd);
fd = -1;
if (err)
{
if (err == EINVAL)
err = MU_ERR_LOCK_BAD_FILE;
return err;
}
}
if((err = check_file_permissions(lock->file)))
return err;
if (lock->flags & MU_LOCKER_RETRY)
{
......@@ -501,10 +430,9 @@ locker_lock (locker_t lock)
write (fd, buf, strlen (buf));
}
lock->refcnt = 1;
lock->fd = fd;
close(fd);
INVARIANT (lock);
lock->refcnt = 1;
return 0;
}
......@@ -518,10 +446,6 @@ locker_touchlock (locker_t lock)
if (lock->flags == MU_LOCKER_NULL)
return 0;
assert(lock->dotlock);
INVARIANT(lock);
if(lock->refcnt > 0)
return utime (lock->dotlock, NULL);
......@@ -531,34 +455,46 @@ locker_touchlock (locker_t lock)
int
locker_unlock (locker_t lock)
{
int err = 0;
if (!lock)
return MU_ERR_LOCKER_NULL;
if (lock->flags == MU_LOCKER_NULL)
return 0;
assert(lock->refcnt >= 0);
if (!lock)
return MU_ERR_LOCKER_NULL;
INVARIANT(lock);
if(lock->refcnt == 0)
if (lock->refcnt == 0)
return MU_ERR_LOCK_NOT_HELD;
assert(lock->fd != -1);
/* Do the lock with an external program, if requested. */
if (lock->flags & MU_LOCKER_EXTERNAL)
{
return lock_external (lock, 0);
}
if (--lock->refcnt > 0)
return 0;
if (lock->refcnt > 1)
{
lock->refcnt--;
return 0;
}
assert(lock->refcnt == 0);
if ((err = check_file_permissions (lock->file)))
return err;
if (unlink (lock->dotlock) == -1)
{
err = errno;
if (err == ENOENT)
{
lock->refcnt = 0;
err = MU_ERR_LOCK_NOT_HELD;
return err;
}
close (lock->fd);
lock->fd = -1;
unlink (lock->dotlock);
return err;
}
INVARIANT(lock);
lock->refcnt = 0;
return 0;
}
......@@ -573,29 +509,202 @@ locker_remove_lock (locker_t lock)
if (lock->flags == MU_LOCKER_NULL)
return 0;
INVARIANT(lock);
/* If we hold the lock, do an unlock... */
if(lock->refcnt > 0)
/* Force the reference count to 1 to unlock the file. */
lock->refcnt = 1;
err = locker_unlock(lock);
return err;
}
/* expire a stale lock (if MU_LOCKER_PID or MU_LOCKER_TIME) */
static void
expire_stale_lock (locker_t lock)
{
int stale = 0;
int fd = open (lock->dotlock, O_RDONLY);
if (fd == -1)
return;
/* Check to see if this process is still running. */
if (lock->flags & MU_LOCKER_PID)
{
/* Force the reference count to 1 to unlock the file. */
lock->refcnt = 1;
return locker_unlock(lock);
char buf[16];
pid_t pid;
int nread = read (fd, buf, sizeof (buf) - 1);
if (nread > 0)
{
buf[nread] = '\0';
pid = strtol (buf, NULL, 10);
if (pid > 0)
{
/* Process is gone so we try to remove the lock. */
if (kill (pid, 0) == -1)
stale = 1;
}
else
stale = 1; /* Corrupted file, remove the lock. */
}
}
/* Check to see if the lock expired. */
if (lock->flags & MU_LOCKER_TIME)
{
struct stat stbuf;
/* ... if we don't, unlink the lockfile. */
err = unlink (lock->dotlock);
fstat (fd, &stbuf);
/* The lock has expired. */
if ((time (NULL) - stbuf.st_mtime) > lock->expire_time)
stale = 1;
}
if(err == -1)
close (fd);
if (stale)
unlink (lock->dotlock);
}
static int
stat_check (const char *file, int fd, int links)
{
struct stat fn_stat;
struct stat fd_stat;
int err = 0;
int localfd = -1;
if(fd == -1)
{
localfd = open(file, O_RDONLY);
if(localfd == -1)
return errno;
fd = localfd;
}
/* We should always be able to stat a valid fd, so this
is an error condition. */
if (lstat (file, &fn_stat) || fstat (fd, &fd_stat))
err = errno;
else
{
err = errno;
if(err == ENOENT)
err = MU_ERR_LOCK_NOT_HELD;
/* If the link and stat don't report the same info, or the
file is a symlink, fail the locking. */
#define CHK(X) if(X) err = EINVAL
CHK (!S_ISREG (fn_stat.st_mode));
CHK (!S_ISREG (fd_stat.st_mode));
CHK (fn_stat.st_nlink != links);
CHK (fn_stat.st_dev != fd_stat.st_dev);
CHK (fn_stat.st_ino != fd_stat.st_ino);
CHK (fn_stat.st_mode != fd_stat.st_mode);
CHK (fn_stat.st_nlink != fd_stat.st_nlink);
CHK (fn_stat.st_uid != fd_stat.st_uid);
CHK (fn_stat.st_gid != fd_stat.st_gid);
CHK (fn_stat.st_rdev != fd_stat.st_rdev);
#undef CHK
}
if(localfd != -1)
close(localfd);
return err;
}
static int
check_file_permissions (const char *file)
{
int fd = -1;
int err = 0;
if ((fd = open (file, O_RDONLY)) == -1)
return errno;
err = stat_check (file, fd, 1);
close (fd);
fd = -1;
if (err)
{
if (err == EINVAL)
err = MU_ERR_LOCK_BAD_FILE;
return err;
}
INVARIANT(lock);
return 0;
}
/*
Estimate 1 decimal digit per 3 bits, + 1 for round off.
*/
#define DEC_DIGS_PER_INT (sizeof(int) * 8 / 3 + 1)
static int
lock_external (locker_t l, int lock)
{
int err = 0;
const char *av[6];
int ac = 0;
char aforce[3 + DEC_DIGS_PER_INT + 1];
char aretry[3 + DEC_DIGS_PER_INT + 1];
int status = 0;
assert (l);
assert (l->flags & MU_LOCKER_EXTERNAL);
assert (lock == !l->refcnt);
/* lock is true, refcnt is 0 or lock is false and refcnt is 1 */
av[ac++] = l->external ? l->external : MU_LOCKER_EXTERNAL_PROGRAM;
if (l->flags & MU_LOCKER_TIME)
{
snprintf (aforce, sizeof (aforce), "-f%d", l->expire_time);
aforce[sizeof (aforce) - 1] = 0;
av[ac++] = aforce;
}
if (l->flags & MU_LOCKER_RETRY)
{
snprintf (aretry, sizeof (aretry), "-r%d", l->retries);
aretry[sizeof (aretry) - 1] = 0;
av[ac++] = aretry;
}
if (lock == 0)
{
av[ac++] = "-u";
}
av[ac++] = l->file;
av[ac++] = NULL;
if ((err = mu_spawnvp (av[0], av, &status)))
return err;
if (!WIFEXITED (status))
{
err = MU_ERR_LOCK_EXT_KILLED;
}
else
{
switch (WEXITSTATUS (status))
{
case 127:
err = MU_ERR_LOCK_EXT_FAIL;
break;
case MU_DL_EX_OK:
err = 0;
break;
case MU_DL_EX_NEXIST:
err = MU_ERR_LOCK_NOT_HELD;
break;
case MU_DL_EX_EXIST:
err = MU_ERR_LOCK_CONFLICT;
break;
case MU_DL_EX_PERM:
err = EPERM;
break;
default:
case MU_DL_EX_ERROR:
err = MU_ERR_LOCK_EXT_ERR;
break;
}
}
return err;
}
......