2 * This module is an SMTP and ESMTP implementation for the Citadel system.
3 * It is compliant with all of the following:
5 * RFC 821 - Simple Mail Transfer Protocol
6 * RFC 876 - Survey of SMTP Implementations
7 * RFC 1047 - Duplicate messages and SMTP
8 * RFC 1652 - 8 bit MIME
9 * RFC 1869 - Extended Simple Mail Transfer Protocol
10 * RFC 1870 - SMTP Service Extension for Message Size Declaration
11 * RFC 2033 - Local Mail Transfer Protocol
12 * RFC 2197 - SMTP Service Extension for Command Pipelining
13 * RFC 2476 - Message Submission
14 * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
15 * RFC 2554 - SMTP Service Extension for Authentication
16 * RFC 2821 - Simple Mail Transfer Protocol
17 * RFC 2822 - Internet Message Format
18 * RFC 2920 - SMTP Service Extension for Command Pipelining
20 * Copyright (c) 1998-2015 by the citadel.org team
22 * This program is open source software; you can redistribute it and/or modify
23 * it under the terms of the GNU General Public License version 3.
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
40 #include <sys/types.h>
43 #if TIME_WITH_SYS_TIME
44 # include <sys/time.h>
48 # include <sys/time.h>
57 #include <sys/socket.h>
58 #include <netinet/in.h>
59 #include <arpa/inet.h>
60 #include <libcitadel.h>
63 #include "citserver.h"
70 #include "internet_addressing.h"
73 #include "clientsocket.h"
74 #include "locate_host.h"
75 #include "citadel_dirs.h"
77 #include "ctdl_module.h"
79 #include "smtp_util.h"
80 #include "event_client.h"
81 #include "smtpqueue.h"
82 #include "smtp_clienthandlers.h"
85 #define SMTP_ERROR(WHICH_ERR, ERRSTR) do { \
86 Msg->MyQEntry->Status = WHICH_ERR; \
87 StrBufAppendBufPlain(Msg->MyQEntry->StatusMessage, \
89 StrBufTrim(Msg->MyQEntry->StatusMessage); \
93 #define SMTP_VERROR(WHICH_ERR) do { \
94 Msg->MyQEntry->Status = WHICH_ERR; \
95 StrBufPlain(Msg->MyQEntry->StatusMessage, \
96 ChrPtr(Msg->IO.IOBuf) + 4, \
97 StrLength(Msg->IO.IOBuf) - 4); \
98 StrBufTrim(Msg->MyQEntry->StatusMessage); \
102 #define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(Msg->IO.IOBuf)[0] == WHICH_STATE)
104 #define SMTP_DBG_SEND() \
105 EVS_syslog(LOG_DEBUG, "> %s\n", ChrPtr(Msg->IO.SendBuf.Buf))
107 #define SMTP_DBG_READ() \
108 EVS_syslog(LOG_DEBUG, "< %s\n", ChrPtr(Msg->IO.IOBuf))
111 * if a Read handler wants to skip to a specific part use this macro.
112 * the -1 is here since the auto-forward following has to be taken into account.
114 #define READ_NEXT_STATE(state) Msg->State = state - 1
116 /*****************************************************************************/
117 /* SMTP CLIENT STATE CALLBACKS */
118 /*****************************************************************************/
119 eNextState SMTPC_read_greeting(SmtpOutMsg *Msg)
121 /* Process the SMTP greeting from the server */
122 AsyncIO *IO = &Msg->IO;
124 SetSMTPState(IO, eSTMPsmtp);
126 if (!SMTP_IS_STATE('2')) {
127 if (SMTP_IS_STATE('4'))
135 eNextState SMTPC_send_EHLO(SmtpOutMsg *Msg)
137 AsyncIO *IO = &Msg->IO;
138 /* At this point we know we are talking to a real SMTP server */
140 /* Do a EHLO command. If it fails, try the HELO command. */
141 StrBufPrintf(Msg->IO.SendBuf.Buf, "EHLO %s\r\n", CtdlGetConfigStr("c_fqdn"));
147 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *Msg)
149 AsyncIO *IO = &Msg->IO;
152 if (SMTP_IS_STATE('2')) {
153 READ_NEXT_STATE(eSMTPAuth);
155 if ((Msg->pCurrRelay == NULL) ||
156 (Msg->pCurrRelay->User == NULL))
157 READ_NEXT_STATE(eFROM); /* Skip auth... */
158 if (Msg->pCurrRelay != NULL)
160 if (strstr(ChrPtr(Msg->IO.IOBuf), "LOGIN") != NULL)
162 else if ((Msg->MultiLineBuf != NULL) &&
163 strstr(ChrPtr(Msg->MultiLineBuf), "LOGIN") != NULL)
169 /* else we fall back to 'helo' */
173 eNextState STMPC_send_HELO(SmtpOutMsg *Msg)
175 AsyncIO *IO = &Msg->IO;
176 StrBufPrintf(Msg->IO.SendBuf.Buf, "HELO %s\r\n", CtdlGetConfigStr("c_fqdn"));
182 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *Msg)
184 AsyncIO *IO = &Msg->IO;
187 if (!SMTP_IS_STATE('2'))
189 if (SMTP_IS_STATE('4'))
194 if (Msg->pCurrRelay != NULL)
196 if (strstr(ChrPtr(Msg->IO.IOBuf), "LOGIN") != NULL)
199 if ((Msg->pCurrRelay == NULL) ||
200 (Msg->pCurrRelay->User == NULL))
201 READ_NEXT_STATE(eFROM); /* Skip auth... */
206 eNextState SMTPC_send_auth(SmtpOutMsg *Msg)
208 AsyncIO *IO = &Msg->IO;
212 if ((Msg->pCurrRelay == NULL) ||
213 (Msg->pCurrRelay->User == NULL))
214 READ_NEXT_STATE(eFROM); /* Skip auth, shouldn't even come here!... */
216 /* Do an AUTH command if necessary */
219 StrBufPlain(Msg->IO.SendBuf.Buf,
220 HKEY("AUTH LOGIN\r\n"));
224 sprintf(buf, "%s%c%s%c%s",
225 Msg->pCurrRelay->User, '\0',
226 Msg->pCurrRelay->User, '\0',
227 Msg->pCurrRelay->Pass);
229 size_t len = CtdlEncodeBase64(encoded, buf,
230 strlen(Msg->pCurrRelay->User) * 2 +
231 strlen(Msg->pCurrRelay->Pass) + 2, 0);
233 if (buf[len - 1] == '\n') {
237 StrBufPrintf(Msg->IO.SendBuf.Buf,
247 eNextState SMTPC_read_auth_reply(SmtpOutMsg *Msg)
249 AsyncIO *IO = &Msg->IO;
250 /* Do an AUTH command if necessary */
256 if (!SMTP_IS_STATE('3'))
261 if (!SMTP_IS_STATE('2')) {
262 if (SMTP_IS_STATE('4'))
267 READ_NEXT_STATE(eFROM);
273 eNextState SMTPC_send_authplain_1(SmtpOutMsg *Msg)
275 AsyncIO *IO = &Msg->IO;
281 Msg->pCurrRelay->User);
283 encodedlen = CtdlEncodeBase64(
285 Msg->pCurrRelay->User,
286 strlen(Msg->pCurrRelay->User),
288 if (encoded[encodedlen - 1] == '\n') {
290 encoded[encodedlen] = '\0';
293 StrBufPlain(Msg->IO.SendBuf.Buf,
297 StrBufAppendBufPlain(Msg->IO.SendBuf.Buf,
304 eNextState SMTPC_read_auth_plain_reply_1(SmtpOutMsg *Msg)
306 AsyncIO *IO = &Msg->IO;
307 /* Do an AUTH command if necessary */
311 if (!SMTP_IS_STATE('3'))
317 eNextState SMTPC_send_authplain_2(SmtpOutMsg *Msg)
319 AsyncIO *IO = &Msg->IO;
325 Msg->pCurrRelay->Pass);
327 encodedlen = CtdlEncodeBase64(
329 Msg->pCurrRelay->Pass,
330 strlen(Msg->pCurrRelay->Pass),
333 if (encoded[encodedlen - 1] == '\n') {
335 encoded[encodedlen] = '\0';
338 StrBufPlain(Msg->IO.SendBuf.Buf,
342 StrBufAppendBufPlain(Msg->IO.SendBuf.Buf,
349 eNextState SMTPC_read_auth_plain_reply_2(SmtpOutMsg *Msg)
351 AsyncIO *IO = &Msg->IO;
352 /* Do an AUTH command if necessary */
356 if (!SMTP_IS_STATE('2')) {
357 if (SMTP_IS_STATE('4'))
365 eNextState SMTPC_send_FROM(SmtpOutMsg *Msg)
367 AsyncIO *IO = &Msg->IO;
368 /* previous command succeeded, now try the MAIL FROM: command */
369 StrBufPrintf(Msg->IO.SendBuf.Buf,
370 "MAIL FROM:<%s>\r\n",
377 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *Msg)
379 AsyncIO *IO = &Msg->IO;
382 if (!SMTP_IS_STATE('2')) {
383 if (SMTP_IS_STATE('4'))
392 eNextState SMTPC_send_RCPT(SmtpOutMsg *Msg)
394 AsyncIO *IO = &Msg->IO;
395 /* MAIL succeeded, now try the RCPT To: command */
396 StrBufPrintf(Msg->IO.SendBuf.Buf,
397 "RCPT TO:<%s@%s>\r\n",
405 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *Msg)
407 AsyncIO *IO = &Msg->IO;
410 if (!SMTP_IS_STATE('2')) {
411 if (SMTP_IS_STATE('4'))
419 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *Msg)
421 AsyncIO *IO = &Msg->IO;
422 /* RCPT succeeded, now try the DATA command */
423 StrBufPlain(Msg->IO.SendBuf.Buf,
430 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *Msg)
432 AsyncIO *IO = &Msg->IO;
435 if (!SMTP_IS_STATE('3')) {
436 SetSMTPState(IO, eSTMPfailOne);
437 if (SMTP_IS_STATE('4'))
442 SetSMTPState(IO, eSTMPsmtpdata);
446 eNextState SMTPC_send_data_body(SmtpOutMsg *Msg)
449 /* If we reach this point, the server is expecting data.*/
451 Buf = Msg->IO.SendBuf.Buf;
452 Msg->IO.SendBuf.Buf = Msg->msgtext;
455 * sending the message itself doesn't use this state machine.
456 * so we have to operate it here by ourselves.
463 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *Msg)
467 Buf = Msg->IO.SendBuf.Buf;
468 Msg->IO.SendBuf.Buf = Msg->msgtext;
471 StrBufPlain(Msg->IO.SendBuf.Buf,
478 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *Msg)
480 AsyncIO *IO = &Msg->IO;
483 if (!SMTP_IS_STATE('2')) {
484 if (SMTP_IS_STATE('4'))
490 SetSMTPState(IO, eSTMPsmtpdone);
492 StrBufPlain(Msg->MyQEntry->StatusMessage,
493 &ChrPtr(Msg->IO.RecvBuf.Buf)[4],
494 StrLength(Msg->IO.RecvBuf.Buf) - 4);
495 StrBufTrim(Msg->MyQEntry->StatusMessage);
496 Msg->MyQEntry->Status = 2;
500 eNextState SMTPC_send_QUIT(SmtpOutMsg *Msg)
502 AsyncIO *IO = &Msg->IO;
503 StrBufPlain(Msg->IO.SendBuf.Buf,
510 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *Msg)
512 AsyncIO *IO = &Msg->IO;
515 EVS_syslog(LOG_DEBUG,
516 "delivery to <%s> @ <%s> (%s) succeeded\n",
521 return eTerminateConnection;
524 eNextState SMTPC_read_dummy(SmtpOutMsg *Msg)
529 eNextState SMTPC_send_dummy(SmtpOutMsg *Msg)
534 /*****************************************************************************/
535 /* SMTP CLIENT DISPATCHER */
536 /*****************************************************************************/
537 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
539 SMTPC_read_EHLO_reply,
540 SMTPC_read_HELO_reply,
541 SMTPC_read_auth_reply,
542 SMTPC_read_auth_plain_reply_1,
543 SMTPC_read_auth_plain_reply_2,
544 SMTPC_read_FROM_reply,
545 SMTPC_read_RCPT_reply,
546 SMTPC_read_DATAcmd_reply,
548 SMTPC_read_data_body_reply,
549 SMTPC_read_QUIT_reply
551 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
552 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
556 SMTPC_send_authplain_1,
557 SMTPC_send_authplain_2,
561 SMTPC_send_data_body,
562 SMTPC_send_terminate_data_body,
566 const double SMTP_C_ConnTimeout = 300.; /* wail 1 minute for connections... */
568 const double SMTP_C_ReadTimeouts[eMaxSMTPC] = {
569 300., /* Greeting... */
579 90., /* end of body... */
582 const double SMTP_C_SendTimeouts[eMaxSMTPC] = {
583 90., /* Greeting... */
593 900., /* end of body... */
597 const ConstStr ReadErrors[eMaxSMTPC + 1] = {
598 {HKEY("Connection broken during SMTP conversation")},
599 {HKEY("Connection broken during SMTP EHLO")},
600 {HKEY("Connection broken during SMTP HELO")},
601 {HKEY("Connection broken during SMTP AUTH")},
602 {HKEY("Connection broken during SMTP AUTH PLAIN I")},
603 {HKEY("Connection broken during SMTP AUTH PLAIN II")},
604 {HKEY("Connection broken during SMTP MAIL FROM")},
605 {HKEY("Connection broken during SMTP RCPT")},
606 {HKEY("Connection broken during SMTP DATA")},
607 {HKEY("Connection broken during SMTP message transmit")},
608 {HKEY("Connection broken during SMTP message transmit")},/* quit reply, don't care. */
609 {HKEY("Connection broken during SMTP message transmit")},/* quit reply, don't care. */
610 {HKEY("")}/* quit reply, don't care. */
617 int smtp_resolve_recipients(SmtpOutMsg *Msg)
619 AsyncIO *IO = &Msg->IO;
626 EVNCS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
629 (Msg->MyQEntry == NULL) ||
630 (StrLength(Msg->MyQEntry->Recipient) == 0)) {
634 /* Parse out the host portion of the recipient address */
635 process_rfc822_addr(ChrPtr(Msg->MyQEntry->Recipient),
640 EVNCS_syslog(LOG_DEBUG,
641 "Attempting delivery to <%s> @ <%s> (%s)\n",
646 /* If no envelope_from is supplied, extract one from the message */
647 Msg->envelope_from = ChrPtr(Msg->MyQItem->EnvelopeFrom);
648 if ( (Msg->envelope_from == NULL) ||
649 (IsEmptyStr(Msg->envelope_from)) ) {
650 Msg->mailfrom[0] = '\0';
652 ptr = ChrPtr(Msg->msgtext);
654 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0)
658 if (!strncasecmp(buf, "From:", 5))
660 safestrncpy(Msg->mailfrom,
662 sizeof Msg->mailfrom);
664 striplt(Msg->mailfrom);
665 for (i=0; Msg->mailfrom[i]; ++i) {
666 if (!isprint(Msg->mailfrom[i]))
668 strcpy(&Msg->mailfrom[i],
669 &Msg->mailfrom[i+1]);
674 /* Strip out parenthesized names */
678 !IsEmptyStr(Msg->mailfrom + i);
681 if (Msg->mailfrom[i] == '(') lp = i;
682 if (Msg->mailfrom[i] == ')') rp = i;
686 strcpy(&Msg->mailfrom[lp-1],
687 &Msg->mailfrom[rp+1]);
690 /* Prefer brokketized names */
694 !IsEmptyStr(Msg->mailfrom + i);
697 if (Msg->mailfrom[i] == '<') lp = i;
698 if (Msg->mailfrom[i] == '>') rp = i;
700 if ( (lp>=0) && (rp>lp) ) {
701 Msg->mailfrom[rp] = 0;
702 memmove(Msg->mailfrom,
703 &Msg->mailfrom[lp + 1],
709 } while (scan_done == 0);
710 if (IsEmptyStr(Msg->mailfrom))
711 strcpy(Msg->mailfrom, "someone@somewhere.org");
713 stripallbut(Msg->mailfrom, '<', '>');
714 Msg->envelope_from = Msg->mailfrom;