📄 safefile.c
字号:
/*
* Copyright (c) 1998-2000 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.
*
*/
#ifndef lint
static char id[] = "@(#)$Id: safefile.c,v 8.81.4.7 2000/09/01 21:09:23 ca Exp $";
#endif /* ! lint */
#include <sendmail.h>
/*
** 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
safefile(fn, uid, gid, user, flags, mode, st)
char *fn;
UID_T uid;
GID_T gid;
char *user;
long flags;
int mode;
struct stat *st;
{
register char *p;
register struct group *gr = NULL;
int file_errno = 0;
bool checkpath;
struct stat stbuf;
struct stat fstbuf;
char fbuf[MAXPATHLEN + 1];
if (tTd(44, 4))
dprintf("safefile(%s, uid=%d, gid=%d, flags=%lx, mode=%o):\n",
fn, (int) uid, (int) gid, flags, mode);
errno = 0;
if (st == NULL)
st = &fstbuf;
if (strlcpy(fbuf, fn, sizeof fbuf) >= sizeof fbuf)
{
if (tTd(44, 4))
dprintf("\tpathname too long\n");
return ENAMETOOLONG;
}
fn = fbuf;
/* ignore SFF_SAFEDIRPATH if we are debugging */
if (RealUid != 0 && RunAsUid == RealUid)
flags &= ~SFF_SAFEDIRPATH;
/* first check to see if the file exists at all */
# if HASLSTAT
if ((bitset(SFF_NOSLINK, flags) ? lstat(fn, st)
: stat(fn, st)) < 0)
# else /* HASLSTAT */
if (stat(fn, st) < 0)
# endif /* HASLSTAT */
{
file_errno = errno;
}
else if (bitset(SFF_SETUIDOK, flags) &&
!bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode) &&
S_ISREG(st->st_mode))
{
/*
** If final file is setuid, run as the owner of that
** file. Gotta be careful not to reveal anything too
** soon here!
*/
# ifdef SUID_ROOT_FILES_OK
if (bitset(S_ISUID, st->st_mode))
# else /* SUID_ROOT_FILES_OK */
if (bitset(S_ISUID, st->st_mode) && st->st_uid != 0 &&
st->st_uid != TrustedUid)
# endif /* SUID_ROOT_FILES_OK */
{
uid = st->st_uid;
user = NULL;
}
# ifdef SUID_ROOT_FILES_OK
if (bitset(S_ISGID, st->st_mode))
# else /* SUID_ROOT_FILES_OK */
if (bitset(S_ISGID, st->st_mode) && st->st_gid != 0)
# endif /* SUID_ROOT_FILES_OK */
gid = st->st_gid;
}
checkpath = !bitset(SFF_NOPATHCHECK, flags) ||
(uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags));
if (bitset(SFF_NOWLINK, flags) && !bitset(SFF_SAFEDIRPATH, flags))
{
int ret;
/* check the directory */
p = strrchr(fn, '/');
if (p == NULL)
{
ret = safedirpath(".", uid, gid, user,
flags|SFF_SAFEDIRPATH, 0, 0);
}
else
{
*p = '\0';
ret = safedirpath(fn, uid, gid, user,
flags|SFF_SAFEDIRPATH, 0, 0);
*p = '/';
}
if (ret == 0)
{
/* directory is safe */
checkpath = FALSE;
}
else
{
# if HASLSTAT
/* Need lstat() information if called stat() before */
if (!bitset(SFF_NOSLINK, flags) && lstat(fn, st) < 0)
{
ret = errno;
if (tTd(44, 4))
dprintf("\t%s\n", errstring(ret));
return ret;
}
# endif /* HASLSTAT */
/* directory is writable: disallow links */
flags |= SFF_NOLINK;
}
}
if (checkpath)
{
int ret;
p = strrchr(fn, '/');
if (p == NULL)
{
ret = safedirpath(".", uid, gid, user, flags, 0, 0);
}
else
{
*p = '\0';
ret = safedirpath(fn, uid, gid, user, flags, 0, 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)
{
int ret = file_errno;
char *dir = fn;
if (tTd(44, 4))
dprintf("\t%s\n", errstring(ret));
errno = 0;
if (!bitset(SFF_CREAT, flags) || file_errno != ENOENT)
return ret;
/* check to see if legal to create the file */
p = strrchr(dir, '/');
if (p == NULL)
dir = ".";
else if (p == dir)
dir = "/";
else
*p = '\0';
if (stat(dir, &stbuf) >= 0)
{
int md = S_IWRITE|S_IEXEC;
if (stbuf.st_uid == uid)
/* EMPTY */
;
else if (uid == 0 && stbuf.st_uid == TrustedUid)
/* EMPTY */
;
else
{
md >>= 3;
if (stbuf.st_gid == gid)
/* EMPTY */
;
# ifndef NO_GROUP_SET
else if (user != NULL && !DontInitGroups &&
((gr != NULL &&
gr->gr_gid == stbuf.st_gid) ||
(gr = getgrgid(stbuf.st_gid)) != NULL))
{
register char **gp;
for (gp = gr->gr_mem; *gp != NULL; gp++)
if (strcmp(*gp, user) == 0)
break;
if (*gp == NULL)
md >>= 3;
}
# endif /* ! NO_GROUP_SET */
else
md >>= 3;
}
if ((stbuf.st_mode & md) != md)
errno = EACCES;
}
ret = errno;
if (tTd(44, 4))
dprintf("\t[final dir %s uid %d mode %lo] %s\n",
dir, (int) stbuf.st_uid, (u_long) stbuf.st_mode,
errstring(ret));
if (p != NULL)
*p = '/';
st->st_mode = ST_MODE_NOFILE;
return ret;
}
# ifdef S_ISLNK
if (bitset(SFF_NOSLINK, flags) && S_ISLNK(st->st_mode))
{
if (tTd(44, 4))
dprintf("\t[slink mode %lo]\tE_SM_NOSLINK\n",
(u_long) st->st_mode);
return E_SM_NOSLINK;
}
# endif /* S_ISLNK */
if (bitset(SFF_REGONLY, flags) && !S_ISREG(st->st_mode))
{
if (tTd(44, 4))
dprintf("\t[non-reg mode %lo]\tE_SM_REGONLY\n",
(u_long) st->st_mode);
return E_SM_REGONLY;
}
if (bitset(SFF_NOGWFILES, flags) &&
bitset(S_IWGRP, st->st_mode))
{
if (tTd(44, 4))
dprintf("\t[write bits %lo]\tE_SM_GWFILE\n",
(u_long) st->st_mode);
return E_SM_GWFILE;
}
if (bitset(SFF_NOWWFILES, flags) &&
bitset(S_IWOTH, st->st_mode))
{
if (tTd(44, 4))
dprintf("\t[write bits %lo]\tE_SM_WWFILE\n",
(u_long) st->st_mode);
return E_SM_WWFILE;
}
if (bitset(SFF_NOGRFILES, flags) && bitset(S_IRGRP, st->st_mode))
{
if (tTd(44, 4))
dprintf("\t[read bits %lo]\tE_SM_GRFILE\n",
(u_long) st->st_mode);
return E_SM_GRFILE;
}
if (bitset(SFF_NOWRFILES, flags) && bitset(S_IROTH, st->st_mode))
{
if (tTd(44, 4))
dprintf("\t[read bits %lo]\tE_SM_WRFILE\n",
(u_long) st->st_mode);
return E_SM_WRFILE;
}
if (!bitset(SFF_EXECOK, flags) &&
bitset(S_IWUSR|S_IWGRP|S_IWOTH, mode) &&
bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode))
{
if (tTd(44, 4))
dprintf("\t[exec bits %lo]\tE_SM_ISEXEC]\n",
(u_long) st->st_mode);
return E_SM_ISEXEC;
}
if (bitset(SFF_NOHLINK, flags) && st->st_nlink != 1)
{
if (tTd(44, 4))
dprintf("\t[link count %d]\tE_SM_NOHLINK\n",
(int) st->st_nlink);
return E_SM_NOHLINK;
}
if (uid == 0 && bitset(SFF_OPENASROOT, flags))
/* EMPTY */
;
else if (uid == 0 && !bitset(SFF_ROOTOK, flags))
mode >>= 6;
else if (st->st_uid == uid)
/* EMPTY */
;
else if (uid == 0 && st->st_uid == TrustedUid)
/* EMPTY */
;
else
{
mode >>= 3;
if (st->st_gid == gid)
/* EMPTY */
;
# ifndef NO_GROUP_SET
else if (user != NULL && !DontInitGroups &&
((gr != NULL && gr->gr_gid == st->st_gid) ||
(gr = getgrgid(st->st_gid)) != NULL))
{
register char **gp;
for (gp = gr->gr_mem; *gp != NULL; gp++)
if (strcmp(*gp, user) == 0)
break;
if (*gp == NULL)
mode >>= 3;
}
# endif /* ! NO_GROUP_SET */
else
mode >>= 3;
}
if (tTd(44, 4))
dprintf("\t[uid %d, nlink %d, stat %lo, mode %lo] ",
(int) st->st_uid, (int) st->st_nlink,
(u_long) st->st_mode, (u_long) mode);
if ((st->st_uid == uid || st->st_uid == 0 ||
st->st_uid == TrustedUid ||
!bitset(SFF_MUSTOWN, flags)) &&
(st->st_mode & mode) == mode)
{
if (tTd(44, 4))
dprintf("\tOK\n");
return 0;
}
if (tTd(44, 4))
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
safedirpath(fn, uid, gid, user, flags, level, offset)
char *fn;
UID_T uid;
GID_T gid;
char *user;
long flags;
int level;
int offset;
{
int ret = 0;
int mode = S_IWOTH;
char save = '\0';
char *saveptr = NULL;
char *p, *enddir;
register struct group *gr = NULL;
char s[MAXLINKPATHLEN + 1];
struct stat stbuf;
/* make sure we aren't in a symlink loop */
if (level > MAXSYMLINKS)
return ELOOP;
/* special case root directory */
if (*fn == '\0')
fn = "/";
if (tTd(44, 4))
dprintf("safedirpath(%s, uid=%ld, gid=%ld, flags=%lx, level=%d, offset=%d):\n",
fn, (long) uid, (long) gid, flags, level, offset);
if (!bitnset(DBS_GROUPWRITABLEDIRPATHSAFE, DontBlameSendmail))
mode |= S_IWGRP;
/* Make a modifiable copy of the filename */
if (strlcpy(s, fn, sizeof s) >= sizeof s)
return EINVAL;
p = s + offset;
while (p != NULL)
{
/* put back character */
if (saveptr != NULL)
{
*saveptr = save;
saveptr = NULL;
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 */
enddir = strrchr(s, '/');
if (enddir != NULL &&
(strcmp(enddir, "/..") == 0 ||
strcmp(enddir, "/.") == 0))
continue;
if (tTd(44, 20))
dprintf("\t[dir %s]\n", s);
# if HASLSTAT
ret = lstat(s, &stbuf);
# else /* HASLSTAT */
ret = stat(s, &stbuf);
# endif /* HASLSTAT */
if (ret < 0)
{
ret = errno;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -