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 "smtp_util.h"
89 #include "smtpqueue.h"
90 #include "event_client.h"
92 HashList *QItemHandlers = NULL;
94 citthread_mutex_t ActiveQItemsLock;
95 HashList *ActiveQItems = NULL;
98 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
100 void smtp_try(OneQueItem *MyQItem,
101 MailQEntry *MyQEntry,
103 int KeepMsgText, /* KeepMsgText allows us to use MsgText as ours. */
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)
431 StrBuf *boundary, *Msg = NULL;
433 struct CtdlMessage *bmsg = NULL;
435 struct recptypes *valid;
436 int successful_bounce = 0;
444 CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
446 if ( (time(NULL) - MyQItem->Submitted) > SMTP_GIVE_UP ) {
447 give_up = 1;/// TODO: replace time by libevq timer get
451 * Now go through the instructions checking for stuff.
453 It = GetNewHashPos(MyQItem->MailQEntries, 0);
454 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
456 MailQEntry *ThisItem = vQE;
457 if ((ThisItem->Status == 5) || /* failed now? */
458 ((give_up == 1) && (ThisItem->Status != 2))) /* giving up after failed attempts... */
460 if (num_bounces == 0)
461 Msg = NewStrBufPlain(NULL, 1024);
464 StrBufAppendBuf(Msg, ThisItem->Recipient, 0);
465 StrBufAppendBufPlain(Msg, HKEY(": "), 0);
466 StrBufAppendBuf(Msg, ThisItem->StatusMessage, 0);
467 StrBufAppendBufPlain(Msg, HKEY("\r\n"), 0);
472 /* Deliver the bounce if there's anything worth mentioning */
473 CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
475 if (num_bounces == 0)
478 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
479 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
481 /* Start building our bounce message; go shopping for memory first. */
482 BounceMB = NewStrBufPlain(NULL,
483 1024 + /* mime stuff.... */
484 StrLength(Msg) + /* the bounce information... */
485 StrLength(OMsgTxt)); /* the original message */
486 if (BounceMB == NULL) {
487 FreeStrBuf(&boundary);
488 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
493 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
495 FreeStrBuf(&boundary);
496 FreeStrBuf(&BounceMB);
497 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
501 memset(bmsg, 0, sizeof(struct CtdlMessage));
504 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
505 StrBufAppendBuf(BounceMB, boundary, 0);
506 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
507 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
508 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
509 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
510 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
511 StrBufAppendBuf(BounceMB, boundary, 0);
512 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
513 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
516 StrBufAppendBufPlain(
519 "A message you sent could not be delivered to some or all of its recipients\n"
520 "due to prolonged unavailability of its destination(s).\n"
521 "Giving up on the following addresses:\n\n"
524 StrBufAppendBufPlain(
527 "A message you sent could not be delivered to some or all of its recipients.\n"
528 "The following addresses were undeliverable:\n\n"
531 StrBufAppendBuf(BounceMB, Msg, 0);
534 /* Attach the original message */
535 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
536 StrBufAppendBuf(BounceMB, boundary, 0);
537 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
538 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
539 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
540 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
541 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
542 StrBufAppendBuf(BounceMB, OMsgTxt, 0);
544 /* Close the multipart MIME scope */
545 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
546 StrBufAppendBuf(BounceMB, boundary, 0);
547 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
551 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
552 bmsg->cm_anon_type = MES_NORMAL;
553 bmsg->cm_format_type = FMT_RFC822;
555 bmsg->cm_fields['O'] = strdup(MAILROOM);
556 bmsg->cm_fields['N'] = strdup(config.c_nodename);
557 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
558 bmsg->cm_fields['A'] = SmashStrBuf(&BounceMB);
560 /* First try the user who sent the message */
561 if (StrLength(MyQItem->BounceTo) == 0)
562 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
564 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", ChrPtr(MyQItem->BounceTo));
566 /* Can we deliver the bounce to the original sender? */
567 valid = validate_recipients(ChrPtr(MyQItem->BounceTo), NULL, 0);
568 if ((valid != NULL) && (valid->num_error == 0)) {
569 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
570 successful_bounce = 1;
573 /* If not, post it in the Aide> room */
574 if (successful_bounce == 0) {
575 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
578 /* Free up the memory we used */
579 free_recipients(valid);
580 FreeStrBuf(&boundary);
581 CtdlFreeMessage(bmsg);
582 CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
588 * Called by smtp_do_queue() to handle an individual message.
590 void smtp_do_procmsg(long msgnum, void *userdata) {
591 struct CtdlMessage *msg = NULL;
601 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
602 ///strcpy(envelope_from, "");
604 msg = CtdlFetchMessage(msgnum, 1);
606 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
610 pch = instr = msg->cm_fields['M'];
612 /* Strip out the headers (no not amd any other non-instruction) line */
613 while (pch != NULL) {
614 pch = strchr(pch, '\n');
615 if ((pch != NULL) && (*(pch + 1) == '\n')) {
620 PlainQItem = NewStrBufPlain(instr, -1);
621 CtdlFreeMessage(msg);
622 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
623 FreeStrBuf(&PlainQItem);
625 if (MyQItem == NULL) {
626 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
627 return; /* s.b. else is already processing... */
631 * Postpone delivery if we've already tried recently.
633 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
634 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
636 It = GetNewHashPos(MyQItem->MailQEntries, 0);
637 citthread_mutex_lock(&ActiveQItemsLock);
639 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
640 DeleteEntryFromHash(ActiveQItems, It);
642 citthread_mutex_unlock(&ActiveQItemsLock);
643 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
646 }// TODO: reenable me.*/
649 * Bail out if there's no actual message associated with this
651 if (MyQItem->MessageID < 0L) {
652 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
653 It = GetNewHashPos(MyQItem->MailQEntries, 0);
654 citthread_mutex_lock(&ActiveQItemsLock);
656 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
657 DeleteEntryFromHash(ActiveQItems, It);
659 citthread_mutex_unlock(&ActiveQItemsLock);
661 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
665 It = GetNewHashPos(MyQItem->MailQEntries, 0);
666 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
668 MailQEntry *ThisItem = vQE;
669 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
673 CountActiveQueueEntries(MyQItem);
674 if (MyQItem->ActiveDeliveries > 0)
678 StrBuf *Msg = smtp_load_msg(MyQItem, n);
679 It = GetNewHashPos(MyQItem->MailQEntries, 0);
680 while ((i <= MyQItem->ActiveDeliveries) &&
681 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
683 MailQEntry *ThisItem = vQE;
684 if (ThisItem->Active == 1) {
685 if (i > 1) n = MsgCount++;
686 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
687 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries), n);
695 It = GetNewHashPos(MyQItem->MailQEntries, 0);
696 citthread_mutex_lock(&ActiveQItemsLock);
698 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
699 DeleteEntryFromHash(ActiveQItems, It);
701 citthread_mutex_unlock(&ActiveQItemsLock);
703 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
705 // TODO: bounce & delete?
713 * smtp_queue_thread()
715 * Run through the queue sending out messages.
717 void *smtp_queue_thread(void *arg) {
718 int num_processed = 0;
719 struct CitContext smtp_queue_CC;
723 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
724 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
725 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
727 while (!CtdlThreadCheckStop()) {
729 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
731 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
732 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
735 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
737 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
741 CtdlClearSystemContext();
748 * Initialize the SMTP outbound queue
750 void smtp_init_spoolout(void) {
751 struct ctdlroom qrbuf;
754 * Create the room. This will silently fail if the room already
755 * exists, and that's perfectly ok, because we want it to exist.
757 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
760 * Make sure it's set to be a "system room" so it doesn't show up
761 * in the <K>nown rooms list for Aides.
763 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
764 qrbuf.QRflags2 |= QR2_SYSTEM;
765 CtdlPutRoomLock(&qrbuf);
772 /*****************************************************************************/
773 /* SMTP UTILITY COMMANDS */
774 /*****************************************************************************/
776 void cmd_smtp(char *argbuf) {
783 if (CtdlAccessCheck(ac_aide)) return;
785 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
787 if (!strcasecmp(cmd, "mx")) {
788 extract_token(node, argbuf, 1, '|', sizeof node);
789 num_mxhosts = getmx(buf, node);
790 cprintf("%d %d MX hosts listed for %s\n",
791 LISTING_FOLLOWS, num_mxhosts, node);
792 for (i=0; i<num_mxhosts; ++i) {
793 extract_token(node, buf, i, '|', sizeof node);
794 cprintf("%s\n", node);
800 else if (!strcasecmp(cmd, "runqueue")) {
802 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
807 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
815 CTDL_MODULE_INIT(smtp_queu)
817 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
820 ActiveQItems = NewHash(1, Flathash);
821 citthread_mutex_init(&ActiveQItemsLock, NULL);
823 QItemHandlers = NewHash(0, NULL);
825 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
826 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
827 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
828 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
829 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
830 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
831 Put(QItemHandlers, HKEY("submitted"), QItem_Handle_Submitted, reference_free_handler);
832 ////TODO: flush qitemhandlers on exit
833 smtp_init_spoolout();
835 CtdlRegisterCleanupHook(smtp_evq_cleanup);
836 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
838 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
842 /* return our Subversion id for the Log */
843 return "smtpeventclient";