Make BOOL unsigned; fix resulting latent bugs
[exim.git] / src / src / exim_lock.c
... / ...
CommitLineData
1/* A program to lock a file exactly as Exim would, for investigation of
2interlocking problems.
3
4Options: -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
9Default is -fcntl -lockfile.
10
11Argument: the name of the lock file
12
13Copyright (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
34in sys/file.h. */
35
36#ifndef LOCK_SH
37#define NO_FLOCK
38#endif
39
40
41typedef unsigned BOOL;
42#define FALSE 0
43#define TRUE 1
44
45
46/* Flag for timeout signal handler */
47
48static int sigalrm_seen = FALSE;
49
50
51/* We need to pull in strerror() and os_non_restarting_signal() from the
52os.c source, if they are required for this OS. However, we don't need any of
53the 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
87static void
88sigalrm_handler(int sig)
89{
90sig = sig; /* Keep picky compilers happy */
91sigalrm_seen = TRUE;
92}
93
94
95
96/*************************************************
97* Give usage and die *
98*************************************************/
99
100static void
101usage(void)
102{
103printf("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");
106exit(1);
107}
108
109
110
111/*************************************************
112* Apply a lock to a file descriptor *
113*************************************************/
114
115static int
116apply_lock(int fd, int fcntltype, BOOL dofcntl, int fcntltime, BOOL doflock,
117 int flocktime)
118{
119int yield = 0;
120int save_errno;
121struct flock lock_data;
122lock_data.l_type = fcntltype;
123lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
124
125sigalrm_seen = FALSE;
126
127if (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
143if (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
160return yield;
161}
162
163
164
165/*************************************************
166* The exim_lock program *
167*************************************************/
168
169int main(int argc, char **argv)
170{
171int lock_retries = 10;
172int lock_interval = 3;
173int lock_fcntl_timeout = 0;
174int lock_flock_timeout = 0;
175int i, j, len;
176int fd = -1;
177int hd = -1;
178int md = -1;
179int yield = 0;
180time_t now = time(NULL);
181BOOL use_lockfile = FALSE;
182BOOL use_fcntl = FALSE;
183BOOL use_flock = FALSE;
184BOOL use_mbx = FALSE;
185BOOL verbose = FALSE;
186BOOL quiet = FALSE;
187BOOL restore_times = FALSE;
188char *filename;
189char *lockname = NULL, *hitchname = NULL;
190char *primary_hostname;
191const char *command;
192struct utsname s;
193char buffer[256];
194char tempname[256];
195
196/* Decode options */
197
198for (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
221if (quiet) verbose = FALSE;
222
223/* Can't use flock() if the OS doesn't provide it */
224
225#ifdef NO_FLOCK
226if (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
236if (!use_lockfile && !use_fcntl && !use_flock && !use_mbx)
237 use_lockfile = use_fcntl = TRUE;
238
239/* Default fcntl() for use with mbx */
240
241if (use_mbx && !use_fcntl && !use_flock) use_fcntl = TRUE;
242
243/* Unset unused timeouts */
244
245if (!use_fcntl) lock_fcntl_timeout = 0;
246if (!use_flock) lock_flock_timeout = 0;
247
248/* A file name is required */
249
250if (i >= argc) usage();
251
252filename = argv[i++];
253
254/* Expand file names starting with ~ */
255
256if (*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
290the hitching post name. */
291
292if (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
317for (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 fd = open(filename, O_RDWR + O_APPEND);
361 if (fd < 0)
362 {
363 printf("exim_lock: failed to open %s for writing: %s\n", filename,
364 strerror(errno));
365 yield = 1;
366 goto CLEAN_UP;
367 }
368
369 /* If there is a timeout, implying blocked locking, we don't want to
370 sleep before any retries after this. */
371
372 if (lock_fcntl_timeout > 0 || lock_flock_timeout > 0)
373 sleep_before_retry = FALSE;
374
375 /* Lock using fcntl. There are pros and cons to using a blocking call vs
376 a non-blocking call and retries. Exim is non-blocking by default, but setting
377 a timeout changes it to blocking. */
378
379 if (!use_mbx && (use_fcntl || use_flock))
380 {
381 if (apply_lock(fd, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
382 lock_flock_timeout) >= 0)
383 {
384 if (!quiet)
385 {
386 if (use_fcntl) printf("exim_lock: fcntl() lock successfully applied\n");
387 if (use_flock) printf("exim_lock: flock() lock successfully applied\n");
388 }
389 break;
390 }
391 else goto RETRY; /* Message already output */
392 }
393
394 /* Lock using MBX rules. This is complicated and is documented with the
395 source of the c-client library that goes with Pine and IMAP. What has to
396 be done to interwork correctly is to take out a shared lock on the mailbox,
397 and an exclusive lock on a /tmp file. */
398
399 else
400 {
401 if (apply_lock(fd, F_RDLCK, use_fcntl, lock_fcntl_timeout, use_flock,
402 lock_flock_timeout) >= 0)
403 {
404 if (!quiet)
405 {
406 if (use_fcntl)
407 printf("exim_lock: fcntl() read lock successfully applied\n");
408 if (use_flock)
409 printf("exim_lock: fcntl() read lock successfully applied\n");
410 }
411 }
412 else goto RETRY; /* Message already output */
413
414 if (fstat(fd, &statbuf) < 0)
415 {
416 printf("exim_lock: fstat() of %s failed: %s\n", filename,
417 strerror(errno));
418 yield = 1;
419 goto CLEAN_UP;
420 }
421
422 /* Set up file in /tmp and check its state if already existing. */
423
424 sprintf(tempname, "/tmp/.%lx.%lx", (long)statbuf.st_dev,
425 (long)statbuf.st_ino);
426
427 if (lstat(tempname, &statbuf) >= 0)
428 {
429 if ((statbuf.st_mode & S_IFMT) == S_IFLNK)
430 {
431 printf("exim_lock: symbolic link on lock name %s\n", tempname);
432 yield = 1;
433 goto CLEAN_UP;
434 }
435 if (statbuf.st_nlink > 1)
436 {
437 printf("exim_lock: hard link to lock name %s\n", tempname);
438 yield = 1;
439 goto CLEAN_UP;
440 }
441 }
442
443 mbx_tmp_oflags = O_RDWR | O_CREAT;
444#ifdef O_NOFOLLOW
445 mbx_tmp_oflags |= O_NOFOLLOW;
446#endif
447 md = open(tempname, mbx_tmp_oflags, 0600);
448 if (md < 0)
449 {
450 printf("exim_lock: failed to create mbx lock file %s: %s\n",
451 tempname, strerror(errno));
452 goto CLEAN_UP;
453 }
454
455 /* security fixes from 2010-05 */
456 if (lstat(tempname, &lstatbuf) < 0)
457 {
458 printf("exim_lock: failed to lstat(%s) after opening it: %s\n",
459 tempname, strerror(errno));
460 goto CLEAN_UP;
461 }
462 if (fstat(md, &statbuf2) < 0)
463 {
464 printf("exim_lock: failed to fstat() open fd of \"%s\": %s\n",
465 tempname, strerror(errno));
466 goto CLEAN_UP;
467 }
468 if ((statbuf2.st_nlink > 1) ||
469 (lstatbuf.st_nlink > 1) ||
470 (!S_ISREG(lstatbuf.st_mode)) ||
471 (lstatbuf.st_dev != statbuf2.st_dev) ||
472 (lstatbuf.st_ino != statbuf2.st_ino))
473 {
474 printf("exim_lock: race condition exploited against us when "
475 "locking \"%s\"\n", tempname);
476 goto CLEAN_UP;
477 }
478
479 (void)chmod(tempname, 0600);
480
481 if (apply_lock(md, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
482 lock_flock_timeout) >= 0)
483 {
484 if (!quiet)
485 {
486 if (use_fcntl)
487 printf("exim_lock: fcntl() lock successfully applied to mbx "
488 "lock file %s\n", tempname);
489 if (use_flock)
490 printf("exim_lock: flock() lock successfully applied to mbx "
491 "lock file %s\n", tempname);
492 }
493
494 /* This test checks for a race condition */
495
496 if (lstat(tempname, &statbuf) != 0 ||
497 fstat(md, &ostatbuf) != 0 ||
498 statbuf.st_dev != ostatbuf.st_dev ||
499 statbuf.st_ino != ostatbuf.st_ino)
500 {
501 if (!quiet) printf("exim_lock: mbx lock file %s changed between "
502 "creation and locking\n", tempname);
503 goto RETRY;
504 }
505 else break;
506 }
507 else goto RETRY; /* Message already output */
508 }
509
510 /* Clean up before retrying */
511
512 RETRY:
513
514 if (md >= 0)
515 {
516 if (close(md) < 0)
517 printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
518 else
519 if (!quiet) printf("exim_lock: %s closed\n", tempname);
520 md = -1;
521 }
522
523 if (fd >= 0)
524 {
525 if (close(fd) < 0)
526 printf("exim_lock: close failed: %s\n", strerror(errno));
527 else
528 if (!quiet) printf("exim_lock: file closed\n");
529 fd = -1;
530 }
531
532 if (hd >= 0)
533 {
534 if (unlink(lockname) < 0)
535 printf("exim_lock: unlink of %s failed: %s\n", lockname, strerror(errno));
536 else
537 if (!quiet) printf("exim_lock: lock file removed\n");
538 hd = -1;
539 }
540
541 /* If a blocking call timed out, break the retry loop if the total time
542 so far is not less than than retries * interval. */
543
544 if (sigalrm_seen &&
545 (j + 1) * ((lock_fcntl_timeout > lock_flock_timeout)?
546 lock_fcntl_timeout : lock_flock_timeout) >=
547 lock_retries * lock_interval)
548 j = lock_retries;
549
550 /* Wait a bit before retrying, except when it was a blocked fcntl() that
551 caused the problem. */
552
553 if (j < lock_retries && sleep_before_retry)
554 {
555 printf(" ... waiting\n");
556 sleep(lock_interval);
557 }
558 }
559
560if (j >= lock_retries)
561 {
562 printf("exim_lock: locking failed too many times\n");
563 yield = 1;
564 goto CLEAN_UP;
565 }
566
567if (!quiet) printf("exim_lock: locking %s succeeded: ", filename);
568
569/* If there are no further arguments, run the user's shell; otherwise
570the next argument is a command to run. */
571
572if (i >= argc)
573 {
574 command = getenv("SHELL");
575 if (command == NULL || *command == 0) command = "/bin/sh";
576 if (!quiet) printf("running %s ...\n", command);
577 }
578else
579 {
580 command = argv[i];
581 if (!quiet) printf("running the command ...\n");
582 }
583
584/* Run the command, saving and restoring the times if required. */
585
586if (restore_times)
587 {
588 struct stat strestore;
589 struct utimbuf ut;
590 stat(filename, &strestore);
591 i = system(command);
592 ut.actime = strestore.st_atime;
593 ut.modtime = strestore.st_mtime;
594 utime(filename, &ut);
595 }
596else i = system(command);
597
598if(i && !quiet) printf("warning: nonzero status %d\n", i);
599
600/* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
601lock on the mailbox. This should be a non-blocking lock call, as there is no
602point in waiting. */
603
604CLEAN_UP:
605
606if (md >= 0)
607 {
608 if (apply_lock(fd, F_WRLCK, use_fcntl, 0, use_flock, 0) >= 0)
609 {
610 if (!quiet) printf("exim_lock: %s unlinked - no sharers\n", tempname);
611 unlink(tempname);
612 }
613 else if (!quiet)
614 printf("exim_lock: %s not unlinked - unable to get exclusive mailbox lock\n",
615 tempname);
616 if (close(md) < 0)
617 printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
618 else
619 if (!quiet) printf("exim_lock: %s closed\n", tempname);
620 }
621
622if (fd >= 0)
623 {
624 if (close(fd) < 0)
625 printf("exim_lock: close %s failed: %s\n", filename, strerror(errno));
626 else
627 if (!quiet) printf("exim_lock: %s closed\n", filename);
628 }
629
630if (hd >= 0)
631 {
632 if (unlink(lockname) < 0)
633 printf("exim_lock: unlink %s failed: %s\n", lockname, strerror(errno));
634 else
635 if (!quiet) printf("exim_lock: lock file removed\n");
636 }
637
638return yield;
639}
640
641/* End */