dc74e8a3a3b0d894445bbba94d0d817aea42e081
* Copyright (c) 1983 Eric P. Allman
* Copyright (c) 1988, 1993
* The Regents of the University of California. All rights reserved.
* %sccs.include.redist.c%
static char sccsid
[] = "@(#)recipient.c 8.74 (Berkeley) %G%";
** SENDTOLIST -- Designate a send list.
** The parameter is a comma-separated list of people to send to.
** This routine arranges to send to all of them.
** The `ctladdr' is the address that expanded to be this one,
** e.g., in an alias expansion. This is used for a number of
** purposed, most notably inheritance of uid/gid for protection
** purposes. It is also used to detect self-reference in group
** expansions and the like.
** list -- the send list.
** ctladdr -- the address template for the person to
** send to -- effective uid/gid are important.
** This is typically the alias that caused this
** sendq -- a pointer to the head of a queue to put
** aliaslevel -- the current alias nesting depth -- to
** e -- the envelope in which to add these recipients.
** qflags -- special flags to set in the q_flags field.
** pointer to chain of addresses.
#define MAXRCRSN 10 /* maximum levels of alias recursion */
/* q_flags bits inherited from ctladdr */
#define QINHERITEDBITS (QPINGONSUCCESS|QPINGONFAILURE|QPINGONDELAY|QHASNOTIFY)
sendto(list
, copyf
, ctladdr
, qflags
)
register ADDRESS
*al
; /* list of addresses to send to */
bool firstone
; /* set on first address sent */
char delimiter
; /* the address delimiter */
ADDRESS
*sibl
; /* sibling pointer in tree */
ADDRESS
*prev
; /* previous sibling */
syserr("sendtolist: null list");
printf("sendto: %s\n ctladdr=", list
);
printaddr(ctladdr
, FALSE
);
/* heuristic to determine old versus new style addresses */
(strchr(list
, ',') != NULL
|| strchr(list
, ';') != NULL
||
strchr(list
, '<') != NULL
|| strchr(list
, '(') != NULL
))
e
->e_flags
&= ~EF_OLDSTYLE
;
if (!bitset(EF_OLDSTYLE
, e
->e_flags
) || ctladdr
!= NULL
)
/* make sure we have enough space to copy the string */
strcpy(bufp
, denlstring(list
, FALSE
, TRUE
));
for (p
= bufp
; *p
!= '\0'; )
while ((isascii(*p
) && isspace(*p
)) || *p
== ',')
a
= parseaddr(p
, NULLADDR
, RF_COPYALL
, delimiter
, &delimptr
, e
);
a
->q_flags
|= ctladdr
->q_flags
& ~QPRIMARY
;
/* see if this should be marked as a primary address */
(firstone
&& *p
== '\0' && bitset(QPRIMARY
, ctladdr
->q_flags
)))
extern ADDRESS
*addrref();
if (sibl
->q_fullname
== NULL
&& ctladdr
!= NULL
)
sibl
->q_fullname
= ctladdr
->q_fullname
;
/* link tree together (but only if the node is new) */
** ADDRREF -- return pointer to address that references another address.
** a -- address to check.
** r -- reference to find.
** address of node in tree rooted at 'a' that references
** NULL if no such node exists.
if (a
->q_child
== r
|| a
->q_sibling
== r
)
q
= addrref(a
->q_child
, r
);
** RECIPIENT -- Designate a message recipient
** Saves the named person for future mailing.
** a -- the (preparsed) address header for the recipient.
** sendq -- a pointer to the head of a queue to put the
** recipient in. Duplicate supression is done
** aliaslevel -- the current alias nesting depth.
** e -- the current envelope.
** pointer to address actually inserted in send list.
recipient(a
, sendq
, aliaslevel
, e
)
register ADDRESS
**sendq
;
register struct mailer
*m
;
bool quoted
= FALSE
; /* set if the addr has a quote bit */
char buf0
[MAXNAME
+ 1]; /* unquoted image of the user name */
/* if this is primary, add it to the original recipient list */
if (e
->e_origrcpt
== NULL
)
e
->e_origrcpt
= a
->q_paddr
;
else if (e
->e_origrcpt
!= a
->q_paddr
)
/* break aliasing loops */
if (aliaslevel
> MAXRCRSN
)
usrerr("554 aliasing/forwarding loop broken (%d aliases deep; %d max",
** Finish setting up address structure.
/* get unquoted user for file, program or user.name check */
(void) strcpy(buf
, a
->q_user
);
for (p
= buf
; *p
!= '\0' && !quoted
; p
++)
/* check for direct mailing to restricted mailers */
usrerr("550 Cannot mail directly to programs");
else if (bitset(QBOGUSSHELL
, a
->q_alias
->q_flags
))
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
))
usrerr("550 Address %s is unsafe for mailing to programs",
** Look up this person in the recipient list.
** If they are there already, return, otherwise continue.
** If the list is empty, just add it. Notice the cute
** hack to make from addresses suppress things correctly:
** the QDONTSEND bit will be set in the send list.
** [Please note: the emphasis is on "hack."]
for (pq
= sendq
; (q
= *pq
) != NULL
; pq
= &q
->q_next
)
/* if this is a reinsertion, just go ahead */
if (bitset(QVERIFIED
, q
->q_flags
))
printf("%s in sendq: ", a
->q_paddr
);
if (!bitset(QPRIMARY
, q
->q_flags
))
if (!bitset(QDONTSEND
, a
->q_flags
))
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
))
/* add address on list */
a
->q_flags
&= ~QVERIFIED
;
** Alias the name and handle special mailer types.
printf("at trylocaluser %s\n", a
->q_user
);
if (bitset(QDONTSEND
|QBADADDR
|QVERIFIED
, a
->q_flags
))
usrerr("550 Cannot mail directly to :include:s");
message("including file %s", a
->q_user
);
ret
= include(a
->q_user
, FALSE
, a
, sendq
, aliaslevel
, e
);
syslog(LOG_ERR
, "%s: include %s: transient error: %s",
e
->e_id
== NULL
? "NOQUEUE" : e
->e_id
,
a
->q_user
, errstring(ret
));
a
->q_flags
&= ~QDONTSEND
;
usrerr("451 Cannot open %s: %s",
a
->q_user
, errstring(ret
));
usrerr("550 Cannot open %s: %s",
a
->q_user
, errstring(ret
));
else if (m
== FileMailer
)
/* check if writable or creatable */
usrerr("550 Cannot mail directly to files");
else if (bitset(QBOGUSSHELL
, a
->q_alias
->q_flags
))
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
))
usrerr("550 Address %s is unsafe for mailing to files",
else if (!writable(buf
, getctladdr(a
), SFF_CREAT
))
giveresponse(EX_CANTCREAT
, m
, NULL
, a
->q_alias
,
if (!bitset(QDONTSEND
, a
->q_flags
) && bitnset(M_ALIASABLE
, m
->m_flags
))
alias(a
, sendq
, aliaslevel
, e
);
/* if not aliased, look it up in the user database */
if (!bitset(QDONTSEND
|QNOTREMOTE
|QVERIFIED
, a
->q_flags
) &&
bitnset(M_CHECKUDB
, m
->m_flags
))
if (udbexpand(a
, sendq
, aliaslevel
, e
) == EX_TEMPFAIL
)
if (e
->e_message
== NULL
)
e
->e_message
= newstr("Deferred: user database error");
syslog(LOG_INFO
, "%s: deferred: udbexpand: %s",
e
->e_id
== NULL
? "NOQUEUE" : e
->e_id
,
message("queued (user database error): %s",
** If we have a level two config file, then pass the name through
** Ruleset 5 before sending it off. Ruleset 5 has the right
** to send rewrite it to another mailer. This gives us a hook
** after local aliasing has been done.
printf("recipient: testing local? cl=%d, rr5=%x\n\t",
ConfigLevel
, RewriteRules
[5]);
if (!bitset(QNOTREMOTE
|QDONTSEND
|QQUEUEUP
|QVERIFIED
, a
->q_flags
) &&
ConfigLevel
>= 2 && RewriteRules
[5] != NULL
&&
bitnset(M_TRYRULESET5
, m
->m_flags
))
maplocaluser(a
, sendq
, aliaslevel
, e
);
** If it didn't get rewritten to another mailer, go ahead
if (!bitset(QDONTSEND
|QQUEUEUP
|QVERIFIED
, a
->q_flags
) &&
bitnset(M_HASPWENT
, m
->m_flags
))
register struct passwd
*pw
;
extern struct passwd
*finduser();
/* warning -- finduser may trash buf */
pw
= finduser(buf
, &fuzzy
);
giveresponse(EX_NOUSER
, m
, NULL
, a
->q_alias
,
/* name was a fuzzy match */
a
->q_user
= newstr(pw
->pw_name
);
usrerr("554 aliasing/forwarding loop for %s broken",
(void) strcpy(buf
, pw
->pw_name
);
if (strcmp(pw
->pw_dir
, "/") == 0)
a
->q_home
= newstr(pw
->pw_dir
);
a
->q_ruser
= newstr(pw
->pw_name
);
buildfname(pw
->pw_gecos
, pw
->pw_name
, nbuf
);
a
->q_fullname
= newstr(nbuf
);
if (pw
->pw_shell
!= NULL
&& pw
->pw_shell
[0] != '\0' &&
!usershellok(pw
->pw_shell
))
a
->q_flags
|= QBOGUSSHELL
;
forward(a
, sendq
, aliaslevel
, e
);
if (!bitset(QDONTSEND
, a
->q_flags
))
printf("testselfdestruct: ");
if (a
->q_alias
== NULL
&& a
!= &e
->e_from
&&
bitset(QDONTSEND
, a
->q_flags
))
while (q
!= NULL
&& bitset(QDONTSEND
, q
->q_flags
))
usrerr("554 aliasing/forwarding loop broken");
** FINDUSER -- find the password entry for a user.
** This looks a lot like getpwnam, except that it may want to
** do some fancier pattern matching in /etc/passwd.
** This routine contains most of the time of many sendmail runs.
** It deserves to be optimized.
** name -- the name to match against.
** fuzzyp -- an outarg that is set to TRUE if this entry
** was found using the fuzzy matching algorithm;
** set to FALSE otherwise.
** A pointer to a pw struct.
** NULL if name is unknown or ambiguous.
register struct passwd
*pw
;
extern struct passwd
*getpwent();
extern struct passwd
*getpwnam();
printf("finduser(%s): ", name
);
/* 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");
/* look up this login name using fast path */
if ((pw
= getpwnam(name
)) != NULL
)
printf("found (non-fuzzy)\n");
/* see if fuzzy matching allowed */
printf("not found (fuzzy disabled)\n");
/* search for a matching full name instead */
for (p
= name
; *p
!= '\0'; p
++)
if (*p
== (SpaceSub
& 0177) || *p
== '_')
while ((pw
= getpwent()) != NULL
)
if (strchr(buf
, ' ') != NULL
&& !strcasecmp(buf
, name
))
printf("fuzzy matches %s\n", pw
->pw_name
);
message(Arpa_Info
, "sending to %s <%s>",
printf("no fuzzy match found\n");
printf("not found (fuzzy disabled)\n");
** WRITABLE -- predicate returning if the file is writable.
** This routine must duplicate the algorithm in sys/fio.c.
** Unfortunately, we cannot use the access call since we
** won't necessarily be the real uid when we try to
** actually open the file.
** Notice that ANY file with ANY execute bit is automatically
** not writable. This is also enforced by mailfile.
** filename -- the file name to check.
** ctladdr -- the controlling address for this file.
** flags -- SFF_* flags to control the function.
** TRUE -- if we will be able to write this file.
** FALSE -- if we cannot write this file.
writable(filename
, ctladdr
, flags
)
printf("writable(%s, %x)\n", filename
, flags
);
#ifdef SUID_ROOT_FILES_OK
/* really ought to be passed down -- and not a good idea */
** File does exist -- check that it is writable.
if (ctladdr
!= NULL
&& geteuid() == 0)
extern char RealUserName
[];
else if (FileMailer
!= NULL
)
euid
= FileMailer
->m_uid
;
egid
= FileMailer
->m_gid
;
errno
= safefile(filename
, euid
, egid
, uname
, flags
, S_IWRITE
, NULL
);
** INCLUDE -- handle :include: specification.
** fname -- filename to include.
** forwarding -- if TRUE, we are reading a .forward file.
** if FALSE, it's a :include: file.
** ctladdr -- address template to use to fill in these
** addresses -- effective user/group id are
** sendq -- a pointer to the head of the send queue
** to put these addresses in.
** aliaslevel -- the alias nesting depth.
** e -- the current envelope.
** reads the :include: file and sends to everyone
** 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 void includetimeout();
include(fname
, forwarding
, ctladdr
, sendq
, aliaslevel
, e
)
char *oldfilename
= FileName
;
int oldlinenumber
= LineNumber
;
register EVENT
*ev
= NULL
;
int sfflags
= SFF_REGONLY
;
#ifdef _POSIX_CHOWN_RESTRICTED
# if _POSIX_CHOWN_RESTRICTED == -1
# ifdef _PC_CHOWN_RESTRICTED
printf("include(%s)\n", fname
);
printf(" ruid=%d euid=%d\n", getuid(), geteuid());
printaddr(ctladdr
, FALSE
);
printf("include: old uid = %d/%d\n", getuid(), geteuid());
ca
= getctladdr(ctladdr
);
if (setreuid(0, uid
) < 0)
syserr("setreuid(0, %d) failure (real=%d, eff=%d)",
uid
, getuid(), geteuid());
sfflags
|= SFF_NOPATHCHECK
;
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
;
/* return pseudo-error code */
if (TimeOuts
.to_fileopen
> 0)
ev
= setevent(TimeOuts
.to_fileopen
, includetimeout
, 0);
/* the input file must be marked safe */
rval
= safefile(fname
, uid
, gid
, uname
, sfflags
, S_IREAD
, NULL
);
/* don't use this :include: file */
printf("include: not safe (uid=%d): %s\n",
printf("include: open: %s\n", errstring(rval
));
syserr("setreuid(-1, 0) failure (real=%d, eff=%d)",
if (setreuid(RealUid
, 0) < 0)
syserr("setreuid(%d, 0) failure (real=%d, eff=%d)",
RealUid
, getuid(), geteuid());
printf("include: reset uid = %d/%d\n", getuid(), geteuid());
if (rval
== EOPENTIMEOUT
)
usrerr("451 open timeout on %s", fname
);
if (fstat(fileno(fp
), &st
) < 0)
syserr("Cannot fstat %s!", fname
);
safechown
= chownsafe(fileno(fp
));
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
;
register struct passwd
*pw
;
pw
= getpwuid(st
.st_uid
);
ctladdr
->q_flags
|= QBOGUSSHELL
;
ctladdr
->q_ruser
= newstr(pw
->pw_name
);
sh
= "/SENDMAIL/ANY/SHELL/";
ctladdr
->q_flags
|= QBOGUSSHELL
;
ctladdr
->q_flags
|= QUNSAFEADDR
;
if (bitset(EF_VRFYONLY
, e
->e_flags
))
/* don't do any more now */
ctladdr
->q_flags
|= QVERIFIED
;
xfclose(fp
, "include", fname
);
** 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. */
ctladdr
->q_flags
&= ~QSELFREF
;
while (fgets(buf
, sizeof buf
, fp
) != NULL
)
register char *p
= strchr(buf
, '\n');
if (buf
[0] == '#' || buf
[0] == '\0')
forwarding
? "forwarding" : "sending", buf
);
if (forwarding
&& LogLevel
> 9)
syslog(LOG_INFO
, "%s: forward %s => %s",
e
->e_id
== NULL
? "NOQUEUE" : e
->e_id
,
nincludes
+= sendtolist(buf
, ctladdr
, sendq
, aliaslevel
+ 1, e
);
if (ferror(fp
) && tTd(27, 3))
printf("include: read error: %s\n", errstring(errno
));
if (nincludes
> 0 && !bitset(QSELFREF
, ctladdr
->q_flags
))
printf("include: QDONTSEND ");
printaddr(ctladdr
, FALSE
);
ctladdr
->q_flags
|= QDONTSEND
;
(void) xfclose(fp
, "include", fname
);
LineNumber
= oldlinenumber
;
longjmp(CtxIncludeTimeout
, 1);
** SENDTOARGV -- send to an argument vector.
** argv -- argument vector to send to.
** e -- the current envelope.
** puts all addresses on the argument vector onto the
while ((p
= *argv
++) != NULL
)
sendto(p
, 0, (ADDRESS
*) NULL
, 0);
** GETCTLADDR -- get controlling address from an address header.
** If none, get one corresponding to the effective userid.
** a -- the address to find the controller of.
** the controlling address.
while (a
!= NULL
&& !bitset(QGOODUID
, a
->q_flags
))