|
NCBI Home IEB Home C Toolkit docs C++ Toolkit source browser C Toolkit source browser (2) |
NCBI C Toolkit Cross ReferenceC/connect/ncbi_sendmail.c |
source navigation diff markup identifier search freetext search file search |
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 |
This page was automatically generated by the
LXR engine.
Visit the LXR main site for more information. |