safefile.c revision 66494
1198090Srdivacky/*
2198090Srdivacky * Copyright (c) 1998-2000 Sendmail, Inc. and its suppliers.
3353358Sdim *	All rights reserved.
4353358Sdim * Copyright (c) 1983, 1995-1997 Eric P. Allman.  All rights reserved.
5353358Sdim * Copyright (c) 1988, 1993
6198090Srdivacky *	The Regents of the University of California.  All rights reserved.
7198090Srdivacky *
8198090Srdivacky * By using this file, you agree to the terms and conditions set
9198090Srdivacky * forth in the LICENSE file which can be found at the top level of
10198090Srdivacky * the sendmail distribution.
11198090Srdivacky *
12198090Srdivacky */
13198090Srdivacky
14341825Sdim#ifndef lint
15321369Sdimstatic char id[] = "@(#)$Id: safefile.c,v 8.81.4.7 2000/09/01 21:09:23 ca Exp $";
16249423Sdim#endif /* ! lint */
17249423Sdim
18249423Sdim#include <sendmail.h>
19249423Sdim
20249423Sdim
21276479Sdim/*
22198090Srdivacky**  SAFEFILE -- return 0 if a file exists and is safe for a user.
23198090Srdivacky**
24205218Srdivacky**	Parameters:
25202375Srdivacky**		fn -- filename to check.
26249423Sdim**		uid -- user id to compare against.
27249423Sdim**		gid -- group id to compare against.
28198090Srdivacky**		user -- user name to compare against (used for group
29198090Srdivacky**			sets).
30198090Srdivacky**		flags -- modifiers:
31198090Srdivacky**			SFF_MUSTOWN -- "uid" must own this file.
32198090Srdivacky**			SFF_NOSLINK -- file cannot be a symbolic link.
33198090Srdivacky**		mode -- mode bits that must match.
34198090Srdivacky**		st -- if set, points to a stat structure that will
35198090Srdivacky**			get the stat info for the file.
36226633Sdim**
37226633Sdim**	Returns:
38226633Sdim**		0 if fn exists, is owned by uid, and matches mode.
39226633Sdim**		An errno otherwise.  The actual errno is cleared.
40226633Sdim**
41226633Sdim**	Side Effects:
42314564Sdim**		none.
43321369Sdim*/
44314564Sdim
45327952Sdimint
46327952Sdimsafefile(fn, uid, gid, user, flags, mode, st)
47344779Sdim	char *fn;
48344779Sdim	UID_T uid;
49344779Sdim	GID_T gid;
50353358Sdim	char *user;
51198090Srdivacky	long flags;
52276479Sdim	int mode;
53198090Srdivacky	struct stat *st;
54314564Sdim{
55198090Srdivacky	register char *p;
56198090Srdivacky	register struct group *gr = NULL;
57341825Sdim	int file_errno = 0;
58341825Sdim	bool checkpath;
59341825Sdim	struct stat stbuf;
60341825Sdim	struct stat fstbuf;
61341825Sdim	char fbuf[MAXPATHLEN + 1];
62341825Sdim
63341825Sdim	if (tTd(44, 4))
64341825Sdim		dprintf("safefile(%s, uid=%d, gid=%d, flags=%lx, mode=%o):\n",
65341825Sdim			fn, (int) uid, (int) gid, flags, mode);
66341825Sdim	errno = 0;
67341825Sdim	if (st == NULL)
68341825Sdim		st = &fstbuf;
69341825Sdim	if (strlcpy(fbuf, fn, sizeof fbuf) >= sizeof fbuf)
70341825Sdim	{
71224145Sdim		if (tTd(44, 4))
72198090Srdivacky			dprintf("\tpathname too long\n");
73198090Srdivacky		return ENAMETOOLONG;
74341825Sdim	}
75198090Srdivacky	fn = fbuf;
76198090Srdivacky
77198090Srdivacky	/* ignore SFF_SAFEDIRPATH if we are debugging */
78198090Srdivacky	if (RealUid != 0 && RunAsUid == RealUid)
79198090Srdivacky		flags &= ~SFF_SAFEDIRPATH;
80198090Srdivacky
81198090Srdivacky	/* first check to see if the file exists at all */
82276479Sdim# if HASLSTAT
83198090Srdivacky	if ((bitset(SFF_NOSLINK, flags) ? lstat(fn, st)
84198090Srdivacky					: stat(fn, st)) < 0)
85198090Srdivacky# else /* HASLSTAT */
86198090Srdivacky	if (stat(fn, st) < 0)
87198090Srdivacky# endif /* HASLSTAT */
88198090Srdivacky	{
89198090Srdivacky		file_errno = errno;
90198090Srdivacky	}
91234353Sdim	else if (bitset(SFF_SETUIDOK, flags) &&
92234353Sdim		 !bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode) &&
93198090Srdivacky		 S_ISREG(st->st_mode))
94234353Sdim	{
95234353Sdim		/*
96234353Sdim		**  If final file is setuid, run as the owner of that
97234353Sdim		**  file.  Gotta be careful not to reveal anything too
98341825Sdim		**  soon here!
99234353Sdim		*/
100198090Srdivacky
101341825Sdim# ifdef SUID_ROOT_FILES_OK
102198090Srdivacky		if (bitset(S_ISUID, st->st_mode))
103234353Sdim# else /* SUID_ROOT_FILES_OK */
104234353Sdim		if (bitset(S_ISUID, st->st_mode) && st->st_uid != 0 &&
105198090Srdivacky		    st->st_uid != TrustedUid)
106198090Srdivacky# endif /* SUID_ROOT_FILES_OK */
107198090Srdivacky		{
108198090Srdivacky			uid = st->st_uid;
109198090Srdivacky			user = NULL;
110198090Srdivacky		}
111234353Sdim# ifdef SUID_ROOT_FILES_OK
112198090Srdivacky		if (bitset(S_ISGID, st->st_mode))
113198090Srdivacky# else /* SUID_ROOT_FILES_OK */
114198090Srdivacky		if (bitset(S_ISGID, st->st_mode) && st->st_gid != 0)
115198090Srdivacky# endif /* SUID_ROOT_FILES_OK */
116276479Sdim			gid = st->st_gid;
117314564Sdim	}
118276479Sdim
119276479Sdim	checkpath = !bitset(SFF_NOPATHCHECK, flags) ||
120261991Sdim		    (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags));
121296417Sdim	if (bitset(SFF_NOWLINK, flags) && !bitset(SFF_SAFEDIRPATH, flags))
122314564Sdim	{
123276479Sdim		int ret;
124288943Sdim
125261991Sdim		/* check the directory */
126261991Sdim		p = strrchr(fn, '/');
127276479Sdim		if (p == NULL)
128314564Sdim		{
129276479Sdim			ret = safedirpath(".", uid, gid, user,
130314564Sdim					  flags|SFF_SAFEDIRPATH, 0, 0);
131221345Sdim		}
132221345Sdim		else
133221345Sdim		{
134296417Sdim			*p = '\0';
135221345Sdim			ret = safedirpath(fn, uid, gid, user,
136221345Sdim					  flags|SFF_SAFEDIRPATH, 0, 0);
137221345Sdim			*p = '/';
138221345Sdim		}
139198090Srdivacky		if (ret == 0)
140341825Sdim		{
141341825Sdim			/* directory is safe */
142341825Sdim			checkpath = FALSE;
143314564Sdim		}
144198090Srdivacky		else
145314564Sdim		{
146198090Srdivacky# if HASLSTAT
147198090Srdivacky			/* Need lstat() information if called stat() before */
148341825Sdim			if (!bitset(SFF_NOSLINK, flags) && lstat(fn, st) < 0)
149341825Sdim			{
150198090Srdivacky				ret = errno;
151198090Srdivacky				if (tTd(44, 4))
152341825Sdim					dprintf("\t%s\n", errstring(ret));
153341825Sdim				return ret;
154341825Sdim			}
155198090Srdivacky# endif /* HASLSTAT */
156198090Srdivacky			/* directory is writable: disallow links */
157341825Sdim			flags |= SFF_NOLINK;
158198090Srdivacky		}
159198090Srdivacky	}
160198090Srdivacky
161198090Srdivacky	if (checkpath)
162202878Srdivacky	{
163202878Srdivacky		int ret;
164202878Srdivacky
165202878Srdivacky		p = strrchr(fn, '/');
166341825Sdim		if (p == NULL)
167341825Sdim		{
168341825Sdim			ret = safedirpath(".", uid, gid, user, flags, 0, 0);
169202878Srdivacky		}
170202878Srdivacky		else
171202878Srdivacky		{
172202878Srdivacky			*p = '\0';
173198090Srdivacky			ret = safedirpath(fn, uid, gid, user, flags, 0, 0);
174202878Srdivacky			*p = '/';
175198090Srdivacky		}
176198090Srdivacky		if (ret != 0)
177198090Srdivacky			return ret;
178198090Srdivacky	}
179198090Srdivacky
180239462Sdim	/*
181198090Srdivacky	**  If the target file doesn't exist, check the directory to
182341825Sdim	**  ensure that it is writable by this user.
183296417Sdim	*/
184218893Sdim
185218893Sdim	if (file_errno != 0)
186218893Sdim	{
187309124Sdim		int ret = file_errno;
188218893Sdim		char *dir = fn;
189296417Sdim
190198090Srdivacky		if (tTd(44, 4))
191198090Srdivacky			dprintf("\t%s\n", errstring(ret));
192226633Sdim
193226633Sdim		errno = 0;
194198090Srdivacky		if (!bitset(SFF_CREAT, flags) || file_errno != ENOENT)
195198090Srdivacky			return ret;
196198090Srdivacky
197198090Srdivacky		/* check to see if legal to create the file */
198198090Srdivacky		p = strrchr(dir, '/');
199198090Srdivacky		if (p == NULL)
200198090Srdivacky			dir = ".";
201198090Srdivacky		else if (p == dir)
202198090Srdivacky			dir = "/";
203198090Srdivacky		else
204198090Srdivacky			*p = '\0';
205198090Srdivacky		if (stat(dir, &stbuf) >= 0)
206198090Srdivacky		{
207198090Srdivacky			int md = S_IWRITE|S_IEXEC;
208198090Srdivacky
209198090Srdivacky			if (stbuf.st_uid == uid)
210198090Srdivacky				/* EMPTY */
211198090Srdivacky				;
212314564Sdim			else if (uid == 0 && stbuf.st_uid == TrustedUid)
213314564Sdim				/* EMPTY */
214198090Srdivacky				;
215198090Srdivacky			else
216198090Srdivacky			{
217309124Sdim				md >>= 3;
218288943Sdim				if (stbuf.st_gid == gid)
219288943Sdim					/* EMPTY */
220198090Srdivacky					;
221198090Srdivacky# ifndef NO_GROUP_SET
222296417Sdim				else if (user != NULL && !DontInitGroups &&
223314564Sdim					 ((gr != NULL &&
224314564Sdim					   gr->gr_gid == stbuf.st_gid) ||
225314564Sdim					  (gr = getgrgid(stbuf.st_gid)) != NULL))
226314564Sdim				{
227314564Sdim					register char **gp;
228341825Sdim
229314564Sdim					for (gp = gr->gr_mem; *gp != NULL; gp++)
230314564Sdim						if (strcmp(*gp, user) == 0)
231198090Srdivacky							break;
232198090Srdivacky					if (*gp == NULL)
233198090Srdivacky						md >>= 3;
234198090Srdivacky				}
235198090Srdivacky# endif /* ! NO_GROUP_SET */
236198090Srdivacky				else
237198090Srdivacky					md >>= 3;
238198090Srdivacky			}
239309124Sdim			if ((stbuf.st_mode & md) != md)
240296417Sdim				errno = EACCES;
241198090Srdivacky		}
242198090Srdivacky		ret = errno;
243288943Sdim		if (tTd(44, 4))
244288943Sdim			dprintf("\t[final dir %s uid %d mode %lo] %s\n",
245288943Sdim				dir, (int) stbuf.st_uid, (u_long) stbuf.st_mode,
246314564Sdim				errstring(ret));
247314564Sdim		if (p != NULL)
248198090Srdivacky			*p = '/';
249314564Sdim		st->st_mode = ST_MODE_NOFILE;
250314564Sdim		return ret;
251198090Srdivacky	}
252321369Sdim
253321369Sdim# ifdef S_ISLNK
254321369Sdim	if (bitset(SFF_NOSLINK, flags) && S_ISLNK(st->st_mode))
255321369Sdim	{
256360784Sdim		if (tTd(44, 4))
257321369Sdim			dprintf("\t[slink mode %lo]\tE_SM_NOSLINK\n",
258321369Sdim				(u_long) st->st_mode);
259321369Sdim		return E_SM_NOSLINK;
260321369Sdim	}
261321369Sdim# endif /* S_ISLNK */
262321369Sdim	if (bitset(SFF_REGONLY, flags) && !S_ISREG(st->st_mode))
263321369Sdim	{
264321369Sdim		if (tTd(44, 4))
265321369Sdim			dprintf("\t[non-reg mode %lo]\tE_SM_REGONLY\n",
266321369Sdim				(u_long) st->st_mode);
267198090Srdivacky		return E_SM_REGONLY;
268314564Sdim	}
269198090Srdivacky	if (bitset(SFF_NOGWFILES, flags) &&
270198090Srdivacky	    bitset(S_IWGRP, st->st_mode))
271288943Sdim	{
272314564Sdim		if (tTd(44, 4))
273309124Sdim			dprintf("\t[write bits %lo]\tE_SM_GWFILE\n",
274296417Sdim				(u_long) st->st_mode);
275309124Sdim		return E_SM_GWFILE;
276309124Sdim	}
277288943Sdim	if (bitset(SFF_NOWWFILES, flags) &&
278288943Sdim	    bitset(S_IWOTH, st->st_mode))
279288943Sdim	{
280288943Sdim		if (tTd(44, 4))
281288943Sdim			dprintf("\t[write bits %lo]\tE_SM_WWFILE\n",
282288943Sdim				(u_long) st->st_mode);
283288943Sdim		return E_SM_WWFILE;
284288943Sdim	}
285288943Sdim	if (bitset(SFF_NOGRFILES, flags) && bitset(S_IRGRP, st->st_mode))
286288943Sdim	{
287288943Sdim		if (tTd(44, 4))
288288943Sdim			dprintf("\t[read bits %lo]\tE_SM_GRFILE\n",
289288943Sdim				(u_long) st->st_mode);
290288943Sdim		return E_SM_GRFILE;
291321369Sdim	}
292288943Sdim	if (bitset(SFF_NOWRFILES, flags) && bitset(S_IROTH, st->st_mode))
293288943Sdim	{
294288943Sdim		if (tTd(44, 4))
295288943Sdim			dprintf("\t[read bits %lo]\tE_SM_WRFILE\n",
296296417Sdim				(u_long) st->st_mode);
297309124Sdim		return E_SM_WRFILE;
298309124Sdim	}
299276479Sdim	if (!bitset(SFF_EXECOK, flags) &&
300198090Srdivacky	    bitset(S_IWUSR|S_IWGRP|S_IWOTH, mode) &&
301198090Srdivacky	    bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode))
302198090Srdivacky	{
303198090Srdivacky		if (tTd(44, 4))
304198090Srdivacky			dprintf("\t[exec bits %lo]\tE_SM_ISEXEC]\n",
305249423Sdim				(u_long) st->st_mode);
306203954Srdivacky		return E_SM_ISEXEC;
307203954Srdivacky	}
308276479Sdim	if (bitset(SFF_NOHLINK, flags) && st->st_nlink != 1)
309314564Sdim	{
310314564Sdim		if (tTd(44, 4))
311249423Sdim			dprintf("\t[link count %d]\tE_SM_NOHLINK\n",
312314564Sdim				(int) st->st_nlink);
313249423Sdim		return E_SM_NOHLINK;
314249423Sdim	}
315198090Srdivacky
316198090Srdivacky	if (uid == 0 && bitset(SFF_OPENASROOT, flags))
317203954Srdivacky		/* EMPTY */
318249423Sdim		;
319249423Sdim	else if (uid == 0 && !bitset(SFF_ROOTOK, flags))
320221345Sdim		mode >>= 6;
321203954Srdivacky	else if (st->st_uid == uid)
322207618Srdivacky		/* EMPTY */
323203954Srdivacky		;
324203954Srdivacky	else if (uid == 0 && st->st_uid == TrustedUid)
325249423Sdim		/* EMPTY */
326205218Srdivacky		;
327205218Srdivacky	else
328205218Srdivacky	{
329288943Sdim		mode >>= 3;
330205218Srdivacky		if (st->st_gid == gid)
331288943Sdim			/* EMPTY */
332288943Sdim			;
333198090Srdivacky# ifndef NO_GROUP_SET
334205218Srdivacky		else if (user != NULL && !DontInitGroups &&
335198090Srdivacky			 ((gr != NULL && gr->gr_gid == st->st_gid) ||
336261991Sdim			  (gr = getgrgid(st->st_gid)) != NULL))
337261991Sdim		{
338261991Sdim			register char **gp;
339261991Sdim
340288943Sdim			for (gp = gr->gr_mem; *gp != NULL; gp++)
341261991Sdim				if (strcmp(*gp, user) == 0)
342288943Sdim					break;
343288943Sdim			if (*gp == NULL)
344314564Sdim				mode >>= 3;
345296417Sdim		}
346314564Sdim# endif /* ! NO_GROUP_SET */
347288943Sdim		else
348			mode >>= 3;
349	}
350	if (tTd(44, 4))
351		dprintf("\t[uid %d, nlink %d, stat %lo, mode %lo] ",
352			(int) st->st_uid, (int) st->st_nlink,
353			(u_long) st->st_mode, (u_long) mode);
354	if ((st->st_uid == uid || st->st_uid == 0 ||
355	     st->st_uid == TrustedUid ||
356	     !bitset(SFF_MUSTOWN, flags)) &&
357	    (st->st_mode & mode) == mode)
358	{
359		if (tTd(44, 4))
360			dprintf("\tOK\n");
361		return 0;
362	}
363	if (tTd(44, 4))
364		dprintf("\tEACCES\n");
365	return EACCES;
366}
367/*
368**  SAFEDIRPATH -- check to make sure a path to a directory is safe
369**
370**	Safe means not writable and owned by the right folks.
371**
372**	Parameters:
373**		fn -- filename to check.
374**		uid -- user id to compare against.
375**		gid -- group id to compare against.
376**		user -- user name to compare against (used for group
377**			sets).
378**		flags -- modifiers:
379**			SFF_ROOTOK -- ok to use root permissions to open.
380**			SFF_SAFEDIRPATH -- writable directories are considered
381**				to be fatal errors.
382**		level -- symlink recursive level.
383**		offset -- offset into fn to start checking from.
384**
385**	Returns:
386**		0 -- if the directory path is "safe".
387**		else -- an error number associated with the path.
388*/
389
390int
391safedirpath(fn, uid, gid, user, flags, level, offset)
392	char *fn;
393	UID_T uid;
394	GID_T gid;
395	char *user;
396	long flags;
397	int level;
398	int offset;
399{
400	int ret = 0;
401	int mode = S_IWOTH;
402	char save = '\0';
403	char *saveptr = NULL;
404	char *p, *enddir;
405	register struct group *gr = NULL;
406	char s[MAXLINKPATHLEN + 1];
407	struct stat stbuf;
408
409	/* make sure we aren't in a symlink loop */
410	if (level > MAXSYMLINKS)
411		return ELOOP;
412
413	/* special case root directory */
414	if (*fn == '\0')
415		fn = "/";
416
417	if (tTd(44, 4))
418		dprintf("safedirpath(%s, uid=%ld, gid=%ld, flags=%lx, level=%d, offset=%d):\n",
419			fn, (long) uid, (long) gid, flags, level, offset);
420
421	if (!bitnset(DBS_GROUPWRITABLEDIRPATHSAFE, DontBlameSendmail))
422		mode |= S_IWGRP;
423
424	/* Make a modifiable copy of the filename */
425	if (strlcpy(s, fn, sizeof s) >= sizeof s)
426		return EINVAL;
427
428	p = s + offset;
429	while (p != NULL)
430	{
431		/* put back character */
432		if (saveptr != NULL)
433		{
434			*saveptr = save;
435			saveptr = NULL;
436			p++;
437		}
438
439		if (*p == '\0')
440			break;
441
442		p = strchr(p, '/');
443
444		/* Special case for root directory */
445		if (p == s)
446		{
447			save = *(p + 1);
448			saveptr = p + 1;
449			*(p + 1) = '\0';
450		}
451		else if (p != NULL)
452		{
453			save = *p;
454			saveptr = p;
455			*p = '\0';
456		}
457
458		/* Heuristic: . and .. have already been checked */
459		enddir = strrchr(s, '/');
460		if (enddir != NULL &&
461		    (strcmp(enddir, "/..") == 0 ||
462		     strcmp(enddir, "/.") == 0))
463			continue;
464
465		if (tTd(44, 20))
466			dprintf("\t[dir %s]\n", s);
467
468# if HASLSTAT
469		ret = lstat(s, &stbuf);
470# else /* HASLSTAT */
471		ret = stat(s, &stbuf);
472# endif /* HASLSTAT */
473		if (ret < 0)
474		{
475			ret = errno;
476			break;
477		}
478
479# ifdef S_ISLNK
480		/* Follow symlinks */
481		if (S_ISLNK(stbuf.st_mode))
482		{
483			char *target;
484			char buf[MAXPATHLEN + 1];
485
486			memset(buf, '\0', sizeof buf);
487			if (readlink(s, buf, sizeof buf) < 0)
488			{
489				ret = errno;
490				break;
491			}
492
493			offset = 0;
494			if (*buf == '/')
495			{
496				target = buf;
497
498				/* If path is the same, avoid rechecks */
499				while (s[offset] == buf[offset] &&
500				       s[offset] != '\0')
501					offset++;
502
503				if (s[offset] == '\0' && buf[offset] == '\0')
504				{
505					/* strings match, symlink loop */
506					return ELOOP;
507				}
508
509				/* back off from the mismatch */
510				if (offset > 0)
511					offset--;
512
513				/* Make sure we are at a directory break */
514				if (offset > 0 &&
515				    s[offset] != '/' &&
516				    s[offset] != '\0')
517				{
518					while (buf[offset] != '/' &&
519					       offset > 0)
520						offset--;
521				}
522				if (offset > 0 &&
523				    s[offset] == '/' &&
524				    buf[offset] == '/')
525				{
526					/* Include the trailing slash */
527					offset++;
528				}
529			}
530			else
531			{
532				char *sptr;
533				char fullbuf[MAXLINKPATHLEN + 1];
534
535				sptr = strrchr(s, '/');
536				if (sptr != NULL)
537				{
538					*sptr = '\0';
539					offset = sptr + 1 - s;
540					if ((strlen(s) + 1 +
541					     strlen(buf) + 1) > sizeof fullbuf)
542					{
543						ret = EINVAL;
544						break;
545					}
546					snprintf(fullbuf, sizeof fullbuf,
547						 "%s/%s", s, buf);
548					*sptr = '/';
549				}
550				else
551				{
552					if (strlen(buf) + 1 > sizeof fullbuf)
553					{
554						ret = EINVAL;
555						break;
556					}
557					(void) strlcpy(fullbuf, buf,
558						       sizeof fullbuf);
559				}
560				target = fullbuf;
561			}
562			ret = safedirpath(target, uid, gid, user, flags,
563					  level + 1, offset);
564			if (ret != 0)
565				break;
566
567			/* Don't check permissions on the link file itself */
568			continue;
569		}
570#endif /* S_ISLNK */
571
572		if ((uid == 0 || bitset(SFF_SAFEDIRPATH, flags)) &&
573#ifdef S_ISVTX
574		    !(bitnset(DBS_TRUSTSTICKYBIT, DontBlameSendmail) &&
575		      bitset(S_ISVTX, stbuf.st_mode)) &&
576#endif /* S_ISVTX */
577		    bitset(mode, stbuf.st_mode))
578		{
579			if (tTd(44, 4))
580				dprintf("\t[dir %s] mode %lo ",
581					s, (u_long) stbuf.st_mode);
582			if (bitset(SFF_SAFEDIRPATH, flags))
583			{
584				if (bitset(S_IWOTH, stbuf.st_mode))
585					ret = E_SM_WWDIR;
586				else
587					ret = E_SM_GWDIR;
588				if (tTd(44, 4))
589					dprintf("FATAL\n");
590				break;
591			}
592			if (tTd(44, 4))
593				dprintf("WARNING\n");
594			if (Verbose > 1)
595				message("051 WARNING: %s writable directory %s",
596					bitset(S_IWOTH, stbuf.st_mode)
597					   ? "World"
598					   : "Group",
599					s);
600		}
601		if (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags))
602		{
603			if (bitset(S_IXOTH, stbuf.st_mode))
604				continue;
605			ret = EACCES;
606			break;
607		}
608
609		/*
610		**  Let OS determine access to file if we are not
611		**  running as a privileged user.  This allows ACLs
612		**  to work.  Also, if opening as root, assume we can
613		**  scan the directory.
614		*/
615		if (geteuid() != 0 || bitset(SFF_OPENASROOT, flags))
616			continue;
617
618		if (stbuf.st_uid == uid &&
619		    bitset(S_IXUSR, stbuf.st_mode))
620			continue;
621		if (stbuf.st_gid == gid &&
622		    bitset(S_IXGRP, stbuf.st_mode))
623			continue;
624# ifndef NO_GROUP_SET
625		if (user != NULL && !DontInitGroups &&
626		    ((gr != NULL && gr->gr_gid == stbuf.st_gid) ||
627		     (gr = getgrgid(stbuf.st_gid)) != NULL))
628		{
629			register char **gp;
630
631			for (gp = gr->gr_mem; gp != NULL && *gp != NULL; gp++)
632				if (strcmp(*gp, user) == 0)
633					break;
634			if (gp != NULL && *gp != NULL &&
635			    bitset(S_IXGRP, stbuf.st_mode))
636				continue;
637		}
638# endif /* ! NO_GROUP_SET */
639		if (!bitset(S_IXOTH, stbuf.st_mode))
640		{
641			ret = EACCES;
642			break;
643		}
644	}
645	if (tTd(44, 4))
646		dprintf("\t[dir %s] %s\n", fn,
647			ret == 0 ? "OK" : errstring(ret));
648	return ret;
649}
650/*
651**  SAFEOPEN -- do a file open with extra checking
652**
653**	Parameters:
654**		fn -- the file name to open.
655**		omode -- the open-style mode flags.
656**		cmode -- the create-style mode flags.
657**		sff -- safefile flags.
658**
659**	Returns:
660**		Same as open.
661*/
662
663#ifndef O_ACCMODE
664# define O_ACCMODE	(O_RDONLY|O_WRONLY|O_RDWR)
665#endif /* ! O_ACCMODE */
666
667int
668safeopen(fn, omode, cmode, sff)
669	char *fn;
670	int omode;
671	int cmode;
672	long sff;
673{
674	int rval;
675	int fd;
676	int smode;
677	struct stat stb;
678
679	if (tTd(44, 10))
680		printf("safeopen: fn=%s, omode=%x, cmode=%x, sff=%lx\n",
681		       fn, omode, cmode, sff);
682
683	if (bitset(O_CREAT, omode))
684		sff |= SFF_CREAT;
685	omode &= ~O_CREAT;
686	smode = 0;
687	switch (omode & O_ACCMODE)
688	{
689	  case O_RDONLY:
690		smode = S_IREAD;
691		break;
692
693	  case O_WRONLY:
694		smode = S_IWRITE;
695		break;
696
697	  case O_RDWR:
698		smode = S_IREAD|S_IWRITE;
699		break;
700
701	  default:
702		smode = 0;
703		break;
704	}
705	if (bitset(SFF_OPENASROOT, sff))
706		rval = safefile(fn, RunAsUid, RunAsGid, RunAsUserName,
707				sff, smode, &stb);
708	else
709		rval = safefile(fn, RealUid, RealGid, RealUserName,
710				sff, smode, &stb);
711	if (rval != 0)
712	{
713		errno = rval;
714		return -1;
715	}
716	if (stb.st_mode == ST_MODE_NOFILE && bitset(SFF_CREAT, sff))
717		omode |= O_CREAT | (bitset(SFF_NOTEXCL, sff) ? 0 : O_EXCL);
718	else if (bitset(SFF_CREAT, sff) && bitset(O_EXCL, omode))
719	{
720		/* The file exists so an exclusive create would fail */
721		errno = EEXIST;
722		return -1;
723	}
724
725	fd = dfopen(fn, omode, cmode, sff);
726	if (fd < 0)
727		return fd;
728	if (filechanged(fn, fd, &stb))
729	{
730		syserr("554 5.3.0 cannot open: file %s changed after open", fn);
731		(void) close(fd);
732		errno = E_SM_FILECHANGE;
733		return -1;
734	}
735	return fd;
736}
737/*
738**  SAFEFOPEN -- do a file open with extra checking
739**
740**	Parameters:
741**		fn -- the file name to open.
742**		omode -- the open-style mode flags.
743**		cmode -- the create-style mode flags.
744**		sff -- safefile flags.
745**
746**	Returns:
747**		Same as fopen.
748*/
749
750FILE *
751safefopen(fn, omode, cmode, sff)
752	char *fn;
753	int omode;
754	int cmode;
755	long sff;
756{
757	int fd;
758	int save_errno;
759	FILE *fp;
760	char *fmode;
761
762	switch (omode & O_ACCMODE)
763	{
764	  case O_RDONLY:
765		fmode = "r";
766		break;
767
768	  case O_WRONLY:
769		if (bitset(O_APPEND, omode))
770			fmode = "a";
771		else
772			fmode = "w";
773		break;
774
775	  case O_RDWR:
776		if (bitset(O_TRUNC, omode))
777			fmode = "w+";
778		else if (bitset(O_APPEND, omode))
779			fmode = "a+";
780		else
781			fmode = "r+";
782		break;
783
784	  default:
785		syserr("554 5.3.5 safefopen: unknown omode %o", omode);
786		fmode = "x";
787	}
788	fd = safeopen(fn, omode, cmode, sff);
789	if (fd < 0)
790	{
791		save_errno = errno;
792		if (tTd(44, 10))
793			dprintf("safefopen: safeopen failed: %s\n",
794				errstring(errno));
795		errno = save_errno;
796		return NULL;
797	}
798	fp = fdopen(fd, fmode);
799	if (fp != NULL)
800		return fp;
801
802	save_errno = errno;
803	if (tTd(44, 10))
804	{
805		dprintf("safefopen: fdopen(%s, %s) failed: omode=%x, sff=%lx, err=%s\n",
806			fn, fmode, omode, sff, errstring(errno));
807	}
808	(void) close(fd);
809	errno = save_errno;
810	return NULL;
811}
812/*
813**  FILECHANGED -- check to see if file changed after being opened
814**
815**	Parameters:
816**		fn -- pathname of file to check.
817**		fd -- file descriptor to check.
818**		stb -- stat structure from before open.
819**
820**	Returns:
821**		TRUE -- if a problem was detected.
822**		FALSE -- if this file is still the same.
823*/
824
825bool
826filechanged(fn, fd, stb)
827	char *fn;
828	int fd;
829	struct stat *stb;
830{
831	struct stat sta;
832
833	if (stb->st_mode == ST_MODE_NOFILE)
834	{
835# if HASLSTAT && BOGUS_O_EXCL
836		/* only necessary if exclusive open follows symbolic links */
837		if (lstat(fn, stb) < 0 || stb->st_nlink != 1)
838			return TRUE;
839# else /* HASLSTAT && BOGUS_O_EXCL */
840		return FALSE;
841# endif /* HASLSTAT && BOGUS_O_EXCL */
842	}
843	if (fstat(fd, &sta) < 0)
844		return TRUE;
845
846	if (sta.st_nlink != stb->st_nlink ||
847	    sta.st_dev != stb->st_dev ||
848	    sta.st_ino != stb->st_ino ||
849# if HAS_ST_GEN && 0		/* AFS returns garbage in st_gen */
850	    sta.st_gen != stb->st_gen ||
851# endif /* HAS_ST_GEN && 0 */
852	    sta.st_uid != stb->st_uid ||
853	    sta.st_gid != stb->st_gid)
854	{
855		if (tTd(44, 8))
856		{
857			dprintf("File changed after opening:\n");
858			dprintf(" nlink	= %ld/%ld\n",
859				(long) stb->st_nlink, (long) sta.st_nlink);
860			dprintf(" dev	= %ld/%ld\n",
861				(long) stb->st_dev, (long) sta.st_dev);
862			if (sizeof sta.st_ino > sizeof (long))
863			{
864				dprintf(" ino	= %s/",
865					quad_to_string(stb->st_ino));
866				dprintf("%s\n",
867					quad_to_string(sta.st_ino));
868			}
869			else
870				dprintf(" ino	= %lu/%lu\n",
871					(unsigned long) stb->st_ino,
872					(unsigned long) sta.st_ino);
873# if HAS_ST_GEN
874			dprintf(" gen	= %ld/%ld\n",
875				(long) stb->st_gen, (long) sta.st_gen);
876# endif /* HAS_ST_GEN */
877			dprintf(" uid	= %ld/%ld\n",
878				(long) stb->st_uid, (long) sta.st_uid);
879			dprintf(" gid	= %ld/%ld\n",
880				(long) stb->st_gid, (long) sta.st_gid);
881		}
882		return TRUE;
883	}
884
885	return FALSE;
886}
887/*
888**  DFOPEN -- determined file open
889**
890**	This routine has the semantics of open, except that it will
891**	keep trying a few times to make this happen.  The idea is that
892**	on very loaded systems, we may run out of resources (inodes,
893**	whatever), so this tries to get around it.
894*/
895
896int
897dfopen(filename, omode, cmode, sff)
898	char *filename;
899	int omode;
900	int cmode;
901	long sff;
902{
903	register int tries;
904	int fd = -1;
905	struct stat st;
906
907	for (tries = 0; tries < 10; tries++)
908	{
909		(void) sleep((unsigned) (10 * tries));
910		errno = 0;
911		fd = open(filename, omode, cmode);
912		if (fd >= 0)
913			break;
914		switch (errno)
915		{
916		  case ENFILE:		/* system file table full */
917		  case EINTR:		/* interrupted syscall */
918#ifdef ETXTBSY
919		  case ETXTBSY:		/* Apollo: net file locked */
920#endif /* ETXTBSY */
921			continue;
922		}
923		break;
924	}
925	if (!bitset(SFF_NOLOCK, sff) &&
926	    fd >= 0 &&
927	    fstat(fd, &st) >= 0 &&
928	    S_ISREG(st.st_mode))
929	{
930		int locktype;
931
932		/* lock the file to avoid accidental conflicts */
933		if ((omode & O_ACCMODE) != O_RDONLY)
934			locktype = LOCK_EX;
935		else
936			locktype = LOCK_SH;
937		if (!lockfile(fd, filename, NULL, locktype))
938		{
939			int save_errno = errno;
940
941			(void) close(fd);
942			fd = -1;
943			errno = save_errno;
944		}
945		else
946			errno = 0;
947	}
948	return fd;
949}
950