safefile.c revision 98121
1279377Simp/* 2279377Simp * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers. 3279377Simp * All rights reserved. 4279377Simp * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved. 5279377Simp * Copyright (c) 1988, 1993 6279377Simp * The Regents of the University of California. All rights reserved. 7279377Simp * 8279377Simp * By using this file, you agree to the terms and conditions set 9279377Simp * forth in the LICENSE file which can be found at the top level of 10279377Simp * the sendmail distribution. 11279377Simp * 12279377Simp */ 13279377Simp 14279377Simp#include <sendmail.h> 15279377Simp#include <sm/io.h> 16279377Simp#include <sm/errstring.h> 17279377Simp 18279377SimpSM_RCSID("@(#)$Id: safefile.c,v 8.124 2002/05/24 20:50:15 gshapiro Exp $") 19279377Simp 20279377Simp 21279377Simp/* 22279377Simp** SAFEFILE -- return 0 if a file exists and is safe for a user. 23279377Simp** 24279377Simp** Parameters: 25279377Simp** fn -- filename to check. 26279377Simp** uid -- user id to compare against. 27279377Simp** gid -- group id to compare against. 28279377Simp** user -- user name to compare against (used for group 29279377Simp** sets). 30279377Simp** flags -- modifiers: 31279377Simp** SFF_MUSTOWN -- "uid" must own this file. 32279377Simp** SFF_NOSLINK -- file cannot be a symbolic link. 33279377Simp** mode -- mode bits that must match. 34279377Simp** st -- if set, points to a stat structure that will 35279377Simp** get the stat info for the file. 36279377Simp** 37279377Simp** Returns: 38279377Simp** 0 if fn exists, is owned by uid, and matches mode. 39279377Simp** An errno otherwise. The actual errno is cleared. 40279377Simp** 41279377Simp** Side Effects: 42279377Simp** none. 43279377Simp*/ 44279377Simp 45279377Simpint 46279377Simpsafefile(fn, uid, gid, user, flags, mode, st) 47279377Simp char *fn; 48279377Simp UID_T uid; 49279377Simp GID_T gid; 50279377Simp char *user; 51279377Simp long flags; 52279377Simp int mode; 53279377Simp struct stat *st; 54279377Simp{ 55279377Simp register char *p; 56279377Simp register struct group *gr = NULL; 57279377Simp int file_errno = 0; 58279377Simp bool checkpath; 59279377Simp struct stat stbuf; 60279377Simp struct stat fstbuf; 61279377Simp char fbuf[MAXPATHLEN]; 62279377Simp 63279377Simp if (tTd(44, 4)) 64279377Simp sm_dprintf("safefile(%s, uid=%d, gid=%d, flags=%lx, mode=%o):\n", 65279377Simp fn, (int) uid, (int) gid, flags, mode); 66279377Simp errno = 0; 67279377Simp if (sm_strlcpy(fbuf, fn, sizeof fbuf) >= sizeof fbuf) 68279377Simp { 69279377Simp if (tTd(44, 4)) 70279377Simp sm_dprintf("\tpathname too long\n"); 71279377Simp return ENAMETOOLONG; 72279377Simp } 73279377Simp fn = fbuf; 74279377Simp if (st == NULL) 75279377Simp st = &fstbuf; 76279377Simp 77279377Simp /* ignore SFF_SAFEDIRPATH if we are debugging */ 78279377Simp if (RealUid != 0 && RunAsUid == RealUid) 79279377Simp flags &= ~SFF_SAFEDIRPATH; 80279377Simp 81279377Simp /* first check to see if the file exists at all */ 82279377Simp# if HASLSTAT 83279377Simp if ((bitset(SFF_NOSLINK, flags) ? lstat(fn, st) 84279377Simp : stat(fn, st)) < 0) 85279377Simp# else /* HASLSTAT */ 86279377Simp if (stat(fn, st) < 0) 87279377Simp# endif /* HASLSTAT */ 88279377Simp { 89279377Simp file_errno = errno; 90279377Simp } 91279377Simp else if (bitset(SFF_SETUIDOK, flags) && 92279377Simp !bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode) && 93279377Simp S_ISREG(st->st_mode)) 94279377Simp { 95279377Simp /* 96279377Simp ** If final file is set-user-ID, run as the owner of that 97279377Simp ** file. Gotta be careful not to reveal anything too 98279377Simp ** soon here! 99279377Simp */ 100279377Simp 101279377Simp# ifdef SUID_ROOT_FILES_OK 102279377Simp if (bitset(S_ISUID, st->st_mode)) 103279377Simp# else /* SUID_ROOT_FILES_OK */ 104279377Simp if (bitset(S_ISUID, st->st_mode) && st->st_uid != 0 && 105279377Simp st->st_uid != TrustedUid) 106279377Simp# endif /* SUID_ROOT_FILES_OK */ 107279377Simp { 108279377Simp uid = st->st_uid; 109279377Simp user = NULL; 110279377Simp } 111279377Simp# ifdef SUID_ROOT_FILES_OK 112279377Simp if (bitset(S_ISGID, st->st_mode)) 113279377Simp# else /* SUID_ROOT_FILES_OK */ 114279377Simp if (bitset(S_ISGID, st->st_mode) && st->st_gid != 0) 115279377Simp# endif /* SUID_ROOT_FILES_OK */ 116279377Simp gid = st->st_gid; 117279377Simp } 118279377Simp 119279377Simp checkpath = !bitset(SFF_NOPATHCHECK, flags) || 120279377Simp (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags)); 121279377Simp if (bitset(SFF_NOWLINK, flags) && !bitset(SFF_SAFEDIRPATH, flags)) 122279377Simp { 123279377Simp int ret; 124279377Simp 125279377Simp /* check the directory */ 126279377Simp p = strrchr(fn, '/'); 127279377Simp if (p == NULL) 128279377Simp { 129279377Simp ret = safedirpath(".", uid, gid, user, 130279377Simp flags|SFF_SAFEDIRPATH, 0, 0); 131279377Simp } 132279377Simp else 133279377Simp { 134279377Simp *p = '\0'; 135279377Simp ret = safedirpath(fn, uid, gid, user, 136279377Simp flags|SFF_SAFEDIRPATH, 0, 0); 137279377Simp *p = '/'; 138279377Simp } 139279377Simp if (ret == 0) 140279377Simp { 141279377Simp /* directory is safe */ 142279377Simp checkpath = false; 143279377Simp } 144279377Simp else 145279377Simp { 146279377Simp# if HASLSTAT 147279377Simp /* Need lstat() information if called stat() before */ 148279377Simp if (!bitset(SFF_NOSLINK, flags) && lstat(fn, st) < 0) 149279377Simp { 150279377Simp ret = errno; 151279377Simp if (tTd(44, 4)) 152279377Simp sm_dprintf("\t%s\n", sm_errstring(ret)); 153279377Simp return ret; 154279377Simp } 155279377Simp# endif /* HASLSTAT */ 156279377Simp /* directory is writable: disallow links */ 157279377Simp flags |= SFF_NOLINK; 158279377Simp } 159279377Simp } 160279377Simp 161279377Simp if (checkpath) 162279377Simp { 163279377Simp int ret; 164279377Simp 165279377Simp p = strrchr(fn, '/'); 166279377Simp if (p == NULL) 167279377Simp { 168279377Simp ret = safedirpath(".", uid, gid, user, flags, 0, 0); 169279377Simp } 170279377Simp else 171279377Simp { 172279377Simp *p = '\0'; 173279377Simp ret = safedirpath(fn, uid, gid, user, flags, 0, 0); 174279377Simp *p = '/'; 175279377Simp } 176279377Simp if (ret != 0) 177279377Simp return ret; 178279377Simp } 179279377Simp 180279377Simp /* 181279377Simp ** If the target file doesn't exist, check the directory to 182279377Simp ** ensure that it is writable by this user. 183279377Simp */ 184279377Simp 185279377Simp if (file_errno != 0) 186279377Simp { 187279377Simp int ret = file_errno; 188279377Simp char *dir = fn; 189279377Simp 190279377Simp if (tTd(44, 4)) 191279377Simp sm_dprintf("\t%s\n", sm_errstring(ret)); 192279377Simp 193279377Simp errno = 0; 194279377Simp if (!bitset(SFF_CREAT, flags) || file_errno != ENOENT) 195279377Simp return ret; 196279377Simp 197279377Simp /* check to see if legal to create the file */ 198279377Simp p = strrchr(dir, '/'); 199279377Simp if (p == NULL) 200279377Simp dir = "."; 201279377Simp else if (p == dir) 202279377Simp dir = "/"; 203279377Simp else 204279377Simp *p = '\0'; 205279377Simp if (stat(dir, &stbuf) >= 0) 206279377Simp { 207279377Simp int md = S_IWRITE|S_IEXEC; 208279377Simp 209279377Simp ret = 0; 210279377Simp if (stbuf.st_uid == uid) 211279377Simp /* EMPTY */ 212279377Simp ; 213279377Simp else if (uid == 0 && stbuf.st_uid == TrustedUid) 214279377Simp /* EMPTY */ 215279377Simp ; 216279377Simp else 217279377Simp { 218279377Simp md >>= 3; 219279377Simp if (stbuf.st_gid == gid) 220279377Simp /* EMPTY */ 221279377Simp ; 222279377Simp# ifndef NO_GROUP_SET 223279377Simp else if (user != NULL && !DontInitGroups && 224279377Simp ((gr != NULL && 225279377Simp gr->gr_gid == stbuf.st_gid) || 226279377Simp (gr = getgrgid(stbuf.st_gid)) != NULL)) 227279377Simp { 228279377Simp register char **gp; 229279377Simp 230279377Simp for (gp = gr->gr_mem; *gp != NULL; gp++) 231279377Simp if (strcmp(*gp, user) == 0) 232279377Simp break; 233279377Simp if (*gp == NULL) 234279377Simp md >>= 3; 235279377Simp } 236279377Simp# endif /* ! NO_GROUP_SET */ 237279377Simp else 238279377Simp md >>= 3; 239279377Simp } 240279377Simp if ((stbuf.st_mode & md) != md) 241279377Simp ret = errno = EACCES; 242279377Simp } 243279377Simp else 244279377Simp ret = errno; 245279377Simp if (tTd(44, 4)) 246279377Simp sm_dprintf("\t[final dir %s uid %d mode %lo] %s\n", 247279377Simp dir, (int) stbuf.st_uid, 248279377Simp (unsigned long) stbuf.st_mode, 249279377Simp sm_errstring(ret)); 250279377Simp if (p != NULL) 251279377Simp *p = '/'; 252279377Simp st->st_mode = ST_MODE_NOFILE; 253279377Simp return ret; 254279377Simp } 255279377Simp 256279377Simp# ifdef S_ISLNK 257279377Simp if (bitset(SFF_NOSLINK, flags) && S_ISLNK(st->st_mode)) 258279377Simp { 259279377Simp if (tTd(44, 4)) 260279377Simp sm_dprintf("\t[slink mode %lo]\tE_SM_NOSLINK\n", 261279377Simp (unsigned long) st->st_mode); 262279377Simp return E_SM_NOSLINK; 263279377Simp } 264279377Simp# endif /* S_ISLNK */ 265279377Simp if (bitset(SFF_REGONLY, flags) && !S_ISREG(st->st_mode)) 266279377Simp { 267279377Simp if (tTd(44, 4)) 268279377Simp sm_dprintf("\t[non-reg mode %lo]\tE_SM_REGONLY\n", 269279377Simp (unsigned long) st->st_mode); 270279377Simp return E_SM_REGONLY; 271279377Simp } 272279377Simp if (bitset(SFF_NOGWFILES, flags) && 273279377Simp bitset(S_IWGRP, st->st_mode)) 274279377Simp { 275279377Simp if (tTd(44, 4)) 276279377Simp sm_dprintf("\t[write bits %lo]\tE_SM_GWFILE\n", 277279377Simp (unsigned long) st->st_mode); 278279377Simp return E_SM_GWFILE; 279279377Simp } 280279377Simp if (bitset(SFF_NOWWFILES, flags) && 281279377Simp bitset(S_IWOTH, st->st_mode)) 282279377Simp { 283279377Simp if (tTd(44, 4)) 284279377Simp sm_dprintf("\t[write bits %lo]\tE_SM_WWFILE\n", 285279377Simp (unsigned long) st->st_mode); 286279377Simp return E_SM_WWFILE; 287279377Simp } 288279377Simp if (bitset(SFF_NOGRFILES, flags) && bitset(S_IRGRP, st->st_mode)) 289279377Simp { 290279377Simp if (tTd(44, 4)) 291279377Simp sm_dprintf("\t[read bits %lo]\tE_SM_GRFILE\n", 292279377Simp (unsigned long) st->st_mode); 293279377Simp return E_SM_GRFILE; 294279377Simp } 295279377Simp if (bitset(SFF_NOWRFILES, flags) && bitset(S_IROTH, st->st_mode)) 296279377Simp { 297279377Simp if (tTd(44, 4)) 298279377Simp sm_dprintf("\t[read bits %lo]\tE_SM_WRFILE\n", 299279377Simp (unsigned long) st->st_mode); 300279377Simp return E_SM_WRFILE; 301279377Simp } 302279377Simp if (!bitset(SFF_EXECOK, flags) && 303279377Simp bitset(S_IWUSR|S_IWGRP|S_IWOTH, mode) && 304279377Simp bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode)) 305279377Simp { 306279377Simp if (tTd(44, 4)) 307279377Simp sm_dprintf("\t[exec bits %lo]\tE_SM_ISEXEC]\n", 308279377Simp (unsigned long) st->st_mode); 309279377Simp return E_SM_ISEXEC; 310279377Simp } 311279377Simp if (bitset(SFF_NOHLINK, flags) && st->st_nlink != 1) 312279377Simp { 313279377Simp if (tTd(44, 4)) 314279377Simp sm_dprintf("\t[link count %d]\tE_SM_NOHLINK\n", 315279377Simp (int) st->st_nlink); 316279377Simp return E_SM_NOHLINK; 317279377Simp } 318279377Simp 319279377Simp if (uid == 0 && bitset(SFF_OPENASROOT, flags)) 320279377Simp /* EMPTY */ 321279377Simp ; 322279377Simp else if (uid == 0 && !bitset(SFF_ROOTOK, flags)) 323279377Simp mode >>= 6; 324279377Simp else if (st->st_uid == uid) 325279377Simp /* EMPTY */ 326279377Simp ; 327279377Simp else if (uid == 0 && st->st_uid == TrustedUid) 328279377Simp /* EMPTY */ 329279377Simp ; 330279377Simp else 331279377Simp { 332279377Simp mode >>= 3; 333279377Simp if (st->st_gid == gid) 334279377Simp /* EMPTY */ 335279377Simp ; 336279377Simp# ifndef NO_GROUP_SET 337279377Simp else if (user != NULL && !DontInitGroups && 338279377Simp ((gr != NULL && gr->gr_gid == st->st_gid) || 339279377Simp (gr = getgrgid(st->st_gid)) != NULL)) 340279377Simp { 341279377Simp register char **gp; 342279377Simp 343279377Simp for (gp = gr->gr_mem; *gp != NULL; gp++) 344279377Simp if (strcmp(*gp, user) == 0) 345279377Simp break; 346279377Simp if (*gp == NULL) 347279377Simp mode >>= 3; 348279377Simp } 349279377Simp# endif /* ! NO_GROUP_SET */ 350279377Simp else 351279377Simp mode >>= 3; 352279377Simp } 353279377Simp if (tTd(44, 4)) 354279377Simp sm_dprintf("\t[uid %d, nlink %d, stat %lo, mode %lo] ", 355279377Simp (int) st->st_uid, (int) st->st_nlink, 356279377Simp (unsigned long) st->st_mode, (unsigned long) mode); 357279377Simp if ((st->st_uid == uid || st->st_uid == 0 || 358279377Simp st->st_uid == TrustedUid || 359279377Simp !bitset(SFF_MUSTOWN, flags)) && 360279377Simp (st->st_mode & mode) == mode) 361279377Simp { 362279377Simp if (tTd(44, 4)) 363279377Simp sm_dprintf("\tOK\n"); 364279377Simp return 0; 365279377Simp } 366279377Simp if (tTd(44, 4)) 367279377Simp sm_dprintf("\tEACCES\n"); 368279377Simp return EACCES; 369279377Simp} 370279377Simp/* 371279377Simp** SAFEDIRPATH -- check to make sure a path to a directory is safe 372279377Simp** 373279377Simp** Safe means not writable and owned by the right folks. 374279377Simp** 375279377Simp** Parameters: 376279377Simp** fn -- filename to check. 377279377Simp** uid -- user id to compare against. 378279377Simp** gid -- group id to compare against. 379279377Simp** user -- user name to compare against (used for group 380279377Simp** sets). 381279377Simp** flags -- modifiers: 382279377Simp** SFF_ROOTOK -- ok to use root permissions to open. 383279377Simp** SFF_SAFEDIRPATH -- writable directories are considered 384279377Simp** to be fatal errors. 385279377Simp** level -- symlink recursive level. 386279377Simp** offset -- offset into fn to start checking from. 387279377Simp** 388279377Simp** Returns: 389279377Simp** 0 -- if the directory path is "safe". 390279377Simp** else -- an error number associated with the path. 391279377Simp*/ 392279377Simp 393279377Simpint 394279377Simpsafedirpath(fn, uid, gid, user, flags, level, offset) 395279377Simp char *fn; 396279377Simp UID_T uid; 397279377Simp GID_T gid; 398279377Simp char *user; 399279377Simp long flags; 400279377Simp int level; 401279377Simp int offset; 402279377Simp{ 403279377Simp int ret = 0; 404279377Simp int mode = S_IWOTH; 405279377Simp char save = '\0'; 406279377Simp char *saveptr = NULL; 407279377Simp char *p, *enddir; 408279377Simp register struct group *gr = NULL; 409279377Simp char s[MAXLINKPATHLEN]; 410279377Simp struct stat stbuf; 411279377Simp 412279377Simp /* make sure we aren't in a symlink loop */ 413279377Simp if (level > MAXSYMLINKS) 414279377Simp return ELOOP; 415279377Simp 416279377Simp if (level < 0 || offset < 0 || offset > strlen(fn)) 417279377Simp return EINVAL; 418279377Simp 419279377Simp /* special case root directory */ 420279377Simp if (*fn == '\0') 421279377Simp fn = "/"; 422279377Simp 423279377Simp if (tTd(44, 4)) 424279377Simp sm_dprintf("safedirpath(%s, uid=%ld, gid=%ld, flags=%lx, level=%d, offset=%d):\n", 425279377Simp fn, (long) uid, (long) gid, flags, level, offset); 426279377Simp 427279377Simp if (!bitnset(DBS_GROUPWRITABLEDIRPATHSAFE, DontBlameSendmail)) 428279377Simp mode |= S_IWGRP; 429279377Simp 430279377Simp /* Make a modifiable copy of the filename */ 431279377Simp if (sm_strlcpy(s, fn, sizeof s) >= sizeof s) 432279377Simp return EINVAL; 433279377Simp 434279377Simp p = s + offset; 435279377Simp while (p != NULL) 436279377Simp { 437279377Simp /* put back character */ 438279377Simp if (saveptr != NULL) 439279377Simp { 440279377Simp *saveptr = save; 441279377Simp saveptr = NULL; 442279377Simp p++; 443279377Simp } 444279377Simp 445279377Simp if (*p == '\0') 446279377Simp break; 447279377Simp 448279377Simp p = strchr(p, '/'); 449279377Simp 450279377Simp /* Special case for root directory */ 451279377Simp if (p == s) 452279377Simp { 453279377Simp save = *(p + 1); 454279377Simp saveptr = p + 1; 455279377Simp *(p + 1) = '\0'; 456279377Simp } 457279377Simp else if (p != NULL) 458279377Simp { 459279377Simp save = *p; 460279377Simp saveptr = p; 461279377Simp *p = '\0'; 462279377Simp } 463279377Simp 464279377Simp /* Heuristic: . and .. have already been checked */ 465279377Simp enddir = strrchr(s, '/'); 466279377Simp if (enddir != NULL && 467279377Simp (strcmp(enddir, "/..") == 0 || 468279377Simp strcmp(enddir, "/.") == 0)) 469279377Simp continue; 470279377Simp 471279377Simp if (tTd(44, 20)) 472279377Simp sm_dprintf("\t[dir %s]\n", s); 473279377Simp 474279377Simp# if HASLSTAT 475279377Simp ret = lstat(s, &stbuf); 476279377Simp# else /* HASLSTAT */ 477279377Simp ret = stat(s, &stbuf); 478279377Simp# endif /* HASLSTAT */ 479279377Simp if (ret < 0) 480279377Simp { 481279377Simp ret = errno; 482279377Simp break; 483279377Simp } 484279377Simp 485279377Simp# ifdef S_ISLNK 486279377Simp /* Follow symlinks */ 487279377Simp if (S_ISLNK(stbuf.st_mode)) 488279377Simp { 489279377Simp int linklen; 490279377Simp char *target; 491279377Simp char buf[MAXPATHLEN]; 492279377Simp 493279377Simp memset(buf, '\0', sizeof buf); 494279377Simp linklen = readlink(s, buf, sizeof buf); 495279377Simp if (linklen < 0) 496279377Simp { 497279377Simp ret = errno; 498279377Simp break; 499279377Simp } 500279377Simp if (linklen >= sizeof buf) 501279377Simp { 502279377Simp /* file name too long for buffer */ 503279377Simp ret = errno = EINVAL; 504279377Simp break; 505279377Simp } 506279377Simp 507279377Simp offset = 0; 508279377Simp if (*buf == '/') 509279377Simp { 510279377Simp target = buf; 511279377Simp 512279377Simp /* If path is the same, avoid rechecks */ 513279377Simp while (s[offset] == buf[offset] && 514279377Simp s[offset] != '\0') 515279377Simp offset++; 516279377Simp 517279377Simp if (s[offset] == '\0' && buf[offset] == '\0') 518279377Simp { 519279377Simp /* strings match, symlink loop */ 520279377Simp return ELOOP; 521279377Simp } 522279377Simp 523279377Simp /* back off from the mismatch */ 524279377Simp if (offset > 0) 525279377Simp offset--; 526279377Simp 527279377Simp /* Make sure we are at a directory break */ 528279377Simp if (offset > 0 && 529279377Simp s[offset] != '/' && 530279377Simp s[offset] != '\0') 531279377Simp { 532279377Simp while (buf[offset] != '/' && 533279377Simp offset > 0) 534279377Simp offset--; 535279377Simp } 536279377Simp if (offset > 0 && 537279377Simp s[offset] == '/' && 538279377Simp buf[offset] == '/') 539279377Simp { 540279377Simp /* Include the trailing slash */ 541279377Simp offset++; 542279377Simp } 543279377Simp } 544279377Simp else 545279377Simp { 546279377Simp char *sptr; 547279377Simp char fullbuf[MAXLINKPATHLEN]; 548279377Simp 549279377Simp sptr = strrchr(s, '/'); 550279377Simp if (sptr != NULL) 551279377Simp { 552279377Simp *sptr = '\0'; 553279377Simp offset = sptr + 1 - s; 554279377Simp if (sm_strlcpyn(fullbuf, 555279377Simp sizeof fullbuf, 2, 556279377Simp s, "/") >= 557279377Simp sizeof fullbuf || 558279377Simp sm_strlcat(fullbuf, buf, 559279377Simp sizeof fullbuf) >= 560279377Simp sizeof fullbuf) 561279377Simp { 562279377Simp ret = EINVAL; 563279377Simp break; 564279377Simp } 565279377Simp *sptr = '/'; 566279377Simp } 567279377Simp else 568279377Simp { 569279377Simp if (sm_strlcpy(fullbuf, buf, 570279377Simp sizeof fullbuf) >= 571279377Simp sizeof fullbuf) 572279377Simp { 573279377Simp ret = EINVAL; 574279377Simp break; 575279377Simp } 576279377Simp } 577279377Simp target = fullbuf; 578279377Simp } 579279377Simp ret = safedirpath(target, uid, gid, user, flags, 580279377Simp level + 1, offset); 581279377Simp if (ret != 0) 582279377Simp break; 583279377Simp 584279377Simp /* Don't check permissions on the link file itself */ 585279377Simp continue; 586279377Simp } 587279377Simp#endif /* S_ISLNK */ 588279377Simp 589279377Simp if ((uid == 0 || bitset(SFF_SAFEDIRPATH, flags)) && 590279377Simp#ifdef S_ISVTX 591279377Simp !(bitnset(DBS_TRUSTSTICKYBIT, DontBlameSendmail) && 592279377Simp bitset(S_ISVTX, stbuf.st_mode)) && 593279377Simp#endif /* S_ISVTX */ 594279377Simp bitset(mode, stbuf.st_mode)) 595279377Simp { 596279377Simp if (tTd(44, 4)) 597279377Simp sm_dprintf("\t[dir %s] mode %lo ", 598279377Simp s, (unsigned long) stbuf.st_mode); 599279377Simp if (bitset(SFF_SAFEDIRPATH, flags)) 600279377Simp { 601279377Simp if (bitset(S_IWOTH, stbuf.st_mode)) 602279377Simp ret = E_SM_WWDIR; 603279377Simp else 604279377Simp ret = E_SM_GWDIR; 605279377Simp if (tTd(44, 4)) 606279377Simp sm_dprintf("FATAL\n"); 607279377Simp break; 608279377Simp } 609279377Simp if (tTd(44, 4)) 610279377Simp sm_dprintf("WARNING\n"); 611279377Simp if (Verbose > 1) 612279377Simp message("051 WARNING: %s writable directory %s", 613279377Simp bitset(S_IWOTH, stbuf.st_mode) 614279377Simp ? "World" 615279377Simp : "Group", 616279377Simp s); 617279377Simp } 618279377Simp if (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags)) 619279377Simp { 620279377Simp if (bitset(S_IXOTH, stbuf.st_mode)) 621279377Simp continue; 622279377Simp ret = EACCES; 623279377Simp break; 624279377Simp } 625279377Simp 626279377Simp /* 627279377Simp ** Let OS determine access to file if we are not 628279377Simp ** running as a privileged user. This allows ACLs 629279377Simp ** to work. Also, if opening as root, assume we can 630279377Simp ** scan the directory. 631279377Simp */ 632279377Simp if (geteuid() != 0 || bitset(SFF_OPENASROOT, flags)) 633279377Simp continue; 634279377Simp 635279377Simp if (stbuf.st_uid == uid && 636279377Simp bitset(S_IXUSR, stbuf.st_mode)) 637279377Simp continue; 638279377Simp if (stbuf.st_gid == gid && 639279377Simp bitset(S_IXGRP, stbuf.st_mode)) 640279377Simp continue; 641279377Simp# ifndef NO_GROUP_SET 642279377Simp if (user != NULL && !DontInitGroups && 643279377Simp ((gr != NULL && gr->gr_gid == stbuf.st_gid) || 644279377Simp (gr = getgrgid(stbuf.st_gid)) != NULL)) 645279377Simp { 646279377Simp register char **gp; 647279377Simp 648279377Simp for (gp = gr->gr_mem; gp != NULL && *gp != NULL; gp++) 649279377Simp if (strcmp(*gp, user) == 0) 650279377Simp break; 651279377Simp if (gp != NULL && *gp != NULL && 652279377Simp bitset(S_IXGRP, stbuf.st_mode)) 653279377Simp continue; 654279377Simp } 655279377Simp# endif /* ! NO_GROUP_SET */ 656279377Simp if (!bitset(S_IXOTH, stbuf.st_mode)) 657279377Simp { 658279377Simp ret = EACCES; 659279377Simp break; 660279377Simp } 661279377Simp } 662279377Simp if (tTd(44, 4)) 663279377Simp sm_dprintf("\t[dir %s] %s\n", fn, 664279377Simp ret == 0 ? "OK" : sm_errstring(ret)); 665279377Simp return ret; 666279377Simp} 667279377Simp/* 668279377Simp** SAFEOPEN -- do a file open with extra checking 669279377Simp** 670279377Simp** Parameters: 671279377Simp** fn -- the file name to open. 672279377Simp** omode -- the open-style mode flags. 673279377Simp** cmode -- the create-style mode flags. 674279377Simp** sff -- safefile flags. 675279377Simp** 676279377Simp** Returns: 677279377Simp** Same as open. 678279377Simp*/ 679279377Simp 680279377Simpint 681279377Simpsafeopen(fn, omode, cmode, sff) 682279377Simp char *fn; 683279377Simp int omode; 684279377Simp int cmode; 685279377Simp long sff; 686279377Simp{ 687279377Simp int rval; 688279377Simp int fd; 689279377Simp int smode; 690279377Simp struct stat stb; 691279377Simp 692279377Simp if (tTd(44, 10)) 693279377Simp sm_dprintf("safeopen: fn=%s, omode=%x, cmode=%x, sff=%lx\n", 694279377Simp fn, omode, cmode, sff); 695279377Simp 696279377Simp if (bitset(O_CREAT, omode)) 697279377Simp sff |= SFF_CREAT; 698279377Simp omode &= ~O_CREAT; 699279377Simp smode = 0; 700279377Simp switch (omode & O_ACCMODE) 701279377Simp { 702279377Simp case O_RDONLY: 703279377Simp smode = S_IREAD; 704279377Simp break; 705279377Simp 706279377Simp case O_WRONLY: 707279377Simp smode = S_IWRITE; 708279377Simp break; 709279377Simp 710279377Simp case O_RDWR: 711279377Simp smode = S_IREAD|S_IWRITE; 712279377Simp break; 713279377Simp 714279377Simp default: 715279377Simp smode = 0; 716279377Simp break; 717279377Simp } 718279377Simp if (bitset(SFF_OPENASROOT, sff)) 719279377Simp rval = safefile(fn, RunAsUid, RunAsGid, RunAsUserName, 720279377Simp sff, smode, &stb); 721279377Simp else 722279377Simp rval = safefile(fn, RealUid, RealGid, RealUserName, 723279377Simp sff, smode, &stb); 724279377Simp if (rval != 0) 725279377Simp { 726279377Simp errno = rval; 727279377Simp return -1; 728279377Simp } 729279377Simp if (stb.st_mode == ST_MODE_NOFILE && bitset(SFF_CREAT, sff)) 730279377Simp omode |= O_CREAT | (bitset(SFF_NOTEXCL, sff) ? 0 : O_EXCL); 731279377Simp else if (bitset(SFF_CREAT, sff) && bitset(O_EXCL, omode)) 732279377Simp { 733279377Simp /* The file exists so an exclusive create would fail */ 734279377Simp errno = EEXIST; 735279377Simp return -1; 736279377Simp } 737279377Simp 738279377Simp fd = dfopen(fn, omode, cmode, sff); 739279377Simp if (fd < 0) 740279377Simp return fd; 741279377Simp if (filechanged(fn, fd, &stb)) 742279377Simp { 743279377Simp syserr("554 5.3.0 cannot open: file %s changed after open", fn); 744279377Simp (void) close(fd); 745279377Simp errno = E_SM_FILECHANGE; 746279377Simp return -1; 747279377Simp } 748279377Simp return fd; 749279377Simp} 750279377Simp/* 751279377Simp** SAFEFOPEN -- do a file open with extra checking 752279377Simp** 753279377Simp** Parameters: 754279377Simp** fn -- the file name to open. 755279377Simp** omode -- the open-style mode flags. 756279377Simp** cmode -- the create-style mode flags. 757279377Simp** sff -- safefile flags. 758279377Simp** 759279377Simp** Returns: 760279377Simp** Same as fopen. 761279377Simp*/ 762279377Simp 763279377SimpSM_FILE_T * 764279377Simpsafefopen(fn, omode, cmode, sff) 765279377Simp char *fn; 766279377Simp int omode; 767279377Simp int cmode; 768279377Simp long sff; 769279377Simp{ 770279377Simp int fd; 771279377Simp int save_errno; 772279377Simp SM_FILE_T *fp; 773279377Simp int fmode; 774279377Simp 775279377Simp switch (omode & O_ACCMODE) 776279377Simp { 777279377Simp case O_RDONLY: 778279377Simp fmode = SM_IO_RDONLY; 779279377Simp break; 780279377Simp 781279377Simp case O_WRONLY: 782279377Simp if (bitset(O_APPEND, omode)) 783279377Simp fmode = SM_IO_APPEND; 784279377Simp else 785279377Simp fmode = SM_IO_WRONLY; 786279377Simp break; 787279377Simp 788279377Simp case O_RDWR: 789279377Simp if (bitset(O_TRUNC, omode)) 790279377Simp fmode = SM_IO_RDWRTR; 791279377Simp else if (bitset(O_APPEND, omode)) 792279377Simp fmode = SM_IO_APPENDRW; 793279377Simp else 794279377Simp fmode = SM_IO_RDWR; 795279377Simp break; 796279377Simp 797279377Simp default: 798279377Simp syserr("554 5.3.5 safefopen: unknown omode %o", omode); 799279377Simp fmode = 0; 800279377Simp } 801279377Simp fd = safeopen(fn, omode, cmode, sff); 802279377Simp if (fd < 0) 803279377Simp { 804279377Simp save_errno = errno; 805279377Simp if (tTd(44, 10)) 806279377Simp sm_dprintf("safefopen: safeopen failed: %s\n", 807279377Simp sm_errstring(errno)); 808279377Simp errno = save_errno; 809279377Simp return NULL; 810279377Simp } 811279377Simp fp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT, 812279377Simp (void *) &fd, fmode, NULL); 813279377Simp if (fp != NULL) 814279377Simp return fp; 815279377Simp 816279377Simp save_errno = errno; 817279377Simp if (tTd(44, 10)) 818279377Simp { 819279377Simp sm_dprintf("safefopen: fdopen(%s, %d) failed: omode=%x, sff=%lx, err=%s\n", 820279377Simp fn, fmode, omode, sff, sm_errstring(errno)); 821279377Simp } 822279377Simp (void) close(fd); 823279377Simp errno = save_errno; 824279377Simp return NULL; 825279377Simp} 826279377Simp/* 827279377Simp** FILECHANGED -- check to see if file changed after being opened 828279377Simp** 829279377Simp** Parameters: 830279377Simp** fn -- pathname of file to check. 831279377Simp** fd -- file descriptor to check. 832279377Simp** stb -- stat structure from before open. 833279377Simp** 834279377Simp** Returns: 835279377Simp** true -- if a problem was detected. 836279377Simp** false -- if this file is still the same. 837279377Simp*/ 838279377Simp 839279377Simpbool 840279377Simpfilechanged(fn, fd, stb) 841279377Simp char *fn; 842279377Simp int fd; 843279377Simp struct stat *stb; 844279377Simp{ 845279377Simp struct stat sta; 846279377Simp 847279377Simp if (stb->st_mode == ST_MODE_NOFILE) 848279377Simp { 849279377Simp# if HASLSTAT && BOGUS_O_EXCL 850279377Simp /* only necessary if exclusive open follows symbolic links */ 851279377Simp if (lstat(fn, stb) < 0 || stb->st_nlink != 1) 852279377Simp return true; 853279377Simp# else /* HASLSTAT && BOGUS_O_EXCL */ 854279377Simp return false; 855279377Simp# endif /* HASLSTAT && BOGUS_O_EXCL */ 856279377Simp } 857279377Simp if (fstat(fd, &sta) < 0) 858279377Simp return true; 859279377Simp 860279377Simp if (sta.st_nlink != stb->st_nlink || 861279377Simp sta.st_dev != stb->st_dev || 862279377Simp sta.st_ino != stb->st_ino || 863279377Simp# if HAS_ST_GEN && 0 /* AFS returns garbage in st_gen */ 864279377Simp sta.st_gen != stb->st_gen || 865279377Simp# endif /* HAS_ST_GEN && 0 */ 866279377Simp sta.st_uid != stb->st_uid || 867279377Simp sta.st_gid != stb->st_gid) 868279377Simp { 869279377Simp if (tTd(44, 8)) 870279377Simp { 871279377Simp sm_dprintf("File changed after opening:\n"); 872279377Simp sm_dprintf(" nlink = %ld/%ld\n", 873279377Simp (long) stb->st_nlink, (long) sta.st_nlink); 874279377Simp sm_dprintf(" dev = %ld/%ld\n", 875279377Simp (long) stb->st_dev, (long) sta.st_dev); 876279377Simp sm_dprintf(" ino = %llu/%llu\n", 877279377Simp (ULONGLONG_T) stb->st_ino, 878279377Simp (ULONGLONG_T) sta.st_ino); 879279377Simp# if HAS_ST_GEN 880279377Simp sm_dprintf(" gen = %ld/%ld\n", 881279377Simp (long) stb->st_gen, (long) sta.st_gen); 882279377Simp# endif /* HAS_ST_GEN */ 883279377Simp sm_dprintf(" uid = %ld/%ld\n", 884279377Simp (long) stb->st_uid, (long) sta.st_uid); 885279377Simp sm_dprintf(" gid = %ld/%ld\n", 886279377Simp (long) stb->st_gid, (long) sta.st_gid); 887279377Simp } 888279377Simp return true; 889279377Simp } 890279377Simp 891279377Simp return false; 892279377Simp} 893279377Simp/* 894279377Simp** DFOPEN -- determined file open 895279377Simp** 896279377Simp** This routine has the semantics of open, except that it will 897279377Simp** keep trying a few times to make this happen. The idea is that 898279377Simp** on very loaded systems, we may run out of resources (inodes, 899279377Simp** whatever), so this tries to get around it. 900279377Simp*/ 901279377Simp 902279377Simpint 903279377Simpdfopen(filename, omode, cmode, sff) 904279377Simp char *filename; 905279377Simp int omode; 906279377Simp int cmode; 907279377Simp long sff; 908279377Simp{ 909279377Simp register int tries; 910279377Simp int fd = -1; 911279377Simp struct stat st; 912279377Simp 913279377Simp for (tries = 0; tries < 10; tries++) 914279377Simp { 915279377Simp (void) sleep((unsigned) (10 * tries)); 916279377Simp errno = 0; 917279377Simp fd = open(filename, omode, cmode); 918279377Simp if (fd >= 0) 919279377Simp break; 920279377Simp switch (errno) 921279377Simp { 922279377Simp case ENFILE: /* system file table full */ 923279377Simp case EINTR: /* interrupted syscall */ 924279377Simp#ifdef ETXTBSY 925279377Simp case ETXTBSY: /* Apollo: net file locked */ 926279377Simp#endif /* ETXTBSY */ 927279377Simp continue; 928279377Simp } 929279377Simp break; 930279377Simp } 931279377Simp if (!bitset(SFF_NOLOCK, sff) && 932279377Simp fd >= 0 && 933279377Simp fstat(fd, &st) >= 0 && 934279377Simp S_ISREG(st.st_mode)) 935279377Simp { 936279377Simp int locktype; 937279377Simp 938279377Simp /* lock the file to avoid accidental conflicts */ 939279377Simp if ((omode & O_ACCMODE) != O_RDONLY) 940279377Simp locktype = LOCK_EX; 941279377Simp else 942279377Simp locktype = LOCK_SH; 943279377Simp if (!lockfile(fd, filename, NULL, locktype)) 944279377Simp { 945279377Simp int save_errno = errno; 946279377Simp 947279377Simp (void) close(fd); 948279377Simp fd = -1; 949279377Simp errno = save_errno; 950279377Simp } 951279377Simp else 952279377Simp errno = 0; 953279377Simp } 954279377Simp return fd; 955279377Simp} 956279377Simp