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