COMMAND

    sendmail's mail.local

SYSTEMS AFFECTED

    Sendmail (see below)

PROBLEM

    3APA3A found following.  There are 4 problems:

        1. Possibility to insert LMTP commands into e-mail message
        2. Possibility of deadlock between sendmail and mail.local
        3. Possibility to corrupt user's mailbox
        4. Possibility  to  change  e-mail  headers of the message  in
           user's mailbox

    Vulnerable software:

        - Problems 1 and 2: sendmail before 8.10.0 (8.9.3 tested), all
                            platforms
        - Problems 3 and 4: sendmail 8.10.0 and 8.10.1 (8.10.1 tested)
                            under Solaris only

    Problem 1:
    ==========
    While in  LMTP mode  mail.local checks  input for  ".\n" string to
    find the end of the message.  Sendmail will not allow this  string
    to pass through, but using of fgets() with buffersize 2048  allows
    to emulate end of the message with a string

        (2047 chars).\n

    The rest of the  message will be treated  as LMTP commands.   This
    allows to send messages  to multiple mailboxes (including  private
    and closed ones) with  any spoofed information bypassing  sendmail
    and without any filtering, checking, logging etc.

    Problem 2:
    ==========
    In the conditions  of the problem  1, mail.local will  return LMTP
    answers to sendmail.  Because sendmail doesn't expects any  output
    from mail.local at this point, replies will not be taken from  I/O
    buffer.  If  extreamaly large number  of LMTP commands  is used in
    the text, or the rest of the message is just a collection of  text
    strings (in this case mail.local will reply with a error for every
    string) sendmail and mail.local will be deadlocked then the buffer
    will be filled.  This deadlock problem regardless to LMTP  command
    execution  was  reported  to  sendmail  team  by  Peter Jeremy and
    patched in 8.10.0 release.

    Problem 3:
    ==========
    sendmail 8.10.0 introduces  support for "Content-Length:  " header
    for Solaris  in mail.local.   This header  stores the  size of the
    message and is used by mail retrieving software (f.e. qpopper)  to
    calculate message boundary in the mailbox.  If this header already
    exists in message it will be stripped, but because fgets() is used
    for  parsing  message  it's   possible  to  add   "Content-Length:
    99999999" header and comment  out real "Content-Length: "  causing
    mailbox corruption using strings

        (2047 chars)\n
        Content-Length: 99999999\n

    in the end of message header.

    Problem 4:
    ==========
    If message body is empty and last string of the message header is

        (2047 chars)Content-Length: \n

    the headers of this message will  be glued to the next message  in
    mailbox,  because  '\n'  character  will  be  lost while stripping
    Content-Length.

SOLUTION

    Vendor contacted, problems 1 and 2 are patched in sendmail 8.10.0.
    The patch below will be in the next release of sendmail:

    Index: mail.local.c
    ===================================================================
    RCS file: /cvs/mail.local/mail.local.c,v
    retrieving revision 8.145
    retrieving revision 8.146
    diff -u -r8.145 -r8.146
    --- mail.local.c	2000/04/25 15:27:08	8.145
    +++ mail.local.c	2000/04/25 15:48:32	8.146
    @@ -746,7 +746,8 @@
 	    FILE *fp = NULL;
 	    time_t tval;
 	    bool eline;
    -	bool fullline = TRUE;
    +	bool fullline = TRUE;	/* current line is terminated */
    +	bool prevfl;		/* previous line was terminated */
 	    char line[2048];
 	    int fd;
 	    char tmpbuf[sizeof _PATH_LOCTMP + 1];
    @@ -783,16 +784,19 @@
     #endif /* CONTENTLENGTH */
    
 	    line[0] = '\0';
    -	for (eline = TRUE; fgets(line, sizeof(line), stdin); )
    +	eline = TRUE;
    +	while (fgets(line, sizeof(line), stdin) != (char *)NULL)
 	    {
 		    size_t line_len = 0;
 		    int peek;
    +
    +		prevfl = fullline;	/* preserve state of previous line */
 		    while (line[line_len] != '\n' && line_len < sizeof(line) - 2)
 			    line_len++;
 		    line_len++;
    
 		    /* Check for dot-stuffing */
    -		if (fullline && lmtprcpts && line[0] == '.')
    +		if (prevfl && lmtprcpts && line[0] == '.')
 		    {
 			    if (line[1] == '\n' ||
 			        (line[1] == '\r' && line[2] == '\n'))
    @@ -801,7 +805,7 @@
 			    line_len--;
 		    }
    
    -		/* Check to see if we have the full line from the fgets() */
    +		/* Check to see if we have the full line from fgets() */
 		    fullline = FALSE;
 		    if (line_len > 0)
 		    {
    @@ -809,12 +813,11 @@
 			    {
 				    if (line_len >= 2 &&
 				        line[line_len - 2] == '\r')
    -				    {
    -					(void) strlcpy(line + line_len - 2,
    -						       "\n", sizeof line -
    -							     line_len + 2);
    +				{
    +					line[line_len - 2] = '\n';
    +					line[line_len - 1] = '\0';
 					    line_len--;
    -				    }
    +				}
 				    fullline = TRUE;
 			    }
 			    else if (line[line_len - 1] == '\r')
    @@ -834,7 +837,7 @@
 			    fullline = TRUE;
    
     #ifdef CONTENTLENGTH
    -		if (line[0] == '\n' && HeaderLength == 0)
    +		if (prevfl && line[0] == '\n' && HeaderLength == 0)
 		    {
 			    eline = FALSE;
 			    HeaderLength = ftell(fp);
    @@ -849,7 +852,7 @@
 			    }
 		    }
     #else /* CONTENTLENGTH */
    -		if (line[0] == '\n')
    +		if (prevfl && line[0] == '\n')
 			    eline = TRUE;
     #endif /* CONTENTLENGTH */
 		    else
    @@ -860,10 +863,17 @@
 			    eline = FALSE;
     #ifdef CONTENTLENGTH
 			    /* discard existing "Content-Length:" headers */
    -			if (HeaderLength == 0 &&
    +			if (prevfl && HeaderLength == 0 &&
 			        (line[0] == 'C' || line[0] == 'c') &&
 			        strncasecmp(line, ContentHdr, 15) == 0)
    -					continue;
    +			{
    +				/*
    +				**  be paranoid: clear the line
    +				**  so no "wrong matches" may occur later
    +				*/
    +				line[0] = '\0';
    +				continue;
    +			}
     #endif /* CONTENTLENGTH */
    
 		    }