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