37d9744771217b245aaf386faf3d337524984c08
[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
14 #include "os.h"
15
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <signal.h>
20 #include <errno.h>
21 #include <time.h>
22 #include <netdb.h>
23 #include <fcntl.h>
24 #include <unistd.h>
25 #include <utime.h>
26 #include <sys/utsname.h>
27 #include <sys/stat.h>
28 #include <sys/file.h>
29 #include <pwd.h>
30
31 /* Not all systems have flock() available. Those that do must define LOCK_SH
32 in sys/file.h. */
33
34 #ifndef LOCK_SH
35 #define NO_FLOCK
36 #endif
37
38
39 typedef int BOOL;
40 #define FALSE 0
41 #define TRUE  1
42
43
44 /* Flag for timeout signal handler */
45
46 static int sigalrm_seen = FALSE;
47
48
49 /* We need to pull in strerror() and os_non_restarting_signal() from the
50 os.c source, if they are required for this OS. However, we don't need any of
51 the other stuff in os.c, so force the other macros to omit it. */
52
53 #ifndef OS_RESTARTING_SIGNAL
54   #define OS_RESTARTING_SIGNAL
55 #endif
56
57 #ifndef OS_STRSIGNAL
58   #define OS_STRSIGNAL
59 #endif
60
61 #ifndef OS_STREXIT
62   #define OS_STREXIT
63 #endif
64
65 #ifndef OS_LOAD_AVERAGE
66   #define OS_LOAD_AVERAGE
67 #endif
68
69 #ifndef FIND_RUNNING_INTERFACES
70   #define FIND_RUNNING_INTERFACES
71 #endif
72
73 #ifndef OS_GET_DNS_RESOLVER_RES
74   #define OS_GET_DNS_RESOLVER_RES
75 #endif
76
77 #include "../src/os.c"
78
79
80
81 /*************************************************
82 *             Timeout handler                    *
83 *************************************************/
84
85 static void
86 sigalrm_handler(int sig)
87 {
88 sig = sig;      /* Keep picky compilers happy */
89 sigalrm_seen = TRUE;
90 }
91
92
93
94 /*************************************************
95 *           Give usage and die                   *
96 *************************************************/
97
98 static void
99 usage(void)
100 {
101 printf("usage: exim_lock [-v] [-q] [-lockfile] [-fcntl] [-flock] [-mbx]\n"
102        "       [-retries <n>] [-interval <n>] [-timeout <n>] [-restore-times]\n"
103        "       <file name> [command]\n");
104 exit(1);
105 }
106
107
108
109 /*************************************************
110 *         Apply a lock to a file descriptor      *
111 *************************************************/
112
113 static int
114 apply_lock(int fd, int fcntltype, BOOL dofcntl, int fcntltime, BOOL doflock,
115     int flocktime)
116 {
117 int yield = 0;
118 int save_errno;
119 struct flock lock_data;
120 lock_data.l_type = fcntltype;
121 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
122
123 sigalrm_seen = FALSE;
124
125 if (dofcntl)
126   {
127   if (fcntltime > 0)
128     {
129     os_non_restarting_signal(SIGALRM, sigalrm_handler);
130     alarm(fcntltime);
131     yield = fcntl(fd, F_SETLKW, &lock_data);
132     save_errno = errno;
133     alarm(0);
134     errno = save_errno;
135     }
136   else yield = fcntl(fd, F_SETLK, &lock_data);
137   if (yield < 0) printf("exim_lock: fcntl() failed: %s\n", strerror(errno));
138   }
139
140 #ifndef NO_FLOCK
141 if (doflock && (yield >= 0))
142   {
143   int flocktype = (fcntltype == F_WRLCK)? LOCK_EX : LOCK_SH;
144   if (flocktime > 0)
145     {
146     os_non_restarting_signal(SIGALRM, sigalrm_handler);
147     alarm(flocktime);
148     yield = flock(fd, flocktype);
149     save_errno = errno;
150     alarm(0);
151     errno = save_errno;
152     }
153   else yield = flock(fd, flocktype | LOCK_NB);
154   if (yield < 0) printf("exim_lock: flock() failed: %s\n", strerror(errno));
155   }
156 #endif
157
158 return yield;
159 }
160
161
162
163 /*************************************************
164 *           The exim_lock program                *
165 *************************************************/
166
167 int main(int argc, char **argv)
168 {
169 int  lock_retries = 10;
170 int  lock_interval = 3;
171 int  lock_fcntl_timeout = 0;
172 int  lock_flock_timeout = 0;
173 int  i, j, len;
174 int  fd = -1;
175 int  hd = -1;
176 int  md = -1;
177 int  yield = 0;
178 time_t now = time(NULL);
179 BOOL use_lockfile = FALSE;
180 BOOL use_fcntl = FALSE;
181 BOOL use_flock = FALSE;
182 BOOL use_mbx = FALSE;
183 BOOL verbose = FALSE;
184 BOOL quiet = FALSE;
185 BOOL restore_times = FALSE;
186 char *filename;
187 char *lockname = NULL, *hitchname = NULL;
188 char *primary_hostname;
189 const char *command;
190 struct utsname s;
191 char buffer[256];
192 char tempname[256];
193
194 /* Decode options */
195
196 for (i = 1; i < argc; i++)
197   {
198   char *arg = argv[i];
199   if (*arg != '-') break;
200   if (strcmp(arg, "-fcntl") == 0) use_fcntl = TRUE;
201   else if (strcmp(arg, "-flock") == 0) use_flock = TRUE;
202   else if (strcmp(arg, "-lockfile") == 0) use_lockfile = TRUE;
203   else if (strcmp(arg, "-mbx") == 0) use_mbx = TRUE;
204   else if (strcmp(arg, "-v") == 0) verbose = TRUE;
205   else if (strcmp(arg, "-q") == 0) quiet = TRUE;
206   else if (strcmp(arg, "-restore-times") == 0) restore_times = TRUE;
207   else if (++i < argc)
208     {
209     int value = atoi(argv[i]);
210     if (strcmp(arg, "-retries") == 0) lock_retries = value;
211     else if (strcmp(arg, "-interval") == 0) lock_interval = value;
212     else if (strcmp(arg, "-timeout") == 0)
213       lock_fcntl_timeout = lock_flock_timeout = value;
214     else usage();
215     }
216   else usage();
217   }
218
219 if (quiet) verbose = 0;
220
221 /* Can't use flock() if the OS doesn't provide it */
222
223 #ifdef NO_FLOCK
224 if (use_flock)
225   {
226   printf("exim_lock: can't use flock() because it was not available in the\n"
227          "           operating system when exim_lock was compiled\n");
228   exit(1);
229   }
230 #endif
231
232 /* Default is to use lockfiles and fcntl(). */
233
234 if (!use_lockfile && !use_fcntl && !use_flock && !use_mbx)
235   use_lockfile = use_fcntl = TRUE;
236
237 /* Default fcntl() for use with mbx */
238
239 if (use_mbx && !use_fcntl && !use_flock) use_fcntl = TRUE;
240
241 /* Unset unused timeouts */
242
243 if (!use_fcntl) lock_fcntl_timeout = 0;
244 if (!use_flock) lock_flock_timeout = 0;
245
246 /* A file name is required */
247
248 if (i >= argc) usage();
249
250 filename = argv[i++];
251
252 /* Expand file names starting with ~ */
253
254 if (*filename == '~')
255   {
256   struct passwd *pw;
257
258   if (*(++filename) == '/')
259     pw = getpwuid(getuid());
260   else
261     {
262     char *s = buffer;
263     while (*filename != 0 && *filename != '/')
264       *s++ = *filename++;
265     *s = 0;
266     pw = getpwnam(buffer);
267     }
268
269   if (pw == NULL)
270     {
271     printf("exim_lock: unable to expand file name %s\n", argv[i-1]);
272     exit(1);
273     }
274
275   if ((int)strlen(pw->pw_dir) + (int)strlen(filename) + 1 > sizeof(buffer))
276     {
277     printf("exim_lock: expanded file name %s%s is too long", pw->pw_dir,
278       filename);
279     exit(1);
280     }
281
282   strcpy(buffer, pw->pw_dir);
283   strcat(buffer, filename);
284   filename = buffer;
285   }
286
287 /* If using a lock file, prepare by creating the lock file name and
288 the hitching post name. */
289
290 if (use_lockfile)
291   {
292   if (uname(&s) < 0)
293     {
294     printf("exim_lock: failed to find host name using uname()\n");
295     exit(1);
296     }
297   primary_hostname = s.nodename;
298
299   len = (int)strlen(filename);
300   lockname = malloc(len + 8);
301   sprintf(lockname, "%s.lock", filename);
302   hitchname = malloc(len + 32 + (int)strlen(primary_hostname));
303
304   /* Presumably, this must match appendfile.c */
305   sprintf(hitchname, "%s.%s.%08x.%08x", lockname, primary_hostname,
306     (unsigned int)now, (unsigned int)getpid());
307
308   if (verbose)
309     printf("exim_lock: lockname =  %s\n           hitchname = %s\n", lockname,
310       hitchname);
311   }
312
313 /* Locking retry loop */
314
315 for (j = 0; j < lock_retries; j++)
316   {
317   int sleep_before_retry = TRUE;
318   struct stat statbuf, ostatbuf, lstatbuf, statbuf2;
319   int mbx_tmp_oflags;
320
321   /* Try to build a lock file if so configured */
322
323   if (use_lockfile)
324     {
325     int rc;
326     if (verbose) printf("exim_lock: creating lock file\n");
327     hd = open(hitchname, O_WRONLY | O_CREAT | O_EXCL, 0440);
328     if (hd < 0)
329       {
330       printf("exim_lock: failed to create hitching post %s: %s\n", hitchname,
331         strerror(errno));
332       exit(1);
333       }
334
335     /* Apply hitching post algorithm. */
336
337     if ((rc = link(hitchname, lockname)) != 0) fstat(hd, &statbuf);
338     (void)close(hd);
339     unlink(hitchname);
340
341     if (rc != 0 && statbuf.st_nlink != 2)
342       {
343       printf("exim_lock: failed to link hitching post to lock file\n");
344       hd = -1;
345       goto RETRY;
346       }
347
348     if (!quiet) printf("exim_lock: lock file successfully created\n");
349     }
350
351   /* We are done if no other locking required. */
352
353   if (!use_fcntl && !use_flock && !use_mbx) break;
354
355   /* Open the file for writing. */
356
357   fd = open(filename, O_RDWR + O_APPEND);
358   if (fd < 0)
359     {
360     printf("exim_lock: failed to open %s for writing: %s\n", filename,
361       strerror(errno));
362     yield = 1;
363     goto CLEAN_UP;
364     }
365
366   /* If there is a timeout, implying blocked locking, we don't want to
367   sleep before any retries after this. */
368
369   if (lock_fcntl_timeout > 0 || lock_flock_timeout > 0)
370     sleep_before_retry = FALSE;
371
372   /* Lock using fcntl. There are pros and cons to using a blocking call vs
373   a non-blocking call and retries. Exim is non-blocking by default, but setting
374   a timeout changes it to blocking. */
375
376   if (!use_mbx && (use_fcntl || use_flock))
377     {
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 goto RETRY;   /* Message already output */
389     }
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   struct utimbuf ut;
587   stat(filename, &strestore);
588   i = system(command);
589   ut.actime = strestore.st_atime;
590   ut.modtime = strestore.st_mtime;
591   utime(filename, &ut);
592   }
593 else i = system(command);
594
595 if(i && !quiet) printf("warning: nonzero status %d\n", i);
596
597 /* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
598 lock on the mailbox. This should be a non-blocking lock call, as there is no
599 point in waiting. */
600
601 CLEAN_UP:
602
603 if (md >= 0)
604   {
605   if (apply_lock(fd, F_WRLCK, use_fcntl, 0, use_flock, 0) >= 0)
606     {
607     if (!quiet) printf("exim_lock: %s unlinked - no sharers\n", tempname);
608     unlink(tempname);
609     }
610   else if (!quiet)
611     printf("exim_lock: %s not unlinked - unable to get exclusive mailbox lock\n",
612       tempname);
613   if (close(md) < 0)
614     printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
615   else
616     if (!quiet) printf("exim_lock: %s closed\n", tempname);
617   }
618
619 if (fd >= 0)
620   {
621   if (close(fd) < 0)
622     printf("exim_lock: close %s failed: %s\n", filename, strerror(errno));
623   else
624     if (!quiet) printf("exim_lock: %s closed\n", filename);
625   }
626
627 if (hd >= 0)
628   {
629   if (unlink(lockname) < 0)
630     printf("exim_lock: unlink %s failed: %s\n", lockname, strerror(errno));
631   else
632     if (!quiet) printf("exim_lock: lock file removed\n");
633   }
634
635 return yield;
636 }
637
638 /* End */