/*
* Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers.
* All rights reserved.
* Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
* Copyright (c) 1988, 1993
* The Regents of the University of California. All rights reserved.
*
* By using this file, you agree to the terms and conditions set
* forth in the LICENSE file which can be found at the top level of
* the sendmail distribution.
*
*/
#include <sendmail.h>
#include <sm/errstring.h>
SM_RCSID("@(#)$Id: safefile.c,v 8.129 2008/08/04 18:07:04 gshapiro Exp $")
/*
** SAFEFILE -- return 0 if a file exists and is safe for a user.
**
** Parameters:
** fn -- filename to check.
** uid -- user id to compare against.
** gid -- group id to compare against.
** user -- user name to compare against (used for group
** sets).
** flags -- modifiers:
** SFF_MUSTOWN -- "uid" must own this file.
** SFF_NOSLINK -- file cannot be a symbolic link.
** mode -- mode bits that must match.
** st -- if set, points to a stat structure that will
** get the stat info for the file.
**
** Returns:
** 0 if fn exists, is owned by uid, and matches mode.
** An errno otherwise. The actual errno is cleared.
**
** Side Effects:
** none.
*/
int
char *fn;
char *user;
long flags;
int mode;
{
register char *p;
int file_errno = 0;
bool checkpath;
sm_dprintf("safefile(%s, uid=%d, gid=%d, flags=%lx, mode=%o):\n",
errno = 0;
{
sm_dprintf("\tpathname too long\n");
return ENAMETOOLONG;
}
/* ignore SFF_SAFEDIRPATH if we are debugging */
flags &= ~SFF_SAFEDIRPATH;
/* first check to see if the file exists at all */
# if HASLSTAT
# else /* HASLSTAT */
# endif /* HASLSTAT */
{
file_errno = errno;
}
{
/*
** If final file is set-user-ID, run as the owner of that
** file. Gotta be careful not to reveal anything too
** soon here!
*/
# ifdef SUID_ROOT_FILES_OK
# else /* SUID_ROOT_FILES_OK */
# endif /* SUID_ROOT_FILES_OK */
{
}
# ifdef SUID_ROOT_FILES_OK
# else /* SUID_ROOT_FILES_OK */
# endif /* SUID_ROOT_FILES_OK */
}
{
int ret;
/* check the directory */
if (p == NULL)
{
flags|SFF_SAFEDIRPATH, 0, 0);
}
else
{
*p = '\0';
flags|SFF_SAFEDIRPATH, 0, 0);
*p = '/';
}
if (ret == 0)
{
/* directory is safe */
checkpath = false;
}
else
{
# if HASLSTAT
/* Need lstat() information if called stat() before */
{
return ret;
}
# endif /* HASLSTAT */
/* directory is writable: disallow links */
flags |= SFF_NOLINK;
}
}
if (checkpath)
{
int ret;
if (p == NULL)
{
}
else
{
*p = '\0';
*p = '/';
}
if (ret != 0)
return ret;
}
/*
** If the target file doesn't exist, check the directory to
** ensure that it is writable by this user.
*/
if (file_errno != 0)
{
errno = 0;
return ret;
/* check to see if legal to create the file */
if (p == NULL)
dir = ".";
else if (p == dir)
dir = "/";
else
*p = '\0';
{
ret = 0;
/* EMPTY */
;
/* EMPTY */
;
else
{
md >>= 3;
/* EMPTY */
;
# ifndef NO_GROUP_SET
{
register char **gp;
break;
md >>= 3;
}
# endif /* ! NO_GROUP_SET */
else
md >>= 3;
}
}
else
sm_dprintf("\t[final dir %s uid %d mode %lo] %s\n",
sm_errstring(ret));
if (p != NULL)
*p = '/';
return ret;
}
# ifdef S_ISLNK
{
sm_dprintf("\t[slink mode %lo]\tE_SM_NOSLINK\n",
return E_SM_NOSLINK;
}
# endif /* S_ISLNK */
{
sm_dprintf("\t[non-reg mode %lo]\tE_SM_REGONLY\n",
return E_SM_REGONLY;
}
{
sm_dprintf("\t[write bits %lo]\tE_SM_GWFILE\n",
return E_SM_GWFILE;
}
{
sm_dprintf("\t[write bits %lo]\tE_SM_WWFILE\n",
return E_SM_WWFILE;
}
{
sm_dprintf("\t[read bits %lo]\tE_SM_GRFILE\n",
return E_SM_GRFILE;
}
{
sm_dprintf("\t[read bits %lo]\tE_SM_WRFILE\n",
return E_SM_WRFILE;
}
{
sm_dprintf("\t[exec bits %lo]\tE_SM_ISEXEC\n",
return E_SM_ISEXEC;
}
{
sm_dprintf("\t[link count %d]\tE_SM_NOHLINK\n",
return E_SM_NOHLINK;
}
/* EMPTY */
;
mode >>= 6;
/* EMPTY */
;
/* EMPTY */
;
else
{
mode >>= 3;
/* EMPTY */
;
# ifndef NO_GROUP_SET
{
register char **gp;
break;
mode >>= 3;
}
# endif /* ! NO_GROUP_SET */
else
mode >>= 3;
}
sm_dprintf("\t[uid %d, nlink %d, stat %lo, mode %lo] ",
{
sm_dprintf("\tOK\n");
return 0;
}
sm_dprintf("\tEACCES\n");
return EACCES;
}
/*
** SAFEDIRPATH -- check to make sure a path to a directory is safe
**
** Safe means not writable and owned by the right folks.
**
** Parameters:
** fn -- filename to check.
** uid -- user id to compare against.
** gid -- group id to compare against.
** user -- user name to compare against (used for group
** sets).
** flags -- modifiers:
** SFF_ROOTOK -- ok to use root permissions to open.
** SFF_SAFEDIRPATH -- writable directories are considered
** to be fatal errors.
** level -- symlink recursive level.
** offset -- offset into fn to start checking from.
**
** Returns:
** 0 -- if the directory path is "safe".
** else -- an error number associated with the path.
*/
int
char *fn;
char *user;
long flags;
int level;
int offset;
{
int ret = 0;
char *p, *enddir;
char s[MAXLINKPATHLEN];
/* make sure we aren't in a symlink loop */
if (level > MAXSYMLINKS)
return ELOOP;
return EINVAL;
/* special case root directory */
if (*fn == '\0')
fn = "/";
sm_dprintf("safedirpath(%s, uid=%ld, gid=%ld, flags=%lx, level=%d, offset=%d):\n",
/* Make a modifiable copy of the filename */
if (sm_strlcpy(s, fn, sizeof s) >= sizeof s)
return EINVAL;
p = s + offset;
while (p != NULL)
{
/* put back character */
{
p++;
}
if (*p == '\0')
break;
p = strchr(p, '/');
/* Special case for root directory */
if (p == s)
{
save = *(p + 1);
saveptr = p + 1;
*(p + 1) = '\0';
}
else if (p != NULL)
{
save = *p;
saveptr = p;
*p = '\0';
}
/* Heuristic: . and .. have already been checked */
continue;
sm_dprintf("\t[dir %s]\n", s);
# if HASLSTAT
# else /* HASLSTAT */
# endif /* HASLSTAT */
if (ret < 0)
{
break;
}
# ifdef S_ISLNK
/* Follow symlinks */
{
int linklen;
char *target;
if (linklen < 0)
{
break;
}
{
/* file name too long for buffer */
break;
}
offset = 0;
if (*buf == '/')
{
/* If path is the same, avoid rechecks */
s[offset] != '\0')
offset++;
{
/* strings match, symlink loop */
return ELOOP;
}
/* back off from the mismatch */
if (offset > 0)
offset--;
/* Make sure we are at a directory break */
if (offset > 0 &&
s[offset] != '/' &&
s[offset] != '\0')
{
offset > 0)
offset--;
}
if (offset > 0 &&
s[offset] == '/' &&
{
/* Include the trailing slash */
offset++;
}
}
else
{
char *sptr;
{
*sptr = '\0';
if (sm_strlcpyn(fullbuf,
sizeof fullbuf, 2,
s, "/") >=
sizeof fullbuf ||
sizeof fullbuf) >=
sizeof fullbuf)
{
break;
}
*sptr = '/';
}
else
{
sizeof fullbuf) >=
sizeof fullbuf)
{
break;
}
}
}
if (ret != 0)
break;
/* Don't check permissions on the link file itself */
continue;
}
#endif /* S_ISLNK */
#ifdef S_ISVTX
#endif /* S_ISVTX */
{
sm_dprintf("\t[dir %s] mode %lo ",
{
ret = E_SM_WWDIR;
else
ret = E_SM_GWDIR;
sm_dprintf("FATAL\n");
break;
}
sm_dprintf("WARNING\n");
if (Verbose > 1)
message("051 WARNING: %s writable directory %s",
? "World"
: "Group",
s);
}
{
continue;
break;
}
/*
** Let OS determine access to file if we are not
** running as a privileged user. This allows ACLs
** to work. Also, if opening as root, assume we can
** scan the directory.
*/
continue;
continue;
continue;
# ifndef NO_GROUP_SET
{
register char **gp;
break;
continue;
}
# endif /* ! NO_GROUP_SET */
{
break;
}
}
return ret;
}
/*
** SAFEOPEN -- do a file open with extra checking
**
** Parameters:
** fn -- the file name to open.
** omode -- the open-style mode flags.
** cmode -- the create-style mode flags.
** sff -- safefile flags.
**
** Returns:
** Same as open.
*/
int
char *fn;
int omode;
int cmode;
long sff;
{
#if !NOFTRUNCATE
bool truncate;
#endif /* !NOFTRUNCATE */
int rval;
int fd;
int smode;
sm_dprintf("safeopen: fn=%s, omode=%x, cmode=%x, sff=%lx\n",
{
case O_RDONLY:
break;
case O_WRONLY:
break;
case O_RDWR:
break;
default:
smode = 0;
break;
}
else
if (rval != 0)
{
return -1;
}
{
/* The file exists so an exclusive create would fail */
return -1;
}
#if !NOFTRUNCATE
if (truncate)
#endif /* !NOFTRUNCATE */
if (fd < 0)
return fd;
{
return -1;
}
#if !NOFTRUNCATE
if (truncate &&
{
int save_errno;
save_errno = errno;
syserr("554 5.3.0 cannot open: file %s could not be truncated",
fn);
errno = save_errno;
return -1;
}
#endif /* !NOFTRUNCATE */
return fd;
}
/*
** SAFEFOPEN -- do a file open with extra checking
**
** Parameters:
** fn -- the file name to open.
** omode -- the open-style mode flags.
** cmode -- the create-style mode flags.
** sff -- safefile flags.
**
** Returns:
** Same as fopen.
*/
char *fn;
int omode;
int cmode;
long sff;
{
int fd;
int save_errno;
int fmode;
{
case O_RDONLY:
break;
case O_WRONLY:
else
break;
case O_RDWR:
else
fmode = SM_IO_RDWR;
break;
default:
fmode = 0;
}
if (fd < 0)
{
save_errno = errno;
sm_dprintf("safefopen: safeopen failed: %s\n",
errno = save_errno;
return NULL;
}
return fp;
save_errno = errno;
{
sm_dprintf("safefopen: fdopen(%s, %d) failed: omode=%x, sff=%lx, err=%s\n",
}
errno = save_errno;
return NULL;
}
/*
** FILECHANGED -- check to see if file changed after being opened
**
** Parameters:
** fn -- pathname of file to check.
** fd -- file descriptor to check.
** stb -- stat structure from before open.
**
** Returns:
** true -- if a problem was detected.
** false -- if this file is still the same.
*/
bool
char *fn;
int fd;
{
{
# if HASLSTAT && BOGUS_O_EXCL
/* only necessary if exclusive open follows symbolic links */
return true;
# else /* HASLSTAT && BOGUS_O_EXCL */
return false;
# endif /* HASLSTAT && BOGUS_O_EXCL */
}
return true;
# if HAS_ST_GEN && 0 /* AFS returns garbage in st_gen */
# endif /* HAS_ST_GEN && 0 */
{
{
sm_dprintf("File changed after opening:\n");
sm_dprintf(" nlink = %ld/%ld\n",
sm_dprintf(" dev = %ld/%ld\n",
sm_dprintf(" ino = %llu/%llu\n",
# if HAS_ST_GEN
sm_dprintf(" gen = %ld/%ld\n",
# endif /* HAS_ST_GEN */
sm_dprintf(" uid = %ld/%ld\n",
sm_dprintf(" gid = %ld/%ld\n",
}
return true;
}
return false;
}
/*
** DFOPEN -- determined file open
**
** This routine has the semantics of open, except that it will
** keep trying a few times to make this happen. The idea is that
** on very loaded systems, we may run out of resources (inodes,
** whatever), so this tries to get around it.
*/
int
char *filename;
int omode;
int cmode;
long sff;
{
register int tries;
{
errno = 0;
if (fd >= 0)
break;
switch (errno)
{
case ENFILE: /* system file table full */
case EINTR: /* interrupted syscall */
#ifdef ETXTBSY
case ETXTBSY: /* Apollo: net file locked */
#endif /* ETXTBSY */
continue;
}
break;
}
fd >= 0 &&
{
int locktype;
/* lock the file to avoid accidental conflicts */
else
{
fd = -1;
errno = save_errno;
}
else
errno = 0;
}
return fd;
}