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_ReadTimeouts[eMaxSMTPC] = {
112 90, /* Greeting... */
120 900, /* end of body... */
124 const long SMTP_C_SendTimeouts[eMaxSMTPC] = {
127 const char *ReadErrors[eMaxSMTPC] = {
128 "Connection broken during SMTP conversation",
129 "Connection broken during SMTP EHLO",
130 "Connection broken during SMTP HELO",
131 "Connection broken during SMTP AUTH",
132 "Connection broken during SMTP MAIL FROM",
133 "Connection broken during SMTP RCPT",
134 "Connection broken during SMTP DATA",
135 "Connection broken during SMTP message transmit",
136 ""/* quit reply, don't care. */
140 typedef struct _stmp_out_msg {
141 MailQEntry *MyQEntry;
146 eSMTP_C_States State;
148 struct ares_mx_reply *AllMX;
149 struct ares_mx_reply *CurrMX;
153 struct hostent *OneMX;
165 void DeleteSmtpOutMsg(void *v)
169 ares_free_data(Msg->AllMX);
170 FreeStrBuf(&Msg->msgtext);
171 FreeAsyncIOContents(&Msg->IO);
175 eNextState SMTP_C_Timeout(void *Data);
176 eNextState SMTP_C_ConnFail(void *Data);
177 eNextState SMTP_C_DispatchReadDone(void *Data);
178 eNextState SMTP_C_DispatchWriteDone(void *Data);
179 eNextState SMTP_C_Terminate(void *Data);
180 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO);
182 typedef eNextState (*SMTPReadHandler)(SmtpOutMsg *Msg);
183 typedef eNextState (*SMTPSendHandler)(SmtpOutMsg *Msg);
186 #define SMTP_ERROR(WHICH_ERR, ERRSTR) do {\
187 SendMsg->MyQEntry->Status = WHICH_ERR; \
188 StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, HKEY(ERRSTR), 0); \
192 #define SMTP_VERROR(WHICH_ERR) do {\
193 SendMsg->MyQEntry->Status = WHICH_ERR; \
194 StrBufAppendBufPlain(SendMsg->MyQEntry->StatusMessage, &ChrPtr(SendMsg->IO.IOBuf)[4], -1, 0); \
198 #define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(SendMsg->IO.IOBuf)[0] == WHICH_STATE)
200 #define SMTP_DBG_SEND() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: > %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
201 #define SMTP_DBG_READ() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: < %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
204 void FinalizeMessageSend(SmtpOutMsg *Msg)
206 if (DecreaseQReference(Msg->MyQItem))
211 nRemain = CountActiveQueueEntries(Msg->MyQItem);
214 MsgData = SerializeQueueItem(Msg->MyQItem);
216 * Uncompleted delivery instructions remain, so delete the old
217 * instructions and replace with the updated ones.
219 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, "");
222 struct CtdlMessage *msg;
223 msg = malloc(sizeof(struct CtdlMessage));
224 memset(msg, 0, sizeof(struct CtdlMessage));
225 msg->cm_magic = CTDLMESSAGE_MAGIC;
226 msg->cm_anon_type = MES_NORMAL;
227 msg->cm_format_type = FMT_RFC822;
228 msg->cm_fields['M'] = SmashStrBuf(&MsgData);
229 /* Generate 'bounce' messages */
230 smtp_do_bounce(msg->cm_fields['M'],
233 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
234 CtdlFreeMessage(msg);
237 RemoveQItem(Msg->MyQItem);
241 DeleteSmtpOutMsg(Msg);
247 void get_one_mx_host_ip_done(void *Ctx,
250 struct hostent *hostent)
253 SmtpOutMsg *SendMsg = IO->Data;
254 if ((status == ARES_SUCCESS) && (hostent != NULL) ) {
255 CtdlLogPrintf(CTDL_DEBUG,
256 "SMTP client[%ld]: connecting to %s : %d ...\n",
262 SendMsg->IO.HEnt = hostent;
263 InitEventIO(IO, SendMsg,
264 SMTP_C_DispatchReadDone,
265 SMTP_C_DispatchWriteDone,
269 SMTP_C_ReadServerStatus,
275 const unsigned short DefaultMXPort = 25;
276 void get_one_mx_host_ip(SmtpOutMsg *SendMsg)
281 SendMsg->IO.dport = DefaultMXPort;
285 *SendMsg->mx_user = '\0';
286 *SendMsg->mx_pass = '\0';
287 if (num_tokens(buf, '@') > 1) {
288 strcpy (SendMsg->mx_user, buf);
289 endpart = strrchr(SendMsg->mx_user, '@');
291 strcpy (SendMsg->mx_host, endpart + 1);
292 endpart = strrchr(SendMsg->mx_user, ':');
293 if (endpart != NULL) {
294 strcpy(SendMsg->mx_pass, endpart+1);
298 endpart = strrchr(SendMsg->mx_host, ':');
301 strcpy(SendMsg->mx_port, endpart + 1);
306 SendMsg->mx_host = SendMsg->CurrMX->host;
307 SendMsg->CurrMX = SendMsg->CurrMX->next;
309 CtdlLogPrintf(CTDL_DEBUG,
310 "SMTP client[%ld]: looking up %s : %d ...\n",
314 ares_gethostbyname(SendMsg->IO.DNSChannel,
316 AF_INET6, /* it falls back to ipv4 in doubt... */
317 get_one_mx_host_ip_done,
322 eNextState smtp_resolve_mx_done(void *data)
325 SmtpOutMsg * SendMsg = IO->Data;
327 SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
328 SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
329 SendMsg->IO.IOBuf = NewStrBuf();
330 SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
332 SendMsg->CurrMX = SendMsg->AllMX = IO->VParsedDNSReply;
333 //// TODO: should we remove the current ares context???
334 get_one_mx_host_ip(SendMsg);
339 int resolve_mx_records(void *Ctx)
341 SmtpOutMsg * SendMsg = Ctx;
343 if (!QueueQuery(ns_t_mx,
346 smtp_resolve_mx_done))
348 SendMsg->MyQEntry->Status = 5;
349 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
350 "No MX hosts found for <%s>", SendMsg->node);
351 return 0; ///////TODO: abort!
357 int smtp_resolve_recipients(SmtpOutMsg *SendMsg)
365 /* Parse out the host portion of the recipient address */
366 process_rfc822_addr(ChrPtr(SendMsg->MyQEntry->Recipient),
371 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n",
372 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
373 /* If no envelope_from is supplied, extract one from the message */
374 if ( (SendMsg->envelope_from == NULL) ||
375 (IsEmptyStr(SendMsg->envelope_from)) ) {
376 SendMsg->mailfrom[0] = '\0';
378 ptr = ChrPtr(SendMsg->msgtext);
380 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
383 if (!strncasecmp(buf, "From:", 5)) {
384 safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom);
385 striplt(SendMsg->mailfrom);
386 for (i=0; SendMsg->mailfrom[i]; ++i) {
387 if (!isprint(SendMsg->mailfrom[i])) {
388 strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]);
393 /* Strip out parenthesized names */
396 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
397 if (SendMsg->mailfrom[i] == '(') lp = i;
398 if (SendMsg->mailfrom[i] == ')') rp = i;
400 if ((lp>0)&&(rp>lp)) {
401 strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]);
404 /* Prefer brokketized names */
407 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
408 if (SendMsg->mailfrom[i] == '<') lp = i;
409 if (SendMsg->mailfrom[i] == '>') rp = i;
411 if ( (lp>=0) && (rp>lp) ) {
412 SendMsg->mailfrom[rp] = 0;
413 memmove(SendMsg->mailfrom,
414 &SendMsg->mailfrom[lp + 1],
420 } while (scan_done == 0);
421 if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org");
422 stripallbut(SendMsg->mailfrom, '<', '>');
423 SendMsg->envelope_from = SendMsg->mailfrom;
431 void smtp_try(OneQueItem *MyQItem,
432 MailQEntry *MyQEntry,
434 int KeepMsgText, /* KeepMsgText allows us to use MsgText as ours. */
437 SmtpOutMsg * SendMsg;
439 SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
440 memset(SendMsg, 0, sizeof(SmtpOutMsg));
441 SendMsg->IO.sock = (-1);
442 SendMsg->n = MsgCount++;
443 SendMsg->MyQEntry = MyQEntry;
444 SendMsg->MyQItem = MyQItem;
445 SendMsg->IO.Data = SendMsg;
447 SendMsg->msgtext = MsgText;
449 SendMsg->msgtext = NewStrBufDup(MsgText);
451 smtp_resolve_recipients(SendMsg);
453 QueueEventContext(SendMsg,
462 /*****************************************************************************/
463 /* SMTP CLIENT STATE CALLBACKS */
464 /*****************************************************************************/
465 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
467 /* Process the SMTP greeting from the server */
470 if (!SMTP_IS_STATE('2')) {
471 if (SMTP_IS_STATE('4'))
479 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
481 /* At this point we know we are talking to a real SMTP server */
483 /* Do a EHLO command. If it fails, try the HELO command. */
484 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
485 "EHLO %s\r\n", config.c_fqdn);
491 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
495 if (SMTP_IS_STATE('2')) {
497 if (IsEmptyStr(SendMsg->mx_user))
498 SendMsg->State ++; /* Skip auth... */
500 /* else we fall back to 'helo' */
504 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
506 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
507 "HELO %s\r\n", config.c_fqdn);
513 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
517 if (!SMTP_IS_STATE('2')) {
518 if (SMTP_IS_STATE('4'))
523 if (!IsEmptyStr(SendMsg->mx_user))
524 SendMsg->State ++; /* Skip auth... */
528 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
533 /* Do an AUTH command if necessary */
534 sprintf(buf, "%s%c%s%c%s",
535 SendMsg->mx_user, '\0',
536 SendMsg->mx_user, '\0',
538 CtdlEncodeBase64(encoded, buf,
539 strlen(SendMsg->mx_user) +
540 strlen(SendMsg->mx_user) +
541 strlen(SendMsg->mx_pass) + 2, 0);
542 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
543 "AUTH PLAIN %s\r\n", encoded);
549 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
551 /* Do an AUTH command if necessary */
555 if (!SMTP_IS_STATE('2')) {
556 if (SMTP_IS_STATE('4'))
564 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
566 /* previous command succeeded, now try the MAIL FROM: command */
567 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
568 "MAIL FROM:<%s>\r\n",
569 SendMsg->envelope_from);
575 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
579 if (!SMTP_IS_STATE('2')) {
580 if (SMTP_IS_STATE('4'))
589 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
591 /* MAIL succeeded, now try the RCPT To: command */
592 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
593 "RCPT TO:<%s@%s>\r\n",
601 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
605 if (!SMTP_IS_STATE('2')) {
606 if (SMTP_IS_STATE('4'))
614 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
616 /* RCPT succeeded, now try the DATA command */
617 StrBufPlain(SendMsg->IO.SendBuf.Buf,
624 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
628 if (!SMTP_IS_STATE('3')) {
629 if (SMTP_IS_STATE('4'))
637 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
640 /* If we reach this point, the server is expecting data.*/
642 Buf = SendMsg->IO.SendBuf.Buf;
643 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
644 SendMsg->msgtext = Buf;
645 //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
651 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
655 Buf = SendMsg->IO.SendBuf.Buf;
656 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
657 SendMsg->msgtext = Buf;
659 StrBufPlain(SendMsg->IO.SendBuf.Buf,
666 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
670 if (!SMTP_IS_STATE('2')) {
671 if (SMTP_IS_STATE('4'))
678 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
679 &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
680 StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
681 SendMsg->MyQEntry->Status = 2;
685 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
687 StrBufPlain(SendMsg->IO.SendBuf.Buf,
694 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
698 CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
699 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
700 return eTerminateConnection;
703 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
708 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
714 /*****************************************************************************/
715 /* SMTP CLIENT DISPATCHER */
716 /*****************************************************************************/
717 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
719 SMTPC_read_EHLO_reply,
720 SMTPC_read_HELO_reply,
721 SMTPC_read_auth_reply,
722 SMTPC_read_FROM_reply,
723 SMTPC_read_RCPT_reply,
724 SMTPC_read_DATAcmd_reply,
726 SMTPC_read_data_body_reply,
727 SMTPC_read_QUIT_reply
729 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
730 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
737 SMTPC_send_data_body,
738 SMTPC_send_terminate_data_body,
741 eNextState SMTP_C_DispatchReadDone(void *Data)
743 SmtpOutMsg *pMsg = Data;
744 eNextState rc = ReadHandlers[pMsg->State](pMsg);
748 eNextState SMTP_C_DispatchWriteDone(void *Data)
750 SmtpOutMsg *pMsg = Data;
751 return SendHandlers[pMsg->State](pMsg);
755 /*****************************************************************************/
756 /* SMTP CLIENT ERROR CATCHERS */
757 /*****************************************************************************/
758 eNextState SMTP_C_Terminate(void *Data)
760 SmtpOutMsg *pMsg = Data;
761 FinalizeMessageSend(pMsg);
764 eNextState SMTP_C_Timeout(void *Data)
766 SmtpOutMsg *pMsg = Data;
767 FinalizeMessageSend(pMsg);
770 eNextState SMTP_C_ConnFail(void *Data)
772 SmtpOutMsg *pMsg = Data;
773 FinalizeMessageSend(pMsg);
779 * @brief lineread Handler; understands when to read more SMTP lines, and when this is a one-lined reply.
781 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
783 eReadState Finished = eBufferNotEmpty;
785 while (Finished == eBufferNotEmpty) {
786 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
789 case eMustReadMore: /// read new from socket...
792 case eBufferNotEmpty: /* shouldn't happen... */
793 case eReadSuccess: /// done for now...
794 if (StrLength(IO->IOBuf) < 4)
796 if (ChrPtr(IO->IOBuf)[3] == '-')
797 Finished = eBufferNotEmpty;
801 case eReadFail: /// WHUT?
811 CTDL_MODULE_INIT(smtp_eventclient)
813 return "smtpeventclient";