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"
92 citthread_mutex_t ActiveQItemsLock;
93 HashList *ActiveQItems = NULL;
94 HashList *QItemHandlers = NULL;
97 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
99 void smtp_try_one_queue_entry(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);
142 void FreeMailQEntry(void *qv)
145 FreeStrBuf(&Q->Recipient);
146 FreeStrBuf(&Q->StatusMessage);
149 void FreeQueItem(OneQueItem **Item)
151 DeleteHash(&(*Item)->MailQEntries);
152 FreeStrBuf(&(*Item)->EnvelopeFrom);
153 FreeStrBuf(&(*Item)->BounceTo);
154 FreeURL(&(*Item)->URL);
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,
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[%d]: 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");
598 n_smarthosts = get_hosts(char *mxbuf, char *rectype);
604 * Called by smtp_do_queue() to handle an individual message.
606 void smtp_do_procmsg(long msgnum, void *userdata) {
607 struct CtdlMessage *msg = NULL;
617 ParsedURL *RelayUrls = NULL;
621 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
622 ///strcpy(envelope_from, "");
624 msg = CtdlFetchMessage(msgnum, 1);
626 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
630 pch = instr = msg->cm_fields['M'];
632 /* Strip out the headers (no not amd any other non-instruction) line */
633 while (pch != NULL) {
634 pch = strchr(pch, '\n');
635 if ((pch != NULL) && (*(pch + 1) == '\n')) {
640 PlainQItem = NewStrBufPlain(instr, -1);
641 CtdlFreeMessage(msg);
642 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
643 FreeStrBuf(&PlainQItem);
645 if (MyQItem == NULL) {
646 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
647 return; /* s.b. else is already processing... */
651 * Postpone delivery if we've already tried recently.
653 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
654 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
656 It = GetNewHashPos(MyQItem->MailQEntries, 0);
657 citthread_mutex_lock(&ActiveQItemsLock);
659 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
660 DeleteEntryFromHash(ActiveQItems, It);
662 citthread_mutex_unlock(&ActiveQItemsLock);
663 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
666 }// TODO: reenable me.*/
669 * Bail out if there's no actual message associated with this
671 if (MyQItem->MessageID < 0L) {
672 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
673 It = GetNewHashPos(MyQItem->MailQEntries, 0);
674 citthread_mutex_lock(&ActiveQItemsLock);
676 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
677 DeleteEntryFromHash(ActiveQItems, It);
679 citthread_mutex_unlock(&ActiveQItemsLock);
681 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
687 ParsedURL **Url = &MyQItem->URL;
688 nRelays = get_hosts(mxbuf, "smarthost");
692 const char *Pos = NULL;
693 All = NewStrBufPlain(mxbuf, -1);
694 One = NewStrBufPlain(NULL, StrLength(All) + 1);
696 while ((Pos != StrBufNOTNULL) && ((Pos == NULL) || !IsEmptyStr(Pos))) {
697 StrBufExtract_NextToken(One, All, &Pos, '|');
698 if (!ParseURL(Url, One, 25))
699 CtdlLogPrintf(CTDL_DEBUG, "Failed to parse: %s\n", ChrPtr(One));
701 ///if (!Url->IsIP)) /// todo dupe me fork ipv6
710 Url = &MyQItem->FallBackHost;
711 nRelays = get_hosts(mxbuf, "fallbackhost");
715 const char *Pos = NULL;
716 All = NewStrBufPlain(mxbuf, -1);
717 One = NewStrBufPlain(NULL, StrLength(All) + 1);
719 while ((Pos != StrBufNOTNULL) && ((Pos == NULL) || !IsEmptyStr(Pos))) {
720 StrBufExtract_NextToken(One, All, &Pos, '|');
721 if (!ParseURL(Url, One, 25))
722 CtdlLogPrintf(CTDL_DEBUG, "Failed to parse: %s\n", ChrPtr(One));
731 It = GetNewHashPos(MyQItem->MailQEntries, 0);
732 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
734 MailQEntry *ThisItem = vQE;
735 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
739 CountActiveQueueEntries(MyQItem);
740 if (MyQItem->ActiveDeliveries > 0)
743 int m = MyQItem->ActiveDeliveries;
745 Msg = smtp_load_msg(MyQItem, n);
746 It = GetNewHashPos(MyQItem->MailQEntries, 0);
748 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
750 MailQEntry *ThisItem = vQE;
751 if (ThisItem->Active == 1) {
752 int KeepBuffers = (i == m);
753 if (i > 1) n = MsgCount++;
754 CtdlLogPrintf(CTDL_DEBUG,
755 "SMTP Queue: Trying <%s> %d / %d \n",
756 ChrPtr(ThisItem->Recipient),
759 smtp_try_one_queue_entry(MyQItem,
765 if (KeepBuffers) HaveBuffers = 1;
773 It = GetNewHashPos(MyQItem->MailQEntries, 0);
774 citthread_mutex_lock(&ActiveQItemsLock);
776 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
777 DeleteEntryFromHash(ActiveQItems, It);
779 citthread_mutex_unlock(&ActiveQItemsLock);
781 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
783 // TODO: bounce & delete?
788 // TODO : free RelayUrls
795 * smtp_queue_thread()
797 * Run through the queue sending out messages.
799 void *smtp_queue_thread(void *arg) {
800 int num_processed = 0;
801 struct CitContext smtp_queue_CC;
805 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
806 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
807 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
809 while (!CtdlThreadCheckStop()) {
811 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
813 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
814 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
817 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
819 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
823 CtdlClearSystemContext();
830 * Initialize the SMTP outbound queue
832 void smtp_init_spoolout(void) {
833 struct ctdlroom qrbuf;
836 * Create the room. This will silently fail if the room already
837 * exists, and that's perfectly ok, because we want it to exist.
839 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
842 * Make sure it's set to be a "system room" so it doesn't show up
843 * in the <K>nown rooms list for Aides.
845 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
846 qrbuf.QRflags2 |= QR2_SYSTEM;
847 CtdlPutRoomLock(&qrbuf);
854 /*****************************************************************************/
855 /* SMTP UTILITY COMMANDS */
856 /*****************************************************************************/
858 void cmd_smtp(char *argbuf) {
865 if (CtdlAccessCheck(ac_aide)) return;
867 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
869 if (!strcasecmp(cmd, "mx")) {
870 extract_token(node, argbuf, 1, '|', sizeof node);
871 num_mxhosts = getmx(buf, node);
872 cprintf("%d %d MX hosts listed for %s\n",
873 LISTING_FOLLOWS, num_mxhosts, node);
874 for (i=0; i<num_mxhosts; ++i) {
875 extract_token(node, buf, i, '|', sizeof node);
876 cprintf("%s\n", node);
882 else if (!strcasecmp(cmd, "runqueue")) {
884 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
889 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
897 CTDL_MODULE_INIT(smtp_queu)
899 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
902 ActiveQItems = NewHash(1, lFlathash);
903 citthread_mutex_init(&ActiveQItemsLock, NULL);
905 QItemHandlers = NewHash(0, NULL);
907 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
908 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
909 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
910 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
911 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
912 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
913 Put(QItemHandlers, HKEY("submitted"), QItem_Handle_Submitted, reference_free_handler);
914 ////TODO: flush qitemhandlers on exit
915 smtp_init_spoolout();
917 CtdlRegisterCleanupHook(smtp_evq_cleanup);
918 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
920 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
924 /* return our Subversion id for the Log */
925 return "smtpeventclient";