jaromail

a commandline tool to easily and privately handle your e-mail
git clone git://parazyd.org/jaromail.git
Log | Files | Refs | Submodules | README

dotlock.c (15388B)


      1 /*
      2  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
      3  * Copyright (C) 1998-2001,2007 Thomas Roessler <roessler@does-not-exist.org>
      4  * 
      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.
      9  * 
     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.
     14  * 
     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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
     18  */ 
     19 
     20 /*
     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
     24  * macro.
     25  */
     26 
     27 #if HAVE_CONFIG_H
     28 # include "config.h"
     29 #endif
     30 
     31 #include <stdio.h>
     32 #include <stdlib.h>
     33 #include <string.h>
     34 
     35 #include <unistd.h>
     36 #include <dirent.h>
     37 #include <sys/file.h>
     38 #include <sys/stat.h>
     39 #include <sys/utsname.h>
     40 #include <errno.h>
     41 #include <time.h>
     42 #include <fcntl.h>
     43 #include <limits.h>
     44 
     45 #ifndef _POSIX_PATH_MAX
     46 #include <limits.h>
     47 #endif
     48 
     49 #include "dotlock.h"
     50 
     51 #ifdef HAVE_GETOPT_H
     52 #include <getopt.h>
     53 #endif
     54 
     55 #define VERSION "jaromail"
     56 #define ReleaseDate "29Nov2014"
     57 
     58 /* #ifdef DL_STANDALONE */
     59 /* # include "reldate.h" */
     60 /* #endif */
     61 
     62 #define MAXLINKS 1024 /* maximum link depth */
     63 
     64 #ifdef DL_STANDALONE
     65 
     66 # define LONG_STRING 1024
     67 # define MAXLOCKATTEMPT 5
     68 
     69 # define strfcpy(A,B,C) strncpy (A,B,C), *(A+(C)-1)=0
     70 
     71 # ifdef USE_SETGID
     72 
     73 #  ifdef HAVE_SETEGID
     74 #   define SETEGID setegid
     75 #  else
     76 #   define SETEGID setgid
     77 #  endif
     78 #  ifndef S_ISLNK
     79 #   define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK ? 1 : 0)
     80 #  endif
     81 
     82 # endif
     83 
     84 # ifndef HAVE_SNPRINTF
     85 extern int snprintf (char *, size_t, const char *, ...);
     86 # endif
     87 
     88 #else  /* DL_STANDALONE */
     89 
     90 # ifdef USE_SETGID
     91 #   error Do not try to compile dotlock as a mutt module when requiring egid switching!
     92 # endif
     93 
     94 # include "mutt.h"
     95 # include "mx.h"
     96 
     97 #endif /* DL_STANDALONE */
     98 
     99 static int DotlockFlags;
    100 static int Retry = MAXLOCKATTEMPT;
    101 
    102 #ifdef DL_STANDALONE
    103 static char *Hostname;
    104 #endif
    105 
    106 #ifdef USE_SETGID
    107 static gid_t UserGid;
    108 static gid_t MailGid;
    109 #endif
    110 
    111 static int dotlock_deference_symlink (char *, size_t, const char *);
    112 static int dotlock_prepare (char *, size_t, const char *, int fd);
    113 static int dotlock_check_stats (struct stat *, struct stat *);
    114 static int dotlock_dispatch (const char *, int fd);
    115 
    116 #ifdef DL_STANDALONE
    117 static int dotlock_init_privs (void);
    118 static void usage (const char *);
    119 #endif
    120 
    121 static void dotlock_expand_link (char *, const char *, const char *);
    122 static void BEGIN_PRIVILEGED (void);
    123 static void END_PRIVILEGED (void);
    124 
    125 /* These functions work on the current directory.
    126  * Invoke dotlock_prepare () before and check their
    127  * return value.
    128  */
    129 
    130 static int dotlock_try (void);
    131 static int dotlock_unlock (const char *);
    132 static int dotlock_unlink (const char *);
    133 static int dotlock_lock (const char *);
    134 
    135 
    136 #ifdef DL_STANDALONE
    137 
    138 #define check_flags(a) if (a & DL_FL_ACTIONS) usage (argv[0])
    139 
    140 int main (int argc, char **argv)
    141 {
    142   int i;
    143   char *p;
    144   struct utsname utsname;
    145 
    146   /* first, drop privileges */
    147   
    148   if (dotlock_init_privs () == -1)
    149     return DL_EX_ERROR;
    150 
    151 
    152   /* determine the system's host name */
    153   
    154   uname (&utsname);
    155   if (!(Hostname = strdup (utsname.nodename)))	/* __MEM_CHECKED__ */
    156     return DL_EX_ERROR;
    157   if ((p = strchr (Hostname, '.')))
    158     *p = '\0';
    159 
    160 
    161   /* parse the command line options. */
    162   DotlockFlags = 0;
    163   
    164   while ((i = getopt (argc, argv, "dtfupr:")) != EOF)
    165   {
    166     switch (i)
    167     {
    168       /* actions, mutually exclusive */
    169       case 't': check_flags (DotlockFlags); DotlockFlags |= DL_FL_TRY; break;
    170       case 'd': check_flags (DotlockFlags); DotlockFlags |= DL_FL_UNLINK; break;
    171       case 'u': check_flags (DotlockFlags); DotlockFlags |= DL_FL_UNLOCK; break;
    172 
    173       /* other flags */
    174       case 'f': DotlockFlags |= DL_FL_FORCE; break;
    175       case 'p': DotlockFlags |= DL_FL_USEPRIV; break;
    176       case 'r': DotlockFlags |= DL_FL_RETRY; Retry = atoi (optarg); break;
    177       
    178       default: usage (argv[0]);
    179     }
    180   }
    181 
    182   if (optind == argc || Retry < 0)
    183     usage (argv[0]);
    184 
    185   return dotlock_dispatch (argv[optind], -1);
    186 }
    187 
    188 
    189 /* 
    190  * Determine our effective group ID, and drop 
    191  * privileges.
    192  * 
    193  * Return value:
    194  * 
    195  *  0 - everything went fine
    196  * -1 - we couldn't drop privileges.
    197  * 
    198  */
    199 
    200 
    201 static int
    202 dotlock_init_privs (void)
    203 {
    204 
    205 # ifdef USE_SETGID
    206   
    207   UserGid = getgid ();
    208   MailGid = getegid ();
    209 
    210   if (SETEGID (UserGid) != 0)
    211     return -1;
    212 
    213 # endif
    214 
    215   return 0;
    216 }
    217   
    218 
    219 #else  /* DL_STANDALONE */
    220 
    221 /* 
    222  * This function is intended to be invoked from within
    223  * mutt instead of mx.c's invoke_dotlock ().
    224  */
    225 
    226 int dotlock_invoke (const char *path, int fd, int flags, int retry)
    227 {
    228   int currdir;
    229   int r;
    230 
    231   DotlockFlags = flags;
    232   
    233   if ((currdir = open (".", O_RDONLY)) == -1)
    234     return DL_EX_ERROR;
    235 
    236   if (!(DotlockFlags & DL_FL_RETRY) || retry)
    237     Retry = MAXLOCKATTEMPT;
    238   else
    239     Retry = 0;
    240   
    241   r = dotlock_dispatch (path, fd);
    242   
    243   fchdir (currdir);
    244   close (currdir);
    245   
    246   return r;
    247 }
    248 
    249 #endif  /* DL_STANDALONE */
    250 
    251 
    252 static int dotlock_dispatch (const char *f, int fd)
    253 {
    254   char realpath[_POSIX_PATH_MAX];
    255 
    256   /* If dotlock_prepare () succeeds [return value == 0],
    257    * realpath contains the basename of f, and we have
    258    * successfully changed our working directory to
    259    * `dirname $f`.  Additionally, f has been opened for
    260    * reading to verify that the user has at least read
    261    * permissions on that file.
    262    * 
    263    * For a more detailed explanation of all this, see the
    264    * lengthy comment below.
    265    */
    266 
    267   if (dotlock_prepare (realpath, sizeof (realpath), f, fd) != 0)
    268     return DL_EX_ERROR;
    269 
    270   /* Actually perform the locking operation. */
    271 
    272   if (DotlockFlags & DL_FL_TRY)
    273     return dotlock_try ();
    274   else if (DotlockFlags & DL_FL_UNLOCK)
    275     return dotlock_unlock (realpath);
    276   else if (DotlockFlags & DL_FL_UNLINK)
    277     return dotlock_unlink (realpath);
    278   else /* lock */
    279     return dotlock_lock (realpath);
    280 }
    281 
    282   
    283 /*
    284  * Get privileges 
    285  * 
    286  * This function re-acquires the privileges we may have
    287  * if the user told us to do so by giving the "-p"
    288  * command line option.
    289  * 
    290  * BEGIN_PRIVILEGES () won't return if an error occurs.
    291  * 
    292  */
    293 
    294 static void
    295 BEGIN_PRIVILEGED (void)
    296 {
    297 #ifdef USE_SETGID
    298   if (DotlockFlags & DL_FL_USEPRIV)
    299   {
    300     if (SETEGID (MailGid) != 0)
    301     {
    302       /* perror ("setegid"); */
    303       exit (DL_EX_ERROR);
    304     }
    305   }
    306 #endif
    307 }
    308 
    309 /*
    310  * Drop privileges
    311  * 
    312  * This function drops the group privileges we may have.
    313  * 
    314  * END_PRIVILEGED () won't return if an error occurs.
    315  *
    316  */
    317 
    318 static void
    319 END_PRIVILEGED (void)
    320 {
    321 #ifdef USE_SETGID
    322   if (DotlockFlags & DL_FL_USEPRIV)
    323   {
    324     if (SETEGID (UserGid) != 0)
    325     {
    326       /* perror ("setegid"); */
    327       exit (DL_EX_ERROR);
    328     }
    329   }
    330 #endif
    331 }
    332 
    333 #ifdef DL_STANDALONE
    334 
    335 /*
    336  * Usage information.
    337  * 
    338  * This function doesn't return.
    339  * 
    340  */
    341 
    342 static void 
    343 usage (const char *av0)
    344 {
    345   fprintf (stderr, "dotlock [Mutt %s (%s)]\n", VERSION, ReleaseDate);
    346   fprintf (stderr, "usage: %s [-t|-f|-u|-d] [-p] [-r <retries>] file\n",
    347 	  av0);
    348 
    349   fputs ("\noptions:"
    350 	"\n  -t\t\ttry"
    351 	"\n  -f\t\tforce"
    352 	"\n  -u\t\tunlock"
    353 	"\n  -d\t\tunlink"
    354 	"\n  -p\t\tprivileged"
    355 #ifndef USE_SETGID
    356 	" (ignored)"
    357 #endif
    358 	"\n  -r <retries>\tRetry locking"
    359 	"\n", stderr);
    360   
    361   exit (DL_EX_ERROR);
    362 }
    363 
    364 #endif
    365 
    366 /*
    367  * Access checking: Let's avoid to lock other users' mail
    368  * spool files if we aren't permitted to read them.
    369  * 
    370  * Some simple-minded access (2) checking isn't sufficient
    371  * here: The problem is that the user may give us a
    372  * deeply nested path to a file which has the same name
    373  * as the file he wants to lock, but different
    374  * permissions, say, e.g.
    375  * /tmp/lots/of/subdirs/var/spool/mail/root.
    376  * 
    377  * He may then try to replace /tmp/lots/of/subdirs by a
    378  * symbolic link to / after we have invoked access () to
    379  * check the file's permissions.  The lockfile we'd
    380  * create or remove would then actually be
    381  * /var/spool/mail/root.
    382  * 
    383  * To avoid this attack, we proceed as follows:
    384  * 
    385  * - First, follow symbolic links a la
    386  *   dotlock_deference_symlink ().
    387  * 
    388  * - get the result's dirname.
    389  * 
    390  * - chdir to this directory.  If you can't, bail out.
    391  * 
    392  * - try to open the file in question, only using its
    393  *   basename.  If you can't, bail out.
    394  * 
    395  * - fstat that file and compare the result to a
    396  *   subsequent lstat (only using the basename).  If
    397  *   the comparison fails, bail out.
    398  * 
    399  * dotlock_prepare () is invoked from main () directly
    400  * after the command line parsing has been done.
    401  *
    402  * Return values:
    403  * 
    404  * 0 - Evereything's fine.  The program's new current
    405  *     directory is the contains the file to be locked.
    406  *     The string pointed to by bn contains the name of
    407  *     the file to be locked.
    408  * 
    409  * -1 - Something failed. Don't continue.
    410  * 
    411  * tlr, Jul 15 1998
    412  */
    413 
    414 static int
    415 dotlock_check_stats (struct stat *fsb, struct stat *lsb)
    416 {
    417   /* S_ISLNK (fsb->st_mode) should actually be impossible,
    418    * but we may have mixed up the parameters somewhere.
    419    * play safe.
    420    */
    421 
    422   if (S_ISLNK (lsb->st_mode) || S_ISLNK (fsb->st_mode))
    423     return -1;
    424   
    425   if ((lsb->st_dev != fsb->st_dev) ||
    426      (lsb->st_ino != fsb->st_ino) ||
    427      (lsb->st_mode != fsb->st_mode) ||
    428      (lsb->st_nlink != fsb->st_nlink) ||
    429      (lsb->st_uid != fsb->st_uid) ||
    430      (lsb->st_gid != fsb->st_gid) ||
    431      (lsb->st_rdev != fsb->st_rdev) ||
    432      (lsb->st_size != fsb->st_size))
    433   {
    434     /* something's fishy */
    435     return -1;
    436   }
    437   
    438   return 0;
    439 }
    440 
    441 static int
    442 dotlock_prepare (char *bn, size_t l, const char *f, int _fd)
    443 {
    444   struct stat fsb, lsb;
    445   char realpath[_POSIX_PATH_MAX];
    446   char *basename, *dirname;
    447   char *p;
    448   int fd;
    449   int r;
    450   
    451   if (dotlock_deference_symlink (realpath, sizeof (realpath), f) == -1)
    452     return -1;
    453   
    454   if ((p = strrchr (realpath, '/')))
    455   {
    456     *p = '\0';
    457     basename = p + 1;
    458     dirname = realpath;
    459   }
    460   else
    461   {
    462     basename = realpath;
    463     dirname = ".";
    464   }
    465 
    466   if (strlen (basename) + 1 > l)
    467     return -1;
    468   
    469   strfcpy (bn, basename, l);
    470   
    471   if (chdir (dirname) == -1)
    472     return -1;
    473 
    474   if (_fd != -1)
    475     fd = _fd;
    476   else if ((fd = open (basename, O_RDONLY)) == -1)
    477     return -1;
    478   
    479   r = fstat (fd, &fsb);
    480   
    481   if (_fd == -1)
    482     close (fd);
    483   
    484   if (r == -1)
    485     return -1;
    486   
    487   if (lstat (basename, &lsb) == -1)
    488     return -1;
    489 
    490   if (dotlock_check_stats (&fsb, &lsb) == -1)
    491     return -1;
    492 
    493   return 0;
    494 }
    495 
    496 /*
    497  * Expand a symbolic link.
    498  * 
    499  * This function expects newpath to have space for
    500  * at least _POSIX_PATH_MAX characters.
    501  *
    502  */
    503 
    504 static void 
    505 dotlock_expand_link (char *newpath, const char *path, const char *link)
    506 {
    507   const char *lb = NULL;
    508   size_t len;
    509 
    510   /* link is full path */
    511   if (*link == '/')
    512   {
    513     strfcpy (newpath, link, _POSIX_PATH_MAX);
    514     return;
    515   }
    516 
    517   if ((lb = strrchr (path, '/')) == NULL)
    518   {
    519     /* no path in link */
    520     strfcpy (newpath, link, _POSIX_PATH_MAX);
    521     return;
    522   }
    523 
    524   len = lb - path + 1;
    525   memcpy (newpath, path, len);
    526   strfcpy (newpath + len, link, _POSIX_PATH_MAX - len);
    527 }
    528 
    529 
    530 /*
    531  * Deference a chain of symbolic links
    532  * 
    533  * The final path is written to d.
    534  *
    535  */
    536 
    537 static int
    538 dotlock_deference_symlink (char *d, size_t l, const char *path)
    539 {
    540   struct stat sb;
    541   char realpath[_POSIX_PATH_MAX];
    542   const char *pathptr = path;
    543   int count = 0;
    544   
    545   while (count++ < MAXLINKS)
    546   {
    547     if (lstat (pathptr, &sb) == -1)
    548     {
    549       /* perror (pathptr); */
    550       return -1;
    551     }
    552     
    553     if (S_ISLNK (sb.st_mode))
    554     {
    555       char linkfile[_POSIX_PATH_MAX];
    556       char linkpath[_POSIX_PATH_MAX];
    557       int len;
    558 
    559       if ((len = readlink (pathptr, linkfile, sizeof (linkfile) - 1)) == -1)
    560       {
    561 	/* perror (pathptr); */
    562 	return -1;
    563       }
    564       
    565       linkfile[len] = '\0';
    566       dotlock_expand_link (linkpath, pathptr, linkfile);
    567       strfcpy (realpath, linkpath, sizeof (realpath));
    568       pathptr = realpath;
    569     }
    570     else
    571       break;
    572   }
    573 
    574   strfcpy (d, pathptr, l);
    575   return 0;
    576 }
    577 
    578 /*
    579  * Dotlock a file.
    580  * 
    581  * realpath is assumed _not_ to be an absolute path to
    582  * the file we are about to lock.  Invoke
    583  * dotlock_prepare () before using this function!
    584  * 
    585  */
    586 
    587 #define HARDMAXATTEMPTS 10
    588 
    589 static int
    590 dotlock_lock (const char *realpath)
    591 {
    592   char lockfile[_POSIX_PATH_MAX + LONG_STRING];
    593   char nfslockfile[_POSIX_PATH_MAX + LONG_STRING];
    594   size_t prev_size = 0;
    595   int fd;
    596   int count = 0;
    597   int hard_count = 0;
    598   struct stat sb;
    599   time_t t;
    600 
    601   snprintf (nfslockfile, sizeof (nfslockfile), "%s.%s.%d",
    602 	   realpath, Hostname, (int) getpid ());
    603   snprintf (lockfile, sizeof (lockfile), "%s.lock", realpath);
    604 
    605   
    606   BEGIN_PRIVILEGED ();
    607 
    608   unlink (nfslockfile);
    609 
    610   while ((fd = open (nfslockfile, O_WRONLY | O_EXCL | O_CREAT, 0)) < 0)
    611   {
    612     END_PRIVILEGED ();
    613 
    614   
    615     if (errno != EAGAIN)
    616     {
    617       /* perror ("cannot open NFS lock file"); */
    618       return DL_EX_ERROR;
    619     }
    620 
    621     
    622     BEGIN_PRIVILEGED ();
    623   }
    624 
    625   END_PRIVILEGED ();
    626 
    627   
    628   close (fd);
    629   
    630   while (hard_count++ < HARDMAXATTEMPTS)
    631   {
    632 
    633     BEGIN_PRIVILEGED ();
    634     link (nfslockfile, lockfile);
    635     END_PRIVILEGED ();
    636 
    637     if (stat (nfslockfile, &sb) != 0)
    638     {
    639       /* perror ("stat"); */
    640       return DL_EX_ERROR;
    641     }
    642 
    643     if (sb.st_nlink == 2)
    644       break;
    645 
    646     if (count == 0)
    647       prev_size = sb.st_size;
    648 
    649     if (prev_size == sb.st_size && ++count > Retry)
    650     {
    651       if (DotlockFlags & DL_FL_FORCE)
    652       {
    653 	BEGIN_PRIVILEGED ();
    654 	unlink (lockfile);
    655 	END_PRIVILEGED ();
    656 
    657 	count = 0;
    658 	continue;
    659       }
    660       else
    661       {
    662 	BEGIN_PRIVILEGED ();
    663 	unlink (nfslockfile);
    664 	END_PRIVILEGED ();
    665 	return DL_EX_EXIST;
    666       }
    667     }
    668     
    669     prev_size = sb.st_size;
    670     
    671     /* don't trust sleep (3) as it may be interrupted
    672      * by users sending signals. 
    673      */
    674     
    675     t = time (NULL);
    676     do {
    677       sleep (1);
    678     } while (time (NULL) == t);
    679   }
    680 
    681   BEGIN_PRIVILEGED ();
    682   unlink (nfslockfile);
    683   END_PRIVILEGED ();
    684 
    685   return DL_EX_OK;
    686 }
    687 
    688 
    689 /*
    690  * Unlock a file. 
    691  * 
    692  * The same comment as for dotlock_lock () applies here.
    693  * 
    694  */
    695 
    696 static int
    697 dotlock_unlock (const char *realpath)
    698 {
    699   char lockfile[_POSIX_PATH_MAX + LONG_STRING];
    700   int i;
    701 
    702   snprintf (lockfile, sizeof (lockfile), "%s.lock",
    703 	   realpath);
    704   
    705   BEGIN_PRIVILEGED ();
    706   i = unlink (lockfile);
    707   END_PRIVILEGED ();
    708   
    709   if (i == -1)
    710     return DL_EX_ERROR;
    711   
    712   return DL_EX_OK;
    713 }
    714 
    715 /* remove an empty file */
    716 
    717 static int
    718 dotlock_unlink (const char *realpath)
    719 {
    720   struct stat lsb;
    721   int i = -1;
    722 
    723   if (dotlock_lock (realpath) != DL_EX_OK)
    724     return DL_EX_ERROR;
    725 
    726   if ((i = lstat (realpath, &lsb)) == 0 && lsb.st_size == 0)
    727     unlink (realpath);
    728 
    729   dotlock_unlock (realpath);
    730 
    731   return (i == 0) ?  DL_EX_OK : DL_EX_ERROR;
    732 }
    733 
    734 
    735 /*
    736  * Check if a file can be locked at all.
    737  * 
    738  * The same comment as for dotlock_lock () applies here.
    739  * 
    740  */
    741 
    742 static int
    743 dotlock_try (void)
    744 {
    745 #ifdef USE_SETGID
    746   struct stat sb;
    747 #endif
    748 
    749   if (access (".", W_OK) == 0)
    750     return DL_EX_OK;
    751 
    752 #ifdef USE_SETGID
    753   if (stat (".", &sb) == 0)
    754   {
    755     if ((sb.st_mode & S_IWGRP) == S_IWGRP && sb.st_gid == MailGid)
    756       return DL_EX_NEED_PRIVS;
    757   }
    758 #endif
    759 
    760   return DL_EX_IMPOSSIBLE;
    761 }