2 * Copyright notice from original mutt:
3 * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
4 * Copyright (C) 1998-2000 Thomas Roessler <roessler@does-not-exist.org>
6 * This file is part of mutt-ng, see http://www.muttng.org/.
7 * It's licensed under the GNU General Public License,
8 * please see the file GPL in the top level source directory.
12 * This module either be compiled into Mutt, or it can be
13 * built as a separate program. For building it
14 * separately, define the DL_STANDALONE preprocessor
30 #include <sys/utsname.h>
36 #ifndef _POSIX_PATH_MAX
37 #include <posix1_lim.h>
47 #define MAXLINKS 1024 /* maximum link depth */
51 # define LONG_STRING 1024
52 # define MAXLOCKATTEMPT 5
57 # define SETEGID setegid
59 # define SETEGID setgid
62 # define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK ? 1 : 0)
67 # ifndef HAVE_SNPRINTF
68 extern int snprintf (char *, size_t, const char *, ...);
71 #else /* DL_STANDALONE */
74 # error Do not try to compile dotlock as a mutt module when requiring egid switching!
80 #endif /* DL_STANDALONE */
82 #include <lib-lib/str.h>
84 static int DotlockFlags;
85 static int Retry = MAXLOCKATTEMPT;
88 static char *Hostname;
96 static int dotlock_deference_symlink (char *, size_t, const char *);
97 static int dotlock_prepare (char *, size_t, const char *, int fd);
98 static int dotlock_check_stats (struct stat *, struct stat *);
99 static int dotlock_dispatch (const char *, int fd);
102 static int dotlock_init_privs (void);
103 static void usage (const char *);
106 static void dotlock_expand_link (char *, const char *, const char *);
107 static void BEGIN_PRIVILEGED (void);
108 static void END_PRIVILEGED (void);
110 /* These functions work on the current directory.
111 * Invoke dotlock_prepare () before and check their
115 static int dotlock_try (void);
116 static int dotlock_unlock (const char *);
117 static int dotlock_unlink (const char *);
118 static int dotlock_lock (const char *);
123 #define check_flags(a) if (a & DL_FL_ACTIONS) usage (argv[0])
125 int main (int argc, char **argv)
129 struct utsname utsname;
131 /* first, drop privileges */
133 if (dotlock_init_privs () == -1)
137 /* determine the system's host name */
140 if (!(Hostname = strdup (utsname.nodename))) /* __MEM_CHECKED__ */
142 if ((p = strchr (Hostname, '.')))
146 /* parse the command line options. */
149 while ((i = getopt (argc, argv, "dtfupr:")) != EOF) {
151 /* actions, mutually exclusive */
153 check_flags (DotlockFlags);
154 DotlockFlags |= DL_FL_TRY;
157 check_flags (DotlockFlags);
158 DotlockFlags |= DL_FL_UNLINK;
161 check_flags (DotlockFlags);
162 DotlockFlags |= DL_FL_UNLOCK;
167 DotlockFlags |= DL_FL_FORCE;
170 DotlockFlags |= DL_FL_USEPRIV;
173 DotlockFlags |= DL_FL_RETRY;
174 Retry = atoi (optarg);
182 if (optind == argc || Retry < 0)
185 return dotlock_dispatch (argv[optind], -1);
190 * Determine our effective group ID, and drop
195 * 0 - everything went fine
196 * -1 - we couldn't drop privileges.
201 static int dotlock_init_privs (void)
207 MailGid = getegid ();
209 if (SETEGID (UserGid) != 0)
218 #else /* DL_STANDALONE */
221 * This function is intended to be invoked from within
222 * mutt instead of mx.c's invoke_dotlock ().
225 int dotlock_invoke (const char *path, int fd, int flags, int retry)
230 DotlockFlags = flags;
232 if ((currdir = open (".", O_RDONLY)) == -1)
235 if (!(DotlockFlags & DL_FL_RETRY) || retry)
236 Retry = MAXLOCKATTEMPT;
240 r = dotlock_dispatch (path, fd);
248 #endif /* DL_STANDALONE */
251 static int dotlock_dispatch (const char *f, int fd)
253 char realpath[_POSIX_PATH_MAX];
255 /* If dotlock_prepare () succeeds [return value == 0],
256 * realpath contains the basename of f, and we have
257 * successfully changed our working directory to
258 * `dirname $f`. Additionally, f has been opened for
259 * reading to verify that the user has at least read
260 * permissions on that file.
262 * For a more detailed explanation of all this, see the
263 * lengthy comment below.
266 if (dotlock_prepare (realpath, sizeof (realpath), f, fd) != 0)
269 /* Actually perform the locking operation. */
271 if (DotlockFlags & DL_FL_TRY)
272 return dotlock_try ();
273 else if (DotlockFlags & DL_FL_UNLOCK)
274 return dotlock_unlock (realpath);
275 else if (DotlockFlags & DL_FL_UNLINK)
276 return dotlock_unlink (realpath);
278 return dotlock_lock (realpath);
285 * This function re-acquires the privileges we may have
286 * if the user told us to do so by giving the "-p"
287 * command line option.
289 * BEGIN_PRIVILEGES () won't return if an error occurs.
293 static void BEGIN_PRIVILEGED (void)
296 if (DotlockFlags & DL_FL_USEPRIV) {
297 if (SETEGID (MailGid) != 0) {
298 /* perror ("setegid"); */
308 * This function drops the group privileges we may have.
310 * END_PRIVILEGED () won't return if an error occurs.
314 static void END_PRIVILEGED (void)
317 if (DotlockFlags & DL_FL_USEPRIV) {
318 if (SETEGID (UserGid) != 0) {
319 /* perror ("setegid"); */
331 * This function doesn't return.
335 static void usage (const char *av0)
337 fprintf (stderr, "dotlock [Mutt-ng %s]\n", VERSION);
338 fprintf (stderr, "usage: %s [-t|-f|-u|-d] [-p] [-r <retries>] file\n", av0);
343 "\n -u\t\tunlock" "\n -d\t\tunlink" "\n -p\t\tprivileged"
347 "\n -r <retries>\tRetry locking" "\n", stderr);
355 * Access checking: Let's avoid to lock other users' mail
356 * spool files if we aren't permitted to read them.
358 * Some simple-minded access (2) checking isn't sufficient
359 * here: The problem is that the user may give us a
360 * deeply nested path to a file which has the same name
361 * as the file he wants to lock, but different
362 * permissions, say, e.g.
363 * /tmp/lots/of/subdirs/var/spool/mail/root.
365 * He may then try to replace /tmp/lots/of/subdirs by a
366 * symbolic link to / after we have invoked access () to
367 * check the file's permissions. The lockfile we'd
368 * create or remove would then actually be
369 * /var/spool/mail/root.
371 * To avoid this attack, we proceed as follows:
373 * - First, follow symbolic links a la
374 * dotlock_deference_symlink ().
376 * - get the result's dirname.
378 * - chdir to this directory. If you can't, bail out.
380 * - try to open the file in question, only using its
381 * basename. If you can't, bail out.
383 * - fstat that file and compare the result to a
384 * subsequent lstat (only using the basename). If
385 * the comparison fails, bail out.
387 * dotlock_prepare () is invoked from main () directly
388 * after the command line parsing has been done.
392 * 0 - Evereything's fine. The program's new current
393 * directory is the contains the file to be locked.
394 * The string pointed to by bn contains the name of
395 * the file to be locked.
397 * -1 - Something failed. Don't continue.
402 static int dotlock_check_stats (struct stat *fsb, struct stat *lsb)
404 /* S_ISLNK (fsb->st_mode) should actually be impossible,
405 * but we may have mixed up the parameters somewhere.
409 if (S_ISLNK (lsb->st_mode) || S_ISLNK (fsb->st_mode))
412 if ((lsb->st_dev != fsb->st_dev) ||
413 (lsb->st_ino != fsb->st_ino) ||
414 (lsb->st_mode != fsb->st_mode) ||
415 (lsb->st_nlink != fsb->st_nlink) ||
416 (lsb->st_uid != fsb->st_uid) ||
417 (lsb->st_gid != fsb->st_gid) ||
418 (lsb->st_rdev != fsb->st_rdev) || (lsb->st_size != fsb->st_size)) {
419 /* something's fishy */
426 static int dotlock_prepare (char *bn, size_t l, const char *f, int _fd)
428 struct stat fsb, lsb;
429 char realpath[_POSIX_PATH_MAX];
430 char *basename, *dirname;
435 if (dotlock_deference_symlink (realpath, sizeof (realpath), f) == -1)
438 if ((p = strrchr (realpath, '/'))) {
448 if (m_strlen(basename) + 1 > l)
451 m_strcpy(bn, l, basename);
453 if (chdir (dirname) == -1)
458 else if ((fd = open (basename, O_RDONLY)) == -1)
461 r = fstat (fd, &fsb);
469 if (lstat (basename, &lsb) == -1)
472 if (dotlock_check_stats (&fsb, &lsb) == -1)
479 * Expand a symbolic link.
481 * This function expects newpath to have space for
482 * at least _POSIX_PATH_MAX characters.
487 dotlock_expand_link (char *newpath, const char *path, const char *link)
489 const char *lb = NULL;
492 /* link is full path */
494 m_strcpy(newpath, _POSIX_PATH_MAX, link);
498 if ((lb = strrchr (path, '/')) == NULL) {
499 /* no path in link */
500 m_strcpy(newpath, _POSIX_PATH_MAX, link);
505 memcpy (newpath, path, len);
506 m_strcpy(newpath + len, _POSIX_PATH_MAX - len, link);
511 * Deference a chain of symbolic links
513 * The final path is written to d.
517 static int dotlock_deference_symlink (char *d, size_t l, const char *path)
520 char realpath[_POSIX_PATH_MAX];
521 const char *pathptr = path;
524 while (count++ < MAXLINKS) {
525 if (lstat (pathptr, &sb) == -1) {
526 /* perror (pathptr); */
530 if (S_ISLNK (sb.st_mode)) {
531 char linkfile[_POSIX_PATH_MAX];
532 char linkpath[_POSIX_PATH_MAX];
535 if ((len = readlink (pathptr, linkfile, sizeof (linkfile))) == -1) {
536 /* perror (pathptr); */
540 linkfile[len] = '\0';
541 dotlock_expand_link (linkpath, pathptr, linkfile);
542 m_strcpy(realpath, sizeof(realpath), linkpath);
549 m_strcpy(d, l, pathptr);
556 * realpath is assumed _not_ to be an absolute path to
557 * the file we are about to lock. Invoke
558 * dotlock_prepare () before using this function!
562 #define HARDMAXATTEMPTS 10
564 static int dotlock_lock (const char *realpath)
566 char lockfile[_POSIX_PATH_MAX + LONG_STRING];
567 char nfslockfile[_POSIX_PATH_MAX + LONG_STRING];
568 size_t prev_size = 0;
575 snprintf (nfslockfile, sizeof (nfslockfile), "%s.%s.%d",
576 realpath, Hostname, (int) getpid ());
577 snprintf (lockfile, sizeof (lockfile), "%s.lock", realpath);
582 unlink (nfslockfile);
584 while ((fd = open (nfslockfile, O_WRONLY | O_EXCL | O_CREAT, 0)) < 0) {
588 if (errno != EAGAIN) {
589 /* perror ("cannot open NFS lock file"); */
602 while (hard_count++ < HARDMAXATTEMPTS) {
605 link (nfslockfile, lockfile);
608 if (stat (nfslockfile, &sb) != 0) {
609 /* perror ("stat"); */
613 if (sb.st_nlink == 2)
617 prev_size = sb.st_size;
619 if (prev_size == sb.st_size && ++count > Retry) {
620 if (DotlockFlags & DL_FL_FORCE) {
630 unlink (nfslockfile);
636 prev_size = sb.st_size;
638 /* don't trust sleep (3) as it may be interrupted
639 * by users sending signals.
645 } while (time (NULL) == t);
649 unlink (nfslockfile);
659 * The same comment as for dotlock_lock () applies here.
663 static int dotlock_unlock (const char *realpath)
665 char lockfile[_POSIX_PATH_MAX + LONG_STRING];
668 snprintf (lockfile, sizeof (lockfile), "%s.lock", realpath);
671 i = unlink (lockfile);
680 /* remove an empty file */
682 static int dotlock_unlink (const char *realpath)
687 if (dotlock_lock (realpath) != DL_EX_OK)
690 if ((i = lstat (realpath, &lsb)) == 0 && lsb.st_size == 0)
693 dotlock_unlock (realpath);
695 return (i == 0) ? DL_EX_OK : DL_EX_ERROR;
700 * Check if a file can be locked at all.
702 * The same comment as for dotlock_lock () applies here.
706 static int dotlock_try (void)
712 if (access (".", W_OK) == 0)
716 if (stat (".", &sb) == 0) {
717 if ((sb.st_mode & S_IWGRP) == S_IWGRP && sb.st_gid == MailGid)
718 return DL_EX_NEED_PRIVS;
722 return DL_EX_IMPOSSIBLE;