1 /*
2 * lockfile.c Safely creates a lockfile, also over NFS.
3 * This file also holds the implementation for
4 * the Svr4 maillock functions.
5 *
6 * Copyright (C) Miquel van Smoorenburg and contributors 1997-2021.
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 */
13
14 #include "autoconf.h"
15
16 #include <sys/types.h>
17 #if HAVE_SYS_PARAM_H
18 #include <sys/param.h>
19 #endif
20 #include <sys/stat.h>
21 #include <sys/wait.h>
22 #include <stdarg.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <signal.h>
26 #include <fcntl.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <time.h>
30 #include <errno.h>
31 #include <lockfile.h>
32 #include <maillock.h>
33
34 #ifdef HAVE_UTIME
35 #include <utime.h>
36 #endif
37
38 #ifdef LIB
39 static char *mlockfile;
40 static int islocked = 0;
41 #endif
42
43 #ifndef LIB
44 extern int check_sleep(int, int);
45 #endif
46
47 #ifdef MAILGROUP
48 /*
49 * Get the id of the mailgroup, by statting the helper program.
50 * If it is setgroup-id, then the group is the mailgroup.
51 */
mailgid()52 static int mailgid()
53 {
54 struct stat st;
55
56 if (stat(LOCKPROG, &st) != 0)
57 return (gid_t)-1;
58 if ((st.st_mode & 02000) == 0)
59 return (gid_t)-1;
60 return st.st_gid;
61 }
62
63 /*
64 * Is this a lock for a mailbox? Check:
65 * - is the file in /path/to/USERNAME.lock format
66 * - is /path/to/USERNAME present and owned by us
67 * - is /path/to writable by group mail
68 *
69 * To be safe in a setgid program, chdir() into the lockfile
70 * directory first, then pass in the basename of the lockfile.
71 */
72 #ifdef LIB
73 static
74 #endif
is_maillock(const char * lockfile)75 int is_maillock(const char *lockfile)
76 {
77 struct stat st;
78 gid_t gid;
79 char tmp[1024];
80 char *p;
81
82 /* remove .lock suffix */
83 strncpy(tmp, lockfile, sizeof(tmp) - 1);
84 tmp[sizeof(tmp) - 1] = 0;
85 if ((p = strrchr(tmp, '.')) == NULL || strcmp(p, ".lock") != 0)
86 return 0;
87 *p = 0;
88
89 /* file to lock must exist, and must be owned by us */
90 if (lstat(tmp, &st) != 0 ||
91 (st.st_mode & S_IFMT) != S_IFREG || st.st_uid != getuid())
92 return 0;
93
94 /* Directory this file is in must be writable by group mail. */
95 if ((gid = mailgid()) == (gid_t)-1)
96 return 0;
97 if ((p = strrchr(tmp, '/')) != NULL)
98 *p = 0;
99 else
100 strncpy(tmp, ".", sizeof(tmp));
101 if (stat(tmp, &st) != 0 || st.st_gid != gid || (st.st_mode & 0020) == 0)
102 return 0;
103
104 return 1;
105 }
106
107 #ifdef LIB
108 /*
109 * Call external program to do the actual locking.
110 */
run_helper(char * opt,const char * lockfile,int retries,int flags)111 static int run_helper(char *opt, const char *lockfile, int retries, int flags)
112 {
113 sigset_t set, oldset;
114 char buf[8];
115 pid_t pid, n;
116 int st;
117
118 /*
119 * Better safe than sorry.
120 */
121 if (geteuid() == 0)
122 return L_ERROR;
123
124 /*
125 * Block SIGCHLD. The main program might have installed
126 * handlers we don't want to call.
127 */
128 sigemptyset(&set);
129 sigaddset(&set, SIGCHLD);
130 sigprocmask(SIG_BLOCK, &set, &oldset);
131
132 /*
133 * Fork, execute locking program and wait.
134 */
135 if ((pid = fork()) < 0)
136 return L_ERROR;
137 if (pid == 0) {
138 /* drop privs */
139 if (setuid(geteuid()) < 0) {
140 perror("setuid");
141 _exit(L_ERROR);
142 }
143 snprintf(buf, sizeof(buf), "%d", retries % 1000);
144 execl(LOCKPROG, LOCKPROG, opt, "-r", buf, "-q",
145 (flags & L_PID) ? "-p" : "-N", lockfile, NULL);
146 _exit(L_ERROR);
147 }
148
149 /*
150 * Wait for return status - do something appropriate
151 * if program died or returned L_ERROR.
152 */
153 while ((n = waitpid(pid, &st, 0)) != pid)
154 if (n < 0 && errno != EINTR)
155 break;
156 if (!sigismember(&oldset, SIGCHLD))
157 sigprocmask(SIG_UNBLOCK, &set, NULL);
158 if (n < 0)
159 return L_ERROR;
160 if (!WIFEXITED(st) || WEXITSTATUS(st) == L_ERROR) {
161 errno = EINTR;
162 return L_ERROR;
163 }
164
165 return WEXITSTATUS(st);
166 }
167 #endif /* LIB*/
168
169 #endif /* MAILGROUP */
170
171 #define TMPLOCKSTR ".lk"
172 #define TMPLOCKSTRSZ strlen(TMPLOCKSTR)
173 #define TMPLOCKPIDSZ 5
174 #define TMPLOCKTIMESZ 1
175 #define TMPLOCKSYSNAMESZ 23
176 #define TMPLOCKFILENAMESZ (TMPLOCKSTRSZ + TMPLOCKPIDSZ + \
177 TMPLOCKTIMESZ + TMPLOCKSYSNAMESZ)
178
lockfilename(const char * lockfile,char * tmplock,int tmplocksz)179 static int lockfilename(const char *lockfile, char *tmplock, int tmplocksz)
180 {
181 char sysname[256];
182 char *p;
183
184 #ifdef MAXPATHLEN
185 /*
186 * Safety measure.
187 */
188 if (strlen(lockfile) + TMPLOCKFILENAMESZ > MAXPATHLEN) {
189 errno = ENAMETOOLONG;
190 return L_ERROR;
191 }
192 #endif
193
194 if (strlen(lockfile) + TMPLOCKFILENAMESZ + 1 > tmplocksz) {
195 errno = EINVAL;
196 return L_ERROR;
197 }
198
199 /*
200 * Create a temp lockfile (hopefully unique) and write
201 * either our pid/ppid in it, or 0\0 for svr4 compatibility.
202 */
203 if (gethostname(sysname, sizeof(sysname)) < 0)
204 return L_ERROR;
205 if ((p = strchr(sysname, '.')) != NULL)
206 *p = 0;
207 /* strcpy is safe: length-check above, limited at snprintf below */
208 strcpy(tmplock, lockfile);
209 if ((p = strrchr(tmplock, '/')) == NULL)
210 p = tmplock;
211 else
212 p++;
213 if (snprintf(p, TMPLOCKFILENAMESZ, "%s%0*d%0*x%s", TMPLOCKSTR,
214 TMPLOCKPIDSZ, (int)getpid(),
215 TMPLOCKTIMESZ, (int)time(NULL) & 15,
216 sysname) < 0) {
217 // never happens but gets rid of gcc truncation warning.
218 errno = EOVERFLOW;
219 return L_ERROR;
220 }
221
222 return 0;
223 }
224
225 /*
226 * Create a lockfile.
227 */
lockfile_create_save_tmplock(const char * lockfile,char * tmplock,int tmplocksz,volatile char ** xtmplock,int retries,int flags,struct __lockargs * args)228 static int lockfile_create_save_tmplock(const char *lockfile,
229 char *tmplock, int tmplocksz,
230 volatile char **xtmplock,
231 int retries, int flags, struct __lockargs *args)
232 {
233 struct stat st, st1;
234 char pidbuf[40];
235 pid_t pid = 0;
236 int sleeptime = 0;
237 int statfailed = 0;
238 int fd;
239 int i, e, pidlen;
240 int dontsleep = 1;
241 int tries = retries + 1;
242
243 /* process optional flags that have arguments */
244 if (flags & __L_INTERVAL) {
245 sleeptime = args->interval;
246 }
247
248 /* decide which PID to write to the lockfile */
249 if (flags & L_PID)
250 pid = getpid();
251 if (flags & L_PPID) {
252 pid = getppid();
253 if (pid == 1) {
254 /* orphaned */
255 return L_ORPHANED;
256 }
257 }
258 pidlen = snprintf(pidbuf, sizeof(pidbuf), "%d\n", pid);
259 if (pidlen > sizeof(pidbuf) - 1) {
260 errno = EOVERFLOW;
261 return L_ERROR;
262 }
263
264 /* create temporary lockfile */
265 if ((i = lockfilename(lockfile, tmplock, tmplocksz)) != 0)
266 return i;
267 if (xtmplock)
268 *xtmplock = tmplock;
269 fd = open(tmplock, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0644);
270 if (fd < 0) {
271 /* permission denied? perhaps try suid helper */
272 #if defined(LIB) && defined(MAILGROUP)
273 if (errno == EACCES && is_maillock(lockfile))
274 return run_helper("-l", lockfile, retries, flags);
275 #endif
276 return L_TMPLOCK;
277 }
278 i = write(fd, pidbuf, pidlen);
279 e = errno;
280
281 if (close(fd) != 0) {
282 e = errno;
283 i = -1;
284 }
285 if (i != pidlen) {
286 unlink(tmplock);
287 tmplock[0] = 0;
288 errno = i < 0 ? e : EAGAIN;
289 return L_TMPWRITE;
290 }
291
292 /*
293 * Now try to link the temporary lock to the lock.
294 */
295 for (i = 0; i < tries && tries > 0; i++) {
296 if (!dontsleep) {
297 if (!(flags & __L_INTERVAL))
298 sleeptime += 5;
299
300 if (sleeptime > 60) sleeptime = 60;
301 #ifdef LIB
302 sleep(sleeptime);
303 #else
304 if ((e = check_sleep(sleeptime, flags)) != 0) {
305 unlink(tmplock);
306 tmplock[0] = 0;
307 return e;
308 }
309 #endif
310 }
311 dontsleep = 0;
312
313
314 /*
315 * Now lock by linking the tempfile to the lock.
316 *
317 * KLUDGE: some people say the return code of
318 * link() over NFS can't be trusted.
319 * EXTRA FIX: the value of the nlink field
320 * can't be trusted (may be cached).
321 */
322 (void)!link(tmplock, lockfile);
323
324 if (lstat(tmplock, &st1) < 0) {
325 tmplock[0] = 0;
326 return L_ERROR; /* Can't happen */
327 }
328
329 if (lstat(lockfile, &st) < 0) {
330 if (statfailed++ > 5) {
331 /*
332 * Normally, this can't happen; either
333 * another process holds the lockfile or
334 * we do. So if this error pops up
335 * repeatedly, just exit...
336 */
337 e = errno;
338 (void)unlink(tmplock);
339 tmplock[0] = 0;
340 errno = e;
341 return L_MAXTRYS;
342 }
343 continue;
344 }
345
346 /*
347 * See if we got the lock.
348 */
349 if (st.st_rdev == st1.st_rdev &&
350 st.st_ino == st1.st_ino) {
351 (void)unlink(tmplock);
352 tmplock[0] = 0;
353 return L_SUCCESS;
354 }
355 statfailed = 0;
356
357 /*
358 * If there is a lockfile and it is invalid,
359 * remove the lockfile.
360 */
361 if (lockfile_check(lockfile, flags) == -1) {
362 if (unlink(lockfile) < 0 && errno != ENOENT) {
363 /*
364 * we failed to unlink the stale
365 * lockfile, give up.
366 */
367 return L_RMSTALE;
368 }
369 dontsleep = 1;
370 /*
371 * If the lockfile was invalid, then the first
372 * try wasn't valid either - make sure we
373 * try at least once more.
374 */
375 if (tries == 1) tries++;
376 }
377
378 }
379 (void)unlink(tmplock);
380 tmplock[0] = 0;
381 errno = EAGAIN;
382 return L_MAXTRYS;
383 }
384
385 #ifdef LIB
386 static
387 #endif
lockfile_create_set_tmplock(const char * lockfile,volatile char ** xtmplock,int retries,int flags,struct __lockargs * args)388 int lockfile_create_set_tmplock(const char *lockfile, volatile char **xtmplock, int retries, int flags, struct __lockargs *args)
389 {
390 char *tmplock;
391 int l, r, e;
392
393 l = strlen(lockfile)+TMPLOCKFILENAMESZ+1;
394 if ((tmplock = (char *)malloc(l)) == NULL)
395 return L_ERROR;
396 tmplock[0] = 0;
397 r = lockfile_create_save_tmplock(lockfile,
398 tmplock, l, xtmplock, retries, flags, args);
399 if (xtmplock)
400 *xtmplock = NULL;
401 e = errno;
402 free(tmplock);
403 errno = e;
404 return r;
405 }
406
407 #ifdef LIB
lockfile_create(const char * lockfile,int retries,int flags)408 int lockfile_create(const char *lockfile, int retries, int flags)
409 {
410 /* check against unknown flags */
411 if (flags & ~(L_PID|L_PPID)) {
412 errno = EINVAL;
413 return L_ERROR;
414 }
415 return lockfile_create_set_tmplock(lockfile, NULL, retries, flags, NULL);
416 }
417
418 #ifdef STATIC
lockfile_create2(const char * lockfile,int retries,int flags,struct __lockargs * args,int args_sz)419 int lockfile_create2(const char *lockfile, int retries,
420 int flags, struct __lockargs *args, int args_sz)
421 {
422
423 #define FLAGS_WITH_ARGS (__L_INTERVAL)
424 #define KNOWN_FLAGS (L_PID|L_PPID|__L_INTERVAL)
425
426 /* check if size is the same (version check) */
427 if (args != NULL && sizeof(struct __lockargs) != args_sz) {
428 errno = EINVAL;
429 return L_ERROR;
430 }
431 /* some flags _must_ have a non-null args */
432 if (args == NULL && (flags & FLAGS_WITH_ARGS)) {
433 errno = EINVAL;
434 return L_ERROR;
435 }
436 /* check against unknown flags */
437 if (flags & ~KNOWN_FLAGS) {
438 errno = EINVAL;
439 return L_ERROR;
440 }
441 return lockfile_create_set_tmplock(lockfile, NULL, retries, flags, args);
442 }
443 #endif
444
445 #endif
446
447 /*
448 * See if a valid lockfile is present.
449 * Returns 0 if so, -1 if not.
450 */
lockfile_check(const char * lockfile,int flags)451 int lockfile_check(const char *lockfile, int flags)
452 {
453 struct stat st, st2;
454 char buf[16];
455 time_t now;
456 pid_t pid;
457 int fd, len, r;
458
459 if (stat(lockfile, &st) < 0)
460 return -1;
461
462 /*
463 * Get the contents and mtime of the lockfile.
464 */
465 time(&now);
466 pid = 0;
467 if ((fd = open(lockfile, O_RDONLY)) >= 0) {
468 /*
469 * Try to use 'atime after read' as now, this is
470 * the time of the filesystem. Should not get
471 * confused by 'atime' or 'noatime' mount options.
472 */
473 len = 0;
474 if (fstat(fd, &st) == 0 &&
475 (len = read(fd, buf, sizeof(buf))) >= 0 &&
476 fstat(fd, &st2) == 0 &&
477 st.st_atime != st2.st_atime)
478 now = st.st_atime;
479 close(fd);
480 if (len > 0 && (flags & (L_PID|L_PPID))) {
481 buf[len] = 0;
482 pid = atoi(buf);
483 }
484 }
485
486 if (pid > 0) {
487 /*
488 * If we have a pid, see if the process
489 * owning the lockfile is still alive.
490 */
491 r = kill(pid, 0);
492 if (r == 0 || errno == EPERM)
493 return 0;
494 if (r < 0 && errno == ESRCH)
495 return -1;
496 /* EINVAL - FALLTHRU */
497 }
498
499 /*
500 * Without a pid in the lockfile, the lock
501 * is valid if it is newer than 5 mins.
502 */
503
504 if (now < st.st_mtime + 300)
505 return 0;
506
507 return -1;
508 }
509
510 /*
511 * Remove a lock.
512 */
lockfile_remove(const char * lockfile)513 int lockfile_remove(const char *lockfile)
514 {
515 if (unlink(lockfile) < 0) {
516 #if defined(LIB) && defined(MAILGROUP)
517 if (errno == EACCES && is_maillock(lockfile))
518 return run_helper("-u", lockfile, 0, 0);
519 #endif
520 return errno == ENOENT ? 0 : -1;
521 }
522 return 0;
523 }
524
525 /*
526 * Touch a lock.
527 */
lockfile_touch(const char * lockfile)528 int lockfile_touch(const char *lockfile)
529 {
530 #ifdef HAVE_UTIME
531 return utime(lockfile, NULL);
532 #else
533 return utimes(lockfile, NULL);
534 #endif
535 }
536
537 #ifdef LIB
538 /*
539 * Lock a mailfile. This looks a lot like the SVR4 function.
540 * Arguments: lusername, retries.
541 */
maillock(const char * name,int retries)542 int maillock(const char *name, int retries)
543 {
544 char *p, *mail;
545 char *newlock;
546 int i, e;
547 int len, newlen;
548
549 if (islocked) return 0;
550
551 #ifdef MAXPATHLEN
552 if (strlen(name) + sizeof(MAILDIR) + 6 > MAXPATHLEN) {
553 errno = ENAMETOOLONG;
554 return L_NAMELEN;
555 }
556 #endif
557
558 /*
559 * If $MAIL is for the same username as "name"
560 * then use $MAIL instead.
561 */
562
563 len = strlen(name)+strlen(MAILDIR)+6;
564 mlockfile = (char *)malloc(len);
565 if (!mlockfile)
566 return L_ERROR;
567 sprintf(mlockfile, "%s%s.lock", MAILDIR, name);
568 if ((mail = getenv("MAIL")) != NULL) {
569 if ((p = strrchr(mail, '/')) != NULL)
570 p++;
571 else
572 p = mail;
573 if (strcmp(p, name) == 0) {
574 newlen = strlen(mail)+6;
575 #ifdef MAXPATHLEN
576 if (newlen > MAXPATHLEN) {
577 errno = ENAMETOOLONG;
578 return L_NAMELEN;
579 }
580 #endif
581 if (newlen > len) {
582 newlock = (char *)realloc (mlockfile, newlen);
583 if (newlock == NULL) {
584 e = errno;
585 free (mlockfile);
586 mlockfile = NULL;
587 errno = e;
588 return L_ERROR;
589 }
590 mlockfile = newlock;
591 }
592 sprintf(mlockfile, "%s.lock", mail);
593 }
594 }
595 i = lockfile_create(mlockfile, retries, 0);
596 if (i == 0) islocked = 1;
597
598 return i;
599 }
600
mailunlock(void)601 void mailunlock(void)
602 {
603 if (!islocked) return;
604 lockfile_remove(mlockfile);
605 free (mlockfile);
606 islocked = 0;
607 }
608
touchlock(void)609 void touchlock(void)
610 {
611 lockfile_touch(mlockfile);
612 }
613 #endif
614
615