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