2 * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
3 * Copyright (C) 1998-2000 Thomas Roessler <roessler@does-not-exist.org>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
21 * This module either be compiled into Mutt, or it can be
22 * built as a separate program. For building it
23 * separately, define the DL_STANDALONE preprocessor
35 #include <sys/utsname.h>
41 #ifndef _POSIX_PATH_MAX
42 #include <posix1_lim.h>
56 #define MAXLINKS 1024 /* maximum link depth */
60 # define LONG_STRING 1024
61 # define MAXLOCKATTEMPT 5
63 # define strfcpy(A,B,C) strncpy (A,B,C), *(A+(C)-1)=0
68 # define SETEGID setegid
70 # define SETEGID setgid
73 # define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK ? 1 : 0)
78 # ifndef HAVE_SNPRINTF
79 extern int snprintf (char *, size_t, const char *, ...);
82 #else /* DL_STANDALONE */
85 # error Do not try to compile dotlock as a mutt module when requiring egid switching!
91 #endif /* DL_STANDALONE */
93 static int DotlockFlags;
94 static int Retry = MAXLOCKATTEMPT;
97 static char *Hostname;
101 static gid_t UserGid;
102 static gid_t MailGid;
105 static int dotlock_deference_symlink (char *, size_t, const char *);
106 static int dotlock_prepare (char *, size_t, const char *, int fd);
107 static int dotlock_check_stats (struct stat *, struct stat *);
108 static int dotlock_dispatch (const char *, int fd);
111 static int dotlock_init_privs (void);
112 static void usage (const char *);
115 static void dotlock_expand_link (char *, const char *, const char *);
116 static void BEGIN_PRIVILEGED (void);
117 static void END_PRIVILEGED (void);
119 /* These functions work on the current directory.
120 * Invoke dotlock_prepare () before and check their
124 static int dotlock_try (void);
125 static int dotlock_unlock (const char *);
126 static int dotlock_unlink (const char *);
127 static int dotlock_lock (const char *);
132 #define check_flags(a) if (a & DL_FL_ACTIONS) usage (argv[0])
134 int main (int argc, char **argv)
138 struct utsname utsname;
140 /* first, drop privileges */
142 if (dotlock_init_privs () == -1)
146 /* determine the system's host name */
149 if (!(Hostname = strdup (utsname.nodename))) /* __MEM_CHECKED__ */
151 if ((p = strchr (Hostname, '.')))
155 /* parse the command line options. */
158 while ((i = getopt (argc, argv, "dtfupr:")) != EOF)
162 /* actions, mutually exclusive */
163 case 't': check_flags (DotlockFlags); DotlockFlags |= DL_FL_TRY; break;
164 case 'd': check_flags (DotlockFlags); DotlockFlags |= DL_FL_UNLINK; break;
165 case 'u': check_flags (DotlockFlags); DotlockFlags |= DL_FL_UNLOCK; break;
168 case 'f': DotlockFlags |= DL_FL_FORCE; break;
169 case 'p': DotlockFlags |= DL_FL_USEPRIV; break;
170 case 'r': DotlockFlags |= DL_FL_RETRY; Retry = atoi (optarg); break;
172 default: usage (argv[0]);
176 if (optind == argc || Retry < 0)
179 return dotlock_dispatch (argv[optind], -1);
184 * Determine our effective group ID, and drop
189 * 0 - everything went fine
190 * -1 - we couldn't drop privileges.
196 dotlock_init_privs (void)
202 MailGid = getegid ();
204 if (SETEGID (UserGid) != 0)
213 #else /* DL_STANDALONE */
216 * This function is intended to be invoked from within
217 * mutt instead of mx.c's invoke_dotlock ().
220 int dotlock_invoke (const char *path, int fd, int flags, int retry)
225 DotlockFlags = flags;
227 if ((currdir = open (".", O_RDONLY)) == -1)
230 if (!(DotlockFlags & DL_FL_RETRY) || retry)
231 Retry = MAXLOCKATTEMPT;
235 r = dotlock_dispatch (path, fd);
243 #endif /* DL_STANDALONE */
246 static int dotlock_dispatch (const char *f, int fd)
248 char realpath[_POSIX_PATH_MAX];
250 /* If dotlock_prepare () succeeds [return value == 0],
251 * realpath contains the basename of f, and we have
252 * successfully changed our working directory to
253 * `dirname $f`. Additionally, f has been opened for
254 * reading to verify that the user has at least read
255 * permissions on that file.
257 * For a more detailed explanation of all this, see the
258 * lengthy comment below.
261 if (dotlock_prepare (realpath, sizeof (realpath), f, fd) != 0)
264 /* Actually perform the locking operation. */
266 if (DotlockFlags & DL_FL_TRY)
267 return dotlock_try ();
268 else if (DotlockFlags & DL_FL_UNLOCK)
269 return dotlock_unlock (realpath);
270 else if (DotlockFlags & DL_FL_UNLINK)
271 return dotlock_unlink (realpath);
273 return dotlock_lock (realpath);
280 * This function re-acquires the privileges we may have
281 * if the user told us to do so by giving the "-p"
282 * command line option.
284 * BEGIN_PRIVILEGES () won't return if an error occurs.
289 BEGIN_PRIVILEGED (void)
292 if (DotlockFlags & DL_FL_USEPRIV)
294 if (SETEGID (MailGid) != 0)
296 /* perror ("setegid"); */
306 * This function drops the group privileges we may have.
308 * END_PRIVILEGED () won't return if an error occurs.
313 END_PRIVILEGED (void)
316 if (DotlockFlags & DL_FL_USEPRIV)
318 if (SETEGID (UserGid) != 0)
320 /* perror ("setegid"); */
332 * This function doesn't return.
337 usage (const char *av0)
339 fprintf (stderr, "dotlock [Mutt %s (%s)]\n", VERSION, ReleaseDate);
340 fprintf (stderr, "usage: %s [-t|-f|-u|-d] [-p] [-r <retries>] file\n",
348 "\n -p\t\tprivileged"
352 "\n -r <retries>\tRetry locking"
361 * Access checking: Let's avoid to lock other users' mail
362 * spool files if we aren't permitted to read them.
364 * Some simple-minded access (2) checking isn't sufficient
365 * here: The problem is that the user may give us a
366 * deeply nested path to a file which has the same name
367 * as the file he wants to lock, but different
368 * permissions, say, e.g.
369 * /tmp/lots/of/subdirs/var/spool/mail/root.
371 * He may then try to replace /tmp/lots/of/subdirs by a
372 * symbolic link to / after we have invoked access () to
373 * check the file's permissions. The lockfile we'd
374 * create or remove would then actually be
375 * /var/spool/mail/root.
377 * To avoid this attack, we proceed as follows:
379 * - First, follow symbolic links a la
380 * dotlock_deference_symlink ().
382 * - get the result's dirname.
384 * - chdir to this directory. If you can't, bail out.
386 * - try to open the file in question, only using its
387 * basename. If you can't, bail out.
389 * - fstat that file and compare the result to a
390 * subsequent lstat (only using the basename). If
391 * the comparison fails, bail out.
393 * dotlock_prepare () is invoked from main () directly
394 * after the command line parsing has been done.
398 * 0 - Evereything's fine. The program's new current
399 * directory is the contains the file to be locked.
400 * The string pointed to by bn contains the name of
401 * the file to be locked.
403 * -1 - Something failed. Don't continue.
409 dotlock_check_stats (struct stat *fsb, struct stat *lsb)
411 /* S_ISLNK (fsb->st_mode) should actually be impossible,
412 * but we may have mixed up the parameters somewhere.
416 if (S_ISLNK (lsb->st_mode) || S_ISLNK (fsb->st_mode))
419 if ((lsb->st_dev != fsb->st_dev) ||
420 (lsb->st_ino != fsb->st_ino) ||
421 (lsb->st_mode != fsb->st_mode) ||
422 (lsb->st_nlink != fsb->st_nlink) ||
423 (lsb->st_uid != fsb->st_uid) ||
424 (lsb->st_gid != fsb->st_gid) ||
425 (lsb->st_rdev != fsb->st_rdev) ||
426 (lsb->st_size != fsb->st_size))
428 /* something's fishy */
436 dotlock_prepare (char *bn, size_t l, const char *f, int _fd)
438 struct stat fsb, lsb;
439 char realpath[_POSIX_PATH_MAX];
440 char *basename, *dirname;
445 if (dotlock_deference_symlink (realpath, sizeof (realpath), f) == -1)
448 if ((p = strrchr (realpath, '/')))
460 if (strlen (basename) + 1 > l)
463 strfcpy (bn, basename, l);
465 if (chdir (dirname) == -1)
470 else if ((fd = open (basename, O_RDONLY)) == -1)
473 r = fstat (fd, &fsb);
481 if (lstat (basename, &lsb) == -1)
484 if (dotlock_check_stats (&fsb, &lsb) == -1)
491 * Expand a symbolic link.
493 * This function expects newpath to have space for
494 * at least _POSIX_PATH_MAX characters.
499 dotlock_expand_link (char *newpath, const char *path, const char *link)
501 const char *lb = NULL;
504 /* link is full path */
507 strfcpy (newpath, link, _POSIX_PATH_MAX);
511 if ((lb = strrchr (path, '/')) == NULL)
513 /* no path in link */
514 strfcpy (newpath, link, _POSIX_PATH_MAX);
519 memcpy (newpath, path, len);
520 strfcpy (newpath + len, link, _POSIX_PATH_MAX - len);
525 * Deference a chain of symbolic links
527 * The final path is written to d.
532 dotlock_deference_symlink (char *d, size_t l, const char *path)
535 char realpath[_POSIX_PATH_MAX];
536 const char *pathptr = path;
539 while (count++ < MAXLINKS)
541 if (lstat (pathptr, &sb) == -1)
543 /* perror (pathptr); */
547 if (S_ISLNK (sb.st_mode))
549 char linkfile[_POSIX_PATH_MAX];
550 char linkpath[_POSIX_PATH_MAX];
553 if ((len = readlink (pathptr, linkfile, sizeof (linkfile))) == -1)
555 /* perror (pathptr); */
559 linkfile[len] = '\0';
560 dotlock_expand_link (linkpath, pathptr, linkfile);
561 strfcpy (realpath, linkpath, sizeof (realpath));
568 strfcpy (d, pathptr, l);
575 * realpath is assumed _not_ to be an absolute path to
576 * the file we are about to lock. Invoke
577 * dotlock_prepare () before using this function!
581 #define HARDMAXATTEMPTS 10
584 dotlock_lock (const char *realpath)
586 char lockfile[_POSIX_PATH_MAX + LONG_STRING];
587 char nfslockfile[_POSIX_PATH_MAX + LONG_STRING];
588 size_t prev_size = 0;
595 snprintf (nfslockfile, sizeof (nfslockfile), "%s.%s.%d",
596 realpath, Hostname, (int) getpid ());
597 snprintf (lockfile, sizeof (lockfile), "%s.lock", realpath);
602 unlink (nfslockfile);
604 while ((fd = open (nfslockfile, O_WRONLY | O_EXCL | O_CREAT, 0)) < 0)
611 /* perror ("cannot open NFS lock file"); */
624 while (hard_count++ < HARDMAXATTEMPTS)
628 link (nfslockfile, lockfile);
631 if (stat (nfslockfile, &sb) != 0)
633 /* perror ("stat"); */
637 if (sb.st_nlink == 2)
641 prev_size = sb.st_size;
643 if (prev_size == sb.st_size && ++count > Retry)
645 if (DotlockFlags & DL_FL_FORCE)
657 unlink (nfslockfile);
663 prev_size = sb.st_size;
665 /* don't trust sleep (3) as it may be interrupted
666 * by users sending signals.
672 } while (time (NULL) == t);
676 unlink (nfslockfile);
686 * The same comment as for dotlock_lock () applies here.
691 dotlock_unlock (const char *realpath)
693 char lockfile[_POSIX_PATH_MAX + LONG_STRING];
696 snprintf (lockfile, sizeof (lockfile), "%s.lock",
700 i = unlink (lockfile);
709 /* remove an empty file */
712 dotlock_unlink (const char *realpath)
717 if (dotlock_lock (realpath) != DL_EX_OK)
720 if ((i = lstat (realpath, &lsb)) == 0 && lsb.st_size == 0)
723 dotlock_unlock (realpath);
725 return (i == 0) ? DL_EX_OK : DL_EX_ERROR;
730 * Check if a file can be locked at all.
732 * The same comment as for dotlock_lock () applies here.
743 if (access (".", W_OK) == 0)
747 if (stat (".", &sb) == 0)
749 if ((sb.st_mode & S_IWGRP) == S_IWGRP && sb.st_gid == MailGid)
750 return DL_EX_NEED_PRIVS;
754 return DL_EX_IMPOSSIBLE;