safefile.c revision 261194
190075Sobrien/*
290075Sobrien * Copyright (c) 1998-2004 Proofpoint, Inc. and its suppliers.
3169689Skan *	All rights reserved.
4169689Skan * Copyright (c) 1983, 1995-1997 Eric P. Allman.  All rights reserved.
590075Sobrien * Copyright (c) 1988, 1993
690075Sobrien *	The Regents of the University of California.  All rights reserved.
790075Sobrien *
890075Sobrien * By using this file, you agree to the terms and conditions set
990075Sobrien * forth in the LICENSE file which can be found at the top level of
1090075Sobrien * the sendmail distribution.
1190075Sobrien *
1290075Sobrien */
1390075Sobrien
1490075Sobrien#include <sendmail.h>
1590075Sobrien#include <sm/io.h>
1690075Sobrien#include <sm/errstring.h>
1790075Sobrien
1890075SobrienSM_RCSID("@(#)$Id: safefile.c,v 8.130 2013/11/22 20:51:50 ca Exp $")
1990075Sobrien
20169689Skan
21169689Skan/*
2290075Sobrien**  SAFEFILE -- return 0 if a file exists and is safe for a user.
2390075Sobrien**
2490075Sobrien**	Parameters:
2590075Sobrien**		fn -- filename to check.
26169689Skan**		uid -- user id to compare against.
2790075Sobrien**		gid -- group id to compare against.
2890075Sobrien**		user -- user name to compare against (used for group
2990075Sobrien**			sets).
3090075Sobrien**		flags -- modifiers:
3190075Sobrien**			SFF_MUSTOWN -- "uid" must own this file.
3290075Sobrien**			SFF_NOSLINK -- file cannot be a symbolic link.
3390075Sobrien**		mode -- mode bits that must match.
3490075Sobrien**		st -- if set, points to a stat structure that will
3590075Sobrien**			get the stat info for the file.
3690075Sobrien**
3790075Sobrien**	Returns:
3890075Sobrien**		0 if fn exists, is owned by uid, and matches mode.
3990075Sobrien**		An errno otherwise.  The actual errno is cleared.
4090075Sobrien**
4190075Sobrien**	Side Effects:
4290075Sobrien**		none.
4390075Sobrien*/
4490075Sobrien
4590075Sobrienint
4690075Sobriensafefile(fn, uid, gid, user, flags, mode, st)
4790075Sobrien	char *fn;
4890075Sobrien	UID_T uid;
4990075Sobrien	GID_T gid;
5090075Sobrien	char *user;
5190075Sobrien	long flags;
5290075Sobrien	int mode;
5390075Sobrien	struct stat *st;
54117395Skan{
55117395Skan	register char *p;
56117395Skan	register struct group *gr = NULL;
57169689Skan	int file_errno = 0;
58132718Skan	bool checkpath;
59132718Skan	struct stat stbuf;
60132718Skan	struct stat fstbuf;
61132718Skan	char fbuf[MAXPATHLEN];
62132718Skan
63132718Skan	if (tTd(44, 4))
64169689Skan		sm_dprintf("safefile(%s, uid=%d, gid=%d, flags=%lx, mode=%o):\n",
65169689Skan			fn, (int) uid, (int) gid, flags, mode);
66169689Skan	errno = 0;
67169689Skan	if (sm_strlcpy(fbuf, fn, sizeof fbuf) >= sizeof fbuf)
6890075Sobrien	{
6990075Sobrien		if (tTd(44, 4))
7090075Sobrien			sm_dprintf("\tpathname too long\n");
7190075Sobrien		return ENAMETOOLONG;
72117395Skan	}
73132718Skan	fn = fbuf;
74169689Skan	if (st == NULL)
7590075Sobrien		st = &fstbuf;
76169689Skan
77169689Skan	/* ignore SFF_SAFEDIRPATH if we are debugging */
78169689Skan	if (RealUid != 0 && RunAsUid == RealUid)
79169689Skan		flags &= ~SFF_SAFEDIRPATH;
80169689Skan
81169689Skan	/* first check to see if the file exists at all */
8290075Sobrien# if HASLSTAT
8390075Sobrien	if ((bitset(SFF_NOSLINK, flags) ? lstat(fn, st)
8490075Sobrien					: stat(fn, st)) < 0)
8590075Sobrien# else /* HASLSTAT */
8690075Sobrien	if (stat(fn, st) < 0)
87117395Skan# endif /* HASLSTAT */
88169689Skan	{
89117395Skan		file_errno = errno;
90132718Skan	}
91169689Skan	else if (bitset(SFF_SETUIDOK, flags) &&
9290075Sobrien		 !bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode) &&
9390075Sobrien		 S_ISREG(st->st_mode))
9490075Sobrien	{
9590075Sobrien		/*
9690075Sobrien		**  If final file is set-user-ID, run as the owner of that
97169689Skan		**  file.  Gotta be careful not to reveal anything too
98117395Skan		**  soon here!
99132718Skan		*/
100169689Skan
10190075Sobrien# ifdef SUID_ROOT_FILES_OK
10290075Sobrien		if (bitset(S_ISUID, st->st_mode))
103169689Skan# else /* SUID_ROOT_FILES_OK */
104169689Skan		if (bitset(S_ISUID, st->st_mode) && st->st_uid != 0 &&
105169689Skan		    st->st_uid != TrustedUid)
106169689Skan# endif /* SUID_ROOT_FILES_OK */
107169689Skan		{
108169689Skan			uid = st->st_uid;
109169689Skan			user = NULL;
11090075Sobrien		}
111169689Skan# ifdef SUID_ROOT_FILES_OK
112117395Skan		if (bitset(S_ISGID, st->st_mode))
113132718Skan# else /* SUID_ROOT_FILES_OK */
114169689Skan		if (bitset(S_ISGID, st->st_mode) && st->st_gid != 0)
11590075Sobrien# endif /* SUID_ROOT_FILES_OK */
116132718Skan			gid = st->st_gid;
117132718Skan	}
118132718Skan
119169689Skan	checkpath = !bitset(SFF_NOPATHCHECK, flags) ||
120132718Skan		    (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags));
121132718Skan	if (bitset(SFF_NOWLINK, flags) && !bitset(SFF_SAFEDIRPATH, flags))
122169689Skan	{
12390075Sobrien		int ret;
124169689Skan
125169689Skan		/* check the directory */
126169689Skan		p = strrchr(fn, '/');
127169689Skan		if (p == NULL)
128169689Skan		{
129169689Skan			ret = safedirpath(".", uid, gid, user,
130169689Skan					  flags|SFF_SAFEDIRPATH, 0, 0);
131169689Skan		}
132169689Skan		else
133169689Skan		{
134169689Skan			*p = '\0';
135169689Skan			ret = safedirpath(fn, uid, gid, user,
136169689Skan					  flags|SFF_SAFEDIRPATH, 0, 0);
137169689Skan			*p = '/';
138169689Skan		}
139169689Skan		if (ret == 0)
140169689Skan		{
141169689Skan			/* directory is safe */
142169689Skan			checkpath = false;
143169689Skan		}
144169689Skan		else
145132718Skan		{
146132718Skan# if HASLSTAT
147132718Skan			/* Need lstat() information if called stat() before */
148132718Skan			if (!bitset(SFF_NOSLINK, flags) && lstat(fn, st) < 0)
149132718Skan			{
150132718Skan				ret = errno;
15190075Sobrien				if (tTd(44, 4))
152132718Skan					sm_dprintf("\t%s\n", sm_errstring(ret));
153132718Skan				return ret;
154132718Skan			}
155169689Skan# endif /* HASLSTAT */
156132718Skan			/* directory is writable: disallow links */
157132718Skan			flags |= SFF_NOLINK;
158132718Skan		}
159169689Skan	}
16090075Sobrien
161132718Skan	if (checkpath)
162132718Skan	{
163132718Skan		int ret;
164132718Skan
165132718Skan		p = strrchr(fn, '/');
166132718Skan		if (p == NULL)
167132718Skan		{
16890075Sobrien			ret = safedirpath(".", uid, gid, user, flags, 0, 0);
169132718Skan		}
170132718Skan		else
171132718Skan		{
172132718Skan			*p = '\0';
173132718Skan			ret = safedirpath(fn, uid, gid, user, flags, 0, 0);
174132718Skan			*p = '/';
17590075Sobrien		}
176132718Skan		if (ret != 0)
177132718Skan			return ret;
178132718Skan	}
179132718Skan
180132718Skan	/*
181132718Skan	**  If the target file doesn't exist, check the directory to
182132718Skan	**  ensure that it is writable by this user.
183132718Skan	*/
184132718Skan
185132718Skan	if (file_errno != 0)
186132718Skan	{
187132718Skan		int ret = file_errno;
188132718Skan		char *dir = fn;
189132718Skan
190132718Skan		if (tTd(44, 4))
191132718Skan			sm_dprintf("\t%s\n", sm_errstring(ret));
192132718Skan
193132718Skan		errno = 0;
194132718Skan		if (!bitset(SFF_CREAT, flags) || file_errno != ENOENT)
195132718Skan			return ret;
196132718Skan
197132718Skan		/* check to see if legal to create the file */
198132718Skan		p = strrchr(dir, '/');
199132718Skan		if (p == NULL)
200132718Skan			dir = ".";
201132718Skan		else if (p == dir)
202132718Skan			dir = "/";
203132718Skan		else
204132718Skan			*p = '\0';
205132718Skan		if (stat(dir, &stbuf) >= 0)
206132718Skan		{
207132718Skan			int md = S_IWRITE|S_IEXEC;
208132718Skan
209132718Skan			ret = 0;
210132718Skan			if (stbuf.st_uid == uid)
211132718Skan				/* EMPTY */
212132718Skan				;
213132718Skan			else if (uid == 0 && stbuf.st_uid == TrustedUid)
214132718Skan				/* EMPTY */
215132718Skan				;
216132718Skan			else
217132718Skan			{
218132718Skan				md >>= 3;
219132718Skan				if (stbuf.st_gid == gid)
220132718Skan					/* EMPTY */
221132718Skan					;
222132718Skan# ifndef NO_GROUP_SET
223132718Skan				else if (user != NULL && !DontInitGroups &&
224132718Skan					 ((gr != NULL &&
225132718Skan					   gr->gr_gid == stbuf.st_gid) ||
226132718Skan					  (gr = getgrgid(stbuf.st_gid)) != NULL))
227132718Skan				{
228132718Skan					register char **gp;
229132718Skan
230132718Skan					for (gp = gr->gr_mem; *gp != NULL; gp++)
231132718Skan						if (strcmp(*gp, user) == 0)
232132718Skan							break;
233132718Skan					if (*gp == NULL)
234132718Skan						md >>= 3;
235132718Skan				}
236132718Skan# endif /* ! NO_GROUP_SET */
237132718Skan				else
238132718Skan					md >>= 3;
239132718Skan			}
240132718Skan			if ((stbuf.st_mode & md) != md)
241132718Skan				ret = errno = EACCES;
242132718Skan		}
243132718Skan		else
244132718Skan			ret = errno;
245132718Skan		if (tTd(44, 4))
246132718Skan			sm_dprintf("\t[final dir %s uid %d mode %lo] %s\n",
247132718Skan				dir, (int) stbuf.st_uid,
248132718Skan				(unsigned long) stbuf.st_mode,
249132718Skan				sm_errstring(ret));
250132718Skan		if (p != NULL)
251132718Skan			*p = '/';
252132718Skan		st->st_mode = ST_MODE_NOFILE;
253132718Skan		return ret;
254132718Skan	}
255132718Skan
256132718Skan# ifdef S_ISLNK
257132718Skan	if (bitset(SFF_NOSLINK, flags) && S_ISLNK(st->st_mode))
258169689Skan	{
259169689Skan		if (tTd(44, 4))
260169689Skan			sm_dprintf("\t[slink mode %lo]\tE_SM_NOSLINK\n",
261132718Skan				(unsigned long) st->st_mode);
262132718Skan		return E_SM_NOSLINK;
263132718Skan	}
264132718Skan# endif /* S_ISLNK */
265132718Skan	if (bitset(SFF_REGONLY, flags) && !S_ISREG(st->st_mode))
266132718Skan	{
267132718Skan		if (tTd(44, 4))
268132718Skan			sm_dprintf("\t[non-reg mode %lo]\tE_SM_REGONLY\n",
269132718Skan				(unsigned long) st->st_mode);
270132718Skan		return E_SM_REGONLY;
271132718Skan	}
272132718Skan	if (bitset(SFF_NOGWFILES, flags) &&
273169689Skan	    bitset(S_IWGRP, st->st_mode))
274169689Skan	{
275169689Skan		if (tTd(44, 4))
276132718Skan			sm_dprintf("\t[write bits %lo]\tE_SM_GWFILE\n",
277132718Skan				(unsigned long) st->st_mode);
278132718Skan		return E_SM_GWFILE;
279132718Skan	}
280132718Skan	if (bitset(SFF_NOWWFILES, flags) &&
281132718Skan	    bitset(S_IWOTH, st->st_mode))
282132718Skan	{
283132718Skan		if (tTd(44, 4))
284132718Skan			sm_dprintf("\t[write bits %lo]\tE_SM_WWFILE\n",
285169689Skan				(unsigned long) st->st_mode);
286169689Skan		return E_SM_WWFILE;
287169689Skan	}
288132718Skan	if (bitset(SFF_NOGRFILES, flags) && bitset(S_IRGRP, st->st_mode))
289132718Skan	{
290132718Skan		if (tTd(44, 4))
291169689Skan			sm_dprintf("\t[read bits %lo]\tE_SM_GRFILE\n",
292169689Skan				(unsigned long) st->st_mode);
293169689Skan		return E_SM_GRFILE;
294169689Skan	}
295169689Skan	if (bitset(SFF_NOWRFILES, flags) && bitset(S_IROTH, st->st_mode))
296169689Skan	{
297169689Skan		if (tTd(44, 4))
298169689Skan			sm_dprintf("\t[read bits %lo]\tE_SM_WRFILE\n",
299169689Skan				(unsigned long) st->st_mode);
300169689Skan		return E_SM_WRFILE;
301169689Skan	}
302169689Skan	if (!bitset(SFF_EXECOK, flags) &&
303132718Skan	    bitset(S_IWUSR|S_IWGRP|S_IWOTH, mode) &&
304132718Skan	    bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode))
305132718Skan	{
306132718Skan		if (tTd(44, 4))
307132718Skan			sm_dprintf("\t[exec bits %lo]\tE_SM_ISEXEC\n",
308132718Skan				(unsigned long) st->st_mode);
309132718Skan		return E_SM_ISEXEC;
310132718Skan	}
311132718Skan	if (bitset(SFF_NOHLINK, flags) && st->st_nlink != 1)
312132718Skan	{
313132718Skan		if (tTd(44, 4))
314132718Skan			sm_dprintf("\t[link count %d]\tE_SM_NOHLINK\n",
315132718Skan				(int) st->st_nlink);
316132718Skan		return E_SM_NOHLINK;
317132718Skan	}
318132718Skan
319132718Skan	if (uid == 0 && bitset(SFF_OPENASROOT, flags))
320132718Skan		/* EMPTY */
321132718Skan		;
322132718Skan	else if (uid == 0 && !bitset(SFF_ROOTOK, flags))
323132718Skan		mode >>= 6;
324132718Skan	else if (st->st_uid == uid)
325132718Skan		/* EMPTY */
326132718Skan		;
327132718Skan	else if (uid == 0 && st->st_uid == TrustedUid)
328132718Skan		/* EMPTY */
329132718Skan		;
330132718Skan	else
331132718Skan	{
332132718Skan		mode >>= 3;
333169689Skan		if (st->st_gid == gid)
334169689Skan			/* EMPTY */
335169689Skan			;
336169689Skan# ifndef NO_GROUP_SET
337169689Skan		else if (user != NULL && !DontInitGroups &&
338169689Skan			 ((gr != NULL && gr->gr_gid == st->st_gid) ||
339169689Skan			  (gr = getgrgid(st->st_gid)) != NULL))
340169689Skan		{
341169689Skan			register char **gp;
342132718Skan
343132718Skan			for (gp = gr->gr_mem; *gp != NULL; gp++)
344132718Skan				if (strcmp(*gp, user) == 0)
345132718Skan					break;
346132718Skan			if (*gp == NULL)
347132718Skan				mode >>= 3;
348132718Skan		}
349132718Skan# endif /* ! NO_GROUP_SET */
350132718Skan		else
351132718Skan			mode >>= 3;
352132718Skan	}
353132718Skan	if (tTd(44, 4))
354132718Skan		sm_dprintf("\t[uid %d, nlink %d, stat %lo, mode %lo] ",
355132718Skan			(int) st->st_uid, (int) st->st_nlink,
356169689Skan			(unsigned long) st->st_mode, (unsigned long) mode);
357169689Skan	if ((st->st_uid == uid || st->st_uid == 0 ||
358169689Skan	     st->st_uid == TrustedUid ||
359132718Skan	     !bitset(SFF_MUSTOWN, flags)) &&
360132718Skan	    (st->st_mode & mode) == mode)
361132718Skan	{
362132718Skan		if (tTd(44, 4))
363132718Skan			sm_dprintf("\tOK\n");
364132718Skan		return 0;
365132718Skan	}
366132718Skan	if (tTd(44, 4))
367132718Skan		sm_dprintf("\tEACCES\n");
368132718Skan	return EACCES;
369132718Skan}
370132718Skan/*
371132718Skan**  SAFEDIRPATH -- check to make sure a path to a directory is safe
372132718Skan**
373132718Skan**	Safe means not writable and owned by the right folks.
374132718Skan**
375132718Skan**	Parameters:
376132718Skan**		fn -- filename to check.
377132718Skan**		uid -- user id to compare against.
378132718Skan**		gid -- group id to compare against.
379132718Skan**		user -- user name to compare against (used for group
380132718Skan**			sets).
381169689Skan**		flags -- modifiers:
382169689Skan**			SFF_ROOTOK -- ok to use root permissions to open.
383169689Skan**			SFF_SAFEDIRPATH -- writable directories are considered
384132718Skan**				to be fatal errors.
385132718Skan**		level -- symlink recursive level.
386132718Skan**		offset -- offset into fn to start checking from.
387132718Skan**
388132718Skan**	Returns:
389132718Skan**		0 -- if the directory path is "safe".
390132718Skan**		else -- an error number associated with the path.
391132718Skan*/
392132718Skan
393132718Skanint
394132718Skansafedirpath(fn, uid, gid, user, flags, level, offset)
395132718Skan	char *fn;
396132718Skan	UID_T uid;
397132718Skan	GID_T gid;
398132718Skan	char *user;
399132718Skan	long flags;
400132718Skan	int level;
401132718Skan	int offset;
402132718Skan{
403132718Skan	int ret = 0;
404132718Skan	int mode = S_IWOTH;
405132718Skan	char save = '\0';
406132718Skan	char *saveptr = NULL;
407132718Skan	char *p, *enddir;
408132718Skan	register struct group *gr = NULL;
409132718Skan	char s[MAXLINKPATHLEN];
410132718Skan	struct stat stbuf;
411132718Skan
412132718Skan	/* make sure we aren't in a symlink loop */
413132718Skan	if (level > MAXSYMLINKS)
414132718Skan		return ELOOP;
415132718Skan
416132718Skan	if (level < 0 || offset < 0 || offset > strlen(fn))
417132718Skan		return EINVAL;
418132718Skan
419132718Skan	/* special case root directory */
42090075Sobrien	if (*fn == '\0')
421132718Skan		fn = "/";
422132718Skan
423132718Skan	if (tTd(44, 4))
424132718Skan		sm_dprintf("safedirpath(%s, uid=%ld, gid=%ld, flags=%lx, level=%d, offset=%d):\n",
425132718Skan			fn, (long) uid, (long) gid, flags, level, offset);
426132718Skan
427132718Skan	if (!bitnset(DBS_GROUPWRITABLEDIRPATHSAFE, DontBlameSendmail))
428132718Skan		mode |= S_IWGRP;
429132718Skan
430132718Skan	/* Make a modifiable copy of the filename */
431132718Skan	if (sm_strlcpy(s, fn, sizeof s) >= sizeof s)
432132718Skan		return EINVAL;
433132718Skan
434132718Skan	p = s + offset;
435132718Skan	while (p != NULL)
436132718Skan	{
437132718Skan		/* put back character */
438132718Skan		if (saveptr != NULL)
439132718Skan		{
440132718Skan			*saveptr = save;
441132718Skan			saveptr = NULL;
442132718Skan			p++;
443132718Skan		}
444132718Skan
445132718Skan		if (*p == '\0')
446132718Skan			break;
447132718Skan
448132718Skan		p = strchr(p, '/');
449132718Skan
450132718Skan		/* Special case for root directory */
451132718Skan		if (p == s)
452132718Skan		{
453132718Skan			save = *(p + 1);
454132718Skan			saveptr = p + 1;
455132718Skan			*(p + 1) = '\0';
456132718Skan		}
457132718Skan		else if (p != NULL)
458169689Skan		{
459169689Skan			save = *p;
460169689Skan			saveptr = p;
461169689Skan			*p = '\0';
462169689Skan		}
463169689Skan
464132718Skan		/* Heuristic: . and .. have already been checked */
465132718Skan		enddir = strrchr(s, '/');
466132718Skan		if (enddir != NULL &&
467132718Skan		    (strcmp(enddir, "/..") == 0 ||
468132718Skan		     strcmp(enddir, "/.") == 0))
469132718Skan			continue;
470132718Skan
471132718Skan		if (tTd(44, 20))
472132718Skan			sm_dprintf("\t[dir %s]\n", s);
473132718Skan
474132718Skan# if HASLSTAT
475132718Skan		ret = lstat(s, &stbuf);
476132718Skan# else /* HASLSTAT */
477132718Skan		ret = stat(s, &stbuf);
478132718Skan# endif /* HASLSTAT */
479132718Skan		if (ret < 0)
480132718Skan		{
481132718Skan			ret = errno;
482132718Skan			break;
483132718Skan		}
484132718Skan
485132718Skan# ifdef S_ISLNK
486132718Skan		/* Follow symlinks */
487132718Skan		if (S_ISLNK(stbuf.st_mode))
488132718Skan		{
489132718Skan			int linklen;
490132718Skan			char *target;
49190075Sobrien			char buf[MAXPATHLEN];
492132718Skan			char fullbuf[MAXLINKPATHLEN];
493132718Skan
494132718Skan			memset(buf, '\0', sizeof buf);
495132718Skan			linklen = readlink(s, buf, sizeof buf);
496132718Skan			if (linklen < 0)
497132718Skan			{
498169689Skan				ret = errno;
499169689Skan				break;
500169689Skan			}
501169689Skan			if (linklen >= sizeof buf)
502169689Skan			{
503169689Skan				/* file name too long for buffer */
504169689Skan				ret = errno = EINVAL;
505169689Skan				break;
506169689Skan			}
507169689Skan
508169689Skan			offset = 0;
509169689Skan			if (*buf == '/')
510169689Skan			{
511169689Skan				target = buf;
512169689Skan
513169689Skan				/* If path is the same, avoid rechecks */
514169689Skan				while (s[offset] == buf[offset] &&
515169689Skan				       s[offset] != '\0')
516169689Skan					offset++;
517169689Skan
518169689Skan				if (s[offset] == '\0' && buf[offset] == '\0')
519169689Skan				{
520169689Skan					/* strings match, symlink loop */
521169689Skan					return ELOOP;
522169689Skan				}
523169689Skan
52490075Sobrien				/* back off from the mismatch */
525132718Skan				if (offset > 0)
526169689Skan					offset--;
527169689Skan
528169689Skan				/* Make sure we are at a directory break */
529169689Skan				if (offset > 0 &&
530169689Skan				    s[offset] != '/' &&
531169689Skan				    s[offset] != '\0')
532169689Skan				{
533169689Skan					while (buf[offset] != '/' &&
534169689Skan					       offset > 0)
535169689Skan						offset--;
536169689Skan				}
537132718Skan				if (offset > 0 &&
538132718Skan				    s[offset] == '/' &&
539169689Skan				    buf[offset] == '/')
540169689Skan				{
541169689Skan					/* Include the trailing slash */
542169689Skan					offset++;
543132718Skan				}
544132718Skan			}
545132718Skan			else
546132718Skan			{
547169689Skan				char *sptr;
548169689Skan
549132718Skan				sptr = strrchr(s, '/');
550132718Skan				if (sptr != NULL)
551132718Skan				{
552132718Skan					*sptr = '\0';
553132718Skan					offset = sptr + 1 - s;
55490075Sobrien					if (sm_strlcpyn(fullbuf,
555169689Skan							sizeof fullbuf, 2,
556169689Skan							s, "/") >=
557169689Skan						sizeof fullbuf ||
558169689Skan					    sm_strlcat(fullbuf, buf,
559169689Skan						       sizeof fullbuf) >=
560169689Skan						sizeof fullbuf)
561169689Skan					{
562169689Skan						ret = EINVAL;
563169689Skan						break;
564169689Skan					}
565169689Skan					*sptr = '/';
566169689Skan				}
567169689Skan				else
568169689Skan				{
569169689Skan					if (sm_strlcpy(fullbuf, buf,
570169689Skan						       sizeof fullbuf) >=
571169689Skan						sizeof fullbuf)
572169689Skan					{
573169689Skan						ret = EINVAL;
574169689Skan						break;
575169689Skan					}
576169689Skan				}
577169689Skan				target = fullbuf;
578169689Skan			}
579169689Skan			ret = safedirpath(target, uid, gid, user, flags,
580169689Skan					  level + 1, offset);
581169689Skan			if (ret != 0)
582169689Skan				break;
583169689Skan
584169689Skan			/* Don't check permissions on the link file itself */
585169689Skan			continue;
586169689Skan		}
587169689Skan#endif /* S_ISLNK */
588169689Skan
589132718Skan		if ((uid == 0 || bitset(SFF_SAFEDIRPATH, flags)) &&
590132718Skan#ifdef S_ISVTX
591132718Skan		    !(bitnset(DBS_TRUSTSTICKYBIT, DontBlameSendmail) &&
592132718Skan		      bitset(S_ISVTX, stbuf.st_mode)) &&
593132718Skan#endif /* S_ISVTX */
594132718Skan		    bitset(mode, stbuf.st_mode))
595132718Skan		{
596132718Skan			if (tTd(44, 4))
597132718Skan				sm_dprintf("\t[dir %s] mode %lo ",
598132718Skan					s, (unsigned long) stbuf.st_mode);
599169689Skan			if (bitset(SFF_SAFEDIRPATH, flags))
600169689Skan			{
601169689Skan				if (bitset(S_IWOTH, stbuf.st_mode))
602169689Skan					ret = E_SM_WWDIR;
603132718Skan				else
604169689Skan					ret = E_SM_GWDIR;
605169689Skan				if (tTd(44, 4))
606169689Skan					sm_dprintf("FATAL\n");
607169689Skan				break;
608132718Skan			}
609132718Skan			if (tTd(44, 4))
610132718Skan				sm_dprintf("WARNING\n");
611169689Skan			if (Verbose > 1)
612132718Skan				message("051 WARNING: %s writable directory %s",
613132718Skan					bitset(S_IWOTH, stbuf.st_mode)
614169689Skan					   ? "World"
615169689Skan					   : "Group",
616169689Skan					s);
617169689Skan		}
618169689Skan		if (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags))
619169689Skan		{
620132718Skan			if (bitset(S_IXOTH, stbuf.st_mode))
621169689Skan				continue;
622132718Skan			ret = EACCES;
623132718Skan			break;
624169689Skan		}
625169689Skan
626169689Skan		/*
627169689Skan		**  Let OS determine access to file if we are not
628169689Skan		**  running as a privileged user.  This allows ACLs
629169689Skan		**  to work.  Also, if opening as root, assume we can
630132718Skan		**  scan the directory.
631132718Skan		*/
632132718Skan		if (geteuid() != 0 || bitset(SFF_OPENASROOT, flags))
633132718Skan			continue;
634169689Skan
635169689Skan		if (stbuf.st_uid == uid &&
636169689Skan		    bitset(S_IXUSR, stbuf.st_mode))
637169689Skan			continue;
638169689Skan		if (stbuf.st_gid == gid &&
639169689Skan		    bitset(S_IXGRP, stbuf.st_mode))
640169689Skan			continue;
641169689Skan# ifndef NO_GROUP_SET
642169689Skan		if (user != NULL && !DontInitGroups &&
643169689Skan		    ((gr != NULL && gr->gr_gid == stbuf.st_gid) ||
644169689Skan		     (gr = getgrgid(stbuf.st_gid)) != NULL))
645169689Skan		{
646169689Skan			register char **gp;
647169689Skan
648169689Skan			for (gp = gr->gr_mem; gp != NULL && *gp != NULL; gp++)
649169689Skan				if (strcmp(*gp, user) == 0)
650169689Skan					break;
651169689Skan			if (gp != NULL && *gp != NULL &&
652132718Skan			    bitset(S_IXGRP, stbuf.st_mode))
653132718Skan				continue;
654132718Skan		}
655132718Skan# endif /* ! NO_GROUP_SET */
656132718Skan		if (!bitset(S_IXOTH, stbuf.st_mode))
657132718Skan		{
658132718Skan			ret = EACCES;
659132718Skan			break;
660132718Skan		}
661132718Skan	}
662132718Skan	if (tTd(44, 4))
663169689Skan		sm_dprintf("\t[dir %s] %s\n", fn,
664169689Skan			ret == 0 ? "OK" : sm_errstring(ret));
665169689Skan	return ret;
666169689Skan}
667169689Skan/*
668169689Skan**  SAFEOPEN -- do a file open with extra checking
669169689Skan**
670169689Skan**	Parameters:
671169689Skan**		fn -- the file name to open.
672132718Skan**		omode -- the open-style mode flags.
673169689Skan**		cmode -- the create-style mode flags.
674132718Skan**		sff -- safefile flags.
675132718Skan**
676132718Skan**	Returns:
677132718Skan**		Same as open.
678132718Skan*/
679132718Skan
680132718Skanint
681169689Skansafeopen(fn, omode, cmode, sff)
682132718Skan	char *fn;
683132718Skan	int omode;
684132718Skan	int cmode;
685132718Skan	long sff;
686132718Skan{
687169689Skan#if !NOFTRUNCATE
688169689Skan	bool truncate;
689169689Skan#endif /* !NOFTRUNCATE */
690169689Skan	int rval;
691169689Skan	int fd;
692169689Skan	int smode;
693169689Skan	struct stat stb;
694169689Skan
695169689Skan	if (tTd(44, 10))
696169689Skan		sm_dprintf("safeopen: fn=%s, omode=%x, cmode=%x, sff=%lx\n",
697169689Skan			   fn, omode, cmode, sff);
698169689Skan
699169689Skan	if (bitset(O_CREAT, omode))
700169689Skan		sff |= SFF_CREAT;
701169689Skan	omode &= ~O_CREAT;
702169689Skan	switch (omode & O_ACCMODE)
703169689Skan	{
704169689Skan	  case O_RDONLY:
705169689Skan		smode = S_IREAD;
706169689Skan		break;
707169689Skan
708169689Skan	  case O_WRONLY:
709169689Skan		smode = S_IWRITE;
710169689Skan		break;
711169689Skan
712169689Skan	  case O_RDWR:
713169689Skan		smode = S_IREAD|S_IWRITE;
714169689Skan		break;
715169689Skan
716169689Skan	  default:
717169689Skan		smode = 0;
718169689Skan		break;
719169689Skan	}
720169689Skan	if (bitset(SFF_OPENASROOT, sff))
721169689Skan		rval = safefile(fn, RunAsUid, RunAsGid, RunAsUserName,
722169689Skan				sff, smode, &stb);
723169689Skan	else
724169689Skan		rval = safefile(fn, RealUid, RealGid, RealUserName,
725169689Skan				sff, smode, &stb);
726169689Skan	if (rval != 0)
727169689Skan	{
728169689Skan		errno = rval;
729169689Skan		return -1;
730169689Skan	}
731	if (stb.st_mode == ST_MODE_NOFILE && bitset(SFF_CREAT, sff))
732		omode |= O_CREAT | (bitset(SFF_NOTEXCL, sff) ? 0 : O_EXCL);
733	else if (bitset(SFF_CREAT, sff) && bitset(O_EXCL, omode))
734	{
735		/* The file exists so an exclusive create would fail */
736		errno = EEXIST;
737		return -1;
738	}
739
740#if !NOFTRUNCATE
741	truncate = bitset(O_TRUNC, omode);
742	if (truncate)
743		omode &= ~O_TRUNC;
744#endif /* !NOFTRUNCATE */
745
746	fd = dfopen(fn, omode, cmode, sff);
747	if (fd < 0)
748		return fd;
749	if (filechanged(fn, fd, &stb))
750	{
751		syserr("554 5.3.0 cannot open: file %s changed after open", fn);
752		(void) close(fd);
753		errno = E_SM_FILECHANGE;
754		return -1;
755	}
756
757#if !NOFTRUNCATE
758	if (truncate &&
759	    ftruncate(fd, (off_t) 0) < 0)
760	{
761		int save_errno;
762
763		save_errno = errno;
764		syserr("554 5.3.0 cannot open: file %s could not be truncated",
765		       fn);
766		(void) close(fd);
767		errno = save_errno;
768		return -1;
769	}
770#endif /* !NOFTRUNCATE */
771
772	return fd;
773}
774/*
775**  SAFEFOPEN -- do a file open with extra checking
776**
777**	Parameters:
778**		fn -- the file name to open.
779**		omode -- the open-style mode flags.
780**		cmode -- the create-style mode flags.
781**		sff -- safefile flags.
782**
783**	Returns:
784**		Same as fopen.
785*/
786
787SM_FILE_T *
788safefopen(fn, omode, cmode, sff)
789	char *fn;
790	int omode;
791	int cmode;
792	long sff;
793{
794	int fd;
795	int save_errno;
796	SM_FILE_T *fp;
797	int fmode;
798
799	switch (omode & O_ACCMODE)
800	{
801	  case O_RDONLY:
802		fmode = SM_IO_RDONLY;
803		break;
804
805	  case O_WRONLY:
806		if (bitset(O_APPEND, omode))
807			fmode = SM_IO_APPEND;
808		else
809			fmode = SM_IO_WRONLY;
810		break;
811
812	  case O_RDWR:
813		if (bitset(O_TRUNC, omode))
814			fmode = SM_IO_RDWRTR;
815		else if (bitset(O_APPEND, omode))
816			fmode = SM_IO_APPENDRW;
817		else
818			fmode = SM_IO_RDWR;
819		break;
820
821	  default:
822		syserr("554 5.3.5 safefopen: unknown omode %o", omode);
823		fmode = 0;
824	}
825	fd = safeopen(fn, omode, cmode, sff);
826	if (fd < 0)
827	{
828		save_errno = errno;
829		if (tTd(44, 10))
830			sm_dprintf("safefopen: safeopen failed: %s\n",
831				   sm_errstring(errno));
832		errno = save_errno;
833		return NULL;
834	}
835	fp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
836			(void *) &fd, fmode, NULL);
837	if (fp != NULL)
838		return fp;
839
840	save_errno = errno;
841	if (tTd(44, 10))
842	{
843		sm_dprintf("safefopen: fdopen(%s, %d) failed: omode=%x, sff=%lx, err=%s\n",
844			   fn, fmode, omode, sff, sm_errstring(errno));
845	}
846	(void) close(fd);
847	errno = save_errno;
848	return NULL;
849}
850/*
851**  FILECHANGED -- check to see if file changed after being opened
852**
853**	Parameters:
854**		fn -- pathname of file to check.
855**		fd -- file descriptor to check.
856**		stb -- stat structure from before open.
857**
858**	Returns:
859**		true -- if a problem was detected.
860**		false -- if this file is still the same.
861*/
862
863bool
864filechanged(fn, fd, stb)
865	char *fn;
866	int fd;
867	struct stat *stb;
868{
869	struct stat sta;
870
871	if (stb->st_mode == ST_MODE_NOFILE)
872	{
873# if HASLSTAT && BOGUS_O_EXCL
874		/* only necessary if exclusive open follows symbolic links */
875		if (lstat(fn, stb) < 0 || stb->st_nlink != 1)
876			return true;
877# else /* HASLSTAT && BOGUS_O_EXCL */
878		return false;
879# endif /* HASLSTAT && BOGUS_O_EXCL */
880	}
881	if (fstat(fd, &sta) < 0)
882		return true;
883
884	if (sta.st_nlink != stb->st_nlink ||
885	    sta.st_dev != stb->st_dev ||
886	    sta.st_ino != stb->st_ino ||
887# if HAS_ST_GEN && 0		/* AFS returns garbage in st_gen */
888	    sta.st_gen != stb->st_gen ||
889# endif /* HAS_ST_GEN && 0 */
890	    sta.st_uid != stb->st_uid ||
891	    sta.st_gid != stb->st_gid)
892	{
893		if (tTd(44, 8))
894		{
895			sm_dprintf("File changed after opening:\n");
896			sm_dprintf(" nlink	= %ld/%ld\n",
897				(long) stb->st_nlink, (long) sta.st_nlink);
898			sm_dprintf(" dev	= %ld/%ld\n",
899				(long) stb->st_dev, (long) sta.st_dev);
900			sm_dprintf(" ino	= %llu/%llu\n",
901				(ULONGLONG_T) stb->st_ino,
902				(ULONGLONG_T) sta.st_ino);
903# if HAS_ST_GEN
904			sm_dprintf(" gen	= %ld/%ld\n",
905				(long) stb->st_gen, (long) sta.st_gen);
906# endif /* HAS_ST_GEN */
907			sm_dprintf(" uid	= %ld/%ld\n",
908				(long) stb->st_uid, (long) sta.st_uid);
909			sm_dprintf(" gid	= %ld/%ld\n",
910				(long) stb->st_gid, (long) sta.st_gid);
911		}
912		return true;
913	}
914
915	return false;
916}
917/*
918**  DFOPEN -- determined file open
919**
920**	This routine has the semantics of open, except that it will
921**	keep trying a few times to make this happen.  The idea is that
922**	on very loaded systems, we may run out of resources (inodes,
923**	whatever), so this tries to get around it.
924*/
925
926int
927dfopen(filename, omode, cmode, sff)
928	char *filename;
929	int omode;
930	int cmode;
931	long sff;
932{
933	register int tries;
934	int fd = -1;
935	struct stat st;
936
937	for (tries = 0; tries < 10; tries++)
938	{
939		(void) sleep((unsigned) (10 * tries));
940		errno = 0;
941		fd = open(filename, omode, cmode);
942		if (fd >= 0)
943			break;
944		switch (errno)
945		{
946		  case ENFILE:		/* system file table full */
947		  case EINTR:		/* interrupted syscall */
948#ifdef ETXTBSY
949		  case ETXTBSY:		/* Apollo: net file locked */
950#endif /* ETXTBSY */
951			continue;
952		}
953		break;
954	}
955	if (!bitset(SFF_NOLOCK, sff) &&
956	    fd >= 0 &&
957	    fstat(fd, &st) >= 0 &&
958	    S_ISREG(st.st_mode))
959	{
960		int locktype;
961
962		/* lock the file to avoid accidental conflicts */
963		if ((omode & O_ACCMODE) != O_RDONLY)
964			locktype = LOCK_EX;
965		else
966			locktype = LOCK_SH;
967		if (bitset(SFF_NBLOCK, sff))
968			locktype |= LOCK_NB;
969
970		if (!lockfile(fd, filename, NULL, locktype))
971		{
972			int save_errno = errno;
973
974			(void) close(fd);
975			fd = -1;
976			errno = save_errno;
977		}
978		else
979			errno = 0;
980	}
981	return fd;
982}
983