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