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 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 if ((SendMsg==NULL) ||
376 (SendMsg->MyQEntry == NULL) ||
377 (StrLength(SendMsg->MyQEntry->Recipient) == 0)) {
381 /* Parse out the host portion of the recipient address */
382 process_rfc822_addr(ChrPtr(SendMsg->MyQEntry->Recipient),
387 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n",
388 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
389 /* If no envelope_from is supplied, extract one from the message */
390 if ( (SendMsg->envelope_from == NULL) ||
391 (IsEmptyStr(SendMsg->envelope_from)) ) {
392 SendMsg->mailfrom[0] = '\0';
394 ptr = ChrPtr(SendMsg->msgtext);
396 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
399 if (!strncasecmp(buf, "From:", 5)) {
400 safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom);
401 striplt(SendMsg->mailfrom);
402 for (i=0; SendMsg->mailfrom[i]; ++i) {
403 if (!isprint(SendMsg->mailfrom[i])) {
404 strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]);
409 /* Strip out parenthesized names */
412 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
413 if (SendMsg->mailfrom[i] == '(') lp = i;
414 if (SendMsg->mailfrom[i] == ')') rp = i;
416 if ((lp>0)&&(rp>lp)) {
417 strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]);
420 /* Prefer brokketized names */
423 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
424 if (SendMsg->mailfrom[i] == '<') lp = i;
425 if (SendMsg->mailfrom[i] == '>') rp = i;
427 if ( (lp>=0) && (rp>lp) ) {
428 SendMsg->mailfrom[rp] = 0;
429 memmove(SendMsg->mailfrom,
430 &SendMsg->mailfrom[lp + 1],
436 } while (scan_done == 0);
437 if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org");
438 stripallbut(SendMsg->mailfrom, '<', '>');
439 SendMsg->envelope_from = SendMsg->mailfrom;
447 void smtp_try(OneQueItem *MyQItem,
448 MailQEntry *MyQEntry,
450 int KeepMsgText, /* KeepMsgText allows us to use MsgText as ours. */
453 SmtpOutMsg * SendMsg;
455 SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
456 memset(SendMsg, 0, sizeof(SmtpOutMsg));
457 SendMsg->IO.sock = (-1);
458 SendMsg->n = MsgCount++;
459 SendMsg->MyQEntry = MyQEntry;
460 SendMsg->MyQItem = MyQItem;
461 SendMsg->IO.Data = SendMsg;
463 SendMsg->msgtext = MsgText;
465 SendMsg->msgtext = NewStrBufDup(MsgText);
467 if (smtp_resolve_recipients(SendMsg)) {
468 QueueEventContext(SendMsg,
473 if ((SendMsg==NULL) ||
474 (SendMsg->MyQEntry == NULL)) {
475 SendMsg->MyQEntry->Status = 5;
476 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
477 HKEY("Invalid Recipient!"));
479 FinalizeMessageSend(SendMsg);
487 /*****************************************************************************/
488 /* SMTP CLIENT STATE CALLBACKS */
489 /*****************************************************************************/
490 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
492 /* Process the SMTP greeting from the server */
495 if (!SMTP_IS_STATE('2')) {
496 if (SMTP_IS_STATE('4'))
504 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
506 /* At this point we know we are talking to a real SMTP server */
508 /* Do a EHLO command. If it fails, try the HELO command. */
509 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
510 "EHLO %s\r\n", config.c_fqdn);
516 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
520 if (SMTP_IS_STATE('2')) {
522 if (IsEmptyStr(SendMsg->mx_user))
523 SendMsg->State ++; /* Skip auth... */
525 /* else we fall back to 'helo' */
529 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
531 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
532 "HELO %s\r\n", config.c_fqdn);
538 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
542 if (!SMTP_IS_STATE('2')) {
543 if (SMTP_IS_STATE('4'))
548 if (!IsEmptyStr(SendMsg->mx_user))
549 SendMsg->State ++; /* Skip auth... */
553 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
558 /* Do an AUTH command if necessary */
559 sprintf(buf, "%s%c%s%c%s",
560 SendMsg->mx_user, '\0',
561 SendMsg->mx_user, '\0',
563 CtdlEncodeBase64(encoded, buf,
564 strlen(SendMsg->mx_user) +
565 strlen(SendMsg->mx_user) +
566 strlen(SendMsg->mx_pass) + 2, 0);
567 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
568 "AUTH PLAIN %s\r\n", encoded);
574 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
576 /* Do an AUTH command if necessary */
580 if (!SMTP_IS_STATE('2')) {
581 if (SMTP_IS_STATE('4'))
589 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
591 /* previous command succeeded, now try the MAIL FROM: command */
592 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
593 "MAIL FROM:<%s>\r\n",
594 SendMsg->envelope_from);
600 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
604 if (!SMTP_IS_STATE('2')) {
605 if (SMTP_IS_STATE('4'))
614 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
616 /* MAIL succeeded, now try the RCPT To: command */
617 StrBufPrintf(SendMsg->IO.SendBuf.Buf,
618 "RCPT TO:<%s@%s>\r\n",
626 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
630 if (!SMTP_IS_STATE('2')) {
631 if (SMTP_IS_STATE('4'))
639 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
641 /* RCPT succeeded, now try the DATA command */
642 StrBufPlain(SendMsg->IO.SendBuf.Buf,
649 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
653 if (!SMTP_IS_STATE('3')) {
654 if (SMTP_IS_STATE('4'))
662 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
665 /* If we reach this point, the server is expecting data.*/
667 Buf = SendMsg->IO.SendBuf.Buf;
668 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
669 SendMsg->msgtext = Buf;
670 //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
676 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
680 Buf = SendMsg->IO.SendBuf.Buf;
681 SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
682 SendMsg->msgtext = Buf;
684 StrBufPlain(SendMsg->IO.SendBuf.Buf,
691 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
695 if (!SMTP_IS_STATE('2')) {
696 if (SMTP_IS_STATE('4'))
703 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
704 &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4],
705 StrLength(SendMsg->IO.RecvBuf.Buf) - 4);
706 SendMsg->MyQEntry->Status = 2;
710 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
712 StrBufPlain(SendMsg->IO.SendBuf.Buf,
719 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
723 CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
724 SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
725 return eTerminateConnection;
728 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
733 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
739 /*****************************************************************************/
740 /* SMTP CLIENT DISPATCHER */
741 /*****************************************************************************/
742 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
744 SMTPC_read_EHLO_reply,
745 SMTPC_read_HELO_reply,
746 SMTPC_read_auth_reply,
747 SMTPC_read_FROM_reply,
748 SMTPC_read_RCPT_reply,
749 SMTPC_read_DATAcmd_reply,
751 SMTPC_read_data_body_reply,
752 SMTPC_read_QUIT_reply
754 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
755 SMTPC_send_dummy, /* we don't send a greeting, the server does... */
762 SMTPC_send_data_body,
763 SMTPC_send_terminate_data_body,
766 eNextState SMTP_C_DispatchReadDone(void *Data)
768 SmtpOutMsg *pMsg = Data;
769 eNextState rc = ReadHandlers[pMsg->State](pMsg);
773 eNextState SMTP_C_DispatchWriteDone(void *Data)
775 SmtpOutMsg *pMsg = Data;
776 return SendHandlers[pMsg->State](pMsg);
780 /*****************************************************************************/
781 /* SMTP CLIENT ERROR CATCHERS */
782 /*****************************************************************************/
783 eNextState SMTP_C_Terminate(void *Data)
786 SmtpOutMsg *pMsg = IO->Data;
787 FinalizeMessageSend(pMsg);
790 eNextState SMTP_C_Timeout(void *Data)
793 SmtpOutMsg *pMsg = IO->Data;
794 FinalizeMessageSend(pMsg);
797 eNextState SMTP_C_ConnFail(void *Data)
800 SmtpOutMsg *pMsg = IO->Data;
801 FinalizeMessageSend(pMsg);
807 * @brief lineread Handler; understands when to read more SMTP lines, and when this is a one-lined reply.
809 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
811 eReadState Finished = eBufferNotEmpty;
813 while (Finished == eBufferNotEmpty) {
814 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
817 case eMustReadMore: /// read new from socket...
820 case eBufferNotEmpty: /* shouldn't happen... */
821 case eReadSuccess: /// done for now...
822 if (StrLength(IO->IOBuf) < 4)
824 if (ChrPtr(IO->IOBuf)[3] == '-')
825 Finished = eBufferNotEmpty;
829 case eReadFail: /// WHUT?
839 CTDL_MODULE_INIT(smtp_eventclient)
841 return "smtpeventclient";