* Copyright (c) 1983, 1995 Eric P. Allman
* Copyright (c) 1988, 1993
* The Regents of the University of California. All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
static char sccsid
[] = "@(#)headers.c 8.70 (Berkeley) 6/19/95";
** CHOMPHEADER -- process and save a header line.
** Called by collect and by readcf to deal with header lines.
** line -- header as a text line.
** def -- if set, this is a default value.
** hdrp -- a pointer to the place to save the header.
** e -- the envelope including this header.
** flags for this header.
** The header is saved on the header list.
** Contents of 'line' are destroyed.
chompheader(line
, def
, hdrp
, e
)
headeronly
= hdrp
!= NULL
;
register char *q
= strchr(p
+ 1, *p
);
syserr("553 header syntax error, line \"%s\"", line
);
/* find canonical name */
while (isascii(*p
) && isgraph(*p
) && *p
!= ':')
while (isascii(*p
) && isspace(*p
))
if (*p
++ != ':' || fname
== fvalue
)
syserr("553 header syntax error, line \"%s\"", line
);
/* strip field value on front */
/* see if it is a known type */
for (hi
= HdrInfo
; hi
->hi_field
!= NULL
; hi
++)
if (strcasecmp(hi
->hi_field
, fname
) == 0)
if (hi
->hi_field
== NULL
)
printf("no header match\n");
printf("header match, hi_flags=%x\n", hi
->hi_flags
);
/* see if this is a resent message */
if (!def
&& !headeronly
&& bitset(H_RESENT
, hi
->hi_flags
))
/* if this is an Errors-To: header keep track of it now */
if (UseErrorsTo
&& !def
&& !headeronly
&&
bitset(H_ERRORSTO
, hi
->hi_flags
))
(void) sendtolist(fvalue
, NULLADDR
, &e
->e_errorqueue
, 0, e
);
/* if this means "end of header" quit now */
if (bitset(H_EOH
, hi
->hi_flags
))
** Drop explicit From: if same as what we would generate.
** This is to make MH (which doesn't always give a full name)
** insert the full name information in all circumstances.
if (!bitset(EF_RESENT
, e
->e_flags
))
if (!def
&& !headeronly
&& !bitset(EF_QUEUERUN
, e
->e_flags
) &&
strcasecmp(fname
, p
) == 0)
printf("comparing header from (%s) against default (%s or %s)\n",
fvalue
, e
->e_from
.q_paddr
, e
->e_from
.q_user
);
if (e
->e_from
.q_paddr
!= NULL
&&
(strcmp(fvalue
, e
->e_from
.q_paddr
) == 0 ||
strcmp(fvalue
, e
->e_from
.q_user
) == 0))
#ifdef MAYBENEXTRELEASE /* XXX UNTESTED XXX UNTESTED XXX UNTESTED XXX */
bool oldSuprErrs
= SuprErrs
;
extern char *crackaddr();
extern char *udbsender();
** Try doing USERDB rewriting even on fully commented
** names; this saves the "comment" information (such
** as full name) and rewrites the electronic part.
** XXX This code doesn't belong here -- parsing should
** XXX not be done during collect() phase because
** XXX error messages can confuse the SMTP phase.
** XXX Setting SuprErrs is a crude hack around this
if (OpMode
== MD_SMTP
|| OpMode
== MD_ARPAFTP
)
fancy
= crackaddr(fvalue
);
if (parseaddr(fvalue
, &a
, RF_COPYNONE
, '\0', NULL
, e
) != NULL
&&
bitnset(M_CHECKUDB
, a
.q_mailer
->m_flags
) &&
(p
= udbsender(a
.q_user
)) != NULL
)
char *oldg
= macvalue('g', e
);
expand(fancy
, buf
, sizeof buf
, e
);
/* delete default value for this header */
for (hp
= hdrp
; (h
= *hp
) != NULL
; hp
= &h
->h_link
)
if (strcasecmp(fname
, h
->h_field
) == 0 &&
bitset(H_DEFAULT
, h
->h_flags
) &&
!bitset(H_FORCE
, h
->h_flags
))
/* copy conditions from default case */
bcopy((char *)h
->h_mflags
, (char *)mopts
,
h
= (HDR
*) xalloc(sizeof *h
);
h
->h_field
= newstr(fname
);
h
->h_value
= newstr(fvalue
);
bcopy((char *) mopts
, (char *) h
->h_mflags
, sizeof mopts
);
h
->h_flags
= hi
->hi_flags
;
/* hack to see if this is a new format message */
if (!def
&& !headeronly
&& bitset(H_RCPT
|H_FROM
, h
->h_flags
) &&
(strchr(fvalue
, ',') != NULL
|| strchr(fvalue
, '(') != NULL
||
strchr(fvalue
, '<') != NULL
|| strchr(fvalue
, ';') != NULL
))
e
->e_flags
&= ~EF_OLDSTYLE
;
** ADDHEADER -- add a header entry to the end of the queue.
** This bypasses the special checking of chompheader.
** field -- the name of the header field.
** value -- the value of the field.
** hp -- an indirect pointer to the header structure list.
** adds the field on the list of headers for this envelope.
addheader(field
, value
, hdrlist
)
register struct hdrinfo
*hi
;
for (hi
= HdrInfo
; hi
->hi_field
!= NULL
; hi
++)
if (strcasecmp(field
, hi
->hi_field
) == 0)
/* find current place in list -- keep back pointer? */
for (hp
= hdrlist
; (h
= *hp
) != NULL
; hp
= &h
->h_link
)
if (strcasecmp(field
, h
->h_field
) == 0)
/* allocate space for new header */
h
= (HDR
*) xalloc(sizeof *h
);
h
->h_value
= newstr(value
);
h
->h_flags
= hi
->hi_flags
| H_DEFAULT
;
** HVALUE -- return value of a header.
** Only "real" fields (i.e., ones that have not been supplied
** as a default) are used.
** field -- the field name.
** header -- the header list.
** pointer to the value part.
for (h
= header
; h
!= NULL
; h
= h
->h_link
)
if (!bitset(H_DEFAULT
, h
->h_flags
) &&
strcasecmp(h
->h_field
, field
) == 0)
** ISHEADER -- predicate telling if argument is a header.
** A line is a header if it has a single word followed by
** optional white space followed by a colon.
** Header fields beginning with two dashes, although technically
** permitted by RFC822, are automatically rejected in order
** to make MIME work out. Without this we could have a technically
** legal header such as ``--"foo:bar"'' that would also be a legal
** h -- string to check for possible headerness.
** TRUE if h is a header.
if (s
[0] == '-' && s
[1] == '-')
while (*s
> ' ' && *s
!= ':' && *s
!= '\0')
/* following technically violates RFC822 */
while (isascii(*s
) && isspace(*s
))
** EATHEADER -- run through the stored header and extract info.
** e -- the envelope to process.
** full -- if set, do full processing (e.g., compute
** message priority). This should not be set
** when reading a queue file because some info
** needed to compute the priority is wrong.
** Sets a bunch of global variables from information
** in the collected header.
** Aborts the message if the hop count is exceeded.
** Set up macros for possible expansion in headers.
define('f', e
->e_sender
, e
);
define('g', e
->e_sender
, e
);
if (e
->e_origrcpt
!= NULL
&& *e
->e_origrcpt
!= '\0')
define('u', e
->e_origrcpt
, e
);
/* full name of from person */
p
= hvalue("full-name", e
->e_header
);
printf("----- collected header -----\n");
for (h
= e
->e_header
; h
!= NULL
; h
= h
->h_link
)
printf("%s: ", h
->h_field
);
if (bitset(H_DEFAULT
, h
->h_flags
))
expand(h
->h_value
, buf
, sizeof buf
, e
);
h
->h_value
= newstr(buf
);
h
->h_flags
&= ~H_DEFAULT
;
/* count the number of times it has been processed */
if (bitset(H_TRACE
, h
->h_flags
))
/* send to this person if we so desire */
if (GrabTo
&& bitset(H_RCPT
, h
->h_flags
) &&
!bitset(H_DEFAULT
, h
->h_flags
) &&
(!bitset(EF_RESENT
, e
->e_flags
) || bitset(H_RESENT
, h
->h_flags
)))
int saveflags
= e
->e_flags
;
(void) sendtolist(h
->h_value
, NULLADDR
,
/* delete fatal errors generated by this address */
if (!GrabTo
&& !bitset(EF_FATALERRS
, saveflags
))
e
->e_flags
&= ~EF_FATALERRS
;
/* save the message-id for logging */
if (strcasecmp(h
->h_field
, "message-id") == 0)
while (isascii(*msgid
) && isspace(*msgid
))
/* see if this is a return-receipt header */
if (bitset(H_RECEIPTTO
, h
->h_flags
))
e
->e_receiptto
= h
->h_value
;
printf("----------------------------\n");
/* if we are just verifying (that is, sendmail -t -bv), drop out now */
if (hopcnt
> e
->e_hopcount
)
p
= hvalue("precedence", e
->e_header
);
e
->e_class
= priencode(p
);
e
->e_timeoutclass
= TOC_NONURGENT
;
e
->e_timeoutclass
= TOC_URGENT
;
e
->e_msgpriority
= e
->e_msgsize
- e
->e_class
* WkClassFact
+ e
->e_nrcpts
* WkRecipFact
;
/* message timeout priority */
p
= hvalue("priority", e
->e_header
);
/* (this should be in the configuration file) */
if (strcasecmp(p
, "urgent"))
e
->e_timeoutclass
= TOC_URGENT
;
else if (strcasecmp(p
, "normal"))
e
->e_timeoutclass
= TOC_NORMAL
;
else if (strcasecmp(p
, "non-urgent"))
e
->e_timeoutclass
= TOC_NONURGENT
;
/* date message originated */
p
= hvalue("posted-date", e
->e_header
);
p
= hvalue("date", e
->e_header
);
/* check to see if this is a MIME message */
else if ((e
->e_bodytype
!= NULL
&&
strcasecmp(e
->e_bodytype
, "8BITMIME") == 0) ||
hvalue("MIME-Version", e
->e_header
) != NULL
)
e
->e_flags
|= EF_IS_MIME
;
e
->e_bodytype
= "8BITMIME";
else if ((p
= hvalue("Content-Type", e
->e_header
)) != NULL
)
/* this may be an RFC 1049 message */
if (p
== NULL
|| *p
== ';')
e
->e_flags
|= EF_DONT_MIME
;
** From person in antiquated ARPANET mode
** required by UK Grey Book e-mail gateways (sigh)
if (OpMode
== MD_ARPAFTP
)
register struct hdrinfo
*hi
;
for (hi
= HdrInfo
; hi
->hi_field
!= NULL
; hi
++)
if (bitset(H_FROM
, hi
->hi_flags
) &&
(!bitset(H_RESENT
, hi
->hi_flags
) ||
bitset(EF_RESENT
, e
->e_flags
)) &&
(p
= hvalue(hi
->hi_field
, e
->e_header
)) != NULL
)
if (hi
->hi_field
!= NULL
)
printf("eatheader: setsender(*%s == %s)\n",
setsender(p
, e
, NULL
, TRUE
);
** Log collection information.
if (bitset(EF_LOGSENDER
, e
->e_flags
) && LogLevel
> 4)
e
->e_flags
&= ~EF_LOGSENDER
;
** LOGSENDER -- log sender information
** e -- the envelope to log
** msgid -- the message id
/* don't allow newlines in the message-id */
while ((p
= strchr(p
, '\n')) != NULL
)
if (bitset(EF_RESPONSE
, e
->e_flags
))
else if ((name
= macvalue('_', e
)) != NULL
)
else if (RealHostName
== NULL
)
else if (RealHostName
[0] == '[')
(void) sprintf(hbuf
, "%.80s", RealHostName
);
if (RealHostAddr
.sa
.sa_family
!= 0)
(void) sprintf(p
, " (%s)",
anynet_ntoa(&RealHostAddr
));
/* some versions of syslog only take 5 printf args */
# if (SYSLOG_BUFSIZE) >= 256
sprintf(sbp
, "from=%.200s, size=%ld, class=%d, pri=%ld, nrcpts=%d",
e
->e_from
.q_paddr
== NULL
? "<NONE>" : e
->e_from
.q_paddr
,
e
->e_msgsize
, e
->e_class
, e
->e_msgpriority
, e
->e_nrcpts
);
sprintf(sbp
, ", msgid=%.100s", mbuf
);
if (e
->e_bodytype
!= NULL
)
(void) sprintf(sbp
, ", bodytype=%.20s", e
->e_bodytype
);
(void) sprintf(sbp
, ", proto=%.20s", p
);
syslog(LOG_INFO
, "%s: %s, relay=%s",
# else /* short syslog buffer */
syslog(LOG_INFO
, "%s: from=%s",
e
->e_id
, e
->e_from
.q_paddr
== NULL
? "<NONE>" :
shortenstring(e
->e_from
.q_paddr
, 83));
syslog(LOG_INFO
, "%s: size=%ld, class=%ld, pri=%ld, nrcpts=%d",
e
->e_id
, e
->e_msgsize
, e
->e_class
,
e
->e_msgpriority
, e
->e_nrcpts
);
syslog(LOG_INFO
, "%s: msgid=%s", e
->e_id
, mbuf
);
sprintf(sbp
, "%s:", e
->e_id
);
if (e
->e_bodytype
!= NULL
)
sprintf(sbp
, " bodytype=%s,", e
->e_bodytype
);
sprintf(sbp
, " proto=%s,", p
);
syslog(LOG_INFO
, "%s relay=%s", sbuf
, name
);
** PRIENCODE -- encode external priority names into internal values.
** p -- priority in ascii.
** priority as a numeric level.
for (i
= 0; i
< NumPriorities
; i
++)
if (!strcasecmp(p
, Priorities
[i
].pri_name
))
return (Priorities
[i
].pri_val
);
** CRACKADDR -- parse an address and turn it into a macro
** This doesn't actually parse the address -- it just extracts
** it and replaces it with "$g". The parse is totally ad hoc
** and isn't even guaranteed to leave something syntactically
** identical to what it started with. However, it does leave
** something semantically identical.
** This algorithm has been cleaned up to handle a wider range
** of cases -- notably quoted and backslash escaped strings.
** This modification makes it substantially better at preserving
** addr -- the address to be cracked.
** a pointer to the new version.
** The return value is saved in local storage and should
** be copied if it is to be reused.
int anglelev
, realanglelev
;
static char buf
[MAXNAME
+ 1];
printf("crackaddr(%s)\n", addr
);
/* strip leading spaces */
while (*addr
!= '\0' && isascii(*addr
) && isspace(*addr
))
** Start by assuming we have no angle brackets. This will be
** adjusted later if we find them.
buflim
= &buf
[sizeof buf
- 5];
copylev
= anglelev
= realanglelev
= cmtlev
= realcmtlev
= 0;
qmode
= realqmode
= FALSE
;
while ((c
= *p
++) != '\0')
** If the buffer is overful, go into a special "skipping"
** mode that tries to keep legal syntax but doesn't actually
if (copylev
> 0 && !skipping
)
/* check for backslash escapes */
/* arrange to quote the address */
if (cmtlev
<= 0 && !qmode
)
if (copylev
> 0 && !skipping
)
/* check for quoted strings */
if (c
== '"' && cmtlev
<= 0)
if (copylev
> 0 && !skipping
)
/* allow space for closing paren */
/* syntax error: unmatched ) */
if (copylev
> 0 && !skipping
)
/* check for group: list; syntax */
if (c
== ':' && anglelev
<= 0 && !gotcolon
&& !ColonOkInAddr
)
/* special case -- :: syntax */
if (cmtlev
<= 0 && !qmode
)
if (copylev
> 0 && !skipping
)
/* back up over the ':' and any spaces */
while (isascii(*--p
) && isspace(*p
))
for (q
= addrhead
; q
< p
; )
while ((c
= *p
++) != ':')
/* any trailing white space is part of group: */
while (isascii(*p
) && isspace(*p
) && bp
< buflim
)
putgmac
= quoteit
= FALSE
;
if (c
== ';' && copylev
<= 0 && !ColonOkInAddr
)
/* check for characters that may have to be quoted */
if (strchr(".'@,;:\\()[]", c
) != NULL
)
** If these occur as the phrase part of a <>
** construct, but are not inside of () or already
** quoted, they will have to be quoted. Note that
** now (but don't actually do the quoting).
if (cmtlev
<= 0 && !qmode
)
/* check for angle brackets */
/* assume first of two angles is bogus */
/* oops -- have to change our mind */
/* back up over the '<' and any spaces */
while (isascii(*--p
) && isspace(*p
))
for (q
= addrhead
; q
< p
; )
while ((c
= *p
++) != '<')
putgmac
= quoteit
= FALSE
;
/* syntax error: unmatched > */
/* must be a real address character */
if (copylev
<= 0 && !putgmac
)
/* repair any syntactic damage */
while (realanglelev
-- > 0)
printf("crackaddr=>`%s'\n", buf
);
** PUTHEADER -- put the header part of a message from the in-core copy
** mci -- the connection information.
** h -- the header to put.
* Macro for fast max (not available in e.g. DG/UX, 386/ix).
# define MAX(a,b) (((a)>(b))?(a):(b))
char buf
[MAX(MAXLINE
,BUFSIZ
)];
printf("--- putheader, mailer = %s ---\n",
mci
->mci_mailer
->m_name
);
mci
->mci_flags
|= MCIF_INHEADER
;
for (; h
!= NULL
; h
= h
->h_link
)
register char *p
= h
->h_value
;
extern bool bitintersect();
printf(" %s: ", h
->h_field
);
/* suppress Content-Transfer-Encoding: if we are MIMEing */
if (bitset(H_CTE
, h
->h_flags
) &&
bitset(MCIF_CVT8TO7
|MCIF_INMIME
, mci
->mci_flags
))
printf(" (skipped (content-transfer-encoding))\n");
if (bitset(MCIF_INMIME
, mci
->mci_flags
))
if (bitset(H_CHECK
|H_ACHECK
, h
->h_flags
) &&
!bitintersect(h
->h_mflags
, mci
->mci_mailer
->m_flags
))
/* handle Resent-... headers specially */
if (bitset(H_RESENT
, h
->h_flags
) && !bitset(EF_RESENT
, e
->e_flags
))
printf(" (skipped (resent))\n");
/* suppress return receipts if requested */
if (bitset(H_RECEIPTTO
, h
->h_flags
) &&
bitset(EF_NORECEIPT
, e
->e_flags
))
printf(" (skipped (receipt))\n");
/* macro expand value if generated internally */
if (bitset(H_DEFAULT
, h
->h_flags
))
expand(p
, buf
, sizeof buf
, e
);
if (p
== NULL
|| *p
== '\0')
printf(" (skipped -- null value)\n");
if (bitset(H_STRIPVAL
, h
->h_flags
))
(void) sprintf(obuf
, "%s:", h
->h_field
);
else if (bitset(H_FROM
|H_RCPT
, h
->h_flags
))
bool oldstyle
= bitset(EF_OLDSTYLE
, e
->e_flags
);
if (bitset(H_FROM
, h
->h_flags
))
commaize(h
, p
, oldstyle
, mci
, e
);
/* vanilla header line */
(void) sprintf(obuf
, "%s: ", h
->h_field
);
while ((nlp
= strchr(p
, '\n')) != NULL
)
** If we are converting this to a MIME message, add the
if (bitset(MM_MIME8BIT
, MimeMode
) &&
bitset(EF_HAS8BIT
, e
->e_flags
) &&
!bitset(EF_DONT_MIME
, e
->e_flags
) &&
!bitnset(M_8BITS
, mci
->mci_mailer
->m_flags
) &&
!bitset(MCIF_CVT8TO7
, mci
->mci_flags
))
if (hvalue("MIME-Version", e
->e_header
) == NULL
)
putline("MIME-Version: 1.0", mci
);
if (hvalue("Content-Type", e
->e_header
) == NULL
)
sprintf(obuf
, "Content-Type: text/plain; charset=%s",
if (hvalue("Content-Transfer-Encoding", e
->e_header
) == NULL
)
putline("Content-Transfer-Encoding: 8bit", mci
);
** COMMAIZE -- output a header field, making a comma-translated list.
** h -- the header field to output.
** p -- the value to put in it.
** oldstyle -- TRUE if this is an old style header.
** mci -- the connection information.
** e -- the envelope containing the message.
** outputs "p" to file "fp".
commaize(h
, p
, oldstyle
, mci
, e
)
** Output the address list translated by the
** mailer and with commas.
printf("commaize(%s: %s)\n", h
->h_field
, p
);
(void) sprintf(obp
, "%s: ", h
->h_field
);
opos
= strlen(h
->h_field
) + 2;
omax
= mci
->mci_mailer
->m_linelimit
- 2;
if (omax
< 0 || omax
> 78)
** Run through the list of values.
** Find the end of the name. New style names
** end with a comma, old style names end with
** a space character. However, spaces do not
** necessarily delimit an old-style name -- at
** signs mean keep going.
while ((isascii(*p
) && isspace(*p
)) || *p
== ',')
(void) prescan(p
, oldstyle
? ' ' : ',', pvpbuf
,
sizeof pvpbuf
, &oldp
, NULL
);
/* look to see if we have an at sign */
while (*p
!= '\0' && isascii(*p
) && isspace(*p
))
while (*p
!= '\0' && isascii(*p
) && isspace(*p
))
/* at the end of one complete name */
/* strip off trailing white space */
((isascii(*p
) && isspace(*p
)) || *p
== ',' || *p
== '\0'))
/* translate the name to be relative */
flags
= RF_HEADERADDR
|RF_ADDDOMAIN
;
if (bitset(H_FROM
, h
->h_flags
))
else if (e
->e_from
.q_mailer
!= NULL
&&
bitnset(M_UDBRECIPIENT
, e
->e_from
.q_mailer
->m_flags
))
extern char *udbsender();
name
= remotename(name
, mci
->mci_mailer
, flags
, &stat
, e
);
/* output the name with nice formatting */
if (opos
> omax
&& !firstone
)
(void) strcpy(obp
, ",\n");
(void) strcpy(obp
, ", ");
while ((c
= *name
++) != '\0' && obp
< &obuf
[MAXLINE
])
(void) strcpy(obp
, "\n");
** COPYHEADER -- copy header list
** This routine is the equivalent of newstr for header lists
** header -- list of header structures to copy.
register HDR
**tail
= &ret
;
newhdr
= (HDR
*) xalloc(sizeof(HDR
));
STRUCTCOPY(*header
, *newhdr
);