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 struct CitContext smtp_queue_CC;
93 pthread_mutex_t ActiveQItemsLock;
94 HashList *ActiveQItems = NULL;
95 HashList *QItemHandlers = NULL;
98 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
100 void smtp_try_one_queue_entry(OneQueItem *MyQItem,
101 MailQEntry *MyQEntry,
103 int KeepMsgText, /* KeepMsgText allows us to use MsgText as ours. */
105 ParsedURL *RelayUrls);
108 void smtp_evq_cleanup(void)
111 pthread_mutex_lock(&ActiveQItemsLock);
112 DeleteHash(&QItemHandlers);
113 DeleteHash(&ActiveQItems);
114 pthread_mutex_unlock(&ActiveQItemsLock);
115 pthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
116 CtdlClearSystemContext();
117 /* citthread_mutex_destroy(&ActiveQItemsLock); TODO */
120 int DecreaseQReference(OneQueItem *MyQItem)
122 int IDestructQueItem;
124 pthread_mutex_lock(&ActiveQItemsLock);
125 MyQItem->ActiveDeliveries--;
126 IDestructQueItem = MyQItem->ActiveDeliveries == 0;
127 pthread_mutex_unlock(&ActiveQItemsLock);
128 return IDestructQueItem;
131 void RemoveQItem(OneQueItem *MyQItem)
135 It = GetNewHashPos(MyQItem->MailQEntries, 0);
136 pthread_mutex_lock(&ActiveQItemsLock);
138 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
139 DeleteEntryFromHash(ActiveQItems, It);
141 pthread_mutex_unlock(&ActiveQItemsLock);
146 void FreeMailQEntry(void *qv)
149 FreeStrBuf(&Q->Recipient);
150 FreeStrBuf(&Q->StatusMessage);
153 void FreeQueItem(OneQueItem **Item)
155 DeleteHash(&(*Item)->MailQEntries);
156 FreeStrBuf(&(*Item)->EnvelopeFrom);
157 FreeStrBuf(&(*Item)->BounceTo);
158 FreeURL(&(*Item)->URL);
162 void HFreeQueItem(void *Item)
164 FreeQueItem((OneQueItem**)&Item);
167 /* inspect recipients with a status of:
168 * - 0 (no delivery yet attempted)
169 * - 3/4 (transient errors
170 * were experienced and it's time to try again)
172 int CountActiveQueueEntries(OneQueItem *MyQItem)
179 MyQItem->ActiveDeliveries = 0;
180 It = GetNewHashPos(MyQItem->MailQEntries, 0);
181 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
183 MailQEntry *ThisItem = vQE;
184 if ((ThisItem->Status == 0) ||
185 (ThisItem->Status == 3) ||
186 (ThisItem->Status == 4))
188 MyQItem->ActiveDeliveries++;
189 ThisItem->Active = 1;
192 ThisItem->Active = 0;
195 return MyQItem->ActiveDeliveries;
198 OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID)
201 const char *pLine = NULL;
206 Item = (OneQueItem*)malloc(sizeof(OneQueItem));
207 memset(Item, 0, sizeof(OneQueItem));
208 Item->LastAttempt.retry = SMTP_RETRY_INTERVAL;
209 Item->MessageID = -1;
210 Item->QueMsgID = QueMsgID;
212 pthread_mutex_lock(&ActiveQItemsLock);
213 if (GetHash(ActiveQItems,
217 /* WHOOPS. somebody else is already working on this. */
218 pthread_mutex_unlock(&ActiveQItemsLock);
223 /* mark our claim on this. */
225 IKEY(Item->QueMsgID),
228 pthread_mutex_unlock(&ActiveQItemsLock);
232 Line = NewStrBufPlain(NULL, 128);
233 while (pLine != StrBufNOTNULL) {
234 const char *pItemPart = NULL;
237 StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n');
238 if (StrLength(Line) == 0) continue;
239 StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
240 if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
243 H = (QItemHandler) vHandler;
244 H(Item, Line, &pItemPart);
252 StrBuf *SerializeQueueItem(OneQueItem *MyQItem)
260 QMessage = NewStrBufPlain(NULL, SIZ);
261 StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME);
263 // "attempted|%ld\n" "retry|%ld\n",, (long)time(NULL), (long)retry );
264 StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0);
265 StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID);
267 StrBufAppendBufPlain(QMessage, HKEY("\nsubmitted|"), 0);
268 StrBufAppendPrintf(QMessage, "%ld", MyQItem->Submitted);
270 if (StrLength(MyQItem->BounceTo) > 0) {
271 StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0);
272 StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0);
275 if (StrLength(MyQItem->EnvelopeFrom) > 0) {
276 StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0);
277 StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0);
280 It = GetNewHashPos(MyQItem->MailQEntries, 0);
281 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
283 MailQEntry *ThisItem = vQE;
286 if (!ThisItem->Active)
287 continue; /* skip already sent ones from the spoolfile. */
289 for (i=0; i < ThisItem->nAttempts; i++) {
290 /* TODO: most probably there is just one retry/attempted per message! */
291 StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0);
292 StrBufAppendPrintf(QMessage, "%ld",
293 ThisItem->Attempts[i].retry);
295 StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0);
296 StrBufAppendPrintf(QMessage, "%ld",
297 ThisItem->Attempts[i].when);
299 StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0);
300 StrBufAppendBuf(QMessage, ThisItem->Recipient, 0);
301 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
302 StrBufAppendPrintf(QMessage, "%d", ThisItem->Status);
303 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
304 StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0);
307 StrBufAppendBufPlain(QMessage, HKEY("\n"), 0);
315 void NewMailQEntry(OneQueItem *Item)
317 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
318 memset(Item->Current, 0, sizeof(MailQEntry));
320 if (Item->MailQEntries == NULL)
321 Item->MailQEntries = NewHash(1, Flathash);
322 Item->Current->StatusMessage = NewStrBuf();
323 Item->Current->n = GetCount(Item->MailQEntries);
324 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
327 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
329 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
332 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
334 if (Item->EnvelopeFrom == NULL)
335 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
336 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
339 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
341 if (Item->BounceTo == NULL)
342 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
343 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
346 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
348 if (Item->Current == NULL)
350 if (Item->Current->Recipient == NULL)
351 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
352 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
353 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
354 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
355 Item->Current = NULL; // TODO: is this always right?
359 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
361 if (Item->Current == NULL)
363 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
364 Item->Current->nAttempts++;
365 if (Item->Current->nAttempts > MaxAttempts) {
369 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
373 void QItem_Handle_Submitted(OneQueItem *Item, StrBuf *Line, const char **Pos)
375 Item->Submitted = atol(*Pos);
379 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
381 if (Item->Current == NULL)
383 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
384 Item->Current->nAttempts++;
385 if (Item->Current->nAttempts > MaxAttempts) {
390 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
391 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
393 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
394 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
395 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
396 Item->LastAttempt.retry = SMTP_RETRY_MAX;
403 * this one has to have the context for loading the message via the redirect buffer...
405 StrBuf *smtp_load_msg(OneQueItem *MyQItem, int n)
410 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
411 CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
412 SendMsg = CCC->redirect_buffer;
413 CCC->redirect_buffer = NULL;
414 if ((StrLength(SendMsg) > 0) &&
415 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
417 "SMTP client[%d]: Possible problem: message did not "
418 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
419 MsgCount, //yes uncool, but best choice here...
420 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
421 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
429 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
430 * instructions for "5" codes (permanent fatal errors) and produce/deliver
431 * a "bounce" message (delivery status notification).
433 void smtpq_do_bounce(OneQueItem *MyQItem, StrBuf *OMsgTxt)
437 struct CtdlMessage *bmsg = NULL;
441 struct recptypes *valid;
448 int successful_bounce = 0;
452 syslog(LOG_DEBUG, "smtp_do_bounce() called\n");
454 if ( (ev_time() - MyQItem->Submitted) > SMTP_GIVE_UP ) {
455 give_up = 1;/// TODO: replace time by libevq timer get
459 * Now go through the instructions checking for stuff.
461 It = GetNewHashPos(MyQItem->MailQEntries, 0);
462 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
464 MailQEntry *ThisItem = vQE;
465 if ((ThisItem->Status == 5) || /* failed now? */
466 ((give_up == 1) && (ThisItem->Status != 2))) /* giving up after failed attempts... */
468 if (num_bounces == 0)
469 Msg = NewStrBufPlain(NULL, 1024);
472 StrBufAppendBuf(Msg, ThisItem->Recipient, 0);
473 StrBufAppendBufPlain(Msg, HKEY(": "), 0);
474 StrBufAppendBuf(Msg, ThisItem->StatusMessage, 0);
475 StrBufAppendBufPlain(Msg, HKEY("\r\n"), 0);
480 /* Deliver the bounce if there's anything worth mentioning */
481 syslog(LOG_DEBUG, "num_bounces = %d\n", num_bounces);
483 if (num_bounces == 0) {
488 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
489 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
491 /* Start building our bounce message; go shopping for memory first. */
492 BounceMB = NewStrBufPlain(NULL,
493 1024 + /* mime stuff.... */
494 StrLength(Msg) + /* the bounce information... */
495 StrLength(OMsgTxt)); /* the original message */
496 if (BounceMB == NULL) {
497 FreeStrBuf(&boundary);
498 syslog(LOG_ERR, "Failed to alloc() bounce message.\n");
503 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
505 FreeStrBuf(&boundary);
506 FreeStrBuf(&BounceMB);
507 syslog(LOG_ERR, "Failed to alloc() bounce message.\n");
511 memset(bmsg, 0, sizeof(struct CtdlMessage));
514 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
515 StrBufAppendBuf(BounceMB, boundary, 0);
516 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
517 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
518 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
519 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
520 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
521 StrBufAppendBuf(BounceMB, boundary, 0);
522 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
523 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
526 StrBufAppendBufPlain(
529 "A message you sent could not be delivered to some or all of its recipients\n"
530 "due to prolonged unavailability of its destination(s).\n"
531 "Giving up on the following addresses:\n\n"
534 StrBufAppendBufPlain(
537 "A message you sent could not be delivered to some or all of its recipients.\n"
538 "The following addresses were undeliverable:\n\n"
541 StrBufAppendBuf(BounceMB, Msg, 0);
544 /* Attach the original message */
545 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
546 StrBufAppendBuf(BounceMB, boundary, 0);
547 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
548 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
549 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
550 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
551 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
552 StrBufAppendBuf(BounceMB, OMsgTxt, 0);
554 /* Close the multipart MIME scope */
555 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
556 StrBufAppendBuf(BounceMB, boundary, 0);
557 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
561 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
562 bmsg->cm_anon_type = MES_NORMAL;
563 bmsg->cm_format_type = FMT_RFC822;
565 bmsg->cm_fields['O'] = strdup(MAILROOM);
566 bmsg->cm_fields['A'] = strdup("Citadel");
567 bmsg->cm_fields['N'] = strdup(config.c_nodename);
568 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
569 bmsg->cm_fields['M'] = SmashStrBuf(&BounceMB);
571 /* First try the user who sent the message */
572 if (StrLength(MyQItem->BounceTo) == 0)
573 syslog(LOG_ERR, "No bounce address specified\n");
575 syslog(LOG_DEBUG, "bounce to user? <%s>\n", ChrPtr(MyQItem->BounceTo));
577 /* Can we deliver the bounce to the original sender? */
578 valid = validate_recipients(ChrPtr(MyQItem->BounceTo), NULL, 0);
579 if ((valid != NULL) && (valid->num_error == 0)) {
580 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
581 successful_bounce = 1;
584 /* If not, post it in the Aide> room */
585 if (successful_bounce == 0) {
586 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
589 /* Free up the memory we used */
590 free_recipients(valid);
591 FreeStrBuf(&boundary);
592 CtdlFreeMessage(bmsg);
593 syslog(LOG_DEBUG, "Done processing bounces\n");
602 n_smarthosts = get_hosts(char *mxbuf, char *rectype);
608 * Called by smtp_do_queue() to handle an individual message.
610 void smtp_do_procmsg(long msgnum, void *userdata) {
611 struct CtdlMessage *msg = NULL;
621 ParsedURL *RelayUrls = NULL;
625 syslog(LOG_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
626 ///strcpy(envelope_from, "");
628 msg = CtdlFetchMessage(msgnum, 1);
630 syslog(LOG_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
634 pch = instr = msg->cm_fields['M'];
636 /* Strip out the headers (no not amd any other non-instruction) line */
637 while (pch != NULL) {
638 pch = strchr(pch, '\n');
639 if ((pch != NULL) && (*(pch + 1) == '\n')) {
644 PlainQItem = NewStrBufPlain(instr, -1);
645 CtdlFreeMessage(msg);
646 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
647 FreeStrBuf(&PlainQItem);
649 if (MyQItem == NULL) {
650 syslog(LOG_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
651 return; /* s.b. else is already processing... */
655 * Postpone delivery if we've already tried recently.
657 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
658 syslog(LOG_DEBUG, "SMTP client: Retry time not yet reached.\n");
660 It = GetNewHashPos(MyQItem->MailQEntries, 0);
661 pthread_mutex_lock(&ActiveQItemsLock);
663 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
664 DeleteEntryFromHash(ActiveQItems, It);
666 pthread_mutex_unlock(&ActiveQItemsLock);
667 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
670 }// TODO: reenable me.*/
673 * Bail out if there's no actual message associated with this
675 if (MyQItem->MessageID < 0L) {
676 syslog(LOG_ERR, "SMTP Queue: no 'msgid' directive found!\n");
677 It = GetNewHashPos(MyQItem->MailQEntries, 0);
678 pthread_mutex_lock(&ActiveQItemsLock);
680 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
681 DeleteEntryFromHash(ActiveQItems, It);
683 pthread_mutex_unlock(&ActiveQItemsLock);
685 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
691 ParsedURL **Url = &MyQItem->URL;
692 nRelays = get_hosts(mxbuf, "smarthost");
696 const char *Pos = NULL;
697 All = NewStrBufPlain(mxbuf, -1);
698 One = NewStrBufPlain(NULL, StrLength(All) + 1);
700 while ((Pos != StrBufNOTNULL) && ((Pos == NULL) || !IsEmptyStr(Pos))) {
701 StrBufExtract_NextToken(One, All, &Pos, '|');
702 if (!ParseURL(Url, One, 25))
703 syslog(LOG_DEBUG, "Failed to parse: %s\n", ChrPtr(One));
705 ///if (!Url->IsIP)) /// todo dupe me fork ipv6
714 Url = &MyQItem->FallBackHost;
715 nRelays = get_hosts(mxbuf, "fallbackhost");
719 const char *Pos = NULL;
720 All = NewStrBufPlain(mxbuf, -1);
721 One = NewStrBufPlain(NULL, StrLength(All) + 1);
723 while ((Pos != StrBufNOTNULL) && ((Pos == NULL) || !IsEmptyStr(Pos))) {
724 StrBufExtract_NextToken(One, All, &Pos, '|');
725 if (!ParseURL(Url, One, 25))
726 syslog(LOG_DEBUG, "Failed to parse: %s\n", ChrPtr(One));
735 It = GetNewHashPos(MyQItem->MailQEntries, 0);
736 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
738 MailQEntry *ThisItem = vQE;
739 syslog(LOG_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
743 CountActiveQueueEntries(MyQItem);
744 if (MyQItem->ActiveDeliveries > 0)
747 int m = MyQItem->ActiveDeliveries;
749 Msg = smtp_load_msg(MyQItem, n);
750 It = GetNewHashPos(MyQItem->MailQEntries, 0);
752 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
754 MailQEntry *ThisItem = vQE;
755 if (ThisItem->Active == 1) {
756 int KeepBuffers = (i == m);
757 if (i > 1) n = MsgCount++;
759 "SMTP Queue: Trying <%s> %d / %d \n",
760 ChrPtr(ThisItem->Recipient),
763 smtp_try_one_queue_entry(MyQItem,
769 if (KeepBuffers) HaveBuffers = 1;
777 It = GetNewHashPos(MyQItem->MailQEntries, 0);
778 pthread_mutex_lock(&ActiveQItemsLock);
780 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
781 DeleteEntryFromHash(ActiveQItems, It);
783 pthread_mutex_unlock(&ActiveQItemsLock);
785 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
787 // TODO: bounce & delete?
792 // TODO : free RelayUrls
799 * smtp_queue_thread()
801 * Run through the queue sending out messages.
803 void smtp_do_queue(void) {
804 static int is_running = 0;
805 int num_processed = 0;
807 if (is_running) return; /* Concurrency check - only one can run */
810 pthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
811 syslog(LOG_INFO, "SMTP client: processing outbound queue");
813 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
814 syslog(LOG_ERR, "Cannot find room <%s>", SMTP_SPOOLOUT_ROOM);
817 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
819 syslog(LOG_INFO, "SMTP client: queue run completed; %d messages processed", num_processed);
827 * Initialize the SMTP outbound queue
829 void smtp_init_spoolout(void) {
830 struct ctdlroom qrbuf;
833 * Create the room. This will silently fail if the room already
834 * exists, and that's perfectly ok, because we want it to exist.
836 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
839 * Make sure it's set to be a "system room" so it doesn't show up
840 * in the <K>nown rooms list for Aides.
842 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
843 qrbuf.QRflags2 |= QR2_SYSTEM;
844 CtdlPutRoomLock(&qrbuf);
851 /*****************************************************************************/
852 /* SMTP UTILITY COMMANDS */
853 /*****************************************************************************/
855 void cmd_smtp(char *argbuf) {
862 if (CtdlAccessCheck(ac_aide)) return;
864 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
866 if (!strcasecmp(cmd, "mx")) {
867 extract_token(node, argbuf, 1, '|', sizeof node);
868 num_mxhosts = getmx(buf, node);
869 cprintf("%d %d MX hosts listed for %s\n",
870 LISTING_FOLLOWS, num_mxhosts, node);
871 for (i=0; i<num_mxhosts; ++i) {
872 extract_token(node, buf, i, '|', sizeof node);
873 cprintf("%s\n", node);
879 else if (!strcasecmp(cmd, "runqueue")) {
881 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
886 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
892 CTDL_MODULE_INIT(smtp_queu)
896 CtdlFillSystemContext(&smtp_queue_CC, "SMTP_Send");
897 ActiveQItems = NewHash(1, lFlathash);
898 pthread_mutex_init(&ActiveQItemsLock, NULL);
900 QItemHandlers = NewHash(0, NULL);
902 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
903 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
904 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
905 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
906 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
907 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
908 Put(QItemHandlers, HKEY("submitted"), QItem_Handle_Submitted, reference_free_handler);
909 ////TODO: flush qitemhandlers on exit
910 smtp_init_spoolout();
912 CtdlRegisterCleanupHook(smtp_evq_cleanup);
914 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
917 /* return our Subversion id for the Log */
918 return "smtpeventclient";