Include string_interpret_escape() in COMPILE_UTILITY cases.
[exim.git] / src / src / exim_lock.c
CommitLineData
059ec3d9
PH
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*/
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
32in sys/file.h. */
33
34#ifndef LOCK_SH
35#define NO_FLOCK
36#endif
37
38
39typedef int BOOL;
40#define FALSE 0
41#define TRUE 1
42
43
44/* Flag for timeout signal handler */
45
46static int sigalrm_seen = FALSE;
47
48
49/* We need to pull in strerror() and os_non_restarting_signal() from the
50os.c source, if they are required for this OS. However, we don't need any of
51the 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
81static void
82sigalrm_handler(int sig)
83{
84sig = sig; /* Keep picky compilers happy */
85sigalrm_seen = TRUE;
86}
87
88
89
90/*************************************************
91* Give usage and die *
92*************************************************/
93
94static void
95usage(void)
96{
97printf("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");
100exit(1);
101}
102
103
104
105/*************************************************
106* Apply a lock to a file descriptor *
107*************************************************/
108
109static int
110apply_lock(int fd, int fcntltype, BOOL dofcntl, int fcntltime, BOOL doflock,
111 int flocktime)
112{
113int yield = 0;
114int save_errno;
115struct flock lock_data;
116lock_data.l_type = fcntltype;
117lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
118
119sigalrm_seen = FALSE;
120
121if (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
137if (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
154return yield;
155}
156
157
158
159/*************************************************
160* The exim_lock program *
161*************************************************/
162
163int main(int argc, char **argv)
164{
165int lock_retries = 10;
166int lock_interval = 3;
167int lock_fcntl_timeout = 0;
168int lock_flock_timeout = 0;
169int i, j, len;
170int fd = -1;
171int hd = -1;
172int md = -1;
173int yield = 0;
174int now = time(NULL);
175BOOL use_lockfile = FALSE;
176BOOL use_fcntl = FALSE;
177BOOL use_flock = FALSE;
178BOOL use_mbx = FALSE;
179BOOL verbose = FALSE;
180BOOL quiet = FALSE;
181BOOL restore_times = FALSE;
182char *filename;
183char *lockname = NULL, *hitchname = NULL;
1ba28e2b
PP
184char *primary_hostname;
185const char *command;
059ec3d9
PH
186struct utsname s;
187char buffer[256];
188char tempname[256];
189
190/* Decode options */
191
192for (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
215if (quiet) verbose = 0;
216
217/* Can't use flock() if the OS doesn't provide it */
218
219#ifdef NO_FLOCK
220if (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
230if (!use_lockfile && !use_fcntl && !use_flock && !use_mbx)
231 use_lockfile = use_fcntl = TRUE;
232
233/* Default fcntl() for use with mbx */
234
235if (use_mbx && !use_fcntl && !use_flock) use_fcntl = TRUE;
236
237/* Unset unused timeouts */
238
239if (!use_fcntl) lock_fcntl_timeout = 0;
240if (!use_flock) lock_flock_timeout = 0;
241
242/* A file name is required */
243
244if (i >= argc) usage();
245
246filename = argv[i++];
247
248/* Expand file names starting with ~ */
249
250if (*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
284the hitching post name. */
285
286if (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
309for (j = 0; j < lock_retries; j++)
310 {
311 int sleep_before_retry = TRUE;
bf83d8d3
PP
312 struct stat statbuf, ostatbuf, lstatbuf, statbuf2;
313 int mbx_tmp_oflags;
059ec3d9
PH
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);
f1e894f3 332 (void)close(hd);
059ec3d9
PH
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
bf83d8d3
PP
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);
059ec3d9
PH
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;
bf83d8d3
PP
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;
059ec3d9
PH
468 }
469
ff790e47 470 (void)chmod(tempname, 0600);
059ec3d9
PH
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
551if (j >= lock_retries)
552 {
553 printf("exim_lock: locking failed too many times\n");
554 yield = 1;
555 goto CLEAN_UP;
556 }
557
558if (!quiet) printf("exim_lock: locking %s succeeded: ", filename);
559
560/* If there are no further arguments, run the user's shell; otherwise
561the next argument is a command to run. */
562
563if (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 }
569else
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
577if (restore_times)
578 {
579 struct stat strestore;
580 struct utimbuf ut;
581 stat(filename, &strestore);
ff790e47 582 (void)system(command);
059ec3d9
PH
583 ut.actime = strestore.st_atime;
584 ut.modtime = strestore.st_mtime;
585 utime(filename, &ut);
586 }
ff790e47 587else (void)system(command);
059ec3d9
PH
588
589/* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
590lock on the mailbox. This should be a non-blocking lock call, as there is no
591point in waiting. */
592
593CLEAN_UP:
594
595if (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
611if (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
619if (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
627return yield;
628}
629
630/* End */