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 /* citthread_mutex_destroy(&ActiveQItemsLock); TODO */
119 int DecreaseQReference(OneQueItem *MyQItem)
121 int IDestructQueItem;
123 pthread_mutex_lock(&ActiveQItemsLock);
124 MyQItem->ActiveDeliveries--;
125 IDestructQueItem = MyQItem->ActiveDeliveries == 0;
126 pthread_mutex_unlock(&ActiveQItemsLock);
127 return IDestructQueItem;
130 void RemoveQItem(OneQueItem *MyQItem)
137 pthread_mutex_lock(&ActiveQItemsLock);
138 It = GetNewHashPos(ActiveQItems, 0);
139 if (GetHashPosFromKey(ActiveQItems, LKEY(MyQItem->MessageID), It))
140 DeleteEntryFromHash(ActiveQItems, It);
144 "SMTP cleanup: unable to find QItem with ID[%ld]",
146 while (GetNextHashPos(ActiveQItems, It, &len, &Key, &VData))
148 "SMTP cleanup: have_: ID[%ld]",
149 ((OneQueItem *)VData)->MessageID);
151 pthread_mutex_unlock(&ActiveQItemsLock);
156 void FreeMailQEntry(void *qv)
159 FreeStrBuf(&Q->Recipient);
160 FreeStrBuf(&Q->StatusMessage);
163 void FreeQueItem(OneQueItem **Item)
165 DeleteHash(&(*Item)->MailQEntries);
166 FreeStrBuf(&(*Item)->EnvelopeFrom);
167 FreeStrBuf(&(*Item)->BounceTo);
168 FreeURL(&(*Item)->URL);
172 void HFreeQueItem(void *Item)
174 FreeQueItem((OneQueItem**)&Item);
177 /* inspect recipients with a status of:
178 * - 0 (no delivery yet attempted)
179 * - 3/4 (transient errors
180 * were experienced and it's time to try again)
182 int CountActiveQueueEntries(OneQueItem *MyQItem)
189 MyQItem->ActiveDeliveries = 0;
190 It = GetNewHashPos(MyQItem->MailQEntries, 0);
191 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
193 MailQEntry *ThisItem = vQE;
194 if ((ThisItem->Status == 0) ||
195 (ThisItem->Status == 3) ||
196 (ThisItem->Status == 4))
198 MyQItem->ActiveDeliveries++;
199 ThisItem->Active = 1;
202 ThisItem->Active = 0;
205 return MyQItem->ActiveDeliveries;
208 OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID)
211 const char *pLine = NULL;
216 Item = (OneQueItem*)malloc(sizeof(OneQueItem));
217 memset(Item, 0, sizeof(OneQueItem));
218 Item->LastAttempt.retry = SMTP_RETRY_INTERVAL;
219 Item->MessageID = -1;
220 Item->QueMsgID = QueMsgID;
223 Line = NewStrBufPlain(NULL, 128);
224 while (pLine != StrBufNOTNULL) {
225 const char *pItemPart = NULL;
228 StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n');
229 if (StrLength(Line) == 0) continue;
230 StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
231 if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
234 H = (QItemHandler) vHandler;
235 H(Item, Line, &pItemPart);
241 pthread_mutex_lock(&ActiveQItemsLock);
242 if (GetHash(ActiveQItems,
243 LKEY(Item->MessageID),
246 /* WHOOPS. somebody else is already working on this. */
247 pthread_mutex_unlock(&ActiveQItemsLock);
252 /* mark our claim on this. */
254 LKEY(Item->MessageID),
257 pthread_mutex_unlock(&ActiveQItemsLock);
263 StrBuf *SerializeQueueItem(OneQueItem *MyQItem)
271 QMessage = NewStrBufPlain(NULL, SIZ);
272 StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME);
274 // "attempted|%ld\n" "retry|%ld\n",, (long)time(NULL), (long)retry );
275 StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0);
276 StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID);
278 StrBufAppendBufPlain(QMessage, HKEY("\nsubmitted|"), 0);
279 StrBufAppendPrintf(QMessage, "%ld", MyQItem->Submitted);
281 if (StrLength(MyQItem->BounceTo) > 0) {
282 StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0);
283 StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0);
286 if (StrLength(MyQItem->EnvelopeFrom) > 0) {
287 StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0);
288 StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0);
291 It = GetNewHashPos(MyQItem->MailQEntries, 0);
292 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
294 MailQEntry *ThisItem = vQE;
297 if (!ThisItem->Active)
298 continue; /* skip already sent ones from the spoolfile. */
300 for (i=0; i < ThisItem->nAttempts; i++) {
301 /* TODO: most probably there is just one retry/attempted per message! */
302 StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0);
303 StrBufAppendPrintf(QMessage, "%ld",
304 ThisItem->Attempts[i].retry);
306 StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0);
307 StrBufAppendPrintf(QMessage, "%ld",
308 ThisItem->Attempts[i].when);
310 StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0);
311 StrBufAppendBuf(QMessage, ThisItem->Recipient, 0);
312 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
313 StrBufAppendPrintf(QMessage, "%d", ThisItem->Status);
314 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
315 StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0);
318 StrBufAppendBufPlain(QMessage, HKEY("\n"), 0);
326 void NewMailQEntry(OneQueItem *Item)
328 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
329 memset(Item->Current, 0, sizeof(MailQEntry));
331 if (Item->MailQEntries == NULL)
332 Item->MailQEntries = NewHash(1, Flathash);
333 Item->Current->StatusMessage = NewStrBuf();
334 Item->Current->n = GetCount(Item->MailQEntries);
335 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
338 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
340 Item->MessageID = StrBufExtractNext_long(Line, Pos, '|');
343 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
345 if (Item->EnvelopeFrom == NULL)
346 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
347 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
350 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
352 if (Item->BounceTo == NULL)
353 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
354 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
357 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
359 if (Item->Current == NULL)
361 if (Item->Current->Recipient == NULL)
362 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
363 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
364 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
365 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
366 Item->Current = NULL; // TODO: is this always right?
370 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
372 if (Item->Current == NULL)
374 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
375 Item->Current->nAttempts++;
376 if (Item->Current->nAttempts > MaxAttempts) {
380 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
384 void QItem_Handle_Submitted(OneQueItem *Item, StrBuf *Line, const char **Pos)
386 Item->Submitted = atol(*Pos);
390 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
392 if (Item->Current == NULL)
394 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
395 Item->Current->nAttempts++;
396 if (Item->Current->nAttempts > MaxAttempts) {
401 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
402 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
404 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
405 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
406 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
407 Item->LastAttempt.retry = SMTP_RETRY_MAX;
414 * this one has to have the context for loading the message via the redirect buffer...
416 StrBuf *smtp_load_msg(OneQueItem *MyQItem, int n)
421 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
422 CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
423 SendMsg = CCC->redirect_buffer;
424 CCC->redirect_buffer = NULL;
425 if ((StrLength(SendMsg) > 0) &&
426 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
428 "SMTP client[%d]: Possible problem: message did not "
429 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
430 MsgCount, //yes uncool, but best choice here...
431 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
432 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
440 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
441 * instructions for "5" codes (permanent fatal errors) and produce/deliver
442 * a "bounce" message (delivery status notification).
444 void smtpq_do_bounce(OneQueItem *MyQItem, StrBuf *OMsgTxt)
448 struct CtdlMessage *bmsg = NULL;
452 struct recptypes *valid;
459 int successful_bounce = 0;
463 syslog(LOG_DEBUG, "smtp_do_bounce() called\n");
465 if ( (ev_time() - MyQItem->Submitted) > SMTP_GIVE_UP ) {
466 give_up = 1;/// TODO: replace time by libevq timer get
470 * Now go through the instructions checking for stuff.
472 It = GetNewHashPos(MyQItem->MailQEntries, 0);
473 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
475 MailQEntry *ThisItem = vQE;
476 if ((ThisItem->Status == 5) || /* failed now? */
477 ((give_up == 1) && (ThisItem->Status != 2))) /* giving up after failed attempts... */
479 if (num_bounces == 0)
480 Msg = NewStrBufPlain(NULL, 1024);
483 StrBufAppendBuf(Msg, ThisItem->Recipient, 0);
484 StrBufAppendBufPlain(Msg, HKEY(": "), 0);
485 StrBufAppendBuf(Msg, ThisItem->StatusMessage, 0);
486 StrBufAppendBufPlain(Msg, HKEY("\r\n"), 0);
491 /* Deliver the bounce if there's anything worth mentioning */
492 syslog(LOG_DEBUG, "num_bounces = %d\n", num_bounces);
494 if (num_bounces == 0) {
499 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
500 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
502 /* Start building our bounce message; go shopping for memory first. */
503 BounceMB = NewStrBufPlain(NULL,
504 1024 + /* mime stuff.... */
505 StrLength(Msg) + /* the bounce information... */
506 StrLength(OMsgTxt)); /* the original message */
507 if (BounceMB == NULL) {
508 FreeStrBuf(&boundary);
509 syslog(LOG_ERR, "Failed to alloc() bounce message.\n");
514 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
516 FreeStrBuf(&boundary);
517 FreeStrBuf(&BounceMB);
518 syslog(LOG_ERR, "Failed to alloc() bounce message.\n");
522 memset(bmsg, 0, sizeof(struct CtdlMessage));
525 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
526 StrBufAppendBuf(BounceMB, boundary, 0);
527 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
528 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
529 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
530 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
531 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
532 StrBufAppendBuf(BounceMB, boundary, 0);
533 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
534 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
537 StrBufAppendBufPlain(
540 "A message you sent could not be delivered to some or all of its recipients\n"
541 "due to prolonged unavailability of its destination(s).\n"
542 "Giving up on the following addresses:\n\n"
545 StrBufAppendBufPlain(
548 "A message you sent could not be delivered to some or all of its recipients.\n"
549 "The following addresses were undeliverable:\n\n"
552 StrBufAppendBuf(BounceMB, Msg, 0);
555 /* Attach the original message */
556 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
557 StrBufAppendBuf(BounceMB, boundary, 0);
558 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
559 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
560 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
561 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
562 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
563 StrBufAppendBuf(BounceMB, OMsgTxt, 0);
565 /* Close the multipart MIME scope */
566 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
567 StrBufAppendBuf(BounceMB, boundary, 0);
568 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
572 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
573 bmsg->cm_anon_type = MES_NORMAL;
574 bmsg->cm_format_type = FMT_RFC822;
576 bmsg->cm_fields['O'] = strdup(MAILROOM);
577 bmsg->cm_fields['A'] = strdup("Citadel");
578 bmsg->cm_fields['N'] = strdup(config.c_nodename);
579 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
580 bmsg->cm_fields['M'] = SmashStrBuf(&BounceMB);
582 /* First try the user who sent the message */
583 if (StrLength(MyQItem->BounceTo) == 0)
584 syslog(LOG_ERR, "No bounce address specified\n");
586 syslog(LOG_DEBUG, "bounce to user? <%s>\n", ChrPtr(MyQItem->BounceTo));
588 /* Can we deliver the bounce to the original sender? */
589 valid = validate_recipients(ChrPtr(MyQItem->BounceTo), NULL, 0);
590 if ((valid != NULL) && (valid->num_error == 0)) {
591 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
592 successful_bounce = 1;
595 /* If not, post it in the Aide> room */
596 if (successful_bounce == 0) {
597 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
600 /* Free up the memory we used */
601 free_recipients(valid);
602 FreeStrBuf(&boundary);
603 CtdlFreeMessage(bmsg);
604 syslog(LOG_DEBUG, "Done processing bounces\n");
613 n_smarthosts = get_hosts(char *mxbuf, char *rectype);
619 * Called by smtp_do_queue() to handle an individual message.
621 void smtp_do_procmsg(long msgnum, void *userdata) {
622 struct CtdlMessage *msg = NULL;
632 ParsedURL *RelayUrls = NULL;
636 syslog(LOG_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
637 ///strcpy(envelope_from, "");
639 msg = CtdlFetchMessage(msgnum, 1);
641 syslog(LOG_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
645 pch = instr = msg->cm_fields['M'];
647 /* Strip out the headers (no not amd any other non-instruction) line */
648 while (pch != NULL) {
649 pch = strchr(pch, '\n');
650 if ((pch != NULL) && (*(pch + 1) == '\n')) {
655 PlainQItem = NewStrBufPlain(instr, -1);
656 CtdlFreeMessage(msg);
657 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
658 FreeStrBuf(&PlainQItem);
660 if (MyQItem == NULL) {
661 syslog(LOG_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
662 return; /* s.b. else is already processing... */
666 * Postpone delivery if we've already tried recently.
668 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
669 syslog(LOG_DEBUG, "SMTP client: Retry time not yet reached.\n");
671 It = GetNewHashPos(MyQItem->MailQEntries, 0);
672 pthread_mutex_lock(&ActiveQItemsLock);
674 if (GetHashPosFromKey(ActiveQItems, LKEY(MyQItem->MessageID), It))
675 DeleteEntryFromHash(ActiveQItems, It);
677 pthread_mutex_unlock(&ActiveQItemsLock);
678 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
681 }// TODO: reenable me.*/
684 * Bail out if there's no actual message associated with this
686 if (MyQItem->MessageID < 0L) {
687 syslog(LOG_ERR, "SMTP Queue: no 'msgid' directive found!\n");
688 It = GetNewHashPos(MyQItem->MailQEntries, 0);
689 pthread_mutex_lock(&ActiveQItemsLock);
691 if (GetHashPosFromKey(ActiveQItems, LKEY(MyQItem->MessageID), It))
692 DeleteEntryFromHash(ActiveQItems, It);
694 pthread_mutex_unlock(&ActiveQItemsLock);
696 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
702 ParsedURL **Url = &MyQItem->URL;
703 nRelays = get_hosts(mxbuf, "smarthost");
707 const char *Pos = NULL;
708 All = NewStrBufPlain(mxbuf, -1);
709 One = NewStrBufPlain(NULL, StrLength(All) + 1);
711 while ((Pos != StrBufNOTNULL) && ((Pos == NULL) || !IsEmptyStr(Pos))) {
712 StrBufExtract_NextToken(One, All, &Pos, '|');
713 if (!ParseURL(Url, One, 25))
714 syslog(LOG_DEBUG, "Failed to parse: %s\n", ChrPtr(One));
716 ///if (!Url->IsIP)) /// todo dupe me fork ipv6
725 Url = &MyQItem->FallBackHost;
726 nRelays = get_hosts(mxbuf, "fallbackhost");
730 const char *Pos = NULL;
731 All = NewStrBufPlain(mxbuf, -1);
732 One = NewStrBufPlain(NULL, StrLength(All) + 1);
734 while ((Pos != StrBufNOTNULL) && ((Pos == NULL) || !IsEmptyStr(Pos))) {
735 StrBufExtract_NextToken(One, All, &Pos, '|');
736 if (!ParseURL(Url, One, 25))
737 syslog(LOG_DEBUG, "Failed to parse: %s\n", ChrPtr(One));
746 It = GetNewHashPos(MyQItem->MailQEntries, 0);
747 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
749 MailQEntry *ThisItem = vQE;
750 syslog(LOG_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
754 CountActiveQueueEntries(MyQItem);
755 if (MyQItem->ActiveDeliveries > 0)
758 int m = MyQItem->ActiveDeliveries;
760 Msg = smtp_load_msg(MyQItem, n);
761 It = GetNewHashPos(MyQItem->MailQEntries, 0);
763 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
765 MailQEntry *ThisItem = vQE;
766 if (ThisItem->Active == 1) {
767 int KeepBuffers = (i == m);
768 if (i > 1) n = MsgCount++;
770 "SMTP Queue: Trying <%ld> <%s> %d / %d \n",
772 ChrPtr(ThisItem->Recipient),
775 smtp_try_one_queue_entry(MyQItem,
781 if (KeepBuffers) HaveBuffers = 1;
789 It = GetNewHashPos(MyQItem->MailQEntries, 0);
790 pthread_mutex_lock(&ActiveQItemsLock);
792 if (GetHashPosFromKey(ActiveQItems, LKEY(MyQItem->MessageID), It))
793 DeleteEntryFromHash(ActiveQItems, It);
800 "SMTP cleanup: unable to find QItem with ID[%ld]",
802 while (GetNextHashPos(ActiveQItems, It, &len, &Key, &VData))
804 "SMTP cleanup: have: ID[%ld]",
805 ((OneQueItem *)VData)->MessageID);
809 pthread_mutex_unlock(&ActiveQItemsLock);
811 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
813 // TODO: bounce & delete?
818 // TODO : free RelayUrls
825 * smtp_queue_thread()
827 * Run through the queue sending out messages.
829 void smtp_do_queue(void) {
830 static int is_running = 0;
831 int num_processed = 0;
833 if (is_running) return; /* Concurrency check - only one can run */
836 pthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
837 syslog(LOG_INFO, "SMTP client: processing outbound queue");
839 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
840 syslog(LOG_ERR, "Cannot find room <%s>", SMTP_SPOOLOUT_ROOM);
843 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
845 syslog(LOG_INFO, "SMTP client: queue run completed; %d messages processed", num_processed);
853 * Initialize the SMTP outbound queue
855 void smtp_init_spoolout(void) {
856 struct ctdlroom qrbuf;
859 * Create the room. This will silently fail if the room already
860 * exists, and that's perfectly ok, because we want it to exist.
862 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
865 * Make sure it's set to be a "system room" so it doesn't show up
866 * in the <K>nown rooms list for Aides.
868 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
869 qrbuf.QRflags2 |= QR2_SYSTEM;
870 CtdlPutRoomLock(&qrbuf);
877 /*****************************************************************************/
878 /* SMTP UTILITY COMMANDS */
879 /*****************************************************************************/
881 void cmd_smtp(char *argbuf) {
888 if (CtdlAccessCheck(ac_aide)) return;
890 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
892 if (!strcasecmp(cmd, "mx")) {
893 extract_token(node, argbuf, 1, '|', sizeof node);
894 num_mxhosts = getmx(buf, node);
895 cprintf("%d %d MX hosts listed for %s\n",
896 LISTING_FOLLOWS, num_mxhosts, node);
897 for (i=0; i<num_mxhosts; ++i) {
898 extract_token(node, buf, i, '|', sizeof node);
899 cprintf("%s\n", node);
905 else if (!strcasecmp(cmd, "runqueue")) {
907 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
912 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
918 CTDL_MODULE_INIT(smtp_queu)
922 CtdlFillSystemContext(&smtp_queue_CC, "SMTP_Send");
923 ActiveQItems = NewHash(1, lFlathash);
924 pthread_mutex_init(&ActiveQItemsLock, NULL);
926 QItemHandlers = NewHash(0, NULL);
928 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
929 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
930 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
931 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
932 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
933 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
934 Put(QItemHandlers, HKEY("submitted"), QItem_Handle_Submitted, reference_free_handler);
935 ////TODO: flush qitemhandlers on exit
936 smtp_init_spoolout();
938 CtdlRegisterCleanupHook(smtp_evq_cleanup);
940 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
941 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
944 /* return our Subversion id for the Log */
945 return "smtpeventclient";