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