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 = 60; /* wail 1 minute 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);
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 unsigned long psaddr;
261 memcpy(&psaddr, hostent->h_addr_list[0], sizeof(psaddr));
262 psaddr = ntohl(psaddr);
264 CtdlLogPrintf(CTDL_DEBUG,
265 "SMTP client[%ld]: connecting to %s [%d.%d.%d.%d:%d] ...\n",
268 (psaddr >> 24) & 0xFF,
269 (psaddr >> 16) & 0xFF,
270 (psaddr >> 8) & 0xFF,
271 (psaddr >> 0) & 0xFF,
274 SendMsg->MyQEntry->Status = 5;
275 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
276 "Timeout while connecting %s",
279 SendMsg->IO.HEnt = hostent;
280 InitEventIO(IO, SendMsg,
281 SMTP_C_DispatchReadDone,
282 SMTP_C_DispatchWriteDone,
286 SMTP_C_ReadServerStatus,
288 SMTP_C_ReadTimeouts[0],
294 const unsigned short DefaultMXPort = 25;
295 void get_one_mx_host_ip(SmtpOutMsg *SendMsg)
300 SendMsg->IO.dport = DefaultMXPort;
304 *SendMsg->mx_user = '\0';
305 *SendMsg->mx_pass = '\0';
306 if (num_tokens(buf, '@') > 1) {
307 strcpy (SendMsg->mx_user, buf);
308 endpart = strrchr(SendMsg->mx_user, '@');
310 strcpy (SendMsg->mx_host, endpart + 1);
311 endpart = strrchr(SendMsg->mx_user, ':');
312 if (endpart != NULL) {
313 strcpy(SendMsg->mx_pass, endpart+1);
317 endpart = strrchr(SendMsg->mx_host, ':');
320 strcpy(SendMsg->mx_port, endpart + 1);
325 SendMsg->mx_host = SendMsg->CurrMX->host;
326 SendMsg->CurrMX = SendMsg->CurrMX->next;
328 CtdlLogPrintf(CTDL_DEBUG,
329 "SMTP client[%ld]: looking up %s : %d ...\n",
333 ares_gethostbyname(SendMsg->IO.DNSChannel,
335 AF_INET6, /* it falls back to ipv4 in doubt... */
336 get_one_mx_host_ip_done,
341 eNextState smtp_resolve_mx_done(void *data)
344 SmtpOutMsg * SendMsg = IO->Data;
346 SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
347 SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
348 SendMsg->IO.IOBuf = NewStrBuf();
349 SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
351 SendMsg->CurrMX = SendMsg->AllMX = IO->VParsedDNSReply;
352 //// TODO: should we remove the current ares context???
353 get_one_mx_host_ip(SendMsg);
358 int resolve_mx_records(void *Ctx)
360 SmtpOutMsg * SendMsg = Ctx;
362 if (!QueueQuery(ns_t_mx,
365 smtp_resolve_mx_done))
367 SendMsg->MyQEntry->Status = 5;
368 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
369 "No MX hosts found for <%s>", SendMsg->node);
370 return 0; ///////TODO: abort!
376 int smtp_resolve_recipients(SmtpOutMsg *SendMsg)
384 if ((SendMsg==NULL) ||
385 (SendMsg->MyQEntry == NULL) ||
386 (StrLength(SendMsg->MyQEntry->Recipient) == 0)) {
390 /* Parse out the host portion of the recipient address */
391 process_rfc822_addr(ChrPtr(SendMsg->MyQEntry->Recipient),
396 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n",
397 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
398 /* If no envelope_from is supplied, extract one from the message */
399 if ( (SendMsg->envelope_from == NULL) ||
400 (IsEmptyStr(SendMsg->envelope_from)) ) {
401 SendMsg->mailfrom[0] = '\0';
403 ptr = ChrPtr(SendMsg->msgtext);
405 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
408 if (!strncasecmp(buf, "From:", 5)) {
409 safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom);
410 striplt(SendMsg->mailfrom);
411 for (i=0; SendMsg->mailfrom[i]; ++i) {
412 if (!isprint(SendMsg->mailfrom[i])) {
413 strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]);
418 /* Strip out parenthesized names */
421 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
422 if (SendMsg->mailfrom[i] == '(') lp = i;
423 if (SendMsg->mailfrom[i] == ')') rp = i;
425 if ((lp>0)&&(rp>lp)) {
426 strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]);
429 /* Prefer brokketized names */
432 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
433 if (SendMsg->mailfrom[i] == '<') lp = i;
434 if (SendMsg->mailfrom[i] == '>') rp = i;
436 if ( (lp>=0) && (rp>lp) ) {
437 SendMsg->mailfrom[rp] = 0;
438 memmove(SendMsg->mailfrom,
439 &SendMsg->mailfrom[lp + 1],
445 } while (scan_done == 0);
446 if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org");
447 stripallbut(SendMsg->mailfrom, '<', '>');
448 SendMsg->envelope_from = SendMsg->mailfrom;
456 void smtp_try(OneQueItem *MyQItem,
457 MailQEntry *MyQEntry,
459 int KeepMsgText, /* KeepMsgText allows us to use MsgText as ours. */
462 SmtpOutMsg * SendMsg;
464 SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
465 memset(SendMsg, 0, sizeof(SmtpOutMsg));
466 SendMsg->IO.sock = (-1);
467 SendMsg->n = MsgCount++;
468 SendMsg->MyQEntry = MyQEntry;
469 SendMsg->MyQItem = MyQItem;
470 SendMsg->IO.Data = SendMsg;
472 SendMsg->msgtext = MsgText;
474 SendMsg->msgtext = NewStrBufDup(MsgText);
476 if (smtp_resolve_recipients(SendMsg)) {
477 QueueEventContext(SendMsg,
482 if ((SendMsg==NULL) ||
483 (SendMsg->MyQEntry == NULL)) {
484 SendMsg->MyQEntry->Status = 5;
485 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
486 HKEY("Invalid Recipient!"));
488 FinalizeMessageSend(SendMsg);
496 /*****************************************************************************/
497 /* SMTP CLIENT STATE CALLBACKS */
498 /*****************************************************************************/
499 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
501 /* Process the SMTP greeting from the server */
504 if (!SMTP_IS_STATE('2')) {
505 if (SMTP_IS_STATE('4'))
513 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
515 /* At this point we know we are talking to a real SMTP server */
517 /* Do a EHLO command. If it fails, try the HELO command. */
518 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
519 "EHLO %s\r\n", config.c_fqdn);
525 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
529 if (SMTP_IS_STATE('2')) {
531 if (IsEmptyStr(SendMsg->mx_user))
532 SendMsg->State ++; /* Skip auth... */
534 /* else we fall back to 'helo' */
538 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
540 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
541 "HELO %s\r\n", config.c_fqdn);
547 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
551 if (!SMTP_IS_STATE('2')) {
552 if (SMTP_IS_STATE('4'))
557 if (!IsEmptyStr(SendMsg->mx_user))
558 SendMsg->State ++; /* Skip auth... */
562 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
567 /* Do an AUTH command if necessary */
568 sprintf(buf, "%s%c%s%c%s",
569 SendMsg->mx_user, '\0',
570 SendMsg->mx_user, '\0',
572 CtdlEncodeBase64(encoded, buf,
573 strlen(SendMsg->mx_user) +
574 strlen(SendMsg->mx_user) +
575 strlen(SendMsg->mx_pass) + 2, 0);
576 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
577 "AUTH PLAIN %s\r\n", encoded);
583 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
585 /* Do an AUTH command if necessary */
589 if (!SMTP_IS_STATE('2')) {
590 if (SMTP_IS_STATE('4'))
598 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
600 /* previous command succeeded, now try the MAIL FROM: command */
601 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
602 "MAIL FROM:<%s>\r\n",
603 SendMsg->envelope_from);
609 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
613 if (!SMTP_IS_STATE('2')) {
614 if (SMTP_IS_STATE('4'))
623 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
625 /* MAIL succeeded, now try the RCPT To: command */
626 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
627 "RCPT TO:<%s@%s>\r\n",
635 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
639 if (!SMTP_IS_STATE('2')) {
640 if (SMTP_IS_STATE('4'))
648 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
650 /* RCPT succeeded, now try the DATA command */
651 StrBufPlain(SendMsg->IO.SendBuf.Buf,
658 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
662 if (!SMTP_IS_STATE('3')) {
663 if (SMTP_IS_STATE('4'))
671 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
674 /* If we reach this point, the server is expecting data.*/
676 Buf = SendMsg->IO.SendBuf.Buf;
677 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
678 SendMsg->msgtext = Buf;
679 //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
685 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
689 Buf = SendMsg->IO.SendBuf.Buf;
690 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
691 SendMsg->msgtext = Buf;
693 StrBufPlain(SendMsg->IO.SendBuf.Buf,
700 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
704 if (!SMTP_IS_STATE('2')) {
705 if (SMTP_IS_STATE('4'))
712 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
713 &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
714 StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
715 SendMsg->MyQEntry->Status = 2;
719 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
721 StrBufPlain(SendMsg->IO.SendBuf.Buf,
728 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
732 CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
733 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
734 return eTerminateConnection;
737 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
742 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
748 /*****************************************************************************/
749 /* SMTP CLIENT DISPATCHER */
750 /*****************************************************************************/
751 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
753 SMTPC_read_EHLO_reply,
754 SMTPC_read_HELO_reply,
755 SMTPC_read_auth_reply,
756 SMTPC_read_FROM_reply,
757 SMTPC_read_RCPT_reply,
758 SMTPC_read_DATAcmd_reply,
760 SMTPC_read_data_body_reply,
761 SMTPC_read_QUIT_reply
763 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
764 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
771 SMTPC_send_data_body,
772 SMTPC_send_terminate_data_body,
775 eNextState SMTP_C_DispatchReadDone(void *Data)
777 SmtpOutMsg *pMsg = Data;
778 eNextState rc = ReadHandlers[pMsg->State](pMsg);
782 eNextState SMTP_C_DispatchWriteDone(void *Data)
784 SmtpOutMsg *pMsg = Data;
785 return SendHandlers[pMsg->State](pMsg);
789 /*****************************************************************************/
790 /* SMTP CLIENT ERROR CATCHERS */
791 /*****************************************************************************/
792 eNextState SMTP_C_Terminate(void *Data)
795 SmtpOutMsg *pMsg = IO->Data;
796 FinalizeMessageSend(pMsg);
799 eNextState SMTP_C_Timeout(void *Data)
802 SmtpOutMsg *pMsg = IO->Data;
803 FinalizeMessageSend(pMsg);
806 eNextState SMTP_C_ConnFail(void *Data)
809 SmtpOutMsg *pMsg = IO->Data;
810 FinalizeMessageSend(pMsg);
816 * @brief lineread Handler; understands when to read more SMTP lines, and when this is a one-lined reply.
818 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
820 eReadState Finished = eBufferNotEmpty;
822 while (Finished == eBufferNotEmpty) {
823 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
826 case eMustReadMore: /// read new from socket...
829 case eBufferNotEmpty: /* shouldn't happen... */
830 case eReadSuccess: /// done for now...
831 if (StrLength(IO->IOBuf) < 4)
833 if (ChrPtr(IO->IOBuf)[3] == '-')
834 Finished = eBufferNotEmpty;
838 case eReadFail: /// WHUT?
848 CTDL_MODULE_INIT(smtp_eventclient)
850 return "smtpeventclient";