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) {
479 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
480 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
482 /* Start building our bounce message; go shopping for memory first. */
483 BounceMB = NewStrBufPlain(NULL,
484 1024 + /* mime stuff.... */
485 StrLength(Msg) + /* the bounce information... */
486 StrLength(OMsgTxt)); /* the original message */
487 if (BounceMB == NULL) {
488 FreeStrBuf(&boundary);
489 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
494 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
496 FreeStrBuf(&boundary);
497 FreeStrBuf(&BounceMB);
498 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
502 memset(bmsg, 0, sizeof(struct CtdlMessage));
505 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
506 StrBufAppendBuf(BounceMB, boundary, 0);
507 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
508 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
509 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
510 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
511 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
512 StrBufAppendBuf(BounceMB, boundary, 0);
513 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
514 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
517 StrBufAppendBufPlain(
520 "A message you sent could not be delivered to some or all of its recipients\n"
521 "due to prolonged unavailability of its destination(s).\n"
522 "Giving up on the following addresses:\n\n"
525 StrBufAppendBufPlain(
528 "A message you sent could not be delivered to some or all of its recipients.\n"
529 "The following addresses were undeliverable:\n\n"
532 StrBufAppendBuf(BounceMB, Msg, 0);
535 /* Attach the original message */
536 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
537 StrBufAppendBuf(BounceMB, boundary, 0);
538 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
539 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
540 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
541 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
542 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
543 StrBufAppendBuf(BounceMB, OMsgTxt, 0);
545 /* Close the multipart MIME scope */
546 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
547 StrBufAppendBuf(BounceMB, boundary, 0);
548 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
552 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
553 bmsg->cm_anon_type = MES_NORMAL;
554 bmsg->cm_format_type = FMT_RFC822;
556 bmsg->cm_fields['O'] = strdup(MAILROOM);
557 bmsg->cm_fields['A'] = strdup("Citadel");
558 bmsg->cm_fields['N'] = strdup(config.c_nodename);
559 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
560 bmsg->cm_fields['M'] = SmashStrBuf(&BounceMB);
562 /* First try the user who sent the message */
563 if (StrLength(MyQItem->BounceTo) == 0)
564 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
566 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", ChrPtr(MyQItem->BounceTo));
568 /* Can we deliver the bounce to the original sender? */
569 valid = validate_recipients(ChrPtr(MyQItem->BounceTo), NULL, 0);
570 if ((valid != NULL) && (valid->num_error == 0)) {
571 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
572 successful_bounce = 1;
575 /* If not, post it in the Aide> room */
576 if (successful_bounce == 0) {
577 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
580 /* Free up the memory we used */
581 free_recipients(valid);
582 FreeStrBuf(&boundary);
583 CtdlFreeMessage(bmsg);
584 CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
590 * Called by smtp_do_queue() to handle an individual message.
592 void smtp_do_procmsg(long msgnum, void *userdata) {
593 struct CtdlMessage *msg = NULL;
603 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
604 ///strcpy(envelope_from, "");
606 msg = CtdlFetchMessage(msgnum, 1);
608 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
612 pch = instr = msg->cm_fields['M'];
614 /* Strip out the headers (no not amd any other non-instruction) line */
615 while (pch != NULL) {
616 pch = strchr(pch, '\n');
617 if ((pch != NULL) && (*(pch + 1) == '\n')) {
622 PlainQItem = NewStrBufPlain(instr, -1);
623 CtdlFreeMessage(msg);
624 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
625 FreeStrBuf(&PlainQItem);
627 if (MyQItem == NULL) {
628 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
629 return; /* s.b. else is already processing... */
633 * Postpone delivery if we've already tried recently.
635 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
636 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
638 It = GetNewHashPos(MyQItem->MailQEntries, 0);
639 citthread_mutex_lock(&ActiveQItemsLock);
641 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
642 DeleteEntryFromHash(ActiveQItems, It);
644 citthread_mutex_unlock(&ActiveQItemsLock);
645 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
648 }// TODO: reenable me.*/
651 * Bail out if there's no actual message associated with this
653 if (MyQItem->MessageID < 0L) {
654 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
655 It = GetNewHashPos(MyQItem->MailQEntries, 0);
656 citthread_mutex_lock(&ActiveQItemsLock);
658 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
659 DeleteEntryFromHash(ActiveQItems, It);
661 citthread_mutex_unlock(&ActiveQItemsLock);
663 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
667 It = GetNewHashPos(MyQItem->MailQEntries, 0);
668 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
670 MailQEntry *ThisItem = vQE;
671 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
675 CountActiveQueueEntries(MyQItem);
676 if (MyQItem->ActiveDeliveries > 0)
680 StrBuf *Msg = smtp_load_msg(MyQItem, n);
681 It = GetNewHashPos(MyQItem->MailQEntries, 0);
682 while ((i <= MyQItem->ActiveDeliveries) &&
683 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
685 MailQEntry *ThisItem = vQE;
686 if (ThisItem->Active == 1) {
687 if (i > 1) n = MsgCount++;
688 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
689 smtp_try(MyQItem, ThisItem, Msg, (i == MyQItem->ActiveDeliveries), n);
697 It = GetNewHashPos(MyQItem->MailQEntries, 0);
698 citthread_mutex_lock(&ActiveQItemsLock);
700 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
701 DeleteEntryFromHash(ActiveQItems, It);
703 citthread_mutex_unlock(&ActiveQItemsLock);
705 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
707 // TODO: bounce & delete?
715 * smtp_queue_thread()
717 * Run through the queue sending out messages.
719 void *smtp_queue_thread(void *arg) {
720 int num_processed = 0;
721 struct CitContext smtp_queue_CC;
725 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
726 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
727 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
729 while (!CtdlThreadCheckStop()) {
731 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
733 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
734 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
737 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
739 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
743 CtdlClearSystemContext();
750 * Initialize the SMTP outbound queue
752 void smtp_init_spoolout(void) {
753 struct ctdlroom qrbuf;
756 * Create the room. This will silently fail if the room already
757 * exists, and that's perfectly ok, because we want it to exist.
759 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
762 * Make sure it's set to be a "system room" so it doesn't show up
763 * in the <K>nown rooms list for Aides.
765 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
766 qrbuf.QRflags2 |= QR2_SYSTEM;
767 CtdlPutRoomLock(&qrbuf);
774 /*****************************************************************************/
775 /* SMTP UTILITY COMMANDS */
776 /*****************************************************************************/
778 void cmd_smtp(char *argbuf) {
785 if (CtdlAccessCheck(ac_aide)) return;
787 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
789 if (!strcasecmp(cmd, "mx")) {
790 extract_token(node, argbuf, 1, '|', sizeof node);
791 num_mxhosts = getmx(buf, node);
792 cprintf("%d %d MX hosts listed for %s\n",
793 LISTING_FOLLOWS, num_mxhosts, node);
794 for (i=0; i<num_mxhosts; ++i) {
795 extract_token(node, buf, i, '|', sizeof node);
796 cprintf("%s\n", node);
802 else if (!strcasecmp(cmd, "runqueue")) {
804 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
809 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
817 CTDL_MODULE_INIT(smtp_queu)
819 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
822 ActiveQItems = NewHash(1, Flathash);
823 citthread_mutex_init(&ActiveQItemsLock, NULL);
825 QItemHandlers = NewHash(0, NULL);
827 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
828 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
829 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
830 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
831 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
832 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
833 Put(QItemHandlers, HKEY("submitted"), QItem_Handle_Submitted, reference_free_handler);
834 ////TODO: flush qitemhandlers on exit
835 smtp_init_spoolout();
837 CtdlRegisterCleanupHook(smtp_evq_cleanup);
838 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
840 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
844 /* return our Subversion id for the Log */
845 return "smtpeventclient";