Document match_*/inlist changes (before coding starts)
[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 #include "../src/os.c"
74
75
76
77 /*************************************************
78 * Timeout handler *
79 *************************************************/
80
81 static void
82 sigalrm_handler(int sig)
83 {
84 sig = sig; /* Keep picky compilers happy */
85 sigalrm_seen = TRUE;
86 }
87
88
89
90 /*************************************************
91 * Give usage and die *
92 *************************************************/
93
94 static void
95 usage(void)
96 {
97 printf("usage: exim_lock [-v] [-q] [-lockfile] [-fcntl] [-flock] [-mbx]\n"
98 " [-retries <n>] [-interval <n>] [-timeout <n>] [-restore-times]\n"
99 " <file name> [command]\n");
100 exit(1);
101 }
102
103
104
105 /*************************************************
106 * Apply a lock to a file descriptor *
107 *************************************************/
108
109 static int
110 apply_lock(int fd, int fcntltype, BOOL dofcntl, int fcntltime, BOOL doflock,
111 int flocktime)
112 {
113 int yield = 0;
114 int save_errno;
115 struct flock lock_data;
116 lock_data.l_type = fcntltype;
117 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
118
119 sigalrm_seen = FALSE;
120
121 if (dofcntl)
122 {
123 if (fcntltime > 0)
124 {
125 os_non_restarting_signal(SIGALRM, sigalrm_handler);
126 alarm(fcntltime);
127 yield = fcntl(fd, F_SETLKW, &lock_data);
128 save_errno = errno;
129 alarm(0);
130 errno = save_errno;
131 }
132 else yield = fcntl(fd, F_SETLK, &lock_data);
133 if (yield < 0) printf("exim_lock: fcntl() failed: %s\n", strerror(errno));
134 }
135
136 #ifndef NO_FLOCK
137 if (doflock && (yield >= 0))
138 {
139 int flocktype = (fcntltype == F_WRLCK)? LOCK_EX : LOCK_SH;
140 if (flocktime > 0)
141 {
142 os_non_restarting_signal(SIGALRM, sigalrm_handler);
143 alarm(flocktime);
144 yield = flock(fd, flocktype);
145 save_errno = errno;
146 alarm(0);
147 errno = save_errno;
148 }
149 else yield = flock(fd, flocktype | LOCK_NB);
150 if (yield < 0) printf("exim_lock: flock() failed: %s\n", strerror(errno));
151 }
152 #endif
153
154 return yield;
155 }
156
157
158
159 /*************************************************
160 * The exim_lock program *
161 *************************************************/
162
163 int main(int argc, char **argv)
164 {
165 int lock_retries = 10;
166 int lock_interval = 3;
167 int lock_fcntl_timeout = 0;
168 int lock_flock_timeout = 0;
169 int i, j, len;
170 int fd = -1;
171 int hd = -1;
172 int md = -1;
173 int yield = 0;
174 int now = time(NULL);
175 BOOL use_lockfile = FALSE;
176 BOOL use_fcntl = FALSE;
177 BOOL use_flock = FALSE;
178 BOOL use_mbx = FALSE;
179 BOOL verbose = FALSE;
180 BOOL quiet = FALSE;
181 BOOL restore_times = FALSE;
182 char *filename;
183 char *lockname = NULL, *hitchname = NULL;
184 char *primary_hostname;
185 const char *command;
186 struct utsname s;
187 char buffer[256];
188 char tempname[256];
189
190 /* Decode options */
191
192 for (i = 1; i < argc; i++)
193 {
194 char *arg = argv[i];
195 if (*arg != '-') break;
196 if (strcmp(arg, "-fcntl") == 0) use_fcntl = TRUE;
197 else if (strcmp(arg, "-flock") == 0) use_flock = TRUE;
198 else if (strcmp(arg, "-lockfile") == 0) use_lockfile = TRUE;
199 else if (strcmp(arg, "-mbx") == 0) use_mbx = TRUE;
200 else if (strcmp(arg, "-v") == 0) verbose = TRUE;
201 else if (strcmp(arg, "-q") == 0) quiet = TRUE;
202 else if (strcmp(arg, "-restore-times") == 0) restore_times = TRUE;
203 else if (++i < argc)
204 {
205 int value = atoi(argv[i]);
206 if (strcmp(arg, "-retries") == 0) lock_retries = value;
207 else if (strcmp(arg, "-interval") == 0) lock_interval = value;
208 else if (strcmp(arg, "-timeout") == 0)
209 lock_fcntl_timeout = lock_flock_timeout = value;
210 else usage();
211 }
212 else usage();
213 }
214
215 if (quiet) verbose = 0;
216
217 /* Can't use flock() if the OS doesn't provide it */
218
219 #ifdef NO_FLOCK
220 if (use_flock)
221 {
222 printf("exim_lock: can't use flock() because it was not available in the\n"
223 " operating system when exim_lock was compiled\n");
224 exit(1);
225 }
226 #endif
227
228 /* Default is to use lockfiles and fcntl(). */
229
230 if (!use_lockfile && !use_fcntl && !use_flock && !use_mbx)
231 use_lockfile = use_fcntl = TRUE;
232
233 /* Default fcntl() for use with mbx */
234
235 if (use_mbx && !use_fcntl && !use_flock) use_fcntl = TRUE;
236
237 /* Unset unused timeouts */
238
239 if (!use_fcntl) lock_fcntl_timeout = 0;
240 if (!use_flock) lock_flock_timeout = 0;
241
242 /* A file name is required */
243
244 if (i >= argc) usage();
245
246 filename = argv[i++];
247
248 /* Expand file names starting with ~ */
249
250 if (*filename == '~')
251 {
252 struct passwd *pw;
253
254 if (*(++filename) == '/')
255 pw = getpwuid(getuid());
256 else
257 {
258 char *s = buffer;
259 while (*filename != 0 && *filename != '/')
260 *s++ = *filename++;
261 *s = 0;
262 pw = getpwnam(buffer);
263 }
264
265 if (pw == NULL)
266 {
267 printf("exim_lock: unable to expand file name %s\n", argv[i-1]);
268 exit(1);
269 }
270
271 if ((int)strlen(pw->pw_dir) + (int)strlen(filename) + 1 > sizeof(buffer))
272 {
273 printf("exim_lock: expanded file name %s%s is too long", pw->pw_dir,
274 filename);
275 exit(1);
276 }
277
278 strcpy(buffer, pw->pw_dir);
279 strcat(buffer, filename);
280 filename = buffer;
281 }
282
283 /* If using a lock file, prepare by creating the lock file name and
284 the hitching post name. */
285
286 if (use_lockfile)
287 {
288 if (uname(&s) < 0)
289 {
290 printf("exim_lock: failed to find host name using uname()\n");
291 exit(1);
292 }
293 primary_hostname = s.nodename;
294
295 len = (int)strlen(filename);
296 lockname = malloc(len + 8);
297 sprintf(lockname, "%s.lock", filename);
298 hitchname = malloc(len + 32 + (int)strlen(primary_hostname));
299 sprintf(hitchname, "%s.%s.%08x.%08x", lockname, primary_hostname,
300 now, (int)getpid());
301
302 if (verbose)
303 printf("exim_lock: lockname = %s\n hitchname = %s\n", lockname,
304 hitchname);
305 }
306
307 /* Locking retry loop */
308
309 for (j = 0; j < lock_retries; j++)
310 {
311 int sleep_before_retry = TRUE;
312 struct stat statbuf, ostatbuf, lstatbuf, statbuf2;
313 int mbx_tmp_oflags;
314
315 /* Try to build a lock file if so configured */
316
317 if (use_lockfile)
318 {
319 int rc;
320 if (verbose) printf("exim_lock: creating lock file\n");
321 hd = open(hitchname, O_WRONLY | O_CREAT | O_EXCL, 0440);
322 if (hd < 0)
323 {
324 printf("exim_lock: failed to create hitching post %s: %s\n", hitchname,
325 strerror(errno));
326 exit(1);
327 }
328
329 /* Apply hitching post algorithm. */
330
331 if ((rc = link(hitchname, lockname)) != 0) fstat(hd, &statbuf);
332 (void)close(hd);
333 unlink(hitchname);
334
335 if (rc != 0 && statbuf.st_nlink != 2)
336 {
337 printf("exim_lock: failed to link hitching post to lock file\n");
338 hd = -1;
339 goto RETRY;
340 }
341
342 if (!quiet) printf("exim_lock: lock file successfully created\n");
343 }
344
345 /* We are done if no other locking required. */
346
347 if (!use_fcntl && !use_flock && !use_mbx) break;
348
349 /* Open the file for writing. */
350
351 fd = open(filename, O_RDWR + O_APPEND);
352 if (fd < 0)
353 {
354 printf("exim_lock: failed to open %s for writing: %s\n", filename,
355 strerror(errno));
356 yield = 1;
357 goto CLEAN_UP;
358 }
359
360 /* If there is a timeout, implying blocked locking, we don't want to
361 sleep before any retries after this. */
362
363 if (lock_fcntl_timeout > 0 || lock_flock_timeout > 0)
364 sleep_before_retry = FALSE;
365
366 /* Lock using fcntl. There are pros and cons to using a blocking call vs
367 a non-blocking call and retries. Exim is non-blocking by default, but setting
368 a timeout changes it to blocking. */
369
370 if (!use_mbx && (use_fcntl || use_flock))
371 {
372 if (apply_lock(fd, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
373 lock_flock_timeout) >= 0)
374 {
375 if (!quiet)
376 {
377 if (use_fcntl) printf("exim_lock: fcntl() lock successfully applied\n");
378 if (use_flock) printf("exim_lock: flock() lock successfully applied\n");
379 }
380 break;
381 }
382 else goto RETRY; /* Message already output */
383 }
384
385 /* Lock using MBX rules. This is complicated and is documented with the
386 source of the c-client library that goes with Pine and IMAP. What has to
387 be done to interwork correctly is to take out a shared lock on the mailbox,
388 and an exclusive lock on a /tmp file. */
389
390 else
391 {
392 if (apply_lock(fd, F_RDLCK, use_fcntl, lock_fcntl_timeout, use_flock,
393 lock_flock_timeout) >= 0)
394 {
395 if (!quiet)
396 {
397 if (use_fcntl)
398 printf("exim_lock: fcntl() read lock successfully applied\n");
399 if (use_flock)
400 printf("exim_lock: fcntl() read lock successfully applied\n");
401 }
402 }
403 else goto RETRY; /* Message already output */
404
405 if (fstat(fd, &statbuf) < 0)
406 {
407 printf("exim_lock: fstat() of %s failed: %s\n", filename,
408 strerror(errno));
409 yield = 1;
410 goto CLEAN_UP;
411 }
412
413 /* Set up file in /tmp and check its state if already existing. */
414
415 sprintf(tempname, "/tmp/.%lx.%lx", (long)statbuf.st_dev,
416 (long)statbuf.st_ino);
417
418 if (lstat(tempname, &statbuf) >= 0)
419 {
420 if ((statbuf.st_mode & S_IFMT) == S_IFLNK)
421 {
422 printf("exim_lock: symbolic link on lock name %s\n", tempname);
423 yield = 1;
424 goto CLEAN_UP;
425 }
426 if (statbuf.st_nlink > 1)
427 {
428 printf("exim_lock: hard link to lock name %s\n", tempname);
429 yield = 1;
430 goto CLEAN_UP;
431 }
432 }
433
434 mbx_tmp_oflags = O_RDWR | O_CREAT;
435 #ifdef O_NOFOLLOW
436 mbx_tmp_oflags |= O_NOFOLLOW;
437 #endif
438 md = open(tempname, mbx_tmp_oflags, 0600);
439 if (md < 0)
440 {
441 printf("exim_lock: failed to create mbx lock file %s: %s\n",
442 tempname, strerror(errno));
443 goto CLEAN_UP;
444 }
445
446 /* security fixes from 2010-05 */
447 if (lstat(tempname, &lstatbuf) < 0)
448 {
449 printf("exim_lock: failed to lstat(%s) after opening it: %s\n",
450 tempname, strerror(errno));
451 goto CLEAN_UP;
452 }
453 if (fstat(md, &statbuf2) < 0)
454 {
455 printf("exim_lock: failed to fstat() open fd of \"%s\": %s\n",
456 tempname, strerror(errno));
457 goto CLEAN_UP;
458 }
459 if ((statbuf2.st_nlink > 1) ||
460 (lstatbuf.st_nlink > 1) ||
461 (!S_ISREG(lstatbuf.st_mode)) ||
462 (lstatbuf.st_dev != statbuf2.st_dev) ||
463 (lstatbuf.st_ino != statbuf2.st_ino))
464 {
465 printf("exim_lock: race condition exploited against us when "
466 "locking \"%s\"\n", tempname);
467 goto CLEAN_UP;
468 }
469
470 (void)chmod(tempname, 0600);
471
472 if (apply_lock(md, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
473 lock_flock_timeout) >= 0)
474 {
475 if (!quiet)
476 {
477 if (use_fcntl)
478 printf("exim_lock: fcntl() lock successfully applied to mbx "
479 "lock file %s\n", tempname);
480 if (use_flock)
481 printf("exim_lock: flock() lock successfully applied to mbx "
482 "lock file %s\n", tempname);
483 }
484
485 /* This test checks for a race condition */
486
487 if (lstat(tempname, &statbuf) != 0 ||
488 fstat(md, &ostatbuf) != 0 ||
489 statbuf.st_dev != ostatbuf.st_dev ||
490 statbuf.st_ino != ostatbuf.st_ino)
491 {
492 if (!quiet) printf("exim_lock: mbx lock file %s changed between "
493 "creation and locking\n", tempname);
494 goto RETRY;
495 }
496 else break;
497 }
498 else goto RETRY; /* Message already output */
499 }
500
501 /* Clean up before retrying */
502
503 RETRY:
504
505 if (md >= 0)
506 {
507 if (close(md) < 0)
508 printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
509 else
510 if (!quiet) printf("exim_lock: %s closed\n", tempname);
511 md = -1;
512 }
513
514 if (fd >= 0)
515 {
516 if (close(fd) < 0)
517 printf("exim_lock: close failed: %s\n", strerror(errno));
518 else
519 if (!quiet) printf("exim_lock: file closed\n");
520 fd = -1;
521 }
522
523 if (hd >= 0)
524 {
525 if (unlink(lockname) < 0)
526 printf("exim_lock: unlink of %s failed: %s\n", lockname, strerror(errno));
527 else
528 if (!quiet) printf("exim_lock: lock file removed\n");
529 hd = -1;
530 }
531
532 /* If a blocking call timed out, break the retry loop if the total time
533 so far is not less than than retries * interval. */
534
535 if (sigalrm_seen &&
536 (j + 1) * ((lock_fcntl_timeout > lock_flock_timeout)?
537 lock_fcntl_timeout : lock_flock_timeout) >=
538 lock_retries * lock_interval)
539 j = lock_retries;
540
541 /* Wait a bit before retrying, except when it was a blocked fcntl() that
542 caused the problem. */
543
544 if (j < lock_retries && sleep_before_retry)
545 {
546 printf(" ... waiting\n");
547 sleep(lock_interval);
548 }
549 }
550
551 if (j >= lock_retries)
552 {
553 printf("exim_lock: locking failed too many times\n");
554 yield = 1;
555 goto CLEAN_UP;
556 }
557
558 if (!quiet) printf("exim_lock: locking %s succeeded: ", filename);
559
560 /* If there are no further arguments, run the user's shell; otherwise
561 the next argument is a command to run. */
562
563 if (i >= argc)
564 {
565 command = getenv("SHELL");
566 if (command == NULL || *command == 0) command = "/bin/sh";
567 if (!quiet) printf("running %s ...\n", command);
568 }
569 else
570 {
571 command = argv[i];
572 if (!quiet) printf("running the command ...\n");
573 }
574
575 /* Run the command, saving and restoring the times if required. */
576
577 if (restore_times)
578 {
579 struct stat strestore;
580 struct utimbuf ut;
581 stat(filename, &strestore);
582 (void)system(command);
583 ut.actime = strestore.st_atime;
584 ut.modtime = strestore.st_mtime;
585 utime(filename, &ut);
586 }
587 else (void)system(command);
588
589 /* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
590 lock on the mailbox. This should be a non-blocking lock call, as there is no
591 point in waiting. */
592
593 CLEAN_UP:
594
595 if (md >= 0)
596 {
597 if (apply_lock(fd, F_WRLCK, use_fcntl, 0, use_flock, 0) >= 0)
598 {
599 if (!quiet) printf("exim_lock: %s unlinked - no sharers\n", tempname);
600 unlink(tempname);
601 }
602 else if (!quiet)
603 printf("exim_lock: %s not unlinked - unable to get exclusive mailbox lock\n",
604 tempname);
605 if (close(md) < 0)
606 printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
607 else
608 if (!quiet) printf("exim_lock: %s closed\n", tempname);
609 }
610
611 if (fd >= 0)
612 {
613 if (close(fd) < 0)
614 printf("exim_lock: close %s failed: %s\n", filename, strerror(errno));
615 else
616 if (!quiet) printf("exim_lock: %s closed\n", filename);
617 }
618
619 if (hd >= 0)
620 {
621 if (unlink(lockname) < 0)
622 printf("exim_lock: unlink %s failed: %s\n", lockname, strerror(errno));
623 else
624 if (!quiet) printf("exim_lock: lock file removed\n");
625 }
626
627 return yield;
628 }
629
630 /* End */