Update git submodules
[LibreOffice.git] / solenv / lockfile / dotlockfile.c
bloba25cdc55001702706d59cbe1a244d256cab72a11
1 /*
2 * dotlockfile.c Command line version of liblockfile.
3 * Runs setgid mail so is able to lock mailboxes
4 * as well. Liblockfile can call this command.
6 * Copyright (C) Miquel van Smoorenburg and contributors 1999-2021
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
14 #include "autoconf.h"
16 #include <sys/types.h>
17 #if HAVE_SYS_PARAM_H
18 #include <sys/param.h>
19 #endif
20 #include <sys/wait.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <pwd.h>
24 #include <fcntl.h>
25 #include <stdlib.h>
26 #include <unistd.h>
27 #include <signal.h>
28 #include <time.h>
29 #include <errno.h>
30 #include "maillock.h"
31 #include "lockfile.h"
33 #ifdef HAVE_GETOPT_H
34 #include <getopt.h>
35 #endif
37 #ifndef HAVE_GETOPT_H
38 extern int getopt();
39 extern char *optarg;
40 extern int optind;
41 #endif
43 static volatile char *tmplock;
44 static int quiet;
47 * If we got SIGINT, SIGQUIT, SIGHUP, remove the
48 * tempfile and re-raise the signal.
50 static void got_signal(int sig)
52 if (tmplock && tmplock[0])
53 unlink((char *)tmplock);
54 signal(sig, SIG_DFL);
55 raise(sig);
58 static void ignore_signal(int sig)
60 (void)sig;
64 * Install signal handler only if the signal was
65 * not ignored already.
67 static int set_signal(int sig, void (*handler)(int))
69 struct sigaction sa;
71 if (sigaction(sig, NULL, &sa) < 0)
72 return -1;
73 if (sa.sa_handler == SIG_IGN)
74 return 0;
75 memset(&sa, 0, sizeof(sa));
76 sa.sa_handler = handler;
77 return sigaction(sig, &sa, NULL);
81 * Sleep for an amount of time while regularly checking if
82 * our parent is still alive.
84 int check_sleep(int sleeptime, int flags)
86 int i;
87 int interval = 5;
88 static int ppid = 0;
90 if (ppid == 0) ppid = getppid();
92 if (flags & L_INTERVAL_D_)
93 interval = 1;
95 for (i = 0; i < sleeptime; i += interval) {
96 sleep(interval);
97 if (kill(ppid, 0) < 0 && errno == ESRCH)
98 return L_ERROR;
100 return 0;
104 * Split a filename up in file and directory.
106 #ifdef MAILGROUP
107 static int fn_split(char *fn, char **fn_p, char **dir_p)
109 static char *buf = NULL;
110 char *p;
112 if (buf)
113 free (buf);
114 buf = (char *) malloc (strlen (fn) + 1);
115 if (! buf)
116 return L_ERROR;
117 strcpy(buf, fn);
118 if ((p = strrchr(buf, '/')) != NULL) {
119 *p++ = 0;
120 *fn_p = p;
121 *dir_p = buf;
122 } else {
123 *fn_p = fn;
124 *dir_p = ".";
126 return L_SUCCESS;
128 #endif
131 * Return name of lockfile for mail.
133 static char *mlockname(char *user)
135 static char *buf = NULL;
136 char *e;
138 if (buf)
139 free(buf);
141 e = getenv("MAIL");
142 if (e) {
143 buf = (char *)malloc(strlen(e)+6);
144 if (!buf)
145 return NULL;
146 sprintf(buf, "%s.lock", e);
147 } else {
148 buf = (char *)malloc(strlen(MAILDIR)+strlen(user)+6);
149 if (!buf)
150 return NULL;
151 sprintf(buf, "%s%s.lock", MAILDIR, user);
153 return buf;
156 static void perror_exit(const char *why)
158 if (!quiet) {
159 fprintf(stderr, "dotlockfile: ");
160 perror(why);
162 exit(L_ERROR);
166 * Print usage message and exit.
168 static void usage(void)
170 fprintf(stderr, "Usage: dotlockfile -l [-r retries] [-i interval] [-p] [-q] <-m|lockfile>\n");
171 fprintf(stderr, " dotlockfile -l [-r retries] [-i interval] [-p] [-q] <-m|lockfile> [-P] command args...\n");
172 fprintf(stderr, " dotlockfile -u|-t\n");
173 exit(1);
176 int main(int argc, char **argv)
178 struct passwd *pwd;
179 struct lockargs_s_ args = { 0 };
180 gid_t gid, egid;
181 char *lockfile = NULL;
182 char **cmd = NULL;
183 int c, r;
184 int retries = 5;
185 int interval = 0;
186 int flags = 0;
187 int lock = 0;
188 int unlock = 0;
189 int check = 0;
190 int touch = 0;
191 int writepid = 0;
192 int passthrough = 0;
193 int cwd_fd = -1;
194 int need_privs = 0;
195 pid_t pid = -1;
196 int e, wstatus;
199 * Remember real and effective gid, and
200 * drop privs for now.
202 gid = getgid();
203 egid = getegid();
204 if (gid != egid) {
205 if (setregid(-1, gid) < 0)
206 perror_exit("setregid(-1, gid)");
209 set_signal(SIGINT, got_signal);
210 set_signal(SIGQUIT, got_signal);
211 set_signal(SIGHUP, got_signal);
212 set_signal(SIGTERM, got_signal);
213 set_signal(SIGPIPE, got_signal);
216 * Process the options.
218 while ((c = getopt(argc, argv, "+qpNr:mluci:tP")) != EOF) switch(c) {
219 case 'q':
220 quiet = 1;
221 break;
222 case 'p':
223 writepid = 1;
224 break;
225 case 'N':
226 /* NOP */
227 break;
228 case 'r':
229 retries = atoi(optarg);
230 if (retries <= 0 &&
231 retries != -1 && strcmp(optarg, "0") != 0) {
232 if (!quiet)
233 fprintf(stderr, "dotlockfile: "
234 "-r %s: invalid argument\n",
235 optarg);
236 return L_ERROR;
238 if (retries == -1) {
239 /* 4000 years */
240 retries = 0x7ffffff0;
242 break;
243 case 'm':
244 if ((pwd = getpwuid(geteuid())) == NULL) {
245 if (!quiet)
246 fprintf(stderr, "dotlockfile: You don't exist. Go away.\n");
247 return L_ERROR;
249 lockfile = mlockname(pwd->pw_name);
250 if (!lockfile) {
251 if (!quiet)
252 perror("dotlockfile");
253 return L_ERROR;
255 break;
256 case 'l':
257 lock = 1;
258 break;
259 case 'u':
260 unlock = 1;
261 break;
262 case 'c':
263 check = 1;
264 break;
265 case 'i':
266 interval = atoi(optarg);
267 if (interval <= 0 && strcmp(optarg, "0") != 0) {
268 fprintf(stderr, "dotlockfile: -i needs argument >= 0\n");
269 return L_ERROR;
271 flags |= L_INTERVAL_D_;
272 args.interval = interval;
273 break;
274 case 't':
275 touch = 1;
276 break;
277 case 'P':
278 passthrough = 1;
279 break;
280 default:
281 usage();
282 break;
286 * next argument may be lockfile name
288 if (!lockfile) {
289 if (optind == argc)
290 usage();
291 lockfile = argv[optind++];
295 * next arguments may be command [args...]
297 if (optind < argc)
298 cmd = argv + optind;
301 * Options sanity check
303 if ((cmd || lock) && (touch || check || unlock))
304 usage();
306 if (writepid)
307 flags |= (cmd ? L_PID : L_PPID);
309 #ifdef MAXPATHLEN
310 if (strlen(lockfile) >= MAXPATHLEN) {
311 if (!quiet)
312 fprintf(stderr, "dotlockfile: %s: name too long\n", lockfile);
313 return L_NAMELEN;
315 #endif
318 * Check if we run setgid.
320 #ifdef MAILGROUP
321 if (gid != egid) {
323 * See if the requested lock is for a mailbox.
324 * First, remember current working directory.
326 #ifdef O_PATH
327 cwd_fd = open(".", O_PATH|O_CLOEXEC);
328 #else
329 cwd_fd = open(".", O_RDONLY|O_CLOEXEC);
330 #endif
331 if (cwd_fd < 0) {
332 if (!quiet)
333 fprintf(stderr, "dotlockfile: opening \".\": %s\n",
334 strerror(errno));
335 return L_ERROR;
338 * Now change directory to the directory the lockfile is in.
340 char *file, *dir;
341 r = fn_split(lockfile, &file, &dir);
342 if (r != L_SUCCESS) {
343 if (!quiet)
344 perror("dotlockfile");
345 return L_ERROR;
347 if (chdir(dir) != 0) {
348 if (!quiet)
349 fprintf(stderr, "dotlockfile: %s: %s\n", dir, strerror(errno));
350 return L_ERROR;
353 lockfile = file;
354 need_privs = is_maillock(lockfile);
356 #endif
359 * See if we actually need to run setgid.
361 if (need_privs) {
362 if (setregid(gid, egid) != 0)
363 perror_exit("setregid");
364 } else {
365 if (gid != egid && setgid(gid) != 0)
366 perror_exit("setgid");
370 * Simple check for a valid lockfile ?
372 if (check)
373 return (lockfile_check(lockfile, flags) < 0) ? 1 : 0;
377 * Touch lock ?
379 if (touch)
380 return (lockfile_touch(lockfile) < 0) ? 1 : 0;
383 * Remove lockfile?
385 if (unlock)
386 return (lockfile_remove(lockfile) == 0) ? 0 : 1;
390 * No, lock.
392 r = lockfile_create_set_tmplock(lockfile, &tmplock, retries, flags, &args);
393 if (r != 0 || !cmd)
394 return r;
398 * Spawn command.
400 * Using an empty signal handler means that we ignore the
401 * signal, but that it's restored to SIG_DFL at execve().
403 set_signal(SIGINT, ignore_signal);
404 set_signal(SIGQUIT, ignore_signal);
405 set_signal(SIGHUP, ignore_signal);
406 set_signal(SIGALRM, ignore_signal);
408 pid = fork();
409 if (pid < 0) {
410 if (!quiet)
411 perror("fork");
412 lockfile_remove(lockfile);
413 exit(L_ERROR);
415 if (pid == 0) {
416 /* drop setgid */
417 if (gid != egid && setgid(gid) < 0) {
418 perror("setgid");
419 exit(127);
421 /* restore current working directory */
422 if (cwd_fd >= 0) {
423 if (fchdir(cwd_fd) < 0) {
424 perror("dotlockfile: restoring cwd:");
425 exit(127);
427 close(cwd_fd);
429 /* exec */
430 execvp(cmd[0], cmd);
431 perror(cmd[0]);
432 exit(127);
435 /* wait for child */
436 while (1) {
437 if (!writepid)
438 alarm(30);
439 e = waitpid(pid, &wstatus, 0);
440 if (e >= 0 || errno != EINTR)
441 break;
442 if (!writepid)
443 lockfile_touch(lockfile);
446 alarm(0);
447 lockfile_remove(lockfile);
449 if (passthrough) {
450 if (WIFEXITED(wstatus))
451 return WEXITSTATUS(wstatus);
452 if (WIFSIGNALED(wstatus))
453 return 128+WTERMSIG(wstatus);
455 return 0;