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 * The VRFY and EXPN commands have been removed from this implementation
21 * because nobody uses these commands anymore, except for spammers.
23 * Copyright (c) 1998-2009 by the citadel.org team
25 * This program is free software; you can redistribute it and/or modify
26 * it under the terms of the GNU General Public License as published by
27 * the Free Software Foundation; either version 3 of the License, or
28 * (at your option) any later version.
30 * This program is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 * GNU General Public License for more details.
35 * You should have received a copy of the GNU General Public License
36 * along with this program; if not, write to the Free Software
37 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
49 #include <sys/types.h>
52 #if TIME_WITH_SYS_TIME
53 # include <sys/time.h>
57 # include <sys/time.h>
66 #include <sys/socket.h>
67 #include <netinet/in.h>
68 #include <arpa/inet.h>
69 #include <libcitadel.h>
72 #include "citserver.h"
79 #include "internet_addressing.h"
82 #include "clientsocket.h"
83 #include "locate_host.h"
84 #include "citadel_dirs.h"
86 #include "ctdl_module.h"
88 #include "smtp_util.h"
89 #include "event_client.h"
90 #include "smtpqueue.h"
92 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
93 /*****************************************************************************/
94 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
95 /*****************************************************************************/
97 typedef enum _eSMTP_C_States {
111 const long SMTP_C_ConnTimeout = 300; /* wail 5 minutes for connections... */
112 const long SMTP_C_ReadTimeouts[eMaxSMTPC] = {
113 90, /* Greeting... */
121 900, /* end of body... */
125 const long SMTP_C_SendTimeouts[eMaxSMTPC] = {
128 const char *ReadErrors[eMaxSMTPC] = {
129 "Connection broken during SMTP conversation",
130 "Connection broken during SMTP EHLO",
131 "Connection broken during SMTP HELO",
132 "Connection broken during SMTP AUTH",
133 "Connection broken during SMTP MAIL FROM",
134 "Connection broken during SMTP RCPT",
135 "Connection broken during SMTP DATA",
136 "Connection broken during SMTP message transmit",
137 ""/* quit reply, don't care. */
141 typedef struct _stmp_out_msg {
142 MailQEntry *MyQEntry;
147 eSMTP_C_States State;
149 struct ares_mx_reply *AllMX;
150 struct ares_mx_reply *CurrMX;
154 struct hostent *OneMX;
166 void DeleteSmtpOutMsg(void *v)
170 ares_free_data(Msg->AllMX);
172 FreeStrBuf(&Msg->msgtext);
173 FreeAsyncIOContents(&Msg->IO);
177 eNextState SMTP_C_Timeout(void *Data);
178 eNextState SMTP_C_ConnFail(void *Data);
179 eNextState SMTP_C_DispatchReadDone(void *Data);
180 eNextState SMTP_C_DispatchWriteDone(void *Data);
181 eNextState SMTP_C_Terminate(void *Data);
182 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO);
184 typedef eNextState (*SMTPReadHandler)(SmtpOutMsg *Msg);
185 typedef eNextState (*SMTPSendHandler)(SmtpOutMsg *Msg);
188 #define SMTP_ERROR(WHICH_ERR, ERRSTR) do {\
189 SendMsg->MyQEntry->Status = WHICH_ERR; \
190 StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, HKEY(ERRSTR), 0); \
194 #define SMTP_VERROR(WHICH_ERR) do {\
195 SendMsg->MyQEntry->Status = WHICH_ERR; \
196 StrBufPlain(SendMsg->MyQEntry->StatusMessage, \
197 ChrPtr(SendMsg->IO.IOBuf) + 4, \
198 StrLength(SendMsg->IO.IOBuf) - 4); \
202 #define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(SendMsg->IO.IOBuf)[0] == WHICH_STATE)
204 #define SMTP_DBG_SEND() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: > %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
205 #define SMTP_DBG_READ() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: < %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
208 void FinalizeMessageSend(SmtpOutMsg *Msg)
210 if (DecreaseQReference(Msg->MyQItem))
215 nRemain = CountActiveQueueEntries(Msg->MyQItem);
217 MsgData = SerializeQueueItem(Msg->MyQItem);
219 * Uncompleted delivery instructions remain, so delete the old
220 * instructions and replace with the updated ones.
222 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, "");
223 smtpq_do_bounce(Msg->MyQItem,
226 struct CtdlMessage *msg;
227 msg = malloc(sizeof(struct CtdlMessage));
228 memset(msg, 0, sizeof(struct CtdlMessage));
229 msg->cm_magic = CTDLMESSAGE_MAGIC;
230 msg->cm_anon_type = MES_NORMAL;
231 msg->cm_format_type = FMT_RFC822;
232 msg->cm_fields['M'] = SmashStrBuf(&MsgData);
233 /* Generate 'bounce' messages */
234 smtp_do_bounce(msg->cm_fields['M'],
237 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
238 CtdlFreeMessage(msg);
241 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->MessageID, 1, "");
243 RemoveQItem(Msg->MyQItem);
247 DeleteSmtpOutMsg(Msg);
253 void get_one_mx_host_ip_done(void *Ctx,
256 struct hostent *hostent)
259 SmtpOutMsg *SendMsg = IO->Data;
260 if ((status == ARES_SUCCESS) && (hostent != NULL) ) {
261 CtdlLogPrintf(CTDL_DEBUG,
262 "SMTP client[%ld]: connecting to %s [ip]: %d ...\n",
267 SendMsg->MyQEntry->Status = 5;
268 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
269 "Timeout while connecting %s",
272 SendMsg->IO.HEnt = hostent;
273 InitEventIO(IO, SendMsg,
274 SMTP_C_DispatchReadDone,
275 SMTP_C_DispatchWriteDone,
279 SMTP_C_ReadServerStatus,
281 SMTP_C_ReadTimeouts[0],
287 const unsigned short DefaultMXPort = 25;
288 void get_one_mx_host_ip(SmtpOutMsg *SendMsg)
293 SendMsg->IO.dport = DefaultMXPort;
297 *SendMsg->mx_user = '\0';
298 *SendMsg->mx_pass = '\0';
299 if (num_tokens(buf, '@') > 1) {
300 strcpy (SendMsg->mx_user, buf);
301 endpart = strrchr(SendMsg->mx_user, '@');
303 strcpy (SendMsg->mx_host, endpart + 1);
304 endpart = strrchr(SendMsg->mx_user, ':');
305 if (endpart != NULL) {
306 strcpy(SendMsg->mx_pass, endpart+1);
310 endpart = strrchr(SendMsg->mx_host, ':');
313 strcpy(SendMsg->mx_port, endpart + 1);
318 SendMsg->mx_host = SendMsg->CurrMX->host;
319 SendMsg->CurrMX = SendMsg->CurrMX->next;
321 CtdlLogPrintf(CTDL_DEBUG,
322 "SMTP client[%ld]: looking up %s : %d ...\n",
326 ares_gethostbyname(SendMsg->IO.DNSChannel,
328 AF_INET6, /* it falls back to ipv4 in doubt... */
329 get_one_mx_host_ip_done,
334 eNextState smtp_resolve_mx_done(void *data)
337 SmtpOutMsg * SendMsg = IO->Data;
339 SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
340 SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
341 SendMsg->IO.IOBuf = NewStrBuf();
342 SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
344 SendMsg->CurrMX = SendMsg->AllMX = IO->VParsedDNSReply;
345 //// TODO: should we remove the current ares context???
346 get_one_mx_host_ip(SendMsg);
351 int resolve_mx_records(void *Ctx)
353 SmtpOutMsg * SendMsg = Ctx;
355 if (!QueueQuery(ns_t_mx,
358 smtp_resolve_mx_done))
360 SendMsg->MyQEntry->Status = 5;
361 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
362 "No MX hosts found for <%s>", SendMsg->node);
363 return 0; ///////TODO: abort!
369 int smtp_resolve_recipients(SmtpOutMsg *SendMsg)
377 if ((SendMsg==NULL) ||
378 (SendMsg->MyQEntry == NULL) ||
379 (StrLength(SendMsg->MyQEntry->Recipient) == 0)) {
383 /* Parse out the host portion of the recipient address */
384 process_rfc822_addr(ChrPtr(SendMsg->MyQEntry->Recipient),
389 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n",
390 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
391 /* If no envelope_from is supplied, extract one from the message */
392 if ( (SendMsg->envelope_from == NULL) ||
393 (IsEmptyStr(SendMsg->envelope_from)) ) {
394 SendMsg->mailfrom[0] = '\0';
396 ptr = ChrPtr(SendMsg->msgtext);
398 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
401 if (!strncasecmp(buf, "From:", 5)) {
402 safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom);
403 striplt(SendMsg->mailfrom);
404 for (i=0; SendMsg->mailfrom[i]; ++i) {
405 if (!isprint(SendMsg->mailfrom[i])) {
406 strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]);
411 /* Strip out parenthesized names */
414 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
415 if (SendMsg->mailfrom[i] == '(') lp = i;
416 if (SendMsg->mailfrom[i] == ')') rp = i;
418 if ((lp>0)&&(rp>lp)) {
419 strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]);
422 /* Prefer brokketized names */
425 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
426 if (SendMsg->mailfrom[i] == '<') lp = i;
427 if (SendMsg->mailfrom[i] == '>') rp = i;
429 if ( (lp>=0) && (rp>lp) ) {
430 SendMsg->mailfrom[rp] = 0;
431 memmove(SendMsg->mailfrom,
432 &SendMsg->mailfrom[lp + 1],
438 } while (scan_done == 0);
439 if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org");
440 stripallbut(SendMsg->mailfrom, '<', '>');
441 SendMsg->envelope_from = SendMsg->mailfrom;
449 void smtp_try(OneQueItem *MyQItem,
450 MailQEntry *MyQEntry,
452 int KeepMsgText, /* KeepMsgText allows us to use MsgText as ours. */
455 SmtpOutMsg * SendMsg;
457 SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
458 memset(SendMsg, 0, sizeof(SmtpOutMsg));
459 SendMsg->IO.sock = (-1);
460 SendMsg->n = MsgCount++;
461 SendMsg->MyQEntry = MyQEntry;
462 SendMsg->MyQItem = MyQItem;
463 SendMsg->IO.Data = SendMsg;
465 SendMsg->msgtext = MsgText;
467 SendMsg->msgtext = NewStrBufDup(MsgText);
469 if (smtp_resolve_recipients(SendMsg)) {
470 QueueEventContext(SendMsg,
475 if ((SendMsg==NULL) ||
476 (SendMsg->MyQEntry == NULL)) {
477 SendMsg->MyQEntry->Status = 5;
478 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
479 HKEY("Invalid Recipient!"));
481 FinalizeMessageSend(SendMsg);
489 /*****************************************************************************/
490 /* SMTP CLIENT STATE CALLBACKS */
491 /*****************************************************************************/
492 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
494 /* Process the SMTP greeting from the server */
497 if (!SMTP_IS_STATE('2')) {
498 if (SMTP_IS_STATE('4'))
506 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
508 /* At this point we know we are talking to a real SMTP server */
510 /* Do a EHLO command. If it fails, try the HELO command. */
511 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
512 "EHLO %s\r\n", config.c_fqdn);
518 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
522 if (SMTP_IS_STATE('2')) {
524 if (IsEmptyStr(SendMsg->mx_user))
525 SendMsg->State ++; /* Skip auth... */
527 /* else we fall back to 'helo' */
531 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
533 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
534 "HELO %s\r\n", config.c_fqdn);
540 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
544 if (!SMTP_IS_STATE('2')) {
545 if (SMTP_IS_STATE('4'))
550 if (!IsEmptyStr(SendMsg->mx_user))
551 SendMsg->State ++; /* Skip auth... */
555 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
560 /* Do an AUTH command if necessary */
561 sprintf(buf, "%s%c%s%c%s",
562 SendMsg->mx_user, '\0',
563 SendMsg->mx_user, '\0',
565 CtdlEncodeBase64(encoded, buf,
566 strlen(SendMsg->mx_user) +
567 strlen(SendMsg->mx_user) +
568 strlen(SendMsg->mx_pass) + 2, 0);
569 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
570 "AUTH PLAIN %s\r\n", encoded);
576 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
578 /* Do an AUTH command if necessary */
582 if (!SMTP_IS_STATE('2')) {
583 if (SMTP_IS_STATE('4'))
591 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
593 /* previous command succeeded, now try the MAIL FROM: command */
594 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
595 "MAIL FROM:<%s>\r\n",
596 SendMsg->envelope_from);
602 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
606 if (!SMTP_IS_STATE('2')) {
607 if (SMTP_IS_STATE('4'))
616 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
618 /* MAIL succeeded, now try the RCPT To: command */
619 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
620 "RCPT TO:<%s@%s>\r\n",
628 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
632 if (!SMTP_IS_STATE('2')) {
633 if (SMTP_IS_STATE('4'))
641 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
643 /* RCPT succeeded, now try the DATA command */
644 StrBufPlain(SendMsg->IO.SendBuf.Buf,
651 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
655 if (!SMTP_IS_STATE('3')) {
656 if (SMTP_IS_STATE('4'))
664 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
667 /* If we reach this point, the server is expecting data.*/
669 Buf = SendMsg->IO.SendBuf.Buf;
670 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
671 SendMsg->msgtext = Buf;
672 //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
678 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
682 Buf = SendMsg->IO.SendBuf.Buf;
683 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
684 SendMsg->msgtext = Buf;
686 StrBufPlain(SendMsg->IO.SendBuf.Buf,
693 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
697 if (!SMTP_IS_STATE('2')) {
698 if (SMTP_IS_STATE('4'))
705 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
706 &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
707 StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
708 SendMsg->MyQEntry->Status = 2;
712 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
714 StrBufPlain(SendMsg->IO.SendBuf.Buf,
721 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
725 CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
726 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
727 return eTerminateConnection;
730 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
735 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
741 /*****************************************************************************/
742 /* SMTP CLIENT DISPATCHER */
743 /*****************************************************************************/
744 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
746 SMTPC_read_EHLO_reply,
747 SMTPC_read_HELO_reply,
748 SMTPC_read_auth_reply,
749 SMTPC_read_FROM_reply,
750 SMTPC_read_RCPT_reply,
751 SMTPC_read_DATAcmd_reply,
753 SMTPC_read_data_body_reply,
754 SMTPC_read_QUIT_reply
756 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
757 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
764 SMTPC_send_data_body,
765 SMTPC_send_terminate_data_body,
768 eNextState SMTP_C_DispatchReadDone(void *Data)
770 SmtpOutMsg *pMsg = Data;
771 eNextState rc = ReadHandlers[pMsg->State](pMsg);
775 eNextState SMTP_C_DispatchWriteDone(void *Data)
777 SmtpOutMsg *pMsg = Data;
778 return SendHandlers[pMsg->State](pMsg);
782 /*****************************************************************************/
783 /* SMTP CLIENT ERROR CATCHERS */
784 /*****************************************************************************/
785 eNextState SMTP_C_Terminate(void *Data)
787 SmtpOutMsg *pMsg = Data;
788 FinalizeMessageSend(pMsg);
791 eNextState SMTP_C_Timeout(void *Data)
793 SmtpOutMsg *pMsg = Data;
794 FinalizeMessageSend(pMsg);
797 eNextState SMTP_C_ConnFail(void *Data)
799 SmtpOutMsg *pMsg = Data;
800 FinalizeMessageSend(pMsg);
806 * @brief lineread Handler; understands when to read more SMTP lines, and when this is a one-lined reply.
808 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
810 eReadState Finished = eBufferNotEmpty;
812 while (Finished == eBufferNotEmpty) {
813 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
816 case eMustReadMore: /// read new from socket...
819 case eBufferNotEmpty: /* shouldn't happen... */
820 case eReadSuccess: /// done for now...
821 if (StrLength(IO->IOBuf) < 4)
823 if (ChrPtr(IO->IOBuf)[3] == '-')
824 Finished = eBufferNotEmpty;
828 case eReadFail: /// WHUT?
838 CTDL_MODULE_INIT(smtp_eventclient)
840 return "smtpeventclient";