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 "smtpqueue.h"
89 #include "event_client.h"
91 HashList *QItemHandlers = NULL;
93 citthread_mutex_t ActiveQItemsLock;
94 HashList *ActiveQItems = NULL;
97 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
99 void smtp_try(OneQueItem *MyQItem,
100 MailQEntry *MyQEntry,
102 int KeepMsgText, /* KeepMsgText allows us to use MsgText as ours. */
104 ParsedURL *RelayUrls);
107 void smtp_evq_cleanup(void)
109 citthread_mutex_lock(&ActiveQItemsLock);
110 DeleteHash(&QItemHandlers);
111 DeleteHash(&ActiveQItems);
112 citthread_mutex_unlock(&ActiveQItemsLock);
113 citthread_mutex_destroy(&ActiveQItemsLock);
116 int DecreaseQReference(OneQueItem *MyQItem)
118 int IDestructQueItem;
120 citthread_mutex_lock(&ActiveQItemsLock);
121 MyQItem->ActiveDeliveries--;
122 IDestructQueItem = MyQItem->ActiveDeliveries == 0;
123 citthread_mutex_unlock(&ActiveQItemsLock);
124 return IDestructQueItem;
127 void RemoveQItem(OneQueItem *MyQItem)
131 It = GetNewHashPos(MyQItem->MailQEntries, 0);
132 citthread_mutex_lock(&ActiveQItemsLock);
134 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
135 DeleteEntryFromHash(ActiveQItems, It);
137 citthread_mutex_unlock(&ActiveQItemsLock);
143 void FreeMailQEntry(void *qv)
146 FreeStrBuf(&Q->Recipient);
147 FreeStrBuf(&Q->StatusMessage);
150 void FreeQueItem(OneQueItem **Item)
152 DeleteHash(&(*Item)->MailQEntries);
153 FreeStrBuf(&(*Item)->EnvelopeFrom);
154 FreeStrBuf(&(*Item)->BounceTo);
158 void HFreeQueItem(void *Item)
160 FreeQueItem((OneQueItem**)&Item);
163 /* inspect recipients with a status of:
164 * - 0 (no delivery yet attempted)
165 * - 3/4 (transient errors
166 * were experienced and it's time to try again)
168 int CountActiveQueueEntries(OneQueItem *MyQItem)
175 MyQItem->ActiveDeliveries = 0;
176 It = GetNewHashPos(MyQItem->MailQEntries, 0);
177 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
179 MailQEntry *ThisItem = vQE;
180 if ((ThisItem->Status == 0) ||
181 (ThisItem->Status == 3) ||
182 (ThisItem->Status == 4))
184 MyQItem->ActiveDeliveries++;
185 ThisItem->Active = 1;
188 ThisItem->Active = 0;
191 return MyQItem->ActiveDeliveries;
194 OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID)
197 const char *pLine = NULL;
202 Item = (OneQueItem*)malloc(sizeof(OneQueItem));
203 memset(Item, 0, sizeof(OneQueItem));
204 Item->LastAttempt.retry = SMTP_RETRY_INTERVAL;
205 Item->MessageID = -1;
206 Item->QueMsgID = QueMsgID;
208 citthread_mutex_lock(&ActiveQItemsLock);
209 if (GetHash(ActiveQItems,
210 IKEY(Item->QueMsgID),
213 /* WHOOPS. somebody else is already working on this. */
214 citthread_mutex_unlock(&ActiveQItemsLock);
219 /* mark our claim on this. */
221 IKEY(Item->QueMsgID),
224 citthread_mutex_unlock(&ActiveQItemsLock);
228 Line = NewStrBufPlain(NULL, 128);
229 while (pLine != StrBufNOTNULL) {
230 const char *pItemPart = NULL;
233 StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n');
234 if (StrLength(Line) == 0) continue;
235 StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
236 if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
239 H = (QItemHandler) vHandler;
240 H(Item, Line, &pItemPart);
248 StrBuf *SerializeQueueItem(OneQueItem *MyQItem)
256 QMessage = NewStrBufPlain(NULL, SIZ);
257 StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME);
259 // "attempted|%ld\n" "retry|%ld\n",, (long)time(NULL), (long)retry );
260 StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0);
261 StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID);
263 StrBufAppendBufPlain(QMessage, HKEY("\nsubmitted|"), 0);
264 StrBufAppendPrintf(QMessage, "%ld", MyQItem->Submitted);
266 if (StrLength(MyQItem->BounceTo) > 0) {
267 StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0);
268 StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0);
271 if (StrLength(MyQItem->EnvelopeFrom) > 0) {
272 StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0);
273 StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0);
276 It = GetNewHashPos(MyQItem->MailQEntries, 0);
277 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
279 MailQEntry *ThisItem = vQE;
282 if (!ThisItem->Active)
283 continue; /* skip already sent ones from the spoolfile. */
285 for (i=0; i < ThisItem->nAttempts; i++) {
286 /* TODO: most probably there is just one retry/attempted per message! */
287 StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0);
288 StrBufAppendPrintf(QMessage, "%ld",
289 ThisItem->Attempts[i].retry);
291 StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0);
292 StrBufAppendPrintf(QMessage, "%ld",
293 ThisItem->Attempts[i].when);
295 StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0);
296 StrBufAppendBuf(QMessage, ThisItem->Recipient, 0);
297 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
298 StrBufAppendPrintf(QMessage, "%d", ThisItem->Status);
299 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
300 StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0);
303 StrBufAppendBufPlain(QMessage, HKEY("\n"), 0);
311 void NewMailQEntry(OneQueItem *Item)
313 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
314 memset(Item->Current, 0, sizeof(MailQEntry));
316 if (Item->MailQEntries == NULL)
317 Item->MailQEntries = NewHash(1, Flathash);
318 Item->Current->StatusMessage = NewStrBuf();
319 Item->Current->n = GetCount(Item->MailQEntries);
320 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
323 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
325 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
328 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
330 if (Item->EnvelopeFrom == NULL)
331 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
332 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
335 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
337 if (Item->BounceTo == NULL)
338 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
339 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
342 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
344 if (Item->Current == NULL)
346 if (Item->Current->Recipient == NULL)
347 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
348 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
349 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
350 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
351 Item->Current = NULL; // TODO: is this always right?
355 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
357 if (Item->Current == NULL)
359 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
360 Item->Current->nAttempts++;
361 if (Item->Current->nAttempts > MaxAttempts) {
365 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
369 void QItem_Handle_Submitted(OneQueItem *Item, StrBuf *Line, const char **Pos)
371 Item->Submitted = atol(*Pos);
375 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
377 if (Item->Current == NULL)
379 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
380 Item->Current->nAttempts++;
381 if (Item->Current->nAttempts > MaxAttempts) {
386 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
387 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
389 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
390 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
391 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
392 Item->LastAttempt.retry = SMTP_RETRY_MAX;
399 * this one has to have the context for loading the message via the redirect buffer...
401 StrBuf *smtp_load_msg(OneQueItem *MyQItem, int n)
406 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
407 CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
408 SendMsg = CCC->redirect_buffer;
409 CCC->redirect_buffer = NULL;
410 if ((StrLength(SendMsg) > 0) &&
411 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
412 CtdlLogPrintf(CTDL_WARNING,
413 "SMTP client[%ld]: Possible problem: message did not "
414 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
415 MsgCount, //yes uncool, but best choice here...
416 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
417 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
425 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
426 * instructions for "5" codes (permanent fatal errors) and produce/deliver
427 * a "bounce" message (delivery status notification).
429 void smtpq_do_bounce(OneQueItem *MyQItem, StrBuf *OMsgTxt)
433 struct CtdlMessage *bmsg = NULL;
437 struct recptypes *valid;
444 int successful_bounce = 0;
448 CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
450 if ( (ev_time() - MyQItem->Submitted) > SMTP_GIVE_UP ) {
451 give_up = 1;/// TODO: replace time by libevq timer get
455 * Now go through the instructions checking for stuff.
457 It = GetNewHashPos(MyQItem->MailQEntries, 0);
458 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
460 MailQEntry *ThisItem = vQE;
461 if ((ThisItem->Status == 5) || /* failed now? */
462 ((give_up == 1) && (ThisItem->Status != 2))) /* giving up after failed attempts... */
464 if (num_bounces == 0)
465 Msg = NewStrBufPlain(NULL, 1024);
468 StrBufAppendBuf(Msg, ThisItem->Recipient, 0);
469 StrBufAppendBufPlain(Msg, HKEY(": "), 0);
470 StrBufAppendBuf(Msg, ThisItem->StatusMessage, 0);
471 StrBufAppendBufPlain(Msg, HKEY("\r\n"), 0);
476 /* Deliver the bounce if there's anything worth mentioning */
477 CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
479 if (num_bounces == 0) {
484 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
485 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
487 /* Start building our bounce message; go shopping for memory first. */
488 BounceMB = NewStrBufPlain(NULL,
489 1024 + /* mime stuff.... */
490 StrLength(Msg) + /* the bounce information... */
491 StrLength(OMsgTxt)); /* the original message */
492 if (BounceMB == NULL) {
493 FreeStrBuf(&boundary);
494 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
499 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
501 FreeStrBuf(&boundary);
502 FreeStrBuf(&BounceMB);
503 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
507 memset(bmsg, 0, sizeof(struct CtdlMessage));
510 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
511 StrBufAppendBuf(BounceMB, boundary, 0);
512 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
513 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
514 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
515 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
516 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
517 StrBufAppendBuf(BounceMB, boundary, 0);
518 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
519 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
522 StrBufAppendBufPlain(
525 "A message you sent could not be delivered to some or all of its recipients\n"
526 "due to prolonged unavailability of its destination(s).\n"
527 "Giving up on the following addresses:\n\n"
530 StrBufAppendBufPlain(
533 "A message you sent could not be delivered to some or all of its recipients.\n"
534 "The following addresses were undeliverable:\n\n"
537 StrBufAppendBuf(BounceMB, Msg, 0);
540 /* Attach the original message */
541 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
542 StrBufAppendBuf(BounceMB, boundary, 0);
543 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
544 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
545 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
546 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
547 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
548 StrBufAppendBuf(BounceMB, OMsgTxt, 0);
550 /* Close the multipart MIME scope */
551 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
552 StrBufAppendBuf(BounceMB, boundary, 0);
553 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
557 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
558 bmsg->cm_anon_type = MES_NORMAL;
559 bmsg->cm_format_type = FMT_RFC822;
561 bmsg->cm_fields['O'] = strdup(MAILROOM);
562 bmsg->cm_fields['A'] = strdup("Citadel");
563 bmsg->cm_fields['N'] = strdup(config.c_nodename);
564 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
565 bmsg->cm_fields['M'] = SmashStrBuf(&BounceMB);
567 /* First try the user who sent the message */
568 if (StrLength(MyQItem->BounceTo) == 0)
569 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
571 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", ChrPtr(MyQItem->BounceTo));
573 /* Can we deliver the bounce to the original sender? */
574 valid = validate_recipients(ChrPtr(MyQItem->BounceTo), NULL, 0);
575 if ((valid != NULL) && (valid->num_error == 0)) {
576 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
577 successful_bounce = 1;
580 /* If not, post it in the Aide> room */
581 if (successful_bounce == 0) {
582 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
585 /* Free up the memory we used */
586 free_recipients(valid);
587 FreeStrBuf(&boundary);
588 CtdlFreeMessage(bmsg);
589 CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
594 int ParseURL(ParsedURL **Url, StrBuf *UrlStr, short DefaultPort)
596 const char *pch, *pStartHost, *pEndHost, *pPort, *pCredEnd, *pUserEnd;
597 ParsedURL *url = (ParsedURL *)malloc(sizeof(ParsedURL));
598 memset(url, 0, sizeof(ParsedURL));
601 url->Port = DefaultPort;
603 * http://username:passvoid@[ipv6]:port/url
605 url->URL = NewStrBufDup(UrlStr);
606 pStartHost = pch = ChrPtr(url->URL);
607 url->LocalPart = strchr(pch, '/');
608 if (url->LocalPart != NULL) {
609 if ((*(url->LocalPart + 1) == '/') &&
610 (*(url->LocalPart + 2) == ':')) { /* TODO: find default port for this protocol... */
611 pStartHost = url->LocalPart + 3;
612 url->LocalPart = strchr(pStartHost, '/');
615 if (url->LocalPart == NULL) {
616 url->LocalPart = pch + StrLength(UrlStr);
619 pCredEnd = strchr(pch, '@');
620 if (pCredEnd >= url->LocalPart)
622 if (pCredEnd != NULL)
624 url->User = pStartHost;
625 pStartHost = pCredEnd + 1;
626 pUserEnd = strchr(url->User, ':');
628 if (pUserEnd > pCredEnd)
631 url->Pass = pUserEnd + 1;
633 StrBufPeek(UrlStr, pUserEnd, 0, '\0');
634 StrBufPeek(UrlStr, pCredEnd, 0, '\0');
638 if (*pStartHost == '[') {
640 pEndHost = strchr(pStartHost, ']');
641 if (pEndHost == NULL) {
643 return 0; /* invalid syntax, no ipv6 */
645 if (*(pEndHost + 1) == ':')
646 pPort = pEndHost + 2;
650 pPort = strchr(pStartHost, ':');
655 url->Port = atol(pPort);
656 url->IsIP = inet_pton(url->af, pStartHost, &url->Addr);
663 n_smarthosts = get_hosts(char *mxbuf, char *rectype);
669 * Called by smtp_do_queue() to handle an individual message.
671 void smtp_do_procmsg(long msgnum, void *userdata) {
672 struct CtdlMessage *msg = NULL;
682 ParsedURL *RelayUrls = NULL;
686 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
687 ///strcpy(envelope_from, "");
689 msg = CtdlFetchMessage(msgnum, 1);
691 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
695 pch = instr = msg->cm_fields['M'];
697 /* Strip out the headers (no not amd any other non-instruction) line */
698 while (pch != NULL) {
699 pch = strchr(pch, '\n');
700 if ((pch != NULL) && (*(pch + 1) == '\n')) {
705 PlainQItem = NewStrBufPlain(instr, -1);
706 CtdlFreeMessage(msg);
707 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
708 FreeStrBuf(&PlainQItem);
710 if (MyQItem == NULL) {
711 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
712 return; /* s.b. else is already processing... */
716 * Postpone delivery if we've already tried recently.
718 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
719 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
721 It = GetNewHashPos(MyQItem->MailQEntries, 0);
722 citthread_mutex_lock(&ActiveQItemsLock);
724 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
725 DeleteEntryFromHash(ActiveQItems, It);
727 citthread_mutex_unlock(&ActiveQItemsLock);
728 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
731 }// TODO: reenable me.*/
734 * Bail out if there's no actual message associated with this
736 if (MyQItem->MessageID < 0L) {
737 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
738 It = GetNewHashPos(MyQItem->MailQEntries, 0);
739 citthread_mutex_lock(&ActiveQItemsLock);
741 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
742 DeleteEntryFromHash(ActiveQItems, It);
744 citthread_mutex_unlock(&ActiveQItemsLock);
746 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
752 ParsedURL **Url = &RelayUrls; ///&MyQItem->Relay;
753 nRelays = get_hosts(mxbuf, "smarthost");
757 const char *Pos = NULL;
758 All = NewStrBufPlain(mxbuf, -1);
759 One = NewStrBufPlain(NULL, StrLength(All) + 1);
761 while (Pos != StrBufNOTNULL) {
762 StrBufExtract_NextToken(One, All, &Pos, '|');
763 if (!ParseURL(Url, One, 25))
764 CtdlLogPrintf(CTDL_DEBUG, "Failed to parse: %s\n", ChrPtr(One));
771 It = GetNewHashPos(MyQItem->MailQEntries, 0);
772 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
774 MailQEntry *ThisItem = vQE;
775 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
779 CountActiveQueueEntries(MyQItem);
780 if (MyQItem->ActiveDeliveries > 0)
784 Msg = smtp_load_msg(MyQItem, n);
785 It = GetNewHashPos(MyQItem->MailQEntries, 0);
786 while ((i <= MyQItem->ActiveDeliveries) &&
787 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
789 MailQEntry *ThisItem = vQE;
790 if (ThisItem->Active == 1) {
791 int KeepBuffers = (i == MyQItem->ActiveDeliveries);
792 if (i > 1) n = MsgCount++;
793 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
794 smtp_try(MyQItem, ThisItem, Msg, KeepBuffers, n, RelayUrls);
795 if (KeepBuffers) HaveBuffers = 1;
803 It = GetNewHashPos(MyQItem->MailQEntries, 0);
804 citthread_mutex_lock(&ActiveQItemsLock);
806 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
807 DeleteEntryFromHash(ActiveQItems, It);
809 citthread_mutex_unlock(&ActiveQItemsLock);
811 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
813 // TODO: bounce & delete?
818 // TODO : free RelayUrls
825 * smtp_queue_thread()
827 * Run through the queue sending out messages.
829 void *smtp_queue_thread(void *arg) {
830 int num_processed = 0;
831 struct CitContext smtp_queue_CC;
835 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
836 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
837 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
839 while (!CtdlThreadCheckStop()) {
841 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
843 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
844 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
847 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
849 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
853 CtdlClearSystemContext();
860 * Initialize the SMTP outbound queue
862 void smtp_init_spoolout(void) {
863 struct ctdlroom qrbuf;
866 * Create the room. This will silently fail if the room already
867 * exists, and that's perfectly ok, because we want it to exist.
869 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
872 * Make sure it's set to be a "system room" so it doesn't show up
873 * in the <K>nown rooms list for Aides.
875 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
876 qrbuf.QRflags2 |= QR2_SYSTEM;
877 CtdlPutRoomLock(&qrbuf);
884 /*****************************************************************************/
885 /* SMTP UTILITY COMMANDS */
886 /*****************************************************************************/
888 void cmd_smtp(char *argbuf) {
895 if (CtdlAccessCheck(ac_aide)) return;
897 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
899 if (!strcasecmp(cmd, "mx")) {
900 extract_token(node, argbuf, 1, '|', sizeof node);
901 num_mxhosts = getmx(buf, node);
902 cprintf("%d %d MX hosts listed for %s\n",
903 LISTING_FOLLOWS, num_mxhosts, node);
904 for (i=0; i<num_mxhosts; ++i) {
905 extract_token(node, buf, i, '|', sizeof node);
906 cprintf("%s\n", node);
912 else if (!strcasecmp(cmd, "runqueue")) {
914 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
919 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
927 CTDL_MODULE_INIT(smtp_queu)
929 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
932 ActiveQItems = NewHash(1, Flathash);
933 citthread_mutex_init(&ActiveQItemsLock, NULL);
935 QItemHandlers = NewHash(0, NULL);
937 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
938 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
939 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
940 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
941 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
942 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
943 Put(QItemHandlers, HKEY("submitted"), QItem_Handle_Submitted, reference_free_handler);
944 ////TODO: flush qitemhandlers on exit
945 smtp_init_spoolout();
947 CtdlRegisterCleanupHook(smtp_evq_cleanup);
948 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
950 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
954 /* return our Subversion id for the Log */
955 return "smtpeventclient";