/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
* This library is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see .
*
* Authors: Michael Zucchi
*/
/* lock helper process */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SETEUID_SAVES (1)
/* we try and include as little as possible */
#include "camel-lock-helper.h"
#include "camel-lock.h"
#define d(x)
/* keeps track of open locks */
struct _lock_info {
struct _lock_info *next;
uid_t uid;
gint id;
gint depth;
time_t stamp; /* when last updated */
gchar path[1];
};
static gint lock_id = 0;
static struct _lock_info *lock_info_list;
static uid_t lock_root_uid = -1;
static uid_t lock_real_uid = -1;
/* utility functions */
static gint
read_n (gint fd,
gpointer buffer,
gint inlen)
{
gchar *p = buffer;
gint len, left = inlen;
do {
len = read (fd, p, left);
if (len == -1) {
if (errno != EINTR)
return -1;
} else {
left -= len;
p += len;
}
} while (left > 0 && len != 0);
return inlen - left;
}
static gint
write_n (gint fd,
gpointer buffer,
gint inlen)
{
gchar *p = buffer;
gint len, left = inlen;
do {
len = write (fd, p, left);
if (len == -1) {
if (errno != EINTR)
return -1;
} else {
left -= len;
p += len;
}
} while (left > 0);
return inlen;
}
gchar *gettext (const gchar *msgid);
gchar *
gettext (const gchar *msgid)
{
return NULL;
}
static gint
lock_path (const gchar *path,
guint32 *lockid)
{
struct _lock_info *info = NULL;
gint res = CAMEL_LOCK_HELPER_STATUS_OK;
struct stat st;
d (fprintf (stderr, "locking path '%s' id = %d\n", path, lock_id));
/* check to see if we have it locked already, make the lock 'recursive' */
/* we could also error i suppose, but why bother */
info = lock_info_list;
while (info) {
if (!strcmp (info->path, path)) {
info->depth++;
return CAMEL_LOCK_HELPER_STATUS_OK;
}
info = info->next;
}
/* check we are allowed to lock it, we must own it, be able to write to it, and it has to exist */
if (g_stat (path, &st) == -1
|| st.st_uid != getuid ()
|| !S_ISREG (st.st_mode)
|| (st.st_mode & 0400) == 0) {
return CAMEL_LOCK_HELPER_STATUS_INVALID;
}
info = malloc (sizeof (*info) + strlen (path));
if (info == NULL) {
res = CAMEL_LOCK_HELPER_STATUS_NOMEM;
goto fail;
}
/* we try the real uid first, and if that fails, try the 'root id' */
if (camel_lock_dot (path, NULL) == -1) {
#ifdef SETEUID_SAVES
if (lock_real_uid != lock_root_uid) {
if (seteuid (lock_root_uid) != -1) {
if (camel_lock_dot (path, NULL) == -1) {
if (seteuid (lock_real_uid) == -1) {
g_warn_if_reached ();
}
res = CAMEL_LOCK_HELPER_STATUS_SYSTEM;
goto fail;
}
if (seteuid (lock_real_uid) == -1) {
g_warn_if_reached ();
}
} else {
res = CAMEL_LOCK_HELPER_STATUS_SYSTEM;
goto fail;
}
} else {
res = CAMEL_LOCK_HELPER_STATUS_SYSTEM;
goto fail;
}
#else
res = CAMEL_LOCK_HELPER_STATUS_SYSTEM;
goto fail;
#endif
} else {
info->uid = lock_real_uid;
}
g_strlcpy (info->path, path, strlen (path) + 1);
info->id = lock_id;
info->depth = 1;
info->next = lock_info_list;
info->stamp = time (NULL);
lock_info_list = info;
if (lockid)
*lockid = lock_id;
lock_id++;
d (fprintf (stderr, "lock ok\n"));
return res;
fail:
d (fprintf (stderr, "lock failed\n"));
if (info)
free (info);
return res;
}
static gint
unlock_id (guint32 lockid)
{
struct _lock_info *info, *p;
d (fprintf (stderr, "unlocking id '%d'\n", lockid));
p = (struct _lock_info *) &lock_info_list;
info = p->next;
while (info) {
if (info->id == lockid) {
d (fprintf (stderr, "found id %d path '%s'\n", lockid, info->path));
info->depth--;
if (info->depth <= 0) {
#ifdef SETEUID_SAVES
if (info->uid != lock_real_uid) {
if (seteuid (lock_root_uid) == -1) {
g_warn_if_reached ();
}
camel_unlock_dot (info->path);
if (seteuid (lock_real_uid) == -1) {
g_warn_if_reached ();
}
} else
#endif
camel_unlock_dot (info->path);
p->next = info->next;
free (info);
}
return CAMEL_LOCK_HELPER_STATUS_OK;
}
p = info;
info = info->next;
}
d (fprintf (stderr, "unknown id asked to be unlocked %d\n", lockid));
return CAMEL_LOCK_HELPER_STATUS_PROTOCOL;
}
static void
lock_touch (const gchar *path)
{
gchar *name;
gsize name_len;
/* we could also check that we haven't had our lock stolen from us here */
name_len = strlen (path) + 10;
name = alloca (name_len);
g_snprintf (name, name_len, "%s.lock", path);
d (fprintf (stderr, "Updating lock %s\n", name));
utime (name, NULL);
}
static void
setup_process (void)
{
struct sigaction sa;
sigset_t sigset;
/* ignore sigint/sigio */
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigemptyset (&sigset);
sigaddset (&sigset, SIGIO);
sigaddset (&sigset, SIGINT);
sigprocmask (SIG_UNBLOCK, &sigset, NULL);
sigaction (SIGIO, &sa, NULL);
sigaction (SIGINT, &sa, NULL);
/* FIXME: add more sanity checks/setup here */
#ifdef SETEUID_SAVES
/* here we change to the real user id, this is probably not particularly
* portable so may need configure checks */
lock_real_uid = getuid ();
lock_root_uid = geteuid ();
if (lock_real_uid != lock_root_uid) {
if (seteuid (lock_real_uid) == -1) {
g_warn_if_reached ();
}
}
#endif
}
gint
main (gint argc,
gchar **argv)
{
struct _CamelLockHelperMsg msg;
gint len;
gint res;
gchar *path;
fd_set rset;
struct timeval tv;
struct _lock_info *info;
setup_process ();
do {
/* do a poll/etc, so we can refresh the .locks as required ... */
FD_ZERO (&rset);
FD_SET (STDIN_FILENO, &rset);
/* check the minimum timeout we need to refresh the next oldest lock */
if (lock_info_list) {
time_t now = time (NULL);
time_t left;
time_t delay = CAMEL_DOT_LOCK_REFRESH;
info = lock_info_list;
while (info) {
left = CAMEL_DOT_LOCK_REFRESH - (now - info->stamp);
left = MAX (left, 0);
delay = MIN (left, delay);
info = info->next;
}
tv.tv_sec = delay;
tv.tv_usec = 0;
}
d (fprintf (stderr, "lock helper waiting for input\n"));
if (select (STDIN_FILENO + 1, &rset, NULL, NULL, lock_info_list ? &tv : NULL) == -1) {
if (errno == EINTR)
break;
continue;
}
/* did we get a timeout? scan for any locks that need updating */
if (!FD_ISSET (STDIN_FILENO, &rset)) {
time_t now = time (NULL);
time_t left;
d (fprintf (stderr, "Got a timeout, checking locks\n"));
info = lock_info_list;
while (info) {
left = (now - info->stamp);
if (left >= CAMEL_DOT_LOCK_REFRESH) {
lock_touch (info->path);
info->stamp = now;
}
info = info->next;
}
continue;
}
len = read_n (STDIN_FILENO, &msg, sizeof (msg));
if (len == 0)
break;
res = CAMEL_LOCK_HELPER_STATUS_PROTOCOL;
if (len == sizeof (msg) && msg.magic == CAMEL_LOCK_HELPER_MAGIC) {
switch (msg.id) {
case CAMEL_LOCK_HELPER_LOCK:
res = CAMEL_LOCK_HELPER_STATUS_NOMEM;
if (msg.data > 0xffff) {
res = CAMEL_LOCK_HELPER_STATUS_PROTOCOL;
} else if ((path = malloc (msg.data + 1)) != NULL) {
res = CAMEL_LOCK_HELPER_STATUS_PROTOCOL;
len = read_n (STDIN_FILENO, path, msg.data);
if (len == msg.data) {
path[len] = 0;
res = lock_path (path, &msg.data);
}
free (path);
}
break;
case CAMEL_LOCK_HELPER_UNLOCK:
res = unlock_id (msg.data);
break;
}
}
d (fprintf (stderr, "returning result %d\n", res));
msg.id = res;
msg.magic = CAMEL_LOCK_HELPER_RETURN_MAGIC;
write_n (STDOUT_FILENO, &msg, sizeof (msg));
} while (1);
d (fprintf (stderr, "parent exited, clsoing down remaining id's\n"));
while (lock_info_list)
unlock_id (lock_info_list->id);
return 0;
}