aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/sendmail/src/recipient.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/sendmail/src/recipient.c')
-rw-r--r--usr.sbin/sendmail/src/recipient.c460
1 files changed, 374 insertions, 86 deletions
diff --git a/usr.sbin/sendmail/src/recipient.c b/usr.sbin/sendmail/src/recipient.c
index bcdf5bfb3bfc..98d800d006ab 100644
--- a/usr.sbin/sendmail/src/recipient.c
+++ b/usr.sbin/sendmail/src/recipient.c
@@ -33,7 +33,7 @@
*/
#ifndef lint
-static char sccsid[] = "@(#)recipient.c 8.3 (Berkeley) 7/13/93";
+static char sccsid[] = "@(#)recipient.c 8.44 (Berkeley) 2/28/94";
#endif /* not lint */
# include "sendmail.h"
@@ -75,6 +75,13 @@ sendtolist(list, ctladdr, sendq, e)
bool firstone; /* set on first address sent */
char delimiter; /* the address delimiter */
int naddrs;
+ char *oldto = e->e_to;
+
+ if (list == NULL)
+ {
+ syserr("sendtolist: null list");
+ return 0;
+ }
if (tTd(25, 1))
{
@@ -103,7 +110,7 @@ sendtolist(list, ctladdr, sendq, e)
/* parse the address */
while ((isascii(*p) && isspace(*p)) || *p == ',')
p++;
- a = parseaddr(p, (ADDRESS *) NULL, 1, delimiter, &delimptr, e);
+ a = parseaddr(p, NULLADDR, RF_COPYALL, delimiter, &delimptr, e);
p = delimptr;
if (a == NULL)
continue;
@@ -135,7 +142,7 @@ sendtolist(list, ctladdr, sendq, e)
naddrs++;
}
- e->e_to = NULL;
+ e->e_to = oldto;
return (naddrs);
}
/*
@@ -182,6 +189,15 @@ recipient(a, sendq, e)
printaddr(a, FALSE);
}
+ /* if this is primary, add it to the original recipient list */
+ if (a->q_alias == NULL)
+ {
+ if (e->e_origrcpt == NULL)
+ e->e_origrcpt = a->q_paddr;
+ else if (e->e_origrcpt != a->q_paddr)
+ e->e_origrcpt = "";
+ }
+
/* break aliasing loops */
if (AliasLevel > MAXRCRSN)
{
@@ -206,11 +222,25 @@ recipient(a, sendq, e)
stripquotes(buf);
/* check for direct mailing to restricted mailers */
- if (a->q_alias == NULL && m == ProgMailer &&
- !bitset(EF_QUEUERUN, e->e_flags))
+ if (m == ProgMailer)
{
- a->q_flags |= QBADADDR;
- usrerr("550 Cannot mail directly to programs", m->m_name);
+ 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);
+ }
}
/*
@@ -237,7 +267,10 @@ recipient(a, sendq, e)
message("duplicate suppressed");
q->q_flags |= a->q_flags;
}
- return (q);
+ else if (bitset(QSELFREF, q->q_flags))
+ q->q_flags |= a->q_flags & ~QDONTSEND;
+ a = q;
+ goto testselfdestruct;
}
}
@@ -254,12 +287,12 @@ recipient(a, sendq, e)
printf("at trylocaluser %s\n", a->q_user);
if (bitset(QDONTSEND|QBADADDR|QVERIFIED, a->q_flags))
- return (a);
+ goto testselfdestruct;
if (m == InclMailer)
{
a->q_flags |= QDONTSEND;
- if (a->q_alias == NULL && !bitset(EF_QUEUERUN, e->e_flags))
+ if (a->q_alias == NULL)
{
a->q_flags |= QBADADDR;
usrerr("550 Cannot mail directly to :include:s");
@@ -274,38 +307,49 @@ recipient(a, sendq, e)
{
#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|QDONTSEND;
+ a->q_flags |= QQUEUEUP;
+ a->q_flags &= ~QDONTSEND;
usrerr("451 Cannot open %s: %s",
a->q_user, errstring(ret));
}
else if (ret != 0)
{
+ a->q_flags |= QBADADDR;
usrerr("550 Cannot open %s: %s",
a->q_user, errstring(ret));
- a->q_flags |= QBADADDR;
}
}
}
else if (m == FileMailer)
{
- struct stat stb;
extern bool writable();
- p = strrchr(buf, '/');
/* check if writable or creatable */
- if (a->q_alias == NULL && !bitset(EF_QUEUERUN, e->e_flags))
+ if (a->q_alias == NULL)
{
a->q_flags |= QBADADDR;
usrerr("550 Cannot mail directly to files");
}
- else if ((stat(buf, &stb) >= 0) ? (!writable(&stb)) :
- (*p = '\0', safefile(buf, RealUid, TRUE, S_IWRITE|S_IEXEC) != 0))
+ 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;
- giveresponse(EX_CANTCREAT, m, NULL, e);
+ 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);
}
}
@@ -313,7 +357,7 @@ recipient(a, sendq, e)
{
if (!bitset(QDONTSEND, a->q_flags))
e->e_nrcpts++;
- return (a);
+ goto testselfdestruct;
}
/* try aliasing */
@@ -324,29 +368,29 @@ recipient(a, sendq, e)
if (!bitset(QDONTSEND|QNOTREMOTE|QVERIFIED, a->q_flags))
{
extern int udbexpand();
- extern int errno;
if (udbexpand(a, sendq, e) == EX_TEMPFAIL)
{
- a->q_flags |= QQUEUEUP|QDONTSEND;
+ a->q_flags |= QQUEUEUP;
if (e->e_message == NULL)
e->e_message = newstr("Deferred: user database error");
# 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));
e->e_nrcpts++;
- return (a);
+ goto testselfdestruct;
}
}
# endif
/* if it was an alias or a UDB expansion, just return now */
if (bitset(QDONTSEND|QQUEUEUP|QVERIFIED, a->q_flags))
- return (a);
+ goto testselfdestruct;
/*
** If we have a level two config file, then pass the name through
@@ -383,7 +427,7 @@ recipient(a, sendq, e)
if (pw == NULL)
{
a->q_flags |= QBADADDR;
- giveresponse(EX_NOUSER, m, NULL, e);
+ giveresponse(EX_NOUSER, m, NULL, a->q_alias, e);
}
else
{
@@ -405,7 +449,10 @@ recipient(a, sendq, e)
(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);
@@ -413,12 +460,36 @@ recipient(a, sendq, e)
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);
}
}
if (!bitset(QDONTSEND, a->q_flags))
e->e_nrcpts++;
+
+ testselfdestruct:
+ if (tTd(26, 8))
+ {
+ printf("testselfdestruct: ");
+ printaddr(a, TRUE);
+ }
+ if (a->q_alias == NULL && a != &e->e_from &&
+ bitset(QDONTSEND, a->q_flags))
+ {
+ q = *sendq;
+ while (q != NULL && bitset(QDONTSEND, q->q_flags))
+ q = q->q_next;
+ if (q == NULL)
+ {
+ a->q_flags |= QBADADDR;
+ usrerr("554 aliasing/forwarding loop broken");
+ }
+ }
return (a);
}
/*
@@ -459,6 +530,17 @@ finduser(name, fuzzyp)
*fuzzyp = FALSE;
+ /* DEC Hesiod getpwnam accepts numeric strings -- short circuit it */
+ for (p = name; *p != '\0'; p++)
+ if (!isascii(*p) || !isdigit(*p))
+ break;
+ if (*p == '\0')
+ {
+ if (tTd(29, 4))
+ printf("failed (numeric input)\n");
+ return NULL;
+ }
+
/* look up this login name using fast path */
if ((pw = getpwnam(name)) != NULL)
{
@@ -517,7 +599,9 @@ finduser(name, fuzzyp)
** not writable. This is also enforced by mailfile.
**
** Parameters:
-** s -- pointer to a stat struct for the file.
+** filename -- the file name to check.
+** ctladdr -- the controlling address for this file.
+** flags -- SFF_* flags to control the function.
**
** Returns:
** TRUE -- if we will be able to write this file.
@@ -528,35 +612,98 @@ finduser(name, fuzzyp)
*/
bool
-writable(s)
- register struct stat *s;
+writable(filename, ctladdr, flags)
+ char *filename;
+ ADDRESS *ctladdr;
+ int flags;
{
uid_t euid;
gid_t egid;
int bits;
+ register char *p;
+ char *uname;
+ struct stat stb;
+ extern char RealUserName[];
- if (bitset(0111, s->st_mode))
- return (FALSE);
- euid = RealUid;
- egid = RealGid;
- if (geteuid() == 0)
+ if (tTd(29, 5))
+ printf("writable(%s, %x)\n", filename, flags);
+
+#ifdef HASLSTAT
+ if ((bitset(SFF_NOSLINK, flags) ? lstat(filename, &stb)
+ : stat(filename, &stb)) < 0)
+#else
+ if (stat(filename, &stb) < 0)
+#endif
+ {
+ /* file does not exist -- see if directory is safe */
+ p = strrchr(filename, '/');
+ if (p == NULL)
+ {
+ errno = ENOTDIR;
+ return FALSE;
+ }
+ *p = '\0';
+ errno = safefile(filename, RealUid, RealGid, RealUserName,
+ SFF_MUSTOWN, S_IWRITE|S_IEXEC);
+ *p = '/';
+ 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 (bitset(0111, stb.st_mode))
{
- if (bitset(S_ISUID, s->st_mode))
- euid = s->st_uid;
- if (bitset(S_ISGID, s->st_mode))
- egid = s->st_gid;
+ if (tTd(29, 5))
+ printf("failed (mode %o: x bits)\n", stb.st_mode);
+ errno = EPERM;
+ return (FALSE);
}
+ if (ctladdr != NULL && geteuid() == 0)
+ {
+ euid = ctladdr->q_uid;
+ egid = ctladdr->q_gid;
+ uname = ctladdr->q_user;
+ }
+ else
+ {
+ euid = RealUid;
+ egid = RealGid;
+ uname = RealUserName;
+ }
if (euid == 0)
- return (TRUE);
- bits = S_IWRITE;
- if (euid != s->st_uid)
{
- bits >>= 3;
- if (egid != s->st_gid)
- bits >>= 3;
+ euid = DefUid;
+ uname = DefUser;
+ }
+ if (egid == 0)
+ egid = DefGid;
+ if (geteuid() == 0)
+ {
+ 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) &&
+ (stb.st_gid != 0 || bitset(SFF_ROOTOK, flags)))
+ egid = stb.st_gid;
}
- return ((s->st_mode & bits) != 0);
+
+ if (tTd(29, 5))
+ printf("\teu/gid=%d/%d, st_u/gid=%d/%d\n",
+ euid, egid, stb.st_uid, stb.st_gid);
+
+ errno = safefile(filename, euid, egid, uname, flags, S_IWRITE);
+ return errno == 0;
}
/*
** INCLUDE -- handle :include: specification.
@@ -577,9 +724,32 @@ writable(s)
** 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
+
+#if defined(__FreeBSD__) && defined(_POSIX_CHOWN_RESTRICTED)
+# undef _POSIX_CHOWN_RESTRICTED
+# define _POSIX_CHOWN_RESTRICTED 1
+#endif
int
include(fname, forwarding, ctladdr, sendq, e)
@@ -589,84 +759,184 @@ include(fname, forwarding, ctladdr, sendq, e)
ADDRESS **sendq;
ENVELOPE *e;
{
- register FILE *fp;
+ register FILE *fp = NULL;
char *oldto = e->e_to;
char *oldfilename = FileName;
int oldlinenumber = LineNumber;
register EVENT *ev = NULL;
int nincludes;
- int ret;
- ADDRESS *ca;
- uid_t uid;
+ register ADDRESS *ca;
+ uid_t saveduid, uid;
+ gid_t savedgid, gid;
+ char *uname;
+ int rval = 0;
+ int sfflags = forwarding ? SFF_MUSTOWN : SFF_ANYFILE;
+ struct stat st;
char buf[MAXLINE];
- static int includetimeout();
+#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);
+ if (tTd(27, 4))
+ printf(" ruid=%d euid=%d\n", getuid(), geteuid());
if (tTd(27, 14))
{
printf("ctladdr ");
printaddr(ctladdr, FALSE);
}
- /*
- ** If home directory is remote mounted but server is down,
- ** this can hang or give errors; use a timeout to avoid this
- */
+ if (tTd(27, 9))
+ printf("include: old uid = %d/%d\n", getuid(), geteuid());
ca = getctladdr(ctladdr);
if (ca == NULL)
- uid = 0;
+ {
+ uid = DefUid;
+ gid = DefGid;
+ uname = DefUser;
+ saveduid = -1;
+ }
else
+ {
uid = ca->q_uid;
+ gid = ca->q_gid;
+ uname = ca->q_user;
+#ifdef HASSETREUID
+ saveduid = geteuid();
+ savedgid = getegid();
+ if (saveduid == 0)
+ {
+ initgroups(uname, gid);
+ if (uid != 0)
+ (void) setreuid(0, uid);
+ }
+#endif
+ }
+
+ if (tTd(27, 9))
+ printf("include: new uid = %d/%d\n", getuid(), geteuid());
+
+ /*
+ ** If home directory is remote mounted but server is down,
+ ** this can hang or give errors; use a timeout to avoid this
+ */
if (setjmp(CtxIncludeTimeout) != 0)
{
- ctladdr->q_flags |= QQUEUEUP|QDONTSEND;
+ ctladdr->q_flags |= QQUEUEUP;
errno = 0;
- usrerr("451 open timeout on %s", fname);
- return ETIMEDOUT;
+
+ /* return pseudo-error code */
+ rval = EOPENTIMEOUT;
+ goto resetuid;
}
ev = setevent((time_t) 60, includetimeout, 0);
/* the input file must be marked safe */
- if ((ret = safefile(fname, uid, forwarding, S_IREAD)) != 0)
+ rval = safefile(fname, uid, gid, uname, sfflags, S_IREAD);
+ if (rval != 0)
{
- /* don't use this .forward file */
- clrevent(ev);
+ /* don't use this :include: file */
if (tTd(27, 4))
printf("include: not safe (uid=%d): %s\n",
- uid, errstring(ret));
- return ret;
+ uid, errstring(rval));
}
-
- fp = fopen(fname, "r");
- if (fp == NULL)
+ else
{
- int ret = errno;
-
- clrevent(ev);
- return ret;
+ fp = fopen(fname, "r");
+ if (fp == NULL)
+ {
+ rval = errno;
+ if (tTd(27, 4))
+ printf("include: open: %s\n", errstring(rval));
+ }
}
+ clrevent(ev);
- if (ca == NULL)
+resetuid:
+
+#ifdef HASSETREUID
+ if (saveduid == 0)
{
- struct stat st;
+ if (uid != 0)
+ if (setreuid(-1, 0) < 0 || setreuid(RealUid, 0) < 0)
+ syserr("setreuid(%d, 0) failure (real=%d, eff=%d)",
+ RealUid, getuid(), geteuid());
+ setgid(savedgid);
+ }
+#endif
- if (fstat(fileno(fp), &st) < 0)
- {
- int ret = errno;
+ if (tTd(27, 9))
+ printf("include: reset uid = %d/%d\n", getuid(), geteuid());
- clrevent(ev);
- syserr("Cannot fstat %s!", fname);
- return ret;
- }
+ 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
+ {
+ char *sh;
+ register struct passwd *pw;
- clrevent(ev);
+ sh = "/SENDMAIL/ANY/SHELL/";
+ pw = getpwuid(st.st_uid);
+ if (pw != NULL)
+ {
+ ctladdr->q_ruser = newstr(pw->pw_name);
+ if (safechown)
+ sh = pw->pw_shell;
+ }
+ if (pw == NULL)
+ ctladdr->q_flags |= QBOGUSSHELL;
+ else if(!usershellok(sh))
+ {
+ if (safechown)
+ ctladdr->q_flags |= QBOGUSSHELL;
+ else
+ ctladdr->q_flags |= QUNSAFEADDR;
+ }
+ }
if (bitset(EF_VRFYONLY, e->e_flags))
{
@@ -674,9 +944,22 @@ include(fname, forwarding, ctladdr, sendq, e)
ctladdr->q_flags |= QVERIFIED;
e->e_nrcpts++;
xfclose(fp, "include", fname);
- return 0;
+ 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;
@@ -697,13 +980,17 @@ include(fname, forwarding, ctladdr, sendq, e)
#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++;
nincludes += sendtolist(buf, ctladdr, sendq, e);
AliasLevel--;
}
+
+ if (ferror(fp) && tTd(27, 3))
+ printf("include: read error: %s\n", errstring(errno));
if (nincludes > 0 && !bitset(QSELFREF, ctladdr->q_flags))
{
if (tTd(27, 5))
@@ -717,7 +1004,8 @@ include(fname, forwarding, ctladdr, sendq, e)
(void) xfclose(fp, "include", fname);
FileName = oldfilename;
LineNumber = oldlinenumber;
- return 0;
+ e->e_to = oldto;
+ return rval;
}
static
@@ -748,7 +1036,7 @@ sendtoargv(argv, e)
while ((p = *argv++) != NULL)
{
- (void) sendtolist(p, (ADDRESS *) NULL, &e->e_sendqueue, e);
+ (void) sendtolist(p, NULLADDR, &e->e_sendqueue, e);
}
}
/*