php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #14983 mail function buffer overflow
Submitted: 2002-01-10 18:56 UTC Modified: 2002-06-15 03:56 UTC
From: enricod at videotron dot ca Assigned:
Status: Closed Package: Reproducible crash
PHP Version: 4.1.1 OS: Windows 2000
Private report: No CVE-ID: None
 [2002-01-10 18:56 UTC] enricod at videotron dot ca
from win32\sendmail.h :

#define  MAIL_BUFFER_SIZE		(1024*4)	/* 4k buffer */

sendmail.c uses sprintf's to this buffer without range checking resulting in a crash of php in most of the cases when the "extra headers" surpass 4k.

Im working on a fixed version of sendmail.c if your interested.

ciao,
Enrico

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2002-01-10 21:07 UTC] enricod at videotron dot ca
this is what i came up with to fix the behaviour. It's a quick fix cause i needed PHP working for a demo tomorrow, but it appears to fix the problem.

- Enrico

/* 
 *    PHP Sendmail for Windows.
 *
 *  This file is rewriten specificly for PHPFI.  Some functionality
 *  has been removed (MIME and file attachments).  This code was 
 *  modified from code based on code writen by Jarle Aase.
 *
 *  This class is based on the original code by Jarle Aase, see bellow:
 *  wSendmail.cpp  It has been striped of some functionality to match
 *  the requirements of phpfi.
 *
 *  Very simple SMTP Send-mail program for sending command-line level
 *  emails and CGI-BIN form response for the Windows platform.
 *
 *  The complete wSendmail package with source code can be located
 *  from http://www.jgaa.com
 *

 modified by Enrico Demarin Jan 2001
 */

#include "php.h"				/*php specific */
#include <stdio.h>
#include <stdlib.h>
#include <winsock.h>
#include "time.h"
#include <string.h>
#include <malloc.h>
#include <memory.h>
#include <winbase.h>
#include "sendmail.h"
#include "php_ini.h"

/*
   extern int _daylight;
   extern long _timezone;
 */
/*enum
   {
   DO_CONNECT = WM_USER +1
   };
 */

static char *days[] =
{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
static char *months[] =
{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

#ifndef THREAD_SAFE
char Buffer[MAIL_BUFFER_SIZE];

/* socket related data */
SOCKET sc;
WSADATA Data;
struct hostent *adr;
SOCKADDR_IN sock_in;
int WinsockStarted;
/* values set by the constructor */
char *AppName;
char MailHost[HOST_NAME_LEN];
char LocalHost[HOST_NAME_LEN];
#endif
char seps[] = " ,\t\n";
char *php_mailer = "PHP 4.0 WIN32";

char *get_header(char *h, char *headers);

/* Error messages */
static char *ErrorMessages[] =
{
	{"Success"},
	{"Bad arguments from form"},
	{"Unable to open temporary mailfile for read"},
	{"Failed to Start Sockets"},
	{"Failed to Resolve Host"},
	{"Failed to obtain socket handle"},
	{"Failed to Connect"},
	{"Failed to Send"},
	{"Failed to Receive"},
	{"Server Error"},
	{"Failed to resolve the host IP name"},
	{"Out of memory"},
	{"Unknown error"},
	{"Bad Message Contents"},
	{"Bad Message Subject"},
	{"Bad Message destination"},
	{"Bad Message Return Path"},
	{"Bad Mail Host"},
	{"Bad Message File"},
	{"PHP Internal error: php.ini sendmail from variable not set!"}
};



/*********************************************************************
// Name:  TSendMail
// Input:   1) host:    Name of the mail host where the SMTP server resides
//                      max accepted length of name = 256
//          2) appname: Name of the application to use in the X-mailer
//                      field of the message. if NULL is given the application
//                      name is used as given by the GetCommandLine() function
//                      max accespted length of name = 100
// Output:  1) error:   Returns the error code if something went wrong or
//                      SUCCESS otherwise.
//
//  See SendText() for additional args!
//********************************************************************/
int TSendMail(char *host, int *error,
			  char *headers, char *Subject, char *mailTo, char *data)
{
	int ret;
	char *RPath = NULL;

	WinsockStarted = FALSE;

	if (host == NULL) {
		*error = BAD_MAIL_HOST;
		return BAD_MAIL_HOST;
	} else if (strlen(host) >= HOST_NAME_LEN) {
		*error = BAD_MAIL_HOST;
		return BAD_MAIL_HOST;
	} else {
		strcpy(MailHost, host);
	}

	if (INI_STR("sendmail_from")){
		RPath = estrdup(INI_STR("sendmail_from"));
		} else {
			return 19;
	}

	/* attempt to connect with mail host */
	*error = MailConnect();
	if (*error != 0) {
		if(RPath)efree(RPath);
		return *error;
	} else {
		ret = SendText(RPath, Subject, mailTo, data, headers);
		TSMClose();
		if (ret != SUCCESS) {
			*error = ret;
		}
		if(RPath)efree(RPath);
		return ret;
	}
}

//********************************************************************
// Name:  TSendMail::~TSendMail
// Input:
// Output:
// Description: DESTRUCTOR
// Author/Date:  jcar 20/9/96
// History:
//********************************************************************/
void TSMClose()
{
	Post("QUIT\r\n");
	Ack();
	/* to guarantee that the cleanup is not made twice and 
	   compomise the rest of the application if sockets are used
	   elesewhere 
	*/

	shutdown(sc, 0); 
	closesocket(sc);
}


/*********************************************************************
// Name:  char *GetSMErrorText
// Input:   Error index returned by the menber functions
// Output:  pointer to a string containing the error description
// Description:
// Author/Date:  jcar 20/9/96
// History:
//*******************************************************************/
char *GetSMErrorText(int index)
{

	if ((index > MAX_ERROR_INDEX) || (index < MIN_ERROR_INDEX))
		return (ErrorMessages[UNKNOWN_ERROR]);
	else
		return (ErrorMessages[index]);
}


/*********************************************************************
// Name:  TSendText
// Input:       1) RPath:   return path of the message
//                                  Is used to fill the "Return-Path" and the
//                                  "X-Sender" fields of the message.
//                  2) Subject: Subject field of the message. If NULL is given
//                                  the subject is set to "No Subject"
//                  3) mailTo:  Destination address
//                  4) data:        Null terminated string containing the data to be send.
// Output:      Error code or SUCCESS
// Description:
// Author/Date:  jcar 20/9/96
// History:
//*******************************************************************/
int SendText(char *RPath, char *Subject, char *mailTo, char *data, char *headers)
{
	int res, i;
	char *p;
	char *tempMailTo, *token, *pos1, *pos2;

	/* check for NULL parameters */
	if (data == NULL)
		return (BAD_MSG_CONTENTS);
	if (mailTo == NULL)
		return (BAD_MSG_DESTINATION);
	if (RPath == NULL)
		return (BAD_MSG_RPATH);

	/* simple checks for the mailto address */
	/* have ampersand ? */
	if (strchr(mailTo, '@') == NULL)
		return (BAD_MSG_DESTINATION);

	snprintf(Buffer, MAIL_BUFFER_SIZE, "HELO %s\r\n", LocalHost);

	/* in the beggining of the dialog */
	/* attempt reconnect if the first Post fail */
	if ((res = Post(Buffer)) != SUCCESS) {
		MailConnect();
		if ((res = Post(Buffer)) != SUCCESS)
			return (res);
	}
	if ((res = Ack()) != SUCCESS)
		return (res);

	snprintf(Buffer, MAIL_BUFFER_SIZE,"MAIL FROM:<%s>\r\n", RPath);
	if ((res = Post(Buffer)) != SUCCESS)
		return (res);
	if ((res = Ack()) != SUCCESS)
		return (res);


	tempMailTo = estrdup(mailTo);
	
	/* Send mail to all rcpt's */
	token = strtok(tempMailTo, ",");
	while(token != NULL)
	{
		snprintf(Buffer, MAIL_BUFFER_SIZE, "RCPT TO:<%s>\r\n", token);
		if ((res = Post(Buffer)) != SUCCESS)
			return (res);
		if ((res = Ack()) != SUCCESS)
			return (res);
		token = strtok(NULL, ",");
	}

	/* Send mail to all Cc rcpt's */
	efree(tempMailTo);
	if (headers && (pos1 = strstr(headers, "Cc:"))) {
		pos2 = strstr(pos1, "\r\n");
		tempMailTo = estrndup(pos1, pos2-pos1);

		token = strtok(tempMailTo, ",");
		while(token != NULL)
		{
			snprintf(Buffer, MAIL_BUFFER_SIZE, "RCPT TO:<%s>\r\n", token);
			if ((res = Post(Buffer)) != SUCCESS)
				return (res);
			if ((res = Ack()) != SUCCESS)
				return (res);
			token = strtok(NULL, ",");
		}
		efree(tempMailTo);
	}

	if ((res = Post("DATA\r\n")) != SUCCESS)
		return (res);
	if ((res = Ack()) != SUCCESS)
		return (res);


	/* send message header */
	if (Subject == NULL)
		res = PostHeader(RPath, "No Subject", mailTo, headers);
	else
		res = PostHeader(RPath, Subject, mailTo, headers);
	if (res != SUCCESS)
		return (res);


	/* send message contents in 1024 chunks */
	if (strlen(data) <= 1024) {
		if ((res = Post(data)) != SUCCESS)
			return (res);
	} else {
		p = data;
		while (1) {
			if (*p == '\0')
				break;
			if (strlen(p) >= 1024)
				i = 1024;
			else
				i = strlen(p);

			/* put next chunk in buffer */
			strncpy(Buffer, p, i);
			Buffer[i] = '\0';
			p += i;

			/* send chunk */
			if ((res = Post(Buffer)) != SUCCESS)
				return (res);
		}
	}

	/*send termination dot */
	if ((res = Post("\r\n.\r\n")) != SUCCESS)
		return (res);
	if ((res = Ack()) != SUCCESS)
		return (res);

	return (SUCCESS);
}



/*********************************************************************
// Name:  PostHeader
// Input:       1) return path
//                  2) Subject
//                  3) destination address
//                  4) DoMime flag
// Output:      Error code or Success
// Description:
// Author/Date:  jcar 20/9/96
// History:
//********************************************************************/
int PostHeader(char *RPath, char *Subject, char *mailTo, char *xheaders)
{

	/* Print message header according to RFC 822 */
	/* Return-path, Received, Date, From, Subject, Sender, To, cc */

	time_t tNow = time(NULL);
	struct tm *tm = localtime(&tNow);
	int zoneh = abs(_timezone);
	int zonem, res;
	int i;

	char *p;

	zoneh /= (60 * 60);
	zonem = (abs(_timezone) / 60) - (zoneh * 60);

	if(!xheaders || !strstr(xheaders, "Date:")){
		snprintf(Buffer, MAIL_BUFFER_SIZE, "Date: %s, %02d %s %04d %02d:%02d:%02d %s%02d%02d\r\n",
					 days[tm->tm_wday],
					 tm->tm_mday,
					 months[tm->tm_mon],
					 tm->tm_year + 1900,
					 tm->tm_hour,
					 tm->tm_min,
					 tm->tm_sec,
					 (_timezone > 0) ? "+" : (_timezone < 0) ? "-" : "",
					 zoneh,
					 zonem);

					if ((res = Post(Buffer)) != SUCCESS)
					return (res);
	}

	if(!xheaders || !strstr(xheaders, "From:")){
		snprintf(Buffer, MAIL_BUFFER_SIZE, "From: %s\r\n", RPath);
		if ((res = Post(Buffer)) != SUCCESS)
		return (res);
	}

	snprintf(Buffer, MAIL_BUFFER_SIZE, "Subject: %s\r\n", Subject);
	if ((res = Post(Buffer)) != SUCCESS)
	return (res);

	if(!xheaders || !strstr(xheaders, "To:")){
		snprintf(Buffer, MAIL_BUFFER_SIZE, "To: %s\r\n", mailTo);
		if ((res = Post(Buffer)) != SUCCESS)
		return (res);
	}

	if(xheaders){

				/* send extre headers in 1024 chunks */
				if (strlen(xheaders) <= 1024) {
						if ((res = Post(xheaders)) != SUCCESS)
						return (res);
				} else {
						p = xheaders;
		
						while (1) {
						if (*p == '\0')
							break;
						if (strlen(p) >= 1024)
						i = 1024;
							else
						i = strlen(p);

						/* put next chunk in buffer */
						strncpy(Buffer, p, i);
						Buffer[i] = '\0';
						p += i;

						/* send chunk */
						if ((res = Post(Buffer)) != SUCCESS)
						return (res);
						}
				}
				}


	if ((res = Post("\r\n")) != SUCCESS)
		return (res);

	return (SUCCESS);
}



/*********************************************************************
// Name:  MailConnect
// Input:   None
// Output:  None
// Description: Connect to the mail host and receive the welcome message.
// Author/Date:  jcar 20/9/96
// History:
//********************************************************************/
int MailConnect()
{

	int res;
	short portnum;

	/* Create Socket */
	if ((sc = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
		return (FAILED_TO_OBTAIN_SOCKET_HANDLE);

	/* Get our own host name */
	if (gethostname(LocalHost, HOST_NAME_LEN))
		return (FAILED_TO_GET_HOSTNAME);

	/* Resolve the servers IP */
	/*
	if (!isdigit(MailHost[0])||!gethostbyname(MailHost))
	{
		return (FAILED_TO_RESOLVE_HOST);
	}
        */

	portnum = (short) INI_INT("sendmail_port");
	if (!portnum) {
		portnum = 25;
	}

	/* Connect to server */
	sock_in.sin_family = AF_INET;
	sock_in.sin_port = htons(portnum);
	sock_in.sin_addr.S_un.S_addr = GetAddr(MailHost);

	if (connect(sc, (LPSOCKADDR) & sock_in, sizeof(sock_in)))
		return (FAILED_TO_CONNECT);

	/* receive Server welcome message */
	res = Ack();
	return (res);
}






/*********************************************************************
// Name:  Post
// Input:
// Output:
// Description:
// Author/Date:  jcar 20/9/96
// History:
//********************************************************************/
int Post(LPCSTR msg)
{
	int len = strlen(msg);
	int slen;
	int index = 0;

	while (len > 0) {
		if ((slen = send(sc, msg + index, len, 0)) < 1)
			return (FAILED_TO_SEND);
		len -= slen;
		index += slen;
	}
	return (SUCCESS);
}



/*********************************************************************
// Name:  Ack
// Input:
// Output:
// Description:
// Get the response from the server. We only want to know if the
// last command was successful.
// Author/Date:  jcar 20/9/96
// History:
//********************************************************************/
int Ack()
{
	static char *buf;
	int rlen;
	int Index = 0;
	int Received = 0;

	if (!buf)
		if ((buf = (char *) malloc(1024 * 4)) == NULL)
			return (OUT_OF_MEMORY);

  again:

	if ((rlen = recv(sc, buf + Index, ((1024 * 4) - 1) - Received, 0)) < 1)
		return (FAILED_TO_RECEIVE);

	Received += rlen;
	buf[Received] = 0;
	/*err_msg   fprintf(stderr,"Received: (%d bytes) %s", rlen, buf + Index); */

	/* Check for newline */
	Index += rlen;
	
	if ((buf[Received - 4] == ' ' && buf[Received - 3] == '-') ||
	    (buf[Received - 2] != '\r') || (buf[Received - 1] != '\n'))
		/* err_msg          fprintf(stderr,"Incomplete server message. Awaiting CRLF\n"); */
		goto again;				/* Incomplete data. Line must be terminated by CRLF
		                           And not contain a space followed by a '-' */

	if (buf[0] > '3')
		return (SMTP_SERVER_ERROR);

	return (SUCCESS);
}


/*********************************************************************
// Name:  unsigned long GetAddr (LPSTR szHost)
// Input:
// Output:
// Description: Given a string, it will return an IP address.
//   - first it tries to convert the string directly
//   - if that fails, it tries o resolve it as a hostname
//
// WARNING: gethostbyname() is a blocking function
// Author/Date:  jcar 20/9/96
// History:
//********************************************************************/
unsigned long GetAddr(LPSTR szHost)
{
	LPHOSTENT lpstHost;
	u_long lAddr = INADDR_ANY;

	/* check that we have a string */
	if (*szHost) {

		/* check for a dotted-IP address string */
		lAddr = inet_addr(szHost);

		/* If not an address, then try to resolve it as a hostname */
		if ((lAddr == INADDR_NONE) && (strcmp(szHost, "255.255.255.255"))) {

			lpstHost = gethostbyname(szHost);
			if (lpstHost) {		/* success */
				lAddr = *((u_long FAR *) (lpstHost->h_addr));
			} else {
				lAddr = INADDR_ANY;		/* failure */
			}
		}
	}
	return (lAddr);
}								/* end GetAddr() */

 [2002-01-11 11:00 UTC] sander@php.net
It would be nice if you post a diff instead of the whole file...
 [2002-01-11 11:31 UTC] enricod at videotron dot ca
Here is the diff.

-Enrico

--- sendmail-old.c	Mon Sep  4 18:26:16 2000
+++ sendmail.c	Thu Jan 10 20:20:56 2002
@@ -16,6 +16,8 @@
  *  The complete wSendmail package with source code can be located
  *  from http://www.jgaa.com
  *
+
+ modified by Enrico Demarin Jan 2001
  */
 
 #include "php.h"				/*php specific */
@@ -217,7 +219,7 @@
 	if (strchr(mailTo, '@') == NULL)
 		return (BAD_MSG_DESTINATION);
 
-	sprintf(Buffer, "HELO %s\r\n", LocalHost);
+	snprintf(Buffer, MAIL_BUFFER_SIZE, "HELO %s\r\n", LocalHost);
 
 	/* in the beggining of the dialog */
 	/* attempt reconnect if the first Post fail */
@@ -229,7 +231,7 @@
 	if ((res = Ack()) != SUCCESS)
 		return (res);
 
-	sprintf(Buffer, "MAIL FROM:<%s>\r\n", RPath);
+	snprintf(Buffer, MAIL_BUFFER_SIZE,"MAIL FROM:<%s>\r\n", RPath);
 	if ((res = Post(Buffer)) != SUCCESS)
 		return (res);
 	if ((res = Ack()) != SUCCESS)
@@ -242,7 +244,7 @@
 	token = strtok(tempMailTo, ",");
 	while(token != NULL)
 	{
-		sprintf(Buffer, "RCPT TO:<%s>\r\n", token);
+		snprintf(Buffer, MAIL_BUFFER_SIZE, "RCPT TO:<%s>\r\n", token);
 		if ((res = Post(Buffer)) != SUCCESS)
 			return (res);
 		if ((res = Ack()) != SUCCESS)
@@ -259,7 +261,7 @@
 		token = strtok(tempMailTo, ",");
 		while(token != NULL)
 		{
-			sprintf(Buffer, "RCPT TO:<%s>\r\n", token);
+			snprintf(Buffer, MAIL_BUFFER_SIZE, "RCPT TO:<%s>\r\n", token);
 			if ((res = Post(Buffer)) != SUCCESS)
 				return (res);
 			if ((res = Ack()) != SUCCESS)
@@ -341,14 +343,15 @@
 	struct tm *tm = localtime(&tNow);
 	int zoneh = abs(_timezone);
 	int zonem, res;
+	int i;
+
 	char *p;
 
-	p = Buffer;
 	zoneh /= (60 * 60);
 	zonem = (abs(_timezone) / 60) - (zoneh * 60);
 
 	if(!xheaders || !strstr(xheaders, "Date:")){
-		p += sprintf(p, "Date: %s, %02d %s %04d %02d:%02d:%02d %s%02d%02d\r\n",
+		snprintf(Buffer, MAIL_BUFFER_SIZE, "Date: %s, %02d %s %04d %02d:%02d:%02d %s%02d%02d\r\n",
 					 days[tm->tm_wday],
 					 tm->tm_mday,
 					 months[tm->tm_mon],
@@ -359,21 +362,56 @@
 					 (_timezone > 0) ? "+" : (_timezone < 0) ? "-" : "",
 					 zoneh,
 					 zonem);
+
+					if ((res = Post(Buffer)) != SUCCESS)
+					return (res);
 	}
 
 	if(!xheaders || !strstr(xheaders, "From:")){
-		p += sprintf(p, "From: %s\r\n", RPath);
+		snprintf(Buffer, MAIL_BUFFER_SIZE, "From: %s\r\n", RPath);
+		if ((res = Post(Buffer)) != SUCCESS)
+		return (res);
 	}
-	p += sprintf(p, "Subject: %s\r\n", Subject);
+
+	snprintf(Buffer, MAIL_BUFFER_SIZE, "Subject: %s\r\n", Subject);
+	if ((res = Post(Buffer)) != SUCCESS)
+	return (res);
+
 	if(!xheaders || !strstr(xheaders, "To:")){
-		p += sprintf(p, "To: %s\r\n", mailTo);
+		snprintf(Buffer, MAIL_BUFFER_SIZE, "To: %s\r\n", mailTo);
+		if ((res = Post(Buffer)) != SUCCESS)
+		return (res);
 	}
+
 	if(xheaders){
-		p += sprintf(p, "%s\r\n", xheaders);
-	}
 
-	if ((res = Post(Buffer)) != SUCCESS)
-		return (res);
+				/* send extre headers in 1024 chunks */
+				if (strlen(xheaders) <= 1024) {
+						if ((res = Post(xheaders)) != SUCCESS)
+						return (res);
+				} else {
+						p = xheaders;
+		
+						while (1) {
+						if (*p == '\0')
+							break;
+						if (strlen(p) >= 1024)
+						i = 1024;
+							else
+						i = strlen(p);
+
+						/* put next chunk in buffer */
+						strncpy(Buffer, p, i);
+						Buffer[i] = '\0';
+						p += i;
+
+						/* send chunk */
+						if ((res = Post(Buffer)) != SUCCESS)
+						return (res);
+						}
+				}
+				}
+
 
 	if ((res = Post("\r\n")) != SUCCESS)
 		return (res);
 [2002-02-02 06:35 UTC] sander@php.net
No feedback was provided for this bug, so it is being suspended.
If you are able to provide the information that was requested,
please do so and change the status of the bug back to "Open".
 [2002-02-02 06:37 UTC] sander@php.net
Sorry, reopening.
 [2002-02-03 14:41 UTC] enricod at videotron dot ca
what feedback is needed ?
 [2002-02-03 15:06 UTC] sander@php.net
There was no feedback needed; somehow the status was still set to feedback, therefore I closed the bug by mistake.
Sorry for the confusion :)
 [2002-05-14 10:00 UTC] mfischer@php.net
Hi enricod@videotron.ca, it took a while :) but can you send me a diff against latest CVS HEAD ? I promise it won't linger this time.
 [2002-06-15 01:00 UTC] php-bugs at lists dot php dot net
No feedback was provided for this bug for over a month, so it is
being suspended automatically. If you are able to provide the
information that was originally requested, please do so and change
the status of the bug back to "Open".
 [2002-06-15 03:56 UTC] mfischer@php.net
This bug has been fixed in CVS. You can grab a snapshot of the
CVS version at http://snaps.php.net/. In case this was a documentation 
problem, the fix will show up soon at http://www.php.net/manual/.
In case this was a PHP.net website problem, the change will show
up on the PHP.net site and on the mirror sites.
Thank you for the report, and for helping us make PHP better.


 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Wed Sep 18 22:01:26 2024 UTC