*/
#ifndef lint
-static char sccsid[] = "@(#)recipient.c 8.27 (Berkeley) %G%";
+static char sccsid[] = "@(#)recipient.c 8.54 (Berkeley) %G%";
#endif /* not lint */
# include "sendmail.h"
** none.
*/
-# define MAXRCRSN 10
+#define MAXRCRSN 10 /* maximum levels of alias recursion */
+
+/* q_flags bits inherited from ctladdr */
+#define QINHERITEDBITS (QPINGONSUCCESS|QPINGONFAILURE|QPINGONDELAY|QHAS_RET_PARAM|QRET_HDRS)
ADDRESS *
sendto(list, copyf, ctladdr, qflags)
char delimiter; /* the address delimiter */
int naddrs;
char *oldto = e->e_to;
+ static char *bufp = NULL;
+ static int buflen;
+ char buf[MAXNAME + 1];
ADDRESS *sibl; /* sibling pointer in tree */
ADDRESS *prev; /* previous sibling */
al = NULL;
naddrs = 0;
- for (p = list; *p != '\0'; )
+ if (buf == NULL)
+ {
+ bufp = buf;
+ buflen = sizeof buf - 1;
+ }
+ if (strlen(list) > buflen)
+ {
+ /* allocate additional space */
+ if (bufp != buf)
+ free(bufp);
+ buflen = strlen(list);
+ bufp = malloc(buflen + 1);
+ }
+ strcpy(bufp, list);
+
+ for (p = bufp; *p != '\0'; )
{
auto char *delimptr;
register ADDRESS *a;
register char *p;
bool quoted = FALSE; /* set if the addr has a quote bit */
int findusercount = 0;
- char buf[MAXNAME]; /* unquoted image of the user name */
+ int i;
+ char *buf;
+ char buf0[MAXNAME]; /* unquoted image of the user name */
extern int safefile();
e->e_to = a->q_paddr;
** Finish setting up address structure.
*/
- /* set the queue timeout */
- a->q_timeout = TimeOuts.to_q_return;
-
/* get unquoted user for file, program or user.name check */
+ i = strlen(a->q_user);
+ if (i >= sizeof buf)
+ buf = xalloc(i + 1);
+ else
+ buf = buf0;
(void) strcpy(buf, a->q_user);
for (p = buf; *p != '\0' && !quoted; p++)
{
stripquotes(buf);
/* check for direct mailing to restricted mailers */
- if (a->q_alias == NULL && m == ProgMailer)
+ if (m == ProgMailer)
{
- a->q_flags |= QBADADDR;
- usrerr("550 Cannot mail directly to programs");
+ if (a->q_alias == NULL)
+ {
+ a->q_flags |= QBADADDR;
+ usrerr("550 Cannot mail directly to programs");
+ }
+ else if (bitset(QBOGUSSHELL, a->q_alias->q_flags))
+ {
+ a->q_flags |= QBADADDR;
+ usrerr("550 User %s@%s doesn't have a valid shell for mailing to programs",
+ a->q_alias->q_ruser, MyHostName);
+ }
+ else if (bitset(QUNSAFEADDR, a->q_alias->q_flags))
+ {
+ a->q_flags |= QBADADDR;
+ usrerr("550 Address %s is unsafe for mailing to programs",
+ a->q_alias->q_paddr);
+ }
}
/*
message("duplicate suppressed");
q->q_flags |= a->q_flags;
}
+ else if (bitset(QSELFREF, q->q_flags))
+ q->q_flags |= a->q_flags & ~QDONTSEND;
if (!bitset(QPSEUDO, a->q_flags))
q->q_flags &= ~QPSEUDO;
return (q);
{
#ifdef LOG
if (LogLevel > 2)
- syslog(LOG_ERR, "%s: include %s: transient error: %e",
- e->e_id, a->q_user, errstring(ret));
+ syslog(LOG_ERR, "%s: include %s: transient error: %s",
+ e->e_id == NULL ? "NOQUEUE" : e->e_id,
+ a->q_user, errstring(ret));
#endif
a->q_flags |= QQUEUEUP;
+ a->q_flags &= ~QDONTSEND;
usrerr("451 Cannot open %s: %s",
a->q_user, errstring(ret));
}
a->q_flags |= QBADADDR;
usrerr("550 Cannot mail directly to files");
}
- else if (!writable(buf, SFF_ANYFILE))
+ else if (bitset(QBOGUSSHELL, a->q_alias->q_flags))
+ {
+ a->q_flags |= QBADADDR;
+ usrerr("550 User %s@%s doesn't have a valid shell for mailing to files",
+ a->q_alias->q_ruser, MyHostName);
+ }
+ else if (bitset(QUNSAFEADDR, a->q_alias->q_flags))
+ {
+ a->q_flags |= QBADADDR;
+ usrerr("550 Address %s is unsafe for mailing to files",
+ a->q_alias->q_paddr);
+ }
+ else if (!writable(buf, getctladdr(a), SFF_ANYFILE))
{
a->q_flags |= QBADADDR;
giveresponse(EX_CANTCREAT, m, NULL, a->q_alias, e);
}
}
- if (m != LocalMailer)
- {
- if (!bitset(QDONTSEND, a->q_flags))
- e->e_nrcpts++;
- goto testselfdestruct;
- }
-
/* try aliasing */
- alias(a, sendq, e);
+ if (!bitset(QDONTSEND, a->q_flags) && bitnset(M_ALIASABLE, m->m_flags))
+ alias(a, sendq, e);
# ifdef USERDB
/* if not aliased, look it up in the user database */
- if (!bitset(QDONTSEND|QNOTREMOTE|QVERIFIED, a->q_flags))
+ if (!bitset(QDONTSEND|QNOTREMOTE|QVERIFIED, a->q_flags) &&
+ bitnset(M_CHECKUDB, m->m_flags))
{
extern int udbexpand();
# ifdef LOG
if (LogLevel > 8)
syslog(LOG_INFO, "%s: deferred: udbexpand: %s",
- e->e_id, errstring(errno));
+ e->e_id == NULL ? "NOQUEUE" : e->e_id,
+ errstring(errno));
# endif
message("queued (user database error): %s",
errstring(errno));
}
# endif
- /* if it was an alias or a UDB expansion, just return now */
- if (bitset(QDONTSEND|QQUEUEUP|QVERIFIED, a->q_flags))
- goto testselfdestruct;
-
/*
** If we have a level two config file, then pass the name through
** Ruleset 5 before sending it off. Ruleset 5 has the right
ConfigLevel, RewriteRules[5]);
printaddr(a, FALSE);
}
- if (!bitset(QNOTREMOTE, a->q_flags) && ConfigLevel >= 2 &&
- RewriteRules[5] != NULL)
+ if (!bitset(QNOTREMOTE|QDONTSEND|QQUEUEUP|QVERIFIED, a->q_flags) &&
+ ConfigLevel >= 2 && RewriteRules[5] != NULL &&
+ bitnset(M_TRYRULESET5, m->m_flags))
{
maplocaluser(a, sendq, e);
}
** and deliver it.
*/
- if (!bitset(QDONTSEND|QQUEUEUP, a->q_flags))
+ if (!bitset(QDONTSEND|QQUEUEUP|QVERIFIED, a->q_flags) &&
+ bitnset(M_HASPWENT, m->m_flags))
{
auto bool fuzzy;
register struct passwd *pw;
a->q_flags |= QBADADDR;
usrerr("554 aliasing/forwarding loop for %s broken",
pw->pw_name);
- return (a);
+ goto done;
}
/* see if it aliases */
(void) strcpy(buf, pw->pw_name);
goto trylocaluser;
}
- a->q_home = newstr(pw->pw_dir);
+ if (strcmp(pw->pw_dir, "/") == 0)
+ a->q_home = "";
+ else
+ a->q_home = newstr(pw->pw_dir);
a->q_uid = pw->pw_uid;
a->q_gid = pw->pw_gid;
a->q_ruser = newstr(pw->pw_name);
buildfname(pw->pw_gecos, pw->pw_name, nbuf);
if (nbuf[0] != '\0')
a->q_fullname = newstr(nbuf);
+ if (pw->pw_shell != NULL && pw->pw_shell[0] != '\0' &&
+ !usershellok(pw->pw_shell))
+ {
+ a->q_flags |= QBOGUSSHELL;
+ }
if (!quoted)
forward(a, sendq, e);
}
usrerr("554 aliasing/forwarding loop broken");
}
}
+
+ done:
+ if (buf != buf0)
+ free(buf);
return (a);
return (a);
*fuzzyp = FALSE;
+#ifdef HESIOD
/* DEC Hesiod getpwnam accepts numeric strings -- short circuit it */
for (p = name; *p != '\0'; p++)
if (!isascii(*p) || !isdigit(*p))
printf("failed (numeric input)\n");
return NULL;
}
+#endif
/* look up this login name using fast path */
if ((pw = getpwnam(name)) != NULL)
**
** Parameters:
** filename -- the file name to check.
+** ctladdr -- the controlling address for this file.
** flags -- SFF_* flags to control the function.
**
** Returns:
*/
bool
-writable(filename, flags)
+writable(filename, ctladdr, flags)
char *filename;
+ ADDRESS *ctladdr;
int flags;
{
uid_t euid;
/* file does not exist -- see if directory is safe */
p = strrchr(filename, '/');
if (p == NULL)
- return FALSE;
- *p = '\0';
- if (safefile(filename, RealUid, RealGid, RealUserName,
- SFF_MUSTOWN, S_IWRITE|S_IEXEC) != 0)
{
- *p = '/';
+ errno = ENOTDIR;
return FALSE;
}
+ *p = '\0';
+ errno = safefile(filename, RealUid, RealGid, RealUserName,
+ SFF_MUSTOWN, S_IWRITE|S_IEXEC);
*p = '/';
- return TRUE;
+ return errno == 0;
}
+#ifdef SUID_ROOT_FILES_OK
+ /* really ought to be passed down -- and not a good idea */
+ flags |= SFF_ROOTOK;
+#endif
+
/*
** File does exist -- check that it is writable.
*/
{
if (tTd(29, 5))
printf("failed (mode %o: x bits)\n", stb.st_mode);
+ errno = EPERM;
return (FALSE);
}
- euid = RealUid;
- uname = RealUserName;
+ if (ctladdr != NULL && geteuid() == 0)
+ {
+ euid = ctladdr->q_uid;
+ egid = ctladdr->q_gid;
+ uname = ctladdr->q_user;
+ }
+#ifdef RUN_AS_REAL_UID
+ else
+ {
+ euid = RealUid;
+ egid = RealGid;
+ uname = RealUserName;
+ }
+#else
+ else if (FileMailer != NULL)
+ {
+ euid = FileMailer->m_uid;
+ egid = FileMailer->m_gid;
+ }
+ else
+ {
+ euid = egid = 0;
+ }
+#endif
if (euid == 0)
{
euid = DefUid;
uname = DefUser;
}
- egid = RealGid;
if (egid == 0)
egid = DefGid;
if (geteuid() == 0)
{
- if (bitset(S_ISUID, stb.st_mode))
+ if (bitset(S_ISUID, stb.st_mode) &&
+ (stb.st_uid != 0 || bitset(SFF_ROOTOK, flags)))
{
euid = stb.st_uid;
uname = NULL;
}
- if (bitset(S_ISGID, stb.st_mode))
+ if (bitset(S_ISGID, stb.st_mode) &&
+ (stb.st_gid != 0 || bitset(SFF_ROOTOK, flags)))
egid = stb.st_gid;
}
printf("\teu/gid=%d/%d, st_u/gid=%d/%d\n",
euid, egid, stb.st_uid, stb.st_gid);
- return safefile(filename, euid, egid, uname, flags, S_IWRITE) == 0;
+ errno = safefile(filename, euid, egid, uname, flags, S_IWRITE);
+ return errno == 0;
}
\f/*
** INCLUDE -- handle :include: specification.
** Side Effects:
** reads the :include: file and sends to everyone
** listed in that file.
+**
+** Security Note:
+** If you have restricted chown (that is, you can't
+** give a file away), it is reasonable to allow programs
+** and files called from this :include: file to be to be
+** run as the owner of the :include: file. This is bogus
+** if there is any chance of someone giving away a file.
+** We assume that pre-POSIX systems can give away files.
+**
+** There is an additional restriction that if you
+** forward to a :include: file, it will not take on
+** the ownership of the :include: file. This may not
+** be necessary, but shouldn't hurt.
*/
static jmp_buf CtxIncludeTimeout;
static int includetimeout();
+#ifndef S_IWOTH
+# define S_IWOTH (S_IWRITE >> 6)
+#endif
+
int
include(fname, forwarding, ctladdr, sendq, e)
char *fname;
char *uname;
int rval = 0;
int sfflags = forwarding ? SFF_MUSTOWN : SFF_ANYFILE;
+ struct stat st;
char buf[MAXLINE];
+#ifdef _POSIX_CHOWN_RESTRICTED
+# if _POSIX_CHOWN_RESTRICTED == -1
+# define safechown FALSE
+# else
+# define safechown TRUE
+# endif
+#else
+# ifdef _PC_CHOWN_RESTRICTED
+ bool safechown;
+# else
+# ifdef BSD
+# define safechown TRUE
+# else
+# define safechown FALSE
+# endif
+# endif
+#endif
+ extern bool chownsafe();
if (tTd(27, 2))
printf("include(%s)\n", fname);
{
initgroups(uname, gid);
if (uid != 0)
- (void) setreuid(0, uid);
+ {
+ if (setreuid(0, uid) < 0)
+ syserr("setreuid(0, %d) failure (real=%d, eff=%d)",
+ uid, getuid(), geteuid());
+ }
}
#endif
}
{
ctladdr->q_flags |= QQUEUEUP;
errno = 0;
- usrerr("451 open timeout on %s", fname);
/* return pseudo-error code */
rval = EOPENTIMEOUT;
goto resetuid;
}
- ev = setevent((time_t) 60, includetimeout, 0);
+ if (TimeOuts.to_fileopen > 0)
+ ev = setevent(TimeOuts.to_fileopen, includetimeout, 0);
+ else
+ ev = NULL;
/* the input file must be marked safe */
rval = safefile(fname, uid, gid, uname, sfflags, S_IREAD);
if (rval != 0)
{
/* don't use this :include: file */
- clrevent(ev);
if (tTd(27, 4))
printf("include: not safe (uid=%d): %s\n",
uid, errstring(rval));
- goto resetuid;
- }
-
- fp = fopen(fname, "r");
- if (fp == NULL)
- {
- rval = errno;
- if (tTd(27, 4))
- printf("include: open: %s\n", errstring(rval));
}
- else if (ca == NULL)
+ else
{
- struct stat st;
-
- if (fstat(fileno(fp), &st) < 0)
+ fp = fopen(fname, "r");
+ if (fp == NULL)
{
rval = errno;
- syserr("Cannot fstat %s!", fname);
- }
- else
- {
- ctladdr->q_uid = st.st_uid;
- ctladdr->q_gid = st.st_gid;
- ctladdr->q_flags |= QGOODUID;
+ if (tTd(27, 4))
+ printf("include: open: %s\n", errstring(rval));
}
}
-
- clrevent(ev);
+ if (ev != NULL)
+ clrevent(ev);
resetuid:
if (saveduid == 0)
{
if (uid != 0)
- if (setreuid(-1, 0) < 0 || setreuid(RealUid, 0) < 0)
+ {
+ if (setreuid(-1, 0) < 0)
+ syserr("setreuid(-1, 0) failure (real=%d, eff=%d)",
+ getuid(), geteuid());
+ if (setreuid(RealUid, 0) < 0)
syserr("setreuid(%d, 0) failure (real=%d, eff=%d)",
RealUid, getuid(), geteuid());
+ }
setgid(savedgid);
}
#endif
if (tTd(27, 9))
printf("include: reset uid = %d/%d\n", getuid(), geteuid());
+ if (rval == EOPENTIMEOUT)
+ usrerr("451 open timeout on %s", fname);
+
if (fp == NULL)
return rval;
+ if (fstat(fileno(fp), &st) < 0)
+ {
+ rval = errno;
+ syserr("Cannot fstat %s!", fname);
+ return rval;
+ }
+
+#ifndef safechown
+ safechown = chownsafe(fileno(fp));
+#endif
+ if (ca == NULL && safechown)
+ {
+ ctladdr->q_uid = st.st_uid;
+ ctladdr->q_gid = st.st_gid;
+ ctladdr->q_flags |= QGOODUID;
+ }
+ if (ca != NULL && ca->q_uid == st.st_uid)
+ {
+ /* optimization -- avoid getpwuid if we already have info */
+ ctladdr->q_flags |= ca->q_flags & QBOGUSSHELL;
+ ctladdr->q_ruser = ca->q_ruser;
+ }
+ else
+ {
+ register struct passwd *pw;
+
+ pw = getpwuid(st.st_uid);
+ if (pw == NULL)
+ ctladdr->q_flags |= QBOGUSSHELL;
+ else
+ {
+ char *sh;
+
+ ctladdr->q_ruser = newstr(pw->pw_name);
+ if (safechown)
+ sh = pw->pw_shell;
+ else
+ sh = "/SENDMAIL/ANY/SHELL/";
+ if (!usershellok(sh))
+ {
+ if (safechown)
+ ctladdr->q_flags |= QBOGUSSHELL;
+ else
+ ctladdr->q_flags |= QUNSAFEADDR;
+ }
+ }
+ }
+
if (bitset(EF_VRFYONLY, e->e_flags))
{
/* don't do any more now */
return rval;
}
+ /*
+ ** Check to see if some bad guy can write this file
+ **
+ ** This should really do something clever with group
+ ** permissions; currently we just view world writable
+ ** as unsafe. Also, we don't check for writable
+ ** directories in the path. We've got to leave
+ ** something for the local sysad to do.
+ */
+
+ if (bitset(S_IWOTH, st.st_mode))
+ ctladdr->q_flags |= QUNSAFEADDR;
+
/* read the file -- each line is a comma-separated list. */
FileName = fname;
LineNumber = 0;
#ifdef LOG
if (forwarding && LogLevel > 9)
syslog(LOG_INFO, "%s: forward %s => %s",
- e->e_id, oldto, buf);
+ e->e_id == NULL ? "NOQUEUE" : e->e_id,
+ oldto, buf);
#endif
AliasLevel++;