NCBI C Toolkit Cross Reference

C/connect/ncbi_sendmail.c


  1 /* $Id: ncbi_sendmail.c,v 6.42 2009/03/26 15:29:29 kazimird 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_sendmail.h>
 36 #include <connect/ncbi_socket.h>
 37 #include <ctype.h>
 38 #include <errno.h>
 39 #include <stdlib.h>
 40 
 41 #define NCBI_USE_ERRCODE_X   Connect_Sendmail
 42 
 43 #ifdef NCBI_CXX_TOOLKIT
 44 #define NCBI_SENDMAIL_TOOLKIT "C++"
 45 #else
 46 #define NCBI_SENDMAIL_TOOLKIT "C"
 47 #endif
 48 
 49 #define MX_MAGIC_NUMBER 0xBA8ADEDA
 50 #define MX_CRLF         "\r\n"
 51 
 52 #define SMTP_READERR    -1      /* Error reading from socket            */
 53 #define SMTP_READTMO    -2      /* Read timed out                       */
 54 #define SMTP_RESPERR    -3      /* Error reading response prefix        */
 55 #define SMTP_NOCODE     -4      /* No response code detected (letters?) */
 56 #define SMTP_BADCODE    -5      /* Response code doesn't match in lines */
 57 #define SMTP_BADRESP    -6      /* Malformed response                   */
 58 
 59 
 60 /* Read SMTP response from the socket.
 61  * Return the response in the buffer provided,
 62  * and the response code (positive value) as a return value.
 63  * Return a negative code in case of problem (protocol response
 64  * read error or protocol violations).
 65  * Return 0 in case of a call error.
 66  */
 67 static int s_SockRead(SOCK sock, char* response, size_t max_response_len)
 68 {
 69     int/*bool*/ done = 0;
 70     size_t n = 0;
 71     int code = 0;
 72 
 73     assert(response  &&  max_response_len);
 74     do {
 75         EIO_Status status;
 76         size_t m = 0;
 77         char buf[4];
 78 
 79         status = SOCK_Read(sock, buf, 4, &m, eIO_ReadPersist);
 80         if (status != eIO_Success) {
 81             if (m == 3  &&  status == eIO_Closed)
 82                 buf[m++] = ' ';
 83             else if (status == eIO_Timeout)
 84                 return SMTP_READTMO;
 85             else if (m)
 86                 return SMTP_RESPERR;
 87             else
 88                 return SMTP_READERR;
 89         }
 90         assert(m == 4);
 91 
 92         if (buf[3] == '-'  ||  (done = isspace((unsigned char) buf[3]))) {
 93             buf[3] = '\0';
 94             if (!code) {
 95                 char* e;
 96                 errno = 0;
 97                 code = (int) strtol(buf, &e, 10);
 98                 if (errno  ||  code <= 0  ||  e != buf + 3)
 99                     return SMTP_NOCODE;
100             } else if (code != atoi(buf))
101                 return SMTP_BADCODE;
102         } else
103             return SMTP_BADRESP;
104 
105         if (status == eIO_Success) {
106             do {
107                 status = SOCK_Read(sock, buf, 1, &m, eIO_ReadPlain);
108                 if (status == eIO_Closed) {
109                     if (n < max_response_len)
110                         response[n++] = '\n';
111                     done = 1;
112                     break;
113                 }
114                 if (!m)
115                     return status == eIO_Timeout ? SMTP_READTMO : SMTP_READERR;
116                 if (*buf != '\r'  &&  n < max_response_len)
117                     response[n++] = *buf;
118                 assert(status == eIO_Success);
119             } while (*buf != '\n');
120 
121             /* At least '\n' should sit in the buffer */
122             assert(n);
123             if (done)
124                 response[n - 1] = '\0';
125             else if (n < max_response_len)
126                 response[n] = ' ';
127         } else {
128             *response = '\0';
129             assert(done);
130             break;
131         }
132     } while (!done);
133 
134     assert(code > 0);
135     return code;
136 }
137 
138 
139 static int/*bool*/ s_SockReadResponse(SOCK sock, int code, int alt_code,
140                                       char* buf, size_t buf_size)
141 {
142     int c = s_SockRead(sock, buf, buf_size);
143     if (c <= 0) {
144         const char* message = 0;
145         switch (c) {
146         case SMTP_READERR:
147             message = "Read error";
148             break;
149         case SMTP_READTMO:
150             message = "Read timed out";
151             break;
152         case SMTP_RESPERR:
153             message = "Error reading response prefix";
154             break;
155         case SMTP_NOCODE:
156             message = "No response code detected";
157             break;
158         case SMTP_BADCODE:
159             message = "Response code doesn't match in lines";
160             break;
161         case SMTP_BADRESP:
162             message = "Malformed response";
163             break;
164         default:
165             message = "Unknown error";
166             assert(0);
167             break;
168         }
169         assert(message);
170         strncpy0(buf, message, buf_size - 1);
171     } else if (c == code  ||  (alt_code  &&  c == alt_code))
172         return 1/*success*/;
173     return 0/*failure*/;
174 }
175 
176 
177 static int/*bool*/ s_SockWrite(SOCK sock, const char* buf, size_t len)
178 {
179     size_t n;
180 
181     if (!len)
182         len = strlen(buf);
183     if (SOCK_Write(sock, buf, len, &n, eIO_WritePersist) == eIO_Success) {
184         assert(n == len);
185         return 1/*success*/;
186     }
187     return 0/*failure*/;
188 }
189 
190 
191 static void s_MakeFrom(char* buf, size_t size)
192 {
193     size_t len;
194 
195     if (!CORE_GetUsername(buf, size)  ||  !*buf)
196         strncpy0(buf, "anonymous", size - 1);
197     len = strlen(buf);
198     size -= len;
199     if (size-- > 1) {
200         buf += len;
201         *buf++ = '@';
202         if ((!SOCK_gethostbyaddr(0, buf, size)  ||  !strchr(buf, '.'))
203             &&  SOCK_gethostname(buf, size) != 0) {
204             *--buf = '\0';
205         }
206     }
207 }
208 
209 
210 SSendMailInfo* SendMailInfo_Init(SSendMailInfo* info)
211 {
212     if (info) {
213         info->magic_number    = MX_MAGIC_NUMBER;
214         info->cc              = 0;
215         info->bcc             = 0;
216         s_MakeFrom(info->from, sizeof(info->from));
217         info->header          = 0;
218         info->body_size       = 0;
219         info->mx_host         = "mailgw.ncbi.nlm.nih.gov";
220         info->mx_port         = 25;
221         info->mx_timeout.sec  = 120;
222         info->mx_timeout.usec = 0;
223         info->mx_options      = 0;
224     }
225     return info;
226 }
227 
228 
229 extern const char* CORE_SendMail(const char* to,
230                                  const char* subject,
231                                  const char* body)
232 {
233     return CORE_SendMailEx(to, subject, body, 0);
234 }
235 
236 
237 /* In two macros below the smartest (or, weak-minded?) Sun
238  * C compiler warns about unreachable end-of-loop condition
239  * (well, it thinks "a condition" is there, dumb!).
240  */
241 
242 #define SENDMAIL_RETURN(subcode, reason)                                \
243     do {                                                                \
244         if (sock) {                                                     \
245             SOCK_Close(sock);                                           \
246             sock = 0;                                                   \
247         }                                                               \
248         CORE_LOGF_X(subcode, eLOG_Error, ("[SendMail]  %s", reason));   \
249         if (!sock)                                                      \
250             return reason;                                              \
251         /*NOTREACHED*/                                                  \
252     } while (0)
253 
254 #define SENDMAIL_RETURN2(subcode, reason, explanation)                  \
255     do {                                                                \
256         if (sock) {                                                     \
257             SOCK_Close(sock);                                           \
258             sock = 0;                                                   \
259         }                                                               \
260         CORE_LOGF_X(subcode, eLOG_Error,                                \
261                     ("[SendMail]  %s: %s", reason, explanation));       \
262         if (!sock)                                                      \
263             return reason;                                              \
264         /*NOTREACHED*/                                                  \
265     } while (0)
266 
267 
268 static const char* s_SendRcpt(SOCK sock, const char* to,
269                               char buf[], size_t buf_size,
270                               const char what[],
271                               const char write_error[],
272                               const char proto_error[])
273 {
274     char c;
275     while ((c = *to++) != 0) {
276         char   quote = 0;
277         size_t k = 0;
278         if (isspace((unsigned char) c))
279             continue;
280         while (k < buf_size) {
281             if (quote) {
282                 if (c == quote)
283                     quote = 0;
284             } else if (c == '"'  ||  c == '<') {
285                 quote = c == '<' ? '>' : c;
286             } else if (c == ',')
287                 break;
288             buf[k++] = c == '\t' ? ' ' : c;
289             if (!(c = *to++))
290                 break;
291             if (isspace((unsigned char) c)) {
292                 while (isspace((unsigned char)(*to)))
293                     to++;
294             }
295         }
296         if (k >= buf_size)
297             SENDMAIL_RETURN(3, "Recipient address is too long");
298         buf[k] = '\0'/*just in case*/;
299         if (quote) {
300             CORE_LOGF_X(1, eLOG_Warning,
301                         ("[SendMail]  Unbalanced delimiters in"
302                          " recipient %s for %s: \"%c\" expected",
303                          buf, what, quote));
304         }
305         if (!s_SockWrite(sock, "RCPT TO: <", 10)  ||
306             !s_SockWrite(sock, buf, k)            ||
307             !s_SockWrite(sock, ">" MX_CRLF, sizeof(MX_CRLF))) {
308             SENDMAIL_RETURN(4, write_error);
309         }
310         if (!s_SockReadResponse(sock, 250, 251, buf, buf_size))
311             SENDMAIL_RETURN2(5, proto_error, buf);
312         if (!c)
313             break;
314     }
315     return 0;
316 }
317 
318 
319 static size_t s_FromSize(const SSendMailInfo* info)
320 {
321     const char* at, *dot;
322     size_t len = strlen(info->from);
323 
324     if (!*info->from  ||  !(info->mx_options & fSendMail_StripNonFQDNHost))
325         return len;
326     if (!(at = (const char*) memchr(info->from, '@', len))
327         ||  at == info->from + len - 1) {
328         return len - 1;
329     }
330     if (!(dot = (const char*) memchr(at + 1, '.',
331                                      len - (size_t)(at - info->from) - 1))
332         ||  dot == at + 1  ||  dot == info->from + len - 1) {
333         return (size_t)(at - info->from);
334     }
335     return len;
336 }
337 
338 
339 #define SENDMAIL_SENDRCPT(what, list, buffer)                              \
340     s_SendRcpt(sock, list, buffer, sizeof(buffer), what,                   \
341                "Write error in RCPT (" what ") command",                   \
342                "Protocol error in RCPT (" what ") command")
343 
344 #define SENDMAIL_READ_RESPONSE(code, altcode, buffer)                      \
345     s_SockReadResponse(sock, code, altcode, buffer, sizeof(buffer))
346 
347 
348 const char* CORE_SendMailEx(const char*          to,
349                             const char*          subject,
350                             const char*          body,
351                             const SSendMailInfo* uinfo)
352 {
353     static const STimeout zero = {0, 0};
354     const SSendMailInfo* info;
355     SSendMailInfo ainfo;
356     char buffer[1024];
357     SOCK sock = 0;
358 
359     info = uinfo ? uinfo : SendMailInfo_Init(&ainfo);
360     if (info->magic_number != MX_MAGIC_NUMBER)
361         SENDMAIL_RETURN(6, "Invalid magic number");
362 
363     if ((!to         ||  !*to)        &&
364         (!info->cc   ||  !*info->cc)  &&
365         (!info->bcc  ||  !*info->bcc)) {
366         SENDMAIL_RETURN(7, "At least one message recipient must be specified");
367     }
368 
369     /* Open connection to sendmail */
370     if (SOCK_Create(info->mx_host, info->mx_port, &info->mx_timeout, &sock)
371         != eIO_Success) {
372         SENDMAIL_RETURN(8, "Cannot connect to sendmail");
373     }
374     SOCK_SetTimeout(sock, eIO_ReadWrite, &info->mx_timeout);
375 
376     /* Follow the protocol conversation, RFC821 */
377     if (!SENDMAIL_READ_RESPONSE(220, 0, buffer))
378         SENDMAIL_RETURN2(9, "Protocol error in connection init", buffer);
379 
380     if ((!(info->mx_options & fSendMail_StripNonFQDNHost)  ||
381          !SOCK_gethostbyaddr(0, buffer, sizeof(buffer)))  &&
382         SOCK_gethostname(buffer, sizeof(buffer)) != 0) {
383         SENDMAIL_RETURN(10, "Unable to get local host name");
384     }
385     if (!s_SockWrite(sock, "HELO ", 5)  ||
386         !s_SockWrite(sock, buffer, 0)   ||
387         !s_SockWrite(sock, MX_CRLF, sizeof(MX_CRLF)-1)) {
388         SENDMAIL_RETURN(11, "Write error in HELO command");
389     }
390     if (!SENDMAIL_READ_RESPONSE(250, 0, buffer))
391         SENDMAIL_RETURN2(12, "Protocol error in HELO command", buffer);
392 
393     if (!s_SockWrite(sock, "MAIL FROM: <", 12)            ||
394         !s_SockWrite(sock, info->from, s_FromSize(info))  ||
395         !s_SockWrite(sock, ">" MX_CRLF, sizeof(MX_CRLF))) {
396         SENDMAIL_RETURN(13, "Write error in MAIL command");
397     }
398     if (!SENDMAIL_READ_RESPONSE(250, 0, buffer))
399         SENDMAIL_RETURN2(14, "Protocol error in MAIL command", buffer);
400 
401     if (to  &&  *to) {
402         const char* error = SENDMAIL_SENDRCPT("To", to, buffer);
403         if (error)
404             return error;
405     }
406 
407     if (info->cc  &&  *info->cc) {
408         const char* error = SENDMAIL_SENDRCPT("Cc", info->cc, buffer);
409         if (error)
410             return error;
411     }
412 
413     if (info->bcc  &&  *info->bcc) {
414         const char* error = SENDMAIL_SENDRCPT("Bcc", info->bcc, buffer);
415         if (error)
416             return error;
417     }
418 
419     if (!s_SockWrite(sock, "DATA" MX_CRLF, 4 + sizeof(MX_CRLF)-1))
420         SENDMAIL_RETURN(15, "Write error in DATA command");
421     if (!SENDMAIL_READ_RESPONSE(354, 0, buffer))
422         SENDMAIL_RETURN2(16, "Protocol error in DATA command", buffer);
423 
424     if (!(info->mx_options & fSendMail_NoMxHeader)) {
425         /* Follow RFC822 to compose message headers.
426          * NB: Both 'Date:'and 'From:' get added by sendmail automagically.
427          */ 
428         if (!s_SockWrite(sock, "Subject: ", 9)             ||
429             (subject  &&  !s_SockWrite(sock, subject, 0))  ||
430             !s_SockWrite(sock, MX_CRLF, sizeof(MX_CRLF)-1))
431             SENDMAIL_RETURN(17, "Write error in sending subject");
432 
433         if (to  &&  *to) {
434             if (!s_SockWrite(sock, "To: ", 4)              ||
435                 !s_SockWrite(sock, to, 0)                  ||
436                 !s_SockWrite(sock, MX_CRLF, sizeof(MX_CRLF)-1))
437                 SENDMAIL_RETURN(18, "Write error in sending To");
438         }
439 
440         if (info->cc  &&  *info->cc) {
441             if (!s_SockWrite(sock, "Cc: ", 4)              ||
442                 !s_SockWrite(sock, info->cc, 0)            ||
443                 !s_SockWrite(sock, MX_CRLF, sizeof(MX_CRLF)-1))
444                 SENDMAIL_RETURN(19, "Write error in sending Cc");
445         }
446     } else if (subject  &&  *subject)
447         CORE_LOG_X(2, eLOG_Warning,
448                    "[SendMail]  Subject ignored in as-is messages");
449 
450     if (!s_SockWrite(sock, "X-Mailer: CORE_SendMail (NCBI "
451                      NCBI_SENDMAIL_TOOLKIT " Toolkit)" MX_CRLF, 0)) {
452         SENDMAIL_RETURN(20, "Write error in sending mailer information");
453     }
454 
455     assert(sizeof(buffer) > sizeof(MX_CRLF)  &&  sizeof(MX_CRLF) >= 3);
456 
457     if (info->header  &&  *info->header) {
458         size_t n = 0, m = strlen(info->header);
459         int/*bool*/ newline = 0/*false*/;
460         while (n < m) {
461             size_t k = 0;
462             if (SOCK_Wait(sock, eIO_Read, &zero) != eIO_Timeout)
463                 break;
464             while (k < sizeof(buffer) - sizeof(MX_CRLF)) {
465                 if (info->header[n] == '\n') {
466                     memcpy(&buffer[k], MX_CRLF, sizeof(MX_CRLF)-1);
467                     k += sizeof(MX_CRLF)-1;
468                     newline = 1/*true*/;
469                 } else {
470                     if (info->header[n] != '\r'  ||  info->header[n+1] != '\n')
471                         buffer[k++] = info->header[n];
472                     newline = 0/*false*/;
473                 }
474                 if (++n >= m)
475                     break;
476             }
477             buffer[k] = '\0'/*just in case*/;
478             if (!s_SockWrite(sock, buffer, k))
479                 SENDMAIL_RETURN(21, "Write error while sending custom header");
480         }
481         if (n < m)
482             SENDMAIL_RETURN(22, "Header write error");
483         if (!newline  &&  !s_SockWrite(sock, MX_CRLF, sizeof(MX_CRLF)-1))
484             SENDMAIL_RETURN(23, "Write error while finalizing custom header");
485     }
486 
487     if (body) {
488         size_t n = 0, m = info->body_size ? info->body_size : strlen(body);
489         int/*bool*/ newline = 0/*false*/;
490         if (!(info->mx_options & fSendMail_NoMxHeader)  &&  m) {
491             if (!s_SockWrite(sock, MX_CRLF, sizeof(MX_CRLF)-1))
492                 SENDMAIL_RETURN(24, "Write error in message body delimiter");
493         }
494         while (n < m) {
495             size_t k = 0;
496             if (SOCK_Wait(sock, eIO_Read, &zero) != eIO_Timeout)
497                 break;
498             while (k < sizeof(buffer) - sizeof(MX_CRLF)) {
499                 if (body[n] == '\n') {
500                     memcpy(&buffer[k], MX_CRLF, sizeof(MX_CRLF)-1);
501                     k += sizeof(MX_CRLF)-1;
502                     newline = 1/*true*/;
503                 } else {
504                     if (body[n] != '\r'  ||  (n+1 < m  &&  body[n+1] != '\n')){
505                         if (body[n] == '.'  &&  (newline  ||  !n)) {
506                             buffer[k++] = '.';
507                             buffer[k++] = '.';
508                         } else
509                             buffer[k++] = body[n];
510                     }
511                     newline = 0/*false*/;
512                 }
513                 if (++n >= m)
514                     break;
515             }
516             buffer[k] = '\0'/*just in case*/;
517             if (!s_SockWrite(sock, buffer, k))
518                 SENDMAIL_RETURN(25, "Write error while sending message body");
519         }
520         if (n < m)
521             SENDMAIL_RETURN(26, "Body write error");
522         if ((!newline  &&  m  &&  !s_SockWrite(sock,MX_CRLF,sizeof(MX_CRLF)-1))
523             ||  !s_SockWrite(sock, "." MX_CRLF, sizeof(MX_CRLF))) {
524             SENDMAIL_RETURN(27, "Write error while finalizing message body");
525         }
526     } else if (!s_SockWrite(sock, "." MX_CRLF, sizeof(MX_CRLF)))
527         SENDMAIL_RETURN(28, "Write error while finalizing message");
528 
529     if (!SENDMAIL_READ_RESPONSE(250, 0, buffer))
530         SENDMAIL_RETURN2(29, "Protocol error in sending message", buffer);
531 
532     if (!s_SockWrite(sock, "QUIT" MX_CRLF, 4 + sizeof(MX_CRLF)-1))
533         SENDMAIL_RETURN(30, "Write error in QUIT command");
534     if (!SENDMAIL_READ_RESPONSE(221, 0, buffer))
535         SENDMAIL_RETURN2(31, "Protocol error in QUIT command", buffer);
536 
537     SOCK_Close(sock);
538     return 0;
539 }
540 
541 #undef SENDMAIL_READ_RESPONSE
542 #undef SENDMAIL_SENDRCPT
543 #undef SENDMAIL_RETURN2
544 #undef SENDMAIL_RETURN
545 

source navigation ]   [ diff markup ]   [ identifier search ]   [ freetext search ]   [ file search ]  

This page was automatically generated by the LXR engine.
Visit the LXR main site for more information.