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