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 RemoveQItem(Msg->MyQItem);
245 DeleteSmtpOutMsg(Msg);
251 void get_one_mx_host_ip_done(void *Ctx,
254 struct hostent *hostent)
257 SmtpOutMsg *SendMsg = IO->Data;
258 if ((status == ARES_SUCCESS) && (hostent != NULL) ) {
259 CtdlLogPrintf(CTDL_DEBUG,
260 "SMTP client[%ld]: connecting to %s [ip]: %d ...\n",
265 SendMsg->MyQEntry->Status = 5;
266 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
267 "Timeout while connecting %s",
270 SendMsg->IO.HEnt = hostent;
271 InitEventIO(IO, SendMsg,
272 SMTP_C_DispatchReadDone,
273 SMTP_C_DispatchWriteDone,
277 SMTP_C_ReadServerStatus,
279 SMTP_C_ReadTimeouts[0],
285 const unsigned short DefaultMXPort = 25;
286 void get_one_mx_host_ip(SmtpOutMsg *SendMsg)
291 SendMsg->IO.dport = DefaultMXPort;
295 *SendMsg->mx_user = '\0';
296 *SendMsg->mx_pass = '\0';
297 if (num_tokens(buf, '@') > 1) {
298 strcpy (SendMsg->mx_user, buf);
299 endpart = strrchr(SendMsg->mx_user, '@');
301 strcpy (SendMsg->mx_host, endpart + 1);
302 endpart = strrchr(SendMsg->mx_user, ':');
303 if (endpart != NULL) {
304 strcpy(SendMsg->mx_pass, endpart+1);
308 endpart = strrchr(SendMsg->mx_host, ':');
311 strcpy(SendMsg->mx_port, endpart + 1);
316 SendMsg->mx_host = SendMsg->CurrMX->host;
317 SendMsg->CurrMX = SendMsg->CurrMX->next;
319 CtdlLogPrintf(CTDL_DEBUG,
320 "SMTP client[%ld]: looking up %s : %d ...\n",
324 ares_gethostbyname(SendMsg->IO.DNSChannel,
326 AF_INET6, /* it falls back to ipv4 in doubt... */
327 get_one_mx_host_ip_done,
332 eNextState smtp_resolve_mx_done(void *data)
335 SmtpOutMsg * SendMsg = IO->Data;
337 SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
338 SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
339 SendMsg->IO.IOBuf = NewStrBuf();
340 SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
342 SendMsg->CurrMX = SendMsg->AllMX = IO->VParsedDNSReply;
343 //// TODO: should we remove the current ares context???
344 get_one_mx_host_ip(SendMsg);
349 int resolve_mx_records(void *Ctx)
351 SmtpOutMsg * SendMsg = Ctx;
353 if (!QueueQuery(ns_t_mx,
356 smtp_resolve_mx_done))
358 SendMsg->MyQEntry->Status = 5;
359 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
360 "No MX hosts found for <%s>", SendMsg->node);
361 return 0; ///////TODO: abort!
367 int smtp_resolve_recipients(SmtpOutMsg *SendMsg)
375 /* Parse out the host portion of the recipient address */
376 process_rfc822_addr(ChrPtr(SendMsg->MyQEntry->Recipient),
381 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n",
382 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
383 /* If no envelope_from is supplied, extract one from the message */
384 if ( (SendMsg->envelope_from == NULL) ||
385 (IsEmptyStr(SendMsg->envelope_from)) ) {
386 SendMsg->mailfrom[0] = '\0';
388 ptr = ChrPtr(SendMsg->msgtext);
390 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
393 if (!strncasecmp(buf, "From:", 5)) {
394 safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom);
395 striplt(SendMsg->mailfrom);
396 for (i=0; SendMsg->mailfrom[i]; ++i) {
397 if (!isprint(SendMsg->mailfrom[i])) {
398 strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]);
403 /* Strip out parenthesized names */
406 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
407 if (SendMsg->mailfrom[i] == '(') lp = i;
408 if (SendMsg->mailfrom[i] == ')') rp = i;
410 if ((lp>0)&&(rp>lp)) {
411 strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]);
414 /* Prefer brokketized names */
417 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
418 if (SendMsg->mailfrom[i] == '<') lp = i;
419 if (SendMsg->mailfrom[i] == '>') rp = i;
421 if ( (lp>=0) && (rp>lp) ) {
422 SendMsg->mailfrom[rp] = 0;
423 memmove(SendMsg->mailfrom,
424 &SendMsg->mailfrom[lp + 1],
430 } while (scan_done == 0);
431 if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org");
432 stripallbut(SendMsg->mailfrom, '<', '>');
433 SendMsg->envelope_from = SendMsg->mailfrom;
441 void smtp_try(OneQueItem *MyQItem,
442 MailQEntry *MyQEntry,
444 int KeepMsgText, /* KeepMsgText allows us to use MsgText as ours. */
447 SmtpOutMsg * SendMsg;
449 SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
450 memset(SendMsg, 0, sizeof(SmtpOutMsg));
451 SendMsg->IO.sock = (-1);
452 SendMsg->n = MsgCount++;
453 SendMsg->MyQEntry = MyQEntry;
454 SendMsg->MyQItem = MyQItem;
455 SendMsg->IO.Data = SendMsg;
457 SendMsg->msgtext = MsgText;
459 SendMsg->msgtext = NewStrBufDup(MsgText);
461 smtp_resolve_recipients(SendMsg);
463 QueueEventContext(SendMsg,
472 /*****************************************************************************/
473 /* SMTP CLIENT STATE CALLBACKS */
474 /*****************************************************************************/
475 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
477 /* Process the SMTP greeting from the server */
480 if (!SMTP_IS_STATE('2')) {
481 if (SMTP_IS_STATE('4'))
489 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
491 /* At this point we know we are talking to a real SMTP server */
493 /* Do a EHLO command. If it fails, try the HELO command. */
494 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
495 "EHLO %s\r\n", config.c_fqdn);
501 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
505 if (SMTP_IS_STATE('2')) {
507 if (IsEmptyStr(SendMsg->mx_user))
508 SendMsg->State ++; /* Skip auth... */
510 /* else we fall back to 'helo' */
514 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
516 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
517 "HELO %s\r\n", config.c_fqdn);
523 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
527 if (!SMTP_IS_STATE('2')) {
528 if (SMTP_IS_STATE('4'))
533 if (!IsEmptyStr(SendMsg->mx_user))
534 SendMsg->State ++; /* Skip auth... */
538 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
543 /* Do an AUTH command if necessary */
544 sprintf(buf, "%s%c%s%c%s",
545 SendMsg->mx_user, '\0',
546 SendMsg->mx_user, '\0',
548 CtdlEncodeBase64(encoded, buf,
549 strlen(SendMsg->mx_user) +
550 strlen(SendMsg->mx_user) +
551 strlen(SendMsg->mx_pass) + 2, 0);
552 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
553 "AUTH PLAIN %s\r\n", encoded);
559 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
561 /* Do an AUTH command if necessary */
565 if (!SMTP_IS_STATE('2')) {
566 if (SMTP_IS_STATE('4'))
574 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
576 /* previous command succeeded, now try the MAIL FROM: command */
577 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
578 "MAIL FROM:<%s>\r\n",
579 SendMsg->envelope_from);
585 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
589 if (!SMTP_IS_STATE('2')) {
590 if (SMTP_IS_STATE('4'))
599 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
601 /* MAIL succeeded, now try the RCPT To: command */
602 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
603 "RCPT TO:<%s@%s>\r\n",
611 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
615 if (!SMTP_IS_STATE('2')) {
616 if (SMTP_IS_STATE('4'))
624 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
626 /* RCPT succeeded, now try the DATA command */
627 StrBufPlain(SendMsg->IO.SendBuf.Buf,
634 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
638 if (!SMTP_IS_STATE('3')) {
639 if (SMTP_IS_STATE('4'))
647 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
650 /* If we reach this point, the server is expecting data.*/
652 Buf = SendMsg->IO.SendBuf.Buf;
653 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
654 SendMsg->msgtext = Buf;
655 //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
661 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
665 Buf = SendMsg->IO.SendBuf.Buf;
666 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
667 SendMsg->msgtext = Buf;
669 StrBufPlain(SendMsg->IO.SendBuf.Buf,
676 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
680 if (!SMTP_IS_STATE('2')) {
681 if (SMTP_IS_STATE('4'))
688 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
689 &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
690 StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
691 SendMsg->MyQEntry->Status = 2;
695 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
697 StrBufPlain(SendMsg->IO.SendBuf.Buf,
704 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
708 CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
709 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
710 return eTerminateConnection;
713 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
718 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
724 /*****************************************************************************/
725 /* SMTP CLIENT DISPATCHER */
726 /*****************************************************************************/
727 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
729 SMTPC_read_EHLO_reply,
730 SMTPC_read_HELO_reply,
731 SMTPC_read_auth_reply,
732 SMTPC_read_FROM_reply,
733 SMTPC_read_RCPT_reply,
734 SMTPC_read_DATAcmd_reply,
736 SMTPC_read_data_body_reply,
737 SMTPC_read_QUIT_reply
739 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
740 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
747 SMTPC_send_data_body,
748 SMTPC_send_terminate_data_body,
751 eNextState SMTP_C_DispatchReadDone(void *Data)
753 SmtpOutMsg *pMsg = Data;
754 eNextState rc = ReadHandlers[pMsg->State](pMsg);
758 eNextState SMTP_C_DispatchWriteDone(void *Data)
760 SmtpOutMsg *pMsg = Data;
761 return SendHandlers[pMsg->State](pMsg);
765 /*****************************************************************************/
766 /* SMTP CLIENT ERROR CATCHERS */
767 /*****************************************************************************/
768 eNextState SMTP_C_Terminate(void *Data)
770 SmtpOutMsg *pMsg = Data;
771 FinalizeMessageSend(pMsg);
774 eNextState SMTP_C_Timeout(void *Data)
776 SmtpOutMsg *pMsg = Data;
777 FinalizeMessageSend(pMsg);
780 eNextState SMTP_C_ConnFail(void *Data)
782 SmtpOutMsg *pMsg = Data;
783 FinalizeMessageSend(pMsg);
789 * @brief lineread Handler; understands when to read more SMTP lines, and when this is a one-lined reply.
791 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
793 eReadState Finished = eBufferNotEmpty;
795 while (Finished == eBufferNotEmpty) {
796 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
799 case eMustReadMore: /// read new from socket...
802 case eBufferNotEmpty: /* shouldn't happen... */
803 case eReadSuccess: /// done for now...
804 if (StrLength(IO->IOBuf) < 4)
806 if (ChrPtr(IO->IOBuf)[3] == '-')
807 Finished = eBufferNotEmpty;
811 case eReadFail: /// WHUT?
821 CTDL_MODULE_INIT(smtp_eventclient)
823 return "smtpeventclient";