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 <pwd.h>
31 
32 /* Not all systems have flock() available. Those that do must define LOCK_SH
33 in sys/file.h. */
34 
35 #ifndef LOCK_SH
36 #define NO_FLOCK
37 #endif
38 
39 
40 typedef unsigned BOOL;
41 #define FALSE 0
42 #define TRUE  1
43 
44 
45 /* Flag for timeout signal handler */
46 
47 static int sigalrm_seen = FALSE;
48 
49 
50 /* We need to pull in strerror() and os_non_restarting_signal() from the
51 os.c source, if they are required for this OS. However, we don't need any of
52 the other stuff in os.c, so force the other macros to omit it. */
53 
54 #ifndef OS_RESTARTING_SIGNAL
55   #define OS_RESTARTING_SIGNAL
56 #endif
57 
58 #ifndef OS_STRSIGNAL
59   #define OS_STRSIGNAL
60 #endif
61 
62 #ifndef OS_STREXIT
63   #define OS_STREXIT
64 #endif
65 
66 #ifndef OS_LOAD_AVERAGE
67   #define OS_LOAD_AVERAGE
68 #endif
69 
70 #ifndef FIND_RUNNING_INTERFACES
71   #define FIND_RUNNING_INTERFACES
72 #endif
73 
74 #ifndef OS_GET_DNS_RESOLVER_RES
75   #define OS_GET_DNS_RESOLVER_RES
76 #endif
77 
78 #include "../src/os.c"
79 
80 
81 
82 /*************************************************
83 *             Timeout handler                    *
84 *************************************************/
85 
86 static void
sigalrm_handler(int sig)87 sigalrm_handler(int sig)
88 {
89 sigalrm_seen = TRUE;
90 }
91 
92 
93 
94 /*************************************************
95 *           Give usage and die                   *
96 *************************************************/
97 
98 static void
usage(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
apply_lock(int fd,int fcntltype,BOOL dofcntl,int fcntltime,BOOL doflock,int flocktime)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 
main(int argc,char ** argv)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   if ((fd = open(filename, O_RDWR + O_APPEND)) < 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     if (apply_lock(fd, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
378         lock_flock_timeout) >= 0)
379       {
380       if (!quiet)
381         {
382         if (use_fcntl) printf("exim_lock: fcntl() lock successfully applied\n");
383         if (use_flock) printf("exim_lock: flock() lock successfully applied\n");
384         }
385       break;
386       }
387     else
388       goto RETRY;   /* Message already output */
389 
390   /* Lock using MBX rules. This is complicated and is documented with the
391   source of the c-client library that goes with Pine and IMAP. What has to
392   be done to interwork correctly is to take out a shared lock on the mailbox,
393   and an exclusive lock on a /tmp file. */
394 
395   else
396     {
397     if (apply_lock(fd, F_RDLCK, use_fcntl, lock_fcntl_timeout, use_flock,
398         lock_flock_timeout) >= 0)
399       {
400       if (!quiet)
401         {
402         if (use_fcntl)
403           printf("exim_lock: fcntl() read lock successfully applied\n");
404         if (use_flock)
405           printf("exim_lock: fcntl() read lock successfully applied\n");
406         }
407       }
408     else goto RETRY;   /* Message already output */
409 
410     if (fstat(fd, &statbuf) < 0)
411       {
412       printf("exim_lock: fstat() of %s failed: %s\n", filename,
413         strerror(errno));
414       yield = 1;
415       goto CLEAN_UP;
416       }
417 
418     /* Set up file in /tmp and check its state if already existing. */
419 
420     sprintf(tempname, "/tmp/.%lx.%lx", (long)statbuf.st_dev,
421       (long)statbuf.st_ino);
422 
423     if (lstat(tempname, &statbuf) >= 0)
424       {
425       if ((statbuf.st_mode & S_IFMT) == S_IFLNK)
426         {
427         printf("exim_lock: symbolic link on lock name %s\n", tempname);
428         yield = 1;
429         goto CLEAN_UP;
430         }
431       if (statbuf.st_nlink > 1)
432         {
433         printf("exim_lock: hard link to lock name %s\n", tempname);
434         yield = 1;
435         goto CLEAN_UP;
436         }
437       }
438 
439     mbx_tmp_oflags = O_RDWR | O_CREAT;
440 #ifdef O_NOFOLLOW
441     mbx_tmp_oflags |= O_NOFOLLOW;
442 #endif
443     md = open(tempname, mbx_tmp_oflags, 0600);
444     if (md < 0)
445       {
446       printf("exim_lock: failed to create mbx lock file %s: %s\n",
447         tempname, strerror(errno));
448       goto CLEAN_UP;
449       }
450 
451     /* security fixes from 2010-05 */
452     if (lstat(tempname, &lstatbuf) < 0)
453       {
454       printf("exim_lock: failed to lstat(%s) after opening it: %s\n",
455           tempname, strerror(errno));
456       goto CLEAN_UP;
457       }
458     if (fstat(md, &statbuf2) < 0)
459       {
460       printf("exim_lock: failed to fstat() open fd of \"%s\": %s\n",
461           tempname, strerror(errno));
462       goto CLEAN_UP;
463       }
464     if ((statbuf2.st_nlink > 1) ||
465         (lstatbuf.st_nlink > 1) ||
466         (!S_ISREG(lstatbuf.st_mode)) ||
467         (lstatbuf.st_dev != statbuf2.st_dev) ||
468         (lstatbuf.st_ino != statbuf2.st_ino))
469       {
470       printf("exim_lock: race condition exploited against us when "
471           "locking \"%s\"\n", tempname);
472       goto CLEAN_UP;
473       }
474 
475     (void)chmod(tempname, 0600);
476 
477     if (apply_lock(md, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
478         lock_flock_timeout) >= 0)
479       {
480       if (!quiet)
481         {
482         if (use_fcntl)
483           printf("exim_lock: fcntl() lock successfully applied to mbx "
484             "lock file %s\n", tempname);
485         if (use_flock)
486           printf("exim_lock: flock() lock successfully applied to mbx "
487             "lock file %s\n", tempname);
488         }
489 
490       /* This test checks for a race condition */
491 
492       if (lstat(tempname, &statbuf) != 0 ||
493           fstat(md, &ostatbuf) != 0 ||
494           statbuf.st_dev != ostatbuf.st_dev ||
495           statbuf.st_ino != ostatbuf.st_ino)
496        {
497        if (!quiet) printf("exim_lock: mbx lock file %s changed between "
498            "creation and locking\n", tempname);
499        goto RETRY;
500        }
501       else break;
502       }
503     else goto RETRY;   /* Message already output */
504     }
505 
506   /* Clean up before retrying */
507 
508   RETRY:
509 
510   if (md >= 0)
511     {
512     if (close(md) < 0)
513       printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
514     else
515       if (!quiet) printf("exim_lock: %s closed\n", tempname);
516     md = -1;
517     }
518 
519   if (fd >= 0)
520     {
521     if (close(fd) < 0)
522       printf("exim_lock: close failed: %s\n", strerror(errno));
523     else
524       if (!quiet) printf("exim_lock: file closed\n");
525     fd = -1;
526     }
527 
528   if (hd >= 0)
529     {
530     if (unlink(lockname) < 0)
531       printf("exim_lock: unlink of %s failed: %s\n", lockname, strerror(errno));
532     else
533       if (!quiet) printf("exim_lock: lock file removed\n");
534     hd = -1;
535     }
536 
537   /* If a blocking call timed out, break the retry loop if the total time
538   so far is not less than than retries * interval. */
539 
540   if (sigalrm_seen &&
541       (j + 1) * ((lock_fcntl_timeout > lock_flock_timeout)?
542         lock_fcntl_timeout : lock_flock_timeout) >=
543           lock_retries * lock_interval)
544     j = lock_retries;
545 
546   /* Wait a bit before retrying, except when it was a blocked fcntl() that
547   caused the problem. */
548 
549   if (j < lock_retries && sleep_before_retry)
550     {
551     printf(" ... waiting\n");
552     sleep(lock_interval);
553     }
554   }
555 
556 if (j >= lock_retries)
557   {
558   printf("exim_lock: locking failed too many times\n");
559   yield = 1;
560   goto CLEAN_UP;
561   }
562 
563 if (!quiet) printf("exim_lock: locking %s succeeded: ", filename);
564 
565 /* If there are no further arguments, run the user's shell; otherwise
566 the next argument is a command to run. */
567 
568 if (i >= argc)
569   {
570   command = getenv("SHELL");
571   if (command == NULL || *command == 0) command = "/bin/sh";
572   if (!quiet) printf("running %s ...\n", command);
573   }
574 else
575   {
576   command = argv[i];
577   if (!quiet) printf("running the command ...\n");
578   }
579 
580 /* Run the command, saving and restoring the times if required. */
581 
582 if (restore_times)
583   {
584   struct stat strestore;
585 #ifdef EXIM_HAVE_FUTIMENS
586   int fd = open(filename, O_RDWR); /* use fd for both get & restore */
587   struct timespec tt[2];
588 
589   if (fd < 0)
590     {
591     printf("open '%s': %s\n", filename, strerror(errno));
592     yield = 1;
593     goto CLEAN_UP;
594     }
595   if (fstat(fd, &strestore) != 0)
596     {
597     printf("fstat '%s': %s\n", filename, strerror(errno));
598     yield = 1;
599     close(fd);
600     goto CLEAN_UP;
601     }
602   i = system(command);
603   tt[0] = strestore.st_atim;
604   tt[1] = strestore.st_mtim;
605   (void) futimens(fd, tt);
606   (void) close(fd);
607 #else
608   struct utimbuf ut;
609 
610   stat(filename, &strestore);
611   i = system(command);
612   ut.actime = strestore.st_atime;
613   ut.modtime = strestore.st_mtime;
614   utime(filename, &ut);
615 #endif
616   }
617 else i = system(command);
618 
619 if(i && !quiet) printf("warning: nonzero status %d\n", i);
620 
621 /* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
622 lock on the mailbox. This should be a non-blocking lock call, as there is no
623 point in waiting. */
624 
625 CLEAN_UP:
626 
627 if (md >= 0)
628   {
629   if (apply_lock(fd, F_WRLCK, use_fcntl, 0, use_flock, 0) >= 0)
630     {
631     if (!quiet) printf("exim_lock: %s unlinked - no sharers\n", tempname);
632     unlink(tempname);
633     }
634   else if (!quiet)
635     printf("exim_lock: %s not unlinked - unable to get exclusive mailbox lock\n",
636       tempname);
637   if (close(md) < 0)
638     printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
639   else
640     if (!quiet) printf("exim_lock: %s closed\n", tempname);
641   }
642 
643 if (fd >= 0)
644   {
645   if (close(fd) < 0)
646     printf("exim_lock: close %s failed: %s\n", filename, strerror(errno));
647   else
648     if (!quiet) printf("exim_lock: %s closed\n", filename);
649   }
650 
651 if (hd >= 0)
652   {
653   if (unlink(lockname) < 0)
654     printf("exim_lock: unlink %s failed: %s\n", lockname, strerror(errno));
655   else
656     if (!quiet) printf("exim_lock: lock file removed\n");
657   }
658 
659 return yield;
660 }
661 
662 /* End */
663