MIME: recode 2231-to-2047 safely. Bug 466
[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 = 0;
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;
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) fstat(hd, &statbuf);
338 (void)close(hd);
339 unlink(hitchname);
340
341 if (rc != 0 && statbuf.st_nlink != 2)
342 {
343 printf("exim_lock: failed to link hitching post to lock file\n");
344 hd = -1;
345 goto RETRY;
346 }
347
348 if (!quiet) printf("exim_lock: lock file successfully created\n");
349 }
350
351 /* We are done if no other locking required. */
352
353 if (!use_fcntl && !use_flock && !use_mbx) break;
354
355 /* Open the file for writing. */
356
357 fd = open(filename, O_RDWR + O_APPEND);
358 if (fd < 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 {
378 if (apply_lock(fd, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
379 lock_flock_timeout) >= 0)
380 {
381 if (!quiet)
382 {
383 if (use_fcntl) printf("exim_lock: fcntl() lock successfully applied\n");
384 if (use_flock) printf("exim_lock: flock() lock successfully applied\n");
385 }
386 break;
387 }
388 else goto RETRY; /* Message already output */
389 }
390
391 /* Lock using MBX rules. This is complicated and is documented with the
392 source of the c-client library that goes with Pine and IMAP. What has to
393 be done to interwork correctly is to take out a shared lock on the mailbox,
394 and an exclusive lock on a /tmp file. */
395
396 else
397 {
398 if (apply_lock(fd, F_RDLCK, use_fcntl, lock_fcntl_timeout, use_flock,
399 lock_flock_timeout) >= 0)
400 {
401 if (!quiet)
402 {
403 if (use_fcntl)
404 printf("exim_lock: fcntl() read lock successfully applied\n");
405 if (use_flock)
406 printf("exim_lock: fcntl() read lock successfully applied\n");
407 }
408 }
409 else goto RETRY; /* Message already output */
410
411 if (fstat(fd, &statbuf) < 0)
412 {
413 printf("exim_lock: fstat() of %s failed: %s\n", filename,
414 strerror(errno));
415 yield = 1;
416 goto CLEAN_UP;
417 }
418
419 /* Set up file in /tmp and check its state if already existing. */
420
421 sprintf(tempname, "/tmp/.%lx.%lx", (long)statbuf.st_dev,
422 (long)statbuf.st_ino);
423
424 if (lstat(tempname, &statbuf) >= 0)
425 {
426 if ((statbuf.st_mode & S_IFMT) == S_IFLNK)
427 {
428 printf("exim_lock: symbolic link on lock name %s\n", tempname);
429 yield = 1;
430 goto CLEAN_UP;
431 }
432 if (statbuf.st_nlink > 1)
433 {
434 printf("exim_lock: hard link to lock name %s\n", tempname);
435 yield = 1;
436 goto CLEAN_UP;
437 }
438 }
439
440 mbx_tmp_oflags = O_RDWR | O_CREAT;
441 #ifdef O_NOFOLLOW
442 mbx_tmp_oflags |= O_NOFOLLOW;
443 #endif
444 md = open(tempname, mbx_tmp_oflags, 0600);
445 if (md < 0)
446 {
447 printf("exim_lock: failed to create mbx lock file %s: %s\n",
448 tempname, strerror(errno));
449 goto CLEAN_UP;
450 }
451
452 /* security fixes from 2010-05 */
453 if (lstat(tempname, &lstatbuf) < 0)
454 {
455 printf("exim_lock: failed to lstat(%s) after opening it: %s\n",
456 tempname, strerror(errno));
457 goto CLEAN_UP;
458 }
459 if (fstat(md, &statbuf2) < 0)
460 {
461 printf("exim_lock: failed to fstat() open fd of \"%s\": %s\n",
462 tempname, strerror(errno));
463 goto CLEAN_UP;
464 }
465 if ((statbuf2.st_nlink > 1) ||
466 (lstatbuf.st_nlink > 1) ||
467 (!S_ISREG(lstatbuf.st_mode)) ||
468 (lstatbuf.st_dev != statbuf2.st_dev) ||
469 (lstatbuf.st_ino != statbuf2.st_ino))
470 {
471 printf("exim_lock: race condition exploited against us when "
472 "locking \"%s\"\n", tempname);
473 goto CLEAN_UP;
474 }
475
476 (void)chmod(tempname, 0600);
477
478 if (apply_lock(md, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
479 lock_flock_timeout) >= 0)
480 {
481 if (!quiet)
482 {
483 if (use_fcntl)
484 printf("exim_lock: fcntl() lock successfully applied to mbx "
485 "lock file %s\n", tempname);
486 if (use_flock)
487 printf("exim_lock: flock() lock successfully applied to mbx "
488 "lock file %s\n", tempname);
489 }
490
491 /* This test checks for a race condition */
492
493 if (lstat(tempname, &statbuf) != 0 ||
494 fstat(md, &ostatbuf) != 0 ||
495 statbuf.st_dev != ostatbuf.st_dev ||
496 statbuf.st_ino != ostatbuf.st_ino)
497 {
498 if (!quiet) printf("exim_lock: mbx lock file %s changed between "
499 "creation and locking\n", tempname);
500 goto RETRY;
501 }
502 else break;
503 }
504 else goto RETRY; /* Message already output */
505 }
506
507 /* Clean up before retrying */
508
509 RETRY:
510
511 if (md >= 0)
512 {
513 if (close(md) < 0)
514 printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
515 else
516 if (!quiet) printf("exim_lock: %s closed\n", tempname);
517 md = -1;
518 }
519
520 if (fd >= 0)
521 {
522 if (close(fd) < 0)
523 printf("exim_lock: close failed: %s\n", strerror(errno));
524 else
525 if (!quiet) printf("exim_lock: file closed\n");
526 fd = -1;
527 }
528
529 if (hd >= 0)
530 {
531 if (unlink(lockname) < 0)
532 printf("exim_lock: unlink of %s failed: %s\n", lockname, strerror(errno));
533 else
534 if (!quiet) printf("exim_lock: lock file removed\n");
535 hd = -1;
536 }
537
538 /* If a blocking call timed out, break the retry loop if the total time
539 so far is not less than than retries * interval. */
540
541 if (sigalrm_seen &&
542 (j + 1) * ((lock_fcntl_timeout > lock_flock_timeout)?
543 lock_fcntl_timeout : lock_flock_timeout) >=
544 lock_retries * lock_interval)
545 j = lock_retries;
546
547 /* Wait a bit before retrying, except when it was a blocked fcntl() that
548 caused the problem. */
549
550 if (j < lock_retries && sleep_before_retry)
551 {
552 printf(" ... waiting\n");
553 sleep(lock_interval);
554 }
555 }
556
557 if (j >= lock_retries)
558 {
559 printf("exim_lock: locking failed too many times\n");
560 yield = 1;
561 goto CLEAN_UP;
562 }
563
564 if (!quiet) printf("exim_lock: locking %s succeeded: ", filename);
565
566 /* If there are no further arguments, run the user's shell; otherwise
567 the next argument is a command to run. */
568
569 if (i >= argc)
570 {
571 command = getenv("SHELL");
572 if (command == NULL || *command == 0) command = "/bin/sh";
573 if (!quiet) printf("running %s ...\n", command);
574 }
575 else
576 {
577 command = argv[i];
578 if (!quiet) printf("running the command ...\n");
579 }
580
581 /* Run the command, saving and restoring the times if required. */
582
583 if (restore_times)
584 {
585 struct stat strestore;
586 struct utimbuf ut;
587 stat(filename, &strestore);
588 i = system(command);
589 ut.actime = strestore.st_atime;
590 ut.modtime = strestore.st_mtime;
591 utime(filename, &ut);
592 }
593 else i = system(command);
594
595 if(i && !quiet) printf("warning: nonzero status %d\n", i);
596
597 /* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
598 lock on the mailbox. This should be a non-blocking lock call, as there is no
599 point in waiting. */
600
601 CLEAN_UP:
602
603 if (md >= 0)
604 {
605 if (apply_lock(fd, F_WRLCK, use_fcntl, 0, use_flock, 0) >= 0)
606 {
607 if (!quiet) printf("exim_lock: %s unlinked - no sharers\n", tempname);
608 unlink(tempname);
609 }
610 else if (!quiet)
611 printf("exim_lock: %s not unlinked - unable to get exclusive mailbox lock\n",
612 tempname);
613 if (close(md) < 0)
614 printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
615 else
616 if (!quiet) printf("exim_lock: %s closed\n", tempname);
617 }
618
619 if (fd >= 0)
620 {
621 if (close(fd) < 0)
622 printf("exim_lock: close %s failed: %s\n", filename, strerror(errno));
623 else
624 if (!quiet) printf("exim_lock: %s closed\n", filename);
625 }
626
627 if (hd >= 0)
628 {
629 if (unlink(lockname) < 0)
630 printf("exim_lock: unlink %s failed: %s\n", lockname, strerror(errno));
631 else
632 if (!quiet) printf("exim_lock: lock file removed\n");
633 }
634
635 return yield;
636 }
637
638 /* End */