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