xref: /PHP-8.2/win32/sendmail.c (revision 81d1a1b4)
1 /*
2  *    PHP Sendmail for Windows.
3  *
4  *  This file is rewritten specifically for PHPFI.  Some functionality
5  *  has been removed (MIME and file attachments).  This code was
6  *  modified from code based on code written by Jarle Aase.
7  *
8  *  This class is based on the original code by Jarle Aase, see below:
9  *  wSendmail.cpp  It has been striped of some functionality to match
10  *  the requirements of phpfi.
11  *
12  *  Very simple SMTP Send-mail program for sending command-line level
13  *  emails and CGI-BIN form response for the Windows platform.
14  *
15  *  The complete wSendmail package with source code can be located
16  *  from http://www.jgaa.com
17  *
18  */
19 
20 #include "php.h"				/*php specific */
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <winsock2.h>
24 #include "time.h"
25 # include <Ws2tcpip.h>
26 #include <string.h>
27 #include <math.h>
28 #include <malloc.h>
29 #include <winbase.h>
30 #include "sendmail.h"
31 #include "php_ini.h"
32 #include "inet.h"
33 
34 #include "php_win32_globals.h"
35 
36 #include "ext/pcre/php_pcre.h"
37 #include "ext/standard/php_string.h"
38 #include "ext/date/php_date.h"
39 
40 #define SENDMAIL_DEBUG 0
41 
42 /*enum
43    {
44    DO_CONNECT = WM_USER +1
45    };
46  */
47 
48 /* '*error_message' has to be passed around from php_mail() */
49 #define SMTP_ERROR_RESPONSE_SPEC	"SMTP server response: %s"
50 /* Convenient way to handle error messages from the SMTP server.
51    response is ecalloc()d in Ack() itself and efree()d here
52    because the content is in *error_message now */
53 #define SMTP_ERROR_RESPONSE(response)	{ \
54 											if (response && error_message) { \
55 												*error_message = ecalloc(1, sizeof(SMTP_ERROR_RESPONSE_SPEC) + strlen(response)); \
56 												snprintf(*error_message, sizeof(SMTP_ERROR_RESPONSE_SPEC) + strlen(response), SMTP_ERROR_RESPONSE_SPEC, response); \
57 												efree(response); \
58 											} \
59 										}
60 #define SMTP_SKIP_SPACE(str)	{ while (isspace(*str)) { str++; } }
61 
62 
63 char seps[] = " ,\t\n";
64 char *php_mailer = "PHP 7 WIN32";
65 
66 /* Error messages */
67 static char *ErrorMessages[] =
68 {
69 	{"Success"}, /* 0 */
70 	{"Bad arguments from form"}, /* 1 */
71 	{"Unable to open temporary mailfile for read"},
72 	{"Failed to Start Sockets"},
73 	{"Failed to Resolve Host"},
74 	{"Failed to obtain socket handle"}, /* 5 */
75 	{"Failed to connect to mailserver, verify your \"SMTP\" setting in php.ini"},
76 	{"Failed to Send"},
77 	{"Failed to Receive"},
78 	{"Server Error"},
79 	{"Failed to resolve the host IP name"}, /* 10 */
80 	{"Out of memory"},
81 	{"Unknown error"},
82 	{"Bad Message Contents"},
83 	{"Bad Message Subject"},
84 	{"Bad Message destination"}, /* 15 */
85 	{"Bad Message Return Path"},
86 	{"Bad Mail Host"},
87 	{"Bad Message File"},
88 	{"\"sendmail_from\" not set in php.ini or custom \"From:\" header missing"},
89 	{"Mailserver rejected our \"sendmail_from\" setting"}, /* 20 */
90 	{"Error while trimming mail header with PCRE, please file a bug report at https://github.com/php/php-src/issues"} /* 21 */
91 };
92 
93 /* This pattern converts all single occurrences of \n (Unix)
94  * without a leading \r to \r\n and all occurrences of \r (Mac)
95  * without a trailing \n to \r\n
96  * Thx to Nibbler from ircnet/#linuxger
97  */
98 #define PHP_WIN32_MAIL_UNIFY_PATTERN	"/(\r\n?)|\n/"
99 #define PHP_WIN32_MAIL_UNIFY_REPLACE	"\r\n"
100 
101 /* This pattern removes \r\n from the start of the string,
102  * \r\n from the end of the string and also makes sure every line
103  * is only wrapped with a single \r\n (thus reduces multiple
104  * occurrences of \r\n between lines to a single \r\n) */
105 #define PHP_WIN32_MAIL_RMVDBL_PATTERN	"/^\r\n|(\r\n)+$/m"
106 #define PHP_WIN32_MAIL_RMVDBL_REPLACE	""
107 
108 /* This pattern escapes \n. inside the message body. It prevents
109  * premature end of message if \n.\n or \r\n.\r\n is encountered
110  * and ensures that \n. sequences are properly displayed in the
111  * message body. */
112 #define PHP_WIN32_MAIL_DOT_PATTERN	"\n."
113 #define PHP_WIN32_MAIL_DOT_REPLACE	"\n.."
114 
115 /* This function is meant to unify the headers passed to to mail()
116  * This means, use PCRE to transform single occurrences of \n or \r in \r\n
117  * As a second step we also eliminate all \r\n occurrences which are:
118  * 1) At the start of the header
119  * 2) At the end of the header
120  * 3) Two or more occurrences in the header are removed so only one is left
121  *
122  * Returns NULL on error, or the new char* buffer on success.
123  * You have to take care and efree() the buffer on your own.
124  */
php_win32_mail_trim_header(const char * header)125 static zend_string *php_win32_mail_trim_header(const char *header)
126 {
127 	zend_string *result, *result2;
128 	zend_string *replace;
129 	zend_string *regex;
130 
131 	if (!header) {
132 		return NULL;
133 	}
134 
135 	replace = zend_string_init(PHP_WIN32_MAIL_UNIFY_REPLACE, strlen(PHP_WIN32_MAIL_UNIFY_REPLACE), 0);
136 	regex = zend_string_init(PHP_WIN32_MAIL_UNIFY_PATTERN, sizeof(PHP_WIN32_MAIL_UNIFY_PATTERN)-1, 0);
137 
138 	result = php_pcre_replace(regex,
139 				  NULL, header, strlen(header),
140 				  replace,
141 				  -1,
142 				  NULL);
143 
144 	zend_string_release_ex(replace, 0);
145 	zend_string_release_ex(regex, 0);
146 
147 	if (NULL == result) {
148 		return NULL;
149 	}
150 
151 	replace = zend_string_init(PHP_WIN32_MAIL_RMVDBL_PATTERN, strlen(PHP_WIN32_MAIL_RMVDBL_PATTERN), 0);
152 	regex = zend_string_init(PHP_WIN32_MAIL_RMVDBL_PATTERN, sizeof(PHP_WIN32_MAIL_RMVDBL_PATTERN)-1, 0);
153 
154 	result2 = php_pcre_replace(regex,
155 				   result, ZSTR_VAL(result), ZSTR_LEN(result),
156 				   replace,
157 				  -1,
158 				  NULL);
159 	zend_string_release_ex(replace, 0);
160 	zend_string_release_ex(regex, 0);
161 	zend_string_release_ex(result, 0);
162 
163 	return result2;
164 }
165 
166 /*********************************************************************
167 // Name:  TSendMail
168 // Input:   1) host:    Name of the mail host where the SMTP server resides
169 //                      max accepted length of name = 256
170 //          2) appname: Name of the application to use in the X-mailer
171 //                      field of the message. if NULL is given the application
172 //                      name is used as given by the GetCommandLine() function
173 //                      max accepted length of name = 100
174 // Output:  1) error:   Returns the error code if something went wrong or
175 //                      SUCCESS otherwise.
176 //
177 //  See SendText() for additional args!
178 //********************************************************************/
TSendMail(const char * host,int * error,char ** error_message,const char * headers,const char * Subject,const char * mailTo,const char * data,char * mailCc,char * mailBcc,char * mailRPath)179 PHPAPI int TSendMail(const char *host, int *error, char **error_message,
180 			  const char *headers, const char *Subject, const char *mailTo, const char *data,
181 			  char *mailCc, char *mailBcc, char *mailRPath)
182 {
183 	int ret;
184 	char *RPath = NULL;
185 	zend_string *headers_lc = NULL, *headers_trim = NULL; /* headers_lc is only created if we've a header at all */
186 	const char *pos1 = NULL, *pos2 = NULL;
187 
188 	if (host == NULL) {
189 		*error = BAD_MAIL_HOST;
190 		return FAILURE;
191 	} else if (strlen(host) >= HOST_NAME_LEN) {
192 		*error = BAD_MAIL_HOST;
193 		return FAILURE;
194 	} else {
195 		strcpy(PW32G(mail_host), host);
196 	}
197 
198 	if (headers) {
199 		char *pos = NULL;
200 
201 		/* Use PCRE to trim the header into the right format */
202 		if (NULL == (headers_trim = php_win32_mail_trim_header(headers))) {
203 			*error = W32_SM_PCRE_ERROR;
204 			return FAILURE;
205 		}
206 
207 		/* Create a lowercased header for all the searches so we're finally case
208 		 * insensitive when searching for a pattern. */
209 		headers_lc = zend_string_tolower(headers_trim);
210 	}
211 
212 	/* Fall back to sendmail_from php.ini setting */
213 	if (mailRPath && *mailRPath) {
214 		RPath = estrdup(mailRPath);
215 	} else if (INI_STR("sendmail_from")) {
216 		RPath = estrdup(INI_STR("sendmail_from"));
217 	} else if (headers_lc) {
218 		int found = 0;
219 		const char *lookup = ZSTR_VAL(headers_lc);
220 
221 		while (lookup) {
222 			pos1 = strstr(lookup, "from:");
223 
224 			if (!pos1) {
225 				break;
226 			} else if (pos1 != ZSTR_VAL(headers_lc) && *(pos1-1) != '\n') {
227 				if (strlen(pos1) >= sizeof("from:")) {
228 					lookup = pos1 + sizeof("from:");
229 					continue;
230 				} else {
231 					break;
232 				}
233 			}
234 
235 			found = 1;
236 
237 			/* Real offset is memaddress from the original headers + difference of
238 			 * string found in the lowercase headers + 5 characters to jump over
239 			 * the from: */
240 			pos1 = headers + (pos1 - lookup) + 5;
241 			if (NULL == (pos2 = strstr(pos1, "\r\n"))) {
242 				RPath = estrndup(pos1, strlen(pos1));
243 			} else {
244 				RPath = estrndup(pos1, pos2 - pos1);
245 			}
246 
247 			break;
248 		}
249 
250 		if (!found) {
251 			if (headers) {
252 				zend_string_release(headers_trim);
253 				zend_string_release(headers_lc);
254 			}
255 			*error = W32_SM_SENDMAIL_FROM_NOT_SET;
256 			return FAILURE;
257 		}
258 	}
259 
260 	/* attempt to connect with mail host */
261 	*error = MailConnect();
262 	if (*error != 0) {
263 		if (RPath) {
264 			efree(RPath);
265 		}
266 		if (headers) {
267 			zend_string_release(headers_trim);
268 			zend_string_release(headers_lc);
269 		}
270 		/* 128 is safe here, the specifier in snprintf isn't longer than that */
271 		*error_message = ecalloc(1, HOST_NAME_LEN + 128);
272 		snprintf(*error_message, HOST_NAME_LEN + 128,
273 			"Failed to connect to mailserver at \"%s\" port " ZEND_ULONG_FMT ", verify your \"SMTP\" "
274 			"and \"smtp_port\" setting in php.ini or use ini_set()",
275 			PW32G(mail_host), !INI_INT("smtp_port") ? 25 : INI_INT("smtp_port"));
276 		return FAILURE;
277 	} else {
278 		ret = SendText(RPath, Subject, mailTo, mailCc, mailBcc, data, headers ? ZSTR_VAL(headers_trim) : NULL, headers ? ZSTR_VAL(headers_lc) : NULL, error_message);
279 		TSMClose();
280 		if (RPath) {
281 			efree(RPath);
282 		}
283 		if (headers) {
284 			zend_string_release(headers_trim);
285 			zend_string_release(headers_lc);
286 		}
287 		if (ret != SUCCESS) {
288 			*error = ret;
289 			return FAILURE;
290 		}
291 		return SUCCESS;
292 	}
293 }
294 
295 //********************************************************************
296 // Name:  TSendMail::~TSendMail
297 // Input:
298 // Output:
299 // Description: DESTRUCTOR
300 // Author/Date:  jcar 20/9/96
301 // History:
302 //********************************************************************/
TSMClose(void)303 PHPAPI void TSMClose(void)
304 {
305 	Post("QUIT\r\n");
306 	Ack(NULL);
307 	/* to guarantee that the cleanup is not made twice and
308 	   compromise the rest of the application if sockets are used
309 	   elsewhere
310 	*/
311 
312 	shutdown(PW32G(mail_socket), 0);
313 	closesocket(PW32G(mail_socket));
314 }
315 
316 
317 /*********************************************************************
318 // Name:  char *GetSMErrorText
319 // Input:   Error index returned by the member functions
320 // Output:  pointer to a string containing the error description
321 // Description:
322 // Author/Date:  jcar 20/9/96
323 // History:
324 //*******************************************************************/
GetSMErrorText(int index)325 PHPAPI char *GetSMErrorText(int index)
326 {
327 	if (MIN_ERROR_INDEX <= index && index < MAX_ERROR_INDEX) {
328 		return (ErrorMessages[index]);
329 
330 	} else {
331 		return (ErrorMessages[UNKNOWN_ERROR]);
332 
333 	}
334 }
335 
336 /* strtok_r like, but recognizes quoted-strings */
find_address(char * list,char ** state)337 static char *find_address(char *list, char **state)
338 {
339 	bool in_quotes = 0;
340 	char *p = list;
341 
342 	if (list == NULL) {
343 		if (*state == NULL) {
344 			return NULL;
345 		}
346 		p = list = *state;
347 	}
348 	*state = NULL;
349 	while ((p = strpbrk(p, ",\"\\")) != NULL) {
350 		if (*p == '\\' && in_quotes) {
351 			if (p[1] == '\0') {
352 				/* invalid address; let SMTP server deal with it */
353 				break;
354 			}
355 			p++;
356 		} else if (*p == '"') {
357 			in_quotes = !in_quotes;
358 		} else if (*p == ',' && !in_quotes) {
359 			*p = '\0';
360 			*state = p + 1;
361 			break;
362 		}
363 		p++;
364 	}
365 	return list;
366 }
367 
368 /*********************************************************************
369 // Name:  SendText
370 // Input:       1) RPath:   return path of the message
371 //                                  Is used to fill the "Return-Path" and the
372 //                                  "X-Sender" fields of the message.
373 //                  2) Subject: Subject field of the message. If NULL is given
374 //                                  the subject is set to "No Subject"
375 //                  3) mailTo:  Destination address
376 //                  4) data:        Null terminated string containing the data to be send.
377 //                  5,6) headers of the message. Note that the second
378 //                  parameter, headers_lc, is actually a lowercased version of
379 //                  headers. The should match exactly (in terms of length),
380 //                  only differ in case
381 // Output:      Error code or SUCCESS
382 // Description:
383 // Author/Date:  jcar 20/9/96
384 // History:
385 //*******************************************************************/
SendText(char * RPath,const char * Subject,const char * mailTo,char * mailCc,char * mailBcc,const char * data,const char * headers,char * headers_lc,char ** error_message)386 static int SendText(char *RPath, const char *Subject, const char *mailTo, char *mailCc, char *mailBcc, const char *data,
387 			 const char *headers, char *headers_lc, char **error_message)
388 {
389 	int res;
390 	char *p;
391 	char *tempMailTo, *token, *token_state;
392 	const char *pos1, *pos2;
393 	char *server_response = NULL;
394 	char *stripped_header  = NULL;
395 	zend_string *data_cln;
396 
397 	/* check for NULL parameters */
398 	if (data == NULL)
399 		return (BAD_MSG_CONTENTS);
400 	if (mailTo == NULL)
401 		return (BAD_MSG_DESTINATION);
402 	if (RPath == NULL)
403 		return (BAD_MSG_RPATH);
404 
405 	/* simple checks for the mailto address */
406 	/* have ampersand ? */
407 	/* mfischer, 20020514: I commented this out because it really
408 	   seems bogus. Only a username for example may still be a
409 	   valid address at the destination system.
410 	if (strchr(mailTo, '@') == NULL)
411 		return (BAD_MSG_DESTINATION);
412 	*/
413 
414 	snprintf(PW32G(mail_buffer), sizeof(PW32G(mail_buffer)), "HELO %s\r\n", PW32G(mail_local_host));
415 
416 	/* in the beginning of the dialog */
417 	/* attempt reconnect if the first Post fail */
418 	if ((res = Post(PW32G(mail_buffer))) != SUCCESS) {
419 		int err = MailConnect();
420 		if (0 != err) {
421 			return (FAILED_TO_SEND);
422 		}
423 
424 		if ((res = Post(PW32G(mail_buffer))) != SUCCESS) {
425 			return (res);
426 		}
427 	}
428 	if ((res = Ack(&server_response)) != SUCCESS) {
429 		SMTP_ERROR_RESPONSE(server_response);
430 		return (res);
431 	}
432 
433 	SMTP_SKIP_SPACE(RPath);
434 	FormatEmailAddress(PW32G(mail_buffer), RPath, "MAIL FROM:<%s>\r\n");
435 	if ((res = Post(PW32G(mail_buffer))) != SUCCESS) {
436 		return (res);
437 	}
438 	if ((res = Ack(&server_response)) != SUCCESS) {
439 		SMTP_ERROR_RESPONSE(server_response);
440 		return W32_SM_SENDMAIL_FROM_MALFORMED;
441 	}
442 
443 	tempMailTo = estrdup(mailTo);
444 	/* Send mail to all rcpt's */
445 	token = find_address(tempMailTo, &token_state);
446 	while (token != NULL)
447 	{
448 		SMTP_SKIP_SPACE(token);
449 		FormatEmailAddress(PW32G(mail_buffer), token, "RCPT TO:<%s>\r\n");
450 		if ((res = Post(PW32G(mail_buffer))) != SUCCESS) {
451 			efree(tempMailTo);
452 			return (res);
453 		}
454 		if ((res = Ack(&server_response)) != SUCCESS) {
455 			SMTP_ERROR_RESPONSE(server_response);
456 			efree(tempMailTo);
457 			return (res);
458 		}
459 		token = find_address(NULL, &token_state);
460 	}
461 	efree(tempMailTo);
462 
463 	if (mailCc && *mailCc) {
464 		tempMailTo = estrdup(mailCc);
465 		/* Send mail to all rcpt's */
466 		token = find_address(tempMailTo, &token_state);
467 		while (token != NULL)
468 		{
469 			SMTP_SKIP_SPACE(token);
470 			FormatEmailAddress(PW32G(mail_buffer), token, "RCPT TO:<%s>\r\n");
471 			if ((res = Post(PW32G(mail_buffer))) != SUCCESS) {
472 				efree(tempMailTo);
473 				return (res);
474 			}
475 			if ((res = Ack(&server_response)) != SUCCESS) {
476 				SMTP_ERROR_RESPONSE(server_response);
477 				efree(tempMailTo);
478 				return (res);
479 			}
480 			token = find_address(NULL, &token_state);
481 		}
482 		efree(tempMailTo);
483 	}
484 	/* Send mail to all Cc rcpt's */
485 	else if (headers && (pos1 = strstr(headers_lc, "cc:")) && ((pos1 == headers_lc) || (*(pos1-1) == '\n'))) {
486 		/* Real offset is memaddress from the original headers + difference of
487 		 * string found in the lowercase headers + 3 characters to jump over
488 		 * the cc: */
489 		pos1 = headers + (pos1 - headers_lc) + 3;
490 		if (NULL == (pos2 = strstr(pos1, "\r\n"))) {
491 			tempMailTo = estrndup(pos1, strlen(pos1));
492 		} else {
493 			char *pos3;
494 			while (pos2[2] == ' ' || pos2[2] == '\t') {
495 				pos3 = strstr(pos2 + 2, "\r\n");
496 				if (pos3 != NULL) {
497 					pos2 = pos3;
498 				} else {
499 					pos2 += strlen(pos2);
500 					break;
501 				}
502 			}
503 			tempMailTo = estrndup(pos1, pos2 - pos1);
504 		}
505 
506 		token = find_address(tempMailTo, &token_state);
507 		while (token != NULL)
508 		{
509 			SMTP_SKIP_SPACE(token);
510 			FormatEmailAddress(PW32G(mail_buffer), token, "RCPT TO:<%s>\r\n");
511 			if ((res = Post(PW32G(mail_buffer))) != SUCCESS) {
512 				efree(tempMailTo);
513 				return (res);
514 			}
515 			if ((res = Ack(&server_response)) != SUCCESS) {
516 				SMTP_ERROR_RESPONSE(server_response);
517 				efree(tempMailTo);
518 				return (res);
519 			}
520 			token = find_address(NULL,&token_state);
521 		}
522 		efree(tempMailTo);
523 	}
524 
525 	/* Send mail to all Bcc rcpt's
526 	   This is basically a rip of the Cc code above.
527 	   Just don't forget to remove the Bcc: from the header afterwards. */
528 	if (mailBcc && *mailBcc) {
529 		tempMailTo = estrdup(mailBcc);
530 		/* Send mail to all rcpt's */
531 		token = find_address(tempMailTo, &token_state);
532 		while (token != NULL)
533 		{
534 			SMTP_SKIP_SPACE(token);
535 			FormatEmailAddress(PW32G(mail_buffer), token, "RCPT TO:<%s>\r\n");
536 			if ((res = Post(PW32G(mail_buffer))) != SUCCESS) {
537 				efree(tempMailTo);
538 				return (res);
539 			}
540 			if ((res = Ack(&server_response)) != SUCCESS) {
541 				SMTP_ERROR_RESPONSE(server_response);
542 				efree(tempMailTo);
543 				return (res);
544 			}
545 			token = find_address(NULL, &token_state);
546 		}
547 		efree(tempMailTo);
548 	}
549 	else if (headers) {
550 		if ((pos1 = strstr(headers_lc, "bcc:")) && (pos1 == headers_lc || *(pos1-1) == '\n')) {
551 			/* Real offset is memaddress from the original headers + difference of
552 			 * string found in the lowercase headers + 4 characters to jump over
553 			 * the bcc: */
554 			pos1 = headers + (pos1 - headers_lc) + 4;
555 			if (NULL == (pos2 = strstr(pos1, "\r\n"))) {
556 				tempMailTo = estrndup(pos1, strlen(pos1));
557 				/* Later, when we remove the Bcc: out of the
558 				   header we know it was the last thing. */
559 				pos2 = pos1;
560 			} else {
561 				const char *pos3 = pos2;
562 				while (pos2[2] == ' ' || pos2[2] == '\t') {
563 					pos3 = strstr(pos2 + 2, "\r\n");
564 					if (pos3 != NULL) {
565 						pos2 = pos3;
566 					} else {
567 						pos2 += strlen(pos2);
568 						break;
569 					}
570 				}
571 				tempMailTo = estrndup(pos1, pos2 - pos1);
572 				if (pos3 == NULL) {
573 					/* Later, when we remove the Bcc: out of the
574 					   header we know it was the last thing. */
575 					pos2 = pos1;
576 				}
577 			}
578 
579 			token = find_address(tempMailTo, &token_state);
580 			while (token != NULL)
581 			{
582 				SMTP_SKIP_SPACE(token);
583 				FormatEmailAddress(PW32G(mail_buffer), token, "RCPT TO:<%s>\r\n");
584 				if ((res = Post(PW32G(mail_buffer))) != SUCCESS) {
585 					efree(tempMailTo);
586 					return (res);
587 				}
588 				if ((res = Ack(&server_response)) != SUCCESS) {
589 					SMTP_ERROR_RESPONSE(server_response);
590 					efree(tempMailTo);
591 					return (res);
592 				}
593 				token = find_address(NULL, &token_state);
594 			}
595 			efree(tempMailTo);
596 
597 			/* Now that we've identified that we've a Bcc list,
598 			   remove it from the current header. */
599 			stripped_header = ecalloc(1, strlen(headers));
600 			/* headers = point to string start of header
601 			   pos1    = pointer IN headers where the Bcc starts
602 			   '4'     = Length of the characters 'bcc:'
603 			   Because we've added +4 above for parsing the Emails
604 			   we've to subtract them here. */
605 			memcpy(stripped_header, headers, pos1 - headers - 4);
606 			if (pos1 != pos2) {
607 				/* if pos1 != pos2 , pos2 points to the rest of the headers.
608 				   Since pos1 != pos2 if "\r\n" was found, we know those characters
609 				   are there and so we jump over them (else we would generate a new header
610 				   which would look like "\r\n\r\n". */
611 				memcpy(stripped_header + (pos1 - headers - 4), pos2 + 2, strlen(pos2) - 2);
612 			}
613 		}
614 	}
615 
616 	/* Simplify the code that we create a copy of stripped_header no matter if
617 	   we actually strip something or not. So we've a single efree() later. */
618 	if (headers && !stripped_header) {
619 		stripped_header = estrndup(headers, strlen(headers));
620 	}
621 
622 	if ((res = Post("DATA\r\n")) != SUCCESS) {
623 		if (stripped_header) {
624 			efree(stripped_header);
625 		}
626 		return (res);
627 	}
628 	if ((res = Ack(&server_response)) != SUCCESS) {
629 		SMTP_ERROR_RESPONSE(server_response);
630 		if (stripped_header) {
631 			efree(stripped_header);
632 		}
633 		return (res);
634 	}
635 
636 	/* send message header */
637 	if (Subject == NULL) {
638 		res = PostHeader(RPath, "No Subject", mailTo, stripped_header);
639 	} else {
640 		res = PostHeader(RPath, Subject, mailTo, stripped_header);
641 	}
642 	if (stripped_header) {
643 		efree(stripped_header);
644 	}
645 	if (res != SUCCESS) {
646 		return (res);
647 	}
648 
649 	/* Escape \n. sequences
650 	 * We use php_str_to_str() and not php_str_replace_in_subject(), since the latter
651 	 * uses ZVAL as it's parameters */
652 	data_cln = php_str_to_str(data, strlen(data), PHP_WIN32_MAIL_DOT_PATTERN, sizeof(PHP_WIN32_MAIL_DOT_PATTERN) - 1,
653 					PHP_WIN32_MAIL_DOT_REPLACE, sizeof(PHP_WIN32_MAIL_DOT_REPLACE) - 1);
654 	if (!data_cln) {
655 		data_cln = ZSTR_EMPTY_ALLOC();
656 	}
657 
658 	/* send message contents in 1024 chunks */
659 	{
660 		char c, *e2, *e = ZSTR_VAL(data_cln) + ZSTR_LEN(data_cln);
661 		p = ZSTR_VAL(data_cln);
662 
663 		while (e - p > 1024) {
664 			e2 = p + 1024;
665 			c = *e2;
666 			*e2 = '\0';
667 			if ((res = Post(p)) != SUCCESS) {
668 				zend_string_free(data_cln);
669 				return(res);
670 			}
671 			*e2 = c;
672 			p = e2;
673 		}
674 		if ((res = Post(p)) != SUCCESS) {
675 			zend_string_free(data_cln);
676 			return(res);
677 		}
678 	}
679 
680 	zend_string_free(data_cln);
681 
682 	/*send termination dot */
683 	if ((res = Post("\r\n.\r\n")) != SUCCESS)
684 		return (res);
685 	if ((res = Ack(&server_response)) != SUCCESS) {
686 		SMTP_ERROR_RESPONSE(server_response);
687 		return (res);
688 	}
689 
690 	return (SUCCESS);
691 }
692 
addToHeader(char ** header_buffer,const char * specifier,const char * string)693 static int addToHeader(char **header_buffer, const char *specifier, const char *string)
694 {
695 	*header_buffer = erealloc(*header_buffer, strlen(*header_buffer) + strlen(specifier) + strlen(string) + 1);
696 	sprintf(*header_buffer + strlen(*header_buffer), specifier, string);
697 	return 1;
698 }
699 
700 /*********************************************************************
701 // Name:  PostHeader
702 // Input:       1) return path
703 //              2) Subject
704 //              3) destination address
705 //              4) headers
706 // Output:      Error code or Success
707 // Description:
708 // Author/Date:  jcar 20/9/96
709 // History:
710 //********************************************************************/
PostHeader(char * RPath,const char * Subject,const char * mailTo,char * xheaders)711 static int PostHeader(char *RPath, const char *Subject, const char *mailTo, char *xheaders)
712 {
713 	/* Print message header according to RFC 822 */
714 	/* Return-path, Received, Date, From, Subject, Sender, To, cc */
715 
716 	int res;
717 	char *header_buffer;
718 	char *headers_lc = NULL;
719 	size_t i;
720 
721 	if (xheaders) {
722 		size_t headers_lc_len;
723 
724 		headers_lc = estrdup(xheaders);
725 		headers_lc_len = strlen(headers_lc);
726 
727 		for (i = 0; i < headers_lc_len; i++) {
728 			headers_lc[i] = tolower(headers_lc[i]);
729 		}
730 	}
731 
732 	header_buffer = ecalloc(1, MAIL_BUFFER_SIZE);
733 
734 	if (!xheaders || !strstr(headers_lc, "date:")) {
735 		time_t tNow = time(NULL);
736 		zend_string *dt = php_format_date("r", 1, tNow, 1);
737 
738 		snprintf(header_buffer, MAIL_BUFFER_SIZE, "Date: %s\r\n", ZSTR_VAL(dt));
739 		zend_string_free(dt);
740 	}
741 
742 	if (!headers_lc || !strstr(headers_lc, "from:")) {
743 		if (!addToHeader(&header_buffer, "From: %s\r\n", RPath)) {
744 			goto PostHeader_outofmem;
745 		}
746 	}
747 	if (!addToHeader(&header_buffer, "Subject: %s\r\n", Subject)) {
748 		goto PostHeader_outofmem;
749 	}
750 
751 	/* Only add the To: field from the $to parameter if isn't in the custom headers */
752 	if ((headers_lc && (!strstr(headers_lc, "\r\nto:") && (strncmp(headers_lc, "to:", 3) != 0))) || !headers_lc) {
753 		if (!addToHeader(&header_buffer, "To: %s\r\n", mailTo)) {
754 			goto PostHeader_outofmem;
755 		}
756 	}
757 	if (xheaders) {
758 		if (!addToHeader(&header_buffer, "%s\r\n", xheaders)) {
759 			goto PostHeader_outofmem;
760 		}
761 	}
762 
763 	if (headers_lc) {
764 		efree(headers_lc);
765 	}
766 	if ((res = Post(header_buffer)) != SUCCESS) {
767 		efree(header_buffer);
768 		return (res);
769 	}
770 	efree(header_buffer);
771 
772 	if ((res = Post("\r\n")) != SUCCESS) {
773 		return (res);
774 	}
775 
776 	return (SUCCESS);
777 
778 PostHeader_outofmem:
779 	if (headers_lc) {
780 		efree(headers_lc);
781 	}
782 	return OUT_OF_MEMORY;
783 }
784 
785 
786 
787 /*********************************************************************
788 // Name:  MailConnect
789 // Input:   None
790 // Output:  None
791 // Description: Connect to the mail host and receive the welcome message.
792 // Author/Date:  jcar 20/9/96
793 // History:
794 //********************************************************************/
MailConnect()795 static int MailConnect()
796 {
797 
798 	int res, namelen;
799 	short portnum;
800 	struct hostent *ent;
801 	IN_ADDR addr;
802 #ifdef HAVE_IPV6
803 	IN6_ADDR addr6;
804 #endif
805 	SOCKADDR_IN sock_in;
806 
807 #if SENDMAIL_DEBUG
808 return 0;
809 #endif
810 
811 	/* Create Socket */
812 	if ((PW32G(mail_socket) = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
813 		return (FAILED_TO_OBTAIN_SOCKET_HANDLE);
814 	}
815 
816 	/* Get our own host name */
817 	if (gethostname(PW32G(mail_local_host), HOST_NAME_LEN)) {
818 		closesocket(PW32G(mail_socket));
819 		return (FAILED_TO_GET_HOSTNAME);
820 	}
821 
822 	ent = gethostbyname(PW32G(mail_local_host));
823 
824 	if (!ent) {
825 		closesocket(PW32G(mail_socket));
826 		return (FAILED_TO_GET_HOSTNAME);
827 	}
828 
829 	namelen = (int)strlen(ent->h_name);
830 
831 #ifdef HAVE_IPV6
832 	if (inet_pton(AF_INET, ent->h_name, &addr) == 1 || inet_pton(AF_INET6, ent->h_name, &addr6) == 1)
833 #else
834 	if (inet_pton(AF_INET, ent->h_name, &addr) == 1)
835 #endif
836 	{
837 		if (namelen + 2 >= HOST_NAME_LEN) {
838 			closesocket(PW32G(mail_socket));
839 			return (FAILED_TO_GET_HOSTNAME);
840 		}
841 
842 		strcpy(PW32G(mail_local_host), "[");
843 		strcpy(PW32G(mail_local_host) + 1, ent->h_name);
844 		strcpy(PW32G(mail_local_host) + namelen + 1, "]");
845 	} else {
846 		if (namelen >= HOST_NAME_LEN) {
847 			closesocket(PW32G(mail_socket));
848 			return (FAILED_TO_GET_HOSTNAME);
849 		}
850 
851 		strcpy(PW32G(mail_local_host), ent->h_name);
852 	}
853 
854 	/* Resolve the servers IP */
855 	/*
856 	if (!isdigit(PW32G(mail_host)[0])||!gethostbyname(PW32G(mail_host)))
857 	{
858 		return (FAILED_TO_RESOLVE_HOST);
859 	}
860 	*/
861 
862 	portnum = (short) INI_INT("smtp_port");
863 	if (!portnum) {
864 		portnum = 25;
865 	}
866 
867 	/* Connect to server */
868 	sock_in.sin_family = AF_INET;
869 	sock_in.sin_port = htons(portnum);
870 	sock_in.sin_addr.S_un.S_addr = GetAddr(PW32G(mail_host));
871 
872 	if (connect(PW32G(mail_socket), (LPSOCKADDR) & sock_in, sizeof(sock_in))) {
873 		closesocket(PW32G(mail_socket));
874 		return (FAILED_TO_CONNECT);
875 	}
876 
877 	/* receive Server welcome message */
878 	res = Ack(NULL);
879 	return (res);
880 }
881 
882 
883 /*********************************************************************
884 // Name:  Post
885 // Input:
886 // Output:
887 // Description:
888 // Author/Date:  jcar 20/9/96
889 // History:
890 //********************************************************************/
Post(LPCSTR msg)891 static int Post(LPCSTR msg)
892 {
893 	int len = (int)strlen(msg);
894 	int slen;
895 	int index = 0;
896 
897 #if SENDMAIL_DEBUG
898 	if (msg)
899 		printf("POST: '%s'\n", msg);
900 	return (SUCCESS);
901 #endif
902 
903 	while (len > 0) {
904 		if ((slen = send(PW32G(mail_socket), msg + index, len, 0)) < 1)
905 			return (FAILED_TO_SEND);
906 		len -= slen;
907 		index += slen;
908 	}
909 	return (SUCCESS);
910 }
911 
912 
913 
914 /*********************************************************************
915 // Name:  Ack
916 // Input:
917 // Output:
918 // Description:
919 // Get the response from the server. We only want to know if the
920 // last command was successful.
921 // Author/Date:  jcar 20/9/96
922 // History:
923 //********************************************************************/
Ack(char ** server_response)924 static int Ack(char **server_response)
925 {
926 	ZEND_TLS char buf[MAIL_BUFFER_SIZE];
927 	int rlen;
928 	int Index = 0;
929 	int Received = 0;
930 
931 #if SENDMAIL_DEBUG
932 	return (SUCCESS);
933 #endif
934 
935 again:
936 
937 	if ((rlen = recv(PW32G(mail_socket), buf + Index, ((MAIL_BUFFER_SIZE) - 1) - Received, 0)) < 1) {
938 		return (FAILED_TO_RECEIVE);
939 	}
940 	Received += rlen;
941 	buf[Received] = 0;
942 	/*err_msg   fprintf(stderr,"Received: (%d bytes) %s", rlen, buf + Index); */
943 
944 	/* Check for newline */
945 	Index += rlen;
946 
947 	/* SMTP RFC says \r\n is the only valid line ending, who are we to argue ;)
948 	 * The response code must contain at least 5 characters ex. 220\r\n */
949 	if (Received < 5 || buf[Received - 1] != '\n' || buf[Received - 2] != '\r') {
950 		goto again;
951 	}
952 
953 	if (buf[0] > '3') {
954 		/* If we've a valid pointer, return the SMTP server response so the error message contains more information */
955 		if (server_response) {
956 			int dec = 0;
957 			/* See if we have something like \r, \n, \r\n or \n\r at the end of the message and chop it off */
958 			if (Received > 2) {
959 				if (buf[Received-1] == '\n' || buf[Received-1] == '\r') {
960 					dec++;
961 					if (buf[Received-2] == '\r' || buf[Received-2] == '\n') {
962 						dec++;
963 					}
964 				}
965 
966 			}
967 			*server_response = estrndup(buf, Received - dec);
968 		}
969 		return (SMTP_SERVER_ERROR);
970 	}
971 
972 	return (SUCCESS);
973 }
974 
975 
976 /*********************************************************************
977 // Name:  unsigned long GetAddr (LPSTR szHost)
978 // Input:
979 // Output:
980 // Description: Given a string, it will return an IP address.
981 //   - first it tries to convert the string directly
982 //   - if that fails, it tries o resolve it as a hostname
983 //
984 // WARNING: gethostbyname() is a blocking function
985 // Author/Date:  jcar 20/9/96
986 // History:
987 //********************************************************************/
GetAddr(LPSTR szHost)988 static unsigned long GetAddr(LPSTR szHost)
989 {
990 	LPHOSTENT lpstHost;
991 	u_long lAddr = INADDR_ANY;
992 
993 	/* check that we have a string */
994 	if (*szHost) {
995 
996 		/* check for a dotted-IP address string */
997 		lAddr = inet_addr(szHost);
998 
999 		/* If not an address, then try to resolve it as a hostname */
1000 		if ((lAddr == INADDR_NONE) && (strcmp(szHost, "255.255.255.255"))) {
1001 
1002 			lpstHost = gethostbyname(szHost);
1003 			if (lpstHost) {		/* success */
1004 				lAddr = *((u_long FAR *) (lpstHost->h_addr));
1005 			} else {
1006 				lAddr = INADDR_ANY;		/* failure */
1007 			}
1008 		}
1009 	}
1010 	return (lAddr);
1011 } /* end GetAddr() */
1012 
1013 /* returns the contents of an angle-addr (caller needs to efree) or NULL */
get_angle_addr(char * address)1014 static char *get_angle_addr(char *address)
1015 {
1016 	bool in_quotes = 0;
1017 	char *p1 = address, *p2;
1018 
1019 	while ((p1 = strpbrk(p1, "<\"\\")) != NULL) {
1020 		if (*p1 == '\\' && in_quotes) {
1021 			if (p1[1] == '\0') {
1022 				/* invalid address; let SMTP server deal with it */
1023 				return NULL;
1024 			}
1025 			p1++;
1026 		} else if (*p1 == '"') {
1027 			in_quotes = !in_quotes;
1028 		} else if (*p1 == '<' && !in_quotes) {
1029 			break;
1030 		}
1031 		p1++;
1032 	}
1033 	if (p1 == NULL) return NULL;
1034 	p2 = ++p1;
1035 	while ((p2 = strpbrk(p2, ">\"\\")) != NULL) {
1036 		if (*p2 == '\\' && in_quotes) {
1037 			if (p2[1] == '\0') {
1038 				/* invalid address; let SMTP server deal with it */
1039 				return NULL;
1040 			}
1041 			p2++;
1042 		} else if (*p2 == '"') {
1043 			in_quotes = !in_quotes;
1044 		} else if (*p2 == '>' && !in_quotes) {
1045 			break;
1046 		}
1047 		p2++;
1048 	}
1049 	if (p2 == NULL) return NULL;
1050 
1051 	return estrndup(p1, p2 - p1);
1052 }
1053 
1054 /*********************************************************************
1055 // Name:  int FormatEmailAddress
1056 // Input:
1057 // Output:
1058 // Description: Formats the email address to remove any content outside
1059 //   of the angle brackets < > as per RFC 2821.
1060 //
1061 //   Returns the invalidly formatted mail address if the < > are
1062 //   unbalanced (the SMTP server should reject it if it's out of spec.)
1063 //
1064 // Author/Date:  garretts 08/18/2009
1065 // History:
1066 //********************************************************************/
FormatEmailAddress(char * Buf,char * EmailAddress,char * FormatString)1067 static int FormatEmailAddress(char* Buf, char* EmailAddress, char* FormatString) {
1068 	char *tmpAddress;
1069 	int result;
1070 
1071 	if ((tmpAddress = get_angle_addr(EmailAddress)) != NULL) {
1072 		result = snprintf(Buf, MAIL_BUFFER_SIZE, FormatString, tmpAddress);
1073 		efree(tmpAddress);
1074 		return result;
1075 	}
1076 	return snprintf(Buf, MAIL_BUFFER_SIZE , FormatString , EmailAddress );
1077 } /* end FormatEmailAddress() */
1078