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 syslog(LOG_DEBUG, "> %s\n", ChrPtr(Msg->IO.SendBuf.Buf))
107 #define SMTP_DBG_READ() \
108 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 /* At this point we know we are talking to a real SMTP server */
139 /* Do a EHLO command. If it fails, try the HELO command. */
140 StrBufPrintf(Msg->IO.SendBuf.Buf, "EHLO %s\r\n", CtdlGetConfigStr("c_fqdn"));
146 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *Msg)
150 if (SMTP_IS_STATE('2')) {
151 READ_NEXT_STATE(eSMTPAuth);
153 if ((Msg->pCurrRelay == NULL) ||
154 (Msg->pCurrRelay->User == NULL))
155 READ_NEXT_STATE(eFROM); /* Skip auth... */
156 if (Msg->pCurrRelay != NULL)
158 if (strstr(ChrPtr(Msg->IO.IOBuf), "LOGIN") != NULL)
160 else if ((Msg->MultiLineBuf != NULL) &&
161 strstr(ChrPtr(Msg->MultiLineBuf), "LOGIN") != NULL)
167 /* else we fall back to 'helo' */
171 eNextState STMPC_send_HELO(SmtpOutMsg *Msg)
173 StrBufPrintf(Msg->IO.SendBuf.Buf, "HELO %s\r\n", CtdlGetConfigStr("c_fqdn"));
179 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *Msg)
183 if (!SMTP_IS_STATE('2'))
185 if (SMTP_IS_STATE('4'))
190 if (Msg->pCurrRelay != NULL)
192 if (strstr(ChrPtr(Msg->IO.IOBuf), "LOGIN") != NULL)
195 if ((Msg->pCurrRelay == NULL) ||
196 (Msg->pCurrRelay->User == NULL))
197 READ_NEXT_STATE(eFROM); /* Skip auth... */
202 eNextState SMTPC_send_auth(SmtpOutMsg *Msg)
207 if ((Msg->pCurrRelay == NULL) ||
208 (Msg->pCurrRelay->User == NULL))
209 READ_NEXT_STATE(eFROM); /* Skip auth, shouldn't even come here!... */
211 /* Do an AUTH command if necessary */
214 StrBufPlain(Msg->IO.SendBuf.Buf,
215 HKEY("AUTH LOGIN\r\n"));
219 sprintf(buf, "%s%c%s%c%s",
220 Msg->pCurrRelay->User, '\0',
221 Msg->pCurrRelay->User, '\0',
222 Msg->pCurrRelay->Pass);
224 size_t len = CtdlEncodeBase64(encoded, buf,
225 strlen(Msg->pCurrRelay->User) * 2 +
226 strlen(Msg->pCurrRelay->Pass) + 2, 0);
228 if (buf[len - 1] == '\n') {
232 StrBufPrintf(Msg->IO.SendBuf.Buf,
242 eNextState SMTPC_read_auth_reply(SmtpOutMsg *Msg)
244 /* Do an AUTH command if necessary */
250 if (!SMTP_IS_STATE('3'))
255 if (!SMTP_IS_STATE('2')) {
256 if (SMTP_IS_STATE('4'))
261 READ_NEXT_STATE(eFROM);
267 eNextState SMTPC_send_authplain_1(SmtpOutMsg *Msg)
274 Msg->pCurrRelay->User);
276 encodedlen = CtdlEncodeBase64(
278 Msg->pCurrRelay->User,
279 strlen(Msg->pCurrRelay->User),
281 if (encoded[encodedlen - 1] == '\n') {
283 encoded[encodedlen] = '\0';
286 StrBufPlain(Msg->IO.SendBuf.Buf,
290 StrBufAppendBufPlain(Msg->IO.SendBuf.Buf,
297 eNextState SMTPC_read_auth_plain_reply_1(SmtpOutMsg *Msg)
299 /* Do an AUTH command if necessary */
303 if (!SMTP_IS_STATE('3'))
309 eNextState SMTPC_send_authplain_2(SmtpOutMsg *Msg)
316 Msg->pCurrRelay->Pass);
318 encodedlen = CtdlEncodeBase64(
320 Msg->pCurrRelay->Pass,
321 strlen(Msg->pCurrRelay->Pass),
324 if (encoded[encodedlen - 1] == '\n') {
326 encoded[encodedlen] = '\0';
329 StrBufPlain(Msg->IO.SendBuf.Buf,
333 StrBufAppendBufPlain(Msg->IO.SendBuf.Buf,
340 eNextState SMTPC_read_auth_plain_reply_2(SmtpOutMsg *Msg)
342 /* Do an AUTH command if necessary */
346 if (!SMTP_IS_STATE('2')) {
347 if (SMTP_IS_STATE('4'))
355 eNextState SMTPC_send_FROM(SmtpOutMsg *Msg)
357 /* previous command succeeded, now try the MAIL FROM: command */
358 StrBufPrintf(Msg->IO.SendBuf.Buf,
359 "MAIL FROM:<%s>\r\n",
366 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *Msg)
370 if (!SMTP_IS_STATE('2')) {
371 if (SMTP_IS_STATE('4'))
380 eNextState SMTPC_send_RCPT(SmtpOutMsg *Msg)
382 /* MAIL succeeded, now try the RCPT To: command */
383 StrBufPrintf(Msg->IO.SendBuf.Buf,
384 "RCPT TO:<%s@%s>\r\n",
392 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *Msg)
396 if (!SMTP_IS_STATE('2')) {
397 if (SMTP_IS_STATE('4'))
405 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *Msg)
407 /* RCPT succeeded, now try the DATA command */
408 StrBufPlain(Msg->IO.SendBuf.Buf,
415 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *Msg)
417 AsyncIO *IO = &Msg->IO;
420 if (!SMTP_IS_STATE('3')) {
421 SetSMTPState(IO, eSTMPfailOne);
422 if (SMTP_IS_STATE('4'))
427 SetSMTPState(IO, eSTMPsmtpdata);
431 eNextState SMTPC_send_data_body(SmtpOutMsg *Msg)
434 /* If we reach this point, the server is expecting data.*/
436 Buf = Msg->IO.SendBuf.Buf;
437 Msg->IO.SendBuf.Buf = Msg->msgtext;
440 * sending the message itself doesn't use this state machine.
441 * so we have to operate it here by ourselves.
448 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *Msg)
452 Buf = Msg->IO.SendBuf.Buf;
453 Msg->IO.SendBuf.Buf = Msg->msgtext;
456 StrBufPlain(Msg->IO.SendBuf.Buf,
463 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *Msg)
465 AsyncIO *IO = &Msg->IO;
468 if (!SMTP_IS_STATE('2')) {
469 if (SMTP_IS_STATE('4'))
475 SetSMTPState(IO, eSTMPsmtpdone);
477 StrBufPlain(Msg->MyQEntry->StatusMessage,
478 &ChrPtr(Msg->IO.RecvBuf.Buf)[4],
479 StrLength(Msg->IO.RecvBuf.Buf) - 4);
480 StrBufTrim(Msg->MyQEntry->StatusMessage);
481 Msg->MyQEntry->Status = 2;
485 eNextState SMTPC_send_QUIT(SmtpOutMsg *Msg)
487 StrBufPlain(Msg->IO.SendBuf.Buf,
494 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *Msg)
499 "delivery to <%s> @ <%s> (%s) succeeded\n",
504 return eTerminateConnection;
507 eNextState SMTPC_read_dummy(SmtpOutMsg *Msg)
512 eNextState SMTPC_send_dummy(SmtpOutMsg *Msg)
517 /*****************************************************************************/
518 /* SMTP CLIENT DISPATCHER */
519 /*****************************************************************************/
520 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
522 SMTPC_read_EHLO_reply,
523 SMTPC_read_HELO_reply,
524 SMTPC_read_auth_reply,
525 SMTPC_read_auth_plain_reply_1,
526 SMTPC_read_auth_plain_reply_2,
527 SMTPC_read_FROM_reply,
528 SMTPC_read_RCPT_reply,
529 SMTPC_read_DATAcmd_reply,
531 SMTPC_read_data_body_reply,
532 SMTPC_read_QUIT_reply
534 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
535 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
539 SMTPC_send_authplain_1,
540 SMTPC_send_authplain_2,
544 SMTPC_send_data_body,
545 SMTPC_send_terminate_data_body,
549 const double SMTP_C_ConnTimeout = 300.; /* wail 1 minute for connections... */
551 const double SMTP_C_ReadTimeouts[eMaxSMTPC] = {
552 300., /* Greeting... */
562 90., /* end of body... */
565 const double SMTP_C_SendTimeouts[eMaxSMTPC] = {
566 90., /* Greeting... */
576 900., /* end of body... */
580 const ConstStr ReadErrors[eMaxSMTPC + 1] = {
581 {HKEY("Connection broken during SMTP conversation")},
582 {HKEY("Connection broken during SMTP EHLO")},
583 {HKEY("Connection broken during SMTP HELO")},
584 {HKEY("Connection broken during SMTP AUTH")},
585 {HKEY("Connection broken during SMTP AUTH PLAIN I")},
586 {HKEY("Connection broken during SMTP AUTH PLAIN II")},
587 {HKEY("Connection broken during SMTP MAIL FROM")},
588 {HKEY("Connection broken during SMTP RCPT")},
589 {HKEY("Connection broken during SMTP DATA")},
590 {HKEY("Connection broken during SMTP message transmit")},
591 {HKEY("Connection broken during SMTP message transmit")},/* quit reply, don't care. */
592 {HKEY("Connection broken during SMTP message transmit")},/* quit reply, don't care. */
593 {HKEY("")}/* quit reply, don't care. */
600 int smtp_resolve_recipients(SmtpOutMsg *Msg)
608 syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
611 (Msg->MyQEntry == NULL) ||
612 (StrLength(Msg->MyQEntry->Recipient) == 0)) {
616 /* Parse out the host portion of the recipient address */
617 process_rfc822_addr(ChrPtr(Msg->MyQEntry->Recipient),
623 "Attempting delivery to <%s> @ <%s> (%s)\n",
628 /* If no envelope_from is supplied, extract one from the message */
629 Msg->envelope_from = ChrPtr(Msg->MyQItem->EnvelopeFrom);
630 if ( (Msg->envelope_from == NULL) ||
631 (IsEmptyStr(Msg->envelope_from)) ) {
632 Msg->mailfrom[0] = '\0';
634 ptr = ChrPtr(Msg->msgtext);
636 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0)
640 if (!strncasecmp(buf, "From:", 5))
642 safestrncpy(Msg->mailfrom,
644 sizeof Msg->mailfrom);
646 striplt(Msg->mailfrom);
647 for (i=0; Msg->mailfrom[i]; ++i) {
648 if (!isprint(Msg->mailfrom[i]))
650 strcpy(&Msg->mailfrom[i],
651 &Msg->mailfrom[i+1]);
656 /* Strip out parenthesized names */
660 !IsEmptyStr(Msg->mailfrom + i);
663 if (Msg->mailfrom[i] == '(') lp = i;
664 if (Msg->mailfrom[i] == ')') rp = i;
668 strcpy(&Msg->mailfrom[lp-1],
669 &Msg->mailfrom[rp+1]);
672 /* Prefer brokketized names */
676 !IsEmptyStr(Msg->mailfrom + i);
679 if (Msg->mailfrom[i] == '<') lp = i;
680 if (Msg->mailfrom[i] == '>') rp = i;
682 if ( (lp>=0) && (rp>lp) ) {
683 Msg->mailfrom[rp] = 0;
684 memmove(Msg->mailfrom,
685 &Msg->mailfrom[lp + 1],
691 } while (scan_done == 0);
692 if (IsEmptyStr(Msg->mailfrom))
693 strcpy(Msg->mailfrom, "someone@somewhere.org");
695 stripallbut(Msg->mailfrom, '<', '>');
696 Msg->envelope_from = Msg->mailfrom;