Please see http://www.sendmail.com/sendmail.8.9.1a.html or ftp://ftp.sendmail.org/pub/sendmail/sendmail.8.9.1a.patch.README for important information about this patch, including the necessary steps to install and activate it. ------- headers.c ------- *** - Wed Dec 31 16:00:00 1969 --- headers.c Mon Aug 10 18:12:34 1998 *************** *** 1194,1199 **** --- 1194,1232 ---- 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)) *************** *** 1563,1566 **** --- 1596,1665 ---- *tail = NULL; 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; } ------- main.c ------- *** - Wed Dec 31 16:00:00 1969 --- main.c Sun Aug 9 11:45:57 1998 *************** *** 1192,1197 **** --- 1192,1210 ---- 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) { ------- conf.h ------- ------- sendmail.h ------- *** - Wed Dec 31 16:00:00 1969 --- sendmail.h Thu Aug 6 13:48:34 1998 *************** *** 1005,1010 **** --- 1005,1011 ---- #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 */ /* *************** *** 1282,1287 **** --- 1283,1291 ---- 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; /* ------- util.c ------- *** - Wed Dec 31 16:00:00 1969 --- util.c Mon Aug 10 18:30:43 1998 *************** *** 170,175 **** --- 170,351 ---- 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 ------- readcf.c ------- *** - Wed Dec 31 16:00:00 1969 --- readcf.c Sun Aug 9 00:10:41 1998 *************** *** 1515,1520 **** --- 1515,1524 ---- #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 } }; *************** *** 2421,2426 **** --- 2425,2453 ---- TrustedFileUid = 0; } #endif + 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 ------- deliver.c ------- *** - Wed Dec 31 16:00:00 1969 --- deliver.c Fri Aug 7 10:35:44 1998 *************** *** 2878,2883 **** --- 2878,2884 ---- char *separator; { char buf[MAXLINE]; + char *boundaries[MAXMIMENESTING + 1]; /* ** Output the body of the message *************** *** 2919,2926 **** #if MIME8TO7 if (bitset(MCIF_CVT8TO7, mci->mci_flags)) { - char *boundaries[MAXMIMENESTING + 1]; - /* ** Do 8 to 7 bit MIME conversion. */ --- 2920,2925 ---- *************** *** 2948,2953 **** --- 2947,2959 ---- 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 { *************** *** 2962,2968 **** size_t eol_len; char peekbuf[10]; - /* we can pass it through unmodified */ if (bitset(MCIF_INHEADER, mci->mci_flags)) { putline("", mci); --- 2968,2973 ---- ------- mime.c ------- *** - Wed Dec 31 16:00:00 1969 --- mime.c Thu Aug 6 14:13:24 1998 *************** *** 221,227 **** ** Do a recursive descent into the message. */ ! if (strcasecmp(type, "multipart") == 0 && !bitset(M87F_NO8BIT, flags)) { int blen; --- 221,228 ---- ** Do a recursive descent into the message. */ ! if (strcasecmp(type, "multipart") == 0 && ! (!bitset(M87F_NO8BIT, flags) || bitset(M87F_NO8TO7, flags))) { int blen; *************** *** 375,381 **** */ sectionsize = sectionhighbits = 0; ! if (!bitset(M87F_NO8BIT, flags)) { /* remember where we were */ offset = ftell(e->e_dfp); --- 376,382 ---- */ sectionsize = sectionhighbits = 0; ! if (!bitset(M87F_NO8BIT|M87F_NO8TO7, flags)) { /* remember where we were */ offset = ftell(e->e_dfp); ------- version.c ------- *** - Thu Jul 2 11:04:58 1998 --- version.c Mon Aug 10 14:41:30 1998 *************** *** 14,17 **** static char sccsid[] = "@(#)version.c 8.9.1.1 (Berkeley) 7/2/98"; #endif /* not lint */ ! char Version[] = "8.9.1"; --- 14,17 ---- static char sccsid[] = "@(#)version.c 8.9.1.1 (Berkeley) 7/2/98"; #endif /* not lint */ ! char Version[] = "8.9.1a";