sendmail.c 30 KB

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