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)
134 It = GetNewHashPos(MyQItem->MailQEntries, 0);
135 pthread_mutex_lock(&ActiveQItemsLock);
137 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
138 DeleteEntryFromHash(ActiveQItems, It);
140 pthread_mutex_unlock(&ActiveQItemsLock);
145 void FreeMailQEntry(void *qv)
148 FreeStrBuf(&Q->Recipient);
149 FreeStrBuf(&Q->StatusMessage);
152 void FreeQueItem(OneQueItem **Item)
154 DeleteHash(&(*Item)->MailQEntries);
155 FreeStrBuf(&(*Item)->EnvelopeFrom);
156 FreeStrBuf(&(*Item)->BounceTo);
157 FreeURL(&(*Item)->URL);
161 void HFreeQueItem(void *Item)
163 FreeQueItem((OneQueItem**)&Item);
166 /* inspect recipients with a status of:
167 * - 0 (no delivery yet attempted)
168 * - 3/4 (transient errors
169 * were experienced and it's time to try again)
171 int CountActiveQueueEntries(OneQueItem *MyQItem)
178 MyQItem->ActiveDeliveries = 0;
179 It = GetNewHashPos(MyQItem->MailQEntries, 0);
180 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
182 MailQEntry *ThisItem = vQE;
183 if ((ThisItem->Status == 0) ||
184 (ThisItem->Status == 3) ||
185 (ThisItem->Status == 4))
187 MyQItem->ActiveDeliveries++;
188 ThisItem->Active = 1;
191 ThisItem->Active = 0;
194 return MyQItem->ActiveDeliveries;
197 OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID)
200 const char *pLine = NULL;
205 Item = (OneQueItem*)malloc(sizeof(OneQueItem));
206 memset(Item, 0, sizeof(OneQueItem));
207 Item->LastAttempt.retry = SMTP_RETRY_INTERVAL;
208 Item->MessageID = -1;
209 Item->QueMsgID = QueMsgID;
211 pthread_mutex_lock(&ActiveQItemsLock);
212 if (GetHash(ActiveQItems,
216 /* WHOOPS. somebody else is already working on this. */
217 pthread_mutex_unlock(&ActiveQItemsLock);
222 /* mark our claim on this. */
224 IKEY(Item->QueMsgID),
227 pthread_mutex_unlock(&ActiveQItemsLock);
231 Line = NewStrBufPlain(NULL, 128);
232 while (pLine != StrBufNOTNULL) {
233 const char *pItemPart = NULL;
236 StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n');
237 if (StrLength(Line) == 0) continue;
238 StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
239 if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
242 H = (QItemHandler) vHandler;
243 H(Item, Line, &pItemPart);
251 StrBuf *SerializeQueueItem(OneQueItem *MyQItem)
259 QMessage = NewStrBufPlain(NULL, SIZ);
260 StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME);
262 // "attempted|%ld\n" "retry|%ld\n",, (long)time(NULL), (long)retry );
263 StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0);
264 StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID);
266 StrBufAppendBufPlain(QMessage, HKEY("\nsubmitted|"), 0);
267 StrBufAppendPrintf(QMessage, "%ld", MyQItem->Submitted);
269 if (StrLength(MyQItem->BounceTo) > 0) {
270 StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0);
271 StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0);
274 if (StrLength(MyQItem->EnvelopeFrom) > 0) {
275 StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0);
276 StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0);
279 It = GetNewHashPos(MyQItem->MailQEntries, 0);
280 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
282 MailQEntry *ThisItem = vQE;
285 if (!ThisItem->Active)
286 continue; /* skip already sent ones from the spoolfile. */
288 for (i=0; i < ThisItem->nAttempts; i++) {
289 /* TODO: most probably there is just one retry/attempted per message! */
290 StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0);
291 StrBufAppendPrintf(QMessage, "%ld",
292 ThisItem->Attempts[i].retry);
294 StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0);
295 StrBufAppendPrintf(QMessage, "%ld",
296 ThisItem->Attempts[i].when);
298 StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0);
299 StrBufAppendBuf(QMessage, ThisItem->Recipient, 0);
300 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
301 StrBufAppendPrintf(QMessage, "%d", ThisItem->Status);
302 StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
303 StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0);
306 StrBufAppendBufPlain(QMessage, HKEY("\n"), 0);
314 void NewMailQEntry(OneQueItem *Item)
316 Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
317 memset(Item->Current, 0, sizeof(MailQEntry));
319 if (Item->MailQEntries == NULL)
320 Item->MailQEntries = NewHash(1, Flathash);
321 Item->Current->StatusMessage = NewStrBuf();
322 Item->Current->n = GetCount(Item->MailQEntries);
323 Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry);
326 void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
328 Item->MessageID = StrBufExtractNext_int(Line, Pos, '|');
331 void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
333 if (Item->EnvelopeFrom == NULL)
334 Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
335 StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
338 void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
340 if (Item->BounceTo == NULL)
341 Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
342 StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
345 void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
347 if (Item->Current == NULL)
349 if (Item->Current->Recipient == NULL)
350 Item->Current->Recipient = NewStrBufPlain(NULL, StrLength(Line));
351 StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
352 Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
353 StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
354 Item->Current = NULL; // TODO: is this always right?
358 void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
360 if (Item->Current == NULL)
362 if (Item->Current->Attempts[Item->Current->nAttempts].retry != 0)
363 Item->Current->nAttempts++;
364 if (Item->Current->nAttempts > MaxAttempts) {
368 Item->Current->Attempts[Item->Current->nAttempts].retry = StrBufExtractNext_int(Line, Pos, '|');
372 void QItem_Handle_Submitted(OneQueItem *Item, StrBuf *Line, const char **Pos)
374 Item->Submitted = atol(*Pos);
378 void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
380 if (Item->Current == NULL)
382 if (Item->Current->Attempts[Item->Current->nAttempts].when != 0)
383 Item->Current->nAttempts++;
384 if (Item->Current->nAttempts > MaxAttempts) {
389 Item->Current->Attempts[Item->Current->nAttempts].when = StrBufExtractNext_int(Line, Pos, '|');
390 if (Item->Current->Attempts[Item->Current->nAttempts].when > Item->LastAttempt.when)
392 Item->LastAttempt.when = Item->Current->Attempts[Item->Current->nAttempts].when;
393 Item->LastAttempt.retry = Item->Current->Attempts[Item->Current->nAttempts].retry * 2;
394 if (Item->LastAttempt.retry > SMTP_RETRY_MAX)
395 Item->LastAttempt.retry = SMTP_RETRY_MAX;
402 * this one has to have the context for loading the message via the redirect buffer...
404 StrBuf *smtp_load_msg(OneQueItem *MyQItem, int n)
409 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
410 CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
411 SendMsg = CCC->redirect_buffer;
412 CCC->redirect_buffer = NULL;
413 if ((StrLength(SendMsg) > 0) &&
414 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
416 "SMTP client[%d]: Possible problem: message did not "
417 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
418 MsgCount, //yes uncool, but best choice here...
419 ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
420 StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
428 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
429 * instructions for "5" codes (permanent fatal errors) and produce/deliver
430 * a "bounce" message (delivery status notification).
432 void smtpq_do_bounce(OneQueItem *MyQItem, StrBuf *OMsgTxt)
436 struct CtdlMessage *bmsg = NULL;
440 struct recptypes *valid;
447 int successful_bounce = 0;
451 syslog(LOG_DEBUG, "smtp_do_bounce() called\n");
453 if ( (ev_time() - MyQItem->Submitted) > SMTP_GIVE_UP ) {
454 give_up = 1;/// TODO: replace time by libevq timer get
458 * Now go through the instructions checking for stuff.
460 It = GetNewHashPos(MyQItem->MailQEntries, 0);
461 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
463 MailQEntry *ThisItem = vQE;
464 if ((ThisItem->Status == 5) || /* failed now? */
465 ((give_up == 1) && (ThisItem->Status != 2))) /* giving up after failed attempts... */
467 if (num_bounces == 0)
468 Msg = NewStrBufPlain(NULL, 1024);
471 StrBufAppendBuf(Msg, ThisItem->Recipient, 0);
472 StrBufAppendBufPlain(Msg, HKEY(": "), 0);
473 StrBufAppendBuf(Msg, ThisItem->StatusMessage, 0);
474 StrBufAppendBufPlain(Msg, HKEY("\r\n"), 0);
479 /* Deliver the bounce if there's anything worth mentioning */
480 syslog(LOG_DEBUG, "num_bounces = %d\n", num_bounces);
482 if (num_bounces == 0) {
487 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
488 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
490 /* Start building our bounce message; go shopping for memory first. */
491 BounceMB = NewStrBufPlain(NULL,
492 1024 + /* mime stuff.... */
493 StrLength(Msg) + /* the bounce information... */
494 StrLength(OMsgTxt)); /* the original message */
495 if (BounceMB == NULL) {
496 FreeStrBuf(&boundary);
497 syslog(LOG_ERR, "Failed to alloc() bounce message.\n");
502 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
504 FreeStrBuf(&boundary);
505 FreeStrBuf(&BounceMB);
506 syslog(LOG_ERR, "Failed to alloc() bounce message.\n");
510 memset(bmsg, 0, sizeof(struct CtdlMessage));
513 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
514 StrBufAppendBuf(BounceMB, boundary, 0);
515 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
516 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
517 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
518 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
519 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
520 StrBufAppendBuf(BounceMB, boundary, 0);
521 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
522 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
525 StrBufAppendBufPlain(
528 "A message you sent could not be delivered to some or all of its recipients\n"
529 "due to prolonged unavailability of its destination(s).\n"
530 "Giving up on the following addresses:\n\n"
533 StrBufAppendBufPlain(
536 "A message you sent could not be delivered to some or all of its recipients.\n"
537 "The following addresses were undeliverable:\n\n"
540 StrBufAppendBuf(BounceMB, Msg, 0);
543 /* Attach the original message */
544 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
545 StrBufAppendBuf(BounceMB, boundary, 0);
546 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
547 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
548 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
549 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
550 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
551 StrBufAppendBuf(BounceMB, OMsgTxt, 0);
553 /* Close the multipart MIME scope */
554 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
555 StrBufAppendBuf(BounceMB, boundary, 0);
556 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
560 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
561 bmsg->cm_anon_type = MES_NORMAL;
562 bmsg->cm_format_type = FMT_RFC822;
564 bmsg->cm_fields['O'] = strdup(MAILROOM);
565 bmsg->cm_fields['A'] = strdup("Citadel");
566 bmsg->cm_fields['N'] = strdup(config.c_nodename);
567 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
568 bmsg->cm_fields['M'] = SmashStrBuf(&BounceMB);
570 /* First try the user who sent the message */
571 if (StrLength(MyQItem->BounceTo) == 0)
572 syslog(LOG_ERR, "No bounce address specified\n");
574 syslog(LOG_DEBUG, "bounce to user? <%s>\n", ChrPtr(MyQItem->BounceTo));
576 /* Can we deliver the bounce to the original sender? */
577 valid = validate_recipients(ChrPtr(MyQItem->BounceTo), NULL, 0);
578 if ((valid != NULL) && (valid->num_error == 0)) {
579 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
580 successful_bounce = 1;
583 /* If not, post it in the Aide> room */
584 if (successful_bounce == 0) {
585 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
588 /* Free up the memory we used */
589 free_recipients(valid);
590 FreeStrBuf(&boundary);
591 CtdlFreeMessage(bmsg);
592 syslog(LOG_DEBUG, "Done processing bounces\n");
601 n_smarthosts = get_hosts(char *mxbuf, char *rectype);
607 * Called by smtp_do_queue() to handle an individual message.
609 void smtp_do_procmsg(long msgnum, void *userdata) {
610 struct CtdlMessage *msg = NULL;
620 ParsedURL *RelayUrls = NULL;
624 syslog(LOG_DEBUG, "SMTP Queue: smtp_do_procmsg(%ld)\n", msgnum);
625 ///strcpy(envelope_from, "");
627 msg = CtdlFetchMessage(msgnum, 1);
629 syslog(LOG_ERR, "SMTP Queue: tried %ld but no such message!\n", msgnum);
633 pch = instr = msg->cm_fields['M'];
635 /* Strip out the headers (no not amd any other non-instruction) line */
636 while (pch != NULL) {
637 pch = strchr(pch, '\n');
638 if ((pch != NULL) && (*(pch + 1) == '\n')) {
643 PlainQItem = NewStrBufPlain(instr, -1);
644 CtdlFreeMessage(msg);
645 MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
646 FreeStrBuf(&PlainQItem);
648 if (MyQItem == NULL) {
649 syslog(LOG_ERR, "SMTP Queue: Msg No %ld: already in progress!\n", msgnum);
650 return; /* s.b. else is already processing... */
654 * Postpone delivery if we've already tried recently.
656 if (((time(NULL) - MyQItem->LastAttempt.when) < MyQItem->LastAttempt.retry) && (run_queue_now == 0)) {
657 syslog(LOG_DEBUG, "SMTP client: Retry time not yet reached.\n");
659 It = GetNewHashPos(MyQItem->MailQEntries, 0);
660 pthread_mutex_lock(&ActiveQItemsLock);
662 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
663 DeleteEntryFromHash(ActiveQItems, It);
665 pthread_mutex_unlock(&ActiveQItemsLock);
666 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
669 }// TODO: reenable me.*/
672 * Bail out if there's no actual message associated with this
674 if (MyQItem->MessageID < 0L) {
675 syslog(LOG_ERR, "SMTP Queue: no 'msgid' directive found!\n");
676 It = GetNewHashPos(MyQItem->MailQEntries, 0);
677 pthread_mutex_lock(&ActiveQItemsLock);
679 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
680 DeleteEntryFromHash(ActiveQItems, It);
682 pthread_mutex_unlock(&ActiveQItemsLock);
684 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
690 ParsedURL **Url = &MyQItem->URL;
691 nRelays = get_hosts(mxbuf, "smarthost");
695 const char *Pos = NULL;
696 All = NewStrBufPlain(mxbuf, -1);
697 One = NewStrBufPlain(NULL, StrLength(All) + 1);
699 while ((Pos != StrBufNOTNULL) && ((Pos == NULL) || !IsEmptyStr(Pos))) {
700 StrBufExtract_NextToken(One, All, &Pos, '|');
701 if (!ParseURL(Url, One, 25))
702 syslog(LOG_DEBUG, "Failed to parse: %s\n", ChrPtr(One));
704 ///if (!Url->IsIP)) /// todo dupe me fork ipv6
713 Url = &MyQItem->FallBackHost;
714 nRelays = get_hosts(mxbuf, "fallbackhost");
718 const char *Pos = NULL;
719 All = NewStrBufPlain(mxbuf, -1);
720 One = NewStrBufPlain(NULL, StrLength(All) + 1);
722 while ((Pos != StrBufNOTNULL) && ((Pos == NULL) || !IsEmptyStr(Pos))) {
723 StrBufExtract_NextToken(One, All, &Pos, '|');
724 if (!ParseURL(Url, One, 25))
725 syslog(LOG_DEBUG, "Failed to parse: %s\n", ChrPtr(One));
734 It = GetNewHashPos(MyQItem->MailQEntries, 0);
735 while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
737 MailQEntry *ThisItem = vQE;
738 syslog(LOG_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active);
742 CountActiveQueueEntries(MyQItem);
743 if (MyQItem->ActiveDeliveries > 0)
746 int m = MyQItem->ActiveDeliveries;
748 Msg = smtp_load_msg(MyQItem, n);
749 It = GetNewHashPos(MyQItem->MailQEntries, 0);
751 (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)))
753 MailQEntry *ThisItem = vQE;
754 if (ThisItem->Active == 1) {
755 int KeepBuffers = (i == m);
756 if (i > 1) n = MsgCount++;
758 "SMTP Queue: Trying <%s> %d / %d \n",
759 ChrPtr(ThisItem->Recipient),
762 smtp_try_one_queue_entry(MyQItem,
768 if (KeepBuffers) HaveBuffers = 1;
776 It = GetNewHashPos(MyQItem->MailQEntries, 0);
777 pthread_mutex_lock(&ActiveQItemsLock);
779 GetHashPosFromKey(ActiveQItems, IKEY(MyQItem->MessageID), It);
780 DeleteEntryFromHash(ActiveQItems, It);
782 pthread_mutex_unlock(&ActiveQItemsLock);
784 ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
786 // TODO: bounce & delete?
791 // TODO : free RelayUrls
798 * smtp_queue_thread()
800 * Run through the queue sending out messages.
802 void smtp_do_queue(void) {
803 static int is_running = 0;
804 int num_processed = 0;
806 if (is_running) return; /* Concurrency check - only one can run */
809 pthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
810 syslog(LOG_INFO, "SMTP client: processing outbound queue");
812 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
813 syslog(LOG_ERR, "Cannot find room <%s>", SMTP_SPOOLOUT_ROOM);
816 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
818 syslog(LOG_INFO, "SMTP client: queue run completed; %d messages processed", num_processed);
826 * Initialize the SMTP outbound queue
828 void smtp_init_spoolout(void) {
829 struct ctdlroom qrbuf;
832 * Create the room. This will silently fail if the room already
833 * exists, and that's perfectly ok, because we want it to exist.
835 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
838 * Make sure it's set to be a "system room" so it doesn't show up
839 * in the <K>nown rooms list for Aides.
841 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
842 qrbuf.QRflags2 |= QR2_SYSTEM;
843 CtdlPutRoomLock(&qrbuf);
850 /*****************************************************************************/
851 /* SMTP UTILITY COMMANDS */
852 /*****************************************************************************/
854 void cmd_smtp(char *argbuf) {
861 if (CtdlAccessCheck(ac_aide)) return;
863 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
865 if (!strcasecmp(cmd, "mx")) {
866 extract_token(node, argbuf, 1, '|', sizeof node);
867 num_mxhosts = getmx(buf, node);
868 cprintf("%d %d MX hosts listed for %s\n",
869 LISTING_FOLLOWS, num_mxhosts, node);
870 for (i=0; i<num_mxhosts; ++i) {
871 extract_token(node, buf, i, '|', sizeof node);
872 cprintf("%s\n", node);
878 else if (!strcasecmp(cmd, "runqueue")) {
880 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
885 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
891 CTDL_MODULE_INIT(smtp_queu)
895 CtdlFillSystemContext(&smtp_queue_CC, "SMTP_Send");
896 ActiveQItems = NewHash(1, lFlathash);
897 pthread_mutex_init(&ActiveQItemsLock, NULL);
899 QItemHandlers = NewHash(0, NULL);
901 Put(QItemHandlers, HKEY("msgid"), QItem_Handle_MsgID, reference_free_handler);
902 Put(QItemHandlers, HKEY("envelope_from"), QItem_Handle_EnvelopeFrom, reference_free_handler);
903 Put(QItemHandlers, HKEY("retry"), QItem_Handle_retry, reference_free_handler);
904 Put(QItemHandlers, HKEY("attempted"), QItem_Handle_Attempted, reference_free_handler);
905 Put(QItemHandlers, HKEY("remote"), QItem_Handle_Recipient, reference_free_handler);
906 Put(QItemHandlers, HKEY("bounceto"), QItem_Handle_BounceTo, reference_free_handler);
907 Put(QItemHandlers, HKEY("submitted"), QItem_Handle_Submitted, reference_free_handler);
908 ////TODO: flush qitemhandlers on exit
909 smtp_init_spoolout();
911 CtdlRegisterCleanupHook(smtp_evq_cleanup);
913 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
916 /* return our Subversion id for the Log */
917 return "smtpeventclient";