xref: /PHP-7.4/win32/sendmail.c (revision 881e43a0)
1 /*
2  *    PHP Sendmail for Windows.
3  *
4  *  This file is rewritten specificly 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 http://bugs.php.net/"} /* 21 */
91 };
92 
93 /* This pattern converts all single occurrences of \n (Unix)
94  * withour 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 eleminate 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  */
125 static zend_string *php_win32_mail_trim_header(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 accespted 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 //********************************************************************/
179 PHPAPI int TSendMail(char *host, int *error, char **error_message,
180 			  char *headers, char *Subject, char *mailTo, 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 	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 		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 headrs + 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 %d, 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 //********************************************************************/
303 PHPAPI void TSMClose()
304 {
305 	Post("QUIT\r\n");
306 	Ack(NULL);
307 	/* to guarantee that the cleanup is not made twice and
308 	   compomise the rest of the application if sockets are used
309 	   elesewhere
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 menber functions
320 // Output:  pointer to a string containing the error description
321 // Description:
322 // Author/Date:  jcar 20/9/96
323 // History:
324 //*******************************************************************/
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 
337 /*********************************************************************
338 // Name:  SendText
339 // Input:       1) RPath:   return path of the message
340 //                                  Is used to fill the "Return-Path" and the
341 //                                  "X-Sender" fields of the message.
342 //                  2) Subject: Subject field of the message. If NULL is given
343 //                                  the subject is set to "No Subject"
344 //                  3) mailTo:  Destination address
345 //                  4) data:        Null terminated string containing the data to be send.
346 //                  5,6) headers of the message. Note that the second
347 //                  parameter, headers_lc, is actually a lowercased version of
348 //                  headers. The should match exactly (in terms of length),
349 //                  only differ in case
350 // Output:      Error code or SUCCESS
351 // Description:
352 // Author/Date:  jcar 20/9/96
353 // History:
354 //*******************************************************************/
355 static int SendText(char *RPath, char *Subject, char *mailTo, char *mailCc, char *mailBcc, char *data,
356 			 char *headers, char *headers_lc, char **error_message)
357 {
358 	int res;
359 	char *p;
360 	char *tempMailTo, *token, *pos1, *pos2;
361 	char *server_response = NULL;
362 	char *stripped_header  = NULL;
363 	zend_string *data_cln;
364 
365 	/* check for NULL parameters */
366 	if (data == NULL)
367 		return (BAD_MSG_CONTENTS);
368 	if (mailTo == NULL)
369 		return (BAD_MSG_DESTINATION);
370 	if (RPath == NULL)
371 		return (BAD_MSG_RPATH);
372 
373 	/* simple checks for the mailto address */
374 	/* have ampersand ? */
375 	/* mfischer, 20020514: I commented this out because it really
376 	   seems bogus. Only a username for example may still be a
377 	   valid address at the destination system.
378 	if (strchr(mailTo, '@') == NULL)
379 		return (BAD_MSG_DESTINATION);
380 	*/
381 
382 	snprintf(PW32G(mail_buffer), sizeof(PW32G(mail_buffer)), "HELO %s\r\n", PW32G(mail_local_host));
383 
384 	/* in the beginning of the dialog */
385 	/* attempt reconnect if the first Post fail */
386 	if ((res = Post(PW32G(mail_buffer))) != SUCCESS) {
387 		int err = MailConnect();
388 		if (0 != err) {
389 			return (FAILED_TO_SEND);
390 		}
391 
392 		if ((res = Post(PW32G(mail_buffer))) != SUCCESS) {
393 			return (res);
394 		}
395 	}
396 	if ((res = Ack(&server_response)) != SUCCESS) {
397 		SMTP_ERROR_RESPONSE(server_response);
398 		return (res);
399 	}
400 
401 	SMTP_SKIP_SPACE(RPath);
402 	FormatEmailAddress(PW32G(mail_buffer), RPath, "MAIL FROM:<%s>\r\n");
403 	if ((res = Post(PW32G(mail_buffer))) != SUCCESS) {
404 		return (res);
405 	}
406 	if ((res = Ack(&server_response)) != SUCCESS) {
407 		SMTP_ERROR_RESPONSE(server_response);
408 		return W32_SM_SENDMAIL_FROM_MALFORMED;
409 	}
410 
411 	tempMailTo = estrdup(mailTo);
412 	/* Send mail to all rcpt's */
413 	token = strtok(tempMailTo, ",");
414 	while (token != NULL)
415 	{
416 		SMTP_SKIP_SPACE(token);
417 		FormatEmailAddress(PW32G(mail_buffer), token, "RCPT TO:<%s>\r\n");
418 		if ((res = Post(PW32G(mail_buffer))) != SUCCESS) {
419 			efree(tempMailTo);
420 			return (res);
421 		}
422 		if ((res = Ack(&server_response)) != SUCCESS) {
423 			SMTP_ERROR_RESPONSE(server_response);
424 			efree(tempMailTo);
425 			return (res);
426 		}
427 		token = strtok(NULL, ",");
428 	}
429 	efree(tempMailTo);
430 
431 	if (mailCc && *mailCc) {
432 		tempMailTo = estrdup(mailCc);
433 		/* Send mail to all rcpt's */
434 		token = strtok(tempMailTo, ",");
435 		while (token != NULL)
436 		{
437 			SMTP_SKIP_SPACE(token);
438 			FormatEmailAddress(PW32G(mail_buffer), token, "RCPT TO:<%s>\r\n");
439 			if ((res = Post(PW32G(mail_buffer))) != SUCCESS) {
440 				efree(tempMailTo);
441 				return (res);
442 			}
443 			if ((res = Ack(&server_response)) != SUCCESS) {
444 				SMTP_ERROR_RESPONSE(server_response);
445 				efree(tempMailTo);
446 				return (res);
447 			}
448 			token = strtok(NULL, ",");
449 		}
450 		efree(tempMailTo);
451 	}
452 	/* Send mail to all Cc rcpt's */
453 	else if (headers && (pos1 = strstr(headers_lc, "cc:")) && ((pos1 == headers_lc) || (*(pos1-1) == '\n'))) {
454 		/* Real offset is memaddress from the original headers + difference of
455 		 * string found in the lowercase headrs + 3 characters to jump over
456 		 * the cc: */
457 		pos1 = headers + (pos1 - headers_lc) + 3;
458 		if (NULL == (pos2 = strstr(pos1, "\r\n"))) {
459 			tempMailTo = estrndup(pos1, strlen(pos1));
460 		} else {
461 			char *pos3;
462 			while (pos2[2] == ' ' || pos2[2] == '\t') {
463 				pos3 = strstr(pos2 + 2, "\r\n");
464 				if (pos3 != NULL) {
465 					pos2 = pos3;
466 				} else {
467 					pos2 += strlen(pos2);
468 					break;
469 				}
470 			}
471 			tempMailTo = estrndup(pos1, pos2 - pos1);
472 		}
473 
474 		token = strtok(tempMailTo, ",");
475 		while (token != NULL)
476 		{
477 			SMTP_SKIP_SPACE(token);
478 			FormatEmailAddress(PW32G(mail_buffer), token, "RCPT TO:<%s>\r\n");
479 			if ((res = Post(PW32G(mail_buffer))) != SUCCESS) {
480 				efree(tempMailTo);
481 				return (res);
482 			}
483 			if ((res = Ack(&server_response)) != SUCCESS) {
484 				SMTP_ERROR_RESPONSE(server_response);
485 				efree(tempMailTo);
486 				return (res);
487 			}
488 			token = strtok(NULL, ",");
489 		}
490 		efree(tempMailTo);
491 	}
492 
493 	/* Send mail to all Bcc rcpt's
494 	   This is basically a rip of the Cc code above.
495 	   Just don't forget to remove the Bcc: from the header afterwards. */
496 	if (mailBcc && *mailBcc) {
497 		tempMailTo = estrdup(mailBcc);
498 		/* Send mail to all rcpt's */
499 		token = strtok(tempMailTo, ",");
500 		while (token != NULL)
501 		{
502 			SMTP_SKIP_SPACE(token);
503 			FormatEmailAddress(PW32G(mail_buffer), token, "RCPT TO:<%s>\r\n");
504 			if ((res = Post(PW32G(mail_buffer))) != SUCCESS) {
505 				efree(tempMailTo);
506 				return (res);
507 			}
508 			if ((res = Ack(&server_response)) != SUCCESS) {
509 				SMTP_ERROR_RESPONSE(server_response);
510 				efree(tempMailTo);
511 				return (res);
512 			}
513 			token = strtok(NULL, ",");
514 		}
515 		efree(tempMailTo);
516 	}
517 	else if (headers) {
518 		if ((pos1 = strstr(headers_lc, "bcc:")) && (pos1 == headers_lc || *(pos1-1) == '\n')) {
519 			/* Real offset is memaddress from the original headers + difference of
520 			 * string found in the lowercase headrs + 4 characters to jump over
521 			 * the bcc: */
522 			pos1 = headers + (pos1 - headers_lc) + 4;
523 			if (NULL == (pos2 = strstr(pos1, "\r\n"))) {
524 				tempMailTo = estrndup(pos1, strlen(pos1));
525 				/* Later, when we remove the Bcc: out of the
526 				   header we know it was the last thing. */
527 				pos2 = pos1;
528 			} else {
529 				char *pos3 = NULL;
530 				while (pos2[2] == ' ' || pos2[2] == '\t') {
531 					pos3 = strstr(pos2 + 2, "\r\n");
532 					if (pos3 != NULL) {
533 						pos2 = pos3;
534 					} else {
535 						pos2 += strlen(pos2);
536 						break;
537 					}
538 				}
539 				tempMailTo = estrndup(pos1, pos2 - pos1);
540 				if (pos3 == NULL) {
541 					/* Later, when we remove the Bcc: out of the
542 					   header we know it was the last thing. */
543 					pos2 = pos1;
544 				}
545 			}
546 
547 			token = strtok(tempMailTo, ",");
548 			while (token != NULL)
549 			{
550 				SMTP_SKIP_SPACE(token);
551 				FormatEmailAddress(PW32G(mail_buffer), token, "RCPT TO:<%s>\r\n");
552 				if ((res = Post(PW32G(mail_buffer))) != SUCCESS) {
553 					efree(tempMailTo);
554 					return (res);
555 				}
556 				if ((res = Ack(&server_response)) != SUCCESS) {
557 					SMTP_ERROR_RESPONSE(server_response);
558 					efree(tempMailTo);
559 					return (res);
560 				}
561 				token = strtok(NULL, ",");
562 			}
563 			efree(tempMailTo);
564 
565 			/* Now that we've identified that we've a Bcc list,
566 			   remove it from the current header. */
567 			stripped_header = ecalloc(1, strlen(headers));
568 			/* headers = point to string start of header
569 			   pos1    = pointer IN headers where the Bcc starts
570 			   '4'     = Length of the characters 'bcc:'
571 			   Because we've added +4 above for parsing the Emails
572 			   we've to subtract them here. */
573 			memcpy(stripped_header, headers, pos1 - headers - 4);
574 			if (pos1 != pos2) {
575 				/* if pos1 != pos2 , pos2 points to the rest of the headers.
576 				   Since pos1 != pos2 if "\r\n" was found, we know those characters
577 				   are there and so we jump over them (else we would generate a new header
578 				   which would look like "\r\n\r\n". */
579 				memcpy(stripped_header + (pos1 - headers - 4), pos2 + 2, strlen(pos2) - 2);
580 			}
581 		}
582 	}
583 
584 	/* Simplify the code that we create a copy of stripped_header no matter if
585 	   we actually strip something or not. So we've a single efree() later. */
586 	if (headers && !stripped_header) {
587 		stripped_header = estrndup(headers, strlen(headers));
588 	}
589 
590 	if ((res = Post("DATA\r\n")) != SUCCESS) {
591 		if (stripped_header) {
592 			efree(stripped_header);
593 		}
594 		return (res);
595 	}
596 	if ((res = Ack(&server_response)) != SUCCESS) {
597 		SMTP_ERROR_RESPONSE(server_response);
598 		if (stripped_header) {
599 			efree(stripped_header);
600 		}
601 		return (res);
602 	}
603 
604 	/* send message header */
605 	if (Subject == NULL) {
606 		res = PostHeader(RPath, "No Subject", mailTo, stripped_header);
607 	} else {
608 		res = PostHeader(RPath, Subject, mailTo, stripped_header);
609 	}
610 	if (stripped_header) {
611 		efree(stripped_header);
612 	}
613 	if (res != SUCCESS) {
614 		return (res);
615 	}
616 
617 	/* Escape \n. sequences
618 	 * We use php_str_to_str() and not php_str_replace_in_subject(), since the latter
619 	 * uses ZVAL as it's parameters */
620 	data_cln = php_str_to_str(data, strlen(data), PHP_WIN32_MAIL_DOT_PATTERN, sizeof(PHP_WIN32_MAIL_DOT_PATTERN) - 1,
621 					PHP_WIN32_MAIL_DOT_REPLACE, sizeof(PHP_WIN32_MAIL_DOT_REPLACE) - 1);
622 	if (!data_cln) {
623 		data_cln = ZSTR_EMPTY_ALLOC();
624 	}
625 
626 	/* send message contents in 1024 chunks */
627 	{
628 		char c, *e2, *e = ZSTR_VAL(data_cln) + ZSTR_LEN(data_cln);
629 		p = ZSTR_VAL(data_cln);
630 
631 		while (e - p > 1024) {
632 			e2 = p + 1024;
633 			c = *e2;
634 			*e2 = '\0';
635 			if ((res = Post(p)) != SUCCESS) {
636 				zend_string_free(data_cln);
637 				return(res);
638 			}
639 			*e2 = c;
640 			p = e2;
641 		}
642 		if ((res = Post(p)) != SUCCESS) {
643 			zend_string_free(data_cln);
644 			return(res);
645 		}
646 	}
647 
648 	zend_string_free(data_cln);
649 
650 	/*send termination dot */
651 	if ((res = Post("\r\n.\r\n")) != SUCCESS)
652 		return (res);
653 	if ((res = Ack(&server_response)) != SUCCESS) {
654 		SMTP_ERROR_RESPONSE(server_response);
655 		return (res);
656 	}
657 
658 	return (SUCCESS);
659 }
660 
661 static int addToHeader(char **header_buffer, const char *specifier, char *string)
662 {
663 	*header_buffer = erealloc(*header_buffer, strlen(*header_buffer) + strlen(specifier) + strlen(string) + 1);
664 	sprintf(*header_buffer + strlen(*header_buffer), specifier, string);
665 	return 1;
666 }
667 
668 /*********************************************************************
669 // Name:  PostHeader
670 // Input:       1) return path
671 //              2) Subject
672 //              3) destination address
673 //              4) headers
674 // Output:      Error code or Success
675 // Description:
676 // Author/Date:  jcar 20/9/96
677 // History:
678 //********************************************************************/
679 static int PostHeader(char *RPath, char *Subject, char *mailTo, char *xheaders)
680 {
681 	/* Print message header according to RFC 822 */
682 	/* Return-path, Received, Date, From, Subject, Sender, To, cc */
683 
684 	int res;
685 	char *header_buffer;
686 	char *headers_lc = NULL;
687 	size_t i;
688 
689 	if (xheaders) {
690 		size_t headers_lc_len;
691 
692 		headers_lc = estrdup(xheaders);
693 		headers_lc_len = strlen(headers_lc);
694 
695 		for (i = 0; i < headers_lc_len; i++) {
696 			headers_lc[i] = tolower(headers_lc[i]);
697 		}
698 	}
699 
700 	header_buffer = ecalloc(1, MAIL_BUFFER_SIZE);
701 
702 	if (!xheaders || !strstr(headers_lc, "date:")) {
703 		time_t tNow = time(NULL);
704 		zend_string *dt = php_format_date("r", 1, tNow, 1);
705 
706 		snprintf(header_buffer, MAIL_BUFFER_SIZE, "Date: %s\r\n", ZSTR_VAL(dt));
707 		zend_string_free(dt);
708 	}
709 
710 	if (!headers_lc || !strstr(headers_lc, "from:")) {
711 		if (!addToHeader(&header_buffer, "From: %s\r\n", RPath)) {
712 			goto PostHeader_outofmem;
713 		}
714 	}
715 	if (!addToHeader(&header_buffer, "Subject: %s\r\n", Subject)) {
716 		goto PostHeader_outofmem;
717 	}
718 
719 	/* Only add the To: field from the $to parameter if isn't in the custom headers */
720 	if ((headers_lc && (!strstr(headers_lc, "\r\nto:") && (strncmp(headers_lc, "to:", 3) != 0))) || !headers_lc) {
721 		if (!addToHeader(&header_buffer, "To: %s\r\n", mailTo)) {
722 			goto PostHeader_outofmem;
723 		}
724 	}
725 	if (xheaders) {
726 		if (!addToHeader(&header_buffer, "%s\r\n", xheaders)) {
727 			goto PostHeader_outofmem;
728 		}
729 	}
730 
731 	if (headers_lc) {
732 		efree(headers_lc);
733 	}
734 	if ((res = Post(header_buffer)) != SUCCESS) {
735 		efree(header_buffer);
736 		return (res);
737 	}
738 	efree(header_buffer);
739 
740 	if ((res = Post("\r\n")) != SUCCESS) {
741 		return (res);
742 	}
743 
744 	return (SUCCESS);
745 
746 PostHeader_outofmem:
747 	if (headers_lc) {
748 		efree(headers_lc);
749 	}
750 	return OUT_OF_MEMORY;
751 }
752 
753 
754 
755 /*********************************************************************
756 // Name:  MailConnect
757 // Input:   None
758 // Output:  None
759 // Description: Connect to the mail host and receive the welcome message.
760 // Author/Date:  jcar 20/9/96
761 // History:
762 //********************************************************************/
763 static int MailConnect()
764 {
765 
766 	int res, namelen;
767 	short portnum;
768 	struct hostent *ent;
769 	IN_ADDR addr;
770 #ifdef HAVE_IPV6
771 	IN6_ADDR addr6;
772 #endif
773 	SOCKADDR_IN sock_in;
774 
775 #if SENDMAIL_DEBUG
776 return 0;
777 #endif
778 
779 	/* Create Socket */
780 	if ((PW32G(mail_socket) = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
781 		return (FAILED_TO_OBTAIN_SOCKET_HANDLE);
782 	}
783 
784 	/* Get our own host name */
785 	if (gethostname(PW32G(mail_local_host), HOST_NAME_LEN)) {
786 		closesocket(PW32G(mail_socket));
787 		return (FAILED_TO_GET_HOSTNAME);
788 	}
789 
790 	ent = gethostbyname(PW32G(mail_local_host));
791 
792 	if (!ent) {
793 		closesocket(PW32G(mail_socket));
794 		return (FAILED_TO_GET_HOSTNAME);
795 	}
796 
797 	namelen = (int)strlen(ent->h_name);
798 
799 #ifdef HAVE_IPV6
800 	if (inet_pton(AF_INET, ent->h_name, &addr) == 1 || inet_pton(AF_INET6, ent->h_name, &addr6) == 1)
801 #else
802 	if (inet_pton(AF_INET, ent->h_name, &addr) == 1)
803 #endif
804 	{
805 		if (namelen + 2 >= HOST_NAME_LEN) {
806 			closesocket(PW32G(mail_socket));
807 			return (FAILED_TO_GET_HOSTNAME);
808 		}
809 
810 		strcpy(PW32G(mail_local_host), "[");
811 		strcpy(PW32G(mail_local_host) + 1, ent->h_name);
812 		strcpy(PW32G(mail_local_host) + namelen + 1, "]");
813 	} else {
814 		if (namelen >= HOST_NAME_LEN) {
815 			closesocket(PW32G(mail_socket));
816 			return (FAILED_TO_GET_HOSTNAME);
817 		}
818 
819 		strcpy(PW32G(mail_local_host), ent->h_name);
820 	}
821 
822 	/* Resolve the servers IP */
823 	/*
824 	if (!isdigit(PW32G(mail_host)[0])||!gethostbyname(PW32G(mail_host)))
825 	{
826 		return (FAILED_TO_RESOLVE_HOST);
827 	}
828 	*/
829 
830 	portnum = (short) INI_INT("smtp_port");
831 	if (!portnum) {
832 		portnum = 25;
833 	}
834 
835 	/* Connect to server */
836 	sock_in.sin_family = AF_INET;
837 	sock_in.sin_port = htons(portnum);
838 	sock_in.sin_addr.S_un.S_addr = GetAddr(PW32G(mail_host));
839 
840 	if (connect(PW32G(mail_socket), (LPSOCKADDR) & sock_in, sizeof(sock_in))) {
841 		closesocket(PW32G(mail_socket));
842 		return (FAILED_TO_CONNECT);
843 	}
844 
845 	/* receive Server welcome message */
846 	res = Ack(NULL);
847 	return (res);
848 }
849 
850 
851 /*********************************************************************
852 // Name:  Post
853 // Input:
854 // Output:
855 // Description:
856 // Author/Date:  jcar 20/9/96
857 // History:
858 //********************************************************************/
859 static int Post(LPCSTR msg)
860 {
861 	int len = (int)strlen(msg);
862 	int slen;
863 	int index = 0;
864 
865 #if SENDMAIL_DEBUG
866 	if (msg)
867 		printf("POST: '%s'\n", msg);
868 	return (SUCCESS);
869 #endif
870 
871 	while (len > 0) {
872 		if ((slen = send(PW32G(mail_socket), msg + index, len, 0)) < 1)
873 			return (FAILED_TO_SEND);
874 		len -= slen;
875 		index += slen;
876 	}
877 	return (SUCCESS);
878 }
879 
880 
881 
882 /*********************************************************************
883 // Name:  Ack
884 // Input:
885 // Output:
886 // Description:
887 // Get the response from the server. We only want to know if the
888 // last command was successful.
889 // Author/Date:  jcar 20/9/96
890 // History:
891 //********************************************************************/
892 static int Ack(char **server_response)
893 {
894 	ZEND_TLS char buf[MAIL_BUFFER_SIZE];
895 	int rlen;
896 	int Index = 0;
897 	int Received = 0;
898 
899 #if SENDMAIL_DEBUG
900 	return (SUCCESS);
901 #endif
902 
903 again:
904 
905 	if ((rlen = recv(PW32G(mail_socket), buf + Index, ((MAIL_BUFFER_SIZE) - 1) - Received, 0)) < 1) {
906 		return (FAILED_TO_RECEIVE);
907 	}
908 	Received += rlen;
909 	buf[Received] = 0;
910 	/*err_msg   fprintf(stderr,"Received: (%d bytes) %s", rlen, buf + Index); */
911 
912 	/* Check for newline */
913 	Index += rlen;
914 
915 	/* SMPT RFC says \r\n is the only valid line ending, who are we to argue ;)
916 	 * The response code must contain at least 5 characters ex. 220\r\n */
917 	if (Received < 5 || buf[Received - 1] != '\n' || buf[Received - 2] != '\r') {
918 		goto again;
919 	}
920 
921 	if (buf[0] > '3') {
922 		/* If we've a valid pointer, return the SMTP server response so the error message contains more information */
923 		if (server_response) {
924 			int dec = 0;
925 			/* See if we have something like \r, \n, \r\n or \n\r at the end of the message and chop it off */
926 			if (Received > 2) {
927 				if (buf[Received-1] == '\n' || buf[Received-1] == '\r') {
928 					dec++;
929 					if (buf[Received-2] == '\r' || buf[Received-2] == '\n') {
930 						dec++;
931 					}
932 				}
933 
934 			}
935 			*server_response = estrndup(buf, Received - dec);
936 		}
937 		return (SMTP_SERVER_ERROR);
938 	}
939 
940 	return (SUCCESS);
941 }
942 
943 
944 /*********************************************************************
945 // Name:  unsigned long GetAddr (LPSTR szHost)
946 // Input:
947 // Output:
948 // Description: Given a string, it will return an IP address.
949 //   - first it tries to convert the string directly
950 //   - if that fails, it tries o resolve it as a hostname
951 //
952 // WARNING: gethostbyname() is a blocking function
953 // Author/Date:  jcar 20/9/96
954 // History:
955 //********************************************************************/
956 static unsigned long GetAddr(LPSTR szHost)
957 {
958 	LPHOSTENT lpstHost;
959 	u_long lAddr = INADDR_ANY;
960 
961 	/* check that we have a string */
962 	if (*szHost) {
963 
964 		/* check for a dotted-IP address string */
965 		lAddr = inet_addr(szHost);
966 
967 		/* If not an address, then try to resolve it as a hostname */
968 		if ((lAddr == INADDR_NONE) && (strcmp(szHost, "255.255.255.255"))) {
969 
970 			lpstHost = gethostbyname(szHost);
971 			if (lpstHost) {		/* success */
972 				lAddr = *((u_long FAR *) (lpstHost->h_addr));
973 			} else {
974 				lAddr = INADDR_ANY;		/* failure */
975 			}
976 		}
977 	}
978 	return (lAddr);
979 } /* end GetAddr() */
980 
981 
982 /*********************************************************************
983 // Name:  int FormatEmailAddress
984 // Input:
985 // Output:
986 // Description: Formats the email address to remove any content ouside
987 //   of the angle brackets < > as per RFC 2821.
988 //
989 //   Returns the invalidly formatted mail address if the < > are
990 //   unbalanced (the SMTP server should reject it if it's out of spec.)
991 //
992 // Author/Date:  garretts 08/18/2009
993 // History:
994 //********************************************************************/
995 static int FormatEmailAddress(char* Buf, char* EmailAddress, char* FormatString) {
996 	char *tmpAddress1, *tmpAddress2;
997 	int result;
998 
999 	if( (tmpAddress1 = strchr(EmailAddress, '<')) && (tmpAddress2 = strchr(tmpAddress1, '>'))  ) {
1000 		*tmpAddress2 = 0; // terminate the string temporarily.
1001 		result = snprintf(Buf, MAIL_BUFFER_SIZE, FormatString , tmpAddress1+1);
1002 		*tmpAddress2 = '>'; // put it back the way it was.
1003 		return result;
1004 	}
1005 	return snprintf(Buf, MAIL_BUFFER_SIZE , FormatString , EmailAddress );
1006 } /* end FormatEmailAddress() */
1007