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"
91 #include "smtp_clienthandlers.h"
93 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
94 const unsigned short DefaultMXPort = 25;
95 void DeleteSmtpOutMsg(void *v)
99 ares_free_data(Msg->AllMX);
100 if (Msg->HostLookup.VParsedDNSReply != NULL)
101 Msg->HostLookup.DNSReplyFree(Msg->HostLookup.VParsedDNSReply);
102 FreeStrBuf(&Msg->msgtext);
103 FreeAsyncIOContents(&Msg->IO);
107 eNextState SMTP_C_Shutdown(AsyncIO *IO);
108 eNextState SMTP_C_Timeout(AsyncIO *IO);
109 eNextState SMTP_C_ConnFail(AsyncIO *IO);
110 eNextState SMTP_C_DispatchReadDone(AsyncIO *IO);
111 eNextState SMTP_C_DispatchWriteDone(AsyncIO *IO);
112 eNextState SMTP_C_Terminate(AsyncIO *IO);
113 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO);
115 /******************************************************************************
116 * So, we're finished with sending (regardless of success or failure) *
117 * This Message might be referenced by several Queue-Items, if we're the last,*
118 * we need to free the memory and send bounce messages (on terminal failure) *
119 * else we just free our SMTP-Message struct. *
120 ******************************************************************************/
121 void FinalizeMessageSend(SmtpOutMsg *Msg)
123 CtdlLogPrintf(CTDL_DEBUG, "SMTP: %s\n", __FUNCTION__);
125 if (DecreaseQReference(Msg->MyQItem))
130 nRemain = CountActiveQueueEntries(Msg->MyQItem);
132 MsgData = SerializeQueueItem(Msg->MyQItem);
134 * Uncompleted delivery instructions remain, so delete the old
135 * instructions and replace with the updated ones.
137 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, "");
138 smtpq_do_bounce(Msg->MyQItem,
141 struct CtdlMessage *msg;
142 msg = malloc(sizeof(struct CtdlMessage));
143 memset(msg, 0, sizeof(struct CtdlMessage));
144 msg->cm_magic = CTDLMESSAGE_MAGIC;
145 msg->cm_anon_type = MES_NORMAL;
146 msg->cm_format_type = FMT_RFC822;
147 msg->cm_fields['M'] = SmashStrBuf(&MsgData);
148 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
149 CtdlFreeMessage(msg);
152 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->MessageID, 1, "");
153 FreeStrBuf(&MsgData);
156 RemoveQItem(Msg->MyQItem);
158 DeleteSmtpOutMsg(Msg);
161 eNextState FailOneAttempt(AsyncIO *IO)
164 * possible ways here:
165 * - connection timeout
173 void SetConnectStatus(AsyncIO *IO)
176 SmtpOutMsg *SendMsg = IO->Data;
183 src = &IO->Addr.sin6_addr;
186 struct sockaddr_in *addr = (struct sockaddr_in *)&IO->Addr;
188 src = &addr->sin_addr.s_addr;
191 inet_ntop((IO->IP6)?AF_INET6:AF_INET,
194 if (SendMsg->mx_host == NULL)
195 SendMsg->mx_host = "<no name>";
197 CtdlLogPrintf(CTDL_DEBUG,
198 "SMTP client[%ld]: connecting to %s [%s]:%d ...\n",
204 SendMsg->MyQEntry->Status = 5;
205 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
206 "Timeout while connecting %s [%s]:%d ",
212 /*****************************************************************************
213 * So we connect our Relay IP here. *
214 *****************************************************************************/
215 eNextState mx_connect_relay_ip(AsyncIO *IO)
218 SmtpOutMsg *SendMsg = IO->Data;
220 CtdlLogPrintf(CTDL_DEBUG, "SMTP: %s\n", __FUNCTION__);
222 IO->IP6 = SendMsg->pCurrRelay->af == AF_INET6;
224 if (SendMsg->pCurrRelay->Port != 0)
225 IO->dport = SendMsg->pCurrRelay->Port;
227 memset(&IO->Addr, 0, sizeof(struct sockaddr_in6));
229 memcpy(&IO->Addr.sin6_addr.s6_addr,
230 &SendMsg->pCurrRelay->Addr,
231 sizeof(struct in6_addr));
233 IO->Addr.sin6_family = AF_INET6;
234 IO->Addr.sin6_port = htons(IO->dport);
237 struct sockaddr_in *addr = (struct sockaddr_in*) &IO->Addr;
238 /* Bypass the ns lookup result like this: IO->Addr.sin_addr.s_addr = inet_addr("127.0.0.1"); */
239 memcpy(&addr->sin_addr,///.s_addr,
240 &SendMsg->pCurrRelay->Addr,
241 sizeof(struct in_addr));
243 addr->sin_family = AF_INET;
244 addr->sin_port = htons(IO->dport);
247 SetConnectStatus(IO);
249 return InitEventIO(IO, SendMsg,
251 SMTP_C_ReadTimeouts[0],
255 eNextState get_one_mx_host_ip_done(AsyncIO *IO)
257 SmtpOutMsg *SendMsg = IO->Data;
258 struct hostent *hostent;
262 hostent = SendMsg->HostLookup.VParsedDNSReply;
263 if ((SendMsg->HostLookup.DNSStatus == ARES_SUCCESS) &&
264 (hostent != NULL) ) {
266 IO->IP6 = hostent->h_addrtype == AF_INET6;
267 ////IO->HEnt = hostent;
269 memset(&IO->Addr, 0, sizeof(struct in6_addr));
271 memcpy(&IO->Addr.sin6_addr.s6_addr,
272 &hostent->h_addr_list[0],
273 sizeof(struct in6_addr));
275 IO->Addr.sin6_family = hostent->h_addrtype;
276 IO->Addr.sin6_port = htons(IO->dport);
279 struct sockaddr_in *addr = (struct sockaddr_in*) &IO->Addr;
280 /* Bypass the ns lookup result like this: IO->Addr.sin_addr.s_addr = inet_addr("127.0.0.1"); */
281 // addr->sin_addr.s_addr = htonl((uint32_t)&hostent->h_addr_list[0]);
282 memcpy(&addr->sin_addr.s_addr,
283 hostent->h_addr_list[0],
286 addr->sin_family = hostent->h_addrtype;
287 addr->sin_port = htons(IO->dport);
290 ////SendMsg->IO.HEnt = hostent;
291 SetConnectStatus(IO);
292 return InitEventIO(IO,
295 SMTP_C_ReadTimeouts[0],
298 else // TODO: here we need to find out whether there are more mx'es, backup relay, and so on
299 return SMTP_C_Terminate(IO);
304 if (SendMsg->pCurrRelay != NULL) {
305 SendMsg->mx_host = Hostname = SendMsg->pCurrRelay->Host;
306 if (SendMsg->pCurrRelay->Port != 0)
307 SendMsg->IO.dport = SendMsg->pCurrRelay->Port;
311 eNextState get_one_mx_host_ip(AsyncIO *IO)
313 SmtpOutMsg * SendMsg = IO->Data;
314 const char *Hostname;
319 * here we start with the lookup of one host. it might be...
320 * - the relay host *sigh*
321 * - the direct hostname if there was no mx record
327 if (SendMsg->mx_host != NULL) Hostname = SendMsg->mx_host;
328 else Hostname = SendMsg->node;
330 CtdlLogPrintf(CTDL_DEBUG, "SMTP: %s\n", __FUNCTION__);
332 CtdlLogPrintf(CTDL_DEBUG,
333 "SMTP client[%ld]: looking up %s : %d ...\n",
338 if (!QueueQuery(ns_t_a,
341 &SendMsg->HostLookup,
342 get_one_mx_host_ip_done))
344 SendMsg->MyQEntry->Status = 5;
345 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
346 "No MX hosts found for <%s>", SendMsg->node);
347 return IO->NextState;
349 return IO->NextState;
353 /*****************************************************************************
354 * here we try to find out about the MX records for our recipients. *
355 *****************************************************************************/
356 eNextState smtp_resolve_mx_record_done(AsyncIO *IO)
358 SmtpOutMsg * SendMsg = IO->Data;
363 CtdlLogPrintf(CTDL_DEBUG, "SMTP: %s\n", __FUNCTION__);
364 pp = &SendMsg->Relay;
365 while ((pp != NULL) && (*pp != NULL) && ((*pp)->Next != NULL))
368 if ((IO->DNSQuery->DNSStatus == ARES_SUCCESS) &&
369 (IO->DNSQuery->VParsedDNSReply != NULL))
370 { /* ok, we found mx records. */
371 SendMsg->IO.ErrMsg = SendMsg->MyQEntry->StatusMessage;
374 SendMsg->CurrMX = SendMsg->AllMX
375 = IO->DNSQuery->VParsedDNSReply;
376 while (SendMsg->CurrMX) {
378 for (i = 0; i < 2; i++) {
381 p = (ParsedURL*) malloc(sizeof(ParsedURL));
382 memset(p, 0, sizeof(ParsedURL));
384 p->Port = 25; //// TODO define.
386 p->Host = SendMsg->CurrMX->host;
391 SendMsg->CurrMX = SendMsg->CurrMX->next;
393 // SendMsg->mx_host = SendMsg->CurrMX->host;
394 // SendMsg->CurrMX = SendMsg->CurrMX->next;
395 SendMsg->CXFlags = SendMsg->CXFlags & F_HAVE_MX;
397 else { /* else fall back to the plain hostname */
399 for (i = 0; i < 2; i++) {
402 p = (ParsedURL*) malloc(sizeof(ParsedURL));
403 memset(p, 0, sizeof(ParsedURL));
405 p->Port = 25; //// TODO define.
407 p->Host = SendMsg->node;
412 /// SendMsg->mx_host = SendMsg->node;
413 SendMsg->CXFlags = SendMsg->CXFlags & F_DIRECT;
415 (*pp)->Next = SendMsg->MyQItem->FallBackHost;
416 return get_one_mx_host_ip(IO);
419 eNextState resolve_mx_records(AsyncIO *IO)
421 SmtpOutMsg * SendMsg = IO->Data;
423 CtdlLogPrintf(CTDL_DEBUG, "SMTP: %s\n", __FUNCTION__);
424 /* start resolving MX records here. */
425 if (!QueueQuery(ns_t_mx,
429 smtp_resolve_mx_record_done))
431 SendMsg->MyQEntry->Status = 5;
432 StrBufPrintf(SendMsg->MyQEntry->StatusMessage,
433 "No MX hosts found for <%s>", SendMsg->node);
434 return IO->NextState;
441 /******************************************************************************
442 * so, we're going to start a SMTP delivery. lets get it on. *
443 ******************************************************************************/
445 SmtpOutMsg *new_smtp_outmsg(OneQueItem *MyQItem,
446 MailQEntry *MyQEntry,
449 SmtpOutMsg * SendMsg;
451 SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
452 memset(SendMsg, 0, sizeof(SmtpOutMsg));
454 SendMsg->n = MsgCount;
455 SendMsg->MyQEntry = MyQEntry;
456 SendMsg->MyQItem = MyQItem;
457 SendMsg->pCurrRelay = MyQItem->URL;
459 SendMsg->IO.Data = SendMsg;
461 SendMsg->IO.SendDone = SMTP_C_DispatchWriteDone;
462 SendMsg->IO.ReadDone = SMTP_C_DispatchReadDone;
463 SendMsg->IO.Terminate = SMTP_C_Terminate;
464 SendMsg->IO.LineReader = SMTP_C_ReadServerStatus;
465 SendMsg->IO.ConnFail = SMTP_C_ConnFail;
466 SendMsg->IO.Timeout = SMTP_C_Timeout;
467 SendMsg->IO.ShutdownAbort = SMTP_C_Shutdown;
469 SendMsg->IO.SendBuf.Buf = NewStrBufPlain(NULL, 1024);
470 SendMsg->IO.RecvBuf.Buf = NewStrBufPlain(NULL, 1024);
471 SendMsg->IO.IOBuf = NewStrBuf();
473 SendMsg->IO.sock = (-1);
474 SendMsg->IO.NextState = eReadMessage;
475 SendMsg->IO.dport = DefaultMXPort;
480 void smtp_try_one_queue_entry(OneQueItem *MyQItem,
481 MailQEntry *MyQEntry,
483 int KeepMsgText, /* KeepMsgText allows us to use MsgText as ours. */
488 CtdlLogPrintf(CTDL_DEBUG, "SMTP: %s\n", __FUNCTION__);
490 SendMsg = new_smtp_outmsg(MyQItem, MyQEntry, MsgCount);
491 if (KeepMsgText) SendMsg->msgtext = MsgText;
492 else SendMsg->msgtext = NewStrBufDup(MsgText);
494 if (smtp_resolve_recipients(SendMsg)) {
495 if (SendMsg->pCurrRelay == NULL)
496 QueueEventContext(&SendMsg->IO,
498 else { /* oh... via relay host */
499 if (SendMsg->pCurrRelay->IsIP) {
500 QueueEventContext(&SendMsg->IO,
501 mx_connect_relay_ip);
503 else { /* uneducated admin has chosen to add DNS to the equation... */
504 QueueEventContext(&SendMsg->IO,
510 /* No recipients? well fail then. */
511 if ((SendMsg==NULL) ||
512 (SendMsg->MyQEntry == NULL)) {
513 SendMsg->MyQEntry->Status = 5;
514 StrBufPlain(SendMsg->MyQEntry->StatusMessage,
515 HKEY("Invalid Recipient!"));
517 FinalizeMessageSend(SendMsg);
526 /*****************************************************************************/
527 /* SMTP CLIENT DISPATCHER */
528 /*****************************************************************************/
530 void SMTPSetTimeout(eNextState NextTCPState, SmtpOutMsg *pMsg)
532 CtdlLogPrintf(CTDL_DEBUG, "SMTP: %s\n", __FUNCTION__);
534 switch (NextTCPState) {
537 Timeout = SMTP_C_SendTimeouts[pMsg->State];
538 if (pMsg->State == eDATABody) {
539 /* if we're sending a huge message, we need more time. */
540 Timeout += StrLength(pMsg->msgtext) / 1024;
544 Timeout = SMTP_C_ReadTimeouts[pMsg->State];
545 if (pMsg->State == eDATATerminateBody) {
547 * some mailservers take a nap before accepting the message
548 * content inspection and such.
550 Timeout += StrLength(pMsg->msgtext) / 1024;
553 case eTerminateConnection:
557 SetNextTimeout(&pMsg->IO, Timeout);
559 eNextState SMTP_C_DispatchReadDone(AsyncIO *IO)
561 CtdlLogPrintf(CTDL_DEBUG, "SMTP: %s\n", __FUNCTION__);
562 SmtpOutMsg *pMsg = IO->Data;
565 rc = ReadHandlers[pMsg->State](pMsg);
567 SMTPSetTimeout(rc, pMsg);
570 eNextState SMTP_C_DispatchWriteDone(AsyncIO *IO)
572 CtdlLogPrintf(CTDL_DEBUG, "SMTP: %s\n", __FUNCTION__);
573 SmtpOutMsg *pMsg = IO->Data;
576 rc = SendHandlers[pMsg->State](pMsg);
577 SMTPSetTimeout(rc, pMsg);
582 /*****************************************************************************/
583 /* SMTP CLIENT ERROR CATCHERS */
584 /*****************************************************************************/
585 eNextState SMTP_C_Terminate(AsyncIO *IO)
587 SmtpOutMsg *pMsg = IO->Data;
589 CtdlLogPrintf(CTDL_DEBUG, "SMTP: %s\n", __FUNCTION__);
590 FinalizeMessageSend(pMsg);
593 eNextState SMTP_C_Timeout(AsyncIO *IO)
595 SmtpOutMsg *pMsg = IO->Data;
597 CtdlLogPrintf(CTDL_DEBUG, "SMTP: %s\n", __FUNCTION__);
598 StrBufPlain(IO->ErrMsg, CKEY(ReadErrors[pMsg->State]));
599 return FailOneAttempt(IO);
601 eNextState SMTP_C_ConnFail(AsyncIO *IO)
603 SmtpOutMsg *pMsg = IO->Data;
605 CtdlLogPrintf(CTDL_DEBUG, "SMTP: %s\n", __FUNCTION__);
606 StrBufPlain(IO->ErrMsg, CKEY(ReadErrors[pMsg->State]));
607 return FailOneAttempt(IO);
609 eNextState SMTP_C_Shutdown(AsyncIO *IO)
611 CtdlLogPrintf(CTDL_DEBUG, "SMTP: %s\n", __FUNCTION__);
612 SmtpOutMsg *pMsg = IO->Data;
614 pMsg->MyQEntry->Status = 3;
615 StrBufPlain(pMsg->MyQEntry->StatusMessage, HKEY("server shutdown during message submit."));
616 FinalizeMessageSend(pMsg);
622 * @brief lineread Handler; understands when to read more SMTP lines, and when this is a one-lined reply.
624 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
626 eReadState Finished = eBufferNotEmpty;
628 while (Finished == eBufferNotEmpty) {
629 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
632 case eMustReadMore: /// read new from socket...
635 case eBufferNotEmpty: /* shouldn't happen... */
636 case eReadSuccess: /// done for now...
637 if (StrLength(IO->IOBuf) < 4)
639 if (ChrPtr(IO->IOBuf)[3] == '-')
640 Finished = eBufferNotEmpty;
644 case eReadFail: /// WHUT?
653 CTDL_MODULE_INIT(smtp_eventclient)
655 return "smtpeventclient";