office-gobmx/solenv/lockfile/dotlockfile.c

458 lines
8.5 KiB
C
Raw Normal View History

/*
* dotlockfile.c Command line version of liblockfile.
* Runs setgid mail so is able to lock mailboxes
* as well. Liblockfile can call this command.
*
* Copyright (C) Miquel van Smoorenburg and contributors 1999-2021
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*/
#include "autoconf.h"
#include <sys/types.h>
#if HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#include <sys/wait.h>
#include <stdio.h>
#include <string.h>
#include <pwd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include "maillock.h"
#include "lockfile.h"
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
#ifndef HAVE_GETOPT_H
extern int getopt();
extern char *optarg;
extern int optind;
#endif
static volatile char *tmplock;
static int quiet;
/*
* If we got SIGINT, SIGQUIT, SIGHUP, remove the
* tempfile and re-raise the signal.
*/
static void got_signal(int sig)
{
if (tmplock && tmplock[0])
unlink((char *)tmplock);
signal(sig, SIG_DFL);
raise(sig);
}
static void ignore_signal(int sig)
{
(void)sig;
}
/*
* Install signal handler only if the signal was
* not ignored already.
*/
static int set_signal(int sig, void (*handler)(int))
{
struct sigaction sa;
if (sigaction(sig, NULL, &sa) < 0)
return -1;
if (sa.sa_handler == SIG_IGN)
return 0;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handler;
return sigaction(sig, &sa, NULL);
}
/*
* Sleep for an amount of time while regularly checking if
* our parent is still alive.
*/
int check_sleep(int sleeptime, int flags)
{
int i;
int interval = 5;
static int ppid = 0;
if (ppid == 0) ppid = getppid();
if (flags & L_INTERVAL_D_)
interval = 1;
for (i = 0; i < sleeptime; i += interval) {
sleep(interval);
if (kill(ppid, 0) < 0 && errno == ESRCH)
return L_ERROR;
}
return 0;
}
/*
* Split a filename up in file and directory.
*/
#ifdef MAILGROUP
static int fn_split(char *fn, char **fn_p, char **dir_p)
{
static char *buf = NULL;
char *p;
if (buf)
free (buf);
buf = (char *) malloc (strlen (fn) + 1);
if (! buf)
return L_ERROR;
strcpy(buf, fn);
if ((p = strrchr(buf, '/')) != NULL) {
*p++ = 0;
*fn_p = p;
*dir_p = buf;
} else {
*fn_p = fn;
*dir_p = ".";
}
return L_SUCCESS;
}
#endif
/*
* Return name of lockfile for mail.
*/
static char *mlockname(char *user)
{
static char *buf = NULL;
char *e;
if (buf)
free(buf);
e = getenv("MAIL");
if (e) {
buf = (char *)malloc(strlen(e)+6);
if (!buf)
return NULL;
sprintf(buf, "%s.lock", e);
} else {
buf = (char *)malloc(strlen(MAILDIR)+strlen(user)+6);
if (!buf)
return NULL;
sprintf(buf, "%s%s.lock", MAILDIR, user);
}
return buf;
}
static void perror_exit(const char *why)
{
if (!quiet) {
fprintf(stderr, "dotlockfile: ");
perror(why);
}
exit(L_ERROR);
}
/*
* Print usage message and exit.
*/
static void usage(void)
{
fprintf(stderr, "Usage: dotlockfile -l [-r retries] [-i interval] [-p] [-q] <-m|lockfile>\n");
fprintf(stderr, " dotlockfile -l [-r retries] [-i interval] [-p] [-q] <-m|lockfile> [-P] command args...\n");
fprintf(stderr, " dotlockfile -u|-t\n");
exit(1);
}
int main(int argc, char **argv)
{
struct passwd *pwd;
struct lockargs_s_ args = { 0 };
gid_t gid, egid;
char *lockfile = NULL;
char **cmd = NULL;
int c, r;
int retries = 5;
int interval = 0;
int flags = 0;
int lock = 0;
int unlock = 0;
int check = 0;
int touch = 0;
int writepid = 0;
int passthrough = 0;
int cwd_fd = -1;
int need_privs = 0;
pid_t pid = -1;
int e, wstatus;
/*
* Remember real and effective gid, and
* drop privs for now.
*/
gid = getgid();
egid = getegid();
if (gid != egid) {
if (setregid(-1, gid) < 0)
perror_exit("setregid(-1, gid)");
}
set_signal(SIGINT, got_signal);
set_signal(SIGQUIT, got_signal);
set_signal(SIGHUP, got_signal);
set_signal(SIGTERM, got_signal);
set_signal(SIGPIPE, got_signal);
/*
* Process the options.
*/
while ((c = getopt(argc, argv, "+qpNr:mluci:tP")) != EOF) switch(c) {
case 'q':
quiet = 1;
break;
case 'p':
writepid = 1;
break;
case 'N':
/* NOP */
break;
case 'r':
retries = atoi(optarg);
if (retries <= 0 &&
retries != -1 && strcmp(optarg, "0") != 0) {
if (!quiet)
fprintf(stderr, "dotlockfile: "
"-r %s: invalid argument\n",
optarg);
return L_ERROR;
}
if (retries == -1) {
/* 4000 years */
retries = 0x7ffffff0;
}
break;
case 'm':
if ((pwd = getpwuid(geteuid())) == NULL) {
if (!quiet)
fprintf(stderr, "dotlockfile: You don't exist. Go away.\n");
return L_ERROR;
}
lockfile = mlockname(pwd->pw_name);
if (!lockfile) {
if (!quiet)
perror("dotlockfile");
return L_ERROR;
}
break;
case 'l':
lock = 1;
break;
case 'u':
unlock = 1;
break;
case 'c':
check = 1;
break;
case 'i':
interval = atoi(optarg);
if (interval <= 0 && strcmp(optarg, "0") != 0) {
fprintf(stderr, "dotlockfile: -i needs argument >= 0\n");
return L_ERROR;
}
flags |= L_INTERVAL_D_;
args.interval = interval;
break;
case 't':
touch = 1;
break;
case 'P':
passthrough = 1;
break;
default:
usage();
break;
}
/*
* next argument may be lockfile name
*/
if (!lockfile) {
if (optind == argc)
usage();
lockfile = argv[optind++];
}
/*
* next arguments may be command [args...]
*/
if (optind < argc)
cmd = argv + optind;
/*
* Options sanity check
*/
if ((cmd || lock) && (touch || check || unlock))
usage();
if (writepid)
flags |= (cmd ? L_PID : L_PPID);
#ifdef MAXPATHLEN
if (strlen(lockfile) >= MAXPATHLEN) {
if (!quiet)
fprintf(stderr, "dotlockfile: %s: name too long\n", lockfile);
return L_NAMELEN;
}
#endif
/*
* Check if we run setgid.
*/
#ifdef MAILGROUP
if (gid != egid) {
/*
* See if the requested lock is for a mailbox.
* First, remember current working directory.
*/
#ifdef O_PATH
cwd_fd = open(".", O_PATH|O_CLOEXEC);
#else
cwd_fd = open(".", O_RDONLY|O_CLOEXEC);
#endif
if (cwd_fd < 0) {
if (!quiet)
fprintf(stderr, "dotlockfile: opening \".\": %s\n",
strerror(errno));
return L_ERROR;
}
/*
* Now change directory to the directory the lockfile is in.
*/
char *file, *dir;
r = fn_split(lockfile, &file, &dir);
if (r != L_SUCCESS) {
if (!quiet)
perror("dotlockfile");
return L_ERROR;
}
if (chdir(dir) != 0) {
if (!quiet)
fprintf(stderr, "dotlockfile: %s: %s\n", dir, strerror(errno));
return L_ERROR;
}
lockfile = file;
need_privs = is_maillock(lockfile);
}
#endif
/*
* See if we actually need to run setgid.
*/
if (need_privs) {
if (setregid(gid, egid) != 0)
perror_exit("setregid");
} else {
if (gid != egid && setgid(gid) != 0)
perror_exit("setgid");
}
/*
* Simple check for a valid lockfile ?
*/
if (check)
return (lockfile_check(lockfile, flags) < 0) ? 1 : 0;
/*
* Touch lock ?
*/
if (touch)
return (lockfile_touch(lockfile) < 0) ? 1 : 0;
/*
* Remove lockfile?
*/
if (unlock)
return (lockfile_remove(lockfile) == 0) ? 0 : 1;
/*
* No, lock.
*/
r = lockfile_create_set_tmplock(lockfile, &tmplock, retries, flags, &args);
if (r != 0 || !cmd)
return r;
/*
* Spawn command.
*
* Using an empty signal handler means that we ignore the
* signal, but that it's restored to SIG_DFL at execve().
*/
set_signal(SIGINT, ignore_signal);
set_signal(SIGQUIT, ignore_signal);
set_signal(SIGHUP, ignore_signal);
set_signal(SIGALRM, ignore_signal);
pid = fork();
if (pid < 0) {
if (!quiet)
perror("fork");
lockfile_remove(lockfile);
exit(L_ERROR);
}
if (pid == 0) {
/* drop setgid */
if (gid != egid && setgid(gid) < 0) {
perror("setgid");
exit(127);
}
/* restore current working directory */
if (cwd_fd >= 0) {
if (fchdir(cwd_fd) < 0) {
perror("dotlockfile: restoring cwd:");
exit(127);
}
close(cwd_fd);
}
/* exec */
execvp(cmd[0], cmd);
perror(cmd[0]);
exit(127);
}
/* wait for child */
while (1) {
if (!writepid)
alarm(30);
e = waitpid(pid, &wstatus, 0);
if (e >= 0 || errno != EINTR)
break;
if (!writepid)
lockfile_touch(lockfile);
}
alarm(0);
lockfile_remove(lockfile);
if (passthrough) {
if (WIFEXITED(wstatus))
return WEXITSTATUS(wstatus);
if (WIFSIGNALED(wstatus))
return 128+WTERMSIG(wstatus);
}
return 0;
}