diff options
author | Todd C. Miller <millert@cvs.openbsd.org> | 1998-08-14 00:56:04 +0000 |
---|---|---|
committer | Todd C. Miller <millert@cvs.openbsd.org> | 1998-08-14 00:56:04 +0000 |
commit | 66d78bfbde3d33a31d3df849876a7f66b90ac3f7 (patch) | |
tree | 3fdd6117cd7e2ce3d9d915b1e36a66cdc9ece0a1 | |
parent | 791af4a1e2fe58697fff3df66450976407115302 (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/Makefile | 25 | ||||
-rw-r--r-- | usr.sbin/sendmail/src/deliver.c | 11 | ||||
-rw-r--r-- | usr.sbin/sendmail/src/headers.c | 99 | ||||
-rw-r--r-- | usr.sbin/sendmail/src/main.c | 13 | ||||
-rw-r--r-- | usr.sbin/sendmail/src/mime.c | 5 | ||||
-rw-r--r-- | usr.sbin/sendmail/src/readcf.c | 27 | ||||
-rw-r--r-- | usr.sbin/sendmail/src/sendmail.h | 4 | ||||
-rw-r--r-- | usr.sbin/sendmail/src/util.c | 176 | ||||
-rw-r--r-- | usr.sbin/sendmail/src/version.c | 2 |
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"; |