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.
5 *
6 * Copyright (C) Miquel van Smoorenburg and contributors 1999-2021
7 *
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.
12 */
13
14 #include "autoconf.h"
15
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>
32
33 #ifdef HAVE_GETOPT_H
34 #include <getopt.h>
35 #endif
36
37 #ifndef HAVE_GETOPT_H
38 extern int getopt();
39 extern char *optarg;
40 extern int optind;
41 #endif
42
43 extern int is_maillock(const char *lockfile);
44 extern int lockfile_create_set_tmplock(const char *lockfile,
45 volatile char **tmplock, int retries, int flags, struct __lockargs *);
46
47 static volatile char *tmplock;
48 static int quiet;
49
50 /*
51 * If we got SIGINT, SIGQUIT, SIGHUP, remove the
52 * tempfile and re-raise the signal.
53 */
got_signal(int sig)54 void got_signal(int sig)
55 {
56 if (tmplock && tmplock[0])
57 unlink((char *)tmplock);
58 signal(sig, SIG_DFL);
59 raise(sig);
60 }
61
ignore_signal(int sig)62 void ignore_signal(int sig)
63 {
64 }
65
66 /*
67 * Install signal handler only if the signal was
68 * not ignored already.
69 */
set_signal(int sig,void (* handler)(int))70 int set_signal(int sig, void (*handler)(int))
71 {
72 struct sigaction sa;
73
74 if (sigaction(sig, NULL, &sa) < 0)
75 return -1;
76 if (sa.sa_handler == SIG_IGN)
77 return 0;
78 memset(&sa, 0, sizeof(sa));
79 sa.sa_handler = handler;
80 return sigaction(sig, &sa, NULL);
81 }
82
83 /*
84 * Sleep for an amount of time while regulary checking if
85 * our parent is still alive.
86 */
check_sleep(int sleeptime,int flags)87 int check_sleep(int sleeptime, int flags)
88 {
89 int i;
90 int interval = 5;
91 static int ppid = 0;
92
93 if (ppid == 0) ppid = getppid();
94
95 if (flags & __L_INTERVAL)
96 interval = 1;
97
98 for (i = 0; i < sleeptime; i += interval) {
99 sleep(interval);
100 if (kill(ppid, 0) < 0 && errno == ESRCH)
101 return L_ERROR;
102 }
103 return 0;
104 }
105
106 /*
107 * Split a filename up in file and directory.
108 */
fn_split(char * fn,char ** fn_p,char ** dir_p)109 int fn_split(char *fn, char **fn_p, char **dir_p)
110 {
111 static char *buf = NULL;
112 char *p;
113
114 if (buf)
115 free (buf);
116 buf = (char *) malloc (strlen (fn) + 1);
117 if (! buf)
118 return L_ERROR;
119 strcpy(buf, fn);
120 if ((p = strrchr(buf, '/')) != NULL) {
121 *p++ = 0;
122 *fn_p = p;
123 *dir_p = buf;
124 } else {
125 *fn_p = fn;
126 *dir_p = ".";
127 }
128 return L_SUCCESS;
129 }
130
131
132 /*
133 * Return name of lockfile for mail.
134 */
mlockname(char * user)135 char *mlockname(char *user)
136 {
137 static char *buf = NULL;
138 char *e;
139
140 if (buf)
141 free(buf);
142
143 e = getenv("MAIL");
144 if (e) {
145 buf = (char *)malloc(strlen(e)+6);
146 if (!buf)
147 return NULL;
148 sprintf(buf, "%s.lock", e);
149 } else {
150 buf = (char *)malloc(strlen(MAILDIR)+strlen(user)+6);
151 if (!buf)
152 return NULL;
153 sprintf(buf, "%s%s.lock", MAILDIR, user);
154 }
155 return buf;
156 }
157
perror_exit(const char * why)158 void perror_exit(const char *why) {
159 if (!quiet) {
160 fprintf(stderr, "dotlockfile: ");
161 perror(why);
162 }
163 exit(L_ERROR);
164 }
165
166 /*
167 * Print usage mesage and exit.
168 */
usage(void)169 void usage(void)
170 {
171 fprintf(stderr, "Usage: dotlockfile -l [-r retries] [-i interval] [-p] [-q] <-m|lockfile>\n");
172 fprintf(stderr, " dotlockfile -l [-r retries] [-i interval] [-p] [-q] <-m|lockfile> [-P] command args...\n");
173 fprintf(stderr, " dotlockfile -u|-t\n");
174 exit(1);
175 }
176
main(int argc,char ** argv)177 int main(int argc, char **argv)
178 {
179 struct passwd *pwd;
180 struct __lockargs args = { 0 };
181 gid_t gid, egid;
182 char *lockfile = NULL;
183 char **cmd = NULL;
184 int c, r;
185 int retries = 5;
186 int interval = 0;
187 int flags = 0;
188 int lock = 0;
189 int unlock = 0;
190 int check = 0;
191 int touch = 0;
192 int writepid = 0;
193 int passthrough = 0;
194
195 /*
196 * Remember real and effective gid, and
197 * drop privs for now.
198 */
199 if ((gid = getgid()) < 0)
200 perror_exit("getgid");
201 if ((egid = getegid()) < 0)
202 perror_exit("getegid");
203 if (gid != egid) {
204 if (setregid(-1, gid) < 0)
205 perror_exit("setregid(-1, gid)");
206 }
207
208 set_signal(SIGINT, got_signal);
209 set_signal(SIGQUIT, got_signal);
210 set_signal(SIGHUP, got_signal);
211 set_signal(SIGTERM, got_signal);
212 set_signal(SIGPIPE, got_signal);
213
214 /*
215 * Process the options.
216 */
217 while ((c = getopt(argc, argv, "+qpNr:mluci:tP")) != EOF) switch(c) {
218 case 'q':
219 quiet = 1;
220 break;
221 case 'p':
222 writepid = 1;
223 break;
224 case 'N':
225 /* NOP */
226 break;
227 case 'r':
228 retries = atoi(optarg);
229 if (retries <= 0 &&
230 retries != -1 && strcmp(optarg, "0") != 0) {
231 if (!quiet)
232 fprintf(stderr, "dotlockfile: "
233 "-r %s: invalid argument\n",
234 optarg);
235 return L_ERROR;
236 }
237 if (retries == -1) {
238 /* 4000 years */
239 retries = 0x7ffffff0;
240 }
241 break;
242 case 'm':
243 if ((pwd = getpwuid(geteuid())) == NULL) {
244 if (!quiet)
245 fprintf(stderr, "dotlockfile: You don't exist. Go away.\n");
246 return L_ERROR;
247 }
248 lockfile = mlockname(pwd->pw_name);
249 if (!lockfile) {
250 if (!quiet)
251 perror("dotlockfile");
252 return L_ERROR;
253 }
254 break;
255 case 'l':
256 lock = 1;
257 break;
258 case 'u':
259 unlock = 1;
260 break;
261 case 'c':
262 check = 1;
263 break;
264 case 'i':
265 interval = atoi(optarg);
266 if (interval <= 0 && strcmp(optarg, "0") != 0) {
267 fprintf(stderr, "dotlockfile: -i needs argument >= 0\n");
268 return L_ERROR;
269 }
270 flags |= __L_INTERVAL;
271 args.interval = interval;
272 break;
273 case 't':
274 touch = 1;
275 break;
276 case 'P':
277 passthrough = 1;
278 break;
279 default:
280 usage();
281 break;
282 }
283
284 /*
285 * next argument may be lockfile name
286 */
287 if (!lockfile) {
288 if (optind == argc)
289 usage();
290 lockfile = argv[optind++];
291 }
292
293 /*
294 * next arguments may be command [args...]
295 */
296 if (optind < argc)
297 cmd = argv + optind;
298
299 /*
300 * Options sanity check
301 */
302 if ((cmd || lock) && (touch || check || unlock))
303 usage();
304
305 if (writepid)
306 flags |= (cmd ? L_PID : L_PPID);
307
308 #ifdef MAXPATHLEN
309 if (strlen(lockfile) >= MAXPATHLEN) {
310 if (!quiet)
311 fprintf(stderr, "dotlockfile: %s: name too long\n", lockfile);
312 return L_NAMELEN;
313 }
314 #endif
315
316 /*
317 * Check if we run setgid.
318 */
319 int cwd_fd = -1;
320 int need_privs = 0;
321 #ifdef MAILGROUP
322 if (gid != egid) {
323 /*
324 * See if the requested lock is for a mailbox.
325 * First, remember currect working directory.
326 */
327 #ifdef O_PATH
328 cwd_fd = open(".", O_PATH|O_CLOEXEC);
329 #else
330 cwd_fd = open(".", O_RDONLY|O_CLOEXEC);
331 #endif
332 if (cwd_fd < 0) {
333 if (!quiet)
334 fprintf(stderr, "dotlockfile: opening \".\": %s\n",
335 strerror(errno));
336 return L_ERROR;
337 }
338 /*
339 * Now change directory to the directory the lockfile is in.
340 */
341 char *file, *dir;
342 r = fn_split(lockfile, &file, &dir);
343 if (r != L_SUCCESS) {
344 if (!quiet)
345 perror("dotlockfile");
346 return L_ERROR;
347 }
348 if (chdir(dir) != 0) {
349 if (!quiet)
350 fprintf(stderr, "dotlockfile: %s: %s\n", dir, strerror(errno));
351 return L_ERROR;
352 }
353
354 lockfile = file;
355 need_privs = is_maillock(lockfile);
356 }
357 #endif
358
359 /*
360 * See if we actually need to run setgid.
361 */
362 if (need_privs) {
363 if (setregid(gid, egid) != 0)
364 perror_exit("setregid");
365 } else {
366 if (gid != egid && setgid(gid) != 0)
367 perror_exit("setgid");
368 }
369
370 /*
371 * Simple check for a valid lockfile ?
372 */
373 if (check)
374 return (lockfile_check(lockfile, flags) < 0) ? 1 : 0;
375
376
377 /*
378 * Touch lock ?
379 */
380 if (touch)
381 return (lockfile_touch(lockfile) < 0) ? 1 : 0;
382
383 /*
384 * Remove lockfile?
385 */
386 if (unlock)
387 return (lockfile_remove(lockfile) == 0) ? 0 : 1;
388
389
390 /*
391 * No, lock.
392 */
393 r = lockfile_create_set_tmplock(lockfile, &tmplock, retries, flags, &args);
394 if (r != 0 || !cmd)
395 return r;
396
397
398 /*
399 * Spawn command.
400 *
401 * Using an empty signal handler means that we ignore the
402 * signal, but that it's restored to SIG_DFL at execve().
403 */
404 set_signal(SIGINT, ignore_signal);
405 set_signal(SIGQUIT, ignore_signal);
406 set_signal(SIGHUP, ignore_signal);
407 set_signal(SIGALRM, ignore_signal);
408
409 pid_t pid = fork();
410 if (pid < 0) {
411 if (!quiet)
412 perror("fork");
413 lockfile_remove(lockfile);
414 exit(L_ERROR);
415 }
416 if (pid == 0) {
417 /* drop setgid */
418 if (gid != egid && setgid(gid) < 0) {
419 perror("setgid");
420 exit(127);
421 }
422 /* restore current working directory */
423 if (cwd_fd >= 0) {
424 if (fchdir(cwd_fd) < 0) {
425 perror("dotlockfile: restoring cwd:");
426 exit(127);
427 }
428 close(cwd_fd);
429 }
430 /* exec */
431 execvp(cmd[0], cmd);
432 perror(cmd[0]);
433 exit(127);
434 }
435
436 /* wait for child */
437 int e, wstatus;
438 while (1) {
439 if (!writepid)
440 alarm(30);
441 e = waitpid(pid, &wstatus, 0);
442 if (e >= 0 || errno != EINTR)
443 break;
444 if (!writepid)
445 lockfile_touch(lockfile);
446 }
447
448 alarm(0);
449 lockfile_remove(lockfile);
450
451 if (passthrough) {
452 if (WIFEXITED(wstatus))
453 return WEXITSTATUS(wstatus);
454 if (WIFSIGNALED(wstatus))
455 return 128+WTERMSIG(wstatus);
456 }
457 return 0;
458 }
459
460