Supress TLS/Env warnings in checking and listing mode
[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 */