Import Upstream version 4.89
[hcoop/debian/exim4.git] / 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 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 if ((fd = open(filename, O_RDWR + O_APPEND)) < 0)
361 {
362 printf("exim_lock: failed to open %s for writing: %s\n", filename,
363 strerror(errno));
364 yield = 1;
365 goto CLEAN_UP;
366 }
367
368 /* If there is a timeout, implying blocked locking, we don't want to
369 sleep before any retries after this. */
370
371 if (lock_fcntl_timeout > 0 || lock_flock_timeout > 0)
372 sleep_before_retry = FALSE;
373
374 /* Lock using fcntl. There are pros and cons to using a blocking call vs
375 a non-blocking call and retries. Exim is non-blocking by default, but setting
376 a timeout changes it to blocking. */
377
378 if (!use_mbx && (use_fcntl || use_flock))
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
390 goto RETRY; /* Message already output */
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 #ifdef EXIM_HAVE_OPENAT
588 int fd = open(filename, O_RDWR); /* use fd for both get & restore */
589 struct timespec tt[2];
590
591 if (fd < 0)
592 {
593 printf("open '%s': %s\n", filename, strerror(errno));
594 yield = 1;
595 goto CLEAN_UP;
596 }
597 if (fstat(fd, &strestore) != 0)
598 {
599 printf("fstat '%s': %s\n", filename, strerror(errno));
600 yield = 1;
601 close(fd);
602 goto CLEAN_UP;
603 }
604 i = system(command);
605 tt[0] = strestore.st_atim;
606 tt[1] = strestore.st_mtim;
607 (void) futimens(fd, tt);
608 (void) close(fd);
609 #else
610 struct utimbuf ut;
611
612 stat(filename, &strestore);
613 i = system(command);
614 ut.actime = strestore.st_atime;
615 ut.modtime = strestore.st_mtime;
616 utime(filename, &ut);
617 #endif
618 }
619 else i = system(command);
620
621 if(i && !quiet) printf("warning: nonzero status %d\n", i);
622
623 /* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
624 lock on the mailbox. This should be a non-blocking lock call, as there is no
625 point in waiting. */
626
627 CLEAN_UP:
628
629 if (md >= 0)
630 {
631 if (apply_lock(fd, F_WRLCK, use_fcntl, 0, use_flock, 0) >= 0)
632 {
633 if (!quiet) printf("exim_lock: %s unlinked - no sharers\n", tempname);
634 unlink(tempname);
635 }
636 else if (!quiet)
637 printf("exim_lock: %s not unlinked - unable to get exclusive mailbox lock\n",
638 tempname);
639 if (close(md) < 0)
640 printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
641 else
642 if (!quiet) printf("exim_lock: %s closed\n", tempname);
643 }
644
645 if (fd >= 0)
646 {
647 if (close(fd) < 0)
648 printf("exim_lock: close %s failed: %s\n", filename, strerror(errno));
649 else
650 if (!quiet) printf("exim_lock: %s closed\n", filename);
651 }
652
653 if (hd >= 0)
654 {
655 if (unlink(lockname) < 0)
656 printf("exim_lock: unlink %s failed: %s\n", lockname, strerror(errno));
657 else
658 if (!quiet) printf("exim_lock: lock file removed\n");
659 }
660
661 return yield;
662 }
663
664 /* End */