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)
430 StrBuf *boundary, *Msg = NULL;
432 struct CtdlMessage *bmsg = NULL;
434 struct recptypes *valid;
435 int successful_bounce = 0;
443 CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
445 if ( (time(NULL) - MyQItem->Submitted) > SMTP_GIVE_UP ) {
446 give_up = 1;/// TODO: replace time by libevq timer get
450 * Now go through the instructions checking for stuff.
452 It = GetNewHashPos(MyQItem->MailQEntries, 0);
453 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
455 MailQEntry *ThisItem = vQE;
456 if ((ThisItem->Status == 5) || /* failed now? */
457 ((give_up == 1) && (ThisItem->Status != 2))) /* giving up after failed attempts... */
459 if (num_bounces == 0)
460 Msg = NewStrBufPlain(NULL, 1024);
463 StrBufAppendBuf(Msg, ThisItem->Recipient, 0);
464 StrBufAppendBufPlain(Msg, HKEY(": "), 0);
465 StrBufAppendBuf(Msg, ThisItem->StatusMessage, 0);
466 StrBufAppendBufPlain(Msg, HKEY("\r\n"), 0);
471 /* Deliver the bounce if there's anything worth mentioning */
472 CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
474 if (num_bounces == 0)
477 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
478 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
480 /* Start building our bounce message; go shopping for memory first. */
481 BounceMB = NewStrBufPlain(NULL,
482 1024 + /* mime stuff.... */
483 StrLength(Msg) + /* the bounce information... */
484 StrLength(OMsgTxt)); /* the original message */
485 if (BounceMB == NULL) {
486 FreeStrBuf(&boundary);
487 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
492 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
494 FreeStrBuf(&boundary);
495 FreeStrBuf(&BounceMB);
496 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
500 memset(bmsg, 0, sizeof(struct CtdlMessage));
503 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
504 StrBufAppendBuf(BounceMB, boundary, 0);
505 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
506 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
507 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
508 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
509 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
510 StrBufAppendBuf(BounceMB, boundary, 0);
511 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
512 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
515 StrBufAppendBufPlain(
518 "A message you sent could not be delivered to some or all of its recipients\n"
519 "due to prolonged unavailability of its destination(s).\n"
520 "Giving up on the following addresses:\n\n"
523 StrBufAppendBufPlain(
526 "A message you sent could not be delivered to some or all of its recipients.\n"
527 "The following addresses were undeliverable:\n\n"
530 StrBufAppendBuf(BounceMB, Msg, 0);
533 /* Attach the original message */
534 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
535 StrBufAppendBuf(BounceMB, boundary, 0);
536 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
537 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
538 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
539 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
540 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
541 StrBufAppendBuf(BounceMB, OMsgTxt, 0);
543 /* Close the multipart MIME scope */
544 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
545 StrBufAppendBuf(BounceMB, boundary, 0);
546 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
550 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
551 bmsg->cm_anon_type = MES_NORMAL;
552 bmsg->cm_format_type = FMT_RFC822;
554 bmsg->cm_fields['O'] = strdup(MAILROOM);
555 bmsg->cm_fields['N'] = strdup(config.c_nodename);
556 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
557 bmsg->cm_fields['A'] = SmashStrBuf(&BounceMB);
559 /* First try the user who sent the message */
560 if (StrLength(MyQItem->BounceTo) == 0)
561 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
563 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", ChrPtr(MyQItem->BounceTo));
565 /* Can we deliver the bounce to the original sender? */
566 valid = validate_recipients(ChrPtr(MyQItem->BounceTo), NULL, 0);
567 if ((valid != NULL) && (valid->num_error == 0)) {
568 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
569 successful_bounce = 1;
572 /* If not, post it in the Aide> room */
573 if (successful_bounce == 0) {
574 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
577 /* Free up the memory we used */
578 free_recipients(valid);
579 FreeStrBuf(&boundary);
580 CtdlFreeMessage(bmsg);
581 CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
587 * Called by smtp_do_queue() to handle an individual message.
589 void smtp_do_procmsg(long msgnum, void *userdata) {
590 struct CtdlMessage *msg = NULL;
600 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
601 ///strcpy(envelope_from, "");
603 msg = CtdlFetchMessage(msgnum, 1);
605 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
609 pch = instr = msg->cm_fields['M'];
611 /* Strip out the headers (no not amd any other non-instruction) line */
612 while (pch != NULL) {
613 pch = strchr(pch, '\n');
614 if ((pch != NULL) && (*(pch + 1) == '\n')) {
619 PlainQItem = NewStrBufPlain(instr, -1);
620 CtdlFreeMessage(msg);
621 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
622 FreeStrBuf(&PlainQItem);
624 if (MyQItem == NULL) {
625 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
626 return; /* s.b. else is already processing... */
630 * Postpone delivery if we've already tried recently.
632 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
633 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
635 It = GetNewHashPos(MyQItem->MailQEntries, 0);
636 citthread_mutex_lock(&ActiveQItemsLock);
638 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
639 DeleteEntryFromHash(ActiveQItems, It);
641 citthread_mutex_unlock(&ActiveQItemsLock);
642 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
645 }// TODO: reenable me.*/
648 * Bail out if there's no actual message associated with this
650 if (MyQItem->MessageID < 0L) {
651 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
652 It = GetNewHashPos(MyQItem->MailQEntries, 0);
653 citthread_mutex_lock(&ActiveQItemsLock);
655 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
656 DeleteEntryFromHash(ActiveQItems, It);
658 citthread_mutex_unlock(&ActiveQItemsLock);
660 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
664 It = GetNewHashPos(MyQItem->MailQEntries, 0);
665 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
667 MailQEntry *ThisItem = vQE;
668 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
672 CountActiveQueueEntries(MyQItem);
673 if (MyQItem->ActiveDeliveries > 0)
677 StrBuf *Msg = smtp_load_msg(MyQItem, n);
678 It = GetNewHashPos(MyQItem->MailQEntries, 0);
679 while ((i <= MyQItem->ActiveDeliveries) &&
680 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
682 MailQEntry *ThisItem = vQE;
683 if (ThisItem->Active == 1) {
684 if (i > 1) n = MsgCount++;
685 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
686 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries), n);
694 It = GetNewHashPos(MyQItem->MailQEntries, 0);
695 citthread_mutex_lock(&ActiveQItemsLock);
697 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
698 DeleteEntryFromHash(ActiveQItems, It);
700 citthread_mutex_unlock(&ActiveQItemsLock);
702 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
704 // TODO: bounce & delete?
712 * smtp_queue_thread()
714 * Run through the queue sending out messages.
716 void *smtp_queue_thread(void *arg) {
717 int num_processed = 0;
718 struct CitContext smtp_queue_CC;
722 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
723 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
724 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
726 while (!CtdlThreadCheckStop()) {
728 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
730 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
731 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
734 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
736 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
740 CtdlClearSystemContext();
747 * Initialize the SMTP outbound queue
749 void smtp_init_spoolout(void) {
750 struct ctdlroom qrbuf;
753 * Create the room. This will silently fail if the room already
754 * exists, and that's perfectly ok, because we want it to exist.
756 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
759 * Make sure it's set to be a "system room" so it doesn't show up
760 * in the <K>nown rooms list for Aides.
762 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
763 qrbuf.QRflags2 |= QR2_SYSTEM;
764 CtdlPutRoomLock(&qrbuf);
771 /*****************************************************************************/
772 /* SMTP UTILITY COMMANDS */
773 /*****************************************************************************/
775 void cmd_smtp(char *argbuf) {
782 if (CtdlAccessCheck(ac_aide)) return;
784 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
786 if (!strcasecmp(cmd, "mx")) {
787 extract_token(node, argbuf, 1, '|', sizeof node);
788 num_mxhosts = getmx(buf, node);
789 cprintf("%d %d MX hosts listed for %s\n",
790 LISTING_FOLLOWS, num_mxhosts, node);
791 for (i=0; i<num_mxhosts; ++i) {
792 extract_token(node, buf, i, '|', sizeof node);
793 cprintf("%s\n", node);
799 else if (!strcasecmp(cmd, "runqueue")) {
801 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
806 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
814 CTDL_MODULE_INIT(smtp_queu)
816 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
819 ActiveQItems = NewHash(1, Flathash);
820 citthread_mutex_init(&ActiveQItemsLock, NULL);
822 QItemHandlers = NewHash(0, NULL);
824 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
825 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
826 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
827 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
828 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
829 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
830 Put(QItemHandlers, HKEY("submitted"), QItem_Handle_Submitted, reference_free_handler);
831 ////TODO: flush qitemhandlers on exit
832 smtp_init_spoolout();
834 CtdlRegisterCleanupHook(smtp_evq_cleanup);
835 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
837 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
841 /* return our Subversion id for the Log */
842 return "smtpeventclient";