summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTodd C. Miller <millert@cvs.openbsd.org>1998-08-14 00:56:04 +0000
committerTodd C. Miller <millert@cvs.openbsd.org>1998-08-14 00:56:04 +0000
commit66d78bfbde3d33a31d3df849876a7f66b90ac3f7 (patch)
tree3fdd6117cd7e2ce3d9d915b1e36a66cdc9ece0a1
parent791af4a1e2fe58697fff3df66450976407115302 (diff)
Update to sendmail.8.9.1a which adds support for MaxMimeHeaderLength option, to help avoid buffer oflows in stupid clients (only if enabled in .cf file)
-rw-r--r--usr.sbin/sendmail/src/Makefile25
-rw-r--r--usr.sbin/sendmail/src/deliver.c11
-rw-r--r--usr.sbin/sendmail/src/headers.c99
-rw-r--r--usr.sbin/sendmail/src/main.c13
-rw-r--r--usr.sbin/sendmail/src/mime.c5
-rw-r--r--usr.sbin/sendmail/src/readcf.c27
-rw-r--r--usr.sbin/sendmail/src/sendmail.h4
-rw-r--r--usr.sbin/sendmail/src/util.c176
-rw-r--r--usr.sbin/sendmail/src/version.c2
9 files changed, 344 insertions, 18 deletions
diff --git a/usr.sbin/sendmail/src/Makefile b/usr.sbin/sendmail/src/Makefile
index ca380d905f2..149e49c9e12 100644
--- a/usr.sbin/sendmail/src/Makefile
+++ b/usr.sbin/sendmail/src/Makefile
@@ -1,4 +1,4 @@
-# $OpenBSD: Makefile,v 1.18 1998/07/12 19:44:15 millert Exp $
+# $OpenBSD: Makefile,v 1.19 1998/08/14 00:55:52 millert Exp $
#
# OpenBSD Makefile
#
@@ -8,29 +8,30 @@
PROG= sendmail
-# define the database format to use for aliases et al. Can be -DNEWDB (for
-# the new BSD database package -- this is preferred) or -DNDBM for the NDBM
-# database package. The old putrescent V7 DBM package is no longer
-# supported.
-# You can define both NEWDB and NDBM during a transition period; old
-# databases are read, but the new format will be used on any rebuilds. On
-# really gnarly systems, you can set this to null; it will crawl like a high
-# spiral snail, but it will work.
+# define the database mechanisms available for map & alias lookups:
+# -DNDBM -- use new DBM
+# -DNEWDB -- use new Berkeley DB
+# -DNIS -- include NIS support
+# The really old (V7) DBM library is no longer supported.
+# See README for a description of how these flags interact.
+#
MAPDEF= -DNEWDB -DMAP_REGEX
.if (${YP} == "yes")
MAPDEF+=-DNIS
.endif
-CFLAGS+=-I${.CURDIR} ${MAPDEF} -DNETISO
+# environment definitions (e.g., -DNETISO)
+ENVDEF= -DNETISO -D_FFR_MAX_MIME_HEADER_LENGTH=1
.if (${TCP_WRAPPERS} == "yes")
-CFLAGS+=-DTCPWRAPPERS
-
+ENVDEF+=-DTCPWRAPPERS
DPADD= ${LIBWRAP}
LDADD= -lwrap
.endif
+CFLAGS+=-I${.CURDIR} ${MAPDEF} ${ENVDEF}
+
SRCS= alias.c arpadate.c clock.c collect.c conf.c convtime.c daemon.c \
deliver.c domain.c envelope.c err.c headers.c macro.c main.c map.c \
mci.c mime.c parseaddr.c queue.c readcf.c recipient.c safefile.c \
diff --git a/usr.sbin/sendmail/src/deliver.c b/usr.sbin/sendmail/src/deliver.c
index 0e5eb072990..55083d34783 100644
--- a/usr.sbin/sendmail/src/deliver.c
+++ b/usr.sbin/sendmail/src/deliver.c
@@ -2882,6 +2882,7 @@ putbody(mci, e, separator)
char *separator;
{
char buf[MAXLINE];
+ char *boundaries[MAXMIMENESTING + 1];
/*
** Output the body of the message
@@ -2923,8 +2924,6 @@ putbody(mci, e, separator)
#if MIME8TO7
if (bitset(MCIF_CVT8TO7, mci->mci_flags))
{
- char *boundaries[MAXMIMENESTING + 1];
-
/*
** Do 8 to 7 bit MIME conversion.
*/
@@ -2952,6 +2951,13 @@ putbody(mci, e, separator)
mime7to8(mci, e->e_header, e);
}
# endif
+ else if (MaxMimeHeaderLength > 0 || MaxMimeFieldLength > 0)
+ {
+ /* Use mime8to7 to check multipart for MIME header overflows */
+ boundaries[0] = NULL;
+ mci->mci_flags |= MCIF_INHEADER;
+ mime8to7(mci, e->e_header, e, boundaries, M87F_OUTER|M87F_NO8TO7);
+ }
else
#endif
{
@@ -2966,7 +2972,6 @@ putbody(mci, e, separator)
size_t eol_len;
char peekbuf[10];
- /* we can pass it through unmodified */
if (bitset(MCIF_INHEADER, mci->mci_flags))
{
putline("", mci);
diff --git a/usr.sbin/sendmail/src/headers.c b/usr.sbin/sendmail/src/headers.c
index a04f59e14f4..b753b7e2dcc 100644
--- a/usr.sbin/sendmail/src/headers.c
+++ b/usr.sbin/sendmail/src/headers.c
@@ -1198,6 +1198,39 @@ putheader(mci, hdr, e)
xputs(p);
}
+#if _FFR_MAX_MIME_HEADER_LENGTH
+ /* heuristic shortening of MIME fields to avoid MUA overflows */
+ if (wordinclass(h->h_field, macid("{checkMIMEFieldHeaders}", NULL)))
+ {
+ extern bool fix_mime_header __P((char *));
+
+ if (fix_mime_header(h->h_value))
+ {
+ sm_syslog(LOG_ALERT, e->e_id,
+ "Truncated MIME %s header due to field size (possible attack)",
+ h->h_field);
+ if (tTd(34, 11))
+ printf(" truncated MIME %s header due to field size (possible attack)\n",
+ h->h_field);
+ }
+ }
+
+ if (wordinclass(h->h_field, macid("{checkMIMEHeaders}", NULL)))
+ {
+ extern bool shorten_rfc822_string __P((char *, int));
+
+ if (shorten_rfc822_string(h->h_value, MaxMimeHeaderLength))
+ {
+ sm_syslog(LOG_ALERT, e->e_id,
+ "Truncated long MIME %s header (possible attack)",
+ h->h_field);
+ if (tTd(34, 11))
+ printf(" truncated long MIME %s header (possible attack)\n",
+ h->h_field);
+ }
+ }
+#endif
+
/* suppress Content-Transfer-Encoding: if we are MIMEing */
if (bitset(H_CTE, h->h_flags) &&
bitset(MCIF_CVT8TO7|MCIF_CVT7TO8|MCIF_INMIME, mci->mci_flags))
@@ -1568,3 +1601,69 @@ copyheader(header)
return ret;
}
+ /*
+** FIX_MIME_HEADER -- possibly truncate/rebalance parameters in a MIME header
+**
+** Run through all of the parameters of a MIME header and
+** possibly truncate and rebalance the parameter according
+** to MaxMimeFieldLength.
+**
+** Parameters:
+** string -- the full header
+**
+** Returns:
+** TRUE if the header was modified, FALSE otherwise
+**
+** Side Effects:
+** string modified in place
+*/
+
+bool
+fix_mime_header(string)
+ char *string;
+{
+ bool modified = FALSE;
+ char *begin = string;
+ char *end;
+ extern char *find_character __P((char *, char));
+ extern bool shorten_rfc822_string __P((char *, int));
+
+ if (string == NULL || *string == '\0')
+ return FALSE;
+
+ /* Split on each ';' */
+ while ((end = find_character(begin, ';')) != NULL)
+ {
+ char save = *end;
+ char *bp;
+
+ *end = '\0';
+
+ /* Shorten individual parameter */
+ if (shorten_rfc822_string(begin, MaxMimeFieldLength))
+ modified = TRUE;
+
+ /* Collapse the possibly shortened string with rest */
+ bp = begin + strlen(begin);
+ if (bp != end)
+ {
+ char *ep = end;
+
+ *end = save;
+ end = bp;
+
+ /* copy character by character due to overlap */
+ while (*ep != '\0')
+ *bp++ = *ep++;
+ *bp = '\0';
+ }
+ else
+ *end = save;
+ if (*end == '\0')
+ break;
+
+ /* Move past ';' */
+ begin = end + 1;
+ }
+ return modified;
+}
diff --git a/usr.sbin/sendmail/src/main.c b/usr.sbin/sendmail/src/main.c
index f014d83c359..1af9f6c6e29 100644
--- a/usr.sbin/sendmail/src/main.c
+++ b/usr.sbin/sendmail/src/main.c
@@ -1199,6 +1199,19 @@ main(argc, argv, envp)
setclass('b', "application/octet-stream");
#endif
+
+ /* MIME headers which have fields to check for overflow */
+ setclass(macid("{checkMIMEFieldHeaders}", NULL), "content-disposition");
+ setclass(macid("{checkMIMEFieldHeaders}", NULL), "content-type");
+
+ /* MIME headers to check for overflow */
+ setclass(macid("{checkMIMEHeaders}", NULL), "content-description");
+ setclass(macid("{checkMIMEHeaders}", NULL), "content-disposition");
+ setclass(macid("{checkMIMEHeaders}", NULL), "content-id");
+ setclass(macid("{checkMIMEHeaders}", NULL), "content-transfer-encoding");
+ setclass(macid("{checkMIMEHeaders}", NULL), "content-type");
+ setclass(macid("{checkMIMEHeaders}", NULL), "mime-version");
+
/* operate in queue directory */
if (QueueDir == NULL)
{
diff --git a/usr.sbin/sendmail/src/mime.c b/usr.sbin/sendmail/src/mime.c
index 11a141e4f8a..2262a169b9f 100644
--- a/usr.sbin/sendmail/src/mime.c
+++ b/usr.sbin/sendmail/src/mime.c
@@ -225,7 +225,8 @@ mime8to7(mci, header, e, boundaries, flags)
** Do a recursive descent into the message.
*/
- if (strcasecmp(type, "multipart") == 0 && !bitset(M87F_NO8BIT, flags))
+ if (strcasecmp(type, "multipart") == 0 &&
+ (!bitset(M87F_NO8BIT, flags) || bitset(M87F_NO8TO7, flags)))
{
int blen;
@@ -379,7 +380,7 @@ mime8to7(mci, header, e, boundaries, flags)
*/
sectionsize = sectionhighbits = 0;
- if (!bitset(M87F_NO8BIT, flags))
+ if (!bitset(M87F_NO8BIT|M87F_NO8TO7, flags))
{
/* remember where we were */
offset = ftell(e->e_dfp);
diff --git a/usr.sbin/sendmail/src/readcf.c b/usr.sbin/sendmail/src/readcf.c
index db71937fba6..c66e98e3855 100644
--- a/usr.sbin/sendmail/src/readcf.c
+++ b/usr.sbin/sendmail/src/readcf.c
@@ -1519,6 +1519,10 @@ struct optioninfo
#define O_TRUSTFILEOWN 0xa7
{ "TrustedFileOwner", O_TRUSTFILEOWN, FALSE },
#endif
+#if _FFR_MAX_MIME_HEADER_LENGTH
+#define O_MAXMIMEHDRLEN 0xa8
+ { "MaxMimeHeaderLength", O_MAXMIMEHDRLEN, FALSE },
+#endif
{ NULL, '\0', FALSE }
};
@@ -2428,6 +2432,29 @@ setoption(opt, val, safe, sticky, e)
break;
#endif
+#if _FFR_MAX_MIME_HEADER_LENGTH
+ case O_MAXMIMEHDRLEN:
+ p = strchr(val, '/');
+ if (p != NULL)
+ *p++ = '\0';
+ MaxMimeHeaderLength = atoi(val);
+ if (p != NULL && *p != '\0')
+ MaxMimeFieldLength = atoi(p);
+ else
+ MaxMimeFieldLength = MaxMimeHeaderLength / 2;
+
+ if (MaxMimeHeaderLength < 0)
+ MaxMimeHeaderLength = 0;
+ else if (MaxMimeHeaderLength < 128)
+ printf("Warning: MaxMimeHeaderLength: header length limit set lower than 128\n");
+
+ if (MaxMimeFieldLength < 0)
+ MaxMimeFieldLength = 0;
+ else if (MaxMimeFieldLength < 40)
+ printf("Warning: MaxMimeHeaderLength: field length limit set lower than 40\n");
+ break;
+#endif
+
default:
if (tTd(37, 1))
{
diff --git a/usr.sbin/sendmail/src/sendmail.h b/usr.sbin/sendmail/src/sendmail.h
index 7bfe11b7606..b35b58908b8 100644
--- a/usr.sbin/sendmail/src/sendmail.h
+++ b/usr.sbin/sendmail/src/sendmail.h
@@ -1008,6 +1008,7 @@ extern bool filechanged __P((char *, int, struct stat *));
#define M87F_OUTER 0 /* outer context */
#define M87F_NO8BIT 0x0001 /* can't have 8-bit in this section */
#define M87F_DIGEST 0x0002 /* processing multipart/digest */
+#define M87F_NO8TO7 0x0004 /* don't do 8->7 bit conversions */
/*
@@ -1285,6 +1286,9 @@ EXTERN char *DoubleBounceAddr; /* where to send double bounces */
EXTERN char **ExternalEnviron; /* input environment */
EXTERN char *UserEnviron[MAXUSERENVIRON + 1];
/* saved user environment */
+EXTERN int MaxMimeHeaderLength; /* maximum MIME header length */
+EXTERN int MaxMimeFieldLength; /* maximum MIME field length */
+
extern int errno;
/*
diff --git a/usr.sbin/sendmail/src/util.c b/usr.sbin/sendmail/src/util.c
index ba35dcd9531..04aea652cc4 100644
--- a/usr.sbin/sendmail/src/util.c
+++ b/usr.sbin/sendmail/src/util.c
@@ -174,6 +174,182 @@ rfc822_string(s)
return TRUE;
}
/*
+** SHORTEN_RFC822_STRING -- Truncate and rebalance an RFC822 string
+**
+** Arbitratily shorten (in place) an RFC822 string and rebalance
+** comments and quotes.
+**
+** Parameters:
+** string -- the string to shorten
+** length -- the maximum size, 0 if no maximum
+**
+** Returns:
+** TRUE if string is changed, FALSE otherwise
+**
+** Side Effects:
+** Changes string in place, possibly resulting
+** in a shorter string.
+*/
+
+bool
+shorten_rfc822_string(string, length)
+ char *string;
+ size_t length;
+{
+ bool backslash = FALSE;
+ bool modified = FALSE;
+ bool quoted = FALSE;
+ size_t slen;
+ int parencount = 0;
+ char *ptr = string;
+
+ /*
+ ** If have to rebalance an already short enough string,
+ ** need to do it within allocated space.
+ */
+ slen = strlen(string);
+ if (length == 0 || slen < length)
+ length = slen;
+
+ while (*ptr != '\0')
+ {
+ if (backslash)
+ {
+ backslash = FALSE;
+ goto increment;
+ }
+
+ if (*ptr == '\\')
+ backslash = TRUE;
+ else if (*ptr == '(')
+ {
+ if (!quoted)
+ parencount++;
+ }
+ else if (*ptr == ')')
+ {
+ if (--parencount < 0)
+ parencount = 0;
+ }
+
+ /* Inside a comment, quotes don't matter */
+ if (parencount <= 0 && *ptr == '"')
+ quoted = !quoted;
+
+increment:
+ /* Check for sufficient space for next character */
+ if (length - (ptr - string) <= ((backslash ? 1 : 0) +
+ parencount +
+ (quoted ? 1 : 0)))
+ {
+ /* Not enough, backtrack */
+ if (*ptr == '\\')
+ backslash = FALSE;
+ else if (*ptr == '(' && !quoted)
+ parencount--;
+ else if (*ptr == '"' && !backslash && parencount == 0)
+ quoted = FALSE;
+ break;
+ }
+ ptr++;
+ }
+
+ /* Rebalance */
+ while (parencount-- > 0)
+ {
+ if (*ptr != ')')
+ {
+ modified = TRUE;
+ *ptr = ')';
+ }
+ ptr++;
+ }
+ if (quoted)
+ {
+ if (*ptr != '"')
+ {
+ modified = TRUE;
+ *ptr = '"';
+ }
+ ptr++;
+ }
+ if (*ptr != '\0')
+ {
+ modified = TRUE;
+ *ptr = '\0';
+ }
+ return modified;
+}
+ /*
+** FIND_CHARACTER -- find an unquoted character in an RFC822 string
+**
+** Find an unquoted, non-commented character in an RFC822
+** string and return a pointer to its location in the
+** string.
+**
+** Parameters:
+** string -- the string to search
+** character -- the character to find
+**
+** Returns:
+** pointer to the character, or
+** a pointer to the end of the line if character is not found
+*/
+
+char *
+find_character(string, character)
+ char *string;
+ char character;
+{
+ bool backslash = FALSE;
+ bool quoted = FALSE;
+ int parencount = 0;
+
+ while (string != NULL && *string != '\0')
+ {
+ if (backslash)
+ {
+ backslash = FALSE;
+ if (!quoted && character == '\\' && *string == '\\')
+ break;
+ string++;
+ continue;
+ }
+ switch (*string)
+ {
+ case '\\':
+ backslash = TRUE;
+ break;
+
+ case '(':
+ if (!quoted)
+ parencount++;
+ break;
+
+ case ')':
+ if (--parencount < 0)
+ parencount = 0;
+ break;
+ }
+
+ /* Inside a comment, nothing matters */
+ if (parencount > 0)
+ {
+ string++;
+ continue;
+ }
+
+ if (*string == '"')
+ quoted = !quoted;
+ else if (*string == character && !quoted)
+ break;
+ string++;
+ }
+
+ /* Return pointer to the character */
+ return string;
+}
+ /*
** XALLOC -- Allocate memory and bitch wildly on failure.
**
** THIS IS A CLUDGE. This should be made to give a proper
diff --git a/usr.sbin/sendmail/src/version.c b/usr.sbin/sendmail/src/version.c
index bf0b3918adc..fdcad5c24f7 100644
--- a/usr.sbin/sendmail/src/version.c
+++ b/usr.sbin/sendmail/src/version.c
@@ -14,4 +14,4 @@
static char sccsid[] = "@(#)version.c 8.9.1.1 (Berkeley) 7/2/98";
#endif /* not lint */
-char Version[] = "8.9.1";
+char Version[] = "8.9.1a";