/* * 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 #if HAVE_SYS_PARAM_H #include #endif #include #include #include #include #include #include #include #include #include #include #include "maillock.h" #include "lockfile.h" #ifdef HAVE_GETOPT_H #include #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; }