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)
144 FreeStrBuf(&(*Url)->URL);
145 if ((*Url)->Next != NULL)
146 FreeURL(&(*Url)->Next);
152 void FreeMailQEntry(void *qv)
155 FreeStrBuf(&Q->Recipient);
156 FreeStrBuf(&Q->StatusMessage);
159 void FreeQueItem(OneQueItem **Item)
161 DeleteHash(&(*Item)->MailQEntries);
162 FreeStrBuf(&(*Item)->EnvelopeFrom);
163 FreeStrBuf(&(*Item)->BounceTo);
164 FreeURL(&(*Item)->URL);
168 void HFreeQueItem(void *Item)
170 FreeQueItem((OneQueItem**)&Item);
173 /* inspect recipients with a status of:
174 * - 0 (no delivery yet attempted)
175 * - 3/4 (transient errors
176 * were experienced and it's time to try again)
178 int CountActiveQueueEntries(OneQueItem *MyQItem)
185 MyQItem->ActiveDeliveries = 0;
186 It = GetNewHashPos(MyQItem->MailQEntries, 0);
187 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
189 MailQEntry *ThisItem = vQE;
190 if ((ThisItem->Status == 0) ||
191 (ThisItem->Status == 3) ||
192 (ThisItem->Status == 4))
194 MyQItem->ActiveDeliveries++;
195 ThisItem->Active = 1;
198 ThisItem->Active = 0;
201 return MyQItem->ActiveDeliveries;
204 OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID)
207 const char *pLine = NULL;
212 Item = (OneQueItem*)malloc(sizeof(OneQueItem));
213 memset(Item, 0, sizeof(OneQueItem));
214 Item->LastAttempt.retry = SMTP_RETRY_INTERVAL;
215 Item->MessageID = -1;
216 Item->QueMsgID = QueMsgID;
218 citthread_mutex_lock(&ActiveQItemsLock);
219 if (GetHash(ActiveQItems,
220 IKEY(Item->QueMsgID),
223 /* WHOOPS. somebody else is already working on this. */
224 citthread_mutex_unlock(&ActiveQItemsLock);
229 /* mark our claim on this. */
231 IKEY(Item->QueMsgID),
234 citthread_mutex_unlock(&ActiveQItemsLock);
238 Line = NewStrBufPlain(NULL, 128);
239 while (pLine != StrBufNOTNULL) {
240 const char *pItemPart = NULL;
243 StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n');
244 if (StrLength(Line) == 0) continue;
245 StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
246 if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
249 H = (QItemHandler) vHandler;
250 H(Item, Line, &pItemPart);
258 StrBuf *SerializeQueueItem(OneQueItem *MyQItem)
266 QMessage = NewStrBufPlain(NULL, SIZ);
267 StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME);
269 // "attempted|%ld\n" "retry|%ld\n",, (long)time(NULL), (long)retry );
270 StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0);
271 StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID);
273 StrBufAppendBufPlain(QMessage, HKEY("\nsubmitted|"), 0);
274 StrBufAppendPrintf(QMessage, "%ld", MyQItem->Submitted);
276 if (StrLength(MyQItem->BounceTo) > 0) {
277 StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0);
278 StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0);
281 if (StrLength(MyQItem->EnvelopeFrom) > 0) {
282 StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0);
283 StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0);
286 It = GetNewHashPos(MyQItem->MailQEntries, 0);
287 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
289 MailQEntry *ThisItem = vQE;
292 if (!ThisItem->Active)
293 continue; /* skip already sent ones from the spoolfile. */
295 for (i=0; i < ThisItem->nAttempts; i++) {
296 /* TODO: most probably there is just one retry/attempted per message! */
297 StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0);
298 StrBufAppendPrintf(QMessage, "%ld",
299 ThisItem->Attempts[i].retry);
301 StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0);
302 StrBufAppendPrintf(QMessage, "%ld",
303 ThisItem->Attempts[i].when);
305 StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0);
306 StrBufAppendBuf(QMessage, ThisItem->Recipient, 0);
307 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
308 StrBufAppendPrintf(QMessage, "%d", ThisItem->Status);
309 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
310 StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0);
313 StrBufAppendBufPlain(QMessage, HKEY("\n"), 0);
321 void NewMailQEntry(OneQueItem *Item)
323 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
324 memset(Item->Current, 0, sizeof(MailQEntry));
326 if (Item->MailQEntries == NULL)
327 Item->MailQEntries = NewHash(1, Flathash);
328 Item->Current->StatusMessage = NewStrBuf();
329 Item->Current->n = GetCount(Item->MailQEntries);
330 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
333 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
335 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
338 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
340 if (Item->EnvelopeFrom == NULL)
341 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
342 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
345 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
347 if (Item->BounceTo == NULL)
348 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
349 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
352 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
354 if (Item->Current == NULL)
356 if (Item->Current->Recipient == NULL)
357 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
358 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
359 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
360 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
361 Item->Current = NULL; // TODO: is this always right?
365 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
367 if (Item->Current == NULL)
369 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
370 Item->Current->nAttempts++;
371 if (Item->Current->nAttempts > MaxAttempts) {
375 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
379 void QItem_Handle_Submitted(OneQueItem *Item, StrBuf *Line, const char **Pos)
381 Item->Submitted = atol(*Pos);
385 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
387 if (Item->Current == NULL)
389 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
390 Item->Current->nAttempts++;
391 if (Item->Current->nAttempts > MaxAttempts) {
396 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
397 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
399 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
400 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
401 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
402 Item->LastAttempt.retry = SMTP_RETRY_MAX;
409 * this one has to have the context for loading the message via the redirect buffer...
411 StrBuf *smtp_load_msg(OneQueItem *MyQItem, int n)
416 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
417 CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
418 SendMsg = CCC->redirect_buffer;
419 CCC->redirect_buffer = NULL;
420 if ((StrLength(SendMsg) > 0) &&
421 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
422 CtdlLogPrintf(CTDL_WARNING,
423 "SMTP client[%ld]: Possible problem: message did not "
424 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
425 MsgCount, //yes uncool, but best choice here...
426 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
427 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
435 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
436 * instructions for "5" codes (permanent fatal errors) and produce/deliver
437 * a "bounce" message (delivery status notification).
439 void smtpq_do_bounce(OneQueItem *MyQItem, StrBuf *OMsgTxt)
443 struct CtdlMessage *bmsg = NULL;
447 struct recptypes *valid;
454 int successful_bounce = 0;
458 CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
460 if ( (ev_time() - MyQItem->Submitted) > SMTP_GIVE_UP ) {
461 give_up = 1;/// TODO: replace time by libevq timer get
465 * Now go through the instructions checking for stuff.
467 It = GetNewHashPos(MyQItem->MailQEntries, 0);
468 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
470 MailQEntry *ThisItem = vQE;
471 if ((ThisItem->Status == 5) || /* failed now? */
472 ((give_up == 1) && (ThisItem->Status != 2))) /* giving up after failed attempts... */
474 if (num_bounces == 0)
475 Msg = NewStrBufPlain(NULL, 1024);
478 StrBufAppendBuf(Msg, ThisItem->Recipient, 0);
479 StrBufAppendBufPlain(Msg, HKEY(": "), 0);
480 StrBufAppendBuf(Msg, ThisItem->StatusMessage, 0);
481 StrBufAppendBufPlain(Msg, HKEY("\r\n"), 0);
486 /* Deliver the bounce if there's anything worth mentioning */
487 CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
489 if (num_bounces == 0) {
494 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
495 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
497 /* Start building our bounce message; go shopping for memory first. */
498 BounceMB = NewStrBufPlain(NULL,
499 1024 + /* mime stuff.... */
500 StrLength(Msg) + /* the bounce information... */
501 StrLength(OMsgTxt)); /* the original message */
502 if (BounceMB == NULL) {
503 FreeStrBuf(&boundary);
504 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
509 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
511 FreeStrBuf(&boundary);
512 FreeStrBuf(&BounceMB);
513 CtdlLogPrintf(CTDL_ERR, "Failed to alloc() bounce message.\n");
517 memset(bmsg, 0, sizeof(struct CtdlMessage));
520 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
521 StrBufAppendBuf(BounceMB, boundary, 0);
522 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
523 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
524 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
525 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
526 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
527 StrBufAppendBuf(BounceMB, boundary, 0);
528 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
529 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
532 StrBufAppendBufPlain(
535 "A message you sent could not be delivered to some or all of its recipients\n"
536 "due to prolonged unavailability of its destination(s).\n"
537 "Giving up on the following addresses:\n\n"
540 StrBufAppendBufPlain(
543 "A message you sent could not be delivered to some or all of its recipients.\n"
544 "The following addresses were undeliverable:\n\n"
547 StrBufAppendBuf(BounceMB, Msg, 0);
550 /* Attach the original message */
551 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
552 StrBufAppendBuf(BounceMB, boundary, 0);
553 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
554 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
555 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
556 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
557 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
558 StrBufAppendBuf(BounceMB, OMsgTxt, 0);
560 /* Close the multipart MIME scope */
561 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
562 StrBufAppendBuf(BounceMB, boundary, 0);
563 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
567 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
568 bmsg->cm_anon_type = MES_NORMAL;
569 bmsg->cm_format_type = FMT_RFC822;
571 bmsg->cm_fields['O'] = strdup(MAILROOM);
572 bmsg->cm_fields['A'] = strdup("Citadel");
573 bmsg->cm_fields['N'] = strdup(config.c_nodename);
574 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
575 bmsg->cm_fields['M'] = SmashStrBuf(&BounceMB);
577 /* First try the user who sent the message */
578 if (StrLength(MyQItem->BounceTo) == 0)
579 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
581 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", ChrPtr(MyQItem->BounceTo));
583 /* Can we deliver the bounce to the original sender? */
584 valid = validate_recipients(ChrPtr(MyQItem->BounceTo), NULL, 0);
585 if ((valid != NULL) && (valid->num_error == 0)) {
586 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
587 successful_bounce = 1;
590 /* If not, post it in the Aide> room */
591 if (successful_bounce == 0) {
592 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
595 /* Free up the memory we used */
596 free_recipients(valid);
597 FreeStrBuf(&boundary);
598 CtdlFreeMessage(bmsg);
599 CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
604 int ParseURL(ParsedURL **Url, StrBuf *UrlStr, short DefaultPort)
606 const char *pch, *pEndHost, *pPort, *pCredEnd, *pUserEnd;
607 ParsedURL *url = (ParsedURL *)malloc(sizeof(ParsedURL));
608 memset(url, 0, sizeof(ParsedURL));
611 url->Port = DefaultPort;
613 * http://username:passvoid@[ipv6]:port/url
615 url->URL = NewStrBufDup(UrlStr);
616 url->Host = pch = ChrPtr(url->URL);
617 url->LocalPart = strchr(pch, '/');
618 if (url->LocalPart != NULL) {
619 if ((*(url->LocalPart + 1) == '/') &&
620 (*(url->LocalPart + 2) == ':')) { /* TODO: find default port for this protocol... */
621 url->Host = url->LocalPart + 3;
622 url->LocalPart = strchr(url->Host, '/');
625 if (url->LocalPart == NULL) {
626 url->LocalPart = pch + StrLength(url->URL);
629 pCredEnd = strchr(pch, '@');
630 if (pCredEnd >= url->LocalPart)
632 if (pCredEnd != NULL)
634 url->User = url->Host;
635 url->Host = pCredEnd + 1;
636 pUserEnd = strchr(url->User, ':');
638 if (pUserEnd > pCredEnd)
641 url->Pass = pUserEnd + 1;
643 StrBufPeek(url->URL, pUserEnd, 0, '\0');
644 StrBufPeek(url->URL, pCredEnd, 0, '\0');
648 if (*url->Host == '[') {
650 pEndHost = strchr(url->Host, ']');
651 if (pEndHost == NULL) {
652 FreeStrBuf(&url->URL);
654 return 0; /* invalid syntax, no ipv6 */
656 StrBufPeek(url->URL, pEndHost, 0, '\0');
657 if (*(pEndHost + 1) == ':'){
658 StrBufPeek(url->URL, pEndHost + 1, 0, '\0');
659 pPort = pEndHost + 2;
664 pPort = strchr(url->Host, ':');
666 StrBufPeek(url->URL, pPort, 0, '\0');
671 url->Port = atol(pPort);
672 url->IsIP = inet_pton(url->af, url->Host, &url->Addr);
680 n_smarthosts = get_hosts(char *mxbuf, char *rectype);
686 * Called by smtp_do_queue() to handle an individual message.
688 void smtp_do_procmsg(long msgnum, void *userdata) {
689 struct CtdlMessage *msg = NULL;
699 ParsedURL *RelayUrls = NULL;
703 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
704 ///strcpy(envelope_from, "");
706 msg = CtdlFetchMessage(msgnum, 1);
708 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
712 pch = instr = msg->cm_fields['M'];
714 /* Strip out the headers (no not amd any other non-instruction) line */
715 while (pch != NULL) {
716 pch = strchr(pch, '\n');
717 if ((pch != NULL) && (*(pch + 1) == '\n')) {
722 PlainQItem = NewStrBufPlain(instr, -1);
723 CtdlFreeMessage(msg);
724 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
725 FreeStrBuf(&PlainQItem);
727 if (MyQItem == NULL) {
728 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
729 return; /* s.b. else is already processing... */
733 * Postpone delivery if we've already tried recently.
735 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
736 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
738 It = GetNewHashPos(MyQItem->MailQEntries, 0);
739 citthread_mutex_lock(&ActiveQItemsLock);
741 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
742 DeleteEntryFromHash(ActiveQItems, It);
744 citthread_mutex_unlock(&ActiveQItemsLock);
745 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
748 }// TODO: reenable me.*/
751 * Bail out if there's no actual message associated with this
753 if (MyQItem->MessageID < 0L) {
754 CtdlLogPrintf(CTDL_ERR, "SMTP Queue: no 'msgid' directive found!\n");
755 It = GetNewHashPos(MyQItem->MailQEntries, 0);
756 citthread_mutex_lock(&ActiveQItemsLock);
758 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
759 DeleteEntryFromHash(ActiveQItems, It);
761 citthread_mutex_unlock(&ActiveQItemsLock);
763 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
769 ParsedURL **Url = &MyQItem->URL;
770 nRelays = get_hosts(mxbuf, "smarthost");
774 const char *Pos = NULL;
775 All = NewStrBufPlain(mxbuf, -1);
776 One = NewStrBufPlain(NULL, StrLength(All) + 1);
778 while ((Pos != StrBufNOTNULL) && ((Pos == NULL) || !IsEmptyStr(Pos))) {
779 StrBufExtract_NextToken(One, All, &Pos, '|');
780 if (!ParseURL(Url, One, 25))
781 CtdlLogPrintf(CTDL_DEBUG, "Failed to parse: %s\n", ChrPtr(One));
790 It = GetNewHashPos(MyQItem->MailQEntries, 0);
791 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
793 MailQEntry *ThisItem = vQE;
794 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
798 CountActiveQueueEntries(MyQItem);
799 if (MyQItem->ActiveDeliveries > 0)
803 Msg = smtp_load_msg(MyQItem, n);
804 It = GetNewHashPos(MyQItem->MailQEntries, 0);
805 while ((i <= MyQItem->ActiveDeliveries) &&
806 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
808 MailQEntry *ThisItem = vQE;
809 if (ThisItem->Active == 1) {
810 int KeepBuffers = (i == MyQItem->ActiveDeliveries);
811 if (i > 1) n = MsgCount++;
812 CtdlLogPrintf(CTDL_DEBUG, "SMTP Queue: Trying <%s>\n", ChrPtr(ThisItem->Recipient));
813 smtp_try(MyQItem, ThisItem, Msg, KeepBuffers, n, RelayUrls);
814 if (KeepBuffers) HaveBuffers = 1;
822 It = GetNewHashPos(MyQItem->MailQEntries, 0);
823 citthread_mutex_lock(&ActiveQItemsLock);
825 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
826 DeleteEntryFromHash(ActiveQItems, It);
828 citthread_mutex_unlock(&ActiveQItemsLock);
830 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
832 // TODO: bounce & delete?
837 // TODO : free RelayUrls
844 * smtp_queue_thread()
846 * Run through the queue sending out messages.
848 void *smtp_queue_thread(void *arg) {
849 int num_processed = 0;
850 struct CitContext smtp_queue_CC;
854 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
855 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
856 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
858 while (!CtdlThreadCheckStop()) {
860 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
862 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
863 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
866 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
868 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
872 CtdlClearSystemContext();
879 * Initialize the SMTP outbound queue
881 void smtp_init_spoolout(void) {
882 struct ctdlroom qrbuf;
885 * Create the room. This will silently fail if the room already
886 * exists, and that's perfectly ok, because we want it to exist.
888 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
891 * Make sure it's set to be a "system room" so it doesn't show up
892 * in the <K>nown rooms list for Aides.
894 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
895 qrbuf.QRflags2 |= QR2_SYSTEM;
896 CtdlPutRoomLock(&qrbuf);
903 /*****************************************************************************/
904 /* SMTP UTILITY COMMANDS */
905 /*****************************************************************************/
907 void cmd_smtp(char *argbuf) {
914 if (CtdlAccessCheck(ac_aide)) return;
916 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
918 if (!strcasecmp(cmd, "mx")) {
919 extract_token(node, argbuf, 1, '|', sizeof node);
920 num_mxhosts = getmx(buf, node);
921 cprintf("%d %d MX hosts listed for %s\n",
922 LISTING_FOLLOWS, num_mxhosts, node);
923 for (i=0; i<num_mxhosts; ++i) {
924 extract_token(node, buf, i, '|', sizeof node);
925 cprintf("%s\n", node);
931 else if (!strcasecmp(cmd, "runqueue")) {
933 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
938 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
946 CTDL_MODULE_INIT(smtp_queu)
948 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
951 ActiveQItems = NewHash(1, Flathash);
952 citthread_mutex_init(&ActiveQItemsLock, NULL);
954 QItemHandlers = NewHash(0, NULL);
956 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
957 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
958 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
959 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
960 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
961 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
962 Put(QItemHandlers, HKEY("submitted"), QItem_Handle_Submitted, reference_free_handler);
963 ////TODO: flush qitemhandlers on exit
964 smtp_init_spoolout();
966 CtdlRegisterCleanupHook(smtp_evq_cleanup);
967 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
969 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
973 /* return our Subversion id for the Log */
974 return "smtpeventclient";