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