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.
16 #include <sys/types.h>
18 #include <sys/param.h>
43 static volatile char *tmplock
;
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
);
58 static void ignore_signal(int sig
)
64 * Install signal handler only if the signal was
65 * not ignored already.
67 static int set_signal(int sig
, void (*handler
)(int))
71 if (sigaction(sig
, NULL
, &sa
) < 0)
73 if (sa
.sa_handler
== SIG_IGN
)
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
)
90 if (ppid
== 0) ppid
= getppid();
92 if (flags
& L_INTERVAL_D_
)
95 for (i
= 0; i
< sleeptime
; i
+= interval
) {
97 if (kill(ppid
, 0) < 0 && errno
== ESRCH
)
104 * Split a filename up in file and directory.
107 static int fn_split(char *fn
, char **fn_p
, char **dir_p
)
109 static char *buf
= NULL
;
114 buf
= (char *) malloc (strlen (fn
) + 1);
118 if ((p
= strrchr(buf
, '/')) != NULL
) {
131 * Return name of lockfile for mail.
133 static char *mlockname(char *user
)
135 static char *buf
= NULL
;
143 buf
= (char *)malloc(strlen(e
)+6);
146 sprintf(buf
, "%s.lock", e
);
148 buf
= (char *)malloc(strlen(MAILDIR
)+strlen(user
)+6);
151 sprintf(buf
, "%s%s.lock", MAILDIR
, user
);
156 static void perror_exit(const char *why
)
159 fprintf(stderr
, "dotlockfile: ");
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");
176 int main(int argc
, char **argv
)
179 struct lockargs_s_ args
= { 0 };
181 char *lockfile
= NULL
;
199 * Remember real and effective gid, and
200 * drop privs for now.
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
) {
229 retries
= atoi(optarg
);
231 retries
!= -1 && strcmp(optarg
, "0") != 0) {
233 fprintf(stderr
, "dotlockfile: "
234 "-r %s: invalid argument\n",
240 retries
= 0x7ffffff0;
244 if ((pwd
= getpwuid(geteuid())) == NULL
) {
246 fprintf(stderr
, "dotlockfile: You don't exist. Go away.\n");
249 lockfile
= mlockname(pwd
->pw_name
);
252 perror("dotlockfile");
266 interval
= atoi(optarg
);
267 if (interval
<= 0 && strcmp(optarg
, "0") != 0) {
268 fprintf(stderr
, "dotlockfile: -i needs argument >= 0\n");
271 flags
|= L_INTERVAL_D_
;
272 args
.interval
= interval
;
286 * next argument may be lockfile name
291 lockfile
= argv
[optind
++];
295 * next arguments may be command [args...]
301 * Options sanity check
303 if ((cmd
|| lock
) && (touch
|| check
|| unlock
))
307 flags
|= (cmd
? L_PID
: L_PPID
);
310 if (strlen(lockfile
) >= MAXPATHLEN
) {
312 fprintf(stderr
, "dotlockfile: %s: name too long\n", lockfile
);
318 * Check if we run setgid.
323 * See if the requested lock is for a mailbox.
324 * First, remember current working directory.
327 cwd_fd
= open(".", O_PATH
|O_CLOEXEC
);
329 cwd_fd
= open(".", O_RDONLY
|O_CLOEXEC
);
333 fprintf(stderr
, "dotlockfile: opening \".\": %s\n",
338 * Now change directory to the directory the lockfile is in.
341 r
= fn_split(lockfile
, &file
, &dir
);
342 if (r
!= L_SUCCESS
) {
344 perror("dotlockfile");
347 if (chdir(dir
) != 0) {
349 fprintf(stderr
, "dotlockfile: %s: %s\n", dir
, strerror(errno
));
354 need_privs
= is_maillock(lockfile
);
359 * See if we actually need to run setgid.
362 if (setregid(gid
, egid
) != 0)
363 perror_exit("setregid");
365 if (gid
!= egid
&& setgid(gid
) != 0)
366 perror_exit("setgid");
370 * Simple check for a valid lockfile ?
373 return (lockfile_check(lockfile
, flags
) < 0) ? 1 : 0;
380 return (lockfile_touch(lockfile
) < 0) ? 1 : 0;
386 return (lockfile_remove(lockfile
) == 0) ? 0 : 1;
392 r
= lockfile_create_set_tmplock(lockfile
, &tmplock
, retries
, flags
, &args
);
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
);
412 lockfile_remove(lockfile
);
417 if (gid
!= egid
&& setgid(gid
) < 0) {
421 /* restore current working directory */
423 if (fchdir(cwd_fd
) < 0) {
424 perror("dotlockfile: restoring cwd:");
439 e
= waitpid(pid
, &wstatus
, 0);
440 if (e
>= 0 || errno
!= EINTR
)
443 lockfile_touch(lockfile
);
447 lockfile_remove(lockfile
);
450 if (WIFEXITED(wstatus
))
451 return WEXITSTATUS(wstatus
);
452 if (WIFSIGNALED(wstatus
))
453 return 128+WTERMSIG(wstatus
);