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