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 MyQItem->ActiveDeliveries--;
121 IDestructQueItem = MyQItem->ActiveDeliveries == 0;
122 return IDestructQueItem;
125 void RemoveQItem(OneQueItem *MyQItem)
129 It = GetNewHashPos(MyQItem->MailQEntries, 0);
130 citthread_mutex_lock(&ActiveQItemsLock);
132 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
133 DeleteEntryFromHash(ActiveQItems, It);
135 citthread_mutex_unlock(&ActiveQItemsLock);
140 void FreeMailQEntry(void *qv)
143 FreeStrBuf(&Q->Recipient);
144 FreeStrBuf(&Q->StatusMessage);
147 void FreeQueItem(OneQueItem **Item)
149 DeleteHash(&(*Item)->MailQEntries);
150 FreeStrBuf(&(*Item)->EnvelopeFrom);
151 FreeStrBuf(&(*Item)->BounceTo);
152 FreeURL(&(*Item)->URL);
156 void HFreeQueItem(void *Item)
158 FreeQueItem((OneQueItem**)&Item);
161 /* inspect recipients with a status of:
162 * - 0 (no delivery yet attempted)
163 * - 3/4 (transient errors
164 * were experienced and it's time to try again)
166 int CountActiveQueueEntries(OneQueItem *MyQItem)
173 MyQItem->ActiveDeliveries = 0;
174 It = GetNewHashPos(MyQItem->MailQEntries, 0);
175 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
177 MailQEntry *ThisItem = vQE;
178 if ((ThisItem->Status == 0) ||
179 (ThisItem->Status == 3) ||
180 (ThisItem->Status == 4))
182 MyQItem->ActiveDeliveries++;
183 ThisItem->Active = 1;
186 ThisItem->Active = 0;
189 return MyQItem->ActiveDeliveries;
192 OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID)
195 const char *pLine = NULL;
200 Item = (OneQueItem*)malloc(sizeof(OneQueItem));
201 memset(Item, 0, sizeof(OneQueItem));
202 Item->LastAttempt.retry = SMTP_RETRY_INTERVAL;
203 Item->MessageID = -1;
204 Item->QueMsgID = QueMsgID;
206 citthread_mutex_lock(&ActiveQItemsLock);
207 if (GetHash(ActiveQItems,
208 IKEY(Item->QueMsgID),
211 /* WHOOPS. somebody else is already working on this. */
212 citthread_mutex_unlock(&ActiveQItemsLock);
217 /* mark our claim on this. */
219 IKEY(Item->QueMsgID),
222 citthread_mutex_unlock(&ActiveQItemsLock);
226 Line = NewStrBufPlain(NULL, 128);
227 while (pLine != StrBufNOTNULL) {
228 const char *pItemPart = NULL;
231 StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n');
232 if (StrLength(Line) == 0) continue;
233 StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
234 if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
237 H = (QItemHandler) vHandler;
238 H(Item, Line, &pItemPart);
246 StrBuf *SerializeQueueItem(OneQueItem *MyQItem)
254 QMessage = NewStrBufPlain(NULL, SIZ);
255 StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME);
257 // "attempted|%ld\n" "retry|%ld\n",, (long)time(NULL), (long)retry );
258 StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0);
259 StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID);
261 StrBufAppendBufPlain(QMessage, HKEY("\nsubmitted|"), 0);
262 StrBufAppendPrintf(QMessage, "%ld", MyQItem->Submitted);
264 if (StrLength(MyQItem->BounceTo) > 0) {
265 StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0);
266 StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0);
269 if (StrLength(MyQItem->EnvelopeFrom) > 0) {
270 StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0);
271 StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0);
274 It = GetNewHashPos(MyQItem->MailQEntries, 0);
275 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
277 MailQEntry *ThisItem = vQE;
280 if (!ThisItem->Active)
281 continue; /* skip already sent ones from the spoolfile. */
283 for (i=0; i < ThisItem->nAttempts; i++) {
284 /* TODO: most probably there is just one retry/attempted per message! */
285 StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0);
286 StrBufAppendPrintf(QMessage, "%ld",
287 ThisItem->Attempts[i].retry);
289 StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0);
290 StrBufAppendPrintf(QMessage, "%ld",
291 ThisItem->Attempts[i].when);
293 StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0);
294 StrBufAppendBuf(QMessage, ThisItem->Recipient, 0);
295 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
296 StrBufAppendPrintf(QMessage, "%d", ThisItem->Status);
297 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
298 StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0);
301 StrBufAppendBufPlain(QMessage, HKEY("\n"), 0);
309 void NewMailQEntry(OneQueItem *Item)
311 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
312 memset(Item->Current, 0, sizeof(MailQEntry));
314 if (Item->MailQEntries == NULL)
315 Item->MailQEntries = NewHash(1, Flathash);
316 Item->Current->StatusMessage = NewStrBuf();
317 Item->Current->n = GetCount(Item->MailQEntries);
318 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
321 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
323 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
326 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
328 if (Item->EnvelopeFrom == NULL)
329 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
330 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
333 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
335 if (Item->BounceTo == NULL)
336 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
337 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
340 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
342 if (Item->Current == NULL)
344 if (Item->Current->Recipient == NULL)
345 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
346 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
347 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
348 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
349 Item->Current = NULL; // TODO: is this always right?
353 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
355 if (Item->Current == NULL)
357 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
358 Item->Current->nAttempts++;
359 if (Item->Current->nAttempts > MaxAttempts) {
363 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
367 void QItem_Handle_Submitted(OneQueItem *Item, StrBuf *Line, const char **Pos)
369 Item->Submitted = atol(*Pos);
373 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
375 if (Item->Current == NULL)
377 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
378 Item->Current->nAttempts++;
379 if (Item->Current->nAttempts > MaxAttempts) {
384 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
385 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
387 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
388 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
389 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
390 Item->LastAttempt.retry = SMTP_RETRY_MAX;
397 * this one has to have the context for loading the message via the redirect buffer...
399 StrBuf *smtp_load_msg(OneQueItem *MyQItem, int n)
404 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
405 CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
406 SendMsg = CCC->redirect_buffer;
407 CCC->redirect_buffer = NULL;
408 if ((StrLength(SendMsg) > 0) &&
409 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
410 CtdlLogPrintf(CTDL_WARNING,
411 "SMTP client[%d]: Possible problem: message did not "
412 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
413 MsgCount, //yes uncool, but best choice here...
414 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
415 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
423 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
424 * instructions for "5" codes (permanent fatal errors) and produce/deliver
425 * a "bounce" message (delivery status notification).
427 void smtpq_do_bounce(OneQueItem *MyQItem, StrBuf *OMsgTxt)
431 struct CtdlMessage *bmsg = NULL;
435 struct recptypes *valid;
442 int successful_bounce = 0;
446 CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
448 if ( (ev_time() - MyQItem->Submitted) > SMTP_GIVE_UP ) {
449 give_up = 1;/// TODO: replace time by libevq timer get
453 * Now go through the instructions checking for stuff.
455 It = GetNewHashPos(MyQItem->MailQEntries, 0);
456 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
458 MailQEntry *ThisItem = vQE;
459 if ((ThisItem->Status == 5) || /* failed now? */
460 ((give_up == 1) && (ThisItem->Status != 2))) /* giving up after failed attempts... */
462 if (num_bounces == 0)
463 Msg = NewStrBufPlain(NULL, 1024);
466 StrBufAppendBuf(Msg, ThisItem->Recipient, 0);
467 StrBufAppendBufPlain(Msg, HKEY(": "), 0);
468 StrBufAppendBuf(Msg, ThisItem->StatusMessage, 0);
469 StrBufAppendBufPlain(Msg, HKEY("\r\n"), 0);
474 /* Deliver the bounce if there's anything worth mentioning */
475 CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
477 if (num_bounces == 0) {
482 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
483 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
485 /* Start building our bounce message; go shopping for memory first. */
486 BounceMB = NewStrBufPlain(NULL,
487 1024 + /* mime stuff.... */
488 StrLength(Msg) + /* the bounce information... */
489 StrLength(OMsgTxt)); /* the original message */
490 if (BounceMB == NULL) {
491 FreeStrBuf(&boundary);
492 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
497 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
499 FreeStrBuf(&boundary);
500 FreeStrBuf(&BounceMB);
501 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
505 memset(bmsg, 0, sizeof(struct CtdlMessage));
508 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
509 StrBufAppendBuf(BounceMB, boundary, 0);
510 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
511 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
512 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
513 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
514 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
515 StrBufAppendBuf(BounceMB, boundary, 0);
516 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
517 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
520 StrBufAppendBufPlain(
523 "A message you sent could not be delivered to some or all of its recipients\n"
524 "due to prolonged unavailability of its destination(s).\n"
525 "Giving up on the following addresses:\n\n"
528 StrBufAppendBufPlain(
531 "A message you sent could not be delivered to some or all of its recipients.\n"
532 "The following addresses were undeliverable:\n\n"
535 StrBufAppendBuf(BounceMB, Msg, 0);
538 /* Attach the original message */
539 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
540 StrBufAppendBuf(BounceMB, boundary, 0);
541 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
542 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
543 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
544 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
545 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
546 StrBufAppendBuf(BounceMB, OMsgTxt, 0);
548 /* Close the multipart MIME scope */
549 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
550 StrBufAppendBuf(BounceMB, boundary, 0);
551 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
555 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
556 bmsg->cm_anon_type = MES_NORMAL;
557 bmsg->cm_format_type = FMT_RFC822;
559 bmsg->cm_fields['O'] = strdup(MAILROOM);
560 bmsg->cm_fields['A'] = strdup("Citadel");
561 bmsg->cm_fields['N'] = strdup(config.c_nodename);
562 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
563 bmsg->cm_fields['M'] = SmashStrBuf(&BounceMB);
565 /* First try the user who sent the message */
566 if (StrLength(MyQItem->BounceTo) == 0)
567 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
569 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", ChrPtr(MyQItem->BounceTo));
571 /* Can we deliver the bounce to the original sender? */
572 valid = validate_recipients(ChrPtr(MyQItem->BounceTo), NULL, 0);
573 if ((valid != NULL) && (valid->num_error == 0)) {
574 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
575 successful_bounce = 1;
578 /* If not, post it in the Aide> room */
579 if (successful_bounce == 0) {
580 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
583 /* Free up the memory we used */
584 free_recipients(valid);
585 FreeStrBuf(&boundary);
586 CtdlFreeMessage(bmsg);
587 CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
596 n_smarthosts = get_hosts(char *mxbuf, char *rectype);
602 * Called by smtp_do_queue() to handle an individual message.
604 void smtp_do_procmsg(long msgnum, void *userdata) {
605 struct CtdlMessage *msg = NULL;
615 ParsedURL *RelayUrls = NULL;
619 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
620 ///strcpy(envelope_from, "");
622 msg = CtdlFetchMessage(msgnum, 1);
624 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
628 pch = instr = msg->cm_fields['M'];
630 /* Strip out the headers (no not amd any other non-instruction) line */
631 while (pch != NULL) {
632 pch = strchr(pch, '\n');
633 if ((pch != NULL) && (*(pch + 1) == '\n')) {
638 PlainQItem = NewStrBufPlain(instr, -1);
639 CtdlFreeMessage(msg);
640 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
641 FreeStrBuf(&PlainQItem);
643 if (MyQItem == NULL) {
644 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
645 return; /* s.b. else is already processing... */
649 * Postpone delivery if we've already tried recently.
651 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
652 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
654 It = GetNewHashPos(MyQItem->MailQEntries, 0);
655 citthread_mutex_lock(&ActiveQItemsLock);
657 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
658 DeleteEntryFromHash(ActiveQItems, It);
660 citthread_mutex_unlock(&ActiveQItemsLock);
661 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
664 }// TODO: reenable me.*/
667 * Bail out if there's no actual message associated with this
669 if (MyQItem->MessageID < 0L) {
670 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
671 It = GetNewHashPos(MyQItem->MailQEntries, 0);
672 citthread_mutex_lock(&ActiveQItemsLock);
674 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
675 DeleteEntryFromHash(ActiveQItems, It);
677 citthread_mutex_unlock(&ActiveQItemsLock);
679 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
685 ParsedURL **Url = &MyQItem->URL;
686 nRelays = get_hosts(mxbuf, "smarthost");
690 const char *Pos = NULL;
691 All = NewStrBufPlain(mxbuf, -1);
692 One = NewStrBufPlain(NULL, StrLength(All) + 1);
694 while ((Pos != StrBufNOTNULL) && ((Pos == NULL) || !IsEmptyStr(Pos))) {
695 StrBufExtract_NextToken(One, All, &Pos, '|');
696 if (!ParseURL(Url, One, 25))
697 CtdlLogPrintf(CTDL_DEBUG, "Failed to parse: %s\n", ChrPtr(One));
699 ///if (!Url->IsIP)) /// todo dupe me fork ipv6
708 Url = &MyQItem->FallBackHost;
709 nRelays = get_hosts(mxbuf, "fallbackhost");
713 const char *Pos = NULL;
714 All = NewStrBufPlain(mxbuf, -1);
715 One = NewStrBufPlain(NULL, StrLength(All) + 1);
717 while ((Pos != StrBufNOTNULL) && ((Pos == NULL) || !IsEmptyStr(Pos))) {
718 StrBufExtract_NextToken(One, All, &Pos, '|');
719 if (!ParseURL(Url, One, 25))
720 CtdlLogPrintf(CTDL_DEBUG, "Failed to parse: %s\n", ChrPtr(One));
729 It = GetNewHashPos(MyQItem->MailQEntries, 0);
730 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
732 MailQEntry *ThisItem = vQE;
733 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
737 CountActiveQueueEntries(MyQItem);
738 if (MyQItem->ActiveDeliveries > 0)
741 int m = MyQItem->ActiveDeliveries;
743 Msg = smtp_load_msg(MyQItem, n);
744 It = GetNewHashPos(MyQItem->MailQEntries, 0);
746 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
748 MailQEntry *ThisItem = vQE;
749 if (ThisItem->Active == 1) {
750 int KeepBuffers = (i == m);
751 if (i > 1) n = MsgCount++;
752 CtdlLogPrintf(CTDL_DEBUG,
753 "SMTP Queue: Trying <%s> %d / %d \n",
754 ChrPtr(ThisItem->Recipient),
757 smtp_try_one_queue_entry(MyQItem,
763 if (KeepBuffers) HaveBuffers = 1;
771 It = GetNewHashPos(MyQItem->MailQEntries, 0);
772 citthread_mutex_lock(&ActiveQItemsLock);
774 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
775 DeleteEntryFromHash(ActiveQItems, It);
777 citthread_mutex_unlock(&ActiveQItemsLock);
779 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
781 // TODO: bounce & delete?
786 // TODO : free RelayUrls
793 * smtp_queue_thread()
795 * Run through the queue sending out messages.
797 void *smtp_queue_thread(void *arg) {
798 int num_processed = 0;
799 struct CitContext smtp_queue_CC;
803 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
804 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
805 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
807 while (!CtdlThreadCheckStop()) {
809 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
811 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
812 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
815 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
817 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
821 CtdlClearSystemContext();
828 * Initialize the SMTP outbound queue
830 void smtp_init_spoolout(void) {
831 struct ctdlroom qrbuf;
834 * Create the room. This will silently fail if the room already
835 * exists, and that's perfectly ok, because we want it to exist.
837 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
840 * Make sure it's set to be a "system room" so it doesn't show up
841 * in the <K>nown rooms list for Aides.
843 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
844 qrbuf.QRflags2 |= QR2_SYSTEM;
845 CtdlPutRoomLock(&qrbuf);
852 /*****************************************************************************/
853 /* SMTP UTILITY COMMANDS */
854 /*****************************************************************************/
856 void cmd_smtp(char *argbuf) {
863 if (CtdlAccessCheck(ac_aide)) return;
865 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
867 if (!strcasecmp(cmd, "mx")) {
868 extract_token(node, argbuf, 1, '|', sizeof node);
869 num_mxhosts = getmx(buf, node);
870 cprintf("%d %d MX hosts listed for %s\n",
871 LISTING_FOLLOWS, num_mxhosts, node);
872 for (i=0; i<num_mxhosts; ++i) {
873 extract_token(node, buf, i, '|', sizeof node);
874 cprintf("%s\n", node);
880 else if (!strcasecmp(cmd, "runqueue")) {
882 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
887 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
895 CTDL_MODULE_INIT(smtp_queu)
897 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
900 ActiveQItems = NewHash(1, Flathash);
901 citthread_mutex_init(&ActiveQItemsLock, NULL);
903 QItemHandlers = NewHash(0, NULL);
905 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
906 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
907 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
908 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
909 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
910 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
911 Put(QItemHandlers, HKEY("submitted"), QItem_Handle_Submitted, reference_free_handler);
912 ////TODO: flush qitemhandlers on exit
913 smtp_init_spoolout();
915 CtdlRegisterCleanupHook(smtp_evq_cleanup);
916 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
918 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
922 /* return our Subversion id for the Log */
923 return "smtpeventclient";