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