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