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