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