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. */
106 void smtp_evq_cleanup(void)
108 citthread_mutex_lock(&ActiveQItemsLock);
109 DeleteHash(&QItemHandlers);
110 DeleteHash(&ActiveQItems);
111 citthread_mutex_unlock(&ActiveQItemsLock);
112 citthread_mutex_destroy(&ActiveQItemsLock);
115 int DecreaseQReference(OneQueItem *MyQItem)
117 int IDestructQueItem;
119 citthread_mutex_lock(&ActiveQItemsLock);
120 MyQItem->ActiveDeliveries--;
121 IDestructQueItem = MyQItem->ActiveDeliveries == 0;
122 citthread_mutex_unlock(&ActiveQItemsLock);
123 return IDestructQueItem;
126 void RemoveQItem(OneQueItem *MyQItem)
130 It = GetNewHashPos(MyQItem->MailQEntries, 0);
131 citthread_mutex_lock(&ActiveQItemsLock);
133 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
134 DeleteEntryFromHash(ActiveQItems, It);
136 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);
157 void HFreeQueItem(void *Item)
159 FreeQueItem((OneQueItem**)&Item);
162 /* inspect recipients with a status of:
163 * - 0 (no delivery yet attempted)
164 * - 3/4 (transient errors
165 * were experienced and it's time to try again)
167 int CountActiveQueueEntries(OneQueItem *MyQItem)
174 MyQItem->ActiveDeliveries = 0;
175 It = GetNewHashPos(MyQItem->MailQEntries, 0);
176 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
178 MailQEntry *ThisItem = vQE;
179 if ((ThisItem->Status == 0) ||
180 (ThisItem->Status == 3) ||
181 (ThisItem->Status == 4))
183 MyQItem->ActiveDeliveries++;
184 ThisItem->Active = 1;
187 ThisItem->Active = 0;
190 return MyQItem->ActiveDeliveries;
193 OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID)
196 const char *pLine = NULL;
201 Item = (OneQueItem*)malloc(sizeof(OneQueItem));
202 memset(Item, 0, sizeof(OneQueItem));
203 Item->LastAttempt.retry = SMTP_RETRY_INTERVAL;
204 Item->MessageID = -1;
205 Item->QueMsgID = QueMsgID;
207 citthread_mutex_lock(&ActiveQItemsLock);
208 if (GetHash(ActiveQItems,
209 IKEY(Item->QueMsgID),
212 /* WHOOPS. somebody else is already working on this. */
213 citthread_mutex_unlock(&ActiveQItemsLock);
218 /* mark our claim on this. */
220 IKEY(Item->QueMsgID),
223 citthread_mutex_unlock(&ActiveQItemsLock);
227 Line = NewStrBufPlain(NULL, 128);
228 while (pLine != StrBufNOTNULL) {
229 const char *pItemPart = NULL;
232 StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n');
233 if (StrLength(Line) == 0) continue;
234 StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
235 if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
238 H = (QItemHandler) vHandler;
239 H(Item, Line, &pItemPart);
247 StrBuf *SerializeQueueItem(OneQueItem *MyQItem)
255 QMessage = NewStrBufPlain(NULL, SIZ);
256 StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME);
258 // "attempted|%ld\n" "retry|%ld\n",, (long)time(NULL), (long)retry );
259 StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0);
260 StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID);
262 StrBufAppendBufPlain(QMessage, HKEY("\nsubmitted|"), 0);
263 StrBufAppendPrintf(QMessage, "%ld", MyQItem->Submitted);
265 if (StrLength(MyQItem->BounceTo) > 0) {
266 StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0);
267 StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0);
270 if (StrLength(MyQItem->EnvelopeFrom) > 0) {
271 StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0);
272 StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0);
275 It = GetNewHashPos(MyQItem->MailQEntries, 0);
276 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
278 MailQEntry *ThisItem = vQE;
281 if (!ThisItem->Active)
282 continue; /* skip already sent ones from the spoolfile. */
284 for (i=0; i < ThisItem->nAttempts; i++) {
285 /* TODO: most probably there is just one retry/attempted per message! */
286 StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0);
287 StrBufAppendPrintf(QMessage, "%ld",
288 ThisItem->Attempts[i].retry);
290 StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0);
291 StrBufAppendPrintf(QMessage, "%ld",
292 ThisItem->Attempts[i].when);
294 StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0);
295 StrBufAppendBuf(QMessage, ThisItem->Recipient, 0);
296 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
297 StrBufAppendPrintf(QMessage, "%d", ThisItem->Status);
298 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
299 StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0);
302 StrBufAppendBufPlain(QMessage, HKEY("\n"), 0);
310 void NewMailQEntry(OneQueItem *Item)
312 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
313 memset(Item->Current, 0, sizeof(MailQEntry));
315 if (Item->MailQEntries == NULL)
316 Item->MailQEntries = NewHash(1, Flathash);
317 Item->Current->StatusMessage = NewStrBuf();
318 Item->Current->n = GetCount(Item->MailQEntries);
319 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
322 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
324 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
327 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
329 if (Item->EnvelopeFrom == NULL)
330 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
331 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
334 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
336 if (Item->BounceTo == NULL)
337 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
338 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
341 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
343 if (Item->Current == NULL)
345 if (Item->Current->Recipient == NULL)
346 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
347 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
348 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
349 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
350 Item->Current = NULL; // TODO: is this always right?
354 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
356 if (Item->Current == NULL)
358 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
359 Item->Current->nAttempts++;
360 if (Item->Current->nAttempts > MaxAttempts) {
364 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
368 void QItem_Handle_Submitted(OneQueItem *Item, StrBuf *Line, const char **Pos)
370 Item->Submitted = atol(*Pos);
374 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
376 if (Item->Current == NULL)
378 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
379 Item->Current->nAttempts++;
380 if (Item->Current->nAttempts > MaxAttempts) {
385 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
386 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
388 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
389 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
390 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
391 Item->LastAttempt.retry = SMTP_RETRY_MAX;
398 * this one has to have the context for loading the message via the redirect buffer...
400 StrBuf *smtp_load_msg(OneQueItem *MyQItem, int n)
405 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
406 CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
407 SendMsg = CCC->redirect_buffer;
408 CCC->redirect_buffer = NULL;
409 if ((StrLength(SendMsg) > 0) &&
410 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
411 CtdlLogPrintf(CTDL_WARNING,
412 "SMTP client[%ld]: Possible problem: message did not "
413 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
414 MsgCount, //yes uncool, but best choice here...
415 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
416 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
424 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
425 * instructions for "5" codes (permanent fatal errors) and produce/deliver
426 * a "bounce" message (delivery status notification).
428 void smtpq_do_bounce(OneQueItem *MyQItem, StrBuf *OMsgTxt)
432 struct CtdlMessage *bmsg = NULL;
436 struct recptypes *valid;
443 int successful_bounce = 0;
447 CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
449 if ( (ev_time() - MyQItem->Submitted) > SMTP_GIVE_UP ) {
450 give_up = 1;/// TODO: replace time by libevq timer get
454 * Now go through the instructions checking for stuff.
456 It = GetNewHashPos(MyQItem->MailQEntries, 0);
457 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
459 MailQEntry *ThisItem = vQE;
460 if ((ThisItem->Status == 5) || /* failed now? */
461 ((give_up == 1) && (ThisItem->Status != 2))) /* giving up after failed attempts... */
463 if (num_bounces == 0)
464 Msg = NewStrBufPlain(NULL, 1024);
467 StrBufAppendBuf(Msg, ThisItem->Recipient, 0);
468 StrBufAppendBufPlain(Msg, HKEY(": "), 0);
469 StrBufAppendBuf(Msg, ThisItem->StatusMessage, 0);
470 StrBufAppendBufPlain(Msg, HKEY("\r\n"), 0);
475 /* Deliver the bounce if there's anything worth mentioning */
476 CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
478 if (num_bounces == 0) {
483 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
484 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
486 /* Start building our bounce message; go shopping for memory first. */
487 BounceMB = NewStrBufPlain(NULL,
488 1024 + /* mime stuff.... */
489 StrLength(Msg) + /* the bounce information... */
490 StrLength(OMsgTxt)); /* the original message */
491 if (BounceMB == NULL) {
492 FreeStrBuf(&boundary);
493 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
498 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
500 FreeStrBuf(&boundary);
501 FreeStrBuf(&BounceMB);
502 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
506 memset(bmsg, 0, sizeof(struct CtdlMessage));
509 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
510 StrBufAppendBuf(BounceMB, boundary, 0);
511 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
512 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
513 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
514 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
515 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
516 StrBufAppendBuf(BounceMB, boundary, 0);
517 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
518 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
521 StrBufAppendBufPlain(
524 "A message you sent could not be delivered to some or all of its recipients\n"
525 "due to prolonged unavailability of its destination(s).\n"
526 "Giving up on the following addresses:\n\n"
529 StrBufAppendBufPlain(
532 "A message you sent could not be delivered to some or all of its recipients.\n"
533 "The following addresses were undeliverable:\n\n"
536 StrBufAppendBuf(BounceMB, Msg, 0);
539 /* Attach the original message */
540 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
541 StrBufAppendBuf(BounceMB, boundary, 0);
542 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
543 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
544 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
545 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
546 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
547 StrBufAppendBuf(BounceMB, OMsgTxt, 0);
549 /* Close the multipart MIME scope */
550 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
551 StrBufAppendBuf(BounceMB, boundary, 0);
552 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
556 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
557 bmsg->cm_anon_type = MES_NORMAL;
558 bmsg->cm_format_type = FMT_RFC822;
560 bmsg->cm_fields['O'] = strdup(MAILROOM);
561 bmsg->cm_fields['A'] = strdup("Citadel");
562 bmsg->cm_fields['N'] = strdup(config.c_nodename);
563 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
564 bmsg->cm_fields['M'] = SmashStrBuf(&BounceMB);
566 /* First try the user who sent the message */
567 if (StrLength(MyQItem->BounceTo) == 0)
568 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
570 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", ChrPtr(MyQItem->BounceTo));
572 /* Can we deliver the bounce to the original sender? */
573 valid = validate_recipients(ChrPtr(MyQItem->BounceTo), NULL, 0);
574 if ((valid != NULL) && (valid->num_error == 0)) {
575 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
576 successful_bounce = 1;
579 /* If not, post it in the Aide> room */
580 if (successful_bounce == 0) {
581 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
584 /* Free up the memory we used */
585 free_recipients(valid);
586 FreeStrBuf(&boundary);
587 CtdlFreeMessage(bmsg);
588 CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
594 * Called by smtp_do_queue() to handle an individual message.
596 void smtp_do_procmsg(long msgnum, void *userdata) {
597 struct CtdlMessage *msg = NULL;
607 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
608 ///strcpy(envelope_from, "");
610 msg = CtdlFetchMessage(msgnum, 1);
612 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
616 pch = instr = msg->cm_fields['M'];
618 /* Strip out the headers (no not amd any other non-instruction) line */
619 while (pch != NULL) {
620 pch = strchr(pch, '\n');
621 if ((pch != NULL) && (*(pch + 1) == '\n')) {
626 PlainQItem = NewStrBufPlain(instr, -1);
627 CtdlFreeMessage(msg);
628 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
629 FreeStrBuf(&PlainQItem);
631 if (MyQItem == NULL) {
632 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
633 return; /* s.b. else is already processing... */
637 * Postpone delivery if we've already tried recently.
639 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
640 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
642 It = GetNewHashPos(MyQItem->MailQEntries, 0);
643 citthread_mutex_lock(&ActiveQItemsLock);
645 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
646 DeleteEntryFromHash(ActiveQItems, It);
648 citthread_mutex_unlock(&ActiveQItemsLock);
649 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
652 }// TODO: reenable me.*/
655 * Bail out if there's no actual message associated with this
657 if (MyQItem->MessageID < 0L) {
658 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
659 It = GetNewHashPos(MyQItem->MailQEntries, 0);
660 citthread_mutex_lock(&ActiveQItemsLock);
662 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
663 DeleteEntryFromHash(ActiveQItems, It);
665 citthread_mutex_unlock(&ActiveQItemsLock);
667 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
671 It = GetNewHashPos(MyQItem->MailQEntries, 0);
672 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
674 MailQEntry *ThisItem = vQE;
675 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
679 CountActiveQueueEntries(MyQItem);
680 if (MyQItem->ActiveDeliveries > 0)
684 StrBuf *Msg = smtp_load_msg(MyQItem, n);
685 It = GetNewHashPos(MyQItem->MailQEntries, 0);
686 while ((i <= MyQItem->ActiveDeliveries) &&
687 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
689 MailQEntry *ThisItem = vQE;
690 if (ThisItem->Active == 1) {
691 if (i > 1) n = MsgCount++;
692 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
693 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries), n);
701 It = GetNewHashPos(MyQItem->MailQEntries, 0);
702 citthread_mutex_lock(&ActiveQItemsLock);
704 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
705 DeleteEntryFromHash(ActiveQItems, It);
707 citthread_mutex_unlock(&ActiveQItemsLock);
709 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
711 // TODO: bounce & delete?
719 * smtp_queue_thread()
721 * Run through the queue sending out messages.
723 void *smtp_queue_thread(void *arg) {
724 int num_processed = 0;
725 struct CitContext smtp_queue_CC;
729 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
730 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
731 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
733 while (!CtdlThreadCheckStop()) {
735 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
737 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
738 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
741 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
743 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
747 CtdlClearSystemContext();
754 * Initialize the SMTP outbound queue
756 void smtp_init_spoolout(void) {
757 struct ctdlroom qrbuf;
760 * Create the room. This will silently fail if the room already
761 * exists, and that's perfectly ok, because we want it to exist.
763 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
766 * Make sure it's set to be a "system room" so it doesn't show up
767 * in the <K>nown rooms list for Aides.
769 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
770 qrbuf.QRflags2 |= QR2_SYSTEM;
771 CtdlPutRoomLock(&qrbuf);
778 /*****************************************************************************/
779 /* SMTP UTILITY COMMANDS */
780 /*****************************************************************************/
782 void cmd_smtp(char *argbuf) {
789 if (CtdlAccessCheck(ac_aide)) return;
791 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
793 if (!strcasecmp(cmd, "mx")) {
794 extract_token(node, argbuf, 1, '|', sizeof node);
795 num_mxhosts = getmx(buf, node);
796 cprintf("%d %d MX hosts listed for %s\n",
797 LISTING_FOLLOWS, num_mxhosts, node);
798 for (i=0; i<num_mxhosts; ++i) {
799 extract_token(node, buf, i, '|', sizeof node);
800 cprintf("%s\n", node);
806 else if (!strcasecmp(cmd, "runqueue")) {
808 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
813 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
821 CTDL_MODULE_INIT(smtp_queu)
823 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
826 ActiveQItems = NewHash(1, Flathash);
827 citthread_mutex_init(&ActiveQItemsLock, NULL);
829 QItemHandlers = NewHash(0, NULL);
831 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
832 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
833 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
834 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
835 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
836 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
837 Put(QItemHandlers, HKEY("submitted"), QItem_Handle_Submitted, reference_free_handler);
838 ////TODO: flush qitemhandlers on exit
839 smtp_init_spoolout();
841 CtdlRegisterCleanupHook(smtp_evq_cleanup);
842 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
844 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
848 /* return our Subversion id for the Log */
849 return "smtpeventclient";