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 citthread_mutex_lock(&ActiveQItemsLock);
121 MyQItem->ActiveDeliveries--;
122 IDestructQueItem = MyQItem->ActiveDeliveries == 0;
123 citthread_mutex_unlock(&ActiveQItemsLock);
124 return IDestructQueItem;
127 void RemoveQItem(OneQueItem *MyQItem)
131 It = GetNewHashPos(MyQItem->MailQEntries, 0);
132 citthread_mutex_lock(&ActiveQItemsLock);
134 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
135 DeleteEntryFromHash(ActiveQItems, It);
137 citthread_mutex_unlock(&ActiveQItemsLock);
141 void FreeURL(ParsedURL** Url)
143 FreeStrBuf(&(*Url)->URL);
148 void FreeMailQEntry(void *qv)
151 FreeStrBuf(&Q->Recipient);
152 FreeStrBuf(&Q->StatusMessage);
155 void FreeQueItem(OneQueItem **Item)
157 DeleteHash(&(*Item)->MailQEntries);
158 FreeStrBuf(&(*Item)->EnvelopeFrom);
159 FreeStrBuf(&(*Item)->BounceTo);
160 FreeURL(&(*Item)->URL);
164 void HFreeQueItem(void *Item)
166 FreeQueItem((OneQueItem**)&Item);
169 /* inspect recipients with a status of:
170 * - 0 (no delivery yet attempted)
171 * - 3/4 (transient errors
172 * were experienced and it's time to try again)
174 int CountActiveQueueEntries(OneQueItem *MyQItem)
181 MyQItem->ActiveDeliveries = 0;
182 It = GetNewHashPos(MyQItem->MailQEntries, 0);
183 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
185 MailQEntry *ThisItem = vQE;
186 if ((ThisItem->Status == 0) ||
187 (ThisItem->Status == 3) ||
188 (ThisItem->Status == 4))
190 MyQItem->ActiveDeliveries++;
191 ThisItem->Active = 1;
194 ThisItem->Active = 0;
197 return MyQItem->ActiveDeliveries;
200 OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID)
203 const char *pLine = NULL;
208 Item = (OneQueItem*)malloc(sizeof(OneQueItem));
209 memset(Item, 0, sizeof(OneQueItem));
210 Item->LastAttempt.retry = SMTP_RETRY_INTERVAL;
211 Item->MessageID = -1;
212 Item->QueMsgID = QueMsgID;
214 citthread_mutex_lock(&ActiveQItemsLock);
215 if (GetHash(ActiveQItems,
216 IKEY(Item->QueMsgID),
219 /* WHOOPS. somebody else is already working on this. */
220 citthread_mutex_unlock(&ActiveQItemsLock);
225 /* mark our claim on this. */
227 IKEY(Item->QueMsgID),
230 citthread_mutex_unlock(&ActiveQItemsLock);
234 Line = NewStrBufPlain(NULL, 128);
235 while (pLine != StrBufNOTNULL) {
236 const char *pItemPart = NULL;
239 StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n');
240 if (StrLength(Line) == 0) continue;
241 StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
242 if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
245 H = (QItemHandler) vHandler;
246 H(Item, Line, &pItemPart);
254 StrBuf *SerializeQueueItem(OneQueItem *MyQItem)
262 QMessage = NewStrBufPlain(NULL, SIZ);
263 StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME);
265 // "attempted|%ld\n" "retry|%ld\n",, (long)time(NULL), (long)retry );
266 StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0);
267 StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID);
269 StrBufAppendBufPlain(QMessage, HKEY("\nsubmitted|"), 0);
270 StrBufAppendPrintf(QMessage, "%ld", MyQItem->Submitted);
272 if (StrLength(MyQItem->BounceTo) > 0) {
273 StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0);
274 StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0);
277 if (StrLength(MyQItem->EnvelopeFrom) > 0) {
278 StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0);
279 StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0);
282 It = GetNewHashPos(MyQItem->MailQEntries, 0);
283 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
285 MailQEntry *ThisItem = vQE;
288 if (!ThisItem->Active)
289 continue; /* skip already sent ones from the spoolfile. */
291 for (i=0; i < ThisItem->nAttempts; i++) {
292 /* TODO: most probably there is just one retry/attempted per message! */
293 StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0);
294 StrBufAppendPrintf(QMessage, "%ld",
295 ThisItem->Attempts[i].retry);
297 StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0);
298 StrBufAppendPrintf(QMessage, "%ld",
299 ThisItem->Attempts[i].when);
301 StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0);
302 StrBufAppendBuf(QMessage, ThisItem->Recipient, 0);
303 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
304 StrBufAppendPrintf(QMessage, "%d", ThisItem->Status);
305 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
306 StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0);
309 StrBufAppendBufPlain(QMessage, HKEY("\n"), 0);
317 void NewMailQEntry(OneQueItem *Item)
319 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
320 memset(Item->Current, 0, sizeof(MailQEntry));
322 if (Item->MailQEntries == NULL)
323 Item->MailQEntries = NewHash(1, Flathash);
324 Item->Current->StatusMessage = NewStrBuf();
325 Item->Current->n = GetCount(Item->MailQEntries);
326 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
329 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
331 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
334 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
336 if (Item->EnvelopeFrom == NULL)
337 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
338 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
341 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
343 if (Item->BounceTo == NULL)
344 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
345 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
348 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
350 if (Item->Current == NULL)
352 if (Item->Current->Recipient == NULL)
353 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
354 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
355 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
356 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
357 Item->Current = NULL; // TODO: is this always right?
361 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
363 if (Item->Current == NULL)
365 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
366 Item->Current->nAttempts++;
367 if (Item->Current->nAttempts > MaxAttempts) {
371 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
375 void QItem_Handle_Submitted(OneQueItem *Item, StrBuf *Line, const char **Pos)
377 Item->Submitted = atol(*Pos);
381 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
383 if (Item->Current == NULL)
385 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
386 Item->Current->nAttempts++;
387 if (Item->Current->nAttempts > MaxAttempts) {
392 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
393 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
395 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
396 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
397 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
398 Item->LastAttempt.retry = SMTP_RETRY_MAX;
405 * this one has to have the context for loading the message via the redirect buffer...
407 StrBuf *smtp_load_msg(OneQueItem *MyQItem, int n)
412 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
413 CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
414 SendMsg = CCC->redirect_buffer;
415 CCC->redirect_buffer = NULL;
416 if ((StrLength(SendMsg) > 0) &&
417 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
418 CtdlLogPrintf(CTDL_WARNING,
419 "SMTP client[%ld]: Possible problem: message did not "
420 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
421 MsgCount, //yes uncool, but best choice here...
422 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
423 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
431 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
432 * instructions for "5" codes (permanent fatal errors) and produce/deliver
433 * a "bounce" message (delivery status notification).
435 void smtpq_do_bounce(OneQueItem *MyQItem, StrBuf *OMsgTxt)
439 struct CtdlMessage *bmsg = NULL;
443 struct recptypes *valid;
450 int successful_bounce = 0;
454 CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
456 if ( (ev_time() - MyQItem->Submitted) > SMTP_GIVE_UP ) {
457 give_up = 1;/// TODO: replace time by libevq timer get
461 * Now go through the instructions checking for stuff.
463 It = GetNewHashPos(MyQItem->MailQEntries, 0);
464 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
466 MailQEntry *ThisItem = vQE;
467 if ((ThisItem->Status == 5) || /* failed now? */
468 ((give_up == 1) && (ThisItem->Status != 2))) /* giving up after failed attempts... */
470 if (num_bounces == 0)
471 Msg = NewStrBufPlain(NULL, 1024);
474 StrBufAppendBuf(Msg, ThisItem->Recipient, 0);
475 StrBufAppendBufPlain(Msg, HKEY(": "), 0);
476 StrBufAppendBuf(Msg, ThisItem->StatusMessage, 0);
477 StrBufAppendBufPlain(Msg, HKEY("\r\n"), 0);
482 /* Deliver the bounce if there's anything worth mentioning */
483 CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
485 if (num_bounces == 0) {
490 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
491 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
493 /* Start building our bounce message; go shopping for memory first. */
494 BounceMB = NewStrBufPlain(NULL,
495 1024 + /* mime stuff.... */
496 StrLength(Msg) + /* the bounce information... */
497 StrLength(OMsgTxt)); /* the original message */
498 if (BounceMB == NULL) {
499 FreeStrBuf(&boundary);
500 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
505 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
507 FreeStrBuf(&boundary);
508 FreeStrBuf(&BounceMB);
509 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
513 memset(bmsg, 0, sizeof(struct CtdlMessage));
516 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
517 StrBufAppendBuf(BounceMB, boundary, 0);
518 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
519 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
520 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
521 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
522 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
523 StrBufAppendBuf(BounceMB, boundary, 0);
524 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
525 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
528 StrBufAppendBufPlain(
531 "A message you sent could not be delivered to some or all of its recipients\n"
532 "due to prolonged unavailability of its destination(s).\n"
533 "Giving up on the following addresses:\n\n"
536 StrBufAppendBufPlain(
539 "A message you sent could not be delivered to some or all of its recipients.\n"
540 "The following addresses were undeliverable:\n\n"
543 StrBufAppendBuf(BounceMB, Msg, 0);
546 /* Attach the original message */
547 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
548 StrBufAppendBuf(BounceMB, boundary, 0);
549 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
550 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
551 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
552 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
553 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
554 StrBufAppendBuf(BounceMB, OMsgTxt, 0);
556 /* Close the multipart MIME scope */
557 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
558 StrBufAppendBuf(BounceMB, boundary, 0);
559 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
563 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
564 bmsg->cm_anon_type = MES_NORMAL;
565 bmsg->cm_format_type = FMT_RFC822;
567 bmsg->cm_fields['O'] = strdup(MAILROOM);
568 bmsg->cm_fields['A'] = strdup("Citadel");
569 bmsg->cm_fields['N'] = strdup(config.c_nodename);
570 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
571 bmsg->cm_fields['M'] = SmashStrBuf(&BounceMB);
573 /* First try the user who sent the message */
574 if (StrLength(MyQItem->BounceTo) == 0)
575 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
577 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", ChrPtr(MyQItem->BounceTo));
579 /* Can we deliver the bounce to the original sender? */
580 valid = validate_recipients(ChrPtr(MyQItem->BounceTo), NULL, 0);
581 if ((valid != NULL) && (valid->num_error == 0)) {
582 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
583 successful_bounce = 1;
586 /* If not, post it in the Aide> room */
587 if (successful_bounce == 0) {
588 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
591 /* Free up the memory we used */
592 free_recipients(valid);
593 FreeStrBuf(&boundary);
594 CtdlFreeMessage(bmsg);
595 CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
600 int ParseURL(ParsedURL **Url, StrBuf *UrlStr, short DefaultPort)
602 const char *pch, *pEndHost, *pPort, *pCredEnd, *pUserEnd;
603 ParsedURL *url = (ParsedURL *)malloc(sizeof(ParsedURL));
604 memset(url, 0, sizeof(ParsedURL));
607 url->Port = DefaultPort;
609 * http://username:passvoid@[ipv6]:port/url
611 url->URL = NewStrBufDup(UrlStr);
612 url->Host = pch = ChrPtr(url->URL);
613 url->LocalPart = strchr(pch, '/');
614 if (url->LocalPart != NULL) {
615 if ((*(url->LocalPart + 1) == '/') &&
616 (*(url->LocalPart + 2) == ':')) { /* TODO: find default port for this protocol... */
617 url->Host = url->LocalPart + 3;
618 url->LocalPart = strchr(url->Host, '/');
621 if (url->LocalPart == NULL) {
622 url->LocalPart = pch + StrLength(url->URL);
625 pCredEnd = strchr(pch, '@');
626 if (pCredEnd >= url->LocalPart)
628 if (pCredEnd != NULL)
630 url->User = url->Host;
631 url->Host = pCredEnd + 1;
632 pUserEnd = strchr(url->User, ':');
634 if (pUserEnd > pCredEnd)
637 url->Pass = pUserEnd + 1;
639 StrBufPeek(url->URL, pUserEnd, 0, '\0');
640 StrBufPeek(url->URL, pCredEnd, 0, '\0');
644 if (*url->Host == '[') {
646 pEndHost = strchr(url->Host, ']');
647 if (pEndHost == NULL) {
649 return 0; /* invalid syntax, no ipv6 */
651 if (*(pEndHost + 1) == ':'){
652 StrBufPeek(url->URL, pEndHost + 1, 0, '\0');
653 pPort = pEndHost + 2;
658 pPort = strchr(url->Host, ':');
660 StrBufPeek(url->URL, pPort, 0, '\0');
665 url->Port = atol(pPort);
666 url->IsIP = inet_pton(url->af, url->Host, &url->Addr);
674 n_smarthosts = get_hosts(char *mxbuf, char *rectype);
680 * Called by smtp_do_queue() to handle an individual message.
682 void smtp_do_procmsg(long msgnum, void *userdata) {
683 struct CtdlMessage *msg = NULL;
693 ParsedURL *RelayUrls = NULL;
697 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
698 ///strcpy(envelope_from, "");
700 msg = CtdlFetchMessage(msgnum, 1);
702 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
706 pch = instr = msg->cm_fields['M'];
708 /* Strip out the headers (no not amd any other non-instruction) line */
709 while (pch != NULL) {
710 pch = strchr(pch, '\n');
711 if ((pch != NULL) && (*(pch + 1) == '\n')) {
716 PlainQItem = NewStrBufPlain(instr, -1);
717 CtdlFreeMessage(msg);
718 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
719 FreeStrBuf(&PlainQItem);
721 if (MyQItem == NULL) {
722 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
723 return; /* s.b. else is already processing... */
727 * Postpone delivery if we've already tried recently.
729 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
730 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
732 It = GetNewHashPos(MyQItem->MailQEntries, 0);
733 citthread_mutex_lock(&ActiveQItemsLock);
735 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
736 DeleteEntryFromHash(ActiveQItems, It);
738 citthread_mutex_unlock(&ActiveQItemsLock);
739 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
742 }// TODO: reenable me.*/
745 * Bail out if there's no actual message associated with this
747 if (MyQItem->MessageID < 0L) {
748 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
749 It = GetNewHashPos(MyQItem->MailQEntries, 0);
750 citthread_mutex_lock(&ActiveQItemsLock);
752 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
753 DeleteEntryFromHash(ActiveQItems, It);
755 citthread_mutex_unlock(&ActiveQItemsLock);
757 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
763 ParsedURL **Url = &MyQItem->URL;
764 nRelays = get_hosts(mxbuf, "smarthost");
768 const char *Pos = NULL;
769 All = NewStrBufPlain(mxbuf, -1);
770 One = NewStrBufPlain(NULL, StrLength(All) + 1);
772 while ((Pos != StrBufNOTNULL) && ((Pos == NULL) || !IsEmptyStr(Pos))) {
773 StrBufExtract_NextToken(One, All, &Pos, '|');
774 if (!ParseURL(Url, One, 25))
775 CtdlLogPrintf(CTDL_DEBUG, "Failed to parse: %s\n", ChrPtr(One));
784 It = GetNewHashPos(MyQItem->MailQEntries, 0);
785 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
787 MailQEntry *ThisItem = vQE;
788 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
792 CountActiveQueueEntries(MyQItem);
793 if (MyQItem->ActiveDeliveries > 0)
797 Msg = smtp_load_msg(MyQItem, n);
798 It = GetNewHashPos(MyQItem->MailQEntries, 0);
799 while ((i <= MyQItem->ActiveDeliveries) &&
800 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
802 MailQEntry *ThisItem = vQE;
803 if (ThisItem->Active == 1) {
804 int KeepBuffers = (i == MyQItem->ActiveDeliveries);
805 if (i > 1) n = MsgCount++;
806 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
807 smtp_try(MyQItem, ThisItem, Msg, KeepBuffers, n, RelayUrls);
808 if (KeepBuffers) HaveBuffers = 1;
816 It = GetNewHashPos(MyQItem->MailQEntries, 0);
817 citthread_mutex_lock(&ActiveQItemsLock);
819 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
820 DeleteEntryFromHash(ActiveQItems, It);
822 citthread_mutex_unlock(&ActiveQItemsLock);
824 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
826 // TODO: bounce & delete?
831 // TODO : free RelayUrls
838 * smtp_queue_thread()
840 * Run through the queue sending out messages.
842 void *smtp_queue_thread(void *arg) {
843 int num_processed = 0;
844 struct CitContext smtp_queue_CC;
848 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
849 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
850 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
852 while (!CtdlThreadCheckStop()) {
854 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
856 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
857 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
860 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
862 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
866 CtdlClearSystemContext();
873 * Initialize the SMTP outbound queue
875 void smtp_init_spoolout(void) {
876 struct ctdlroom qrbuf;
879 * Create the room. This will silently fail if the room already
880 * exists, and that's perfectly ok, because we want it to exist.
882 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
885 * Make sure it's set to be a "system room" so it doesn't show up
886 * in the <K>nown rooms list for Aides.
888 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
889 qrbuf.QRflags2 |= QR2_SYSTEM;
890 CtdlPutRoomLock(&qrbuf);
897 /*****************************************************************************/
898 /* SMTP UTILITY COMMANDS */
899 /*****************************************************************************/
901 void cmd_smtp(char *argbuf) {
908 if (CtdlAccessCheck(ac_aide)) return;
910 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
912 if (!strcasecmp(cmd, "mx")) {
913 extract_token(node, argbuf, 1, '|', sizeof node);
914 num_mxhosts = getmx(buf, node);
915 cprintf("%d %d MX hosts listed for %s\n",
916 LISTING_FOLLOWS, num_mxhosts, node);
917 for (i=0; i<num_mxhosts; ++i) {
918 extract_token(node, buf, i, '|', sizeof node);
919 cprintf("%s\n", node);
925 else if (!strcasecmp(cmd, "runqueue")) {
927 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
932 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
940 CTDL_MODULE_INIT(smtp_queu)
942 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
945 ActiveQItems = NewHash(1, Flathash);
946 citthread_mutex_init(&ActiveQItemsLock, NULL);
948 QItemHandlers = NewHash(0, NULL);
950 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
951 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
952 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
953 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
954 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
955 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
956 Put(QItemHandlers, HKEY("submitted"), QItem_Handle_Submitted, reference_free_handler);
957 ////TODO: flush qitemhandlers on exit
958 smtp_init_spoolout();
960 CtdlRegisterCleanupHook(smtp_evq_cleanup);
961 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
963 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
967 /* return our Subversion id for the Log */
968 return "smtpeventclient";