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