summaryrefslogtreecommitdiffstats
path: root/solenv/lockfile/dotlockfile.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--solenv/lockfile/dotlockfile.c457
1 files changed, 457 insertions, 0 deletions
diff --git a/solenv/lockfile/dotlockfile.c b/solenv/lockfile/dotlockfile.c
new file mode 100644
index 000000000..a25cdc550
--- /dev/null
+++ b/solenv/lockfile/dotlockfile.c
@@ -0,0 +1,457 @@
+/*
+ * 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;
+}
+