391 lines
8.1 KiB
C
391 lines
8.1 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
|
*
|
|
* Copyright (C) 2001 Ximian Inc.
|
|
*
|
|
* Authors: Michael Zucchi <notzed@ximian.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of version 2 of the GNU General Public
|
|
* License as published by the Free Software Foundation.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
|
* USA
|
|
*/
|
|
|
|
/* lock helper process */
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <signal.h>
|
|
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <utime.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <string.h>
|
|
|
|
#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;
|
|
int id;
|
|
int depth;
|
|
time_t stamp; /* when last updated */
|
|
char path[1];
|
|
};
|
|
|
|
static int 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 int read_n(int fd, void *buffer, int inlen)
|
|
{
|
|
char *p = buffer;
|
|
int 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 int write_n(int fd, void *buffer, int inlen)
|
|
{
|
|
char *p = buffer;
|
|
int 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;
|
|
}
|
|
|
|
void
|
|
camel_exception_setv (CamelException *ex, ExceptionId id, const char *format, ...)
|
|
{
|
|
;
|
|
}
|
|
|
|
void
|
|
camel_exception_clear (CamelException *exception)
|
|
{
|
|
;
|
|
}
|
|
|
|
char *gettext (const char *msgid);
|
|
|
|
char *
|
|
gettext (const char *msgid)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static int lock_path(const char *path, guint32 *lockid)
|
|
{
|
|
struct _lock_info *info = NULL;
|
|
int 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 (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) {
|
|
seteuid(lock_real_uid);
|
|
res = CAMEL_LOCK_HELPER_STATUS_SYSTEM;
|
|
goto fail;
|
|
}
|
|
seteuid(lock_real_uid);
|
|
} 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;
|
|
}
|
|
|
|
strcpy(info->path, path);
|
|
info->id = lock_id;
|
|
info->depth = 1;
|
|
info->next = lock_info_list;
|
|
info->stamp = time(0);
|
|
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 int 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) {
|
|
seteuid(lock_root_uid);
|
|
camel_unlock_dot(info->path);
|
|
seteuid(lock_real_uid);
|
|
} 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 char *path)
|
|
{
|
|
char *name;
|
|
|
|
/* we could also check that we haven't had our lock stolen from us here */
|
|
|
|
name = alloca(strlen(path) + 10);
|
|
sprintf(name, "%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)
|
|
seteuid(lock_real_uid);
|
|
#endif
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
struct _CamelLockHelperMsg msg;
|
|
int len;
|
|
int res;
|
|
char *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(0);
|
|
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(0);
|
|
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;
|
|
path = malloc(msg.data+1);
|
|
if (path != 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;
|
|
}
|