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