164562Sgshapiro/*
2261363Sgshapiro * Copyright (c) 1998-2004 Proofpoint, 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
18266692SgshapiroSM_RCSID("@(#)$Id: safefile.c,v 8.130 2013-11-22 20:51:50 ca 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;
6198121Sgshapiro	char fbuf[MAXPATHLEN];
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))
307132943Sgshapiro			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;
40998121Sgshapiro	char s[MAXLINKPATHLEN];
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		{
48998121Sgshapiro			int linklen;
49064562Sgshapiro			char *target;
49198121Sgshapiro			char buf[MAXPATHLEN];
492141858Sgshapiro			char fullbuf[MAXLINKPATHLEN];
49364562Sgshapiro
49464562Sgshapiro			memset(buf, '\0', sizeof buf);
49598121Sgshapiro			linklen = readlink(s, buf, sizeof buf);
49698121Sgshapiro			if (linklen < 0)
49764562Sgshapiro			{
49864562Sgshapiro				ret = errno;
49964562Sgshapiro				break;
50064562Sgshapiro			}
50198121Sgshapiro			if (linklen >= sizeof buf)
50298121Sgshapiro			{
50398121Sgshapiro				/* file name too long for buffer */
50498121Sgshapiro				ret = errno = EINVAL;
50598121Sgshapiro				break;
50698121Sgshapiro			}
50764562Sgshapiro
50864562Sgshapiro			offset = 0;
50964562Sgshapiro			if (*buf == '/')
51064562Sgshapiro			{
51164562Sgshapiro				target = buf;
51264562Sgshapiro
51364562Sgshapiro				/* If path is the same, avoid rechecks */
51464562Sgshapiro				while (s[offset] == buf[offset] &&
51564562Sgshapiro				       s[offset] != '\0')
51664562Sgshapiro					offset++;
51764562Sgshapiro
51864562Sgshapiro				if (s[offset] == '\0' && buf[offset] == '\0')
51964562Sgshapiro				{
52064562Sgshapiro					/* strings match, symlink loop */
52164562Sgshapiro					return ELOOP;
52264562Sgshapiro				}
52364562Sgshapiro
52464562Sgshapiro				/* back off from the mismatch */
52564562Sgshapiro				if (offset > 0)
52664562Sgshapiro					offset--;
52764562Sgshapiro
52864562Sgshapiro				/* Make sure we are at a directory break */
52964562Sgshapiro				if (offset > 0 &&
53064562Sgshapiro				    s[offset] != '/' &&
53164562Sgshapiro				    s[offset] != '\0')
53264562Sgshapiro				{
53364562Sgshapiro					while (buf[offset] != '/' &&
53464562Sgshapiro					       offset > 0)
53564562Sgshapiro						offset--;
53664562Sgshapiro				}
53764562Sgshapiro				if (offset > 0 &&
53864562Sgshapiro				    s[offset] == '/' &&
53964562Sgshapiro				    buf[offset] == '/')
54064562Sgshapiro				{
54164562Sgshapiro					/* Include the trailing slash */
54264562Sgshapiro					offset++;
54364562Sgshapiro				}
54464562Sgshapiro			}
54564562Sgshapiro			else
54664562Sgshapiro			{
54764562Sgshapiro				char *sptr;
54864562Sgshapiro
54964562Sgshapiro				sptr = strrchr(s, '/');
55064562Sgshapiro				if (sptr != NULL)
55164562Sgshapiro				{
55264562Sgshapiro					*sptr = '\0';
55364562Sgshapiro					offset = sptr + 1 - s;
55490792Sgshapiro					if (sm_strlcpyn(fullbuf,
55590792Sgshapiro							sizeof fullbuf, 2,
55690792Sgshapiro							s, "/") >=
55790792Sgshapiro						sizeof fullbuf ||
55890792Sgshapiro					    sm_strlcat(fullbuf, buf,
55990792Sgshapiro						       sizeof fullbuf) >=
56090792Sgshapiro						sizeof fullbuf)
56164562Sgshapiro					{
56264562Sgshapiro						ret = EINVAL;
56364562Sgshapiro						break;
56464562Sgshapiro					}
56564562Sgshapiro					*sptr = '/';
56664562Sgshapiro				}
56764562Sgshapiro				else
56864562Sgshapiro				{
56990792Sgshapiro					if (sm_strlcpy(fullbuf, buf,
57090792Sgshapiro						       sizeof fullbuf) >=
57190792Sgshapiro						sizeof fullbuf)
57264562Sgshapiro					{
57364562Sgshapiro						ret = EINVAL;
57464562Sgshapiro						break;
57564562Sgshapiro					}
57664562Sgshapiro				}
57764562Sgshapiro				target = fullbuf;
57864562Sgshapiro			}
57964562Sgshapiro			ret = safedirpath(target, uid, gid, user, flags,
58064562Sgshapiro					  level + 1, offset);
58164562Sgshapiro			if (ret != 0)
58264562Sgshapiro				break;
58364562Sgshapiro
58464562Sgshapiro			/* Don't check permissions on the link file itself */
58564562Sgshapiro			continue;
58664562Sgshapiro		}
58764562Sgshapiro#endif /* S_ISLNK */
58864562Sgshapiro
58964562Sgshapiro		if ((uid == 0 || bitset(SFF_SAFEDIRPATH, flags)) &&
59064562Sgshapiro#ifdef S_ISVTX
59164562Sgshapiro		    !(bitnset(DBS_TRUSTSTICKYBIT, DontBlameSendmail) &&
59264562Sgshapiro		      bitset(S_ISVTX, stbuf.st_mode)) &&
59364562Sgshapiro#endif /* S_ISVTX */
59464562Sgshapiro		    bitset(mode, stbuf.st_mode))
59564562Sgshapiro		{
59664562Sgshapiro			if (tTd(44, 4))
59790792Sgshapiro				sm_dprintf("\t[dir %s] mode %lo ",
59890792Sgshapiro					s, (unsigned long) stbuf.st_mode);
59964562Sgshapiro			if (bitset(SFF_SAFEDIRPATH, flags))
60064562Sgshapiro			{
60164562Sgshapiro				if (bitset(S_IWOTH, stbuf.st_mode))
60264562Sgshapiro					ret = E_SM_WWDIR;
60364562Sgshapiro				else
60464562Sgshapiro					ret = E_SM_GWDIR;
60564562Sgshapiro				if (tTd(44, 4))
60690792Sgshapiro					sm_dprintf("FATAL\n");
60764562Sgshapiro				break;
60864562Sgshapiro			}
60964562Sgshapiro			if (tTd(44, 4))
61090792Sgshapiro				sm_dprintf("WARNING\n");
61164562Sgshapiro			if (Verbose > 1)
61264562Sgshapiro				message("051 WARNING: %s writable directory %s",
61364562Sgshapiro					bitset(S_IWOTH, stbuf.st_mode)
61464562Sgshapiro					   ? "World"
61564562Sgshapiro					   : "Group",
61664562Sgshapiro					s);
61764562Sgshapiro		}
61864562Sgshapiro		if (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags))
61964562Sgshapiro		{
62064562Sgshapiro			if (bitset(S_IXOTH, stbuf.st_mode))
62164562Sgshapiro				continue;
62264562Sgshapiro			ret = EACCES;
62364562Sgshapiro			break;
62464562Sgshapiro		}
62564562Sgshapiro
62664562Sgshapiro		/*
62764562Sgshapiro		**  Let OS determine access to file if we are not
62864562Sgshapiro		**  running as a privileged user.  This allows ACLs
62964562Sgshapiro		**  to work.  Also, if opening as root, assume we can
63064562Sgshapiro		**  scan the directory.
63164562Sgshapiro		*/
63264562Sgshapiro		if (geteuid() != 0 || bitset(SFF_OPENASROOT, flags))
63364562Sgshapiro			continue;
63464562Sgshapiro
63564562Sgshapiro		if (stbuf.st_uid == uid &&
63664562Sgshapiro		    bitset(S_IXUSR, stbuf.st_mode))
63764562Sgshapiro			continue;
63864562Sgshapiro		if (stbuf.st_gid == gid &&
63964562Sgshapiro		    bitset(S_IXGRP, stbuf.st_mode))
64064562Sgshapiro			continue;
64164562Sgshapiro# ifndef NO_GROUP_SET
64264562Sgshapiro		if (user != NULL && !DontInitGroups &&
64364562Sgshapiro		    ((gr != NULL && gr->gr_gid == stbuf.st_gid) ||
64464562Sgshapiro		     (gr = getgrgid(stbuf.st_gid)) != NULL))
64564562Sgshapiro		{
64664562Sgshapiro			register char **gp;
64764562Sgshapiro
64864562Sgshapiro			for (gp = gr->gr_mem; gp != NULL && *gp != NULL; gp++)
64964562Sgshapiro				if (strcmp(*gp, user) == 0)
65064562Sgshapiro					break;
65164562Sgshapiro			if (gp != NULL && *gp != NULL &&
65264562Sgshapiro			    bitset(S_IXGRP, stbuf.st_mode))
65364562Sgshapiro				continue;
65464562Sgshapiro		}
65564562Sgshapiro# endif /* ! NO_GROUP_SET */
65664562Sgshapiro		if (!bitset(S_IXOTH, stbuf.st_mode))
65764562Sgshapiro		{
65864562Sgshapiro			ret = EACCES;
65964562Sgshapiro			break;
66064562Sgshapiro		}
66164562Sgshapiro	}
66264562Sgshapiro	if (tTd(44, 4))
66390792Sgshapiro		sm_dprintf("\t[dir %s] %s\n", fn,
66490792Sgshapiro			ret == 0 ? "OK" : sm_errstring(ret));
66564562Sgshapiro	return ret;
66664562Sgshapiro}
66790792Sgshapiro/*
66864562Sgshapiro**  SAFEOPEN -- do a file open with extra checking
66964562Sgshapiro**
67064562Sgshapiro**	Parameters:
67164562Sgshapiro**		fn -- the file name to open.
67264562Sgshapiro**		omode -- the open-style mode flags.
67364562Sgshapiro**		cmode -- the create-style mode flags.
67464562Sgshapiro**		sff -- safefile flags.
67564562Sgshapiro**
67664562Sgshapiro**	Returns:
67764562Sgshapiro**		Same as open.
67864562Sgshapiro*/
67964562Sgshapiro
68064562Sgshapiroint
68164562Sgshapirosafeopen(fn, omode, cmode, sff)
68264562Sgshapiro	char *fn;
68364562Sgshapiro	int omode;
68464562Sgshapiro	int cmode;
68564562Sgshapiro	long sff;
68664562Sgshapiro{
687132943Sgshapiro#if !NOFTRUNCATE
688132943Sgshapiro	bool truncate;
689132943Sgshapiro#endif /* !NOFTRUNCATE */
69064562Sgshapiro	int rval;
69164562Sgshapiro	int fd;
69264562Sgshapiro	int smode;
69364562Sgshapiro	struct stat stb;
69464562Sgshapiro
69564562Sgshapiro	if (tTd(44, 10))
69690792Sgshapiro		sm_dprintf("safeopen: fn=%s, omode=%x, cmode=%x, sff=%lx\n",
69790792Sgshapiro			   fn, omode, cmode, sff);
69864562Sgshapiro
69964562Sgshapiro	if (bitset(O_CREAT, omode))
70064562Sgshapiro		sff |= SFF_CREAT;
70164562Sgshapiro	omode &= ~O_CREAT;
70264562Sgshapiro	switch (omode & O_ACCMODE)
70364562Sgshapiro	{
70464562Sgshapiro	  case O_RDONLY:
70564562Sgshapiro		smode = S_IREAD;
70664562Sgshapiro		break;
70764562Sgshapiro
70864562Sgshapiro	  case O_WRONLY:
70964562Sgshapiro		smode = S_IWRITE;
71064562Sgshapiro		break;
71164562Sgshapiro
71264562Sgshapiro	  case O_RDWR:
71364562Sgshapiro		smode = S_IREAD|S_IWRITE;
71464562Sgshapiro		break;
71564562Sgshapiro
71664562Sgshapiro	  default:
71764562Sgshapiro		smode = 0;
71864562Sgshapiro		break;
71964562Sgshapiro	}
72064562Sgshapiro	if (bitset(SFF_OPENASROOT, sff))
72164562Sgshapiro		rval = safefile(fn, RunAsUid, RunAsGid, RunAsUserName,
72264562Sgshapiro				sff, smode, &stb);
72364562Sgshapiro	else
72464562Sgshapiro		rval = safefile(fn, RealUid, RealGid, RealUserName,
72564562Sgshapiro				sff, smode, &stb);
72664562Sgshapiro	if (rval != 0)
72764562Sgshapiro	{
72864562Sgshapiro		errno = rval;
72964562Sgshapiro		return -1;
73064562Sgshapiro	}
73164562Sgshapiro	if (stb.st_mode == ST_MODE_NOFILE && bitset(SFF_CREAT, sff))
73264562Sgshapiro		omode |= O_CREAT | (bitset(SFF_NOTEXCL, sff) ? 0 : O_EXCL);
73364562Sgshapiro	else if (bitset(SFF_CREAT, sff) && bitset(O_EXCL, omode))
73464562Sgshapiro	{
73564562Sgshapiro		/* The file exists so an exclusive create would fail */
73664562Sgshapiro		errno = EEXIST;
73764562Sgshapiro		return -1;
73864562Sgshapiro	}
73964562Sgshapiro
740132943Sgshapiro#if !NOFTRUNCATE
741132943Sgshapiro	truncate = bitset(O_TRUNC, omode);
742132943Sgshapiro	if (truncate)
743132943Sgshapiro		omode &= ~O_TRUNC;
744132943Sgshapiro#endif /* !NOFTRUNCATE */
745132943Sgshapiro
74664562Sgshapiro	fd = dfopen(fn, omode, cmode, sff);
74764562Sgshapiro	if (fd < 0)
74864562Sgshapiro		return fd;
74964562Sgshapiro	if (filechanged(fn, fd, &stb))
75064562Sgshapiro	{
75164562Sgshapiro		syserr("554 5.3.0 cannot open: file %s changed after open", fn);
75264562Sgshapiro		(void) close(fd);
75364562Sgshapiro		errno = E_SM_FILECHANGE;
75464562Sgshapiro		return -1;
75564562Sgshapiro	}
756132943Sgshapiro
757132943Sgshapiro#if !NOFTRUNCATE
758132943Sgshapiro	if (truncate &&
759132943Sgshapiro	    ftruncate(fd, (off_t) 0) < 0)
760132943Sgshapiro	{
761132943Sgshapiro		int save_errno;
762132943Sgshapiro
763132943Sgshapiro		save_errno = errno;
764132943Sgshapiro		syserr("554 5.3.0 cannot open: file %s could not be truncated",
765132943Sgshapiro		       fn);
766132943Sgshapiro		(void) close(fd);
767132943Sgshapiro		errno = save_errno;
768132943Sgshapiro		return -1;
769132943Sgshapiro	}
770132943Sgshapiro#endif /* !NOFTRUNCATE */
771132943Sgshapiro
77264562Sgshapiro	return fd;
77364562Sgshapiro}
77490792Sgshapiro/*
77590792Sgshapiro**  SAFEFOPEN -- do a file open with extra checking
77690792Sgshapiro**
77790792Sgshapiro**	Parameters:
77890792Sgshapiro**		fn -- the file name to open.
77990792Sgshapiro**		omode -- the open-style mode flags.
78090792Sgshapiro**		cmode -- the create-style mode flags.
78190792Sgshapiro**		sff -- safefile flags.
78290792Sgshapiro**
78390792Sgshapiro**	Returns:
78490792Sgshapiro**		Same as fopen.
78590792Sgshapiro*/
78690792Sgshapiro
78790792SgshapiroSM_FILE_T *
78890792Sgshapirosafefopen(fn, omode, cmode, sff)
78990792Sgshapiro	char *fn;
79090792Sgshapiro	int omode;
79190792Sgshapiro	int cmode;
79290792Sgshapiro	long sff;
79390792Sgshapiro{
79490792Sgshapiro	int fd;
79590792Sgshapiro	int save_errno;
79690792Sgshapiro	SM_FILE_T *fp;
79790792Sgshapiro	int fmode;
79890792Sgshapiro
79990792Sgshapiro	switch (omode & O_ACCMODE)
80090792Sgshapiro	{
80190792Sgshapiro	  case O_RDONLY:
80290792Sgshapiro		fmode = SM_IO_RDONLY;
80390792Sgshapiro		break;
80490792Sgshapiro
80590792Sgshapiro	  case O_WRONLY:
80690792Sgshapiro		if (bitset(O_APPEND, omode))
80790792Sgshapiro			fmode = SM_IO_APPEND;
80890792Sgshapiro		else
80990792Sgshapiro			fmode = SM_IO_WRONLY;
81090792Sgshapiro		break;
81190792Sgshapiro
81290792Sgshapiro	  case O_RDWR:
81390792Sgshapiro		if (bitset(O_TRUNC, omode))
81490792Sgshapiro			fmode = SM_IO_RDWRTR;
81590792Sgshapiro		else if (bitset(O_APPEND, omode))
81690792Sgshapiro			fmode = SM_IO_APPENDRW;
81790792Sgshapiro		else
81890792Sgshapiro			fmode = SM_IO_RDWR;
81990792Sgshapiro		break;
82090792Sgshapiro
82190792Sgshapiro	  default:
82290792Sgshapiro		syserr("554 5.3.5 safefopen: unknown omode %o", omode);
82390792Sgshapiro		fmode = 0;
82490792Sgshapiro	}
82590792Sgshapiro	fd = safeopen(fn, omode, cmode, sff);
82690792Sgshapiro	if (fd < 0)
82790792Sgshapiro	{
82890792Sgshapiro		save_errno = errno;
82990792Sgshapiro		if (tTd(44, 10))
83090792Sgshapiro			sm_dprintf("safefopen: safeopen failed: %s\n",
83190792Sgshapiro				   sm_errstring(errno));
83290792Sgshapiro		errno = save_errno;
83390792Sgshapiro		return NULL;
83490792Sgshapiro	}
83590792Sgshapiro	fp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
83690792Sgshapiro			(void *) &fd, fmode, NULL);
83790792Sgshapiro	if (fp != NULL)
83890792Sgshapiro		return fp;
83990792Sgshapiro
84090792Sgshapiro	save_errno = errno;
84190792Sgshapiro	if (tTd(44, 10))
84290792Sgshapiro	{
84390792Sgshapiro		sm_dprintf("safefopen: fdopen(%s, %d) failed: omode=%x, sff=%lx, err=%s\n",
84490792Sgshapiro			   fn, fmode, omode, sff, sm_errstring(errno));
84590792Sgshapiro	}
84690792Sgshapiro	(void) close(fd);
84790792Sgshapiro	errno = save_errno;
84890792Sgshapiro	return NULL;
84990792Sgshapiro}
85090792Sgshapiro/*
85164562Sgshapiro**  FILECHANGED -- check to see if file changed after being opened
85264562Sgshapiro**
85364562Sgshapiro**	Parameters:
85464562Sgshapiro**		fn -- pathname of file to check.
85564562Sgshapiro**		fd -- file descriptor to check.
85664562Sgshapiro**		stb -- stat structure from before open.
85764562Sgshapiro**
85864562Sgshapiro**	Returns:
85990792Sgshapiro**		true -- if a problem was detected.
86090792Sgshapiro**		false -- if this file is still the same.
86164562Sgshapiro*/
86264562Sgshapiro
86364562Sgshapirobool
86464562Sgshapirofilechanged(fn, fd, stb)
86564562Sgshapiro	char *fn;
86664562Sgshapiro	int fd;
86764562Sgshapiro	struct stat *stb;
86864562Sgshapiro{
86964562Sgshapiro	struct stat sta;
87064562Sgshapiro
87164562Sgshapiro	if (stb->st_mode == ST_MODE_NOFILE)
87264562Sgshapiro	{
87364562Sgshapiro# if HASLSTAT && BOGUS_O_EXCL
87464562Sgshapiro		/* only necessary if exclusive open follows symbolic links */
87564562Sgshapiro		if (lstat(fn, stb) < 0 || stb->st_nlink != 1)
87690792Sgshapiro			return true;
87764562Sgshapiro# else /* HASLSTAT && BOGUS_O_EXCL */
87890792Sgshapiro		return false;
87964562Sgshapiro# endif /* HASLSTAT && BOGUS_O_EXCL */
88064562Sgshapiro	}
88164562Sgshapiro	if (fstat(fd, &sta) < 0)
88290792Sgshapiro		return true;
88364562Sgshapiro
88464562Sgshapiro	if (sta.st_nlink != stb->st_nlink ||
88564562Sgshapiro	    sta.st_dev != stb->st_dev ||
88664562Sgshapiro	    sta.st_ino != stb->st_ino ||
88764562Sgshapiro# if HAS_ST_GEN && 0		/* AFS returns garbage in st_gen */
88864562Sgshapiro	    sta.st_gen != stb->st_gen ||
88964562Sgshapiro# endif /* HAS_ST_GEN && 0 */
89064562Sgshapiro	    sta.st_uid != stb->st_uid ||
89164562Sgshapiro	    sta.st_gid != stb->st_gid)
89264562Sgshapiro	{
89364562Sgshapiro		if (tTd(44, 8))
89464562Sgshapiro		{
89590792Sgshapiro			sm_dprintf("File changed after opening:\n");
89690792Sgshapiro			sm_dprintf(" nlink	= %ld/%ld\n",
89764562Sgshapiro				(long) stb->st_nlink, (long) sta.st_nlink);
89890792Sgshapiro			sm_dprintf(" dev	= %ld/%ld\n",
89964562Sgshapiro				(long) stb->st_dev, (long) sta.st_dev);
90090792Sgshapiro			sm_dprintf(" ino	= %llu/%llu\n",
90190792Sgshapiro				(ULONGLONG_T) stb->st_ino,
90290792Sgshapiro				(ULONGLONG_T) sta.st_ino);
90364562Sgshapiro# if HAS_ST_GEN
90490792Sgshapiro			sm_dprintf(" gen	= %ld/%ld\n",
90564562Sgshapiro				(long) stb->st_gen, (long) sta.st_gen);
90664562Sgshapiro# endif /* HAS_ST_GEN */
90790792Sgshapiro			sm_dprintf(" uid	= %ld/%ld\n",
90864562Sgshapiro				(long) stb->st_uid, (long) sta.st_uid);
90990792Sgshapiro			sm_dprintf(" gid	= %ld/%ld\n",
91064562Sgshapiro				(long) stb->st_gid, (long) sta.st_gid);
91164562Sgshapiro		}
91290792Sgshapiro		return true;
91364562Sgshapiro	}
91464562Sgshapiro
91590792Sgshapiro	return false;
91664562Sgshapiro}
91790792Sgshapiro/*
91864562Sgshapiro**  DFOPEN -- determined file open
91964562Sgshapiro**
92064562Sgshapiro**	This routine has the semantics of open, except that it will
92164562Sgshapiro**	keep trying a few times to make this happen.  The idea is that
92264562Sgshapiro**	on very loaded systems, we may run out of resources (inodes,
92364562Sgshapiro**	whatever), so this tries to get around it.
92464562Sgshapiro*/
92564562Sgshapiro
92664562Sgshapiroint
92764562Sgshapirodfopen(filename, omode, cmode, sff)
92864562Sgshapiro	char *filename;
92964562Sgshapiro	int omode;
93064562Sgshapiro	int cmode;
93164562Sgshapiro	long sff;
93264562Sgshapiro{
93364562Sgshapiro	register int tries;
93464562Sgshapiro	int fd = -1;
93564562Sgshapiro	struct stat st;
93664562Sgshapiro
93764562Sgshapiro	for (tries = 0; tries < 10; tries++)
93864562Sgshapiro	{
93964562Sgshapiro		(void) sleep((unsigned) (10 * tries));
94064562Sgshapiro		errno = 0;
94164562Sgshapiro		fd = open(filename, omode, cmode);
94264562Sgshapiro		if (fd >= 0)
94364562Sgshapiro			break;
94464562Sgshapiro		switch (errno)
94564562Sgshapiro		{
94664562Sgshapiro		  case ENFILE:		/* system file table full */
94764562Sgshapiro		  case EINTR:		/* interrupted syscall */
94864562Sgshapiro#ifdef ETXTBSY
94964562Sgshapiro		  case ETXTBSY:		/* Apollo: net file locked */
95064562Sgshapiro#endif /* ETXTBSY */
95164562Sgshapiro			continue;
95264562Sgshapiro		}
95364562Sgshapiro		break;
95464562Sgshapiro	}
95564562Sgshapiro	if (!bitset(SFF_NOLOCK, sff) &&
95664562Sgshapiro	    fd >= 0 &&
95764562Sgshapiro	    fstat(fd, &st) >= 0 &&
95864562Sgshapiro	    S_ISREG(st.st_mode))
95964562Sgshapiro	{
96064562Sgshapiro		int locktype;
96164562Sgshapiro
96264562Sgshapiro		/* lock the file to avoid accidental conflicts */
96364562Sgshapiro		if ((omode & O_ACCMODE) != O_RDONLY)
96464562Sgshapiro			locktype = LOCK_EX;
96564562Sgshapiro		else
96664562Sgshapiro			locktype = LOCK_SH;
967132943Sgshapiro		if (bitset(SFF_NBLOCK, sff))
968132943Sgshapiro			locktype |= LOCK_NB;
969132943Sgshapiro
97064562Sgshapiro		if (!lockfile(fd, filename, NULL, locktype))
97164562Sgshapiro		{
97264562Sgshapiro			int save_errno = errno;
97364562Sgshapiro
97464562Sgshapiro			(void) close(fd);
97564562Sgshapiro			fd = -1;
97664562Sgshapiro			errno = save_errno;
97764562Sgshapiro		}
97864562Sgshapiro		else
97964562Sgshapiro			errno = 0;
98064562Sgshapiro	}
98164562Sgshapiro	return fd;
98264562Sgshapiro}
983