Copyright year updates (things touched in 2016)
[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 int 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   fd = open(filename, O_RDWR + O_APPEND);
361   if (fd < 0)
362     {
363     printf("exim_lock: failed to open %s for writing: %s\n", filename,
364       strerror(errno));
365     yield = 1;
366     goto CLEAN_UP;
367     }
368
369   /* If there is a timeout, implying blocked locking, we don't want to
370   sleep before any retries after this. */
371
372   if (lock_fcntl_timeout > 0 || lock_flock_timeout > 0)
373     sleep_before_retry = FALSE;
374
375   /* Lock using fcntl. There are pros and cons to using a blocking call vs
376   a non-blocking call and retries. Exim is non-blocking by default, but setting
377   a timeout changes it to blocking. */
378
379   if (!use_mbx && (use_fcntl || use_flock))
380     {
381     if (apply_lock(fd, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
382         lock_flock_timeout) >= 0)
383       {
384       if (!quiet)
385         {
386         if (use_fcntl) printf("exim_lock: fcntl() lock successfully applied\n");
387         if (use_flock) printf("exim_lock: flock() lock successfully applied\n");
388         }
389       break;
390       }
391     else goto RETRY;   /* Message already output */
392     }
393
394   /* Lock using MBX rules. This is complicated and is documented with the
395   source of the c-client library that goes with Pine and IMAP. What has to
396   be done to interwork correctly is to take out a shared lock on the mailbox,
397   and an exclusive lock on a /tmp file. */
398
399   else
400     {
401     if (apply_lock(fd, F_RDLCK, use_fcntl, lock_fcntl_timeout, use_flock,
402         lock_flock_timeout) >= 0)
403       {
404       if (!quiet)
405         {
406         if (use_fcntl)
407           printf("exim_lock: fcntl() read lock successfully applied\n");
408         if (use_flock)
409           printf("exim_lock: fcntl() read lock successfully applied\n");
410         }
411       }
412     else goto RETRY;   /* Message already output */
413
414     if (fstat(fd, &statbuf) < 0)
415       {
416       printf("exim_lock: fstat() of %s failed: %s\n", filename,
417         strerror(errno));
418       yield = 1;
419       goto CLEAN_UP;
420       }
421
422     /* Set up file in /tmp and check its state if already existing. */
423
424     sprintf(tempname, "/tmp/.%lx.%lx", (long)statbuf.st_dev,
425       (long)statbuf.st_ino);
426
427     if (lstat(tempname, &statbuf) >= 0)
428       {
429       if ((statbuf.st_mode & S_IFMT) == S_IFLNK)
430         {
431         printf("exim_lock: symbolic link on lock name %s\n", tempname);
432         yield = 1;
433         goto CLEAN_UP;
434         }
435       if (statbuf.st_nlink > 1)
436         {
437         printf("exim_lock: hard link to lock name %s\n", tempname);
438         yield = 1;
439         goto CLEAN_UP;
440         }
441       }
442
443     mbx_tmp_oflags = O_RDWR | O_CREAT;
444 #ifdef O_NOFOLLOW
445     mbx_tmp_oflags |= O_NOFOLLOW;
446 #endif
447     md = open(tempname, mbx_tmp_oflags, 0600);
448     if (md < 0)
449       {
450       printf("exim_lock: failed to create mbx lock file %s: %s\n",
451         tempname, strerror(errno));
452       goto CLEAN_UP;
453       }
454
455     /* security fixes from 2010-05 */
456     if (lstat(tempname, &lstatbuf) < 0)
457       {
458       printf("exim_lock: failed to lstat(%s) after opening it: %s\n",
459           tempname, strerror(errno));
460       goto CLEAN_UP;
461       }
462     if (fstat(md, &statbuf2) < 0)
463       {
464       printf("exim_lock: failed to fstat() open fd of \"%s\": %s\n",
465           tempname, strerror(errno));
466       goto CLEAN_UP;
467       }
468     if ((statbuf2.st_nlink > 1) ||
469         (lstatbuf.st_nlink > 1) ||
470         (!S_ISREG(lstatbuf.st_mode)) ||
471         (lstatbuf.st_dev != statbuf2.st_dev) ||
472         (lstatbuf.st_ino != statbuf2.st_ino))
473       {
474       printf("exim_lock: race condition exploited against us when "
475           "locking \"%s\"\n", tempname);
476       goto CLEAN_UP;
477       }
478
479     (void)chmod(tempname, 0600);
480
481     if (apply_lock(md, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
482         lock_flock_timeout) >= 0)
483       {
484       if (!quiet)
485         {
486         if (use_fcntl)
487           printf("exim_lock: fcntl() lock successfully applied to mbx "
488             "lock file %s\n", tempname);
489         if (use_flock)
490           printf("exim_lock: flock() lock successfully applied to mbx "
491             "lock file %s\n", tempname);
492         }
493
494       /* This test checks for a race condition */
495
496       if (lstat(tempname, &statbuf) != 0 ||
497           fstat(md, &ostatbuf) != 0 ||
498           statbuf.st_dev != ostatbuf.st_dev ||
499           statbuf.st_ino != ostatbuf.st_ino)
500        {
501        if (!quiet) printf("exim_lock: mbx lock file %s changed between "
502            "creation and locking\n", tempname);
503        goto RETRY;
504        }
505       else break;
506       }
507     else goto RETRY;   /* Message already output */
508     }
509
510   /* Clean up before retrying */
511
512   RETRY:
513
514   if (md >= 0)
515     {
516     if (close(md) < 0)
517       printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
518     else
519       if (!quiet) printf("exim_lock: %s closed\n", tempname);
520     md = -1;
521     }
522
523   if (fd >= 0)
524     {
525     if (close(fd) < 0)
526       printf("exim_lock: close failed: %s\n", strerror(errno));
527     else
528       if (!quiet) printf("exim_lock: file closed\n");
529     fd = -1;
530     }
531
532   if (hd >= 0)
533     {
534     if (unlink(lockname) < 0)
535       printf("exim_lock: unlink of %s failed: %s\n", lockname, strerror(errno));
536     else
537       if (!quiet) printf("exim_lock: lock file removed\n");
538     hd = -1;
539     }
540
541   /* If a blocking call timed out, break the retry loop if the total time
542   so far is not less than than retries * interval. */
543
544   if (sigalrm_seen &&
545       (j + 1) * ((lock_fcntl_timeout > lock_flock_timeout)?
546         lock_fcntl_timeout : lock_flock_timeout) >=
547           lock_retries * lock_interval)
548     j = lock_retries;
549
550   /* Wait a bit before retrying, except when it was a blocked fcntl() that
551   caused the problem. */
552
553   if (j < lock_retries && sleep_before_retry)
554     {
555     printf(" ... waiting\n");
556     sleep(lock_interval);
557     }
558   }
559
560 if (j >= lock_retries)
561   {
562   printf("exim_lock: locking failed too many times\n");
563   yield = 1;
564   goto CLEAN_UP;
565   }
566
567 if (!quiet) printf("exim_lock: locking %s succeeded: ", filename);
568
569 /* If there are no further arguments, run the user's shell; otherwise
570 the next argument is a command to run. */
571
572 if (i >= argc)
573   {
574   command = getenv("SHELL");
575   if (command == NULL || *command == 0) command = "/bin/sh";
576   if (!quiet) printf("running %s ...\n", command);
577   }
578 else
579   {
580   command = argv[i];
581   if (!quiet) printf("running the command ...\n");
582   }
583
584 /* Run the command, saving and restoring the times if required. */
585
586 if (restore_times)
587   {
588   struct stat strestore;
589   struct utimbuf ut;
590   stat(filename, &strestore);
591   i = system(command);
592   ut.actime = strestore.st_atime;
593   ut.modtime = strestore.st_mtime;
594   utime(filename, &ut);
595   }
596 else i = system(command);
597
598 if(i && !quiet) printf("warning: nonzero status %d\n", i);
599
600 /* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
601 lock on the mailbox. This should be a non-blocking lock call, as there is no
602 point in waiting. */
603
604 CLEAN_UP:
605
606 if (md >= 0)
607   {
608   if (apply_lock(fd, F_WRLCK, use_fcntl, 0, use_flock, 0) >= 0)
609     {
610     if (!quiet) printf("exim_lock: %s unlinked - no sharers\n", tempname);
611     unlink(tempname);
612     }
613   else if (!quiet)
614     printf("exim_lock: %s not unlinked - unable to get exclusive mailbox lock\n",
615       tempname);
616   if (close(md) < 0)
617     printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
618   else
619     if (!quiet) printf("exim_lock: %s closed\n", tempname);
620   }
621
622 if (fd >= 0)
623   {
624   if (close(fd) < 0)
625     printf("exim_lock: close %s failed: %s\n", filename, strerror(errno));
626   else
627     if (!quiet) printf("exim_lock: %s closed\n", filename);
628   }
629
630 if (hd >= 0)
631   {
632   if (unlink(lockname) < 0)
633     printf("exim_lock: unlink %s failed: %s\n", lockname, strerror(errno));
634   else
635     if (!quiet) printf("exim_lock: lock file removed\n");
636   }
637
638 return yield;
639 }
640
641 /* End */