safefile.c revision 98121
1279377Simp/*
2279377Simp * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
3279377Simp *	All rights reserved.
4279377Simp * Copyright (c) 1983, 1995-1997 Eric P. Allman.  All rights reserved.
5279377Simp * Copyright (c) 1988, 1993
6279377Simp *	The Regents of the University of California.  All rights reserved.
7279377Simp *
8279377Simp * By using this file, you agree to the terms and conditions set
9279377Simp * forth in the LICENSE file which can be found at the top level of
10279377Simp * the sendmail distribution.
11279377Simp *
12279377Simp */
13279377Simp
14279377Simp#include <sendmail.h>
15279377Simp#include <sm/io.h>
16279377Simp#include <sm/errstring.h>
17279377Simp
18279377SimpSM_RCSID("@(#)$Id: safefile.c,v 8.124 2002/05/24 20:50:15 gshapiro Exp $")
19279377Simp
20279377Simp
21279377Simp/*
22279377Simp**  SAFEFILE -- return 0 if a file exists and is safe for a user.
23279377Simp**
24279377Simp**	Parameters:
25279377Simp**		fn -- filename to check.
26279377Simp**		uid -- user id to compare against.
27279377Simp**		gid -- group id to compare against.
28279377Simp**		user -- user name to compare against (used for group
29279377Simp**			sets).
30279377Simp**		flags -- modifiers:
31279377Simp**			SFF_MUSTOWN -- "uid" must own this file.
32279377Simp**			SFF_NOSLINK -- file cannot be a symbolic link.
33279377Simp**		mode -- mode bits that must match.
34279377Simp**		st -- if set, points to a stat structure that will
35279377Simp**			get the stat info for the file.
36279377Simp**
37279377Simp**	Returns:
38279377Simp**		0 if fn exists, is owned by uid, and matches mode.
39279377Simp**		An errno otherwise.  The actual errno is cleared.
40279377Simp**
41279377Simp**	Side Effects:
42279377Simp**		none.
43279377Simp*/
44279377Simp
45279377Simpint
46279377Simpsafefile(fn, uid, gid, user, flags, mode, st)
47279377Simp	char *fn;
48279377Simp	UID_T uid;
49279377Simp	GID_T gid;
50279377Simp	char *user;
51279377Simp	long flags;
52279377Simp	int mode;
53279377Simp	struct stat *st;
54279377Simp{
55279377Simp	register char *p;
56279377Simp	register struct group *gr = NULL;
57279377Simp	int file_errno = 0;
58279377Simp	bool checkpath;
59279377Simp	struct stat stbuf;
60279377Simp	struct stat fstbuf;
61279377Simp	char fbuf[MAXPATHLEN];
62279377Simp
63279377Simp	if (tTd(44, 4))
64279377Simp		sm_dprintf("safefile(%s, uid=%d, gid=%d, flags=%lx, mode=%o):\n",
65279377Simp			fn, (int) uid, (int) gid, flags, mode);
66279377Simp	errno = 0;
67279377Simp	if (sm_strlcpy(fbuf, fn, sizeof fbuf) >= sizeof fbuf)
68279377Simp	{
69279377Simp		if (tTd(44, 4))
70279377Simp			sm_dprintf("\tpathname too long\n");
71279377Simp		return ENAMETOOLONG;
72279377Simp	}
73279377Simp	fn = fbuf;
74279377Simp	if (st == NULL)
75279377Simp		st = &fstbuf;
76279377Simp
77279377Simp	/* ignore SFF_SAFEDIRPATH if we are debugging */
78279377Simp	if (RealUid != 0 && RunAsUid == RealUid)
79279377Simp		flags &= ~SFF_SAFEDIRPATH;
80279377Simp
81279377Simp	/* first check to see if the file exists at all */
82279377Simp# if HASLSTAT
83279377Simp	if ((bitset(SFF_NOSLINK, flags) ? lstat(fn, st)
84279377Simp					: stat(fn, st)) < 0)
85279377Simp# else /* HASLSTAT */
86279377Simp	if (stat(fn, st) < 0)
87279377Simp# endif /* HASLSTAT */
88279377Simp	{
89279377Simp		file_errno = errno;
90279377Simp	}
91279377Simp	else if (bitset(SFF_SETUIDOK, flags) &&
92279377Simp		 !bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode) &&
93279377Simp		 S_ISREG(st->st_mode))
94279377Simp	{
95279377Simp		/*
96279377Simp		**  If final file is set-user-ID, run as the owner of that
97279377Simp		**  file.  Gotta be careful not to reveal anything too
98279377Simp		**  soon here!
99279377Simp		*/
100279377Simp
101279377Simp# ifdef SUID_ROOT_FILES_OK
102279377Simp		if (bitset(S_ISUID, st->st_mode))
103279377Simp# else /* SUID_ROOT_FILES_OK */
104279377Simp		if (bitset(S_ISUID, st->st_mode) && st->st_uid != 0 &&
105279377Simp		    st->st_uid != TrustedUid)
106279377Simp# endif /* SUID_ROOT_FILES_OK */
107279377Simp		{
108279377Simp			uid = st->st_uid;
109279377Simp			user = NULL;
110279377Simp		}
111279377Simp# ifdef SUID_ROOT_FILES_OK
112279377Simp		if (bitset(S_ISGID, st->st_mode))
113279377Simp# else /* SUID_ROOT_FILES_OK */
114279377Simp		if (bitset(S_ISGID, st->st_mode) && st->st_gid != 0)
115279377Simp# endif /* SUID_ROOT_FILES_OK */
116279377Simp			gid = st->st_gid;
117279377Simp	}
118279377Simp
119279377Simp	checkpath = !bitset(SFF_NOPATHCHECK, flags) ||
120279377Simp		    (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags));
121279377Simp	if (bitset(SFF_NOWLINK, flags) && !bitset(SFF_SAFEDIRPATH, flags))
122279377Simp	{
123279377Simp		int ret;
124279377Simp
125279377Simp		/* check the directory */
126279377Simp		p = strrchr(fn, '/');
127279377Simp		if (p == NULL)
128279377Simp		{
129279377Simp			ret = safedirpath(".", uid, gid, user,
130279377Simp					  flags|SFF_SAFEDIRPATH, 0, 0);
131279377Simp		}
132279377Simp		else
133279377Simp		{
134279377Simp			*p = '\0';
135279377Simp			ret = safedirpath(fn, uid, gid, user,
136279377Simp					  flags|SFF_SAFEDIRPATH, 0, 0);
137279377Simp			*p = '/';
138279377Simp		}
139279377Simp		if (ret == 0)
140279377Simp		{
141279377Simp			/* directory is safe */
142279377Simp			checkpath = false;
143279377Simp		}
144279377Simp		else
145279377Simp		{
146279377Simp# if HASLSTAT
147279377Simp			/* Need lstat() information if called stat() before */
148279377Simp			if (!bitset(SFF_NOSLINK, flags) && lstat(fn, st) < 0)
149279377Simp			{
150279377Simp				ret = errno;
151279377Simp				if (tTd(44, 4))
152279377Simp					sm_dprintf("\t%s\n", sm_errstring(ret));
153279377Simp				return ret;
154279377Simp			}
155279377Simp# endif /* HASLSTAT */
156279377Simp			/* directory is writable: disallow links */
157279377Simp			flags |= SFF_NOLINK;
158279377Simp		}
159279377Simp	}
160279377Simp
161279377Simp	if (checkpath)
162279377Simp	{
163279377Simp		int ret;
164279377Simp
165279377Simp		p = strrchr(fn, '/');
166279377Simp		if (p == NULL)
167279377Simp		{
168279377Simp			ret = safedirpath(".", uid, gid, user, flags, 0, 0);
169279377Simp		}
170279377Simp		else
171279377Simp		{
172279377Simp			*p = '\0';
173279377Simp			ret = safedirpath(fn, uid, gid, user, flags, 0, 0);
174279377Simp			*p = '/';
175279377Simp		}
176279377Simp		if (ret != 0)
177279377Simp			return ret;
178279377Simp	}
179279377Simp
180279377Simp	/*
181279377Simp	**  If the target file doesn't exist, check the directory to
182279377Simp	**  ensure that it is writable by this user.
183279377Simp	*/
184279377Simp
185279377Simp	if (file_errno != 0)
186279377Simp	{
187279377Simp		int ret = file_errno;
188279377Simp		char *dir = fn;
189279377Simp
190279377Simp		if (tTd(44, 4))
191279377Simp			sm_dprintf("\t%s\n", sm_errstring(ret));
192279377Simp
193279377Simp		errno = 0;
194279377Simp		if (!bitset(SFF_CREAT, flags) || file_errno != ENOENT)
195279377Simp			return ret;
196279377Simp
197279377Simp		/* check to see if legal to create the file */
198279377Simp		p = strrchr(dir, '/');
199279377Simp		if (p == NULL)
200279377Simp			dir = ".";
201279377Simp		else if (p == dir)
202279377Simp			dir = "/";
203279377Simp		else
204279377Simp			*p = '\0';
205279377Simp		if (stat(dir, &stbuf) >= 0)
206279377Simp		{
207279377Simp			int md = S_IWRITE|S_IEXEC;
208279377Simp
209279377Simp			ret = 0;
210279377Simp			if (stbuf.st_uid == uid)
211279377Simp				/* EMPTY */
212279377Simp				;
213279377Simp			else if (uid == 0 && stbuf.st_uid == TrustedUid)
214279377Simp				/* EMPTY */
215279377Simp				;
216279377Simp			else
217279377Simp			{
218279377Simp				md >>= 3;
219279377Simp				if (stbuf.st_gid == gid)
220279377Simp					/* EMPTY */
221279377Simp					;
222279377Simp# ifndef NO_GROUP_SET
223279377Simp				else if (user != NULL && !DontInitGroups &&
224279377Simp					 ((gr != NULL &&
225279377Simp					   gr->gr_gid == stbuf.st_gid) ||
226279377Simp					  (gr = getgrgid(stbuf.st_gid)) != NULL))
227279377Simp				{
228279377Simp					register char **gp;
229279377Simp
230279377Simp					for (gp = gr->gr_mem; *gp != NULL; gp++)
231279377Simp						if (strcmp(*gp, user) == 0)
232279377Simp							break;
233279377Simp					if (*gp == NULL)
234279377Simp						md >>= 3;
235279377Simp				}
236279377Simp# endif /* ! NO_GROUP_SET */
237279377Simp				else
238279377Simp					md >>= 3;
239279377Simp			}
240279377Simp			if ((stbuf.st_mode & md) != md)
241279377Simp				ret = errno = EACCES;
242279377Simp		}
243279377Simp		else
244279377Simp			ret = errno;
245279377Simp		if (tTd(44, 4))
246279377Simp			sm_dprintf("\t[final dir %s uid %d mode %lo] %s\n",
247279377Simp				dir, (int) stbuf.st_uid,
248279377Simp				(unsigned long) stbuf.st_mode,
249279377Simp				sm_errstring(ret));
250279377Simp		if (p != NULL)
251279377Simp			*p = '/';
252279377Simp		st->st_mode = ST_MODE_NOFILE;
253279377Simp		return ret;
254279377Simp	}
255279377Simp
256279377Simp# ifdef S_ISLNK
257279377Simp	if (bitset(SFF_NOSLINK, flags) && S_ISLNK(st->st_mode))
258279377Simp	{
259279377Simp		if (tTd(44, 4))
260279377Simp			sm_dprintf("\t[slink mode %lo]\tE_SM_NOSLINK\n",
261279377Simp				(unsigned long) st->st_mode);
262279377Simp		return E_SM_NOSLINK;
263279377Simp	}
264279377Simp# endif /* S_ISLNK */
265279377Simp	if (bitset(SFF_REGONLY, flags) && !S_ISREG(st->st_mode))
266279377Simp	{
267279377Simp		if (tTd(44, 4))
268279377Simp			sm_dprintf("\t[non-reg mode %lo]\tE_SM_REGONLY\n",
269279377Simp				(unsigned long) st->st_mode);
270279377Simp		return E_SM_REGONLY;
271279377Simp	}
272279377Simp	if (bitset(SFF_NOGWFILES, flags) &&
273279377Simp	    bitset(S_IWGRP, st->st_mode))
274279377Simp	{
275279377Simp		if (tTd(44, 4))
276279377Simp			sm_dprintf("\t[write bits %lo]\tE_SM_GWFILE\n",
277279377Simp				(unsigned long) st->st_mode);
278279377Simp		return E_SM_GWFILE;
279279377Simp	}
280279377Simp	if (bitset(SFF_NOWWFILES, flags) &&
281279377Simp	    bitset(S_IWOTH, st->st_mode))
282279377Simp	{
283279377Simp		if (tTd(44, 4))
284279377Simp			sm_dprintf("\t[write bits %lo]\tE_SM_WWFILE\n",
285279377Simp				(unsigned long) st->st_mode);
286279377Simp		return E_SM_WWFILE;
287279377Simp	}
288279377Simp	if (bitset(SFF_NOGRFILES, flags) && bitset(S_IRGRP, st->st_mode))
289279377Simp	{
290279377Simp		if (tTd(44, 4))
291279377Simp			sm_dprintf("\t[read bits %lo]\tE_SM_GRFILE\n",
292279377Simp				(unsigned long) st->st_mode);
293279377Simp		return E_SM_GRFILE;
294279377Simp	}
295279377Simp	if (bitset(SFF_NOWRFILES, flags) && bitset(S_IROTH, st->st_mode))
296279377Simp	{
297279377Simp		if (tTd(44, 4))
298279377Simp			sm_dprintf("\t[read bits %lo]\tE_SM_WRFILE\n",
299279377Simp				(unsigned long) st->st_mode);
300279377Simp		return E_SM_WRFILE;
301279377Simp	}
302279377Simp	if (!bitset(SFF_EXECOK, flags) &&
303279377Simp	    bitset(S_IWUSR|S_IWGRP|S_IWOTH, mode) &&
304279377Simp	    bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode))
305279377Simp	{
306279377Simp		if (tTd(44, 4))
307279377Simp			sm_dprintf("\t[exec bits %lo]\tE_SM_ISEXEC]\n",
308279377Simp				(unsigned long) st->st_mode);
309279377Simp		return E_SM_ISEXEC;
310279377Simp	}
311279377Simp	if (bitset(SFF_NOHLINK, flags) && st->st_nlink != 1)
312279377Simp	{
313279377Simp		if (tTd(44, 4))
314279377Simp			sm_dprintf("\t[link count %d]\tE_SM_NOHLINK\n",
315279377Simp				(int) st->st_nlink);
316279377Simp		return E_SM_NOHLINK;
317279377Simp	}
318279377Simp
319279377Simp	if (uid == 0 && bitset(SFF_OPENASROOT, flags))
320279377Simp		/* EMPTY */
321279377Simp		;
322279377Simp	else if (uid == 0 && !bitset(SFF_ROOTOK, flags))
323279377Simp		mode >>= 6;
324279377Simp	else if (st->st_uid == uid)
325279377Simp		/* EMPTY */
326279377Simp		;
327279377Simp	else if (uid == 0 && st->st_uid == TrustedUid)
328279377Simp		/* EMPTY */
329279377Simp		;
330279377Simp	else
331279377Simp	{
332279377Simp		mode >>= 3;
333279377Simp		if (st->st_gid == gid)
334279377Simp			/* EMPTY */
335279377Simp			;
336279377Simp# ifndef NO_GROUP_SET
337279377Simp		else if (user != NULL && !DontInitGroups &&
338279377Simp			 ((gr != NULL && gr->gr_gid == st->st_gid) ||
339279377Simp			  (gr = getgrgid(st->st_gid)) != NULL))
340279377Simp		{
341279377Simp			register char **gp;
342279377Simp
343279377Simp			for (gp = gr->gr_mem; *gp != NULL; gp++)
344279377Simp				if (strcmp(*gp, user) == 0)
345279377Simp					break;
346279377Simp			if (*gp == NULL)
347279377Simp				mode >>= 3;
348279377Simp		}
349279377Simp# endif /* ! NO_GROUP_SET */
350279377Simp		else
351279377Simp			mode >>= 3;
352279377Simp	}
353279377Simp	if (tTd(44, 4))
354279377Simp		sm_dprintf("\t[uid %d, nlink %d, stat %lo, mode %lo] ",
355279377Simp			(int) st->st_uid, (int) st->st_nlink,
356279377Simp			(unsigned long) st->st_mode, (unsigned long) mode);
357279377Simp	if ((st->st_uid == uid || st->st_uid == 0 ||
358279377Simp	     st->st_uid == TrustedUid ||
359279377Simp	     !bitset(SFF_MUSTOWN, flags)) &&
360279377Simp	    (st->st_mode & mode) == mode)
361279377Simp	{
362279377Simp		if (tTd(44, 4))
363279377Simp			sm_dprintf("\tOK\n");
364279377Simp		return 0;
365279377Simp	}
366279377Simp	if (tTd(44, 4))
367279377Simp		sm_dprintf("\tEACCES\n");
368279377Simp	return EACCES;
369279377Simp}
370279377Simp/*
371279377Simp**  SAFEDIRPATH -- check to make sure a path to a directory is safe
372279377Simp**
373279377Simp**	Safe means not writable and owned by the right folks.
374279377Simp**
375279377Simp**	Parameters:
376279377Simp**		fn -- filename to check.
377279377Simp**		uid -- user id to compare against.
378279377Simp**		gid -- group id to compare against.
379279377Simp**		user -- user name to compare against (used for group
380279377Simp**			sets).
381279377Simp**		flags -- modifiers:
382279377Simp**			SFF_ROOTOK -- ok to use root permissions to open.
383279377Simp**			SFF_SAFEDIRPATH -- writable directories are considered
384279377Simp**				to be fatal errors.
385279377Simp**		level -- symlink recursive level.
386279377Simp**		offset -- offset into fn to start checking from.
387279377Simp**
388279377Simp**	Returns:
389279377Simp**		0 -- if the directory path is "safe".
390279377Simp**		else -- an error number associated with the path.
391279377Simp*/
392279377Simp
393279377Simpint
394279377Simpsafedirpath(fn, uid, gid, user, flags, level, offset)
395279377Simp	char *fn;
396279377Simp	UID_T uid;
397279377Simp	GID_T gid;
398279377Simp	char *user;
399279377Simp	long flags;
400279377Simp	int level;
401279377Simp	int offset;
402279377Simp{
403279377Simp	int ret = 0;
404279377Simp	int mode = S_IWOTH;
405279377Simp	char save = '\0';
406279377Simp	char *saveptr = NULL;
407279377Simp	char *p, *enddir;
408279377Simp	register struct group *gr = NULL;
409279377Simp	char s[MAXLINKPATHLEN];
410279377Simp	struct stat stbuf;
411279377Simp
412279377Simp	/* make sure we aren't in a symlink loop */
413279377Simp	if (level > MAXSYMLINKS)
414279377Simp		return ELOOP;
415279377Simp
416279377Simp	if (level < 0 || offset < 0 || offset > strlen(fn))
417279377Simp		return EINVAL;
418279377Simp
419279377Simp	/* special case root directory */
420279377Simp	if (*fn == '\0')
421279377Simp		fn = "/";
422279377Simp
423279377Simp	if (tTd(44, 4))
424279377Simp		sm_dprintf("safedirpath(%s, uid=%ld, gid=%ld, flags=%lx, level=%d, offset=%d):\n",
425279377Simp			fn, (long) uid, (long) gid, flags, level, offset);
426279377Simp
427279377Simp	if (!bitnset(DBS_GROUPWRITABLEDIRPATHSAFE, DontBlameSendmail))
428279377Simp		mode |= S_IWGRP;
429279377Simp
430279377Simp	/* Make a modifiable copy of the filename */
431279377Simp	if (sm_strlcpy(s, fn, sizeof s) >= sizeof s)
432279377Simp		return EINVAL;
433279377Simp
434279377Simp	p = s + offset;
435279377Simp	while (p != NULL)
436279377Simp	{
437279377Simp		/* put back character */
438279377Simp		if (saveptr != NULL)
439279377Simp		{
440279377Simp			*saveptr = save;
441279377Simp			saveptr = NULL;
442279377Simp			p++;
443279377Simp		}
444279377Simp
445279377Simp		if (*p == '\0')
446279377Simp			break;
447279377Simp
448279377Simp		p = strchr(p, '/');
449279377Simp
450279377Simp		/* Special case for root directory */
451279377Simp		if (p == s)
452279377Simp		{
453279377Simp			save = *(p + 1);
454279377Simp			saveptr = p + 1;
455279377Simp			*(p + 1) = '\0';
456279377Simp		}
457279377Simp		else if (p != NULL)
458279377Simp		{
459279377Simp			save = *p;
460279377Simp			saveptr = p;
461279377Simp			*p = '\0';
462279377Simp		}
463279377Simp
464279377Simp		/* Heuristic: . and .. have already been checked */
465279377Simp		enddir = strrchr(s, '/');
466279377Simp		if (enddir != NULL &&
467279377Simp		    (strcmp(enddir, "/..") == 0 ||
468279377Simp		     strcmp(enddir, "/.") == 0))
469279377Simp			continue;
470279377Simp
471279377Simp		if (tTd(44, 20))
472279377Simp			sm_dprintf("\t[dir %s]\n", s);
473279377Simp
474279377Simp# if HASLSTAT
475279377Simp		ret = lstat(s, &stbuf);
476279377Simp# else /* HASLSTAT */
477279377Simp		ret = stat(s, &stbuf);
478279377Simp# endif /* HASLSTAT */
479279377Simp		if (ret < 0)
480279377Simp		{
481279377Simp			ret = errno;
482279377Simp			break;
483279377Simp		}
484279377Simp
485279377Simp# ifdef S_ISLNK
486279377Simp		/* Follow symlinks */
487279377Simp		if (S_ISLNK(stbuf.st_mode))
488279377Simp		{
489279377Simp			int linklen;
490279377Simp			char *target;
491279377Simp			char buf[MAXPATHLEN];
492279377Simp
493279377Simp			memset(buf, '\0', sizeof buf);
494279377Simp			linklen = readlink(s, buf, sizeof buf);
495279377Simp			if (linklen < 0)
496279377Simp			{
497279377Simp				ret = errno;
498279377Simp				break;
499279377Simp			}
500279377Simp			if (linklen >= sizeof buf)
501279377Simp			{
502279377Simp				/* file name too long for buffer */
503279377Simp				ret = errno = EINVAL;
504279377Simp				break;
505279377Simp			}
506279377Simp
507279377Simp			offset = 0;
508279377Simp			if (*buf == '/')
509279377Simp			{
510279377Simp				target = buf;
511279377Simp
512279377Simp				/* If path is the same, avoid rechecks */
513279377Simp				while (s[offset] == buf[offset] &&
514279377Simp				       s[offset] != '\0')
515279377Simp					offset++;
516279377Simp
517279377Simp				if (s[offset] == '\0' && buf[offset] == '\0')
518279377Simp				{
519279377Simp					/* strings match, symlink loop */
520279377Simp					return ELOOP;
521279377Simp				}
522279377Simp
523279377Simp				/* back off from the mismatch */
524279377Simp				if (offset > 0)
525279377Simp					offset--;
526279377Simp
527279377Simp				/* Make sure we are at a directory break */
528279377Simp				if (offset > 0 &&
529279377Simp				    s[offset] != '/' &&
530279377Simp				    s[offset] != '\0')
531279377Simp				{
532279377Simp					while (buf[offset] != '/' &&
533279377Simp					       offset > 0)
534279377Simp						offset--;
535279377Simp				}
536279377Simp				if (offset > 0 &&
537279377Simp				    s[offset] == '/' &&
538279377Simp				    buf[offset] == '/')
539279377Simp				{
540279377Simp					/* Include the trailing slash */
541279377Simp					offset++;
542279377Simp				}
543279377Simp			}
544279377Simp			else
545279377Simp			{
546279377Simp				char *sptr;
547279377Simp				char fullbuf[MAXLINKPATHLEN];
548279377Simp
549279377Simp				sptr = strrchr(s, '/');
550279377Simp				if (sptr != NULL)
551279377Simp				{
552279377Simp					*sptr = '\0';
553279377Simp					offset = sptr + 1 - s;
554279377Simp					if (sm_strlcpyn(fullbuf,
555279377Simp							sizeof fullbuf, 2,
556279377Simp							s, "/") >=
557279377Simp						sizeof fullbuf ||
558279377Simp					    sm_strlcat(fullbuf, buf,
559279377Simp						       sizeof fullbuf) >=
560279377Simp						sizeof fullbuf)
561279377Simp					{
562279377Simp						ret = EINVAL;
563279377Simp						break;
564279377Simp					}
565279377Simp					*sptr = '/';
566279377Simp				}
567279377Simp				else
568279377Simp				{
569279377Simp					if (sm_strlcpy(fullbuf, buf,
570279377Simp						       sizeof fullbuf) >=
571279377Simp						sizeof fullbuf)
572279377Simp					{
573279377Simp						ret = EINVAL;
574279377Simp						break;
575279377Simp					}
576279377Simp				}
577279377Simp				target = fullbuf;
578279377Simp			}
579279377Simp			ret = safedirpath(target, uid, gid, user, flags,
580279377Simp					  level + 1, offset);
581279377Simp			if (ret != 0)
582279377Simp				break;
583279377Simp
584279377Simp			/* Don't check permissions on the link file itself */
585279377Simp			continue;
586279377Simp		}
587279377Simp#endif /* S_ISLNK */
588279377Simp
589279377Simp		if ((uid == 0 || bitset(SFF_SAFEDIRPATH, flags)) &&
590279377Simp#ifdef S_ISVTX
591279377Simp		    !(bitnset(DBS_TRUSTSTICKYBIT, DontBlameSendmail) &&
592279377Simp		      bitset(S_ISVTX, stbuf.st_mode)) &&
593279377Simp#endif /* S_ISVTX */
594279377Simp		    bitset(mode, stbuf.st_mode))
595279377Simp		{
596279377Simp			if (tTd(44, 4))
597279377Simp				sm_dprintf("\t[dir %s] mode %lo ",
598279377Simp					s, (unsigned long) stbuf.st_mode);
599279377Simp			if (bitset(SFF_SAFEDIRPATH, flags))
600279377Simp			{
601279377Simp				if (bitset(S_IWOTH, stbuf.st_mode))
602279377Simp					ret = E_SM_WWDIR;
603279377Simp				else
604279377Simp					ret = E_SM_GWDIR;
605279377Simp				if (tTd(44, 4))
606279377Simp					sm_dprintf("FATAL\n");
607279377Simp				break;
608279377Simp			}
609279377Simp			if (tTd(44, 4))
610279377Simp				sm_dprintf("WARNING\n");
611279377Simp			if (Verbose > 1)
612279377Simp				message("051 WARNING: %s writable directory %s",
613279377Simp					bitset(S_IWOTH, stbuf.st_mode)
614279377Simp					   ? "World"
615279377Simp					   : "Group",
616279377Simp					s);
617279377Simp		}
618279377Simp		if (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags))
619279377Simp		{
620279377Simp			if (bitset(S_IXOTH, stbuf.st_mode))
621279377Simp				continue;
622279377Simp			ret = EACCES;
623279377Simp			break;
624279377Simp		}
625279377Simp
626279377Simp		/*
627279377Simp		**  Let OS determine access to file if we are not
628279377Simp		**  running as a privileged user.  This allows ACLs
629279377Simp		**  to work.  Also, if opening as root, assume we can
630279377Simp		**  scan the directory.
631279377Simp		*/
632279377Simp		if (geteuid() != 0 || bitset(SFF_OPENASROOT, flags))
633279377Simp			continue;
634279377Simp
635279377Simp		if (stbuf.st_uid == uid &&
636279377Simp		    bitset(S_IXUSR, stbuf.st_mode))
637279377Simp			continue;
638279377Simp		if (stbuf.st_gid == gid &&
639279377Simp		    bitset(S_IXGRP, stbuf.st_mode))
640279377Simp			continue;
641279377Simp# ifndef NO_GROUP_SET
642279377Simp		if (user != NULL && !DontInitGroups &&
643279377Simp		    ((gr != NULL && gr->gr_gid == stbuf.st_gid) ||
644279377Simp		     (gr = getgrgid(stbuf.st_gid)) != NULL))
645279377Simp		{
646279377Simp			register char **gp;
647279377Simp
648279377Simp			for (gp = gr->gr_mem; gp != NULL && *gp != NULL; gp++)
649279377Simp				if (strcmp(*gp, user) == 0)
650279377Simp					break;
651279377Simp			if (gp != NULL && *gp != NULL &&
652279377Simp			    bitset(S_IXGRP, stbuf.st_mode))
653279377Simp				continue;
654279377Simp		}
655279377Simp# endif /* ! NO_GROUP_SET */
656279377Simp		if (!bitset(S_IXOTH, stbuf.st_mode))
657279377Simp		{
658279377Simp			ret = EACCES;
659279377Simp			break;
660279377Simp		}
661279377Simp	}
662279377Simp	if (tTd(44, 4))
663279377Simp		sm_dprintf("\t[dir %s] %s\n", fn,
664279377Simp			ret == 0 ? "OK" : sm_errstring(ret));
665279377Simp	return ret;
666279377Simp}
667279377Simp/*
668279377Simp**  SAFEOPEN -- do a file open with extra checking
669279377Simp**
670279377Simp**	Parameters:
671279377Simp**		fn -- the file name to open.
672279377Simp**		omode -- the open-style mode flags.
673279377Simp**		cmode -- the create-style mode flags.
674279377Simp**		sff -- safefile flags.
675279377Simp**
676279377Simp**	Returns:
677279377Simp**		Same as open.
678279377Simp*/
679279377Simp
680279377Simpint
681279377Simpsafeopen(fn, omode, cmode, sff)
682279377Simp	char *fn;
683279377Simp	int omode;
684279377Simp	int cmode;
685279377Simp	long sff;
686279377Simp{
687279377Simp	int rval;
688279377Simp	int fd;
689279377Simp	int smode;
690279377Simp	struct stat stb;
691279377Simp
692279377Simp	if (tTd(44, 10))
693279377Simp		sm_dprintf("safeopen: fn=%s, omode=%x, cmode=%x, sff=%lx\n",
694279377Simp			   fn, omode, cmode, sff);
695279377Simp
696279377Simp	if (bitset(O_CREAT, omode))
697279377Simp		sff |= SFF_CREAT;
698279377Simp	omode &= ~O_CREAT;
699279377Simp	smode = 0;
700279377Simp	switch (omode & O_ACCMODE)
701279377Simp	{
702279377Simp	  case O_RDONLY:
703279377Simp		smode = S_IREAD;
704279377Simp		break;
705279377Simp
706279377Simp	  case O_WRONLY:
707279377Simp		smode = S_IWRITE;
708279377Simp		break;
709279377Simp
710279377Simp	  case O_RDWR:
711279377Simp		smode = S_IREAD|S_IWRITE;
712279377Simp		break;
713279377Simp
714279377Simp	  default:
715279377Simp		smode = 0;
716279377Simp		break;
717279377Simp	}
718279377Simp	if (bitset(SFF_OPENASROOT, sff))
719279377Simp		rval = safefile(fn, RunAsUid, RunAsGid, RunAsUserName,
720279377Simp				sff, smode, &stb);
721279377Simp	else
722279377Simp		rval = safefile(fn, RealUid, RealGid, RealUserName,
723279377Simp				sff, smode, &stb);
724279377Simp	if (rval != 0)
725279377Simp	{
726279377Simp		errno = rval;
727279377Simp		return -1;
728279377Simp	}
729279377Simp	if (stb.st_mode == ST_MODE_NOFILE && bitset(SFF_CREAT, sff))
730279377Simp		omode |= O_CREAT | (bitset(SFF_NOTEXCL, sff) ? 0 : O_EXCL);
731279377Simp	else if (bitset(SFF_CREAT, sff) && bitset(O_EXCL, omode))
732279377Simp	{
733279377Simp		/* The file exists so an exclusive create would fail */
734279377Simp		errno = EEXIST;
735279377Simp		return -1;
736279377Simp	}
737279377Simp
738279377Simp	fd = dfopen(fn, omode, cmode, sff);
739279377Simp	if (fd < 0)
740279377Simp		return fd;
741279377Simp	if (filechanged(fn, fd, &stb))
742279377Simp	{
743279377Simp		syserr("554 5.3.0 cannot open: file %s changed after open", fn);
744279377Simp		(void) close(fd);
745279377Simp		errno = E_SM_FILECHANGE;
746279377Simp		return -1;
747279377Simp	}
748279377Simp	return fd;
749279377Simp}
750279377Simp/*
751279377Simp**  SAFEFOPEN -- do a file open with extra checking
752279377Simp**
753279377Simp**	Parameters:
754279377Simp**		fn -- the file name to open.
755279377Simp**		omode -- the open-style mode flags.
756279377Simp**		cmode -- the create-style mode flags.
757279377Simp**		sff -- safefile flags.
758279377Simp**
759279377Simp**	Returns:
760279377Simp**		Same as fopen.
761279377Simp*/
762279377Simp
763279377SimpSM_FILE_T *
764279377Simpsafefopen(fn, omode, cmode, sff)
765279377Simp	char *fn;
766279377Simp	int omode;
767279377Simp	int cmode;
768279377Simp	long sff;
769279377Simp{
770279377Simp	int fd;
771279377Simp	int save_errno;
772279377Simp	SM_FILE_T *fp;
773279377Simp	int fmode;
774279377Simp
775279377Simp	switch (omode & O_ACCMODE)
776279377Simp	{
777279377Simp	  case O_RDONLY:
778279377Simp		fmode = SM_IO_RDONLY;
779279377Simp		break;
780279377Simp
781279377Simp	  case O_WRONLY:
782279377Simp		if (bitset(O_APPEND, omode))
783279377Simp			fmode = SM_IO_APPEND;
784279377Simp		else
785279377Simp			fmode = SM_IO_WRONLY;
786279377Simp		break;
787279377Simp
788279377Simp	  case O_RDWR:
789279377Simp		if (bitset(O_TRUNC, omode))
790279377Simp			fmode = SM_IO_RDWRTR;
791279377Simp		else if (bitset(O_APPEND, omode))
792279377Simp			fmode = SM_IO_APPENDRW;
793279377Simp		else
794279377Simp			fmode = SM_IO_RDWR;
795279377Simp		break;
796279377Simp
797279377Simp	  default:
798279377Simp		syserr("554 5.3.5 safefopen: unknown omode %o", omode);
799279377Simp		fmode = 0;
800279377Simp	}
801279377Simp	fd = safeopen(fn, omode, cmode, sff);
802279377Simp	if (fd < 0)
803279377Simp	{
804279377Simp		save_errno = errno;
805279377Simp		if (tTd(44, 10))
806279377Simp			sm_dprintf("safefopen: safeopen failed: %s\n",
807279377Simp				   sm_errstring(errno));
808279377Simp		errno = save_errno;
809279377Simp		return NULL;
810279377Simp	}
811279377Simp	fp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
812279377Simp			(void *) &fd, fmode, NULL);
813279377Simp	if (fp != NULL)
814279377Simp		return fp;
815279377Simp
816279377Simp	save_errno = errno;
817279377Simp	if (tTd(44, 10))
818279377Simp	{
819279377Simp		sm_dprintf("safefopen: fdopen(%s, %d) failed: omode=%x, sff=%lx, err=%s\n",
820279377Simp			   fn, fmode, omode, sff, sm_errstring(errno));
821279377Simp	}
822279377Simp	(void) close(fd);
823279377Simp	errno = save_errno;
824279377Simp	return NULL;
825279377Simp}
826279377Simp/*
827279377Simp**  FILECHANGED -- check to see if file changed after being opened
828279377Simp**
829279377Simp**	Parameters:
830279377Simp**		fn -- pathname of file to check.
831279377Simp**		fd -- file descriptor to check.
832279377Simp**		stb -- stat structure from before open.
833279377Simp**
834279377Simp**	Returns:
835279377Simp**		true -- if a problem was detected.
836279377Simp**		false -- if this file is still the same.
837279377Simp*/
838279377Simp
839279377Simpbool
840279377Simpfilechanged(fn, fd, stb)
841279377Simp	char *fn;
842279377Simp	int fd;
843279377Simp	struct stat *stb;
844279377Simp{
845279377Simp	struct stat sta;
846279377Simp
847279377Simp	if (stb->st_mode == ST_MODE_NOFILE)
848279377Simp	{
849279377Simp# if HASLSTAT && BOGUS_O_EXCL
850279377Simp		/* only necessary if exclusive open follows symbolic links */
851279377Simp		if (lstat(fn, stb) < 0 || stb->st_nlink != 1)
852279377Simp			return true;
853279377Simp# else /* HASLSTAT && BOGUS_O_EXCL */
854279377Simp		return false;
855279377Simp# endif /* HASLSTAT && BOGUS_O_EXCL */
856279377Simp	}
857279377Simp	if (fstat(fd, &sta) < 0)
858279377Simp		return true;
859279377Simp
860279377Simp	if (sta.st_nlink != stb->st_nlink ||
861279377Simp	    sta.st_dev != stb->st_dev ||
862279377Simp	    sta.st_ino != stb->st_ino ||
863279377Simp# if HAS_ST_GEN && 0		/* AFS returns garbage in st_gen */
864279377Simp	    sta.st_gen != stb->st_gen ||
865279377Simp# endif /* HAS_ST_GEN && 0 */
866279377Simp	    sta.st_uid != stb->st_uid ||
867279377Simp	    sta.st_gid != stb->st_gid)
868279377Simp	{
869279377Simp		if (tTd(44, 8))
870279377Simp		{
871279377Simp			sm_dprintf("File changed after opening:\n");
872279377Simp			sm_dprintf(" nlink	= %ld/%ld\n",
873279377Simp				(long) stb->st_nlink, (long) sta.st_nlink);
874279377Simp			sm_dprintf(" dev	= %ld/%ld\n",
875279377Simp				(long) stb->st_dev, (long) sta.st_dev);
876279377Simp			sm_dprintf(" ino	= %llu/%llu\n",
877279377Simp				(ULONGLONG_T) stb->st_ino,
878279377Simp				(ULONGLONG_T) sta.st_ino);
879279377Simp# if HAS_ST_GEN
880279377Simp			sm_dprintf(" gen	= %ld/%ld\n",
881279377Simp				(long) stb->st_gen, (long) sta.st_gen);
882279377Simp# endif /* HAS_ST_GEN */
883279377Simp			sm_dprintf(" uid	= %ld/%ld\n",
884279377Simp				(long) stb->st_uid, (long) sta.st_uid);
885279377Simp			sm_dprintf(" gid	= %ld/%ld\n",
886279377Simp				(long) stb->st_gid, (long) sta.st_gid);
887279377Simp		}
888279377Simp		return true;
889279377Simp	}
890279377Simp
891279377Simp	return false;
892279377Simp}
893279377Simp/*
894279377Simp**  DFOPEN -- determined file open
895279377Simp**
896279377Simp**	This routine has the semantics of open, except that it will
897279377Simp**	keep trying a few times to make this happen.  The idea is that
898279377Simp**	on very loaded systems, we may run out of resources (inodes,
899279377Simp**	whatever), so this tries to get around it.
900279377Simp*/
901279377Simp
902279377Simpint
903279377Simpdfopen(filename, omode, cmode, sff)
904279377Simp	char *filename;
905279377Simp	int omode;
906279377Simp	int cmode;
907279377Simp	long sff;
908279377Simp{
909279377Simp	register int tries;
910279377Simp	int fd = -1;
911279377Simp	struct stat st;
912279377Simp
913279377Simp	for (tries = 0; tries < 10; tries++)
914279377Simp	{
915279377Simp		(void) sleep((unsigned) (10 * tries));
916279377Simp		errno = 0;
917279377Simp		fd = open(filename, omode, cmode);
918279377Simp		if (fd >= 0)
919279377Simp			break;
920279377Simp		switch (errno)
921279377Simp		{
922279377Simp		  case ENFILE:		/* system file table full */
923279377Simp		  case EINTR:		/* interrupted syscall */
924279377Simp#ifdef ETXTBSY
925279377Simp		  case ETXTBSY:		/* Apollo: net file locked */
926279377Simp#endif /* ETXTBSY */
927279377Simp			continue;
928279377Simp		}
929279377Simp		break;
930279377Simp	}
931279377Simp	if (!bitset(SFF_NOLOCK, sff) &&
932279377Simp	    fd >= 0 &&
933279377Simp	    fstat(fd, &st) >= 0 &&
934279377Simp	    S_ISREG(st.st_mode))
935279377Simp	{
936279377Simp		int locktype;
937279377Simp
938279377Simp		/* lock the file to avoid accidental conflicts */
939279377Simp		if ((omode & O_ACCMODE) != O_RDONLY)
940279377Simp			locktype = LOCK_EX;
941279377Simp		else
942279377Simp			locktype = LOCK_SH;
943279377Simp		if (!lockfile(fd, filename, NULL, locktype))
944279377Simp		{
945279377Simp			int save_errno = errno;
946279377Simp
947279377Simp			(void) close(fd);
948279377Simp			fd = -1;
949279377Simp			errno = save_errno;
950279377Simp		}
951279377Simp		else
952279377Simp			errno = 0;
953279377Simp	}
954279377Simp	return fd;
955279377Simp}
956279377Simp