Utilites: fix exim_lock
[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 = FALSE;
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, rc2;
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)
338      rc2 = fstat(hd, &statbuf);
339     (void)close(hd);
340     unlink(hitchname);
341
342     if (rc != 0 && (rc2 != 0 || statbuf.st_nlink != 2))
343       {
344       printf("exim_lock: failed to link hitching post to lock file\n");
345       hd = -1;
346       goto RETRY;
347       }
348
349     if (!quiet) printf("exim_lock: lock file successfully created\n");
350     }
351
352   /* We are done if no other locking required. */
353
354   if (!use_fcntl && !use_flock && !use_mbx) break;
355
356   /* Open the file for writing. */
357
358   fd = open(filename, O_RDWR + O_APPEND);
359   if (fd < 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     {
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 goto RETRY;   /* Message already output */
390     }
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   struct utimbuf ut;
588   stat(filename, &strestore);
589   i = system(command);
590   ut.actime = strestore.st_atime;
591   ut.modtime = strestore.st_mtime;
592   utime(filename, &ut);
593   }
594 else i = system(command);
595
596 if(i && !quiet) printf("warning: nonzero status %d\n", i);
597
598 /* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
599 lock on the mailbox. This should be a non-blocking lock call, as there is no
600 point in waiting. */
601
602 CLEAN_UP:
603
604 if (md >= 0)
605   {
606   if (apply_lock(fd, F_WRLCK, use_fcntl, 0, use_flock, 0) >= 0)
607     {
608     if (!quiet) printf("exim_lock: %s unlinked - no sharers\n", tempname);
609     unlink(tempname);
610     }
611   else if (!quiet)
612     printf("exim_lock: %s not unlinked - unable to get exclusive mailbox lock\n",
613       tempname);
614   if (close(md) < 0)
615     printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
616   else
617     if (!quiet) printf("exim_lock: %s closed\n", tempname);
618   }
619
620 if (fd >= 0)
621   {
622   if (close(fd) < 0)
623     printf("exim_lock: close %s failed: %s\n", filename, strerror(errno));
624   else
625     if (!quiet) printf("exim_lock: %s closed\n", filename);
626   }
627
628 if (hd >= 0)
629   {
630   if (unlink(lockname) < 0)
631     printf("exim_lock: unlink %s failed: %s\n", lockname, strerror(errno));
632   else
633     if (!quiet) printf("exim_lock: lock file removed\n");
634   }
635
636 return yield;
637 }
638
639 /* End */