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.
23 #include <sys/utsname.h>
29 #ifndef _POSIX_PATH_MAX
30 #include <posix1_lim.h>
40 #define MAXLINKS 1024 /* maximum link depth */
42 # define LONG_STRING 1024
43 # define MAXLOCKATTEMPT 5
48 # define SETEGID setegid
50 # define SETEGID setgid
53 # define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK ? 1 : 0)
58 # ifndef HAVE_SNPRINTF
59 extern int snprintf (char *, size_t, const char *, ...);
62 #include <lib-lib/str.h>
64 static int DotlockFlags;
65 static int Retry = MAXLOCKATTEMPT;
67 static char *Hostname;
74 static int dotlock_deference_symlink (char *, size_t, const char *);
75 static int dotlock_prepare (char *, ssize_t, const char *, int fd);
76 static int dotlock_check_stats (struct stat *, struct stat *);
77 static int dotlock_dispatch (const char *, int fd);
79 static int dotlock_init_privs (void);
80 static void usage (const char *);
82 static void dotlock_expand_link (char *, const char *, const char *);
83 static void BEGIN_PRIVILEGED (void);
84 static void END_PRIVILEGED (void);
86 /* These functions work on the current directory.
87 * Invoke dotlock_prepare () before and check their
91 static int dotlock_try (void);
92 static int dotlock_unlock (const char *);
93 static int dotlock_unlink (const char *);
94 static int dotlock_lock (const char *);
97 #define check_flags(a) if (a & DL_FL_ACTIONS) usage (argv[0])
99 int main (int argc, char **argv)
103 struct utsname utsname;
105 /* first, drop privileges */
107 if (dotlock_init_privs () == -1)
111 /* determine the system's host name */
114 if (!(Hostname = strdup (utsname.nodename))) /* __MEM_CHECKED__ */
116 if ((p = strchr (Hostname, '.')))
120 /* parse the command line options. */
123 while ((i = getopt (argc, argv, "dtfupr:")) != EOF) {
125 /* actions, mutually exclusive */
127 check_flags (DotlockFlags);
128 DotlockFlags |= DL_FL_TRY;
131 check_flags (DotlockFlags);
132 DotlockFlags |= DL_FL_UNLINK;
135 check_flags (DotlockFlags);
136 DotlockFlags |= DL_FL_UNLOCK;
141 DotlockFlags |= DL_FL_FORCE;
144 DotlockFlags |= DL_FL_USEPRIV;
147 DotlockFlags |= DL_FL_RETRY;
148 Retry = atoi (optarg);
156 if (optind == argc || Retry < 0)
159 return dotlock_dispatch (argv[optind], -1);
164 * Determine our effective group ID, and drop
169 * 0 - everything went fine
170 * -1 - we couldn't drop privileges.
175 static int dotlock_init_privs (void)
181 MailGid = getegid ();
183 if (SETEGID (UserGid) != 0)
192 static int dotlock_dispatch (const char *f, int fd)
194 char frealpath[_POSIX_PATH_MAX];
196 /* If dotlock_prepare () succeeds [return value == 0],
197 * realpath contains the basename of f, and we have
198 * successfully changed our working directory to
199 * `dirname $f`. Additionally, f has been opened for
200 * reading to verify that the user has at least read
201 * permissions on that file.
203 * For a more detailed explanation of all this, see the
204 * lengthy comment below.
207 if (dotlock_prepare (frealpath, sizeof (frealpath), f, fd) != 0)
210 /* Actually perform the locking operation. */
212 if (DotlockFlags & DL_FL_TRY)
213 return dotlock_try ();
214 else if (DotlockFlags & DL_FL_UNLOCK)
215 return dotlock_unlock (frealpath);
216 else if (DotlockFlags & DL_FL_UNLINK)
217 return dotlock_unlink (frealpath);
219 return dotlock_lock (frealpath);
226 * This function re-acquires the privileges we may have
227 * if the user told us to do so by giving the "-p"
228 * command line option.
230 * BEGIN_PRIVILEGES () won't return if an error occurs.
234 static void BEGIN_PRIVILEGED (void)
237 if (DotlockFlags & DL_FL_USEPRIV) {
238 if (SETEGID (MailGid) != 0) {
239 /* perror ("setegid"); */
249 * This function drops the group privileges we may have.
251 * END_PRIVILEGED () won't return if an error occurs.
255 static void END_PRIVILEGED (void)
258 if (DotlockFlags & DL_FL_USEPRIV) {
259 if (SETEGID (UserGid) != 0) {
260 /* perror ("setegid"); */
270 * This function doesn't return.
274 static void usage (const char *av0)
276 fprintf (stderr, "dotlock [Madmutt %s]\n", VERSION);
277 fprintf (stderr, "usage: %s [-t|-f|-u|-d] [-p] [-r <retries>] file\n", av0);
282 "\n -u\t\tunlock" "\n -d\t\tunlink" "\n -p\t\tprivileged"
286 "\n -r <retries>\tRetry locking" "\n", stderr);
292 * Access checking: Let's avoid to lock other users' mail
293 * spool files if we aren't permitted to read them.
295 * Some simple-minded access (2) checking isn't sufficient
296 * here: The problem is that the user may give us a
297 * deeply nested path to a file which has the same name
298 * as the file he wants to lock, but different
299 * permissions, say, e.g.
300 * /tmp/lots/of/subdirs/var/spool/mail/root.
302 * He may then try to replace /tmp/lots/of/subdirs by a
303 * symbolic link to / after we have invoked access () to
304 * check the file's permissions. The lockfile we'd
305 * create or remove would then actually be
306 * /var/spool/mail/root.
308 * To avoid this attack, we proceed as follows:
310 * - First, follow symbolic links a la
311 * dotlock_deference_symlink ().
313 * - get the result's dirname.
315 * - chdir to this directory. If you can't, bail out.
317 * - try to open the file in question, only using its
318 * basename. If you can't, bail out.
320 * - fstat that file and compare the result to a
321 * subsequent lstat (only using the basename). If
322 * the comparison fails, bail out.
324 * dotlock_prepare () is invoked from main () directly
325 * after the command line parsing has been done.
329 * 0 - Evereything's fine. The program's new current
330 * directory is the contains the file to be locked.
331 * The string pointed to by bn contains the name of
332 * the file to be locked.
334 * -1 - Something failed. Don't continue.
339 static int dotlock_check_stats (struct stat *fsb, struct stat *lsb)
341 /* S_ISLNK (fsb->st_mode) should actually be impossible,
342 * but we may have mixed up the parameters somewhere.
346 if (S_ISLNK (lsb->st_mode) || S_ISLNK (fsb->st_mode))
349 if ((lsb->st_dev != fsb->st_dev) ||
350 (lsb->st_ino != fsb->st_ino) ||
351 (lsb->st_mode != fsb->st_mode) ||
352 (lsb->st_nlink != fsb->st_nlink) ||
353 (lsb->st_uid != fsb->st_uid) ||
354 (lsb->st_gid != fsb->st_gid) ||
355 (lsb->st_rdev != fsb->st_rdev) || (lsb->st_size != fsb->st_size)) {
356 /* something's fishy */
363 static int dotlock_prepare (char *bn, ssize_t l, const char *f, int _fd)
365 struct stat fsb, lsb;
366 char frealpath[_POSIX_PATH_MAX];
367 char *fbasename, *dirname;
372 if (dotlock_deference_symlink (frealpath, sizeof (frealpath), f) == -1)
375 if ((p = strrchr (frealpath, '/'))) {
381 fbasename = frealpath;
382 dirname = m_strdup(".");
385 if (m_strlen(fbasename) + 1 > l)
388 m_strcpy(bn, l, fbasename);
390 if (chdir (dirname) == -1)
395 else if ((fd = open (fbasename, O_RDONLY)) == -1)
398 r = fstat (fd, &fsb);
406 if (lstat (fbasename, &lsb) == -1)
409 if (dotlock_check_stats (&fsb, &lsb) == -1)
416 * Expand a symbolic link.
418 * This function expects newpath to have space for
419 * at least _POSIX_PATH_MAX characters.
424 dotlock_expand_link (char *newpath, const char *path, const char *flink)
426 const char *lb = NULL;
429 /* link is full path */
431 m_strcpy(newpath, _POSIX_PATH_MAX, flink);
435 if ((lb = strrchr (path, '/')) == NULL) {
436 /* no path in link */
437 m_strcpy(newpath, _POSIX_PATH_MAX, flink);
442 memcpy (newpath, path, len);
443 m_strcpy(newpath + len, _POSIX_PATH_MAX - len, flink);
448 * Deference a chain of symbolic links
450 * The final path is written to d.
454 static int dotlock_deference_symlink (char *d, size_t l, const char *path)
457 char frealpath[_POSIX_PATH_MAX];
458 const char *pathptr = path;
461 while (count++ < MAXLINKS) {
462 if (lstat (pathptr, &sb) == -1) {
463 /* perror (pathptr); */
467 if (S_ISLNK (sb.st_mode)) {
468 char linkfile[_POSIX_PATH_MAX];
469 char linkpath[_POSIX_PATH_MAX];
472 if ((len = readlink (pathptr, linkfile, sizeof (linkfile))) == -1) {
473 /* perror (pathptr); */
477 linkfile[len] = '\0';
478 dotlock_expand_link (linkpath, pathptr, linkfile);
479 m_strcpy(frealpath, sizeof(frealpath), linkpath);
486 m_strcpy(d, l, pathptr);
493 * realpath is assumed _not_ to be an absolute path to
494 * the file we are about to lock. Invoke
495 * dotlock_prepare () before using this function!
499 #define HARDMAXATTEMPTS 10
501 static int dotlock_lock (const char *frealpath)
503 char lockfile[_POSIX_PATH_MAX + LONG_STRING];
504 char nfslockfile[_POSIX_PATH_MAX + LONG_STRING];
505 size_t prev_size = 0;
512 snprintf (nfslockfile, sizeof (nfslockfile), "%s.%s.%d",
513 frealpath, Hostname, (int) getpid ());
514 snprintf (lockfile, sizeof (lockfile), "%s.lock", frealpath);
519 unlink (nfslockfile);
521 while ((fd = open (nfslockfile, O_WRONLY | O_EXCL | O_CREAT, 0)) < 0) {
525 if (errno != EAGAIN) {
526 /* perror ("cannot open NFS lock file"); */
539 while (hard_count++ < HARDMAXATTEMPTS) {
542 link (nfslockfile, lockfile);
545 if (stat (nfslockfile, &sb) != 0) {
546 /* perror ("stat"); */
550 if (sb.st_nlink == 2)
554 prev_size = sb.st_size;
556 if (prev_size == sb.st_size && ++count > Retry) {
557 if (DotlockFlags & DL_FL_FORCE) {
567 unlink (nfslockfile);
573 prev_size = sb.st_size;
575 /* don't trust sleep (3) as it may be interrupted
576 * by users sending signals.
582 } while (time (NULL) == t);
586 unlink (nfslockfile);
596 * The same comment as for dotlock_lock () applies here.
600 static int dotlock_unlock (const char *frealpath)
602 char lockfile[_POSIX_PATH_MAX + LONG_STRING];
605 snprintf (lockfile, sizeof (lockfile), "%s.lock", frealpath);
608 i = unlink (lockfile);
617 /* remove an empty file */
619 static int dotlock_unlink (const char *frealpath)
624 if (dotlock_lock (frealpath) != DL_EX_OK)
627 if ((i = lstat (frealpath, &lsb)) == 0 && lsb.st_size == 0)
630 dotlock_unlock (frealpath);
632 return (i == 0) ? DL_EX_OK : DL_EX_ERROR;
637 * Check if a file can be locked at all.
639 * The same comment as for dotlock_lock () applies here.
643 static int dotlock_try (void)
649 if (access (".", W_OK) == 0)
653 if (stat (".", &sb) == 0) {
654 if ((sb.st_mode & S_IWGRP) == S_IWGRP && sb.st_gid == MailGid)
655 return DL_EX_NEED_PRIVS;
659 return DL_EX_IMPOSSIBLE;