Build-variant tidyup
[users/jgh/exim.git] / src / src / exim_lock.c
1 /* A program to lock a file exactly as Exim would, for investigation of
2 interlocking problems.
3
4 Options:  -fcntl    use fcntl() lock
5           -flock    use flock() lock
6           -lockfile use lock file
7           -mbx      use mbx locking rules, with either fcntl() or flock()
8
9 Default is -fcntl -lockfile.
10
11 Argument: the name of the lock file
12
13 Copyright (c) The Exim Maintainers 2016
14 */
15
16 #include "os.h"
17
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <signal.h>
22 #include <errno.h>
23 #include <time.h>
24 #include <netdb.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27 #include <utime.h>
28 #include <sys/utsname.h>
29 #include <sys/stat.h>
30 #include <sys/file.h>
31 #include <pwd.h>
32
33 /* Not all systems have flock() available. Those that do must define LOCK_SH
34 in sys/file.h. */
35
36 #ifndef LOCK_SH
37 #define NO_FLOCK
38 #endif
39
40
41 typedef unsigned BOOL;
42 #define FALSE 0
43 #define TRUE  1
44
45
46 /* Flag for timeout signal handler */
47
48 static int sigalrm_seen = FALSE;
49
50
51 /* We need to pull in strerror() and os_non_restarting_signal() from the
52 os.c source, if they are required for this OS. However, we don't need any of
53 the other stuff in os.c, so force the other macros to omit it. */
54
55 #ifndef OS_RESTARTING_SIGNAL
56   #define OS_RESTARTING_SIGNAL
57 #endif
58
59 #ifndef OS_STRSIGNAL
60   #define OS_STRSIGNAL
61 #endif
62
63 #ifndef OS_STREXIT
64   #define OS_STREXIT
65 #endif
66
67 #ifndef OS_LOAD_AVERAGE
68   #define OS_LOAD_AVERAGE
69 #endif
70
71 #ifndef FIND_RUNNING_INTERFACES
72   #define FIND_RUNNING_INTERFACES
73 #endif
74
75 #ifndef OS_GET_DNS_RESOLVER_RES
76   #define OS_GET_DNS_RESOLVER_RES
77 #endif
78
79 #include "../src/os.c"
80
81
82
83 /*************************************************
84 *             Timeout handler                    *
85 *************************************************/
86
87 static void
88 sigalrm_handler(int sig)
89 {
90 sig = sig;      /* Keep picky compilers happy */
91 sigalrm_seen = TRUE;
92 }
93
94
95
96 /*************************************************
97 *           Give usage and die                   *
98 *************************************************/
99
100 static void
101 usage(void)
102 {
103 printf("usage: exim_lock [-v] [-q] [-lockfile] [-fcntl] [-flock] [-mbx]\n"
104        "       [-retries <n>] [-interval <n>] [-timeout <n>] [-restore-times]\n"
105        "       <file name> [command]\n");
106 exit(1);
107 }
108
109
110
111 /*************************************************
112 *         Apply a lock to a file descriptor      *
113 *************************************************/
114
115 static int
116 apply_lock(int fd, int fcntltype, BOOL dofcntl, int fcntltime, BOOL doflock,
117     int flocktime)
118 {
119 int yield = 0;
120 int save_errno;
121 struct flock lock_data;
122 lock_data.l_type = fcntltype;
123 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
124
125 sigalrm_seen = FALSE;
126
127 if (dofcntl)
128   {
129   if (fcntltime > 0)
130     {
131     os_non_restarting_signal(SIGALRM, sigalrm_handler);
132     alarm(fcntltime);
133     yield = fcntl(fd, F_SETLKW, &lock_data);
134     save_errno = errno;
135     alarm(0);
136     errno = save_errno;
137     }
138   else yield = fcntl(fd, F_SETLK, &lock_data);
139   if (yield < 0) printf("exim_lock: fcntl() failed: %s\n", strerror(errno));
140   }
141
142 #ifndef NO_FLOCK
143 if (doflock && (yield >= 0))
144   {
145   int flocktype = (fcntltype == F_WRLCK)? LOCK_EX : LOCK_SH;
146   if (flocktime > 0)
147     {
148     os_non_restarting_signal(SIGALRM, sigalrm_handler);
149     alarm(flocktime);
150     yield = flock(fd, flocktype);
151     save_errno = errno;
152     alarm(0);
153     errno = save_errno;
154     }
155   else yield = flock(fd, flocktype | LOCK_NB);
156   if (yield < 0) printf("exim_lock: flock() failed: %s\n", strerror(errno));
157   }
158 #endif
159
160 return yield;
161 }
162
163
164
165 /*************************************************
166 *           The exim_lock program                *
167 *************************************************/
168
169 int main(int argc, char **argv)
170 {
171 int  lock_retries = 10;
172 int  lock_interval = 3;
173 int  lock_fcntl_timeout = 0;
174 int  lock_flock_timeout = 0;
175 int  i, j, len;
176 int  fd = -1;
177 int  hd = -1;
178 int  md = -1;
179 int  yield = 0;
180 time_t now = time(NULL);
181 BOOL use_lockfile = FALSE;
182 BOOL use_fcntl = FALSE;
183 BOOL use_flock = FALSE;
184 BOOL use_mbx = FALSE;
185 BOOL verbose = FALSE;
186 BOOL quiet = FALSE;
187 BOOL restore_times = FALSE;
188 char *filename;
189 char *lockname = NULL, *hitchname = NULL;
190 char *primary_hostname;
191 const char *command;
192 struct utsname s;
193 char buffer[256];
194 char tempname[256];
195
196 /* Decode options */
197
198 for (i = 1; i < argc; i++)
199   {
200   char *arg = argv[i];
201   if (*arg != '-') break;
202   if (strcmp(arg, "-fcntl") == 0) use_fcntl = TRUE;
203   else if (strcmp(arg, "-flock") == 0) use_flock = TRUE;
204   else if (strcmp(arg, "-lockfile") == 0) use_lockfile = TRUE;
205   else if (strcmp(arg, "-mbx") == 0) use_mbx = TRUE;
206   else if (strcmp(arg, "-v") == 0) verbose = TRUE;
207   else if (strcmp(arg, "-q") == 0) quiet = TRUE;
208   else if (strcmp(arg, "-restore-times") == 0) restore_times = TRUE;
209   else if (++i < argc)
210     {
211     int value = atoi(argv[i]);
212     if (strcmp(arg, "-retries") == 0) lock_retries = value;
213     else if (strcmp(arg, "-interval") == 0) lock_interval = value;
214     else if (strcmp(arg, "-timeout") == 0)
215       lock_fcntl_timeout = lock_flock_timeout = value;
216     else usage();
217     }
218   else usage();
219   }
220
221 if (quiet) verbose = FALSE;
222
223 /* Can't use flock() if the OS doesn't provide it */
224
225 #ifdef NO_FLOCK
226 if (use_flock)
227   {
228   printf("exim_lock: can't use flock() because it was not available in the\n"
229          "           operating system when exim_lock was compiled\n");
230   exit(1);
231   }
232 #endif
233
234 /* Default is to use lockfiles and fcntl(). */
235
236 if (!use_lockfile && !use_fcntl && !use_flock && !use_mbx)
237   use_lockfile = use_fcntl = TRUE;
238
239 /* Default fcntl() for use with mbx */
240
241 if (use_mbx && !use_fcntl && !use_flock) use_fcntl = TRUE;
242
243 /* Unset unused timeouts */
244
245 if (!use_fcntl) lock_fcntl_timeout = 0;
246 if (!use_flock) lock_flock_timeout = 0;
247
248 /* A file name is required */
249
250 if (i >= argc) usage();
251
252 filename = argv[i++];
253
254 /* Expand file names starting with ~ */
255
256 if (*filename == '~')
257   {
258   struct passwd *pw;
259
260   if (*(++filename) == '/')
261     pw = getpwuid(getuid());
262   else
263     {
264     char *s = buffer;
265     while (*filename != 0 && *filename != '/')
266       *s++ = *filename++;
267     *s = 0;
268     pw = getpwnam(buffer);
269     }
270
271   if (pw == NULL)
272     {
273     printf("exim_lock: unable to expand file name %s\n", argv[i-1]);
274     exit(1);
275     }
276
277   if ((int)strlen(pw->pw_dir) + (int)strlen(filename) + 1 > sizeof(buffer))
278     {
279     printf("exim_lock: expanded file name %s%s is too long", pw->pw_dir,
280       filename);
281     exit(1);
282     }
283
284   strcpy(buffer, pw->pw_dir);
285   strcat(buffer, filename);
286   filename = buffer;
287   }
288
289 /* If using a lock file, prepare by creating the lock file name and
290 the hitching post name. */
291
292 if (use_lockfile)
293   {
294   if (uname(&s) < 0)
295     {
296     printf("exim_lock: failed to find host name using uname()\n");
297     exit(1);
298     }
299   primary_hostname = s.nodename;
300
301   len = (int)strlen(filename);
302   lockname = malloc(len + 8);
303   sprintf(lockname, "%s.lock", filename);
304   hitchname = malloc(len + 32 + (int)strlen(primary_hostname));
305
306   /* Presumably, this must match appendfile.c */
307   sprintf(hitchname, "%s.%s.%08x.%08x", lockname, primary_hostname,
308     (unsigned int)now, (unsigned int)getpid());
309
310   if (verbose)
311     printf("exim_lock: lockname =  %s\n           hitchname = %s\n", lockname,
312       hitchname);
313   }
314
315 /* Locking retry loop */
316
317 for (j = 0; j < lock_retries; j++)
318   {
319   int sleep_before_retry = TRUE;
320   struct stat statbuf, ostatbuf, lstatbuf, statbuf2;
321   int mbx_tmp_oflags;
322
323   /* Try to build a lock file if so configured */
324
325   if (use_lockfile)
326     {
327     int rc, rc2;
328     if (verbose) printf("exim_lock: creating lock file\n");
329     hd = open(hitchname, O_WRONLY | O_CREAT | O_EXCL, 0440);
330     if (hd < 0)
331       {
332       printf("exim_lock: failed to create hitching post %s: %s\n", hitchname,
333         strerror(errno));
334       exit(1);
335       }
336
337     /* Apply hitching post algorithm. */
338
339     if ((rc = link(hitchname, lockname)) != 0)
340      rc2 = fstat(hd, &statbuf);
341     (void)close(hd);
342     unlink(hitchname);
343
344     if (rc != 0 && (rc2 != 0 || statbuf.st_nlink != 2))
345       {
346       printf("exim_lock: failed to link hitching post to lock file\n");
347       hd = -1;
348       goto RETRY;
349       }
350
351     if (!quiet) printf("exim_lock: lock file successfully created\n");
352     }
353
354   /* We are done if no other locking required. */
355
356   if (!use_fcntl && !use_flock && !use_mbx) break;
357
358   /* Open the file for writing. */
359
360   if ((fd = open(filename, O_RDWR + O_APPEND)) < 0)
361     {
362     printf("exim_lock: failed to open %s for writing: %s\n", filename,
363       strerror(errno));
364     yield = 1;
365     goto CLEAN_UP;
366     }
367
368   /* If there is a timeout, implying blocked locking, we don't want to
369   sleep before any retries after this. */
370
371   if (lock_fcntl_timeout > 0 || lock_flock_timeout > 0)
372     sleep_before_retry = FALSE;
373
374   /* Lock using fcntl. There are pros and cons to using a blocking call vs
375   a non-blocking call and retries. Exim is non-blocking by default, but setting
376   a timeout changes it to blocking. */
377
378   if (!use_mbx && (use_fcntl || use_flock))
379     if (apply_lock(fd, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
380         lock_flock_timeout) >= 0)
381       {
382       if (!quiet)
383         {
384         if (use_fcntl) printf("exim_lock: fcntl() lock successfully applied\n");
385         if (use_flock) printf("exim_lock: flock() lock successfully applied\n");
386         }
387       break;
388       }
389     else
390       goto RETRY;   /* Message already output */
391
392   /* Lock using MBX rules. This is complicated and is documented with the
393   source of the c-client library that goes with Pine and IMAP. What has to
394   be done to interwork correctly is to take out a shared lock on the mailbox,
395   and an exclusive lock on a /tmp file. */
396
397   else
398     {
399     if (apply_lock(fd, F_RDLCK, use_fcntl, lock_fcntl_timeout, use_flock,
400         lock_flock_timeout) >= 0)
401       {
402       if (!quiet)
403         {
404         if (use_fcntl)
405           printf("exim_lock: fcntl() read lock successfully applied\n");
406         if (use_flock)
407           printf("exim_lock: fcntl() read lock successfully applied\n");
408         }
409       }
410     else goto RETRY;   /* Message already output */
411
412     if (fstat(fd, &statbuf) < 0)
413       {
414       printf("exim_lock: fstat() of %s failed: %s\n", filename,
415         strerror(errno));
416       yield = 1;
417       goto CLEAN_UP;
418       }
419
420     /* Set up file in /tmp and check its state if already existing. */
421
422     sprintf(tempname, "/tmp/.%lx.%lx", (long)statbuf.st_dev,
423       (long)statbuf.st_ino);
424
425     if (lstat(tempname, &statbuf) >= 0)
426       {
427       if ((statbuf.st_mode & S_IFMT) == S_IFLNK)
428         {
429         printf("exim_lock: symbolic link on lock name %s\n", tempname);
430         yield = 1;
431         goto CLEAN_UP;
432         }
433       if (statbuf.st_nlink > 1)
434         {
435         printf("exim_lock: hard link to lock name %s\n", tempname);
436         yield = 1;
437         goto CLEAN_UP;
438         }
439       }
440
441     mbx_tmp_oflags = O_RDWR | O_CREAT;
442 #ifdef O_NOFOLLOW
443     mbx_tmp_oflags |= O_NOFOLLOW;
444 #endif
445     md = open(tempname, mbx_tmp_oflags, 0600);
446     if (md < 0)
447       {
448       printf("exim_lock: failed to create mbx lock file %s: %s\n",
449         tempname, strerror(errno));
450       goto CLEAN_UP;
451       }
452
453     /* security fixes from 2010-05 */
454     if (lstat(tempname, &lstatbuf) < 0)
455       {
456       printf("exim_lock: failed to lstat(%s) after opening it: %s\n",
457           tempname, strerror(errno));
458       goto CLEAN_UP;
459       }
460     if (fstat(md, &statbuf2) < 0)
461       {
462       printf("exim_lock: failed to fstat() open fd of \"%s\": %s\n",
463           tempname, strerror(errno));
464       goto CLEAN_UP;
465       }
466     if ((statbuf2.st_nlink > 1) ||
467         (lstatbuf.st_nlink > 1) ||
468         (!S_ISREG(lstatbuf.st_mode)) ||
469         (lstatbuf.st_dev != statbuf2.st_dev) ||
470         (lstatbuf.st_ino != statbuf2.st_ino))
471       {
472       printf("exim_lock: race condition exploited against us when "
473           "locking \"%s\"\n", tempname);
474       goto CLEAN_UP;
475       }
476
477     (void)chmod(tempname, 0600);
478
479     if (apply_lock(md, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
480         lock_flock_timeout) >= 0)
481       {
482       if (!quiet)
483         {
484         if (use_fcntl)
485           printf("exim_lock: fcntl() lock successfully applied to mbx "
486             "lock file %s\n", tempname);
487         if (use_flock)
488           printf("exim_lock: flock() lock successfully applied to mbx "
489             "lock file %s\n", tempname);
490         }
491
492       /* This test checks for a race condition */
493
494       if (lstat(tempname, &statbuf) != 0 ||
495           fstat(md, &ostatbuf) != 0 ||
496           statbuf.st_dev != ostatbuf.st_dev ||
497           statbuf.st_ino != ostatbuf.st_ino)
498        {
499        if (!quiet) printf("exim_lock: mbx lock file %s changed between "
500            "creation and locking\n", tempname);
501        goto RETRY;
502        }
503       else break;
504       }
505     else goto RETRY;   /* Message already output */
506     }
507
508   /* Clean up before retrying */
509
510   RETRY:
511
512   if (md >= 0)
513     {
514     if (close(md) < 0)
515       printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
516     else
517       if (!quiet) printf("exim_lock: %s closed\n", tempname);
518     md = -1;
519     }
520
521   if (fd >= 0)
522     {
523     if (close(fd) < 0)
524       printf("exim_lock: close failed: %s\n", strerror(errno));
525     else
526       if (!quiet) printf("exim_lock: file closed\n");
527     fd = -1;
528     }
529
530   if (hd >= 0)
531     {
532     if (unlink(lockname) < 0)
533       printf("exim_lock: unlink of %s failed: %s\n", lockname, strerror(errno));
534     else
535       if (!quiet) printf("exim_lock: lock file removed\n");
536     hd = -1;
537     }
538
539   /* If a blocking call timed out, break the retry loop if the total time
540   so far is not less than than retries * interval. */
541
542   if (sigalrm_seen &&
543       (j + 1) * ((lock_fcntl_timeout > lock_flock_timeout)?
544         lock_fcntl_timeout : lock_flock_timeout) >=
545           lock_retries * lock_interval)
546     j = lock_retries;
547
548   /* Wait a bit before retrying, except when it was a blocked fcntl() that
549   caused the problem. */
550
551   if (j < lock_retries && sleep_before_retry)
552     {
553     printf(" ... waiting\n");
554     sleep(lock_interval);
555     }
556   }
557
558 if (j >= lock_retries)
559   {
560   printf("exim_lock: locking failed too many times\n");
561   yield = 1;
562   goto CLEAN_UP;
563   }
564
565 if (!quiet) printf("exim_lock: locking %s succeeded: ", filename);
566
567 /* If there are no further arguments, run the user's shell; otherwise
568 the next argument is a command to run. */
569
570 if (i >= argc)
571   {
572   command = getenv("SHELL");
573   if (command == NULL || *command == 0) command = "/bin/sh";
574   if (!quiet) printf("running %s ...\n", command);
575   }
576 else
577   {
578   command = argv[i];
579   if (!quiet) printf("running the command ...\n");
580   }
581
582 /* Run the command, saving and restoring the times if required. */
583
584 if (restore_times)
585   {
586   struct stat strestore;
587 #ifdef EXIM_HAVE_OPENAT
588   int fd = open(filename, O_RDWR); /* use fd for both get & restore */
589   struct timespec tt[2];
590
591   if (fd < 0)
592     {
593     printf("open '%s': %s\n", filename, strerror(errno));
594     yield = 1;
595     goto CLEAN_UP;
596     }
597   if (fstat(fd, &strestore) != 0)
598     {
599     printf("fstat '%s': %s\n", filename, strerror(errno));
600     yield = 1;
601     close(fd);
602     goto CLEAN_UP;
603     }
604   i = system(command);
605   tt[0] = strestore.st_atim;
606   tt[1] = strestore.st_mtim;
607   (void) futimens(fd, tt);
608   (void) close(fd);
609 #else
610   struct utimbuf ut;
611
612   stat(filename, &strestore);
613   i = system(command);
614   ut.actime = strestore.st_atime;
615   ut.modtime = strestore.st_mtime;
616   utime(filename, &ut);
617 #endif
618   }
619 else i = system(command);
620
621 if(i && !quiet) printf("warning: nonzero status %d\n", i);
622
623 /* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
624 lock on the mailbox. This should be a non-blocking lock call, as there is no
625 point in waiting. */
626
627 CLEAN_UP:
628
629 if (md >= 0)
630   {
631   if (apply_lock(fd, F_WRLCK, use_fcntl, 0, use_flock, 0) >= 0)
632     {
633     if (!quiet) printf("exim_lock: %s unlinked - no sharers\n", tempname);
634     unlink(tempname);
635     }
636   else if (!quiet)
637     printf("exim_lock: %s not unlinked - unable to get exclusive mailbox lock\n",
638       tempname);
639   if (close(md) < 0)
640     printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
641   else
642     if (!quiet) printf("exim_lock: %s closed\n", tempname);
643   }
644
645 if (fd >= 0)
646   {
647   if (close(fd) < 0)
648     printf("exim_lock: close %s failed: %s\n", filename, strerror(errno));
649   else
650     if (!quiet) printf("exim_lock: %s closed\n", filename);
651   }
652
653 if (hd >= 0)
654   {
655   if (unlink(lockname) < 0)
656     printf("exim_lock: unlink %s failed: %s\n", lockname, strerror(errno));
657   else
658     if (!quiet) printf("exim_lock: lock file removed\n");
659   }
660
661 return yield;
662 }
663
664 /* End */