safefile.c revision 90792
164562Sgshapiro/* 280785Sgshapiro * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers. 364562Sgshapiro * All rights reserved. 464562Sgshapiro * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved. 564562Sgshapiro * Copyright (c) 1988, 1993 664562Sgshapiro * The Regents of the University of California. All rights reserved. 764562Sgshapiro * 864562Sgshapiro * By using this file, you agree to the terms and conditions set 964562Sgshapiro * forth in the LICENSE file which can be found at the top level of 1064562Sgshapiro * the sendmail distribution. 1164562Sgshapiro * 1264562Sgshapiro */ 1364562Sgshapiro 1464562Sgshapiro#include <sendmail.h> 1590792Sgshapiro#include <sm/io.h> 1690792Sgshapiro#include <sm/errstring.h> 1764562Sgshapiro 1890792SgshapiroSM_RCSID("@(#)$Id: safefile.c,v 8.121 2001/10/11 21:46:13 gshapiro Exp $") 1964562Sgshapiro 2090792Sgshapiro 2190792Sgshapiro/* 2264562Sgshapiro** SAFEFILE -- return 0 if a file exists and is safe for a user. 2364562Sgshapiro** 2464562Sgshapiro** Parameters: 2564562Sgshapiro** fn -- filename to check. 2664562Sgshapiro** uid -- user id to compare against. 2764562Sgshapiro** gid -- group id to compare against. 2864562Sgshapiro** user -- user name to compare against (used for group 2964562Sgshapiro** sets). 3064562Sgshapiro** flags -- modifiers: 3164562Sgshapiro** SFF_MUSTOWN -- "uid" must own this file. 3264562Sgshapiro** SFF_NOSLINK -- file cannot be a symbolic link. 3364562Sgshapiro** mode -- mode bits that must match. 3464562Sgshapiro** st -- if set, points to a stat structure that will 3564562Sgshapiro** get the stat info for the file. 3664562Sgshapiro** 3764562Sgshapiro** Returns: 3864562Sgshapiro** 0 if fn exists, is owned by uid, and matches mode. 3964562Sgshapiro** An errno otherwise. The actual errno is cleared. 4064562Sgshapiro** 4164562Sgshapiro** Side Effects: 4264562Sgshapiro** none. 4364562Sgshapiro*/ 4464562Sgshapiro 4564562Sgshapiroint 4664562Sgshapirosafefile(fn, uid, gid, user, flags, mode, st) 4764562Sgshapiro char *fn; 4864562Sgshapiro UID_T uid; 4964562Sgshapiro GID_T gid; 5064562Sgshapiro char *user; 5164562Sgshapiro long flags; 5264562Sgshapiro int mode; 5364562Sgshapiro struct stat *st; 5464562Sgshapiro{ 5564562Sgshapiro register char *p; 5664562Sgshapiro register struct group *gr = NULL; 5764562Sgshapiro int file_errno = 0; 5864562Sgshapiro bool checkpath; 5964562Sgshapiro struct stat stbuf; 6064562Sgshapiro struct stat fstbuf; 6164562Sgshapiro char fbuf[MAXPATHLEN + 1]; 6264562Sgshapiro 6364562Sgshapiro if (tTd(44, 4)) 6490792Sgshapiro sm_dprintf("safefile(%s, uid=%d, gid=%d, flags=%lx, mode=%o):\n", 6564562Sgshapiro fn, (int) uid, (int) gid, flags, mode); 6664562Sgshapiro errno = 0; 6790792Sgshapiro if (sm_strlcpy(fbuf, fn, sizeof fbuf) >= sizeof fbuf) 6864562Sgshapiro { 6964562Sgshapiro if (tTd(44, 4)) 7090792Sgshapiro sm_dprintf("\tpathname too long\n"); 7164562Sgshapiro return ENAMETOOLONG; 7264562Sgshapiro } 7364562Sgshapiro fn = fbuf; 7490792Sgshapiro if (st == NULL) 7590792Sgshapiro st = &fstbuf; 7664562Sgshapiro 7764562Sgshapiro /* ignore SFF_SAFEDIRPATH if we are debugging */ 7864562Sgshapiro if (RealUid != 0 && RunAsUid == RealUid) 7964562Sgshapiro flags &= ~SFF_SAFEDIRPATH; 8064562Sgshapiro 8164562Sgshapiro /* first check to see if the file exists at all */ 8264562Sgshapiro# if HASLSTAT 8364562Sgshapiro if ((bitset(SFF_NOSLINK, flags) ? lstat(fn, st) 8464562Sgshapiro : stat(fn, st)) < 0) 8564562Sgshapiro# else /* HASLSTAT */ 8664562Sgshapiro if (stat(fn, st) < 0) 8764562Sgshapiro# endif /* HASLSTAT */ 8864562Sgshapiro { 8964562Sgshapiro file_errno = errno; 9064562Sgshapiro } 9164562Sgshapiro else if (bitset(SFF_SETUIDOK, flags) && 9264562Sgshapiro !bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode) && 9364562Sgshapiro S_ISREG(st->st_mode)) 9464562Sgshapiro { 9564562Sgshapiro /* 9690792Sgshapiro ** If final file is set-user-ID, run as the owner of that 9764562Sgshapiro ** file. Gotta be careful not to reveal anything too 9864562Sgshapiro ** soon here! 9964562Sgshapiro */ 10064562Sgshapiro 10164562Sgshapiro# ifdef SUID_ROOT_FILES_OK 10264562Sgshapiro if (bitset(S_ISUID, st->st_mode)) 10364562Sgshapiro# else /* SUID_ROOT_FILES_OK */ 10464562Sgshapiro if (bitset(S_ISUID, st->st_mode) && st->st_uid != 0 && 10564562Sgshapiro st->st_uid != TrustedUid) 10664562Sgshapiro# endif /* SUID_ROOT_FILES_OK */ 10764562Sgshapiro { 10864562Sgshapiro uid = st->st_uid; 10964562Sgshapiro user = NULL; 11064562Sgshapiro } 11164562Sgshapiro# ifdef SUID_ROOT_FILES_OK 11264562Sgshapiro if (bitset(S_ISGID, st->st_mode)) 11364562Sgshapiro# else /* SUID_ROOT_FILES_OK */ 11464562Sgshapiro if (bitset(S_ISGID, st->st_mode) && st->st_gid != 0) 11564562Sgshapiro# endif /* SUID_ROOT_FILES_OK */ 11664562Sgshapiro gid = st->st_gid; 11764562Sgshapiro } 11864562Sgshapiro 11964562Sgshapiro checkpath = !bitset(SFF_NOPATHCHECK, flags) || 12064562Sgshapiro (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags)); 12164562Sgshapiro if (bitset(SFF_NOWLINK, flags) && !bitset(SFF_SAFEDIRPATH, flags)) 12264562Sgshapiro { 12364562Sgshapiro int ret; 12464562Sgshapiro 12564562Sgshapiro /* check the directory */ 12664562Sgshapiro p = strrchr(fn, '/'); 12764562Sgshapiro if (p == NULL) 12864562Sgshapiro { 12964562Sgshapiro ret = safedirpath(".", uid, gid, user, 13064562Sgshapiro flags|SFF_SAFEDIRPATH, 0, 0); 13164562Sgshapiro } 13264562Sgshapiro else 13364562Sgshapiro { 13464562Sgshapiro *p = '\0'; 13564562Sgshapiro ret = safedirpath(fn, uid, gid, user, 13664562Sgshapiro flags|SFF_SAFEDIRPATH, 0, 0); 13764562Sgshapiro *p = '/'; 13864562Sgshapiro } 13964562Sgshapiro if (ret == 0) 14064562Sgshapiro { 14164562Sgshapiro /* directory is safe */ 14290792Sgshapiro checkpath = false; 14364562Sgshapiro } 14464562Sgshapiro else 14564562Sgshapiro { 14664562Sgshapiro# if HASLSTAT 14764562Sgshapiro /* Need lstat() information if called stat() before */ 14864562Sgshapiro if (!bitset(SFF_NOSLINK, flags) && lstat(fn, st) < 0) 14964562Sgshapiro { 15064562Sgshapiro ret = errno; 15164562Sgshapiro if (tTd(44, 4)) 15290792Sgshapiro sm_dprintf("\t%s\n", sm_errstring(ret)); 15364562Sgshapiro return ret; 15464562Sgshapiro } 15564562Sgshapiro# endif /* HASLSTAT */ 15664562Sgshapiro /* directory is writable: disallow links */ 15764562Sgshapiro flags |= SFF_NOLINK; 15864562Sgshapiro } 15964562Sgshapiro } 16064562Sgshapiro 16164562Sgshapiro if (checkpath) 16264562Sgshapiro { 16364562Sgshapiro int ret; 16464562Sgshapiro 16564562Sgshapiro p = strrchr(fn, '/'); 16664562Sgshapiro if (p == NULL) 16764562Sgshapiro { 16864562Sgshapiro ret = safedirpath(".", uid, gid, user, flags, 0, 0); 16964562Sgshapiro } 17064562Sgshapiro else 17164562Sgshapiro { 17264562Sgshapiro *p = '\0'; 17364562Sgshapiro ret = safedirpath(fn, uid, gid, user, flags, 0, 0); 17464562Sgshapiro *p = '/'; 17564562Sgshapiro } 17664562Sgshapiro if (ret != 0) 17764562Sgshapiro return ret; 17864562Sgshapiro } 17964562Sgshapiro 18064562Sgshapiro /* 18164562Sgshapiro ** If the target file doesn't exist, check the directory to 18264562Sgshapiro ** ensure that it is writable by this user. 18364562Sgshapiro */ 18464562Sgshapiro 18564562Sgshapiro if (file_errno != 0) 18664562Sgshapiro { 18764562Sgshapiro int ret = file_errno; 18864562Sgshapiro char *dir = fn; 18964562Sgshapiro 19064562Sgshapiro if (tTd(44, 4)) 19190792Sgshapiro sm_dprintf("\t%s\n", sm_errstring(ret)); 19264562Sgshapiro 19364562Sgshapiro errno = 0; 19464562Sgshapiro if (!bitset(SFF_CREAT, flags) || file_errno != ENOENT) 19564562Sgshapiro return ret; 19664562Sgshapiro 19764562Sgshapiro /* check to see if legal to create the file */ 19864562Sgshapiro p = strrchr(dir, '/'); 19964562Sgshapiro if (p == NULL) 20064562Sgshapiro dir = "."; 20164562Sgshapiro else if (p == dir) 20264562Sgshapiro dir = "/"; 20364562Sgshapiro else 20464562Sgshapiro *p = '\0'; 20564562Sgshapiro if (stat(dir, &stbuf) >= 0) 20664562Sgshapiro { 20764562Sgshapiro int md = S_IWRITE|S_IEXEC; 20864562Sgshapiro 20990792Sgshapiro ret = 0; 21064562Sgshapiro if (stbuf.st_uid == uid) 21164562Sgshapiro /* EMPTY */ 21264562Sgshapiro ; 21364562Sgshapiro else if (uid == 0 && stbuf.st_uid == TrustedUid) 21464562Sgshapiro /* EMPTY */ 21564562Sgshapiro ; 21664562Sgshapiro else 21764562Sgshapiro { 21864562Sgshapiro md >>= 3; 21964562Sgshapiro if (stbuf.st_gid == gid) 22064562Sgshapiro /* EMPTY */ 22164562Sgshapiro ; 22264562Sgshapiro# ifndef NO_GROUP_SET 22364562Sgshapiro else if (user != NULL && !DontInitGroups && 22464562Sgshapiro ((gr != NULL && 22564562Sgshapiro gr->gr_gid == stbuf.st_gid) || 22664562Sgshapiro (gr = getgrgid(stbuf.st_gid)) != NULL)) 22764562Sgshapiro { 22864562Sgshapiro register char **gp; 22964562Sgshapiro 23064562Sgshapiro for (gp = gr->gr_mem; *gp != NULL; gp++) 23164562Sgshapiro if (strcmp(*gp, user) == 0) 23264562Sgshapiro break; 23364562Sgshapiro if (*gp == NULL) 23464562Sgshapiro md >>= 3; 23564562Sgshapiro } 23664562Sgshapiro# endif /* ! NO_GROUP_SET */ 23764562Sgshapiro else 23864562Sgshapiro md >>= 3; 23964562Sgshapiro } 24064562Sgshapiro if ((stbuf.st_mode & md) != md) 24190792Sgshapiro ret = errno = EACCES; 24264562Sgshapiro } 24390792Sgshapiro else 24490792Sgshapiro ret = errno; 24564562Sgshapiro if (tTd(44, 4)) 24690792Sgshapiro sm_dprintf("\t[final dir %s uid %d mode %lo] %s\n", 24790792Sgshapiro dir, (int) stbuf.st_uid, 24890792Sgshapiro (unsigned long) stbuf.st_mode, 24990792Sgshapiro sm_errstring(ret)); 25064562Sgshapiro if (p != NULL) 25164562Sgshapiro *p = '/'; 25264562Sgshapiro st->st_mode = ST_MODE_NOFILE; 25364562Sgshapiro return ret; 25464562Sgshapiro } 25564562Sgshapiro 25664562Sgshapiro# ifdef S_ISLNK 25764562Sgshapiro if (bitset(SFF_NOSLINK, flags) && S_ISLNK(st->st_mode)) 25864562Sgshapiro { 25964562Sgshapiro if (tTd(44, 4)) 26090792Sgshapiro sm_dprintf("\t[slink mode %lo]\tE_SM_NOSLINK\n", 26190792Sgshapiro (unsigned long) st->st_mode); 26264562Sgshapiro return E_SM_NOSLINK; 26364562Sgshapiro } 26464562Sgshapiro# endif /* S_ISLNK */ 26564562Sgshapiro if (bitset(SFF_REGONLY, flags) && !S_ISREG(st->st_mode)) 26664562Sgshapiro { 26764562Sgshapiro if (tTd(44, 4)) 26890792Sgshapiro sm_dprintf("\t[non-reg mode %lo]\tE_SM_REGONLY\n", 26990792Sgshapiro (unsigned long) st->st_mode); 27064562Sgshapiro return E_SM_REGONLY; 27164562Sgshapiro } 27264562Sgshapiro if (bitset(SFF_NOGWFILES, flags) && 27364562Sgshapiro bitset(S_IWGRP, st->st_mode)) 27464562Sgshapiro { 27564562Sgshapiro if (tTd(44, 4)) 27690792Sgshapiro sm_dprintf("\t[write bits %lo]\tE_SM_GWFILE\n", 27790792Sgshapiro (unsigned long) st->st_mode); 27864562Sgshapiro return E_SM_GWFILE; 27964562Sgshapiro } 28064562Sgshapiro if (bitset(SFF_NOWWFILES, flags) && 28164562Sgshapiro bitset(S_IWOTH, st->st_mode)) 28264562Sgshapiro { 28364562Sgshapiro if (tTd(44, 4)) 28490792Sgshapiro sm_dprintf("\t[write bits %lo]\tE_SM_WWFILE\n", 28590792Sgshapiro (unsigned long) st->st_mode); 28664562Sgshapiro return E_SM_WWFILE; 28764562Sgshapiro } 28864562Sgshapiro if (bitset(SFF_NOGRFILES, flags) && bitset(S_IRGRP, st->st_mode)) 28964562Sgshapiro { 29064562Sgshapiro if (tTd(44, 4)) 29190792Sgshapiro sm_dprintf("\t[read bits %lo]\tE_SM_GRFILE\n", 29290792Sgshapiro (unsigned long) st->st_mode); 29364562Sgshapiro return E_SM_GRFILE; 29464562Sgshapiro } 29564562Sgshapiro if (bitset(SFF_NOWRFILES, flags) && bitset(S_IROTH, st->st_mode)) 29664562Sgshapiro { 29764562Sgshapiro if (tTd(44, 4)) 29890792Sgshapiro sm_dprintf("\t[read bits %lo]\tE_SM_WRFILE\n", 29990792Sgshapiro (unsigned long) st->st_mode); 30064562Sgshapiro return E_SM_WRFILE; 30164562Sgshapiro } 30264562Sgshapiro if (!bitset(SFF_EXECOK, flags) && 30364562Sgshapiro bitset(S_IWUSR|S_IWGRP|S_IWOTH, mode) && 30464562Sgshapiro bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode)) 30564562Sgshapiro { 30664562Sgshapiro if (tTd(44, 4)) 30790792Sgshapiro sm_dprintf("\t[exec bits %lo]\tE_SM_ISEXEC]\n", 30890792Sgshapiro (unsigned long) st->st_mode); 30964562Sgshapiro return E_SM_ISEXEC; 31064562Sgshapiro } 31164562Sgshapiro if (bitset(SFF_NOHLINK, flags) && st->st_nlink != 1) 31264562Sgshapiro { 31364562Sgshapiro if (tTd(44, 4)) 31490792Sgshapiro sm_dprintf("\t[link count %d]\tE_SM_NOHLINK\n", 31564562Sgshapiro (int) st->st_nlink); 31664562Sgshapiro return E_SM_NOHLINK; 31764562Sgshapiro } 31864562Sgshapiro 31964562Sgshapiro if (uid == 0 && bitset(SFF_OPENASROOT, flags)) 32064562Sgshapiro /* EMPTY */ 32164562Sgshapiro ; 32264562Sgshapiro else if (uid == 0 && !bitset(SFF_ROOTOK, flags)) 32364562Sgshapiro mode >>= 6; 32464562Sgshapiro else if (st->st_uid == uid) 32564562Sgshapiro /* EMPTY */ 32664562Sgshapiro ; 32764562Sgshapiro else if (uid == 0 && st->st_uid == TrustedUid) 32864562Sgshapiro /* EMPTY */ 32964562Sgshapiro ; 33064562Sgshapiro else 33164562Sgshapiro { 33264562Sgshapiro mode >>= 3; 33364562Sgshapiro if (st->st_gid == gid) 33464562Sgshapiro /* EMPTY */ 33564562Sgshapiro ; 33664562Sgshapiro# ifndef NO_GROUP_SET 33764562Sgshapiro else if (user != NULL && !DontInitGroups && 33864562Sgshapiro ((gr != NULL && gr->gr_gid == st->st_gid) || 33964562Sgshapiro (gr = getgrgid(st->st_gid)) != NULL)) 34064562Sgshapiro { 34164562Sgshapiro register char **gp; 34264562Sgshapiro 34364562Sgshapiro for (gp = gr->gr_mem; *gp != NULL; gp++) 34464562Sgshapiro if (strcmp(*gp, user) == 0) 34564562Sgshapiro break; 34664562Sgshapiro if (*gp == NULL) 34764562Sgshapiro mode >>= 3; 34864562Sgshapiro } 34964562Sgshapiro# endif /* ! NO_GROUP_SET */ 35064562Sgshapiro else 35164562Sgshapiro mode >>= 3; 35264562Sgshapiro } 35364562Sgshapiro if (tTd(44, 4)) 35490792Sgshapiro sm_dprintf("\t[uid %d, nlink %d, stat %lo, mode %lo] ", 35564562Sgshapiro (int) st->st_uid, (int) st->st_nlink, 35690792Sgshapiro (unsigned long) st->st_mode, (unsigned long) mode); 35764562Sgshapiro if ((st->st_uid == uid || st->st_uid == 0 || 35864562Sgshapiro st->st_uid == TrustedUid || 35964562Sgshapiro !bitset(SFF_MUSTOWN, flags)) && 36064562Sgshapiro (st->st_mode & mode) == mode) 36164562Sgshapiro { 36264562Sgshapiro if (tTd(44, 4)) 36390792Sgshapiro sm_dprintf("\tOK\n"); 36464562Sgshapiro return 0; 36564562Sgshapiro } 36664562Sgshapiro if (tTd(44, 4)) 36790792Sgshapiro sm_dprintf("\tEACCES\n"); 36864562Sgshapiro return EACCES; 36964562Sgshapiro} 37090792Sgshapiro/* 37164562Sgshapiro** SAFEDIRPATH -- check to make sure a path to a directory is safe 37264562Sgshapiro** 37364562Sgshapiro** Safe means not writable and owned by the right folks. 37464562Sgshapiro** 37564562Sgshapiro** Parameters: 37664562Sgshapiro** fn -- filename to check. 37764562Sgshapiro** uid -- user id to compare against. 37864562Sgshapiro** gid -- group id to compare against. 37964562Sgshapiro** user -- user name to compare against (used for group 38064562Sgshapiro** sets). 38164562Sgshapiro** flags -- modifiers: 38264562Sgshapiro** SFF_ROOTOK -- ok to use root permissions to open. 38364562Sgshapiro** SFF_SAFEDIRPATH -- writable directories are considered 38464562Sgshapiro** to be fatal errors. 38564562Sgshapiro** level -- symlink recursive level. 38664562Sgshapiro** offset -- offset into fn to start checking from. 38764562Sgshapiro** 38864562Sgshapiro** Returns: 38964562Sgshapiro** 0 -- if the directory path is "safe". 39064562Sgshapiro** else -- an error number associated with the path. 39164562Sgshapiro*/ 39264562Sgshapiro 39364562Sgshapiroint 39464562Sgshapirosafedirpath(fn, uid, gid, user, flags, level, offset) 39564562Sgshapiro char *fn; 39664562Sgshapiro UID_T uid; 39764562Sgshapiro GID_T gid; 39864562Sgshapiro char *user; 39964562Sgshapiro long flags; 40064562Sgshapiro int level; 40164562Sgshapiro int offset; 40264562Sgshapiro{ 40364562Sgshapiro int ret = 0; 40464562Sgshapiro int mode = S_IWOTH; 40564562Sgshapiro char save = '\0'; 40664562Sgshapiro char *saveptr = NULL; 40764562Sgshapiro char *p, *enddir; 40864562Sgshapiro register struct group *gr = NULL; 40964562Sgshapiro char s[MAXLINKPATHLEN + 1]; 41064562Sgshapiro struct stat stbuf; 41164562Sgshapiro 41264562Sgshapiro /* make sure we aren't in a symlink loop */ 41364562Sgshapiro if (level > MAXSYMLINKS) 41464562Sgshapiro return ELOOP; 41564562Sgshapiro 41690792Sgshapiro if (level < 0 || offset < 0 || offset > strlen(fn)) 41790792Sgshapiro return EINVAL; 41890792Sgshapiro 41964562Sgshapiro /* special case root directory */ 42064562Sgshapiro if (*fn == '\0') 42164562Sgshapiro fn = "/"; 42264562Sgshapiro 42364562Sgshapiro if (tTd(44, 4)) 42490792Sgshapiro sm_dprintf("safedirpath(%s, uid=%ld, gid=%ld, flags=%lx, level=%d, offset=%d):\n", 42564562Sgshapiro fn, (long) uid, (long) gid, flags, level, offset); 42664562Sgshapiro 42764562Sgshapiro if (!bitnset(DBS_GROUPWRITABLEDIRPATHSAFE, DontBlameSendmail)) 42864562Sgshapiro mode |= S_IWGRP; 42964562Sgshapiro 43064562Sgshapiro /* Make a modifiable copy of the filename */ 43190792Sgshapiro if (sm_strlcpy(s, fn, sizeof s) >= sizeof s) 43264562Sgshapiro return EINVAL; 43364562Sgshapiro 43464562Sgshapiro p = s + offset; 43564562Sgshapiro while (p != NULL) 43664562Sgshapiro { 43764562Sgshapiro /* put back character */ 43864562Sgshapiro if (saveptr != NULL) 43964562Sgshapiro { 44064562Sgshapiro *saveptr = save; 44164562Sgshapiro saveptr = NULL; 44264562Sgshapiro p++; 44364562Sgshapiro } 44464562Sgshapiro 44564562Sgshapiro if (*p == '\0') 44664562Sgshapiro break; 44764562Sgshapiro 44864562Sgshapiro p = strchr(p, '/'); 44964562Sgshapiro 45064562Sgshapiro /* Special case for root directory */ 45164562Sgshapiro if (p == s) 45264562Sgshapiro { 45364562Sgshapiro save = *(p + 1); 45464562Sgshapiro saveptr = p + 1; 45564562Sgshapiro *(p + 1) = '\0'; 45664562Sgshapiro } 45764562Sgshapiro else if (p != NULL) 45864562Sgshapiro { 45964562Sgshapiro save = *p; 46064562Sgshapiro saveptr = p; 46164562Sgshapiro *p = '\0'; 46264562Sgshapiro } 46364562Sgshapiro 46464562Sgshapiro /* Heuristic: . and .. have already been checked */ 46564562Sgshapiro enddir = strrchr(s, '/'); 46664562Sgshapiro if (enddir != NULL && 46764562Sgshapiro (strcmp(enddir, "/..") == 0 || 46864562Sgshapiro strcmp(enddir, "/.") == 0)) 46964562Sgshapiro continue; 47064562Sgshapiro 47164562Sgshapiro if (tTd(44, 20)) 47290792Sgshapiro sm_dprintf("\t[dir %s]\n", s); 47364562Sgshapiro 47464562Sgshapiro# if HASLSTAT 47564562Sgshapiro ret = lstat(s, &stbuf); 47664562Sgshapiro# else /* HASLSTAT */ 47764562Sgshapiro ret = stat(s, &stbuf); 47864562Sgshapiro# endif /* HASLSTAT */ 47964562Sgshapiro if (ret < 0) 48064562Sgshapiro { 48164562Sgshapiro ret = errno; 48264562Sgshapiro break; 48364562Sgshapiro } 48464562Sgshapiro 48564562Sgshapiro# ifdef S_ISLNK 48664562Sgshapiro /* Follow symlinks */ 48764562Sgshapiro if (S_ISLNK(stbuf.st_mode)) 48864562Sgshapiro { 48964562Sgshapiro char *target; 49064562Sgshapiro char buf[MAXPATHLEN + 1]; 49164562Sgshapiro 49264562Sgshapiro memset(buf, '\0', sizeof buf); 49364562Sgshapiro if (readlink(s, buf, sizeof buf) < 0) 49464562Sgshapiro { 49564562Sgshapiro ret = errno; 49664562Sgshapiro break; 49764562Sgshapiro } 49864562Sgshapiro 49964562Sgshapiro offset = 0; 50064562Sgshapiro if (*buf == '/') 50164562Sgshapiro { 50264562Sgshapiro target = buf; 50364562Sgshapiro 50464562Sgshapiro /* If path is the same, avoid rechecks */ 50564562Sgshapiro while (s[offset] == buf[offset] && 50664562Sgshapiro s[offset] != '\0') 50764562Sgshapiro offset++; 50864562Sgshapiro 50964562Sgshapiro if (s[offset] == '\0' && buf[offset] == '\0') 51064562Sgshapiro { 51164562Sgshapiro /* strings match, symlink loop */ 51264562Sgshapiro return ELOOP; 51364562Sgshapiro } 51464562Sgshapiro 51564562Sgshapiro /* back off from the mismatch */ 51664562Sgshapiro if (offset > 0) 51764562Sgshapiro offset--; 51864562Sgshapiro 51964562Sgshapiro /* Make sure we are at a directory break */ 52064562Sgshapiro if (offset > 0 && 52164562Sgshapiro s[offset] != '/' && 52264562Sgshapiro s[offset] != '\0') 52364562Sgshapiro { 52464562Sgshapiro while (buf[offset] != '/' && 52564562Sgshapiro offset > 0) 52664562Sgshapiro offset--; 52764562Sgshapiro } 52864562Sgshapiro if (offset > 0 && 52964562Sgshapiro s[offset] == '/' && 53064562Sgshapiro buf[offset] == '/') 53164562Sgshapiro { 53264562Sgshapiro /* Include the trailing slash */ 53364562Sgshapiro offset++; 53464562Sgshapiro } 53564562Sgshapiro } 53664562Sgshapiro else 53764562Sgshapiro { 53864562Sgshapiro char *sptr; 53964562Sgshapiro char fullbuf[MAXLINKPATHLEN + 1]; 54064562Sgshapiro 54164562Sgshapiro sptr = strrchr(s, '/'); 54264562Sgshapiro if (sptr != NULL) 54364562Sgshapiro { 54464562Sgshapiro *sptr = '\0'; 54564562Sgshapiro offset = sptr + 1 - s; 54690792Sgshapiro if (sm_strlcpyn(fullbuf, 54790792Sgshapiro sizeof fullbuf, 2, 54890792Sgshapiro s, "/") >= 54990792Sgshapiro sizeof fullbuf || 55090792Sgshapiro sm_strlcat(fullbuf, buf, 55190792Sgshapiro sizeof fullbuf) >= 55290792Sgshapiro sizeof fullbuf) 55364562Sgshapiro { 55464562Sgshapiro ret = EINVAL; 55564562Sgshapiro break; 55664562Sgshapiro } 55764562Sgshapiro *sptr = '/'; 55864562Sgshapiro } 55964562Sgshapiro else 56064562Sgshapiro { 56190792Sgshapiro if (sm_strlcpy(fullbuf, buf, 56290792Sgshapiro sizeof fullbuf) >= 56390792Sgshapiro sizeof fullbuf) 56464562Sgshapiro { 56564562Sgshapiro ret = EINVAL; 56664562Sgshapiro break; 56764562Sgshapiro } 56864562Sgshapiro } 56964562Sgshapiro target = fullbuf; 57064562Sgshapiro } 57164562Sgshapiro ret = safedirpath(target, uid, gid, user, flags, 57264562Sgshapiro level + 1, offset); 57364562Sgshapiro if (ret != 0) 57464562Sgshapiro break; 57564562Sgshapiro 57664562Sgshapiro /* Don't check permissions on the link file itself */ 57764562Sgshapiro continue; 57864562Sgshapiro } 57964562Sgshapiro#endif /* S_ISLNK */ 58064562Sgshapiro 58164562Sgshapiro if ((uid == 0 || bitset(SFF_SAFEDIRPATH, flags)) && 58264562Sgshapiro#ifdef S_ISVTX 58364562Sgshapiro !(bitnset(DBS_TRUSTSTICKYBIT, DontBlameSendmail) && 58464562Sgshapiro bitset(S_ISVTX, stbuf.st_mode)) && 58564562Sgshapiro#endif /* S_ISVTX */ 58664562Sgshapiro bitset(mode, stbuf.st_mode)) 58764562Sgshapiro { 58864562Sgshapiro if (tTd(44, 4)) 58990792Sgshapiro sm_dprintf("\t[dir %s] mode %lo ", 59090792Sgshapiro s, (unsigned long) stbuf.st_mode); 59164562Sgshapiro if (bitset(SFF_SAFEDIRPATH, flags)) 59264562Sgshapiro { 59364562Sgshapiro if (bitset(S_IWOTH, stbuf.st_mode)) 59464562Sgshapiro ret = E_SM_WWDIR; 59564562Sgshapiro else 59664562Sgshapiro ret = E_SM_GWDIR; 59764562Sgshapiro if (tTd(44, 4)) 59890792Sgshapiro sm_dprintf("FATAL\n"); 59964562Sgshapiro break; 60064562Sgshapiro } 60164562Sgshapiro if (tTd(44, 4)) 60290792Sgshapiro sm_dprintf("WARNING\n"); 60364562Sgshapiro if (Verbose > 1) 60464562Sgshapiro message("051 WARNING: %s writable directory %s", 60564562Sgshapiro bitset(S_IWOTH, stbuf.st_mode) 60664562Sgshapiro ? "World" 60764562Sgshapiro : "Group", 60864562Sgshapiro s); 60964562Sgshapiro } 61064562Sgshapiro if (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags)) 61164562Sgshapiro { 61264562Sgshapiro if (bitset(S_IXOTH, stbuf.st_mode)) 61364562Sgshapiro continue; 61464562Sgshapiro ret = EACCES; 61564562Sgshapiro break; 61664562Sgshapiro } 61764562Sgshapiro 61864562Sgshapiro /* 61964562Sgshapiro ** Let OS determine access to file if we are not 62064562Sgshapiro ** running as a privileged user. This allows ACLs 62164562Sgshapiro ** to work. Also, if opening as root, assume we can 62264562Sgshapiro ** scan the directory. 62364562Sgshapiro */ 62464562Sgshapiro if (geteuid() != 0 || bitset(SFF_OPENASROOT, flags)) 62564562Sgshapiro continue; 62664562Sgshapiro 62764562Sgshapiro if (stbuf.st_uid == uid && 62864562Sgshapiro bitset(S_IXUSR, stbuf.st_mode)) 62964562Sgshapiro continue; 63064562Sgshapiro if (stbuf.st_gid == gid && 63164562Sgshapiro bitset(S_IXGRP, stbuf.st_mode)) 63264562Sgshapiro continue; 63364562Sgshapiro# ifndef NO_GROUP_SET 63464562Sgshapiro if (user != NULL && !DontInitGroups && 63564562Sgshapiro ((gr != NULL && gr->gr_gid == stbuf.st_gid) || 63664562Sgshapiro (gr = getgrgid(stbuf.st_gid)) != NULL)) 63764562Sgshapiro { 63864562Sgshapiro register char **gp; 63964562Sgshapiro 64064562Sgshapiro for (gp = gr->gr_mem; gp != NULL && *gp != NULL; gp++) 64164562Sgshapiro if (strcmp(*gp, user) == 0) 64264562Sgshapiro break; 64364562Sgshapiro if (gp != NULL && *gp != NULL && 64464562Sgshapiro bitset(S_IXGRP, stbuf.st_mode)) 64564562Sgshapiro continue; 64664562Sgshapiro } 64764562Sgshapiro# endif /* ! NO_GROUP_SET */ 64864562Sgshapiro if (!bitset(S_IXOTH, stbuf.st_mode)) 64964562Sgshapiro { 65064562Sgshapiro ret = EACCES; 65164562Sgshapiro break; 65264562Sgshapiro } 65364562Sgshapiro } 65464562Sgshapiro if (tTd(44, 4)) 65590792Sgshapiro sm_dprintf("\t[dir %s] %s\n", fn, 65690792Sgshapiro ret == 0 ? "OK" : sm_errstring(ret)); 65764562Sgshapiro return ret; 65864562Sgshapiro} 65990792Sgshapiro/* 66064562Sgshapiro** SAFEOPEN -- do a file open with extra checking 66164562Sgshapiro** 66264562Sgshapiro** Parameters: 66364562Sgshapiro** fn -- the file name to open. 66464562Sgshapiro** omode -- the open-style mode flags. 66564562Sgshapiro** cmode -- the create-style mode flags. 66664562Sgshapiro** sff -- safefile flags. 66764562Sgshapiro** 66864562Sgshapiro** Returns: 66964562Sgshapiro** Same as open. 67064562Sgshapiro*/ 67164562Sgshapiro 67264562Sgshapiroint 67364562Sgshapirosafeopen(fn, omode, cmode, sff) 67464562Sgshapiro char *fn; 67564562Sgshapiro int omode; 67664562Sgshapiro int cmode; 67764562Sgshapiro long sff; 67864562Sgshapiro{ 67964562Sgshapiro int rval; 68064562Sgshapiro int fd; 68164562Sgshapiro int smode; 68264562Sgshapiro struct stat stb; 68364562Sgshapiro 68464562Sgshapiro if (tTd(44, 10)) 68590792Sgshapiro sm_dprintf("safeopen: fn=%s, omode=%x, cmode=%x, sff=%lx\n", 68690792Sgshapiro fn, omode, cmode, sff); 68764562Sgshapiro 68864562Sgshapiro if (bitset(O_CREAT, omode)) 68964562Sgshapiro sff |= SFF_CREAT; 69064562Sgshapiro omode &= ~O_CREAT; 69164562Sgshapiro smode = 0; 69264562Sgshapiro switch (omode & O_ACCMODE) 69364562Sgshapiro { 69464562Sgshapiro case O_RDONLY: 69564562Sgshapiro smode = S_IREAD; 69664562Sgshapiro break; 69764562Sgshapiro 69864562Sgshapiro case O_WRONLY: 69964562Sgshapiro smode = S_IWRITE; 70064562Sgshapiro break; 70164562Sgshapiro 70264562Sgshapiro case O_RDWR: 70364562Sgshapiro smode = S_IREAD|S_IWRITE; 70464562Sgshapiro break; 70564562Sgshapiro 70664562Sgshapiro default: 70764562Sgshapiro smode = 0; 70864562Sgshapiro break; 70964562Sgshapiro } 71064562Sgshapiro if (bitset(SFF_OPENASROOT, sff)) 71164562Sgshapiro rval = safefile(fn, RunAsUid, RunAsGid, RunAsUserName, 71264562Sgshapiro sff, smode, &stb); 71364562Sgshapiro else 71464562Sgshapiro rval = safefile(fn, RealUid, RealGid, RealUserName, 71564562Sgshapiro sff, smode, &stb); 71664562Sgshapiro if (rval != 0) 71764562Sgshapiro { 71864562Sgshapiro errno = rval; 71964562Sgshapiro return -1; 72064562Sgshapiro } 72164562Sgshapiro if (stb.st_mode == ST_MODE_NOFILE && bitset(SFF_CREAT, sff)) 72264562Sgshapiro omode |= O_CREAT | (bitset(SFF_NOTEXCL, sff) ? 0 : O_EXCL); 72364562Sgshapiro else if (bitset(SFF_CREAT, sff) && bitset(O_EXCL, omode)) 72464562Sgshapiro { 72564562Sgshapiro /* The file exists so an exclusive create would fail */ 72664562Sgshapiro errno = EEXIST; 72764562Sgshapiro return -1; 72864562Sgshapiro } 72964562Sgshapiro 73064562Sgshapiro fd = dfopen(fn, omode, cmode, sff); 73164562Sgshapiro if (fd < 0) 73264562Sgshapiro return fd; 73364562Sgshapiro if (filechanged(fn, fd, &stb)) 73464562Sgshapiro { 73564562Sgshapiro syserr("554 5.3.0 cannot open: file %s changed after open", fn); 73664562Sgshapiro (void) close(fd); 73764562Sgshapiro errno = E_SM_FILECHANGE; 73864562Sgshapiro return -1; 73964562Sgshapiro } 74064562Sgshapiro return fd; 74164562Sgshapiro} 74290792Sgshapiro/* 74390792Sgshapiro** SAFEFOPEN -- do a file open with extra checking 74490792Sgshapiro** 74590792Sgshapiro** Parameters: 74690792Sgshapiro** fn -- the file name to open. 74790792Sgshapiro** omode -- the open-style mode flags. 74890792Sgshapiro** cmode -- the create-style mode flags. 74990792Sgshapiro** sff -- safefile flags. 75090792Sgshapiro** 75190792Sgshapiro** Returns: 75290792Sgshapiro** Same as fopen. 75390792Sgshapiro*/ 75490792Sgshapiro 75590792SgshapiroSM_FILE_T * 75690792Sgshapirosafefopen(fn, omode, cmode, sff) 75790792Sgshapiro char *fn; 75890792Sgshapiro int omode; 75990792Sgshapiro int cmode; 76090792Sgshapiro long sff; 76190792Sgshapiro{ 76290792Sgshapiro int fd; 76390792Sgshapiro int save_errno; 76490792Sgshapiro SM_FILE_T *fp; 76590792Sgshapiro int fmode; 76690792Sgshapiro 76790792Sgshapiro switch (omode & O_ACCMODE) 76890792Sgshapiro { 76990792Sgshapiro case O_RDONLY: 77090792Sgshapiro fmode = SM_IO_RDONLY; 77190792Sgshapiro break; 77290792Sgshapiro 77390792Sgshapiro case O_WRONLY: 77490792Sgshapiro if (bitset(O_APPEND, omode)) 77590792Sgshapiro fmode = SM_IO_APPEND; 77690792Sgshapiro else 77790792Sgshapiro fmode = SM_IO_WRONLY; 77890792Sgshapiro break; 77990792Sgshapiro 78090792Sgshapiro case O_RDWR: 78190792Sgshapiro if (bitset(O_TRUNC, omode)) 78290792Sgshapiro fmode = SM_IO_RDWRTR; 78390792Sgshapiro else if (bitset(O_APPEND, omode)) 78490792Sgshapiro fmode = SM_IO_APPENDRW; 78590792Sgshapiro else 78690792Sgshapiro fmode = SM_IO_RDWR; 78790792Sgshapiro break; 78890792Sgshapiro 78990792Sgshapiro default: 79090792Sgshapiro syserr("554 5.3.5 safefopen: unknown omode %o", omode); 79190792Sgshapiro fmode = 0; 79290792Sgshapiro } 79390792Sgshapiro fd = safeopen(fn, omode, cmode, sff); 79490792Sgshapiro if (fd < 0) 79590792Sgshapiro { 79690792Sgshapiro save_errno = errno; 79790792Sgshapiro if (tTd(44, 10)) 79890792Sgshapiro sm_dprintf("safefopen: safeopen failed: %s\n", 79990792Sgshapiro sm_errstring(errno)); 80090792Sgshapiro errno = save_errno; 80190792Sgshapiro return NULL; 80290792Sgshapiro } 80390792Sgshapiro fp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT, 80490792Sgshapiro (void *) &fd, fmode, NULL); 80590792Sgshapiro if (fp != NULL) 80690792Sgshapiro return fp; 80790792Sgshapiro 80890792Sgshapiro save_errno = errno; 80990792Sgshapiro if (tTd(44, 10)) 81090792Sgshapiro { 81190792Sgshapiro sm_dprintf("safefopen: fdopen(%s, %d) failed: omode=%x, sff=%lx, err=%s\n", 81290792Sgshapiro fn, fmode, omode, sff, sm_errstring(errno)); 81390792Sgshapiro } 81490792Sgshapiro (void) close(fd); 81590792Sgshapiro errno = save_errno; 81690792Sgshapiro return NULL; 81790792Sgshapiro} 81890792Sgshapiro/* 81964562Sgshapiro** FILECHANGED -- check to see if file changed after being opened 82064562Sgshapiro** 82164562Sgshapiro** Parameters: 82264562Sgshapiro** fn -- pathname of file to check. 82364562Sgshapiro** fd -- file descriptor to check. 82464562Sgshapiro** stb -- stat structure from before open. 82564562Sgshapiro** 82664562Sgshapiro** Returns: 82790792Sgshapiro** true -- if a problem was detected. 82890792Sgshapiro** false -- if this file is still the same. 82964562Sgshapiro*/ 83064562Sgshapiro 83164562Sgshapirobool 83264562Sgshapirofilechanged(fn, fd, stb) 83364562Sgshapiro char *fn; 83464562Sgshapiro int fd; 83564562Sgshapiro struct stat *stb; 83664562Sgshapiro{ 83764562Sgshapiro struct stat sta; 83864562Sgshapiro 83964562Sgshapiro if (stb->st_mode == ST_MODE_NOFILE) 84064562Sgshapiro { 84164562Sgshapiro# if HASLSTAT && BOGUS_O_EXCL 84264562Sgshapiro /* only necessary if exclusive open follows symbolic links */ 84364562Sgshapiro if (lstat(fn, stb) < 0 || stb->st_nlink != 1) 84490792Sgshapiro return true; 84564562Sgshapiro# else /* HASLSTAT && BOGUS_O_EXCL */ 84690792Sgshapiro return false; 84764562Sgshapiro# endif /* HASLSTAT && BOGUS_O_EXCL */ 84864562Sgshapiro } 84964562Sgshapiro if (fstat(fd, &sta) < 0) 85090792Sgshapiro return true; 85164562Sgshapiro 85264562Sgshapiro if (sta.st_nlink != stb->st_nlink || 85364562Sgshapiro sta.st_dev != stb->st_dev || 85464562Sgshapiro sta.st_ino != stb->st_ino || 85564562Sgshapiro# if HAS_ST_GEN && 0 /* AFS returns garbage in st_gen */ 85664562Sgshapiro sta.st_gen != stb->st_gen || 85764562Sgshapiro# endif /* HAS_ST_GEN && 0 */ 85864562Sgshapiro sta.st_uid != stb->st_uid || 85964562Sgshapiro sta.st_gid != stb->st_gid) 86064562Sgshapiro { 86164562Sgshapiro if (tTd(44, 8)) 86264562Sgshapiro { 86390792Sgshapiro sm_dprintf("File changed after opening:\n"); 86490792Sgshapiro sm_dprintf(" nlink = %ld/%ld\n", 86564562Sgshapiro (long) stb->st_nlink, (long) sta.st_nlink); 86690792Sgshapiro sm_dprintf(" dev = %ld/%ld\n", 86764562Sgshapiro (long) stb->st_dev, (long) sta.st_dev); 86890792Sgshapiro sm_dprintf(" ino = %llu/%llu\n", 86990792Sgshapiro (ULONGLONG_T) stb->st_ino, 87090792Sgshapiro (ULONGLONG_T) sta.st_ino); 87164562Sgshapiro# if HAS_ST_GEN 87290792Sgshapiro sm_dprintf(" gen = %ld/%ld\n", 87364562Sgshapiro (long) stb->st_gen, (long) sta.st_gen); 87464562Sgshapiro# endif /* HAS_ST_GEN */ 87590792Sgshapiro sm_dprintf(" uid = %ld/%ld\n", 87664562Sgshapiro (long) stb->st_uid, (long) sta.st_uid); 87790792Sgshapiro sm_dprintf(" gid = %ld/%ld\n", 87864562Sgshapiro (long) stb->st_gid, (long) sta.st_gid); 87964562Sgshapiro } 88090792Sgshapiro return true; 88164562Sgshapiro } 88264562Sgshapiro 88390792Sgshapiro return false; 88464562Sgshapiro} 88590792Sgshapiro/* 88664562Sgshapiro** DFOPEN -- determined file open 88764562Sgshapiro** 88864562Sgshapiro** This routine has the semantics of open, except that it will 88964562Sgshapiro** keep trying a few times to make this happen. The idea is that 89064562Sgshapiro** on very loaded systems, we may run out of resources (inodes, 89164562Sgshapiro** whatever), so this tries to get around it. 89264562Sgshapiro*/ 89364562Sgshapiro 89464562Sgshapiroint 89564562Sgshapirodfopen(filename, omode, cmode, sff) 89664562Sgshapiro char *filename; 89764562Sgshapiro int omode; 89864562Sgshapiro int cmode; 89964562Sgshapiro long sff; 90064562Sgshapiro{ 90164562Sgshapiro register int tries; 90264562Sgshapiro int fd = -1; 90364562Sgshapiro struct stat st; 90464562Sgshapiro 90564562Sgshapiro for (tries = 0; tries < 10; tries++) 90664562Sgshapiro { 90764562Sgshapiro (void) sleep((unsigned) (10 * tries)); 90864562Sgshapiro errno = 0; 90964562Sgshapiro fd = open(filename, omode, cmode); 91064562Sgshapiro if (fd >= 0) 91164562Sgshapiro break; 91264562Sgshapiro switch (errno) 91364562Sgshapiro { 91464562Sgshapiro case ENFILE: /* system file table full */ 91564562Sgshapiro case EINTR: /* interrupted syscall */ 91664562Sgshapiro#ifdef ETXTBSY 91764562Sgshapiro case ETXTBSY: /* Apollo: net file locked */ 91864562Sgshapiro#endif /* ETXTBSY */ 91964562Sgshapiro continue; 92064562Sgshapiro } 92164562Sgshapiro break; 92264562Sgshapiro } 92364562Sgshapiro if (!bitset(SFF_NOLOCK, sff) && 92464562Sgshapiro fd >= 0 && 92564562Sgshapiro fstat(fd, &st) >= 0 && 92664562Sgshapiro S_ISREG(st.st_mode)) 92764562Sgshapiro { 92864562Sgshapiro int locktype; 92964562Sgshapiro 93064562Sgshapiro /* lock the file to avoid accidental conflicts */ 93164562Sgshapiro if ((omode & O_ACCMODE) != O_RDONLY) 93264562Sgshapiro locktype = LOCK_EX; 93364562Sgshapiro else 93464562Sgshapiro locktype = LOCK_SH; 93564562Sgshapiro if (!lockfile(fd, filename, NULL, locktype)) 93664562Sgshapiro { 93764562Sgshapiro int save_errno = errno; 93864562Sgshapiro 93964562Sgshapiro (void) close(fd); 94064562Sgshapiro fd = -1; 94164562Sgshapiro errno = save_errno; 94264562Sgshapiro } 94364562Sgshapiro else 94464562Sgshapiro errno = 0; 94564562Sgshapiro } 94664562Sgshapiro return fd; 94764562Sgshapiro} 948