xref: /freebsd/contrib/sendmail/libsmutil/safefile.c (revision 06f25ae9f1d6020a600a10f713046203d1a82570)
106f25ae9SGregory Neil Shapiro /*
206f25ae9SGregory Neil Shapiro  * Copyright (c) 1998-2000 Sendmail, Inc. and its suppliers.
306f25ae9SGregory Neil Shapiro  *	All rights reserved.
406f25ae9SGregory Neil Shapiro  * Copyright (c) 1983, 1995-1997 Eric P. Allman.  All rights reserved.
506f25ae9SGregory Neil Shapiro  * Copyright (c) 1988, 1993
606f25ae9SGregory Neil Shapiro  *	The Regents of the University of California.  All rights reserved.
706f25ae9SGregory Neil Shapiro  *
806f25ae9SGregory Neil Shapiro  * By using this file, you agree to the terms and conditions set
906f25ae9SGregory Neil Shapiro  * forth in the LICENSE file which can be found at the top level of
1006f25ae9SGregory Neil Shapiro  * the sendmail distribution.
1106f25ae9SGregory Neil Shapiro  *
1206f25ae9SGregory Neil Shapiro  */
1306f25ae9SGregory Neil Shapiro 
1406f25ae9SGregory Neil Shapiro #ifndef lint
1506f25ae9SGregory Neil Shapiro static char id[] = "@(#)$Id: safefile.c,v 8.81.4.5 2000/07/17 22:33:37 ca Exp $";
1606f25ae9SGregory Neil Shapiro #endif /* ! lint */
1706f25ae9SGregory Neil Shapiro 
1806f25ae9SGregory Neil Shapiro #include <sendmail.h>
1906f25ae9SGregory Neil Shapiro 
2006f25ae9SGregory Neil Shapiro 
2106f25ae9SGregory Neil Shapiro /*
2206f25ae9SGregory Neil Shapiro **  SAFEFILE -- return 0 if a file exists and is safe for a user.
2306f25ae9SGregory Neil Shapiro **
2406f25ae9SGregory Neil Shapiro **	Parameters:
2506f25ae9SGregory Neil Shapiro **		fn -- filename to check.
2606f25ae9SGregory Neil Shapiro **		uid -- user id to compare against.
2706f25ae9SGregory Neil Shapiro **		gid -- group id to compare against.
2806f25ae9SGregory Neil Shapiro **		user -- user name to compare against (used for group
2906f25ae9SGregory Neil Shapiro **			sets).
3006f25ae9SGregory Neil Shapiro **		flags -- modifiers:
3106f25ae9SGregory Neil Shapiro **			SFF_MUSTOWN -- "uid" must own this file.
3206f25ae9SGregory Neil Shapiro **			SFF_NOSLINK -- file cannot be a symbolic link.
3306f25ae9SGregory Neil Shapiro **		mode -- mode bits that must match.
3406f25ae9SGregory Neil Shapiro **		st -- if set, points to a stat structure that will
3506f25ae9SGregory Neil Shapiro **			get the stat info for the file.
3606f25ae9SGregory Neil Shapiro **
3706f25ae9SGregory Neil Shapiro **	Returns:
3806f25ae9SGregory Neil Shapiro **		0 if fn exists, is owned by uid, and matches mode.
3906f25ae9SGregory Neil Shapiro **		An errno otherwise.  The actual errno is cleared.
4006f25ae9SGregory Neil Shapiro **
4106f25ae9SGregory Neil Shapiro **	Side Effects:
4206f25ae9SGregory Neil Shapiro **		none.
4306f25ae9SGregory Neil Shapiro */
4406f25ae9SGregory Neil Shapiro 
4506f25ae9SGregory Neil Shapiro int
4606f25ae9SGregory Neil Shapiro safefile(fn, uid, gid, user, flags, mode, st)
4706f25ae9SGregory Neil Shapiro 	char *fn;
4806f25ae9SGregory Neil Shapiro 	UID_T uid;
4906f25ae9SGregory Neil Shapiro 	GID_T gid;
5006f25ae9SGregory Neil Shapiro 	char *user;
5106f25ae9SGregory Neil Shapiro 	long flags;
5206f25ae9SGregory Neil Shapiro 	int mode;
5306f25ae9SGregory Neil Shapiro 	struct stat *st;
5406f25ae9SGregory Neil Shapiro {
5506f25ae9SGregory Neil Shapiro 	register char *p;
5606f25ae9SGregory Neil Shapiro 	register struct group *gr = NULL;
5706f25ae9SGregory Neil Shapiro 	int file_errno = 0;
5806f25ae9SGregory Neil Shapiro 	bool checkpath;
5906f25ae9SGregory Neil Shapiro 	struct stat stbuf;
6006f25ae9SGregory Neil Shapiro 	struct stat fstbuf;
6106f25ae9SGregory Neil Shapiro 	char fbuf[MAXPATHLEN + 1];
6206f25ae9SGregory Neil Shapiro 
6306f25ae9SGregory Neil Shapiro 	if (tTd(44, 4))
6406f25ae9SGregory Neil Shapiro 		dprintf("safefile(%s, uid=%d, gid=%d, flags=%lx, mode=%o):\n",
6506f25ae9SGregory Neil Shapiro 			fn, (int) uid, (int) gid, flags, mode);
6606f25ae9SGregory Neil Shapiro 	errno = 0;
6706f25ae9SGregory Neil Shapiro 	if (st == NULL)
6806f25ae9SGregory Neil Shapiro 		st = &fstbuf;
6906f25ae9SGregory Neil Shapiro 	if (strlcpy(fbuf, fn, sizeof fbuf) >= sizeof fbuf)
7006f25ae9SGregory Neil Shapiro 	{
7106f25ae9SGregory Neil Shapiro 		if (tTd(44, 4))
7206f25ae9SGregory Neil Shapiro 			dprintf("\tpathname too long\n");
7306f25ae9SGregory Neil Shapiro 		return ENAMETOOLONG;
7406f25ae9SGregory Neil Shapiro 	}
7506f25ae9SGregory Neil Shapiro 	fn = fbuf;
7606f25ae9SGregory Neil Shapiro 
7706f25ae9SGregory Neil Shapiro 	/* ignore SFF_SAFEDIRPATH if we are debugging */
7806f25ae9SGregory Neil Shapiro 	if (RealUid != 0 && RunAsUid == RealUid)
7906f25ae9SGregory Neil Shapiro 		flags &= ~SFF_SAFEDIRPATH;
8006f25ae9SGregory Neil Shapiro 
8106f25ae9SGregory Neil Shapiro 	/* first check to see if the file exists at all */
8206f25ae9SGregory Neil Shapiro # if HASLSTAT
8306f25ae9SGregory Neil Shapiro 	if ((bitset(SFF_NOSLINK, flags) ? lstat(fn, st)
8406f25ae9SGregory Neil Shapiro 					: stat(fn, st)) < 0)
8506f25ae9SGregory Neil Shapiro # else /* HASLSTAT */
8606f25ae9SGregory Neil Shapiro 	if (stat(fn, st) < 0)
8706f25ae9SGregory Neil Shapiro # endif /* HASLSTAT */
8806f25ae9SGregory Neil Shapiro 	{
8906f25ae9SGregory Neil Shapiro 		file_errno = errno;
9006f25ae9SGregory Neil Shapiro 	}
9106f25ae9SGregory Neil Shapiro 	else if (bitset(SFF_SETUIDOK, flags) &&
9206f25ae9SGregory Neil Shapiro 		 !bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode) &&
9306f25ae9SGregory Neil Shapiro 		 S_ISREG(st->st_mode))
9406f25ae9SGregory Neil Shapiro 	{
9506f25ae9SGregory Neil Shapiro 		/*
9606f25ae9SGregory Neil Shapiro 		**  If final file is setuid, run as the owner of that
9706f25ae9SGregory Neil Shapiro 		**  file.  Gotta be careful not to reveal anything too
9806f25ae9SGregory Neil Shapiro 		**  soon here!
9906f25ae9SGregory Neil Shapiro 		*/
10006f25ae9SGregory Neil Shapiro 
10106f25ae9SGregory Neil Shapiro # ifdef SUID_ROOT_FILES_OK
10206f25ae9SGregory Neil Shapiro 		if (bitset(S_ISUID, st->st_mode))
10306f25ae9SGregory Neil Shapiro # else /* SUID_ROOT_FILES_OK */
10406f25ae9SGregory Neil Shapiro 		if (bitset(S_ISUID, st->st_mode) && st->st_uid != 0 &&
10506f25ae9SGregory Neil Shapiro 		    st->st_uid != TrustedUid)
10606f25ae9SGregory Neil Shapiro # endif /* SUID_ROOT_FILES_OK */
10706f25ae9SGregory Neil Shapiro 		{
10806f25ae9SGregory Neil Shapiro 			uid = st->st_uid;
10906f25ae9SGregory Neil Shapiro 			user = NULL;
11006f25ae9SGregory Neil Shapiro 		}
11106f25ae9SGregory Neil Shapiro # ifdef SUID_ROOT_FILES_OK
11206f25ae9SGregory Neil Shapiro 		if (bitset(S_ISGID, st->st_mode))
11306f25ae9SGregory Neil Shapiro # else /* SUID_ROOT_FILES_OK */
11406f25ae9SGregory Neil Shapiro 		if (bitset(S_ISGID, st->st_mode) && st->st_gid != 0)
11506f25ae9SGregory Neil Shapiro # endif /* SUID_ROOT_FILES_OK */
11606f25ae9SGregory Neil Shapiro 			gid = st->st_gid;
11706f25ae9SGregory Neil Shapiro 	}
11806f25ae9SGregory Neil Shapiro 
11906f25ae9SGregory Neil Shapiro 	checkpath = !bitset(SFF_NOPATHCHECK, flags) ||
12006f25ae9SGregory Neil Shapiro 		    (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags));
12106f25ae9SGregory Neil Shapiro 	if (bitset(SFF_NOWLINK, flags) && !bitset(SFF_SAFEDIRPATH, flags))
12206f25ae9SGregory Neil Shapiro 	{
12306f25ae9SGregory Neil Shapiro 		int ret;
12406f25ae9SGregory Neil Shapiro 
12506f25ae9SGregory Neil Shapiro 		/* check the directory */
12606f25ae9SGregory Neil Shapiro 		p = strrchr(fn, '/');
12706f25ae9SGregory Neil Shapiro 		if (p == NULL)
12806f25ae9SGregory Neil Shapiro 		{
12906f25ae9SGregory Neil Shapiro 			ret = safedirpath(".", uid, gid, user,
13006f25ae9SGregory Neil Shapiro 					  flags|SFF_SAFEDIRPATH, 0, 0);
13106f25ae9SGregory Neil Shapiro 		}
13206f25ae9SGregory Neil Shapiro 		else
13306f25ae9SGregory Neil Shapiro 		{
13406f25ae9SGregory Neil Shapiro 			*p = '\0';
13506f25ae9SGregory Neil Shapiro 			ret = safedirpath(fn, uid, gid, user,
13606f25ae9SGregory Neil Shapiro 					  flags|SFF_SAFEDIRPATH, 0, 0);
13706f25ae9SGregory Neil Shapiro 			*p = '/';
13806f25ae9SGregory Neil Shapiro 		}
13906f25ae9SGregory Neil Shapiro 		if (ret == 0)
14006f25ae9SGregory Neil Shapiro 		{
14106f25ae9SGregory Neil Shapiro 			/* directory is safe */
14206f25ae9SGregory Neil Shapiro 			checkpath = FALSE;
14306f25ae9SGregory Neil Shapiro 		}
14406f25ae9SGregory Neil Shapiro 		else
14506f25ae9SGregory Neil Shapiro 		{
14606f25ae9SGregory Neil Shapiro # if HASLSTAT
14706f25ae9SGregory Neil Shapiro 			/* Need lstat() information if called stat() before */
14806f25ae9SGregory Neil Shapiro 			if (!bitset(SFF_NOSLINK, flags) && lstat(fn, st) < 0)
14906f25ae9SGregory Neil Shapiro 			{
15006f25ae9SGregory Neil Shapiro 				ret = errno;
15106f25ae9SGregory Neil Shapiro 				if (tTd(44, 4))
15206f25ae9SGregory Neil Shapiro 					dprintf("\t%s\n", errstring(ret));
15306f25ae9SGregory Neil Shapiro 				return ret;
15406f25ae9SGregory Neil Shapiro 			}
15506f25ae9SGregory Neil Shapiro # endif /* HASLSTAT */
15606f25ae9SGregory Neil Shapiro 			/* directory is writable: disallow links */
15706f25ae9SGregory Neil Shapiro 			flags |= SFF_NOLINK;
15806f25ae9SGregory Neil Shapiro 		}
15906f25ae9SGregory Neil Shapiro 	}
16006f25ae9SGregory Neil Shapiro 
16106f25ae9SGregory Neil Shapiro 	if (checkpath)
16206f25ae9SGregory Neil Shapiro 	{
16306f25ae9SGregory Neil Shapiro 		int ret;
16406f25ae9SGregory Neil Shapiro 
16506f25ae9SGregory Neil Shapiro 		p = strrchr(fn, '/');
16606f25ae9SGregory Neil Shapiro 		if (p == NULL)
16706f25ae9SGregory Neil Shapiro 		{
16806f25ae9SGregory Neil Shapiro 			ret = safedirpath(".", uid, gid, user, flags, 0, 0);
16906f25ae9SGregory Neil Shapiro 		}
17006f25ae9SGregory Neil Shapiro 		else
17106f25ae9SGregory Neil Shapiro 		{
17206f25ae9SGregory Neil Shapiro 			*p = '\0';
17306f25ae9SGregory Neil Shapiro 			ret = safedirpath(fn, uid, gid, user, flags, 0, 0);
17406f25ae9SGregory Neil Shapiro 			*p = '/';
17506f25ae9SGregory Neil Shapiro 		}
17606f25ae9SGregory Neil Shapiro 		if (ret != 0)
17706f25ae9SGregory Neil Shapiro 			return ret;
17806f25ae9SGregory Neil Shapiro 	}
17906f25ae9SGregory Neil Shapiro 
18006f25ae9SGregory Neil Shapiro 	/*
18106f25ae9SGregory Neil Shapiro 	**  If the target file doesn't exist, check the directory to
18206f25ae9SGregory Neil Shapiro 	**  ensure that it is writable by this user.
18306f25ae9SGregory Neil Shapiro 	*/
18406f25ae9SGregory Neil Shapiro 
18506f25ae9SGregory Neil Shapiro 	if (file_errno != 0)
18606f25ae9SGregory Neil Shapiro 	{
18706f25ae9SGregory Neil Shapiro 		int ret = file_errno;
18806f25ae9SGregory Neil Shapiro 		char *dir = fn;
18906f25ae9SGregory Neil Shapiro 
19006f25ae9SGregory Neil Shapiro 		if (tTd(44, 4))
19106f25ae9SGregory Neil Shapiro 			dprintf("\t%s\n", errstring(ret));
19206f25ae9SGregory Neil Shapiro 
19306f25ae9SGregory Neil Shapiro 		errno = 0;
19406f25ae9SGregory Neil Shapiro 		if (!bitset(SFF_CREAT, flags) || file_errno != ENOENT)
19506f25ae9SGregory Neil Shapiro 			return ret;
19606f25ae9SGregory Neil Shapiro 
19706f25ae9SGregory Neil Shapiro 		/* check to see if legal to create the file */
19806f25ae9SGregory Neil Shapiro 		p = strrchr(dir, '/');
19906f25ae9SGregory Neil Shapiro 		if (p == NULL)
20006f25ae9SGregory Neil Shapiro 			dir = ".";
20106f25ae9SGregory Neil Shapiro 		else if (p == dir)
20206f25ae9SGregory Neil Shapiro 			dir = "/";
20306f25ae9SGregory Neil Shapiro 		else
20406f25ae9SGregory Neil Shapiro 			*p = '\0';
20506f25ae9SGregory Neil Shapiro 		if (stat(dir, &stbuf) >= 0)
20606f25ae9SGregory Neil Shapiro 		{
20706f25ae9SGregory Neil Shapiro 			int md = S_IWRITE|S_IEXEC;
20806f25ae9SGregory Neil Shapiro 
20906f25ae9SGregory Neil Shapiro 			if (stbuf.st_uid == uid)
21006f25ae9SGregory Neil Shapiro 				/* EMPTY */
21106f25ae9SGregory Neil Shapiro 				;
21206f25ae9SGregory Neil Shapiro 			else if (uid == 0 && stbuf.st_uid == TrustedUid)
21306f25ae9SGregory Neil Shapiro 				/* EMPTY */
21406f25ae9SGregory Neil Shapiro 				;
21506f25ae9SGregory Neil Shapiro 			else
21606f25ae9SGregory Neil Shapiro 			{
21706f25ae9SGregory Neil Shapiro 				md >>= 3;
21806f25ae9SGregory Neil Shapiro 				if (stbuf.st_gid == gid)
21906f25ae9SGregory Neil Shapiro 					/* EMPTY */
22006f25ae9SGregory Neil Shapiro 					;
22106f25ae9SGregory Neil Shapiro # ifndef NO_GROUP_SET
22206f25ae9SGregory Neil Shapiro 				else if (user != NULL && !DontInitGroups &&
22306f25ae9SGregory Neil Shapiro 					 ((gr != NULL &&
22406f25ae9SGregory Neil Shapiro 					   gr->gr_gid == stbuf.st_gid) ||
22506f25ae9SGregory Neil Shapiro 					  (gr = getgrgid(stbuf.st_gid)) != NULL))
22606f25ae9SGregory Neil Shapiro 				{
22706f25ae9SGregory Neil Shapiro 					register char **gp;
22806f25ae9SGregory Neil Shapiro 
22906f25ae9SGregory Neil Shapiro 					for (gp = gr->gr_mem; *gp != NULL; gp++)
23006f25ae9SGregory Neil Shapiro 						if (strcmp(*gp, user) == 0)
23106f25ae9SGregory Neil Shapiro 							break;
23206f25ae9SGregory Neil Shapiro 					if (*gp == NULL)
23306f25ae9SGregory Neil Shapiro 						md >>= 3;
23406f25ae9SGregory Neil Shapiro 				}
23506f25ae9SGregory Neil Shapiro # endif /* ! NO_GROUP_SET */
23606f25ae9SGregory Neil Shapiro 				else
23706f25ae9SGregory Neil Shapiro 					md >>= 3;
23806f25ae9SGregory Neil Shapiro 			}
23906f25ae9SGregory Neil Shapiro 			if ((stbuf.st_mode & md) != md)
24006f25ae9SGregory Neil Shapiro 				errno = EACCES;
24106f25ae9SGregory Neil Shapiro 		}
24206f25ae9SGregory Neil Shapiro 		ret = errno;
24306f25ae9SGregory Neil Shapiro 		if (tTd(44, 4))
24406f25ae9SGregory Neil Shapiro 			dprintf("\t[final dir %s uid %d mode %lo] %s\n",
24506f25ae9SGregory Neil Shapiro 				dir, (int) stbuf.st_uid, (u_long) stbuf.st_mode,
24606f25ae9SGregory Neil Shapiro 				errstring(ret));
24706f25ae9SGregory Neil Shapiro 		if (p != NULL)
24806f25ae9SGregory Neil Shapiro 			*p = '/';
24906f25ae9SGregory Neil Shapiro 		st->st_mode = ST_MODE_NOFILE;
25006f25ae9SGregory Neil Shapiro 		return ret;
25106f25ae9SGregory Neil Shapiro 	}
25206f25ae9SGregory Neil Shapiro 
25306f25ae9SGregory Neil Shapiro # ifdef S_ISLNK
25406f25ae9SGregory Neil Shapiro 	if (bitset(SFF_NOSLINK, flags) && S_ISLNK(st->st_mode))
25506f25ae9SGregory Neil Shapiro 	{
25606f25ae9SGregory Neil Shapiro 		if (tTd(44, 4))
25706f25ae9SGregory Neil Shapiro 			dprintf("\t[slink mode %lo]\tE_SM_NOSLINK\n",
25806f25ae9SGregory Neil Shapiro 				(u_long) st->st_mode);
25906f25ae9SGregory Neil Shapiro 		return E_SM_NOSLINK;
26006f25ae9SGregory Neil Shapiro 	}
26106f25ae9SGregory Neil Shapiro # endif /* S_ISLNK */
26206f25ae9SGregory Neil Shapiro 	if (bitset(SFF_REGONLY, flags) && !S_ISREG(st->st_mode))
26306f25ae9SGregory Neil Shapiro 	{
26406f25ae9SGregory Neil Shapiro 		if (tTd(44, 4))
26506f25ae9SGregory Neil Shapiro 			dprintf("\t[non-reg mode %lo]\tE_SM_REGONLY\n",
26606f25ae9SGregory Neil Shapiro 				(u_long) st->st_mode);
26706f25ae9SGregory Neil Shapiro 		return E_SM_REGONLY;
26806f25ae9SGregory Neil Shapiro 	}
26906f25ae9SGregory Neil Shapiro 	if (bitset(SFF_NOGWFILES, flags) &&
27006f25ae9SGregory Neil Shapiro 	    bitset(S_IWGRP, st->st_mode))
27106f25ae9SGregory Neil Shapiro 	{
27206f25ae9SGregory Neil Shapiro 		if (tTd(44, 4))
27306f25ae9SGregory Neil Shapiro 			dprintf("\t[write bits %lo]\tE_SM_GWFILE\n",
27406f25ae9SGregory Neil Shapiro 				(u_long) st->st_mode);
27506f25ae9SGregory Neil Shapiro 		return E_SM_GWFILE;
27606f25ae9SGregory Neil Shapiro 	}
27706f25ae9SGregory Neil Shapiro 	if (bitset(SFF_NOWWFILES, flags) &&
27806f25ae9SGregory Neil Shapiro 	    bitset(S_IWOTH, st->st_mode))
27906f25ae9SGregory Neil Shapiro 	{
28006f25ae9SGregory Neil Shapiro 		if (tTd(44, 4))
28106f25ae9SGregory Neil Shapiro 			dprintf("\t[write bits %lo]\tE_SM_WWFILE\n",
28206f25ae9SGregory Neil Shapiro 				(u_long) st->st_mode);
28306f25ae9SGregory Neil Shapiro 		return E_SM_WWFILE;
28406f25ae9SGregory Neil Shapiro 	}
28506f25ae9SGregory Neil Shapiro 	if (bitset(SFF_NOGRFILES, flags) && bitset(S_IRGRP, st->st_mode))
28606f25ae9SGregory Neil Shapiro 	{
28706f25ae9SGregory Neil Shapiro 		if (tTd(44, 4))
28806f25ae9SGregory Neil Shapiro 			dprintf("\t[read bits %lo]\tE_SM_GRFILE\n",
28906f25ae9SGregory Neil Shapiro 				(u_long) st->st_mode);
29006f25ae9SGregory Neil Shapiro 		return E_SM_GRFILE;
29106f25ae9SGregory Neil Shapiro 	}
29206f25ae9SGregory Neil Shapiro 	if (bitset(SFF_NOWRFILES, flags) && bitset(S_IROTH, st->st_mode))
29306f25ae9SGregory Neil Shapiro 	{
29406f25ae9SGregory Neil Shapiro 		if (tTd(44, 4))
29506f25ae9SGregory Neil Shapiro 			dprintf("\t[read bits %lo]\tE_SM_WRFILE\n",
29606f25ae9SGregory Neil Shapiro 				(u_long) st->st_mode);
29706f25ae9SGregory Neil Shapiro 		return E_SM_WRFILE;
29806f25ae9SGregory Neil Shapiro 	}
29906f25ae9SGregory Neil Shapiro 	if (!bitset(SFF_EXECOK, flags) &&
30006f25ae9SGregory Neil Shapiro 	    bitset(S_IWUSR|S_IWGRP|S_IWOTH, mode) &&
30106f25ae9SGregory Neil Shapiro 	    bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode))
30206f25ae9SGregory Neil Shapiro 	{
30306f25ae9SGregory Neil Shapiro 		if (tTd(44, 4))
30406f25ae9SGregory Neil Shapiro 			dprintf("\t[exec bits %lo]\tE_SM_ISEXEC]\n",
30506f25ae9SGregory Neil Shapiro 				(u_long) st->st_mode);
30606f25ae9SGregory Neil Shapiro 		return E_SM_ISEXEC;
30706f25ae9SGregory Neil Shapiro 	}
30806f25ae9SGregory Neil Shapiro 	if (bitset(SFF_NOHLINK, flags) && st->st_nlink != 1)
30906f25ae9SGregory Neil Shapiro 	{
31006f25ae9SGregory Neil Shapiro 		if (tTd(44, 4))
31106f25ae9SGregory Neil Shapiro 			dprintf("\t[link count %d]\tE_SM_NOHLINK\n",
31206f25ae9SGregory Neil Shapiro 				(int) st->st_nlink);
31306f25ae9SGregory Neil Shapiro 		return E_SM_NOHLINK;
31406f25ae9SGregory Neil Shapiro 	}
31506f25ae9SGregory Neil Shapiro 
31606f25ae9SGregory Neil Shapiro 	if (uid == 0 && bitset(SFF_OPENASROOT, flags))
31706f25ae9SGregory Neil Shapiro 		/* EMPTY */
31806f25ae9SGregory Neil Shapiro 		;
31906f25ae9SGregory Neil Shapiro 	else if (uid == 0 && !bitset(SFF_ROOTOK, flags))
32006f25ae9SGregory Neil Shapiro 		mode >>= 6;
32106f25ae9SGregory Neil Shapiro 	else if (st->st_uid == uid)
32206f25ae9SGregory Neil Shapiro 		/* EMPTY */
32306f25ae9SGregory Neil Shapiro 		;
32406f25ae9SGregory Neil Shapiro 	else if (uid == 0 && st->st_uid == TrustedUid)
32506f25ae9SGregory Neil Shapiro 		/* EMPTY */
32606f25ae9SGregory Neil Shapiro 		;
32706f25ae9SGregory Neil Shapiro 	else
32806f25ae9SGregory Neil Shapiro 	{
32906f25ae9SGregory Neil Shapiro 		mode >>= 3;
33006f25ae9SGregory Neil Shapiro 		if (st->st_gid == gid)
33106f25ae9SGregory Neil Shapiro 			/* EMPTY */
33206f25ae9SGregory Neil Shapiro 			;
33306f25ae9SGregory Neil Shapiro # ifndef NO_GROUP_SET
33406f25ae9SGregory Neil Shapiro 		else if (user != NULL && !DontInitGroups &&
33506f25ae9SGregory Neil Shapiro 			 ((gr != NULL && gr->gr_gid == st->st_gid) ||
33606f25ae9SGregory Neil Shapiro 			  (gr = getgrgid(st->st_gid)) != NULL))
33706f25ae9SGregory Neil Shapiro 		{
33806f25ae9SGregory Neil Shapiro 			register char **gp;
33906f25ae9SGregory Neil Shapiro 
34006f25ae9SGregory Neil Shapiro 			for (gp = gr->gr_mem; *gp != NULL; gp++)
34106f25ae9SGregory Neil Shapiro 				if (strcmp(*gp, user) == 0)
34206f25ae9SGregory Neil Shapiro 					break;
34306f25ae9SGregory Neil Shapiro 			if (*gp == NULL)
34406f25ae9SGregory Neil Shapiro 				mode >>= 3;
34506f25ae9SGregory Neil Shapiro 		}
34606f25ae9SGregory Neil Shapiro # endif /* ! NO_GROUP_SET */
34706f25ae9SGregory Neil Shapiro 		else
34806f25ae9SGregory Neil Shapiro 			mode >>= 3;
34906f25ae9SGregory Neil Shapiro 	}
35006f25ae9SGregory Neil Shapiro 	if (tTd(44, 4))
35106f25ae9SGregory Neil Shapiro 		dprintf("\t[uid %d, nlink %d, stat %lo, mode %lo] ",
35206f25ae9SGregory Neil Shapiro 			(int) st->st_uid, (int) st->st_nlink,
35306f25ae9SGregory Neil Shapiro 			(u_long) st->st_mode, (u_long) mode);
35406f25ae9SGregory Neil Shapiro 	if ((st->st_uid == uid || st->st_uid == 0 ||
35506f25ae9SGregory Neil Shapiro 	     st->st_uid == TrustedUid ||
35606f25ae9SGregory Neil Shapiro 	     !bitset(SFF_MUSTOWN, flags)) &&
35706f25ae9SGregory Neil Shapiro 	    (st->st_mode & mode) == mode)
35806f25ae9SGregory Neil Shapiro 	{
35906f25ae9SGregory Neil Shapiro 		if (tTd(44, 4))
36006f25ae9SGregory Neil Shapiro 			dprintf("\tOK\n");
36106f25ae9SGregory Neil Shapiro 		return 0;
36206f25ae9SGregory Neil Shapiro 	}
36306f25ae9SGregory Neil Shapiro 	if (tTd(44, 4))
36406f25ae9SGregory Neil Shapiro 		dprintf("\tEACCES\n");
36506f25ae9SGregory Neil Shapiro 	return EACCES;
36606f25ae9SGregory Neil Shapiro }
36706f25ae9SGregory Neil Shapiro /*
36806f25ae9SGregory Neil Shapiro **  SAFEDIRPATH -- check to make sure a path to a directory is safe
36906f25ae9SGregory Neil Shapiro **
37006f25ae9SGregory Neil Shapiro **	Safe means not writable and owned by the right folks.
37106f25ae9SGregory Neil Shapiro **
37206f25ae9SGregory Neil Shapiro **	Parameters:
37306f25ae9SGregory Neil Shapiro **		fn -- filename to check.
37406f25ae9SGregory Neil Shapiro **		uid -- user id to compare against.
37506f25ae9SGregory Neil Shapiro **		gid -- group id to compare against.
37606f25ae9SGregory Neil Shapiro **		user -- user name to compare against (used for group
37706f25ae9SGregory Neil Shapiro **			sets).
37806f25ae9SGregory Neil Shapiro **		flags -- modifiers:
37906f25ae9SGregory Neil Shapiro **			SFF_ROOTOK -- ok to use root permissions to open.
38006f25ae9SGregory Neil Shapiro **			SFF_SAFEDIRPATH -- writable directories are considered
38106f25ae9SGregory Neil Shapiro **				to be fatal errors.
38206f25ae9SGregory Neil Shapiro **		level -- symlink recursive level.
38306f25ae9SGregory Neil Shapiro **		offset -- offset into fn to start checking from.
38406f25ae9SGregory Neil Shapiro **
38506f25ae9SGregory Neil Shapiro **	Returns:
38606f25ae9SGregory Neil Shapiro **		0 -- if the directory path is "safe".
38706f25ae9SGregory Neil Shapiro **		else -- an error number associated with the path.
38806f25ae9SGregory Neil Shapiro */
38906f25ae9SGregory Neil Shapiro 
39006f25ae9SGregory Neil Shapiro int
39106f25ae9SGregory Neil Shapiro safedirpath(fn, uid, gid, user, flags, level, offset)
39206f25ae9SGregory Neil Shapiro 	char *fn;
39306f25ae9SGregory Neil Shapiro 	UID_T uid;
39406f25ae9SGregory Neil Shapiro 	GID_T gid;
39506f25ae9SGregory Neil Shapiro 	char *user;
39606f25ae9SGregory Neil Shapiro 	long flags;
39706f25ae9SGregory Neil Shapiro 	int level;
39806f25ae9SGregory Neil Shapiro 	int offset;
39906f25ae9SGregory Neil Shapiro {
40006f25ae9SGregory Neil Shapiro 	int ret = 0;
40106f25ae9SGregory Neil Shapiro 	int mode = S_IWOTH;
40206f25ae9SGregory Neil Shapiro 	char save = '\0';
40306f25ae9SGregory Neil Shapiro 	char *saveptr = NULL;
40406f25ae9SGregory Neil Shapiro 	char *p, *enddir;
40506f25ae9SGregory Neil Shapiro 	register struct group *gr = NULL;
40606f25ae9SGregory Neil Shapiro 	char s[MAXLINKPATHLEN + 1];
40706f25ae9SGregory Neil Shapiro 	struct stat stbuf;
40806f25ae9SGregory Neil Shapiro 
40906f25ae9SGregory Neil Shapiro 	/* make sure we aren't in a symlink loop */
41006f25ae9SGregory Neil Shapiro 	if (level > MAXSYMLINKS)
41106f25ae9SGregory Neil Shapiro 		return ELOOP;
41206f25ae9SGregory Neil Shapiro 
41306f25ae9SGregory Neil Shapiro 	/* special case root directory */
41406f25ae9SGregory Neil Shapiro 	if (*fn == '\0')
41506f25ae9SGregory Neil Shapiro 		fn = "/";
41606f25ae9SGregory Neil Shapiro 
41706f25ae9SGregory Neil Shapiro 	if (tTd(44, 4))
41806f25ae9SGregory Neil Shapiro 		dprintf("safedirpath(%s, uid=%ld, gid=%ld, flags=%lx, level=%d, offset=%d):\n",
41906f25ae9SGregory Neil Shapiro 			fn, (long) uid, (long) gid, flags, level, offset);
42006f25ae9SGregory Neil Shapiro 
42106f25ae9SGregory Neil Shapiro 	if (!bitnset(DBS_GROUPWRITABLEDIRPATHSAFE, DontBlameSendmail))
42206f25ae9SGregory Neil Shapiro 		mode |= S_IWGRP;
42306f25ae9SGregory Neil Shapiro 
42406f25ae9SGregory Neil Shapiro 	/* Make a modifiable copy of the filename */
42506f25ae9SGregory Neil Shapiro 	if (strlcpy(s, fn, sizeof s) >= sizeof s)
42606f25ae9SGregory Neil Shapiro 		return EINVAL;
42706f25ae9SGregory Neil Shapiro 
42806f25ae9SGregory Neil Shapiro 	p = s + offset;
42906f25ae9SGregory Neil Shapiro 	while (p != NULL)
43006f25ae9SGregory Neil Shapiro 	{
43106f25ae9SGregory Neil Shapiro 		/* put back character */
43206f25ae9SGregory Neil Shapiro 		if (saveptr != NULL)
43306f25ae9SGregory Neil Shapiro 		{
43406f25ae9SGregory Neil Shapiro 			*saveptr = save;
43506f25ae9SGregory Neil Shapiro 			saveptr = NULL;
43606f25ae9SGregory Neil Shapiro 			p++;
43706f25ae9SGregory Neil Shapiro 		}
43806f25ae9SGregory Neil Shapiro 
43906f25ae9SGregory Neil Shapiro 		if (*p == '\0')
44006f25ae9SGregory Neil Shapiro 			break;
44106f25ae9SGregory Neil Shapiro 
44206f25ae9SGregory Neil Shapiro 		p = strchr(p, '/');
44306f25ae9SGregory Neil Shapiro 
44406f25ae9SGregory Neil Shapiro 		/* Special case for root directory */
44506f25ae9SGregory Neil Shapiro 		if (p == s)
44606f25ae9SGregory Neil Shapiro 		{
44706f25ae9SGregory Neil Shapiro 			save = *(p + 1);
44806f25ae9SGregory Neil Shapiro 			saveptr = p + 1;
44906f25ae9SGregory Neil Shapiro 			*(p + 1) = '\0';
45006f25ae9SGregory Neil Shapiro 		}
45106f25ae9SGregory Neil Shapiro 		else if (p != NULL)
45206f25ae9SGregory Neil Shapiro 		{
45306f25ae9SGregory Neil Shapiro 			save = *p;
45406f25ae9SGregory Neil Shapiro 			saveptr = p;
45506f25ae9SGregory Neil Shapiro 			*p = '\0';
45606f25ae9SGregory Neil Shapiro 		}
45706f25ae9SGregory Neil Shapiro 
45806f25ae9SGregory Neil Shapiro 		/* Heuristic: . and .. have already been checked */
45906f25ae9SGregory Neil Shapiro 		enddir = strrchr(s, '/');
46006f25ae9SGregory Neil Shapiro 		if (enddir != NULL &&
46106f25ae9SGregory Neil Shapiro 		    (strcmp(enddir, "/..") == 0 ||
46206f25ae9SGregory Neil Shapiro 		     strcmp(enddir, "/.") == 0))
46306f25ae9SGregory Neil Shapiro 			continue;
46406f25ae9SGregory Neil Shapiro 
46506f25ae9SGregory Neil Shapiro 		if (tTd(44, 20))
46606f25ae9SGregory Neil Shapiro 			dprintf("\t[dir %s]\n", s);
46706f25ae9SGregory Neil Shapiro 
46806f25ae9SGregory Neil Shapiro # if HASLSTAT
46906f25ae9SGregory Neil Shapiro 		ret = lstat(s, &stbuf);
47006f25ae9SGregory Neil Shapiro # else /* HASLSTAT */
47106f25ae9SGregory Neil Shapiro 		ret = stat(s, &stbuf);
47206f25ae9SGregory Neil Shapiro # endif /* HASLSTAT */
47306f25ae9SGregory Neil Shapiro 		if (ret < 0)
47406f25ae9SGregory Neil Shapiro 		{
47506f25ae9SGregory Neil Shapiro 			ret = errno;
47606f25ae9SGregory Neil Shapiro 			break;
47706f25ae9SGregory Neil Shapiro 		}
47806f25ae9SGregory Neil Shapiro 
47906f25ae9SGregory Neil Shapiro # ifdef S_ISLNK
48006f25ae9SGregory Neil Shapiro 		/* Follow symlinks */
48106f25ae9SGregory Neil Shapiro 		if (S_ISLNK(stbuf.st_mode))
48206f25ae9SGregory Neil Shapiro 		{
48306f25ae9SGregory Neil Shapiro 			char *target;
48406f25ae9SGregory Neil Shapiro 			char buf[MAXPATHLEN + 1];
48506f25ae9SGregory Neil Shapiro 
48606f25ae9SGregory Neil Shapiro 			memset(buf, '\0', sizeof buf);
48706f25ae9SGregory Neil Shapiro 			if (readlink(s, buf, sizeof buf) < 0)
48806f25ae9SGregory Neil Shapiro 			{
48906f25ae9SGregory Neil Shapiro 				ret = errno;
49006f25ae9SGregory Neil Shapiro 				break;
49106f25ae9SGregory Neil Shapiro 			}
49206f25ae9SGregory Neil Shapiro 
49306f25ae9SGregory Neil Shapiro 			offset = 0;
49406f25ae9SGregory Neil Shapiro 			if (*buf == '/')
49506f25ae9SGregory Neil Shapiro 			{
49606f25ae9SGregory Neil Shapiro 				target = buf;
49706f25ae9SGregory Neil Shapiro 
49806f25ae9SGregory Neil Shapiro 				/* If path is the same, avoid rechecks */
49906f25ae9SGregory Neil Shapiro 				while (s[offset] == buf[offset] &&
50006f25ae9SGregory Neil Shapiro 				       s[offset] != '\0')
50106f25ae9SGregory Neil Shapiro 					offset++;
50206f25ae9SGregory Neil Shapiro 
50306f25ae9SGregory Neil Shapiro 				if (s[offset] == '\0' && buf[offset] == '\0')
50406f25ae9SGregory Neil Shapiro 				{
50506f25ae9SGregory Neil Shapiro 					/* strings match, symlink loop */
50606f25ae9SGregory Neil Shapiro 					return ELOOP;
50706f25ae9SGregory Neil Shapiro 				}
50806f25ae9SGregory Neil Shapiro 
50906f25ae9SGregory Neil Shapiro 				/* back off from the mismatch */
51006f25ae9SGregory Neil Shapiro 				if (offset > 0)
51106f25ae9SGregory Neil Shapiro 					offset--;
51206f25ae9SGregory Neil Shapiro 
51306f25ae9SGregory Neil Shapiro 				/* Make sure we are at a directory break */
51406f25ae9SGregory Neil Shapiro 				if (offset > 0 &&
51506f25ae9SGregory Neil Shapiro 				    s[offset] != '/' &&
51606f25ae9SGregory Neil Shapiro 				    s[offset] != '\0')
51706f25ae9SGregory Neil Shapiro 				{
51806f25ae9SGregory Neil Shapiro 					while (buf[offset] != '/' &&
51906f25ae9SGregory Neil Shapiro 					       offset > 0)
52006f25ae9SGregory Neil Shapiro 						offset--;
52106f25ae9SGregory Neil Shapiro 				}
52206f25ae9SGregory Neil Shapiro 				if (offset > 0 &&
52306f25ae9SGregory Neil Shapiro 				    s[offset] == '/' &&
52406f25ae9SGregory Neil Shapiro 				    buf[offset] == '/')
52506f25ae9SGregory Neil Shapiro 				{
52606f25ae9SGregory Neil Shapiro 					/* Include the trailing slash */
52706f25ae9SGregory Neil Shapiro 					offset++;
52806f25ae9SGregory Neil Shapiro 				}
52906f25ae9SGregory Neil Shapiro 			}
53006f25ae9SGregory Neil Shapiro 			else
53106f25ae9SGregory Neil Shapiro 			{
53206f25ae9SGregory Neil Shapiro 				char *sptr;
53306f25ae9SGregory Neil Shapiro 				char fullbuf[MAXLINKPATHLEN + 1];
53406f25ae9SGregory Neil Shapiro 
53506f25ae9SGregory Neil Shapiro 				sptr = strrchr(s, '/');
53606f25ae9SGregory Neil Shapiro 				if (sptr != NULL)
53706f25ae9SGregory Neil Shapiro 				{
53806f25ae9SGregory Neil Shapiro 					*sptr = '\0';
53906f25ae9SGregory Neil Shapiro 					offset = sptr + 1 - s;
54006f25ae9SGregory Neil Shapiro 					if ((strlen(s) + 1 +
54106f25ae9SGregory Neil Shapiro 					     strlen(buf) + 1) > sizeof fullbuf)
54206f25ae9SGregory Neil Shapiro 					{
54306f25ae9SGregory Neil Shapiro 						ret = EINVAL;
54406f25ae9SGregory Neil Shapiro 						break;
54506f25ae9SGregory Neil Shapiro 					}
54606f25ae9SGregory Neil Shapiro 					snprintf(fullbuf, sizeof fullbuf,
54706f25ae9SGregory Neil Shapiro 						 "%s/%s", s, buf);
54806f25ae9SGregory Neil Shapiro 					*sptr = '/';
54906f25ae9SGregory Neil Shapiro 				}
55006f25ae9SGregory Neil Shapiro 				else
55106f25ae9SGregory Neil Shapiro 				{
55206f25ae9SGregory Neil Shapiro 					if (strlen(buf) + 1 > sizeof fullbuf)
55306f25ae9SGregory Neil Shapiro 					{
55406f25ae9SGregory Neil Shapiro 						ret = EINVAL;
55506f25ae9SGregory Neil Shapiro 						break;
55606f25ae9SGregory Neil Shapiro 					}
55706f25ae9SGregory Neil Shapiro 					(void) strlcpy(fullbuf, buf,
55806f25ae9SGregory Neil Shapiro 						       sizeof fullbuf);
55906f25ae9SGregory Neil Shapiro 				}
56006f25ae9SGregory Neil Shapiro 				target = fullbuf;
56106f25ae9SGregory Neil Shapiro 			}
56206f25ae9SGregory Neil Shapiro 			ret = safedirpath(target, uid, gid, user, flags,
56306f25ae9SGregory Neil Shapiro 					  level + 1, offset);
56406f25ae9SGregory Neil Shapiro 			if (ret != 0)
56506f25ae9SGregory Neil Shapiro 				break;
56606f25ae9SGregory Neil Shapiro 
56706f25ae9SGregory Neil Shapiro 			/* Don't check permissions on the link file itself */
56806f25ae9SGregory Neil Shapiro 			continue;
56906f25ae9SGregory Neil Shapiro 		}
57006f25ae9SGregory Neil Shapiro #endif /* S_ISLNK */
57106f25ae9SGregory Neil Shapiro 
57206f25ae9SGregory Neil Shapiro 		if ((uid == 0 || bitset(SFF_SAFEDIRPATH, flags)) &&
57306f25ae9SGregory Neil Shapiro #ifdef S_ISVTX
57406f25ae9SGregory Neil Shapiro 		    !(bitnset(DBS_TRUSTSTICKYBIT, DontBlameSendmail) &&
57506f25ae9SGregory Neil Shapiro 		      bitset(S_ISVTX, stbuf.st_mode)) &&
57606f25ae9SGregory Neil Shapiro #endif /* S_ISVTX */
57706f25ae9SGregory Neil Shapiro 		    bitset(mode, stbuf.st_mode))
57806f25ae9SGregory Neil Shapiro 		{
57906f25ae9SGregory Neil Shapiro 			if (tTd(44, 4))
58006f25ae9SGregory Neil Shapiro 				dprintf("\t[dir %s] mode %lo ",
58106f25ae9SGregory Neil Shapiro 					s, (u_long) stbuf.st_mode);
58206f25ae9SGregory Neil Shapiro 			if (bitset(SFF_SAFEDIRPATH, flags))
58306f25ae9SGregory Neil Shapiro 			{
58406f25ae9SGregory Neil Shapiro 				if (bitset(S_IWOTH, stbuf.st_mode))
58506f25ae9SGregory Neil Shapiro 					ret = E_SM_WWDIR;
58606f25ae9SGregory Neil Shapiro 				else
58706f25ae9SGregory Neil Shapiro 					ret = E_SM_GWDIR;
58806f25ae9SGregory Neil Shapiro 				if (tTd(44, 4))
58906f25ae9SGregory Neil Shapiro 					dprintf("FATAL\n");
59006f25ae9SGregory Neil Shapiro 				break;
59106f25ae9SGregory Neil Shapiro 			}
59206f25ae9SGregory Neil Shapiro 			if (tTd(44, 4))
59306f25ae9SGregory Neil Shapiro 				dprintf("WARNING\n");
59406f25ae9SGregory Neil Shapiro 			if (Verbose > 1)
59506f25ae9SGregory Neil Shapiro 				message("051 WARNING: %s writable directory %s",
59606f25ae9SGregory Neil Shapiro 					bitset(S_IWOTH, stbuf.st_mode)
59706f25ae9SGregory Neil Shapiro 					   ? "World"
59806f25ae9SGregory Neil Shapiro 					   : "Group",
59906f25ae9SGregory Neil Shapiro 					s);
60006f25ae9SGregory Neil Shapiro 		}
60106f25ae9SGregory Neil Shapiro 		if (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags))
60206f25ae9SGregory Neil Shapiro 		{
60306f25ae9SGregory Neil Shapiro 			if (bitset(S_IXOTH, stbuf.st_mode))
60406f25ae9SGregory Neil Shapiro 				continue;
60506f25ae9SGregory Neil Shapiro 			ret = EACCES;
60606f25ae9SGregory Neil Shapiro 			break;
60706f25ae9SGregory Neil Shapiro 		}
60806f25ae9SGregory Neil Shapiro 
60906f25ae9SGregory Neil Shapiro 		/*
61006f25ae9SGregory Neil Shapiro 		**  Let OS determine access to file if we are not
61106f25ae9SGregory Neil Shapiro 		**  running as a privileged user.  This allows ACLs
61206f25ae9SGregory Neil Shapiro 		**  to work.  Also, if opening as root, assume we can
61306f25ae9SGregory Neil Shapiro 		**  scan the directory.
61406f25ae9SGregory Neil Shapiro 		*/
61506f25ae9SGregory Neil Shapiro 		if (geteuid() != 0 || bitset(SFF_OPENASROOT, flags))
61606f25ae9SGregory Neil Shapiro 			continue;
61706f25ae9SGregory Neil Shapiro 
61806f25ae9SGregory Neil Shapiro 		if (stbuf.st_uid == uid &&
61906f25ae9SGregory Neil Shapiro 		    bitset(S_IXUSR, stbuf.st_mode))
62006f25ae9SGregory Neil Shapiro 			continue;
62106f25ae9SGregory Neil Shapiro 		if (stbuf.st_gid == gid &&
62206f25ae9SGregory Neil Shapiro 		    bitset(S_IXGRP, stbuf.st_mode))
62306f25ae9SGregory Neil Shapiro 			continue;
62406f25ae9SGregory Neil Shapiro # ifndef NO_GROUP_SET
62506f25ae9SGregory Neil Shapiro 		if (user != NULL && !DontInitGroups &&
62606f25ae9SGregory Neil Shapiro 		    ((gr != NULL && gr->gr_gid == stbuf.st_gid) ||
62706f25ae9SGregory Neil Shapiro 		     (gr = getgrgid(stbuf.st_gid)) != NULL))
62806f25ae9SGregory Neil Shapiro 		{
62906f25ae9SGregory Neil Shapiro 			register char **gp;
63006f25ae9SGregory Neil Shapiro 
63106f25ae9SGregory Neil Shapiro 			for (gp = gr->gr_mem; gp != NULL && *gp != NULL; gp++)
63206f25ae9SGregory Neil Shapiro 				if (strcmp(*gp, user) == 0)
63306f25ae9SGregory Neil Shapiro 					break;
63406f25ae9SGregory Neil Shapiro 			if (gp != NULL && *gp != NULL &&
63506f25ae9SGregory Neil Shapiro 			    bitset(S_IXGRP, stbuf.st_mode))
63606f25ae9SGregory Neil Shapiro 				continue;
63706f25ae9SGregory Neil Shapiro 		}
63806f25ae9SGregory Neil Shapiro # endif /* ! NO_GROUP_SET */
63906f25ae9SGregory Neil Shapiro 		if (!bitset(S_IXOTH, stbuf.st_mode))
64006f25ae9SGregory Neil Shapiro 		{
64106f25ae9SGregory Neil Shapiro 			ret = EACCES;
64206f25ae9SGregory Neil Shapiro 			break;
64306f25ae9SGregory Neil Shapiro 		}
64406f25ae9SGregory Neil Shapiro 	}
64506f25ae9SGregory Neil Shapiro 	if (tTd(44, 4))
64606f25ae9SGregory Neil Shapiro 		dprintf("\t[dir %s] %s\n", fn,
64706f25ae9SGregory Neil Shapiro 			ret == 0 ? "OK" : errstring(ret));
64806f25ae9SGregory Neil Shapiro 	return ret;
64906f25ae9SGregory Neil Shapiro }
65006f25ae9SGregory Neil Shapiro /*
65106f25ae9SGregory Neil Shapiro **  SAFEOPEN -- do a file open with extra checking
65206f25ae9SGregory Neil Shapiro **
65306f25ae9SGregory Neil Shapiro **	Parameters:
65406f25ae9SGregory Neil Shapiro **		fn -- the file name to open.
65506f25ae9SGregory Neil Shapiro **		omode -- the open-style mode flags.
65606f25ae9SGregory Neil Shapiro **		cmode -- the create-style mode flags.
65706f25ae9SGregory Neil Shapiro **		sff -- safefile flags.
65806f25ae9SGregory Neil Shapiro **
65906f25ae9SGregory Neil Shapiro **	Returns:
66006f25ae9SGregory Neil Shapiro **		Same as open.
66106f25ae9SGregory Neil Shapiro */
66206f25ae9SGregory Neil Shapiro 
66306f25ae9SGregory Neil Shapiro #ifndef O_ACCMODE
66406f25ae9SGregory Neil Shapiro # define O_ACCMODE	(O_RDONLY|O_WRONLY|O_RDWR)
66506f25ae9SGregory Neil Shapiro #endif /* ! O_ACCMODE */
66606f25ae9SGregory Neil Shapiro 
66706f25ae9SGregory Neil Shapiro int
66806f25ae9SGregory Neil Shapiro safeopen(fn, omode, cmode, sff)
66906f25ae9SGregory Neil Shapiro 	char *fn;
67006f25ae9SGregory Neil Shapiro 	int omode;
67106f25ae9SGregory Neil Shapiro 	int cmode;
67206f25ae9SGregory Neil Shapiro 	long sff;
67306f25ae9SGregory Neil Shapiro {
67406f25ae9SGregory Neil Shapiro 	int rval;
67506f25ae9SGregory Neil Shapiro 	int fd;
67606f25ae9SGregory Neil Shapiro 	int smode;
67706f25ae9SGregory Neil Shapiro 	struct stat stb;
67806f25ae9SGregory Neil Shapiro 
67906f25ae9SGregory Neil Shapiro 	if (tTd(44, 10))
68006f25ae9SGregory Neil Shapiro 		printf("safeopen: fn=%s, omode=%x, cmode=%x, sff=%lx\n",
68106f25ae9SGregory Neil Shapiro 		       fn, omode, cmode, sff);
68206f25ae9SGregory Neil Shapiro 
68306f25ae9SGregory Neil Shapiro 	if (bitset(O_CREAT, omode))
68406f25ae9SGregory Neil Shapiro 		sff |= SFF_CREAT;
68506f25ae9SGregory Neil Shapiro 	omode &= ~O_CREAT;
68606f25ae9SGregory Neil Shapiro 	smode = 0;
68706f25ae9SGregory Neil Shapiro 	switch (omode & O_ACCMODE)
68806f25ae9SGregory Neil Shapiro 	{
68906f25ae9SGregory Neil Shapiro 	  case O_RDONLY:
69006f25ae9SGregory Neil Shapiro 		smode = S_IREAD;
69106f25ae9SGregory Neil Shapiro 		break;
69206f25ae9SGregory Neil Shapiro 
69306f25ae9SGregory Neil Shapiro 	  case O_WRONLY:
69406f25ae9SGregory Neil Shapiro 		smode = S_IWRITE;
69506f25ae9SGregory Neil Shapiro 		break;
69606f25ae9SGregory Neil Shapiro 
69706f25ae9SGregory Neil Shapiro 	  case O_RDWR:
69806f25ae9SGregory Neil Shapiro 		smode = S_IREAD|S_IWRITE;
69906f25ae9SGregory Neil Shapiro 		break;
70006f25ae9SGregory Neil Shapiro 
70106f25ae9SGregory Neil Shapiro 	  default:
70206f25ae9SGregory Neil Shapiro 		smode = 0;
70306f25ae9SGregory Neil Shapiro 		break;
70406f25ae9SGregory Neil Shapiro 	}
70506f25ae9SGregory Neil Shapiro 	if (bitset(SFF_OPENASROOT, sff))
70606f25ae9SGregory Neil Shapiro 		rval = safefile(fn, RunAsUid, RunAsGid, RunAsUserName,
70706f25ae9SGregory Neil Shapiro 				sff, smode, &stb);
70806f25ae9SGregory Neil Shapiro 	else
70906f25ae9SGregory Neil Shapiro 		rval = safefile(fn, RealUid, RealGid, RealUserName,
71006f25ae9SGregory Neil Shapiro 				sff, smode, &stb);
71106f25ae9SGregory Neil Shapiro 	if (rval != 0)
71206f25ae9SGregory Neil Shapiro 	{
71306f25ae9SGregory Neil Shapiro 		errno = rval;
71406f25ae9SGregory Neil Shapiro 		return -1;
71506f25ae9SGregory Neil Shapiro 	}
71606f25ae9SGregory Neil Shapiro 	if (stb.st_mode == ST_MODE_NOFILE && bitset(SFF_CREAT, sff))
71706f25ae9SGregory Neil Shapiro 		omode |= O_CREAT | (bitset(SFF_NOTEXCL, sff) ? 0 : O_EXCL);
71806f25ae9SGregory Neil Shapiro 	else if (bitset(SFF_CREAT, sff) && bitset(O_EXCL, omode))
71906f25ae9SGregory Neil Shapiro 	{
72006f25ae9SGregory Neil Shapiro 		/* The file exists so an exclusive create would fail */
72106f25ae9SGregory Neil Shapiro 		errno = EEXIST;
72206f25ae9SGregory Neil Shapiro 		return -1;
72306f25ae9SGregory Neil Shapiro 	}
72406f25ae9SGregory Neil Shapiro 
72506f25ae9SGregory Neil Shapiro 	fd = dfopen(fn, omode, cmode, sff);
72606f25ae9SGregory Neil Shapiro 	if (fd < 0)
72706f25ae9SGregory Neil Shapiro 		return fd;
72806f25ae9SGregory Neil Shapiro 	if (filechanged(fn, fd, &stb))
72906f25ae9SGregory Neil Shapiro 	{
73006f25ae9SGregory Neil Shapiro 		syserr("554 5.3.0 cannot open: file %s changed after open", fn);
73106f25ae9SGregory Neil Shapiro 		(void) close(fd);
73206f25ae9SGregory Neil Shapiro 		errno = E_SM_FILECHANGE;
73306f25ae9SGregory Neil Shapiro 		return -1;
73406f25ae9SGregory Neil Shapiro 	}
73506f25ae9SGregory Neil Shapiro 	return fd;
73606f25ae9SGregory Neil Shapiro }
73706f25ae9SGregory Neil Shapiro /*
73806f25ae9SGregory Neil Shapiro **  SAFEFOPEN -- do a file open with extra checking
73906f25ae9SGregory Neil Shapiro **
74006f25ae9SGregory Neil Shapiro **	Parameters:
74106f25ae9SGregory Neil Shapiro **		fn -- the file name to open.
74206f25ae9SGregory Neil Shapiro **		omode -- the open-style mode flags.
74306f25ae9SGregory Neil Shapiro **		cmode -- the create-style mode flags.
74406f25ae9SGregory Neil Shapiro **		sff -- safefile flags.
74506f25ae9SGregory Neil Shapiro **
74606f25ae9SGregory Neil Shapiro **	Returns:
74706f25ae9SGregory Neil Shapiro **		Same as fopen.
74806f25ae9SGregory Neil Shapiro */
74906f25ae9SGregory Neil Shapiro 
75006f25ae9SGregory Neil Shapiro FILE *
75106f25ae9SGregory Neil Shapiro safefopen(fn, omode, cmode, sff)
75206f25ae9SGregory Neil Shapiro 	char *fn;
75306f25ae9SGregory Neil Shapiro 	int omode;
75406f25ae9SGregory Neil Shapiro 	int cmode;
75506f25ae9SGregory Neil Shapiro 	long sff;
75606f25ae9SGregory Neil Shapiro {
75706f25ae9SGregory Neil Shapiro 	int fd;
75806f25ae9SGregory Neil Shapiro 	int save_errno;
75906f25ae9SGregory Neil Shapiro 	FILE *fp;
76006f25ae9SGregory Neil Shapiro 	char *fmode;
76106f25ae9SGregory Neil Shapiro 
76206f25ae9SGregory Neil Shapiro 	switch (omode & O_ACCMODE)
76306f25ae9SGregory Neil Shapiro 	{
76406f25ae9SGregory Neil Shapiro 	  case O_RDONLY:
76506f25ae9SGregory Neil Shapiro 		fmode = "r";
76606f25ae9SGregory Neil Shapiro 		break;
76706f25ae9SGregory Neil Shapiro 
76806f25ae9SGregory Neil Shapiro 	  case O_WRONLY:
76906f25ae9SGregory Neil Shapiro 		if (bitset(O_APPEND, omode))
77006f25ae9SGregory Neil Shapiro 			fmode = "a";
77106f25ae9SGregory Neil Shapiro 		else
77206f25ae9SGregory Neil Shapiro 			fmode = "w";
77306f25ae9SGregory Neil Shapiro 		break;
77406f25ae9SGregory Neil Shapiro 
77506f25ae9SGregory Neil Shapiro 	  case O_RDWR:
77606f25ae9SGregory Neil Shapiro 		if (bitset(O_TRUNC, omode))
77706f25ae9SGregory Neil Shapiro 			fmode = "w+";
77806f25ae9SGregory Neil Shapiro 		else if (bitset(O_APPEND, omode))
77906f25ae9SGregory Neil Shapiro 			fmode = "a+";
78006f25ae9SGregory Neil Shapiro 		else
78106f25ae9SGregory Neil Shapiro 			fmode = "r+";
78206f25ae9SGregory Neil Shapiro 		break;
78306f25ae9SGregory Neil Shapiro 
78406f25ae9SGregory Neil Shapiro 	  default:
78506f25ae9SGregory Neil Shapiro 		syserr("554 5.3.5 safefopen: unknown omode %o", omode);
78606f25ae9SGregory Neil Shapiro 		fmode = "x";
78706f25ae9SGregory Neil Shapiro 	}
78806f25ae9SGregory Neil Shapiro 	fd = safeopen(fn, omode, cmode, sff);
78906f25ae9SGregory Neil Shapiro 	if (fd < 0)
79006f25ae9SGregory Neil Shapiro 	{
79106f25ae9SGregory Neil Shapiro 		save_errno = errno;
79206f25ae9SGregory Neil Shapiro 		if (tTd(44, 10))
79306f25ae9SGregory Neil Shapiro 			dprintf("safefopen: safeopen failed: %s\n",
79406f25ae9SGregory Neil Shapiro 				errstring(errno));
79506f25ae9SGregory Neil Shapiro 		errno = save_errno;
79606f25ae9SGregory Neil Shapiro 		return NULL;
79706f25ae9SGregory Neil Shapiro 	}
79806f25ae9SGregory Neil Shapiro 	fp = fdopen(fd, fmode);
79906f25ae9SGregory Neil Shapiro 	if (fp != NULL)
80006f25ae9SGregory Neil Shapiro 		return fp;
80106f25ae9SGregory Neil Shapiro 
80206f25ae9SGregory Neil Shapiro 	save_errno = errno;
80306f25ae9SGregory Neil Shapiro 	if (tTd(44, 10))
80406f25ae9SGregory Neil Shapiro 	{
80506f25ae9SGregory Neil Shapiro 		dprintf("safefopen: fdopen(%s, %s) failed: omode=%x, sff=%lx, err=%s\n",
80606f25ae9SGregory Neil Shapiro 			fn, fmode, omode, sff, errstring(errno));
80706f25ae9SGregory Neil Shapiro 	}
80806f25ae9SGregory Neil Shapiro 	(void) close(fd);
80906f25ae9SGregory Neil Shapiro 	errno = save_errno;
81006f25ae9SGregory Neil Shapiro 	return NULL;
81106f25ae9SGregory Neil Shapiro }
81206f25ae9SGregory Neil Shapiro /*
81306f25ae9SGregory Neil Shapiro **  FILECHANGED -- check to see if file changed after being opened
81406f25ae9SGregory Neil Shapiro **
81506f25ae9SGregory Neil Shapiro **	Parameters:
81606f25ae9SGregory Neil Shapiro **		fn -- pathname of file to check.
81706f25ae9SGregory Neil Shapiro **		fd -- file descriptor to check.
81806f25ae9SGregory Neil Shapiro **		stb -- stat structure from before open.
81906f25ae9SGregory Neil Shapiro **
82006f25ae9SGregory Neil Shapiro **	Returns:
82106f25ae9SGregory Neil Shapiro **		TRUE -- if a problem was detected.
82206f25ae9SGregory Neil Shapiro **		FALSE -- if this file is still the same.
82306f25ae9SGregory Neil Shapiro */
82406f25ae9SGregory Neil Shapiro 
82506f25ae9SGregory Neil Shapiro bool
82606f25ae9SGregory Neil Shapiro filechanged(fn, fd, stb)
82706f25ae9SGregory Neil Shapiro 	char *fn;
82806f25ae9SGregory Neil Shapiro 	int fd;
82906f25ae9SGregory Neil Shapiro 	struct stat *stb;
83006f25ae9SGregory Neil Shapiro {
83106f25ae9SGregory Neil Shapiro 	struct stat sta;
83206f25ae9SGregory Neil Shapiro 
83306f25ae9SGregory Neil Shapiro 	if (stb->st_mode == ST_MODE_NOFILE)
83406f25ae9SGregory Neil Shapiro 	{
83506f25ae9SGregory Neil Shapiro # if HASLSTAT && BOGUS_O_EXCL
83606f25ae9SGregory Neil Shapiro 		/* only necessary if exclusive open follows symbolic links */
83706f25ae9SGregory Neil Shapiro 		if (lstat(fn, stb) < 0 || stb->st_nlink != 1)
83806f25ae9SGregory Neil Shapiro 			return TRUE;
83906f25ae9SGregory Neil Shapiro # else /* HASLSTAT && BOGUS_O_EXCL */
84006f25ae9SGregory Neil Shapiro 		return FALSE;
84106f25ae9SGregory Neil Shapiro # endif /* HASLSTAT && BOGUS_O_EXCL */
84206f25ae9SGregory Neil Shapiro 	}
84306f25ae9SGregory Neil Shapiro 	if (fstat(fd, &sta) < 0)
84406f25ae9SGregory Neil Shapiro 		return TRUE;
84506f25ae9SGregory Neil Shapiro 
84606f25ae9SGregory Neil Shapiro 	if (sta.st_nlink != stb->st_nlink ||
84706f25ae9SGregory Neil Shapiro 	    sta.st_dev != stb->st_dev ||
84806f25ae9SGregory Neil Shapiro 	    sta.st_ino != stb->st_ino ||
84906f25ae9SGregory Neil Shapiro # if HAS_ST_GEN && 0		/* AFS returns garbage in st_gen */
85006f25ae9SGregory Neil Shapiro 	    sta.st_gen != stb->st_gen ||
85106f25ae9SGregory Neil Shapiro # endif /* HAS_ST_GEN && 0 */
85206f25ae9SGregory Neil Shapiro 	    sta.st_uid != stb->st_uid ||
85306f25ae9SGregory Neil Shapiro 	    sta.st_gid != stb->st_gid)
85406f25ae9SGregory Neil Shapiro 	{
85506f25ae9SGregory Neil Shapiro 		if (tTd(44, 8))
85606f25ae9SGregory Neil Shapiro 		{
85706f25ae9SGregory Neil Shapiro 			dprintf("File changed after opening:\n");
85806f25ae9SGregory Neil Shapiro 			dprintf(" nlink	= %ld/%ld\n",
85906f25ae9SGregory Neil Shapiro 				(long) stb->st_nlink, (long) sta.st_nlink);
86006f25ae9SGregory Neil Shapiro 			dprintf(" dev	= %ld/%ld\n",
86106f25ae9SGregory Neil Shapiro 				(long) stb->st_dev, (long) sta.st_dev);
86206f25ae9SGregory Neil Shapiro 			if (sizeof sta.st_ino > sizeof (long))
86306f25ae9SGregory Neil Shapiro 			{
86406f25ae9SGregory Neil Shapiro 				dprintf(" ino	= %s/",
86506f25ae9SGregory Neil Shapiro 					quad_to_string(stb->st_ino));
86606f25ae9SGregory Neil Shapiro 				dprintf("%s\n",
86706f25ae9SGregory Neil Shapiro 					quad_to_string(sta.st_ino));
86806f25ae9SGregory Neil Shapiro 			}
86906f25ae9SGregory Neil Shapiro 			else
87006f25ae9SGregory Neil Shapiro 				dprintf(" ino	= %lu/%lu\n",
87106f25ae9SGregory Neil Shapiro 					(unsigned long) stb->st_ino,
87206f25ae9SGregory Neil Shapiro 					(unsigned long) sta.st_ino);
87306f25ae9SGregory Neil Shapiro # if HAS_ST_GEN
87406f25ae9SGregory Neil Shapiro 			dprintf(" gen	= %ld/%ld\n",
87506f25ae9SGregory Neil Shapiro 				(long) stb->st_gen, (long) sta.st_gen);
87606f25ae9SGregory Neil Shapiro # endif /* HAS_ST_GEN */
87706f25ae9SGregory Neil Shapiro 			dprintf(" uid	= %ld/%ld\n",
87806f25ae9SGregory Neil Shapiro 				(long) stb->st_uid, (long) sta.st_uid);
87906f25ae9SGregory Neil Shapiro 			dprintf(" gid	= %ld/%ld\n",
88006f25ae9SGregory Neil Shapiro 				(long) stb->st_gid, (long) sta.st_gid);
88106f25ae9SGregory Neil Shapiro 		}
88206f25ae9SGregory Neil Shapiro 		return TRUE;
88306f25ae9SGregory Neil Shapiro 	}
88406f25ae9SGregory Neil Shapiro 
88506f25ae9SGregory Neil Shapiro 	return FALSE;
88606f25ae9SGregory Neil Shapiro }
88706f25ae9SGregory Neil Shapiro /*
88806f25ae9SGregory Neil Shapiro **  DFOPEN -- determined file open
88906f25ae9SGregory Neil Shapiro **
89006f25ae9SGregory Neil Shapiro **	This routine has the semantics of open, except that it will
89106f25ae9SGregory Neil Shapiro **	keep trying a few times to make this happen.  The idea is that
89206f25ae9SGregory Neil Shapiro **	on very loaded systems, we may run out of resources (inodes,
89306f25ae9SGregory Neil Shapiro **	whatever), so this tries to get around it.
89406f25ae9SGregory Neil Shapiro */
89506f25ae9SGregory Neil Shapiro 
89606f25ae9SGregory Neil Shapiro int
89706f25ae9SGregory Neil Shapiro dfopen(filename, omode, cmode, sff)
89806f25ae9SGregory Neil Shapiro 	char *filename;
89906f25ae9SGregory Neil Shapiro 	int omode;
90006f25ae9SGregory Neil Shapiro 	int cmode;
90106f25ae9SGregory Neil Shapiro 	long sff;
90206f25ae9SGregory Neil Shapiro {
90306f25ae9SGregory Neil Shapiro 	register int tries;
90406f25ae9SGregory Neil Shapiro 	int fd = -1;
90506f25ae9SGregory Neil Shapiro 	struct stat st;
90606f25ae9SGregory Neil Shapiro 
90706f25ae9SGregory Neil Shapiro 	for (tries = 0; tries < 10; tries++)
90806f25ae9SGregory Neil Shapiro 	{
90906f25ae9SGregory Neil Shapiro 		(void) sleep((unsigned) (10 * tries));
91006f25ae9SGregory Neil Shapiro 		errno = 0;
91106f25ae9SGregory Neil Shapiro 		fd = open(filename, omode, cmode);
91206f25ae9SGregory Neil Shapiro 		if (fd >= 0)
91306f25ae9SGregory Neil Shapiro 			break;
91406f25ae9SGregory Neil Shapiro 		switch (errno)
91506f25ae9SGregory Neil Shapiro 		{
91606f25ae9SGregory Neil Shapiro 		  case ENFILE:		/* system file table full */
91706f25ae9SGregory Neil Shapiro 		  case EINTR:		/* interrupted syscall */
91806f25ae9SGregory Neil Shapiro #ifdef ETXTBSY
91906f25ae9SGregory Neil Shapiro 		  case ETXTBSY:		/* Apollo: net file locked */
92006f25ae9SGregory Neil Shapiro #endif /* ETXTBSY */
92106f25ae9SGregory Neil Shapiro 			continue;
92206f25ae9SGregory Neil Shapiro 		}
92306f25ae9SGregory Neil Shapiro 		break;
92406f25ae9SGregory Neil Shapiro 	}
92506f25ae9SGregory Neil Shapiro 	if (!bitset(SFF_NOLOCK, sff) &&
92606f25ae9SGregory Neil Shapiro 	    fd >= 0 &&
92706f25ae9SGregory Neil Shapiro 	    fstat(fd, &st) >= 0 &&
92806f25ae9SGregory Neil Shapiro 	    S_ISREG(st.st_mode))
92906f25ae9SGregory Neil Shapiro 	{
93006f25ae9SGregory Neil Shapiro 		int locktype;
93106f25ae9SGregory Neil Shapiro 
93206f25ae9SGregory Neil Shapiro 		/* lock the file to avoid accidental conflicts */
93306f25ae9SGregory Neil Shapiro 		if ((omode & O_ACCMODE) != O_RDONLY)
93406f25ae9SGregory Neil Shapiro 			locktype = LOCK_EX;
93506f25ae9SGregory Neil Shapiro 		else
93606f25ae9SGregory Neil Shapiro 			locktype = LOCK_SH;
93706f25ae9SGregory Neil Shapiro 		if (!lockfile(fd, filename, NULL, locktype))
93806f25ae9SGregory Neil Shapiro 		{
93906f25ae9SGregory Neil Shapiro 			int save_errno = errno;
94006f25ae9SGregory Neil Shapiro 
94106f25ae9SGregory Neil Shapiro 			(void) close(fd);
94206f25ae9SGregory Neil Shapiro 			fd = -1;
94306f25ae9SGregory Neil Shapiro 			errno = save_errno;
94406f25ae9SGregory Neil Shapiro 		}
94506f25ae9SGregory Neil Shapiro 		else
94606f25ae9SGregory Neil Shapiro 			errno = 0;
94706f25ae9SGregory Neil Shapiro 	}
94806f25ae9SGregory Neil Shapiro 	return fd;
94906f25ae9SGregory Neil Shapiro }
950