1 /* $Id: ncbi_sendmail.c,v 6.73 2016/09/08 15:54:13 fukanchi Exp $
2 * ===========================================================================
3 *
4 * PUBLIC DOMAIN NOTICE
5 * National Center for Biotechnology Information
6 *
7 * This software/database is a "United States Government Work" under the
8 * terms of the United States Copyright Act. It was written as part of
9 * the author's official duties as a United States Government employee and
10 * thus cannot be copyrighted. This software/database is freely available
11 * to the public for use. The National Library of Medicine and the U.S.
12 * Government have not placed any restriction on its use or reproduction.
13 *
14 * Although all reasonable efforts have been taken to ensure the accuracy
15 * and reliability of the software and data, the NLM and the U.S.
16 * Government do not and cannot warrant the performance or results that
17 * may be obtained by using this software or data. The NLM and the U.S.
18 * Government disclaim all warranties, express or implied, including
19 * warranties of performance, merchantability or fitness for any particular
20 * purpose.
21 *
22 * Please cite the author in any work or product based on this material.
23 *
24 * ===========================================================================
25 *
26 * Author: Anton Lavrentiev
27 *
28 * File Description:
29 * Send mail
30 *
31 */
32
33 #include "ncbi_ansi_ext.h"
34 #include "ncbi_priv.h"
35 #include <connect/ncbi_connutil.h>
36 #include <connect/ncbi_sendmail.h>
37 #include <ctype.h>
38 #include <errno.h>
39 #include <stdlib.h>
40 #include <time.h>
41
42 #define NCBI_USE_ERRCODE_X Connect_SMTP
43
44 #define MX_MAGIC_COOKIE 0xBA8ADEDA
45 #define MX_CRLF "\r\n"
46
47 #define SMTP_READERR -1 /* Read error from socket */
48 #define SMTP_READTMO -2 /* Read timed out */
49 #define SMTP_RESPERR -3 /* Cannot read response prefix */
50 #define SMTP_NOCODE -4 /* No response code detected (letters?) */
51 #define SMTP_BADCODE -5 /* Response code doesn't match in lines */
52 #define SMTP_BADRESP -6 /* Malformed response */
53
54
55 /* Read SMTP response from the socket.
56 * Return the response in the buffer provided,
57 * and the response code (positive value) as a return value.
58 * Return a negative code in case of problem (protocol response
59 * read error or protocol violations).
60 * Return 0 in case of a call error.
61 */
s_SockRead(SOCK sock,char * response,size_t max_response_len,int savecode)62 static int s_SockRead(SOCK sock, char* response, size_t max_response_len,
63 int/*bool*/ savecode)
64 {
65 int/*bool*/ done = 0;
66 size_t n = 0;
67 int code = 0;
68
69 assert(response && max_response_len);
70 do {
71 EIO_Status status;
72 size_t m = 0;
73 char buf[4];
74
75 status = SOCK_Read(sock, buf, 4, &m, eIO_ReadPersist);
76 if (status != eIO_Success) {
77 if (m == 3 && status == eIO_Closed)
78 buf[m++] = ' ';
79 else if (status == eIO_Timeout)
80 return SMTP_READTMO;
81 else if (m)
82 return SMTP_RESPERR;
83 else
84 return SMTP_READERR;
85 }
86 assert(m == 4);
87
88 if (buf[3] == '-' || (done = isspace((unsigned char) buf[3]))) {
89 buf[3] = '\0';
90 if (!code) {
91 char* e;
92 errno = 0;
93 code = (int) strtol(buf, &e, 10);
94 if (errno || code <= 0 || e != buf + 3)
95 return SMTP_NOCODE;
96 if (savecode) {
97 n = 3;
98 if (n > max_response_len)
99 n = max_response_len;
100 memcpy(response, buf, n);
101 if (n < max_response_len)
102 response[n++] = ' ';
103 }
104 } else if (code != atoi(buf))
105 return SMTP_BADCODE;
106 } else
107 return SMTP_BADRESP;
108
109 if (status == eIO_Success) {
110 do {
111 status = SOCK_Read(sock, buf, 1, &m, eIO_ReadPlain);
112 if (status == eIO_Closed) {
113 if (n < max_response_len)
114 response[n++] = '\n';
115 done = 1;
116 break;
117 }
118 if (!m)
119 return status == eIO_Timeout ? SMTP_READTMO : SMTP_READERR;
120 if (*buf != '\r' && n < max_response_len)
121 response[n++] = *buf;
122 assert(status == eIO_Success);
123 } while (*buf != '\n');
124
125 /* At least '\n' should sit in the buffer */
126 assert(n);
127 if (done)
128 response[n - 1] = '\0';
129 else if (n < max_response_len)
130 response[n] = ' ';
131 } else {
132 *response = '\0';
133 assert(done);
134 break;
135 }
136 } while (!done);
137
138 assert(code > 0);
139 return code;
140 }
141
142
s_SockReadResponse(SOCK sock,int code,int alt_code,char * buf,size_t buf_size,int savecode)143 static int/*bool*/ s_SockReadResponse(SOCK sock, int code, int alt_code,
144 char* buf, size_t buf_size,
145 int/*bool*/ savecode)
146 {
147 int c = s_SockRead(sock, buf, buf_size, savecode);
148 if (c <= 0) {
149 const char* message = 0;
150 switch (c) {
151 case SMTP_READERR:
152 message = "Read error";
153 break;
154 case SMTP_READTMO:
155 message = "Read timed out";
156 break;
157 case SMTP_RESPERR:
158 message = "Cannot read response prefix";
159 break;
160 case SMTP_NOCODE:
161 message = "No response code detected";
162 break;
163 case SMTP_BADCODE:
164 message = "Response code mismatch";
165 break;
166 case SMTP_BADRESP:
167 message = "Malformed response";
168 break;
169 default:
170 message = "Unknown error";
171 assert(0);
172 break;
173 }
174 assert(message);
175 strncpy0(buf, message, buf_size - 1);
176 } else if (!code || c == code || (alt_code && c == alt_code))
177 return 1/*success*/;
178 return 0/*failure*/;
179 }
180
181
s_SockWrite(SOCK sock,const char * buf,size_t len)182 static int/*bool*/ s_SockWrite(SOCK sock, const char* buf, size_t len)
183 {
184 size_t n;
185
186 if (!len)
187 len = strlen(buf);
188 if (SOCK_Write(sock, buf, len, &n, eIO_WritePersist) == eIO_Success) {
189 assert(n == len);
190 return 1/*success*/;
191 }
192 return 0/*failure*/;
193 }
194
195
s_MakeFrom(char * buf,size_t size,const char * from,ECORE_Username user)196 static void s_MakeFrom(char* buf, size_t size, const char* from,
197 ECORE_Username user)
198 {
199 char x_buf[sizeof(((SSendMailInfo*) 0)->from)];
200 const char* at;
201 size_t len;
202
203 if (from && *from) {
204 if (!(at = strchr(from, '@'))) {
205 /* no "@", verbatim copy */
206 if (buf != from)
207 strncpy0(buf, from, size - 1);
208 return;
209 }
210 if (at != from) {
211 /* "user@[host]" */
212 if (buf != from) {
213 len = (size_t)(at - from);
214 if (len < size) {
215 size_t tmp = len + strlen(at);
216 if (tmp < size)
217 len = tmp;
218 } else
219 len = size - 1;
220 strncpy0(buf, from, len);
221 }
222 if (*++at)
223 return; /* "user@host", all done */
224 /* (*at) == '\0' */
225 } else if (at[1]) {
226 /* "@host", save host if it fits the temp buffer */
227 if ((len = strlen(at)) < sizeof(x_buf)) {
228 memcpy(x_buf, at, len + 1);
229 at = x_buf;
230 } else
231 at = "@";
232 *buf = '\0';
233 /* (*at) != '\0' (=='@') */
234 } else {
235 /* "@" */
236 *buf = '\0';
237 return;
238 }
239 } else
240 at = 0;
241 if (!at || *at) {
242 if (!CORE_GetUsernameEx(buf, size, user) || !*buf)
243 strncpy0(buf, "anonymous", size - 1);
244 len = strlen(buf);
245 } else
246 len = strlen(buf) - 1/*'@'*/;
247 size -= len;
248 buf += len;
249 if (!at || !*at) {
250 if (size-- > 2) {
251 *buf++ = '@';
252 if ((!SOCK_gethostbyaddr(0, buf, size) || !strchr(buf, '.'))
253 && SOCK_gethostname(buf, size) != 0) {
254 const char* host;
255 CORE_LOCK_READ;
256 if ((!(host = getenv("HOSTNAME")) && !(host = getenv("HOST")))
257 || (len = strlen(host)) >= size) {
258 *--buf = '\0';
259 } else
260 strcpy(buf, host);
261 CORE_UNLOCK;
262 }
263 } else
264 *buf = '\0';
265 } else if (1 < (len = strlen(at)) && len < size)
266 memcpy(buf, at, len + 1);
267 }
268
269
270 static STimeout s_MxTimeout;
271 static char s_MxHost[256];
272 static unsigned short s_MxPort;
273
274
x_Sendmail_InitEnv(void)275 static void x_Sendmail_InitEnv(void)
276 {
277 char buf[sizeof(s_MxHost)], *e;
278 unsigned int port;
279 double tmo;
280
281 if (s_MxPort)
282 return;
283
284 if (!ConnNetInfo_GetValue(0, "MX_TIMEOUT", buf, sizeof(buf), 0) || !*buf
285 || (tmo = NCBI_simple_atof(buf, &e)) < 0.000001 || errno || !*e) {
286 tmo = 120.0/*2 min*/;
287 }
288 if (!ConnNetInfo_GetValue(0, "MX_PORT", buf, sizeof(buf), 0)
289 || !(port = atoi(buf)) || port > 65535) {
290 port = CONN_PORT_SMTP;
291 }
292 if (!ConnNetInfo_GetValue(0, "MX_HOST", buf, sizeof(buf), 0)
293 || !*buf) {
294 #if defined(NCBI_OS_UNIX) && !defined(NCBI_OS_CYGWIN)
295 strcpy(buf, "localhost");
296 #else
297 strcpy(buf, "mailgw");
298 #endif /*NCBI_OS_UNIX && !NCBI_OS_CYGWIN*/
299 }
300
301 CORE_LOCK_WRITE;
302 s_MxTimeout.sec = (unsigned int) tmo;
303 s_MxTimeout.usec = (unsigned int)((tmo - s_MxTimeout.sec) * 1000000.0);
304 strcpy(s_MxHost, buf);
305 s_MxPort = port;
306 CORE_UNLOCK;
307 }
308
309
SendMailInfo_InitEx(SSendMailInfo * info,const char * from,ECORE_Username user)310 extern SSendMailInfo* SendMailInfo_InitEx(SSendMailInfo* info,
311 const char* from,
312 ECORE_Username user)
313 {
314 if (info) {
315 x_Sendmail_InitEnv();
316 info->cc = 0;
317 info->bcc = 0;
318 s_MakeFrom(info->from, sizeof(info->from), from, user);
319 info->header = 0;
320 info->body_size = 0;
321 info->mx_timeout = s_MxTimeout;
322 info->mx_host = s_MxHost;
323 info->mx_port = s_MxPort;
324 info->mx_options = 0;
325 info->magic_cookie = MX_MAGIC_COOKIE;
326 }
327 return info;
328 }
329
330
CORE_SendMail(const char * to,const char * subject,const char * body)331 extern const char* CORE_SendMail(const char* to,
332 const char* subject,
333 const char* body)
334 {
335 return CORE_SendMailEx(to, subject, body, 0);
336 }
337
338
339 /* In two macros below the smartest (or, weak-minded?) Sun
340 * C compiler warns about unreachable end-of-loop condition
341 * (well, it thinks "a condition" is there, dumb!).
342 */
343
344 #define SENDMAIL_RETURN(subcode, reason) \
345 do { \
346 if (sock) { \
347 SOCK_Close(sock); \
348 sock = 0; \
349 } \
350 CORE_LOGF_X(subcode, eLOG_Error, ("[SendMail] %s", reason)); \
351 if (!sock) { \
352 if (info->mx_options & fSendMail_ExtendedErrInfo) { \
353 char* retval = strdup(reason); \
354 return retval ? retval : ""; \
355 } \
356 return reason; \
357 } \
358 /*NOTREACHED*/ \
359 } while (0)
360
361 #define SENDMAIL_RETURN2(subcode, reason, explanation) \
362 do { \
363 if (sock) { \
364 SOCK_Close(sock); \
365 sock = 0; \
366 } \
367 CORE_LOGF_X(subcode, eLOG_Error, \
368 ("[SendMail] %s: %s", reason, explanation)); \
369 if (!sock) { \
370 if (info->mx_options & fSendMail_ExtendedErrInfo) { \
371 size_t len = strlen(reason); \
372 char* retval = (char*) malloc(len + 3 \
373 + strlen(explanation)); \
374 if (!retval) \
375 return ""; \
376 memcpy(retval, reason, len); \
377 retval[len++] = ':'; \
378 retval[len++] = ' '; \
379 strcpy(retval + len, explanation); \
380 return retval; \
381 } \
382 return reason; \
383 } \
384 /*NOTREACHED*/ \
385 } while (0)
386
387
s_SendRcpt(SOCK sock,const char * to,char buf[],size_t buf_size,const char what[],const char write_error[],const char proto_error[],const SSendMailInfo * info)388 static const char* s_SendRcpt(SOCK sock, const char* to,
389 char buf[], size_t buf_size,
390 const char what[],
391 const char write_error[],
392 const char proto_error[],
393 const SSendMailInfo* info)
394 {
395 char c;
396 while ((c = *to++) != '\0') {
397 char quote = 0;
398 size_t k = 0;
399 if (isspace((unsigned char) c))
400 continue;
401 while (k < buf_size) {
402 if (quote) {
403 if (c == quote)
404 quote = 0;
405 } else if (c == '"' || c == '<') {
406 quote = c == '<' ? '>' : c;
407 } else if (c == ',')
408 break;
409 buf[k++] = c == '\t' ? ' ' : c;
410 if (!(c = *to++))
411 break;
412 if (isspace((unsigned char) c)) {
413 while (isspace((unsigned char)(*to)))
414 to++;
415 }
416 }
417 if (k >= buf_size)
418 SENDMAIL_RETURN(3, "Recipient address is too long");
419 buf[k] = '\0'/*just in case*/;
420 if (quote) {
421 CORE_LOGF_X(1, eLOG_Warning,
422 ("[SendMail] Unbalanced delimiters in"
423 " recipient %s for %s: \"%c\" expected",
424 buf, what, quote));
425 }
426 if (!s_SockWrite(sock, "RCPT TO: <", 10) ||
427 !s_SockWrite(sock, buf, k) ||
428 !s_SockWrite(sock, ">" MX_CRLF, sizeof(MX_CRLF))) {
429 SENDMAIL_RETURN(4, write_error);
430 }
431 if (!s_SockReadResponse(sock, 250, 251, buf, buf_size,
432 info->mx_options & fSendMail_ExtendedErrInfo)){
433 SENDMAIL_RETURN2(5, proto_error, buf);
434 }
435 if (!c)
436 break;
437 }
438 return 0;
439 }
440
441
s_FromSize(const SSendMailInfo * info)442 static size_t s_FromSize(const SSendMailInfo* info)
443 {
444 const char* at, *dot;
445 size_t len = *info->from ? strlen(info->from) : 0;
446
447 if (!len || !(info->mx_options & fSendMail_StripNonFQDNHost))
448 return len;
449 if (!(at = (const char*) memchr(info->from, '@', len))
450 || at == info->from + len - 1) {
451 return len - 1;
452 }
453 if (!(dot = (const char*) memchr(at + 1, '.',
454 len - (size_t)(at - info->from) - 1))
455 || dot == at + 1 || dot == info->from + len - 1) {
456 return (size_t)(at - info->from);
457 }
458 return len;
459 }
460
461
462 #define SENDMAIL_SENDRCPT(what, list, buffer) \
463 s_SendRcpt(sock, list, buffer, sizeof(buffer), what, \
464 "Write error in RCPT (" what ") command", \
465 "Protocol error in RCPT (" what ") command", \
466 info)
467
468 #define SENDMAIL_READ_RESPONSE(code, buffer) \
469 s_SockReadResponse(sock, code, 0, buffer, sizeof(buffer), \
470 info->mx_options & fSendMail_ExtendedErrInfo)
471
472
CORE_SendMailEx(const char * to,const char * subject,const char * body,const SSendMailInfo * uinfo)473 extern const char* CORE_SendMailEx(const char* to,
474 const char* subject,
475 const char* body,
476 const SSendMailInfo* uinfo)
477 {
478 static const STimeout zero = {0, 0};
479 const SSendMailInfo* info;
480 SSendMailInfo ainfo;
481 EIO_Status status;
482 char buffer[1024];
483 TSOCK_Flags log;
484 SOCK sock = 0;
485
486 info = uinfo ? uinfo : SendMailInfo_Init(&ainfo);
487 if (info->magic_cookie != MX_MAGIC_COOKIE)
488 SENDMAIL_RETURN(6, "Invalid magic cookie");
489
490 if ((!to || !*to) &&
491 (!info->cc || !*info->cc) &&
492 (!info->bcc || !*info->bcc)) {
493 SENDMAIL_RETURN(7, "At least one message recipient must be specified");
494 }
495
496 /* Open connection to sendmail */
497 log = info->mx_options & fSendMail_LogOn ? fSOCK_LogOn : fSOCK_LogDefault;
498 if ((status = SOCK_CreateEx(info->mx_host, info->mx_port,
499 &info->mx_timeout, &sock,
500 0, 0, log)) != eIO_Success) {
501 sprintf(buffer, "%s:%hu (%s)", info->mx_host, info->mx_port,
502 IO_StatusStr(status));
503 SENDMAIL_RETURN2(8, "Cannot connect to sendmail", buffer);
504 }
505 SOCK_SetTimeout(sock, eIO_ReadWrite, &info->mx_timeout);
506 SOCK_SetTimeout(sock, eIO_Close, &info->mx_timeout);
507
508 /* Follow the protocol conversation, RFC821 */
509 if (!SENDMAIL_READ_RESPONSE(220, buffer))
510 SENDMAIL_RETURN2(9, "Protocol error in connection init", buffer);
511
512 if ((!(info->mx_options & fSendMail_StripNonFQDNHost)
513 || !SOCK_gethostbyaddr(0, buffer, sizeof(buffer)))
514 && SOCK_gethostname(buffer, sizeof(buffer)) != 0) {
515 SENDMAIL_RETURN(10, "Unable to get local host name");
516 }
517 if (!s_SockWrite(sock, "HELO ", 5) ||
518 !s_SockWrite(sock, buffer, 0) ||
519 !s_SockWrite(sock, MX_CRLF, sizeof(MX_CRLF)-1)) {
520 SENDMAIL_RETURN(11, "Write error in HELO command");
521 }
522 if (!SENDMAIL_READ_RESPONSE(250, buffer))
523 SENDMAIL_RETURN2(12, "Protocol error in HELO command", buffer);
524
525 if (!s_SockWrite(sock, "MAIL FROM: <", 12) ||
526 !s_SockWrite(sock, info->from, s_FromSize(info)) ||
527 !s_SockWrite(sock, ">" MX_CRLF, sizeof(MX_CRLF))) {
528 SENDMAIL_RETURN(13, "Write error in MAIL command");
529 }
530 if (!SENDMAIL_READ_RESPONSE(250, buffer))
531 SENDMAIL_RETURN2(14, "Protocol error in MAIL command", buffer);
532
533 if (to && *to) {
534 const char* error = SENDMAIL_SENDRCPT("To", to, buffer);
535 if (error)
536 return error;
537 }
538
539 if (info->cc && *info->cc) {
540 const char* error = SENDMAIL_SENDRCPT("Cc", info->cc, buffer);
541 if (error)
542 return error;
543 }
544
545 if (info->bcc && *info->bcc) {
546 const char* error = SENDMAIL_SENDRCPT("Bcc", info->bcc, buffer);
547 if (error)
548 return error;
549 }
550
551 if (!s_SockWrite(sock, "DATA" MX_CRLF, 4 + sizeof(MX_CRLF)-1))
552 SENDMAIL_RETURN(15, "Write error in DATA command");
553 if (!SENDMAIL_READ_RESPONSE(354, buffer))
554 SENDMAIL_RETURN2(16, "Protocol error in DATA command", buffer);
555
556 (void) SOCK_SetCork(sock, eOn);
557
558 if (!(info->mx_options & fSendMail_NoMxHeader)) {
559 if (!(info->mx_options & fSendMail_Old822Headers)) {
560 /* Locale-independent month names per RFC5322 and older */
561 static const char* kMonth[] = { "Jan", "Feb", "Mar", "Apr",
562 "May", "Jun", "Jul", "Aug",
563 "Sep", "Oct", "Nov", "Dec" };
564 /* Skip DoW: it's optional yet must also be locale-independent */
565 static const char kDateFmt[] = "%d %%s %Y %H:%M:%S %z" MX_CRLF;
566 time_t now = time(0);
567 char datefmt[80];
568 struct tm* tm;
569 #if defined(NCBI_OS_SOLARIS)
570 /* MT safe */
571 tm = localtime(&now);
572 #elif defined(HAVE_LOCALTIME_R)
573 struct tm tmp;
574 localtime_r(&now, tm = &tmp);
575 #else
576 struct tm tmp;
577 CORE_LOCK_WRITE;
578 tm = (struct tm*) memcpy(&tmp, localtime(&now), sizeof(tmp));
579 CORE_UNLOCK;
580 #endif /*NCBI_OS_SOLARIS*/
581 if (strftime(datefmt, sizeof(datefmt), kDateFmt, tm)) {
582 sprintf(buffer, datefmt, kMonth[tm->tm_mon]);
583 if (!s_SockWrite(sock, "Date: ", 6) ||
584 !s_SockWrite(sock, buffer, 0)) {
585 SENDMAIL_RETURN(32, "Write error in sending Date");
586 }
587 }
588 if (*info->from) {
589 if (!s_SockWrite(sock, "From: ", 6) ||
590 !s_SockWrite(sock, info->from, 0) ||
591 !s_SockWrite(sock, MX_CRLF, sizeof(MX_CRLF)-1)) {
592 SENDMAIL_RETURN(33, "Write error in sending From");
593 }
594 }
595 }
596 if (subject) {
597 if (!s_SockWrite(sock, "Subject: ", 9) ||
598 (*subject && !s_SockWrite(sock, subject, 0)) ||
599 !s_SockWrite(sock, MX_CRLF, sizeof(MX_CRLF)-1)) {
600 SENDMAIL_RETURN(17, "Write error in sending Subject");
601 }
602 }
603 if (to && *to) {
604 if (!s_SockWrite(sock, "To: ", 4) ||
605 !s_SockWrite(sock, to, 0) ||
606 !s_SockWrite(sock, MX_CRLF, sizeof(MX_CRLF)-1)) {
607 SENDMAIL_RETURN(18, "Write error in sending To");
608 }
609 }
610 if (info->cc && *info->cc) {
611 if (!s_SockWrite(sock, "Cc: ", 4) ||
612 !s_SockWrite(sock, info->cc, 0) ||
613 !s_SockWrite(sock, MX_CRLF, sizeof(MX_CRLF)-1)) {
614 SENDMAIL_RETURN(19, "Write error in sending Cc");
615 }
616 }
617 } else if (subject && *subject) {
618 CORE_LOG_X(2, eLOG_Warning,
619 "[SendMail] Subject ignored in as-is messages");
620 }
621
622 if (!s_SockWrite(sock, "X-Mailer: CORE_SendMail (NCBI "
623 #ifdef NCBI_CXX_TOOLKIT
624 "CXX Toolkit"
625 #else
626 "C Toolkit"
627 #endif /*NCBI_CXX_TOOLKIT*/
628 ")" MX_CRLF, 0)) {
629 SENDMAIL_RETURN(20, "Write error in sending mailer information");
630 }
631
632 assert(sizeof(buffer) > sizeof(MX_CRLF) && sizeof(MX_CRLF) >= 3);
633
634 status = eIO_Timeout;
635 if (info->header && *info->header) {
636 size_t n = 0, m = strlen(info->header);
637 int/*bool*/ newline = 0/*false*/;
638 while (n < m) {
639 size_t k = 0;
640 if ((status = SOCK_Wait(sock, eIO_Read, &zero)) != eIO_Timeout)
641 break;
642 while (k < sizeof(buffer) - sizeof(MX_CRLF)) {
643 if (info->header[n] == '\n') {
644 memcpy(&buffer[k], MX_CRLF, sizeof(MX_CRLF)-1);
645 k += sizeof(MX_CRLF)-1;
646 newline = 1/*true*/;
647 } else {
648 if (info->header[n] != '\r' || info->header[n+1] != '\n')
649 buffer[k++] = info->header[n];
650 newline = 0/*false*/;
651 }
652 if (++n >= m)
653 break;
654 }
655 buffer[k] = '\0'/*just in case*/;
656 if (!s_SockWrite(sock, buffer, k))
657 SENDMAIL_RETURN(21, "Write error while sending custom header");
658 }
659 if (n < m) {
660 const char* error = "Custom header write error";
661 assert(status != eIO_Timeout);
662 if (status != eIO_Success)
663 strncpy0(buffer, IO_StatusStr(status), sizeof(buffer) - 1);
664 else if (SENDMAIL_READ_RESPONSE(0, buffer))
665 error = "Spurious response while writing custom header";
666 SENDMAIL_RETURN2(22, error, buffer);
667 }
668 if (!newline && !s_SockWrite(sock, MX_CRLF, sizeof(MX_CRLF)-1))
669 SENDMAIL_RETURN(23, "Write error in finalizing custom header");
670 }
671
672 assert(status == eIO_Timeout);
673 if (body) {
674 size_t n = 0, m = info->body_size ? info->body_size : strlen(body);
675 int/*bool*/ newline = 0/*false*/;
676 if (!(info->mx_options & fSendMail_NoMxHeader) && m) {
677 if (!s_SockWrite(sock, MX_CRLF, sizeof(MX_CRLF)-1))
678 SENDMAIL_RETURN(24, "Write error in message body delimiter");
679 }
680 while (n < m) {
681 size_t k = 0;
682 if ((status = SOCK_Wait(sock, eIO_Read, &zero)) != eIO_Timeout)
683 break;
684 while (k < sizeof(buffer) - sizeof(MX_CRLF)) {
685 if (body[n] == '\n') {
686 memcpy(&buffer[k], MX_CRLF, sizeof(MX_CRLF)-1);
687 k += sizeof(MX_CRLF)-1;
688 newline = 1/*true*/;
689 } else {
690 if (body[n] != '\r' || (n+1 < m && body[n+1] != '\n')){
691 if (body[n] == '.' && (newline || !n)) {
692 buffer[k++] = '.';
693 buffer[k++] = '.';
694 } else
695 buffer[k++] = body[n];
696 }
697 newline = 0/*false*/;
698 }
699 if (++n >= m)
700 break;
701 }
702 buffer[k] = '\0'/*just in case*/;
703 if (!s_SockWrite(sock, buffer, k))
704 SENDMAIL_RETURN(25, "Write error while sending message body");
705 }
706 if (n < m) {
707 const char* error = "Message body write error";
708 assert(status == eIO_Timeout);
709 if (status != eIO_Success)
710 strncpy0(buffer, IO_StatusStr(status), sizeof(buffer) - 1);
711 else if (SENDMAIL_READ_RESPONSE(0, buffer))
712 error = "Spurious response while writing message body";
713 SENDMAIL_RETURN2(26, error, buffer);
714 }
715 if ((!newline && m && !s_SockWrite(sock,MX_CRLF,sizeof(MX_CRLF)-1))
716 || !s_SockWrite(sock, "." MX_CRLF, sizeof(MX_CRLF))) {
717 SENDMAIL_RETURN(27, "Write error while finalizing message body");
718 }
719 } else if (!s_SockWrite(sock, "." MX_CRLF, sizeof(MX_CRLF)))
720 SENDMAIL_RETURN(28, "Write error while finalizing message");
721
722 (void) SOCK_SetCork(sock, eOff);
723
724 if (!SENDMAIL_READ_RESPONSE(250, buffer))
725 SENDMAIL_RETURN2(29, "Protocol error in sending message", buffer);
726
727 if (!s_SockWrite(sock, "QUIT" MX_CRLF, 4 + sizeof(MX_CRLF)-1))
728 SENDMAIL_RETURN(30, "Write error in QUIT command");
729 if (!SENDMAIL_READ_RESPONSE(221, buffer))
730 SENDMAIL_RETURN2(31, "Protocol error in QUIT command", buffer);
731
732 SOCK_Close(sock);
733 return 0;
734 }
735
736 #undef SENDMAIL_READ_RESPONSE
737 #undef SENDMAIL_SENDRCPT
738 #undef SENDMAIL_RETURN2
739 #undef SENDMAIL_RETURN
740