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. */
104 ParsedURL *RelayUrls);
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 MyQItem->ActiveDeliveries--;
121 IDestructQueItem = MyQItem->ActiveDeliveries == 0;
122 return IDestructQueItem;
125 void RemoveQItem(OneQueItem *MyQItem)
129 It = GetNewHashPos(MyQItem->MailQEntries, 0);
130 citthread_mutex_lock(&ActiveQItemsLock);
132 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
133 DeleteEntryFromHash(ActiveQItems, It);
135 citthread_mutex_unlock(&ActiveQItemsLock);
139 void FreeURL(ParsedURL** Url)
142 FreeStrBuf(&(*Url)->URL);
143 if ((*Url)->Next != NULL)
144 FreeURL(&(*Url)->Next);
150 void FreeMailQEntry(void *qv)
153 FreeStrBuf(&Q->Recipient);
154 FreeStrBuf(&Q->StatusMessage);
157 void FreeQueItem(OneQueItem **Item)
159 DeleteHash(&(*Item)->MailQEntries);
160 FreeStrBuf(&(*Item)->EnvelopeFrom);
161 FreeStrBuf(&(*Item)->BounceTo);
162 FreeURL(&(*Item)->URL);
166 void HFreeQueItem(void *Item)
168 FreeQueItem((OneQueItem**)&Item);
171 /* inspect recipients with a status of:
172 * - 0 (no delivery yet attempted)
173 * - 3/4 (transient errors
174 * were experienced and it's time to try again)
176 int CountActiveQueueEntries(OneQueItem *MyQItem)
183 MyQItem->ActiveDeliveries = 0;
184 It = GetNewHashPos(MyQItem->MailQEntries, 0);
185 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
187 MailQEntry *ThisItem = vQE;
188 if ((ThisItem->Status == 0) ||
189 (ThisItem->Status == 3) ||
190 (ThisItem->Status == 4))
192 MyQItem->ActiveDeliveries++;
193 ThisItem->Active = 1;
196 ThisItem->Active = 0;
199 return MyQItem->ActiveDeliveries;
202 OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID)
205 const char *pLine = NULL;
210 Item = (OneQueItem*)malloc(sizeof(OneQueItem));
211 memset(Item, 0, sizeof(OneQueItem));
212 Item->LastAttempt.retry = SMTP_RETRY_INTERVAL;
213 Item->MessageID = -1;
214 Item->QueMsgID = QueMsgID;
216 citthread_mutex_lock(&ActiveQItemsLock);
217 if (GetHash(ActiveQItems,
218 IKEY(Item->QueMsgID),
221 /* WHOOPS. somebody else is already working on this. */
222 citthread_mutex_unlock(&ActiveQItemsLock);
227 /* mark our claim on this. */
229 IKEY(Item->QueMsgID),
232 citthread_mutex_unlock(&ActiveQItemsLock);
236 Line = NewStrBufPlain(NULL, 128);
237 while (pLine != StrBufNOTNULL) {
238 const char *pItemPart = NULL;
241 StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n');
242 if (StrLength(Line) == 0) continue;
243 StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
244 if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
247 H = (QItemHandler) vHandler;
248 H(Item, Line, &pItemPart);
256 StrBuf *SerializeQueueItem(OneQueItem *MyQItem)
264 QMessage = NewStrBufPlain(NULL, SIZ);
265 StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME);
267 // "attempted|%ld\n" "retry|%ld\n",, (long)time(NULL), (long)retry );
268 StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0);
269 StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID);
271 StrBufAppendBufPlain(QMessage, HKEY("\nsubmitted|"), 0);
272 StrBufAppendPrintf(QMessage, "%ld", MyQItem->Submitted);
274 if (StrLength(MyQItem->BounceTo) > 0) {
275 StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0);
276 StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0);
279 if (StrLength(MyQItem->EnvelopeFrom) > 0) {
280 StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0);
281 StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0);
284 It = GetNewHashPos(MyQItem->MailQEntries, 0);
285 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
287 MailQEntry *ThisItem = vQE;
290 if (!ThisItem->Active)
291 continue; /* skip already sent ones from the spoolfile. */
293 for (i=0; i < ThisItem->nAttempts; i++) {
294 /* TODO: most probably there is just one retry/attempted per message! */
295 StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0);
296 StrBufAppendPrintf(QMessage, "%ld",
297 ThisItem->Attempts[i].retry);
299 StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0);
300 StrBufAppendPrintf(QMessage, "%ld",
301 ThisItem->Attempts[i].when);
303 StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0);
304 StrBufAppendBuf(QMessage, ThisItem->Recipient, 0);
305 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
306 StrBufAppendPrintf(QMessage, "%d", ThisItem->Status);
307 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
308 StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0);
311 StrBufAppendBufPlain(QMessage, HKEY("\n"), 0);
319 void NewMailQEntry(OneQueItem *Item)
321 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
322 memset(Item->Current, 0, sizeof(MailQEntry));
324 if (Item->MailQEntries == NULL)
325 Item->MailQEntries = NewHash(1, Flathash);
326 Item->Current->StatusMessage = NewStrBuf();
327 Item->Current->n = GetCount(Item->MailQEntries);
328 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
331 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
333 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
336 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
338 if (Item->EnvelopeFrom == NULL)
339 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
340 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
343 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
345 if (Item->BounceTo == NULL)
346 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
347 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
350 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
352 if (Item->Current == NULL)
354 if (Item->Current->Recipient == NULL)
355 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
356 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
357 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
358 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
359 Item->Current = NULL; // TODO: is this always right?
363 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
365 if (Item->Current == NULL)
367 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
368 Item->Current->nAttempts++;
369 if (Item->Current->nAttempts > MaxAttempts) {
373 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
377 void QItem_Handle_Submitted(OneQueItem *Item, StrBuf *Line, const char **Pos)
379 Item->Submitted = atol(*Pos);
383 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
385 if (Item->Current == NULL)
387 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
388 Item->Current->nAttempts++;
389 if (Item->Current->nAttempts > MaxAttempts) {
394 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
395 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
397 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
398 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
399 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
400 Item->LastAttempt.retry = SMTP_RETRY_MAX;
407 * this one has to have the context for loading the message via the redirect buffer...
409 StrBuf *smtp_load_msg(OneQueItem *MyQItem, int n)
414 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
415 CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
416 SendMsg = CCC->redirect_buffer;
417 CCC->redirect_buffer = NULL;
418 if ((StrLength(SendMsg) > 0) &&
419 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
420 CtdlLogPrintf(CTDL_WARNING,
421 "SMTP client[%d]: Possible problem: message did not "
422 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
423 MsgCount, //yes uncool, but best choice here...
424 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
425 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
433 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
434 * instructions for "5" codes (permanent fatal errors) and produce/deliver
435 * a "bounce" message (delivery status notification).
437 void smtpq_do_bounce(OneQueItem *MyQItem, StrBuf *OMsgTxt)
441 struct CtdlMessage *bmsg = NULL;
445 struct recptypes *valid;
452 int successful_bounce = 0;
456 CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
458 if ( (ev_time() - MyQItem->Submitted) > SMTP_GIVE_UP ) {
459 give_up = 1;/// TODO: replace time by libevq timer get
463 * Now go through the instructions checking for stuff.
465 It = GetNewHashPos(MyQItem->MailQEntries, 0);
466 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
468 MailQEntry *ThisItem = vQE;
469 if ((ThisItem->Status == 5) || /* failed now? */
470 ((give_up == 1) && (ThisItem->Status != 2))) /* giving up after failed attempts... */
472 if (num_bounces == 0)
473 Msg = NewStrBufPlain(NULL, 1024);
476 StrBufAppendBuf(Msg, ThisItem->Recipient, 0);
477 StrBufAppendBufPlain(Msg, HKEY(": "), 0);
478 StrBufAppendBuf(Msg, ThisItem->StatusMessage, 0);
479 StrBufAppendBufPlain(Msg, HKEY("\r\n"), 0);
484 /* Deliver the bounce if there's anything worth mentioning */
485 CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
487 if (num_bounces == 0) {
492 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
493 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
495 /* Start building our bounce message; go shopping for memory first. */
496 BounceMB = NewStrBufPlain(NULL,
497 1024 + /* mime stuff.... */
498 StrLength(Msg) + /* the bounce information... */
499 StrLength(OMsgTxt)); /* the original message */
500 if (BounceMB == NULL) {
501 FreeStrBuf(&boundary);
502 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
507 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
509 FreeStrBuf(&boundary);
510 FreeStrBuf(&BounceMB);
511 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
515 memset(bmsg, 0, sizeof(struct CtdlMessage));
518 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
519 StrBufAppendBuf(BounceMB, boundary, 0);
520 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
521 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
522 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
523 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
524 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
525 StrBufAppendBuf(BounceMB, boundary, 0);
526 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
527 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
530 StrBufAppendBufPlain(
533 "A message you sent could not be delivered to some or all of its recipients\n"
534 "due to prolonged unavailability of its destination(s).\n"
535 "Giving up on the following addresses:\n\n"
538 StrBufAppendBufPlain(
541 "A message you sent could not be delivered to some or all of its recipients.\n"
542 "The following addresses were undeliverable:\n\n"
545 StrBufAppendBuf(BounceMB, Msg, 0);
548 /* Attach the original message */
549 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
550 StrBufAppendBuf(BounceMB, boundary, 0);
551 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
552 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
553 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
554 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
555 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
556 StrBufAppendBuf(BounceMB, OMsgTxt, 0);
558 /* Close the multipart MIME scope */
559 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
560 StrBufAppendBuf(BounceMB, boundary, 0);
561 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
565 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
566 bmsg->cm_anon_type = MES_NORMAL;
567 bmsg->cm_format_type = FMT_RFC822;
569 bmsg->cm_fields['O'] = strdup(MAILROOM);
570 bmsg->cm_fields['A'] = strdup("Citadel");
571 bmsg->cm_fields['N'] = strdup(config.c_nodename);
572 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
573 bmsg->cm_fields['M'] = SmashStrBuf(&BounceMB);
575 /* First try the user who sent the message */
576 if (StrLength(MyQItem->BounceTo) == 0)
577 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
579 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", ChrPtr(MyQItem->BounceTo));
581 /* Can we deliver the bounce to the original sender? */
582 valid = validate_recipients(ChrPtr(MyQItem->BounceTo), NULL, 0);
583 if ((valid != NULL) && (valid->num_error == 0)) {
584 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
585 successful_bounce = 1;
588 /* If not, post it in the Aide> room */
589 if (successful_bounce == 0) {
590 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
593 /* Free up the memory we used */
594 free_recipients(valid);
595 FreeStrBuf(&boundary);
596 CtdlFreeMessage(bmsg);
597 CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
602 int ParseURL(ParsedURL **Url, StrBuf *UrlStr, short DefaultPort)
604 const char *pch, *pEndHost, *pPort, *pCredEnd, *pUserEnd;
605 ParsedURL *url = (ParsedURL *)malloc(sizeof(ParsedURL));
606 memset(url, 0, sizeof(ParsedURL));
609 url->Port = DefaultPort;
611 * http://username:passvoid@[ipv6]:port/url
613 url->URL = NewStrBufDup(UrlStr);
614 url->Host = pch = ChrPtr(url->URL);
615 url->LocalPart = strchr(pch, '/');
616 if (url->LocalPart != NULL) {
617 if ((*(url->LocalPart + 1) == '/') &&
618 (*(url->LocalPart + 2) == ':')) { /* TODO: find default port for this protocol... */
619 url->Host = url->LocalPart + 3;
620 url->LocalPart = strchr(url->Host, '/');
623 if (url->LocalPart == NULL) {
624 url->LocalPart = pch + StrLength(url->URL);
627 pCredEnd = strchr(pch, '@');
628 if (pCredEnd >= url->LocalPart)
630 if (pCredEnd != NULL)
632 url->User = url->Host;
633 url->Host = pCredEnd + 1;
634 pUserEnd = strchr(url->User, ':');
636 if (pUserEnd > pCredEnd)
639 url->Pass = pUserEnd + 1;
641 StrBufPeek(url->URL, pUserEnd, 0, '\0');
642 StrBufPeek(url->URL, pCredEnd, 0, '\0');
646 if (*url->Host == '[') {
648 pEndHost = strchr(url->Host, ']');
649 if (pEndHost == NULL) {
650 FreeStrBuf(&url->URL);
652 return 0; /* invalid syntax, no ipv6 */
654 StrBufPeek(url->URL, pEndHost, 0, '\0');
655 if (*(pEndHost + 1) == ':'){
656 StrBufPeek(url->URL, pEndHost + 1, 0, '\0');
657 pPort = pEndHost + 2;
662 pPort = strchr(url->Host, ':');
664 StrBufPeek(url->URL, pPort, 0, '\0');
669 url->Port = atol(pPort);
670 url->IsIP = inet_pton(url->af, url->Host, &url->Addr);
678 n_smarthosts = get_hosts(char *mxbuf, char *rectype);
684 * Called by smtp_do_queue() to handle an individual message.
686 void smtp_do_procmsg(long msgnum, void *userdata) {
687 struct CtdlMessage *msg = NULL;
697 ParsedURL *RelayUrls = NULL;
701 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
702 ///strcpy(envelope_from, "");
704 msg = CtdlFetchMessage(msgnum, 1);
706 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
710 pch = instr = msg->cm_fields['M'];
712 /* Strip out the headers (no not amd any other non-instruction) line */
713 while (pch != NULL) {
714 pch = strchr(pch, '\n');
715 if ((pch != NULL) && (*(pch + 1) == '\n')) {
720 PlainQItem = NewStrBufPlain(instr, -1);
721 CtdlFreeMessage(msg);
722 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
723 FreeStrBuf(&PlainQItem);
725 if (MyQItem == NULL) {
726 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
727 return; /* s.b. else is already processing... */
731 * Postpone delivery if we've already tried recently.
733 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
734 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
736 It = GetNewHashPos(MyQItem->MailQEntries, 0);
737 citthread_mutex_lock(&ActiveQItemsLock);
739 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
740 DeleteEntryFromHash(ActiveQItems, It);
742 citthread_mutex_unlock(&ActiveQItemsLock);
743 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
746 }// TODO: reenable me.*/
749 * Bail out if there's no actual message associated with this
751 if (MyQItem->MessageID < 0L) {
752 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
753 It = GetNewHashPos(MyQItem->MailQEntries, 0);
754 citthread_mutex_lock(&ActiveQItemsLock);
756 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
757 DeleteEntryFromHash(ActiveQItems, It);
759 citthread_mutex_unlock(&ActiveQItemsLock);
761 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
767 ParsedURL **Url = &MyQItem->URL;
768 nRelays = get_hosts(mxbuf, "smarthost");
772 const char *Pos = NULL;
773 All = NewStrBufPlain(mxbuf, -1);
774 One = NewStrBufPlain(NULL, StrLength(All) + 1);
776 while ((Pos != StrBufNOTNULL) && ((Pos == NULL) || !IsEmptyStr(Pos))) {
777 StrBufExtract_NextToken(One, All, &Pos, '|');
778 if (!ParseURL(Url, One, 25))
779 CtdlLogPrintf(CTDL_DEBUG, "Failed to parse: %s\n", ChrPtr(One));
787 Url = &MyQItem->FallBackHost;
788 nRelays = get_hosts(mxbuf, "fallbackhost");
792 const char *Pos = NULL;
793 All = NewStrBufPlain(mxbuf, -1);
794 One = NewStrBufPlain(NULL, StrLength(All) + 1);
796 while ((Pos != StrBufNOTNULL) && ((Pos == NULL) || !IsEmptyStr(Pos))) {
797 StrBufExtract_NextToken(One, All, &Pos, '|');
798 if (!ParseURL(Url, One, 25))
799 CtdlLogPrintf(CTDL_DEBUG, "Failed to parse: %s\n", ChrPtr(One));
808 It = GetNewHashPos(MyQItem->MailQEntries, 0);
809 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
811 MailQEntry *ThisItem = vQE;
812 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
816 CountActiveQueueEntries(MyQItem);
817 if (MyQItem->ActiveDeliveries > 0)
820 int m = MyQItem->ActiveDeliveries;
822 Msg = smtp_load_msg(MyQItem, n);
823 It = GetNewHashPos(MyQItem->MailQEntries, 0);
825 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
827 MailQEntry *ThisItem = vQE;
828 if (ThisItem->Active == 1) {
829 int KeepBuffers = (i == m);
830 if (i > 1) n = MsgCount++;
831 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s> %d / %d \n", ChrPtr(ThisItem->Recipient), i, m);
832 smtp_try(MyQItem, ThisItem, Msg, KeepBuffers, n, RelayUrls);
833 if (KeepBuffers) HaveBuffers = 1;
841 It = GetNewHashPos(MyQItem->MailQEntries, 0);
842 citthread_mutex_lock(&ActiveQItemsLock);
844 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
845 DeleteEntryFromHash(ActiveQItems, It);
847 citthread_mutex_unlock(&ActiveQItemsLock);
849 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
851 // TODO: bounce & delete?
856 // TODO : free RelayUrls
863 * smtp_queue_thread()
865 * Run through the queue sending out messages.
867 void *smtp_queue_thread(void *arg) {
868 int num_processed = 0;
869 struct CitContext smtp_queue_CC;
873 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
874 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
875 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
877 while (!CtdlThreadCheckStop()) {
879 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
881 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
882 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
885 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
887 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
891 CtdlClearSystemContext();
898 * Initialize the SMTP outbound queue
900 void smtp_init_spoolout(void) {
901 struct ctdlroom qrbuf;
904 * Create the room. This will silently fail if the room already
905 * exists, and that's perfectly ok, because we want it to exist.
907 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
910 * Make sure it's set to be a "system room" so it doesn't show up
911 * in the <K>nown rooms list for Aides.
913 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
914 qrbuf.QRflags2 |= QR2_SYSTEM;
915 CtdlPutRoomLock(&qrbuf);
922 /*****************************************************************************/
923 /* SMTP UTILITY COMMANDS */
924 /*****************************************************************************/
926 void cmd_smtp(char *argbuf) {
933 if (CtdlAccessCheck(ac_aide)) return;
935 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
937 if (!strcasecmp(cmd, "mx")) {
938 extract_token(node, argbuf, 1, '|', sizeof node);
939 num_mxhosts = getmx(buf, node);
940 cprintf("%d %d MX hosts listed for %s\n",
941 LISTING_FOLLOWS, num_mxhosts, node);
942 for (i=0; i<num_mxhosts; ++i) {
943 extract_token(node, buf, i, '|', sizeof node);
944 cprintf("%s\n", node);
950 else if (!strcasecmp(cmd, "runqueue")) {
952 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
957 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
965 CTDL_MODULE_INIT(smtp_queu)
967 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
970 ActiveQItems = NewHash(1, Flathash);
971 citthread_mutex_init(&ActiveQItemsLock, NULL);
973 QItemHandlers = NewHash(0, NULL);
975 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
976 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
977 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
978 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
979 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
980 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
981 Put(QItemHandlers, HKEY("submitted"), QItem_Handle_Submitted, reference_free_handler);
982 ////TODO: flush qitemhandlers on exit
983 smtp_init_spoolout();
985 CtdlRegisterCleanupHook(smtp_evq_cleanup);
986 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
988 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
992 /* return our Subversion id for the Log */
993 return "smtpeventclient";