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)
186 long ActiveDeliveries;
190 ActiveDeliveries = 0;
191 It = GetNewHashPos(MyQItem->MailQEntries, 0);
192 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
194 MailQEntry *ThisItem = vQE;
195 if ((ThisItem->Status == 0) ||
196 (ThisItem->Status == 3) ||
197 (ThisItem->Status == 4))
200 ThisItem->Active = 1;
203 ThisItem->Active = 0;
206 return ActiveDeliveries;
209 OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID)
212 const char *pLine = NULL;
217 Item = (OneQueItem*)malloc(sizeof(OneQueItem));
218 memset(Item, 0, sizeof(OneQueItem));
219 Item->LastAttempt.retry = SMTP_RETRY_INTERVAL;
220 Item->MessageID = -1;
221 Item->QueMsgID = QueMsgID;
224 Line = NewStrBufPlain(NULL, 128);
225 while (pLine != StrBufNOTNULL) {
226 const char *pItemPart = NULL;
229 StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n');
230 if (StrLength(Line) == 0) continue;
231 StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
232 if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
235 H = (QItemHandler) vHandler;
236 H(Item, Line, &pItemPart);
242 pthread_mutex_lock(&ActiveQItemsLock);
243 if (GetHash(ActiveQItems,
244 LKEY(Item->MessageID),
247 /* WHOOPS. somebody else is already working on this. */
248 pthread_mutex_unlock(&ActiveQItemsLock);
253 /* mark our claim on this. */
255 LKEY(Item->MessageID),
258 pthread_mutex_unlock(&ActiveQItemsLock);
264 StrBuf *SerializeQueueItem(OneQueItem *MyQItem)
272 QMessage = NewStrBufPlain(NULL, SIZ);
273 StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME);
275 // "attempted|%ld\n" "retry|%ld\n",, (long)time(NULL), (long)retry );
276 StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0);
277 StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID);
279 StrBufAppendBufPlain(QMessage, HKEY("\nsubmitted|"), 0);
280 StrBufAppendPrintf(QMessage, "%ld", MyQItem->Submitted);
282 if (StrLength(MyQItem->BounceTo) > 0) {
283 StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0);
284 StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0);
287 if (StrLength(MyQItem->EnvelopeFrom) > 0) {
288 StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0);
289 StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0);
292 It = GetNewHashPos(MyQItem->MailQEntries, 0);
293 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
295 MailQEntry *ThisItem = vQE;
298 if (!ThisItem->Active)
299 continue; /* skip already sent ones from the spoolfile. */
301 for (i=0; i < ThisItem->nAttempts; i++) {
302 /* TODO: most probably there is just one retry/attempted per message! */
303 StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0);
304 StrBufAppendPrintf(QMessage, "%ld",
305 ThisItem->Attempts[i].retry);
307 StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0);
308 StrBufAppendPrintf(QMessage, "%ld",
309 ThisItem->Attempts[i].when);
311 StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0);
312 StrBufAppendBuf(QMessage, ThisItem->Recipient, 0);
313 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
314 StrBufAppendPrintf(QMessage, "%d", ThisItem->Status);
315 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
316 StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0);
319 StrBufAppendBufPlain(QMessage, HKEY("\n"), 0);
327 void NewMailQEntry(OneQueItem *Item)
329 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
330 memset(Item->Current, 0, sizeof(MailQEntry));
332 if (Item->MailQEntries == NULL)
333 Item->MailQEntries = NewHash(1, Flathash);
334 Item->Current->StatusMessage = NewStrBuf();
335 Item->Current->n = GetCount(Item->MailQEntries);
336 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
339 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
341 Item->MessageID = StrBufExtractNext_long(Line, Pos, '|');
344 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
346 if (Item->EnvelopeFrom == NULL)
347 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
348 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
351 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
353 if (Item->BounceTo == NULL)
354 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
355 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
358 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
360 if (Item->Current == NULL)
362 if (Item->Current->Recipient == NULL)
363 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
364 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
365 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
366 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
367 Item->Current = NULL; // TODO: is this always right?
371 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
373 if (Item->Current == NULL)
375 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
376 Item->Current->nAttempts++;
377 if (Item->Current->nAttempts > MaxAttempts) {
381 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
385 void QItem_Handle_Submitted(OneQueItem *Item, StrBuf *Line, const char **Pos)
387 Item->Submitted = atol(*Pos);
391 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
393 if (Item->Current == NULL)
395 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
396 Item->Current->nAttempts++;
397 if (Item->Current->nAttempts > MaxAttempts) {
402 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
403 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
405 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
406 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
407 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
408 Item->LastAttempt.retry = SMTP_RETRY_MAX;
415 * this one has to have the context for loading the message via the redirect buffer...
417 StrBuf *smtp_load_msg(OneQueItem *MyQItem, int n)
422 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
423 CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
424 SendMsg = CCC->redirect_buffer;
425 CCC->redirect_buffer = NULL;
426 if ((StrLength(SendMsg) > 0) &&
427 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
429 "SMTP client[%d]: Possible problem: message did not "
430 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
431 MsgCount, //yes uncool, but best choice here...
432 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
433 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
441 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
442 * instructions for "5" codes (permanent fatal errors) and produce/deliver
443 * a "bounce" message (delivery status notification).
445 void smtpq_do_bounce(OneQueItem *MyQItem, StrBuf *OMsgTxt)
449 struct CtdlMessage *bmsg = NULL;
453 struct recptypes *valid;
460 int successful_bounce = 0;
464 syslog(LOG_DEBUG, "smtp_do_bounce() called\n");
466 if ( (ev_time() - MyQItem->Submitted) > SMTP_GIVE_UP ) {
467 give_up = 1;/// TODO: replace time by libevq timer get
471 * Now go through the instructions checking for stuff.
473 It = GetNewHashPos(MyQItem->MailQEntries, 0);
474 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
476 MailQEntry *ThisItem = vQE;
477 if ((ThisItem->Status == 5) || /* failed now? */
478 ((give_up == 1) && (ThisItem->Status != 2))) /* giving up after failed attempts... */
480 if (num_bounces == 0)
481 Msg = NewStrBufPlain(NULL, 1024);
484 StrBufAppendBuf(Msg, ThisItem->Recipient, 0);
485 StrBufAppendBufPlain(Msg, HKEY(": "), 0);
486 StrBufAppendBuf(Msg, ThisItem->StatusMessage, 0);
487 StrBufAppendBufPlain(Msg, HKEY("\r\n"), 0);
492 /* Deliver the bounce if there's anything worth mentioning */
493 syslog(LOG_DEBUG, "num_bounces = %d\n", num_bounces);
495 if (num_bounces == 0) {
500 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
501 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
503 /* Start building our bounce message; go shopping for memory first. */
504 BounceMB = NewStrBufPlain(NULL,
505 1024 + /* mime stuff.... */
506 StrLength(Msg) + /* the bounce information... */
507 StrLength(OMsgTxt)); /* the original message */
508 if (BounceMB == NULL) {
509 FreeStrBuf(&boundary);
510 syslog(LOG_ERR, "Failed to alloc() bounce message.\n");
515 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
517 FreeStrBuf(&boundary);
518 FreeStrBuf(&BounceMB);
519 syslog(LOG_ERR, "Failed to alloc() bounce message.\n");
523 memset(bmsg, 0, sizeof(struct CtdlMessage));
526 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
527 StrBufAppendBuf(BounceMB, boundary, 0);
528 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
529 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
530 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
531 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
532 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
533 StrBufAppendBuf(BounceMB, boundary, 0);
534 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
535 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
538 StrBufAppendBufPlain(
541 "A message you sent could not be delivered to some or all of its recipients\n"
542 "due to prolonged unavailability of its destination(s).\n"
543 "Giving up on the following addresses:\n\n"
546 StrBufAppendBufPlain(
549 "A message you sent could not be delivered to some or all of its recipients.\n"
550 "The following addresses were undeliverable:\n\n"
553 StrBufAppendBuf(BounceMB, Msg, 0);
556 /* Attach the original message */
557 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
558 StrBufAppendBuf(BounceMB, boundary, 0);
559 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
560 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
561 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
562 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
563 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
564 StrBufAppendBuf(BounceMB, OMsgTxt, 0);
566 /* Close the multipart MIME scope */
567 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
568 StrBufAppendBuf(BounceMB, boundary, 0);
569 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
573 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
574 bmsg->cm_anon_type = MES_NORMAL;
575 bmsg->cm_format_type = FMT_RFC822;
577 bmsg->cm_fields['O'] = strdup(MAILROOM);
578 bmsg->cm_fields['A'] = strdup("Citadel");
579 bmsg->cm_fields['N'] = strdup(config.c_nodename);
580 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
581 bmsg->cm_fields['M'] = SmashStrBuf(&BounceMB);
583 /* First try the user who sent the message */
584 if (StrLength(MyQItem->BounceTo) == 0)
585 syslog(LOG_ERR, "No bounce address specified\n");
587 syslog(LOG_DEBUG, "bounce to user? <%s>\n", ChrPtr(MyQItem->BounceTo));
589 /* Can we deliver the bounce to the original sender? */
590 valid = validate_recipients(ChrPtr(MyQItem->BounceTo), NULL, 0);
591 if ((valid != NULL) && (valid->num_error == 0)) {
592 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
593 successful_bounce = 1;
596 /* If not, post it in the Aide> room */
597 if (successful_bounce == 0) {
598 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
601 /* Free up the memory we used */
602 free_recipients(valid);
603 FreeStrBuf(&boundary);
604 CtdlFreeMessage(bmsg);
605 syslog(LOG_DEBUG, "Done processing bounces\n");
614 n_smarthosts = get_hosts(char *mxbuf, char *rectype);
620 * Called by smtp_do_queue() to handle an individual message.
622 void smtp_do_procmsg(long msgnum, void *userdata) {
623 struct CtdlMessage *msg = NULL;
633 ParsedURL *RelayUrls = NULL;
637 syslog(LOG_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
638 ///strcpy(envelope_from, "");
640 msg = CtdlFetchMessage(msgnum, 1);
642 syslog(LOG_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
646 pch = instr = msg->cm_fields['M'];
648 /* Strip out the headers (no not amd any other non-instruction) line */
649 while (pch != NULL) {
650 pch = strchr(pch, '\n');
651 if ((pch != NULL) && (*(pch + 1) == '\n')) {
656 PlainQItem = NewStrBufPlain(instr, -1);
657 CtdlFreeMessage(msg);
658 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
659 FreeStrBuf(&PlainQItem);
661 if (MyQItem == NULL) {
662 syslog(LOG_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
663 return; /* s.b. else is already processing... */
667 * Postpone delivery if we've already tried recently.
669 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
670 syslog(LOG_DEBUG, "SMTP client: Retry time not yet reached.\n");
672 It = GetNewHashPos(MyQItem->MailQEntries, 0);
673 pthread_mutex_lock(&ActiveQItemsLock);
675 if (GetHashPosFromKey(ActiveQItems, LKEY(MyQItem->MessageID), It))
676 DeleteEntryFromHash(ActiveQItems, It);
678 pthread_mutex_unlock(&ActiveQItemsLock);
679 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
682 }// TODO: reenable me.*/
685 * Bail out if there's no actual message associated with this
687 if (MyQItem->MessageID < 0L) {
688 syslog(LOG_ERR, "SMTP Queue: no 'msgid' directive found!\n");
689 It = GetNewHashPos(MyQItem->MailQEntries, 0);
690 pthread_mutex_lock(&ActiveQItemsLock);
692 if (GetHashPosFromKey(ActiveQItems, LKEY(MyQItem->MessageID), It))
693 DeleteEntryFromHash(ActiveQItems, It);
695 pthread_mutex_unlock(&ActiveQItemsLock);
697 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
703 ParsedURL **Url = &MyQItem->URL;
704 nRelays = get_hosts(mxbuf, "smarthost");
708 const char *Pos = NULL;
709 All = NewStrBufPlain(mxbuf, -1);
710 One = NewStrBufPlain(NULL, StrLength(All) + 1);
712 while ((Pos != StrBufNOTNULL) && ((Pos == NULL) || !IsEmptyStr(Pos))) {
713 StrBufExtract_NextToken(One, All, &Pos, '|');
714 if (!ParseURL(Url, One, 25))
715 syslog(LOG_DEBUG, "Failed to parse: %s\n", ChrPtr(One));
717 ///if (!Url->IsIP)) /// todo dupe me fork ipv6
726 Url = &MyQItem->FallBackHost;
727 nRelays = get_hosts(mxbuf, "fallbackhost");
731 const char *Pos = NULL;
732 All = NewStrBufPlain(mxbuf, -1);
733 One = NewStrBufPlain(NULL, StrLength(All) + 1);
735 while ((Pos != StrBufNOTNULL) && ((Pos == NULL) || !IsEmptyStr(Pos))) {
736 StrBufExtract_NextToken(One, All, &Pos, '|');
737 if (!ParseURL(Url, One, 25))
738 syslog(LOG_DEBUG, "Failed to parse: %s\n", ChrPtr(One));
747 It = GetNewHashPos(MyQItem->MailQEntries, 0);
748 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
750 MailQEntry *ThisItem = vQE;
751 syslog(LOG_DEBUG, "SMTP Queue: Task: <%s> %d\n",
752 ChrPtr(ThisItem->Recipient),
757 MyQItem->ActiveDeliveries = CountActiveQueueEntries(MyQItem);
758 if (MyQItem->ActiveDeliveries > 0)
761 int m = MyQItem->ActiveDeliveries;
763 Msg = smtp_load_msg(MyQItem, n);
764 It = GetNewHashPos(MyQItem->MailQEntries, 0);
766 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
768 MailQEntry *ThisItem = vQE;
769 if (ThisItem->Active == 1) {
770 int KeepBuffers = (i == m);
771 if (i > 1) n = MsgCount++;
773 "SMTP Queue: Trying <%ld> <%s> %d / %d \n",
775 ChrPtr(ThisItem->Recipient),
778 smtp_try_one_queue_entry(MyQItem,
784 if (KeepBuffers) HaveBuffers = 1;
792 It = GetNewHashPos(MyQItem->MailQEntries, 0);
793 pthread_mutex_lock(&ActiveQItemsLock);
795 if (GetHashPosFromKey(ActiveQItems, LKEY(MyQItem->MessageID), It))
796 DeleteEntryFromHash(ActiveQItems, It);
803 "SMTP cleanup: unable to find QItem with ID[%ld]",
805 while (GetNextHashPos(ActiveQItems, It, &len, &Key, &VData))
807 "SMTP cleanup: have: ID[%ld]",
808 ((OneQueItem *)VData)->MessageID);
812 pthread_mutex_unlock(&ActiveQItemsLock);
814 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
816 // TODO: bounce & delete?
821 // TODO : free RelayUrls
828 * smtp_queue_thread()
830 * Run through the queue sending out messages.
832 void smtp_do_queue(void) {
833 static int is_running = 0;
834 int num_processed = 0;
836 if (is_running) return; /* Concurrency check - only one can run */
839 pthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
840 syslog(LOG_INFO, "SMTP client: processing outbound queue");
842 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
843 syslog(LOG_ERR, "Cannot find room <%s>", SMTP_SPOOLOUT_ROOM);
846 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
848 syslog(LOG_INFO, "SMTP client: queue run completed; %d messages processed", num_processed);
856 * Initialize the SMTP outbound queue
858 void smtp_init_spoolout(void) {
859 struct ctdlroom qrbuf;
862 * Create the room. This will silently fail if the room already
863 * exists, and that's perfectly ok, because we want it to exist.
865 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
868 * Make sure it's set to be a "system room" so it doesn't show up
869 * in the <K>nown rooms list for Aides.
871 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
872 qrbuf.QRflags2 |= QR2_SYSTEM;
873 CtdlPutRoomLock(&qrbuf);
880 /*****************************************************************************/
881 /* SMTP UTILITY COMMANDS */
882 /*****************************************************************************/
884 void cmd_smtp(char *argbuf) {
891 if (CtdlAccessCheck(ac_aide)) return;
893 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
895 if (!strcasecmp(cmd, "mx")) {
896 extract_token(node, argbuf, 1, '|', sizeof node);
897 num_mxhosts = getmx(buf, node);
898 cprintf("%d %d MX hosts listed for %s\n",
899 LISTING_FOLLOWS, num_mxhosts, node);
900 for (i=0; i<num_mxhosts; ++i) {
901 extract_token(node, buf, i, '|', sizeof node);
902 cprintf("%s\n", node);
908 else if (!strcasecmp(cmd, "runqueue")) {
910 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
915 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
921 CTDL_MODULE_INIT(smtp_queu)
925 CtdlFillSystemContext(&smtp_queue_CC, "SMTP_Send");
926 ActiveQItems = NewHash(1, lFlathash);
927 pthread_mutex_init(&ActiveQItemsLock, NULL);
929 QItemHandlers = NewHash(0, NULL);
931 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
932 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
933 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
934 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
935 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
936 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
937 Put(QItemHandlers, HKEY("submitted"), QItem_Handle_Submitted, reference_free_handler);
938 ////TODO: flush qitemhandlers on exit
939 smtp_init_spoolout();
941 CtdlRegisterCleanupHook(smtp_evq_cleanup);
943 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
944 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
947 /* return our Subversion id for the Log */
948 return "smtpeventclient";