--- /dev/null
+/*
+ * Transmit outbound SMTP mail to the big wide world of the Internet
+ *
+ * This is the new, exciting, clever version that makes libcurl do all the work :)
+ *
+ * Copyright (c) 1997-2017 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sysconfig.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <libcitadel.h>
+#include <curl/curl.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "ctdl_module.h"
+#include "clientsocket.h"
+#include "msgbase.h"
+#include "domain.h"
+#include "internet_addressing.h"
+#include "citadel_dirs.h"
+#include "modules/smtp/smtp_util.h"
+
+struct smtpmsgsrc { // Data passed in and out of libcurl for message upload
+ StrBuf *TheMessage;
+ int bytes_total;
+ int bytes_sent;
+};
+
+struct CitContext smtp_client_CC;
+static int doing_smtpclient = 0;
+long *smtpq = NULL; // array of msgnums containing queue instructions
+int smtpq_count = 0; // number of queue messages in smtpq
+int smtpq_alloc = 0; // current allocation size for smtpq
+
+
+/*
+ * Initialize the SMTP outbound queue
+ */
+void smtp_init_spoolout(void) {
+ struct ctdlroom qrbuf;
+
+ /*
+ * Create the room. This will silently fail if the room already
+ * exists, and that's perfectly ok, because we want it to exist.
+ */
+ CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_QUEUE);
+
+ /*
+ * Make sure it's set to be a "system room" so it doesn't show up
+ * in the <K>nown rooms list for Aides.
+ */
+ if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
+ qrbuf.QRflags2 |= QR2_SYSTEM;
+ CtdlPutRoomLock(&qrbuf);
+ }
+}
+
+
+/* For internet mail, generate delivery instructions.
+ * Yes, this is recursive. Deal with it. Infinite recursion does
+ * not happen because the delivery instructions message does not
+ * contain a recipient.
+ */
+int smtp_aftersave(struct CtdlMessage *msg, recptypes *recps)
+{
+ if ((recps != NULL) && (recps->num_internet > 0)) {
+ struct CtdlMessage *imsg = NULL;
+ char recipient[SIZ];
+ StrBuf *SpoolMsg = NewStrBuf();
+ long nTokens;
+ int i;
+
+ syslog(LOG_DEBUG, "smtpclient: generating delivery instructions");
+
+ StrBufPrintf(SpoolMsg,
+ "Content-type: "SPOOLMIME"\n"
+ "\n"
+ "msgid|%s\n"
+ "submitted|%ld\n"
+ "bounceto|%s\n",
+ msg->cm_fields[eVltMsgNum],
+ (long)time(NULL),
+ recps->bounce_to);
+
+ if (recps->envelope_from != NULL) {
+ StrBufAppendBufPlain(SpoolMsg, HKEY("envelope_from|"), 0);
+ StrBufAppendBufPlain(SpoolMsg, recps->envelope_from, -1, 0);
+ StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
+ }
+ if (recps->sending_room != NULL) {
+ StrBufAppendBufPlain(SpoolMsg, HKEY("source_room|"), 0);
+ StrBufAppendBufPlain(SpoolMsg, recps->sending_room, -1, 0);
+ StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
+ }
+
+ nTokens = num_tokens(recps->recp_internet, '|');
+ for (i = 0; i < nTokens; i++) {
+ long len;
+ len = extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
+ if (len > 0) {
+ StrBufAppendBufPlain(SpoolMsg, HKEY("remote|"), 0);
+ StrBufAppendBufPlain(SpoolMsg, recipient, len, 0);
+ StrBufAppendBufPlain(SpoolMsg, HKEY("|0||\n"), 0);
+ }
+ }
+
+ imsg = malloc(sizeof(struct CtdlMessage));
+ memset(imsg, 0, sizeof(struct CtdlMessage));
+ imsg->cm_magic = CTDLMESSAGE_MAGIC;
+ imsg->cm_anon_type = MES_NORMAL;
+ imsg->cm_format_type = FMT_RFC822;
+ CM_SetField(imsg, eMsgSubject, HKEY("QMSG"));
+ CM_SetField(imsg, eAuthor, HKEY("Citadel"));
+ CM_SetField(imsg, eJournal, HKEY("do not journal"));
+ CM_SetAsFieldSB(imsg, eMesageText, &SpoolMsg);
+ CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
+ CM_Free(imsg);
+ }
+ return 0;
+}
+
+
+/*
+ * Callback for smtp_attempt_delivery() to supply libcurl with upload data.
+ */
+static size_t upload_source(void *ptr, size_t size, size_t nmemb, void *userp)
+{
+ struct smtpmsgsrc *s = (struct smtpmsgsrc *) userp;
+ int sendbytes = 0;
+ const char *send_this = NULL;
+
+ sendbytes = (size * nmemb);
+
+ if (s->bytes_sent >= s->bytes_total) {
+ return(0); // we are donez0r
+ }
+
+ if (sendbytes > (s->bytes_total - s->bytes_sent)) {
+ sendbytes = s->bytes_total - s->bytes_sent; // can't send more than we have
+ }
+
+ send_this = ChrPtr(s->TheMessage);
+ send_this += s->bytes_sent; // start where we last left off
+
+ memcpy(ptr, send_this, sendbytes);
+ s->bytes_sent += sendbytes;
+ return(sendbytes); // return the number of bytes _actually_ copied
+}
+
+
+/*
+ * Attempt a delivery to one recipient.
+ * Returns a three-digit SMTP status code.
+ */
+int smtp_attempt_delivery(long msgid, char *recp, char *envelope_from)
+{
+ struct smtpmsgsrc s;
+ char *fromaddr = NULL;
+ CURL *curl;
+ CURLcode res = CURLE_OK;
+ struct curl_slist *recipients = NULL;
+ long response_code = 421;
+ int num_mx = 0;
+ char mxes[SIZ];
+ char user[1024];
+ char node[1024];
+ char name[1024];
+ char try_this_mx[256];
+ int i;
+
+ syslog(LOG_DEBUG, "smtpclient: smtp_attempt_delivery(%ld, %s)", msgid, recp);
+
+ process_rfc822_addr(recp, user, node, name); // split recipient address into username, hostname, displayname
+ num_mx = getmx(mxes, node);
+ if (num_mx < 1) {
+ return(421);
+ }
+
+ CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+ CtdlOutputMsg(msgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0, NULL, &fromaddr, NULL);
+ s.TheMessage = CC->redirect_buffer;
+ s.bytes_total = StrLength(CC->redirect_buffer);
+ s.bytes_sent = 0;
+ CC->redirect_buffer = NULL;
+ response_code = 421;
+
+ for (i=0; ((i<num_mx)&&((response_code/100)==4)); ++i) { // keep trying MXes until one works or we run out
+ response_code = 421; // default 421 makes non-protocol errors transient
+ s.bytes_sent = 0; // rewind our buffer in case we try multiple MXes
+
+ curl = curl_easy_init();
+ if (curl) {
+
+ if (!IsEmptyStr(envelope_from)) {
+ curl_easy_setopt(curl, CURLOPT_MAIL_FROM, envelope_from);
+ }
+ else {
+ curl_easy_setopt(curl, CURLOPT_MAIL_FROM, fromaddr);
+ }
+
+ recipients = curl_slist_append(recipients, recp);
+ curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);
+ curl_easy_setopt(curl, CURLOPT_READFUNCTION, upload_source);
+ curl_easy_setopt(curl, CURLOPT_READDATA, &s);
+ curl_easy_setopt(curl, CURLOPT_UPLOAD, 1); // tell libcurl we are uploading
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, 20L); // Time out after 20 seconds
+
+ strcpy(try_this_mx, "smtp://");
+ extract_token(&try_this_mx[7], mxes, i, '|', (sizeof try_this_mx - 7));
+ curl_easy_setopt(curl, CURLOPT_URL, try_this_mx);
+
+ syslog(LOG_DEBUG, "smtpclient: trying %s", try_this_mx); // send the message
+ res = curl_easy_perform(curl);
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
+ syslog(LOG_DEBUG, "smtpclient: libcurl returned %d (%s) , SMTP response %ld",
+ res, curl_easy_strerror(res), response_code
+ );
+
+ if ((res != CURLE_OK) && (response_code == 0)) { // check for errors
+ response_code = 421;
+ }
+
+ curl_slist_free_all(recipients);
+ curl_easy_cleanup(curl);
+ }
+ }
+
+ FreeStrBuf(&s.TheMessage);
+ if (fromaddr) free(fromaddr);
+ return((int)response_code);
+}
+
+
+/*
+ * Process one outbound message.
+ */
+void smtp_process_one_msg(long qmsgnum)
+{
+ struct CtdlMessage *msg = NULL;
+ char *instr = NULL;
+ int i;
+ int num_success = 0;
+ int num_fail = 0;
+ int num_delayed = 0;
+ long deletes[2];
+ int delete_this_queue = 0;
+
+ syslog(LOG_DEBUG, "smtpclient: smtp_process_one_msg(%ld)", qmsgnum);
+
+ msg = CtdlFetchMessage(qmsgnum, 1, 1);
+ if (msg == NULL) {
+ syslog(LOG_WARNING, "smtpclient: queue message %ld does not exist", qmsgnum);
+ return;
+ }
+
+ instr = msg->cm_fields[eMesageText];
+ msg->cm_fields[eMesageText] = NULL;
+ CM_Free(msg);
+
+ // if the queue message has any CRLF's convert them to LF's
+ char *crlf = NULL;
+ while (crlf = strstr(instr, "\r\n"), crlf != NULL) {
+ strcpy(crlf, crlf+1);
+ }
+
+ // Strip out the headers and we are now left with just the instructions.
+ char *soi = strstr(instr, "\n\n");
+ if (soi) {
+ strcpy(instr, soi+2);
+ }
+
+ long msgid = 0;
+ time_t submitted = time(NULL);
+ time_t attempted = 0;
+ char *bounceto = NULL;
+ char *envelope_from = NULL;
+
+ // Example queue instructions
+ //
+ // msgid|3978
+ // submitted|1489343934
+ // bounceto|IGnatius T Foobar@dev
+ // attempted|1489344257
+ // remote|unreachable@example.com|4|Timeout while connecting example.com [93.184.216.34]:25
+ // remote|unreachable@example.org|4|Timeout while connecting example.org [93.184.216.34]:25
+ // remote|unreachable@example.gov|0|1) A-lookup example.gov - Domain name not found; 2) AAAA-lookup example.gov - Domain name not found;
+
+ char cfgline[SIZ];
+ for (i=0; i<num_tokens(instr, '\n'); ++i) {
+ extract_token(cfgline, instr, i, '\n', sizeof cfgline);
+ if (!strncasecmp(cfgline, HKEY("msgid|"))) msgid = atol(&cfgline[6]);
+ if (!strncasecmp(cfgline, HKEY("submitted|"))) submitted = atol(&cfgline[10]);
+ if (!strncasecmp(cfgline, HKEY("attempted|"))) attempted = atol(&cfgline[10]);
+ if (!strncasecmp(cfgline, HKEY("bounceto|"))) bounceto = strdup(&cfgline[9]);
+ if (!strncasecmp(cfgline, HKEY("envelope_from|"))) envelope_from = strdup(&cfgline[14]);
+ }
+
+ int should_try_now = 1;
+ if (attempted < submitted) { // If no attempts have been made yet, try now
+ should_try_now = 1;
+ }
+ else if ((attempted - submitted) <= 14400) {
+ if ((time(NULL) - attempted) > 1800) { // First four hours, retry every 30 minutes
+ should_try_now = 1;
+ }
+ }
+ else {
+ if ((time(NULL) - attempted) > 14400) { // After that, retry once every 4 hours
+ should_try_now = 1;
+ }
+ }
+
+ if (should_try_now) {
+ syslog(LOG_DEBUG, "smtpclient: attempting delivery");
+
+ StrBuf *NewInstr = NewStrBuf();
+ StrBufAppendPrintf(NewInstr, "Content-type: "SPOOLMIME"\n\n");
+ StrBufAppendPrintf(NewInstr, "msgid|%ld\n", msgid);
+ StrBufAppendPrintf(NewInstr, "submitted|%ld\n", submitted);
+ if (bounceto) StrBufAppendPrintf(NewInstr, "bounceto|%s\n", bounceto);
+ if (envelope_from) StrBufAppendPrintf(NewInstr, "envelope_from|%s\n", envelope_from);
+
+ for (i=0; i<num_tokens(instr, '\n'); ++i) {
+ extract_token(cfgline, instr, i, '\n', sizeof cfgline);
+ if (!strncasecmp(cfgline, HKEY("remote|"))) {
+ char recp[SIZ];
+ int previous_result = extract_int(cfgline, 2);
+ if ((previous_result == 0) || (previous_result == 4)) {
+ int new_result = 421;
+ extract_token(recp, cfgline, 1, '|', sizeof recp);
+ new_result = smtp_attempt_delivery(msgid, recp, envelope_from);
+ syslog(LOG_DEBUG, "smtpclient: recp: <%s> , result: %d (%s)", recp, new_result, smtpstatus(new_result));
+ if ((new_result / 100) == 2) {
+ ++num_success;
+ }
+ else {
+ if ((new_result / 100) == 5) {
+ ++num_fail;
+ }
+ else {
+ ++num_delayed;
+ }
+ StrBufAppendPrintf(NewInstr, "remote|%s|%ld|%ld (%s)\n",
+ recp, (new_result / 100) , new_result, smtpstatus(new_result)
+ );
+ }
+ }
+ }
+ }
+
+ StrBufAppendPrintf(NewInstr, "attempted|%ld\n", time(NULL));
+
+ // All deliveries have now been attempted. Now determine the disposition of this queue entry.
+
+ time_t age = time(NULL) - submitted;
+ syslog(LOG_DEBUG, "smtpclient: submission age: %ldd%ldh%ldm%lds", (age/86400) , ((age%86400)/3600) , ((age%3600)/60) , (age%60));
+ syslog(LOG_DEBUG, "smtpclient: num_success=%d , num_fail=%d , num_delayed=%d", num_success, num_fail, num_delayed);
+
+ // If there are permanent fails on this attempt, deliver a bounce to the user.
+ // The 5XX fails will be recorded in the rewritten queue, but they will be removed before the next attempt.
+ if (num_fail > 0) {
+ smtp_do_bounce(ChrPtr(NewInstr), SDB_BOUNCE_FATALS);
+ }
+
+ // If all deliveries have either succeeded or failed, we are finished with this queue entry.
+ //
+ if (num_delayed == 0) {
+ delete_this_queue = 1;
+ }
+
+ // If it's been more than five days, give up and tell the sender we #failed
+ //
+ else if ((time(NULL) - submitted) > SMTP_DELIVER_FAIL) {
+ smtp_do_bounce(ChrPtr(NewInstr), SDB_BOUNCE_ALL);
+ delete_this_queue = 1;
+ }
+
+ // If it's been more than four hours but less than five days, warn the sender that I've Been Delayed
+ //
+ else if ( ((attempted - submitted) < SMTP_DELIVER_WARN) && ((time(NULL) - submitted) >= SMTP_DELIVER_WARN) ) {
+ smtp_do_bounce(ChrPtr(NewInstr), SDB_WARN);
+ }
+
+ if (delete_this_queue) {
+ syslog(LOG_DEBUG, "smtpclient: deleting this queue entry");
+ deletes[0] = qmsgnum;
+ deletes[1] = msgid;
+ CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, deletes, 2, "");
+ FreeStrBuf(&NewInstr); // We have to free NewInstr here, no longer needed
+ }
+ else {
+ // replace the old qmsg with the new one
+ syslog(LOG_DEBUG, "smtpclient: rewriting this queue entry");
+ msg = convert_internet_message_buf(&NewInstr); // This function will free NewInstr for us
+ CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, 0);
+ CM_Free(msg);
+ CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &qmsgnum, 1, "");
+ }
+ }
+ else {
+ syslog(LOG_DEBUG, "smtpclient: retry time not reached");
+ }
+
+ if (bounceto != NULL) free(bounceto);
+ if (envelope_from != NULL) free(envelope_from);
+ free(instr);
+
+}
+
+
+/*
+ * Callback for smtp_do_queue()
+ */
+void smtp_add_msg(long msgnum, void *userdata) {
+
+ if (smtpq == NULL) {
+ smtpq_count = 0;
+ smtpq_alloc = 100;
+ smtpq = malloc(smtpq_alloc * sizeof(long));
+ }
+
+ if (smtpq_alloc >= smtpq_count) {
+ smtpq_alloc += 100;
+ smtpq = realloc(smtpq, (smtpq_alloc * sizeof(long)));
+ }
+
+ smtpq[smtpq_count++] = msgnum;
+}
+
+
+/*
+ * Run through the queue sending out messages.
+ */
+void smtp_do_queue(void) {
+ int i = 0;
+
+ /*
+ * This is a simple concurrency check to make sure only one pop3client
+ * run is done at a time. We could do this with a mutex, but since we
+ * don't really require extremely fine granularity here, we'll do it
+ * with a static variable instead.
+ */
+ if (doing_smtpclient) return;
+ doing_smtpclient = 1;
+
+ syslog(LOG_DEBUG, "smtpclient: start queue run");
+ pthread_setspecific(MyConKey, (void *)&smtp_client_CC);
+
+ if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
+ syslog(LOG_WARNING, "Cannot find room <%s>", SMTP_SPOOLOUT_ROOM);
+ doing_smtpclient = 0;
+ return;
+ }
+
+ // Put the queue in memory so we can close the db cursor
+ CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_add_msg, NULL);
+
+ // We are ready to run through the queue now.
+ for (i=0; i<smtpq_count; ++i) {
+ smtp_process_one_msg(smtpq[i]);
+ }
+
+ smtpq_count = 0; // don't free it, we will use this memory on the next run
+ doing_smtpclient = 0;
+ syslog(LOG_DEBUG, "smtpclient: end queue run");
+}
+
+
+/*
+ * Module entry point
+ */
+CTDL_MODULE_INIT(smtpclient)
+{
+ if (!threading)
+ {
+ CtdlFillSystemContext(&smtp_client_CC, "SMTP_Send");
+ CtdlRegisterMessageHook(smtp_aftersave, EVT_AFTERSAVE);
+ CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER, PRIO_AGGR + 50);
+ smtp_init_spoolout();
+ }
+
+ /* return our module id for the log */
+ return "smtpclient";
+}
+++ /dev/null
-/*
- * This module is an SMTP and ESMTP implementation for the Citadel system.
- * It is compliant with all of the following:
- *
- * RFC 821 - Simple Mail Transfer Protocol
- * RFC 876 - Survey of SMTP Implementations
- * RFC 1047 - Duplicate messages and SMTP
- * RFC 1652 - 8 bit MIME
- * RFC 1869 - Extended Simple Mail Transfer Protocol
- * RFC 1870 - SMTP Service Extension for Message Size Declaration
- * RFC 2033 - Local Mail Transfer Protocol
- * RFC 2197 - SMTP Service Extension for Command Pipelining
- * RFC 2476 - Message Submission
- * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
- * RFC 2554 - SMTP Service Extension for Authentication
- * RFC 2821 - Simple Mail Transfer Protocol
- * RFC 2822 - Internet Message Format
- * RFC 2920 - SMTP Service Extension for Command Pipelining
- *
- * The VRFY and EXPN commands have been removed from this implementation
- * because nobody uses these commands anymore, except for spammers.
- *
- * Copyright (c) 1998-2012 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- *
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- *
- *
- *
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <termios.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <syslog.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "genstamp.h"
-#include "domain.h"
-#include "clientsocket.h"
-#include "locate_host.h"
-#include "citadel_dirs.h"
-
-#include "ctdl_module.h"
-
-#include "smtp_util.h"
-#include "event_client.h"
-#include "smtpqueue.h"
-#include "smtp_clienthandlers.h"
-
-ConstStr SMTPStates[] = {
- {HKEY("looking up mx - record")},
- {HKEY("evaluating what to do next")},
- {HKEY("looking up a - record")},
- {HKEY("looking up aaaa - record")},
- {HKEY("connecting remote")},
- {HKEY("smtp conversation ongoing")},
- {HKEY("smtp sending maildata")},
- {HKEY("smtp sending done")},
- {HKEY("smtp successfully finished")},
- {HKEY("failed one attempt")},
- {HKEY("failed temporarily")},
- {HKEY("failed permanently")}
-};
-
-void SetSMTPState(AsyncIO *IO, smtpstate State)
-{
- CitContext* CCC = IO->CitContext;
- if (CCC != NULL)
- memcpy(CCC->cs_clientname, SMTPStates[State].Key, SMTPStates[State].len + 1);
-}
-
-void DeleteSmtpOutMsg(void *v)
-{
- SmtpOutMsg *Msg = v;
- syslog(LOG_DEBUG, "%s Exit\n", __FUNCTION__);
-
- /* these are kept in our own space and free'd below */
- Msg->IO.ConnectMe = NULL;
-
- ares_free_data(Msg->AllMX);
- if (Msg->HostLookup.VParsedDNSReply != NULL)
- Msg->HostLookup.DNSReplyFree(Msg->HostLookup.VParsedDNSReply);
- FreeURL(&Msg->Relay);
- FreeStrBuf(&Msg->msgtext);
- FreeStrBuf(&Msg->MultiLineBuf);
- FreeAsyncIOContents(&Msg->IO);
- memset (Msg, 0, sizeof(SmtpOutMsg)); /* just to be shure... */
- free(Msg);
-}
-
-eNextState SMTP_C_Shutdown(AsyncIO *IO);
-eNextState SMTP_C_Timeout(AsyncIO *IO);
-eNextState SMTP_C_ConnFail(AsyncIO *IO);
-eNextState SMTP_C_DispatchReadDone(AsyncIO *IO);
-eNextState SMTP_C_DispatchWriteDone(AsyncIO *IO);
-eNextState SMTP_C_DNSFail(AsyncIO *IO);
-eNextState SMTP_C_Terminate(AsyncIO *IO);
-eNextState SMTP_C_TerminateDB(AsyncIO *IO);
-eReadState SMTP_C_ReadServerStatus(AsyncIO *IO);
-
-eNextState mx_connect_ip(AsyncIO *IO);
-eNextState get_one_mx_host_ip(AsyncIO *IO);
-
-/******************************************************************************
- * So, we're finished with sending (regardless of success or failure) *
- * This Message might be referenced by several Queue-Items, if we're the last,*
- * we need to free the memory and send bounce messages (on terminal failure) *
- * else we just free our SMTP-Message struct. *
- ******************************************************************************/
-eNextState FinalizeMessageSend_DB(AsyncIO *IO)
-{
- const char *Status;
- SmtpOutMsg *Msg = IO->Data;
- StrBuf *StatusMessage;
-
- if (Msg->MyQEntry->AllStatusMessages != NULL)
- StatusMessage = Msg->MyQEntry->AllStatusMessages;
- else
- StatusMessage = Msg->MyQEntry->StatusMessage;
-
-
- if (Msg->MyQEntry->Status == 2) {
- SetSMTPState(IO, eSTMPfinished);
- Status = "Delivery successful.";
- }
- else if (Msg->MyQEntry->Status == 5) {
- SetSMTPState(IO, eSMTPFailTotal);
- Status = "Delivery failed permanently; giving up.";
- }
- else {
- SetSMTPState(IO, eSMTPFailTemporary);
- Status = "Delivery failed temporarily; will retry later.";
- }
-
- syslog(LOG_INFO,
- "%s Time[%fs] Recipient <%s> @ <%s> (%s) Status message: %s\n",
- Status,
- Msg->IO.Now - Msg->IO.StartIO,
- Msg->user,
- Msg->node,
- Msg->name,
- ChrPtr(StatusMessage));
-
-
- Msg->IDestructQueItem = DecreaseQReference(Msg->MyQItem);
-
- Msg->nRemain = CountActiveQueueEntries(Msg->MyQItem, 0);
-
- if (Msg->MyQEntry->Active &&
- !Msg->MyQEntry->StillActive &&
- CheckQEntryIsBounce(Msg->MyQEntry))
- {
- /* are we casue for a bounce mail? */
- Msg->MyQItem->SendBounceMail |= (1<<Msg->MyQEntry->Status);
- }
-
- if ((Msg->nRemain > 0) || Msg->IDestructQueItem)
- Msg->QMsgData = SerializeQueueItem(Msg->MyQItem);
- else
- Msg->QMsgData = NULL;
-
- /*
- * Uncompleted delivery instructions remain, so delete the old
- * instructions and replace with the updated ones.
- */
- syslog(LOG_DEBUG, "%ld", Msg->MyQItem->QueMsgID);
- CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, "");
- Msg->MyQItem->QueMsgID = -1;
-
- if (Msg->IDestructQueItem)
- smtpq_do_bounce(Msg->MyQItem, Msg->msgtext, Msg->pCurrRelay);
-
- if (Msg->nRemain > 0)
- {
- struct CtdlMessage *msg;
- msg = malloc(sizeof(struct CtdlMessage));
- memset(msg, 0, sizeof(struct CtdlMessage));
- msg->cm_magic = CTDLMESSAGE_MAGIC;
- msg->cm_anon_type = MES_NORMAL;
- msg->cm_format_type = FMT_RFC822;
- CM_SetAsFieldSB(msg, eMesageText, &Msg->QMsgData);
- CM_SetField(msg, eMsgSubject, HKEY("QMSG"));
- Msg->MyQItem->QueMsgID =
- CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
- syslog(LOG_DEBUG, "%ld", Msg->MyQItem->QueMsgID);
- CM_Free(msg);
- }
- else {
- CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM,
- &Msg->MyQItem->MessageID,
- 1,
- "");
- FreeStrBuf(&Msg->QMsgData);
- }
-
- RemoveContext(Msg->IO.CitContext);
- return eAbort;
-}
-
-eNextState Terminate(AsyncIO *IO)
-{
- SmtpOutMsg *Msg = IO->Data;
-
- if (Msg->IDestructQueItem)
- RemoveQItem(Msg->MyQItem);
-
- DeleteSmtpOutMsg(Msg);
- return eAbort;
-}
-eNextState FinalizeMessageSend(SmtpOutMsg *Msg)
-{
- /* hand over to DB Queue */
- return EventQueueDBOperation(&Msg->IO, FinalizeMessageSend_DB, 0);
-}
-
-eNextState FailOneAttempt(AsyncIO *IO)
-{
- SmtpOutMsg *Msg = IO->Data;
-
- SetSMTPState(IO, eSTMPfailOne);
- if (Msg->MyQEntry->Status == 2)
- return eAbort;
-
- /*
- * possible ways here:
- * - connection timeout
- * - dns lookup failed
- */
- StopClientWatchers(IO, 1);
-
- Msg->MyQEntry->nAttempt ++;
- if (Msg->MyQEntry->AllStatusMessages == NULL)
- Msg->MyQEntry->AllStatusMessages = NewStrBuf();
-
- StrBufAppendPrintf(Msg->MyQEntry->AllStatusMessages, "%ld) ", Msg->MyQEntry->nAttempt);
- StrBufAppendBuf(Msg->MyQEntry->AllStatusMessages, Msg->MyQEntry->StatusMessage, 0);
- StrBufAppendBufPlain(Msg->MyQEntry->AllStatusMessages, HKEY("; "), 0);
-
- if (Msg->pCurrRelay != NULL)
- Msg->pCurrRelay = Msg->pCurrRelay->Next;
- if ((Msg->pCurrRelay != NULL) &&
- !Msg->pCurrRelay->IsRelay &&
- Msg->MyQItem->HaveRelay)
- {
- syslog(LOG_DEBUG, "%s Aborting; last relay failed.\n", __FUNCTION__);
- return FinalizeMessageSend(Msg);
- }
-
- if (Msg->pCurrRelay == NULL) {
- syslog(LOG_DEBUG, "%s Aborting\n", __FUNCTION__);
- return FinalizeMessageSend(Msg);
- }
- if (Msg->pCurrRelay->IsIP) {
- syslog(LOG_DEBUG, "%s connecting IP\n", __FUNCTION__);
- return mx_connect_ip(IO);
- }
- else {
- syslog(LOG_DEBUG,
- "%s resolving next MX Record\n",
- __FUNCTION__);
- return get_one_mx_host_ip(IO);
- }
-}
-
-
-void SetConnectStatus(AsyncIO *IO)
-{
- SmtpOutMsg *Msg = IO->Data;
- char buf[256];
- void *src;
-
- buf[0] = '\0';
-
- if (IO->ConnectMe->IPv6) {
- src = &IO->ConnectMe->Addr.sin6_addr;
- }
- else {
- struct sockaddr_in *addr;
-
- addr = (struct sockaddr_in *)&IO->ConnectMe->Addr;
- src = &addr->sin_addr.s_addr;
- }
-
- inet_ntop((IO->ConnectMe->IPv6)?AF_INET6:AF_INET,
- src,
- buf,
- sizeof(buf));
-
- if (Msg->mx_host == NULL)
- Msg->mx_host = "<no MX-Record>";
-
- syslog(LOG_INFO,
- "connecting to %s [%s]:%d ...\n",
- Msg->mx_host,
- buf,
- Msg->IO.ConnectMe->Port);
-
- Msg->MyQEntry->Status = 4;
- StrBufPrintf(Msg->MyQEntry->StatusMessage,
- "Timeout while connecting %s [%s]:%d ",
- Msg->mx_host,
- buf,
- Msg->IO.ConnectMe->Port);
- Msg->IO.NextState = eConnect;
-}
-
-/*****************************************************************************
- * So we connect our Relay IP here. *
- *****************************************************************************/
-eNextState mx_connect_ip(AsyncIO *IO)
-{
- SmtpOutMsg *Msg = IO->Data;
- SetSMTPState(IO, eSTMPconnecting);
-
- syslog(LOG_DEBUG, "%s(%s)\n", __FUNCTION__, (Msg->IsRelay)? "Relay":"Remote");
-
- IO->ConnectMe = Msg->pCurrRelay;
- Msg->State = eConnectMX;
-
- SetConnectStatus(IO);
-
- return EvConnectSock(IO,
- SMTP_C_ConnTimeout,
- SMTP_C_ReadTimeouts[0],
- 1);
-}
-
-eNextState get_one_mx_host_ip_done(AsyncIO *IO)
-{
- SmtpOutMsg *Msg = IO->Data;
- struct hostent *hostent;
-
- IO->ConnectMe = Msg->pCurrRelay;
-
- QueryCbDone(IO);
- syslog(LOG_DEBUG, "%s Time[%fs]\n",
- __FUNCTION__,
- IO->Now - IO->DNS.Start);
-
- hostent = Msg->HostLookup.VParsedDNSReply;
- if ((Msg->HostLookup.DNSStatus == ARES_SUCCESS) &&
- (hostent != NULL) ) {
- memset(&Msg->pCurrRelay->Addr, 0, sizeof(struct in6_addr));
- if (Msg->pCurrRelay->IPv6) {
- memcpy(&Msg->pCurrRelay->Addr.sin6_addr.s6_addr,
- &hostent->h_addr_list[0],
- sizeof(struct in6_addr));
-
- Msg->pCurrRelay->Addr.sin6_family =
- hostent->h_addrtype;
- Msg->pCurrRelay->Addr.sin6_port =
- htons(Msg->IO.ConnectMe->Port);
- }
- else {
- struct sockaddr_in *addr;
- /*
- * Bypass the ns lookup result like this:
- * IO->Addr.sin_addr.s_addr = inet_addr("127.0.0.1");
- * addr->sin_addr.s_addr =
- * htonl((uint32_t)&hostent->h_addr_list[0]);
- */
-
- addr = (struct sockaddr_in*) &Msg->pCurrRelay->Addr;
-
- memcpy(&addr->sin_addr.s_addr,
- hostent->h_addr_list[0],
- sizeof(uint32_t));
-
- addr->sin_family = hostent->h_addrtype;
- addr->sin_port = htons(Msg->IO.ConnectMe->Port);
- }
- Msg->mx_host = Msg->pCurrRelay->Host;
- if (Msg->HostLookup.VParsedDNSReply != NULL) {
- Msg->HostLookup.DNSReplyFree(Msg->HostLookup.VParsedDNSReply);
- Msg->HostLookup.VParsedDNSReply = NULL;
- }
- return mx_connect_ip(IO);
- }
- else {
- SetSMTPState(IO, eSTMPfailOne);
- if (Msg->HostLookup.VParsedDNSReply != NULL) {
- Msg->HostLookup.DNSReplyFree(Msg->HostLookup.VParsedDNSReply);
- Msg->HostLookup.VParsedDNSReply = NULL;
- }
- return FailOneAttempt(IO);
- }
-}
-
-eNextState get_one_mx_host_ip(AsyncIO *IO)
-{
- SmtpOutMsg * Msg = IO->Data;
- /*
- * here we start with the lookup of one host. it might be...
- * - the relay host *sigh*
- * - the direct hostname if there was no mx record
- * - one of the mx'es
- */
- SetSMTPState(IO, (Msg->pCurrRelay->IPv6)?eSTMPalookup:eSTMPaaaalookup);
-
- syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
-
- syslog(LOG_DEBUG,
- "looking up %s-Record %s : %d ...\n",
- (Msg->pCurrRelay->IPv6)? "aaaa": "a",
- Msg->pCurrRelay->Host,
- Msg->pCurrRelay->Port);
-
- if (!QueueQuery((Msg->pCurrRelay->IPv6)? ns_t_aaaa : ns_t_a,
- Msg->pCurrRelay->Host,
- &Msg->IO,
- &Msg->HostLookup,
- get_one_mx_host_ip_done))
- {
- Msg->MyQEntry->Status = 5;
- StrBufPrintf(Msg->MyQEntry->StatusMessage,
- "No MX hosts found for <%s>", Msg->node);
- Msg->IO.NextState = eTerminateConnection;
- return IO->NextState;
- }
- IO->NextState = eReadDNSReply;
- return IO->NextState;
-}
-
-
-/*****************************************************************************
- * here we try to find out about the MX records for our recipients. *
- *****************************************************************************/
-eNextState smtp_resolve_mx_record_done(AsyncIO *IO)
-{
- SmtpOutMsg * Msg = IO->Data;
- ParsedURL **pp;
-
- QueryCbDone(IO);
-
- syslog(LOG_DEBUG, "%s Time[%fs]\n",
- __FUNCTION__,
- IO->Now - IO->DNS.Start);
-
- pp = &Msg->Relay;
- while ((pp != NULL) && (*pp != NULL) && ((*pp)->Next != NULL))
- pp = &(*pp)->Next;
-
- if ((IO->DNS.Query->DNSStatus == ARES_SUCCESS) &&
- (IO->DNS.Query->VParsedDNSReply != NULL))
- { /* ok, we found mx records. */
-
- Msg->CurrMX
- = Msg->AllMX
- = IO->DNS.Query->VParsedDNSReply;
- while (Msg->CurrMX) {
- int i;
- for (i = 0; i < 2; i++) {
- ParsedURL *p;
-
- p = (ParsedURL*) malloc(sizeof(ParsedURL));
- memset(p, 0, sizeof(ParsedURL));
- p->Priority = Msg->CurrMX->priority;
- p->IsIP = 0;
- p->Port = DefaultMXPort;
- p->IPv6 = i == 1;
- p->Host = Msg->CurrMX->host;
- if (*pp == NULL)
- *pp = p;
- else {
- ParsedURL *ppp = *pp;
-
- while ((ppp->Next != NULL) &&
- (ppp->Next->Priority <= p->Priority))
- ppp = ppp->Next;
- if ((ppp == *pp) &&
- (ppp->Priority > p->Priority)) {
- p->Next = *pp;
- *pp = p;
- }
- else {
- p->Next = ppp->Next;
- ppp->Next = p;
- }
- }
- }
- Msg->CurrMX = Msg->CurrMX->next;
- }
- Msg->CXFlags = Msg->CXFlags & F_HAVE_MX;
- }
- else { /* else fall back to the plain hostname */
- int i;
- for (i = 0; i < 2; i++) {
- ParsedURL *p;
-
- p = (ParsedURL*) malloc(sizeof(ParsedURL));
- memset(p, 0, sizeof(ParsedURL));
- p->IsIP = 0;
- p->Port = DefaultMXPort;
- p->IPv6 = i == 1;
- p->Host = Msg->node;
-
- *pp = p;
- pp = &p->Next;
- }
- Msg->CXFlags = Msg->CXFlags & F_DIRECT;
- }
- if (Msg->MyQItem->FallBackHost != NULL)
- {
- Msg->MyQItem->FallBackHost->Next = *pp;
- *pp = Msg->MyQItem->FallBackHost;
- }
- Msg->pCurrRelay = Msg->Relay;
- return get_one_mx_host_ip(IO);
-}
-
-eNextState resolve_mx_records(AsyncIO *IO)
-{
- SmtpOutMsg * Msg = IO->Data;
-
- SetSMTPState(IO, eSTMPmxlookup);
-
- syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
- /* start resolving MX records here. */
- if (!QueueQuery(ns_t_mx,
- Msg->node,
- &Msg->IO,
- &Msg->MxLookup,
- smtp_resolve_mx_record_done))
- {
- Msg->MyQEntry->Status = 5;
- StrBufPrintf(Msg->MyQEntry->StatusMessage,
- "No MX hosts found for <%s>", Msg->node);
- return IO->NextState;
- }
- Msg->IO.NextState = eReadDNSReply;
- return IO->NextState;
-}
-
-
-
-/******************************************************************************
- * so, we're going to start a SMTP delivery. lets get it on. *
- ******************************************************************************/
-
-SmtpOutMsg *new_smtp_outmsg(OneQueItem *MyQItem,
- MailQEntry *MyQEntry,
- int MsgCount)
-{
- SmtpOutMsg * Msg;
-
- Msg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
- if (Msg == NULL)
- return NULL;
- memset(Msg, 0, sizeof(SmtpOutMsg));
-
- Msg->n = MsgCount;
- Msg->MyQEntry = MyQEntry;
- Msg->MyQItem = MyQItem;
- Msg->pCurrRelay = MyQItem->URL;
-
- InitIOStruct(&Msg->IO,
- Msg,
- eReadMessage,
- SMTP_C_ReadServerStatus,
- SMTP_C_DNSFail,
- SMTP_C_DispatchWriteDone,
- SMTP_C_DispatchReadDone,
- SMTP_C_Terminate,
- SMTP_C_TerminateDB,
- SMTP_C_ConnFail,
- SMTP_C_Timeout,
- SMTP_C_Shutdown);
-
- Msg->IO.ErrMsg = Msg->MyQEntry->StatusMessage;
-
- return Msg;
-}
-
-void smtp_try_one_queue_entry(OneQueItem *MyQItem,
- MailQEntry *MyQEntry,
- StrBuf *MsgText,
- /*KeepMsgText allows us to use MsgText as ours.*/
- int KeepMsgText,
- int MsgCount)
-{
- SmtpOutMsg *Msg;
-
- syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
-
- Msg = new_smtp_outmsg(MyQItem, MyQEntry, MsgCount);
- if (Msg == NULL) {
- syslog(LOG_DEBUG, "%s Failed to alocate message context.\n", __FUNCTION__);
- if (KeepMsgText)
- FreeStrBuf (&MsgText);
- return;
- }
- if (KeepMsgText) Msg->msgtext = MsgText;
- else Msg->msgtext = NewStrBufDup(MsgText);
-
- if (smtp_resolve_recipients(Msg) &&
- (!MyQItem->HaveRelay ||
- (MyQItem->URL != NULL)))
- {
- safestrncpy(
- ((CitContext *)Msg->IO.CitContext)->cs_host,
- Msg->node,
- sizeof(((CitContext *)
- Msg->IO.CitContext)->cs_host));
-
- syslog(LOG_DEBUG, "Starting: [%ld] <%s> CC <%d> \n",
- Msg->MyQItem->MessageID,
- ChrPtr(Msg->MyQEntry->Recipient),
- ((CitContext*)Msg->IO.CitContext)->cs_pid);
- if (Msg->pCurrRelay == NULL) {
- SetSMTPState(&Msg->IO, eSTMPmxlookup);
- QueueEventContext(&Msg->IO,
- resolve_mx_records);
- }
- else { /* oh... via relay host */
- Msg->IsRelay = 1;
- if (Msg->pCurrRelay->IsIP) {
- SetSMTPState(&Msg->IO, eSTMPconnecting);
- QueueEventContext(&Msg->IO,
- mx_connect_ip);
- }
- else {
- SetSMTPState(&Msg->IO, eSTMPalookup);
- /* uneducated admin has chosen to
- add DNS to the equation... */
- QueueEventContext(&Msg->IO,
- get_one_mx_host_ip);
- }
- }
- }
- else {
- SetSMTPState(&Msg->IO, eSMTPFailTotal);
- /* No recipients? well fail then. */
- if (Msg->MyQEntry != NULL) {
- Msg->MyQEntry->Status = 5;
- if (StrLength(Msg->MyQEntry->StatusMessage) == 0)
- StrBufPlain(Msg->MyQEntry->StatusMessage,
- HKEY("Invalid Recipient!"));
- }
- FinalizeMessageSend_DB(&Msg->IO);
- DeleteSmtpOutMsg(Msg);
- }
-}
-
-
-
-
-
-
-/*****************************************************************************/
-/* SMTP CLIENT DISPATCHER */
-/*****************************************************************************/
-
-void SMTPSetTimeout(eNextState NextTCPState, SmtpOutMsg *Msg)
-{
- double Timeout = 0.0;
-
- syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
-
- switch (NextTCPState) {
- case eSendFile:
- case eSendReply:
- case eSendMore:
- Timeout = SMTP_C_SendTimeouts[Msg->State];
- if (Msg->State == eDATABody) {
- /* if we're sending a huge message,
- * we need more time.
- */
- Timeout += StrLength(Msg->msgtext) / 512;
- }
- break;
- case eReadMessage:
- Timeout = SMTP_C_ReadTimeouts[Msg->State];
- if (Msg->State == eDATATerminateBody) {
- /*
- * some mailservers take a nap before accepting
- * the message content inspection and such.
- */
- Timeout += StrLength(Msg->msgtext) / 512;
- }
- break;
- case eSendDNSQuery:
- case eReadDNSReply:
- case eDBQuery:
- case eReadFile:
- case eReadMore:
- case eReadPayload:
- case eConnect:
- case eTerminateConnection:
- case eAbort:
- return;
- }
- SetNextTimeout(&Msg->IO, Timeout);
-}
-eNextState SMTP_C_DispatchReadDone(AsyncIO *IO)
-{
- syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
- SmtpOutMsg *Msg = IO->Data;
- eNextState rc;
-
- rc = ReadHandlers[Msg->State](Msg);
- if (rc != eAbort)
- {
- Msg->State++;
- SMTPSetTimeout(rc, Msg);
- }
- return rc;
-}
-eNextState SMTP_C_DispatchWriteDone(AsyncIO *IO)
-{
- syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
- SmtpOutMsg *Msg = IO->Data;
- eNextState rc;
-
- rc = SendHandlers[Msg->State](Msg);
- SMTPSetTimeout(rc, Msg);
- return rc;
-}
-
-
-/*****************************************************************************/
-/* SMTP CLIENT ERROR CATCHERS */
-/*****************************************************************************/
-eNextState SMTP_C_Terminate(AsyncIO *IO)
-{
- SmtpOutMsg *Msg = IO->Data;
-
- syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
- return FinalizeMessageSend(Msg);
-}
-eNextState SMTP_C_TerminateDB(AsyncIO *IO)
-{
- syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
- return Terminate(IO);
-}
-eNextState SMTP_C_Timeout(AsyncIO *IO)
-{
- SmtpOutMsg *Msg = IO->Data;
-
- Msg->MyQEntry->Status = 4;
- syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
- StrBufPrintf(IO->ErrMsg, "Timeout: %s while talking to %s",
- ReadErrors[Msg->State].Key,
- Msg->mx_host);
- if (Msg->State > eRCPT)
- return eAbort;
- else
- return FailOneAttempt(IO);
-}
-eNextState SMTP_C_ConnFail(AsyncIO *IO)
-{
- SmtpOutMsg *Msg = IO->Data;
-
- Msg->MyQEntry->Status = 4;
- syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
- StrBufPrintf(IO->ErrMsg, "Connection failure: %s while talking to %s",
- ReadErrors[Msg->State].Key,
- Msg->mx_host);
-
- return FailOneAttempt(IO);
-}
-eNextState SMTP_C_DNSFail(AsyncIO *IO)
-{
- SmtpOutMsg *Msg = IO->Data;
- Msg->MyQEntry->Status = 4;
- syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
- return FailOneAttempt(IO);
-}
-eNextState SMTP_C_Shutdown(AsyncIO *IO)
-{
- syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
- SmtpOutMsg *Msg = IO->Data;
-
- switch (IO->NextState) {
- case eSendDNSQuery:
- case eReadDNSReply:
-
- /* todo: abort c-ares */
- case eConnect:
- case eSendReply:
- case eSendMore:
- case eSendFile:
- case eReadMessage:
- case eReadMore:
- case eReadPayload:
- case eReadFile:
- StopClientWatchers(IO, 1);
- break;
- case eDBQuery:
-
- break;
- case eTerminateConnection:
- case eAbort:
- break;
- }
- Msg->MyQEntry->Status = 3;
- StrBufPlain(Msg->MyQEntry->StatusMessage,
- HKEY("server shutdown during message submit."));
- return FinalizeMessageSend(Msg);
-}
-
-
-/**
- * @brief lineread Handler;
- * understands when to read more SMTP lines, and when this is a one-lined reply.
- */
-eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
-{
- eReadState Finished = eBufferNotEmpty;
-
- while (Finished == eBufferNotEmpty) {
- Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
-
- switch (Finished) {
- case eMustReadMore: /// read new from socket...
- return Finished;
- break;
- case eBufferNotEmpty: /* shouldn't happen... */
- case eReadSuccess: /// done for now...
- if (StrLength(IO->IOBuf) < 4)
- continue;
- if (ChrPtr(IO->IOBuf)[3] == '-')
- {
- SmtpOutMsg *Msg;
- Msg = (SmtpOutMsg *)IO->Data;
- if (Msg->MultiLineBuf == NULL)
- Msg->MultiLineBuf = NewStrBuf ();
- else
- StrBufAppendBufPlain(Msg->MultiLineBuf, HKEY("\n"), 0);
- StrBufAppendBuf(Msg->MultiLineBuf, IO->IOBuf, 0);
- Finished = eBufferNotEmpty;
- }
- else
- return Finished;
- break;
- case eReadFail: /// WHUT?
- ///todo: shut down!
- break;
- }
- }
- return Finished;
-}
-
-CTDL_MODULE_INIT(smtp_eventclient)
-{
- if (!threading) {
- // nothing here
- }
- return "smtpeventclient";
-}
+++ /dev/null
-/*
- * This module is an SMTP and ESMTP implementation for the Citadel system.
- * It is compliant with all of the following:
- *
- * RFC 821 - Simple Mail Transfer Protocol
- * RFC 876 - Survey of SMTP Implementations
- * RFC 1047 - Duplicate messages and SMTP
- * RFC 1652 - 8 bit MIME
- * RFC 1869 - Extended Simple Mail Transfer Protocol
- * RFC 1870 - SMTP Service Extension for Message Size Declaration
- * RFC 2033 - Local Mail Transfer Protocol
- * RFC 2197 - SMTP Service Extension for Command Pipelining
- * RFC 2476 - Message Submission
- * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
- * RFC 2554 - SMTP Service Extension for Authentication
- * RFC 2821 - Simple Mail Transfer Protocol
- * RFC 2822 - Internet Message Format
- * RFC 2920 - SMTP Service Extension for Command Pipelining
- *
- * The VRFY and EXPN commands have been removed from this implementation
- * because nobody uses these commands anymore, except for spammers.
- *
- * Copyright (c) 1998-2015 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <termios.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <syslog.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "genstamp.h"
-#include "domain.h"
-#include "clientsocket.h"
-#include "locate_host.h"
-#include "citadel_dirs.h"
-
-#include "ctdl_module.h"
-
-#include "smtpqueue.h"
-#include "smtp_clienthandlers.h"
-#include "event_client.h"
-
-
-struct CitContext smtp_queue_CC;
-pthread_mutex_t ActiveQItemsLock;
-HashList *ActiveQItems = NULL;
-HashList *QItemHandlers = NULL;
-const unsigned short DefaultMXPort = 25;
-int max_sessions_for_outbound_smtp = 500; /* how many sessions might be active till we stop adding more smtp jobs */
-int ndelay_count = 50; /* every n queued messages we will sleep... */
-int delay_msec = 5000; /* this many seconds. */
-
-static const long MaxRetry = SMTP_RETRY_INTERVAL * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2;
-int MsgCount = 0;
-int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
-
-void RegisterQItemHandler(const char *Key, long Len, QItemHandler H)
-{
- QItemHandlerStruct *HS = (QItemHandlerStruct*)malloc(sizeof(QItemHandlerStruct));
- HS->H = H;
- Put(QItemHandlers, Key, Len, HS, NULL);
-}
-
-
-
-void smtp_try_one_queue_entry(OneQueItem *MyQItem,
- MailQEntry *MyQEntry,
- StrBuf *MsgText,
-/* KeepMsgText allows us to use MsgText as ours. */
- int KeepMsgText,
- int MsgCount,
- ParsedURL *RelayUrls);
-
-
-void smtp_evq_cleanup(void)
-{
-
- pthread_mutex_lock(&ActiveQItemsLock);
- DeleteHash(&QItemHandlers);
- DeleteHash(&ActiveQItems);
- pthread_mutex_unlock(&ActiveQItemsLock);
- pthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
-/* citthread_mutex_destroy(&ActiveQItemsLock); TODO */
-}
-
-int DecreaseQReference(OneQueItem *MyQItem)
-{
- int IDestructQueItem;
-
- pthread_mutex_lock(&ActiveQItemsLock);
- MyQItem->ActiveDeliveries--;
- IDestructQueItem = MyQItem->ActiveDeliveries == 0;
- pthread_mutex_unlock(&ActiveQItemsLock);
- return IDestructQueItem;
-}
-
-void DecreaseShutdownDeliveries(OneQueItem *MyQItem)
-{
- pthread_mutex_lock(&ActiveQItemsLock);
- MyQItem->NotYetShutdownDeliveries--;
- pthread_mutex_unlock(&ActiveQItemsLock);
-}
-
-int GetShutdownDeliveries(OneQueItem *MyQItem)
-{
- int DestructNow;
-
- pthread_mutex_lock(&ActiveQItemsLock);
- DestructNow = MyQItem->ActiveDeliveries == 0;
- pthread_mutex_unlock(&ActiveQItemsLock);
- return DestructNow;
-}
-void RemoveQItem(OneQueItem *MyQItem)
-{
- long len;
- const char* Key;
- void *VData;
- HashPos *It;
-
- pthread_mutex_lock(&ActiveQItemsLock);
- It = GetNewHashPos(ActiveQItems, 0);
- if (GetHashPosFromKey(ActiveQItems, LKEY(MyQItem->MessageID), It))
- DeleteEntryFromHash(ActiveQItems, It);
- else
- {
- syslog(LOG_WARNING,
- "unable to find QItem with ID[%ld]",
- MyQItem->MessageID);
- while (GetNextHashPos(ActiveQItems, It, &len, &Key, &VData))
- syslog(LOG_WARNING,
- "have_: ID[%ld]",
- ((OneQueItem *)VData)->MessageID);
- }
- pthread_mutex_unlock(&ActiveQItemsLock);
- DeleteHashPos(&It);
-}
-
-
-void FreeMailQEntry(void *qv)
-{
- MailQEntry *Q = qv;
-/*
- syslog(LOG_DEBUG, "---------------%s--------------", __FUNCTION__);
- cit_backtrace();
-*/
- FreeStrBuf(&Q->Recipient);
- FreeStrBuf(&Q->StatusMessage);
- FreeStrBuf(&Q->AllStatusMessages);
- memset(Q, 0, sizeof(MailQEntry));
- free(Q);
-}
-void FreeQueItem(OneQueItem **Item)
-{
-/*
- syslog(LOG_DEBUG, "---------------%s--------------", __FUNCTION__);
- cit_backtrace();
-*/
- DeleteHash(&(*Item)->MailQEntries);
- FreeStrBuf(&(*Item)->EnvelopeFrom);
- FreeStrBuf(&(*Item)->BounceTo);
- FreeStrBuf(&(*Item)->SenderRoom);
- FreeURL(&(*Item)->URL);
- memset(*Item, 0, sizeof(OneQueItem));
- free(*Item);
- Item = NULL;
-}
-void HFreeQueItem(void *Item)
-{
- FreeQueItem((OneQueItem**)&Item);
-}
-
-/* inspect recipients with a status of:
- * - 0 (no delivery yet attempted)
- * - 3/4 (transient errors
- * were experienced and it's time to try again)
- */
-int CheckQEntryActive(MailQEntry *ThisItem)
-{
- if ((ThisItem->Status == 0) ||
- (ThisItem->Status == 3) ||
- (ThisItem->Status == 4))
- {
- return 1;
- }
- else
- return 0;
-}
-int CheckQEntryIsBounce(MailQEntry *ThisItem)
-{
- if ((ThisItem->Status == 3) ||
- (ThisItem->Status == 4) ||
- (ThisItem->Status == 5))
- {
- return 1;
- }
- else
- return 0;
-}
-
-int CountActiveQueueEntries(OneQueItem *MyQItem, int before)
-{
- HashPos *It;
- long len;
- long ActiveDeliveries;
- const char *Key;
- void *vQE;
-
- ActiveDeliveries = 0;
- It = GetNewHashPos(MyQItem->MailQEntries, 0);
- while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
- {
- int Active;
- MailQEntry *ThisItem = vQE;
-
- if (CheckQEntryActive(ThisItem))
- {
- ActiveDeliveries++;
- Active = 1;
- }
- else
- Active = 0;
- if (before)
- ThisItem->Active = Active;
- else
- ThisItem->StillActive = Active;
- }
- DeleteHashPos(&It);
- return ActiveDeliveries;
-}
-
-OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID)
-{
- OneQueItem *Item;
- const char *pLine = NULL;
- StrBuf *Line;
- StrBuf *Token;
- void *v;
-
- Item = (OneQueItem*)malloc(sizeof(OneQueItem));
- memset(Item, 0, sizeof(OneQueItem));
- Item->Retry = SMTP_RETRY_INTERVAL;
- Item->MessageID = -1;
- Item->QueMsgID = QueMsgID;
-
- Token = NewStrBuf();
- Line = NewStrBufPlain(NULL, 128);
- while (pLine != StrBufNOTNULL) {
- const char *pItemPart = NULL;
- void *vHandler;
-
- StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n');
- if (StrLength(Line) == 0) continue;
- StrBufExtract_NextToken(Token, Line, &pItemPart, '|');
- if (GetHash(QItemHandlers, SKEY(Token), &vHandler))
- {
- QItemHandlerStruct *HS;
- HS = (QItemHandlerStruct*) vHandler;
- HS->H(Item, Line, &pItemPart);
- }
- }
- FreeStrBuf(&Line);
- FreeStrBuf(&Token);
-
- if (Item->Retry >= MaxRetry)
- Item->FailNow = 1;
-
- pthread_mutex_lock(&ActiveQItemsLock);
- if (GetHash(ActiveQItems,
- LKEY(Item->MessageID),
- &v))
- {
- /* WHOOPS. somebody else is already working on this. */
- pthread_mutex_unlock(&ActiveQItemsLock);
- FreeQueItem(&Item);
- return NULL;
- }
- else {
- /* mark our claim on this. */
- Put(ActiveQItems,
- LKEY(Item->MessageID),
- Item,
- HFreeQueItem);
- pthread_mutex_unlock(&ActiveQItemsLock);
- }
-
- return Item;
-}
-
-StrBuf *SerializeQueueItem(OneQueItem *MyQItem)
-{
- StrBuf *QMessage;
- HashPos *It;
- const char *Key;
- long len;
- void *vQE;
-
- QMessage = NewStrBufPlain(NULL, SIZ);
- StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME);
-// "attempted|%ld\n" "retry|%ld\n",, (long)time(NULL), (long)retry );
- StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0);
- StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID);
-
- StrBufAppendBufPlain(QMessage, HKEY("\nsubmitted|"), 0);
- StrBufAppendPrintf(QMessage, "%ld", MyQItem->Submitted);
-
- if (StrLength(MyQItem->BounceTo) > 0) {
- StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0);
- StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0);
- }
-
- if (StrLength(MyQItem->EnvelopeFrom) > 0) {
- StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0);
- StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0);
- }
-
- if (StrLength(MyQItem->SenderRoom) > 0) {
- StrBufAppendBufPlain(QMessage, HKEY("\nsource_room|"), 0);
- StrBufAppendBuf(QMessage, MyQItem->SenderRoom, 0);
- }
-
- StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0);
- StrBufAppendPrintf(QMessage, "%ld",
- MyQItem->Retry);
-
- StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0);
- StrBufAppendPrintf(QMessage, "%ld",
- time(NULL) /*ctdl_ev_now()*/ + MyQItem->Retry);
-
- It = GetNewHashPos(MyQItem->MailQEntries, 0);
- while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
- {
- MailQEntry *ThisItem = vQE;
-
- StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0);
- StrBufAppendBuf(QMessage, ThisItem->Recipient, 0);
- StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
- StrBufAppendPrintf(QMessage, "%d", ThisItem->Status);
- StrBufAppendBufPlain(QMessage, HKEY("|"), 0);
- if (ThisItem->AllStatusMessages != NULL)
- StrBufAppendBuf(QMessage, ThisItem->AllStatusMessages, 0);
- else
- StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0);
- }
- DeleteHashPos(&It);
- StrBufAppendBufPlain(QMessage, HKEY("\n"), 0);
- return QMessage;
-}
-
-
-
-
-
-void NewMailQEntry(OneQueItem *Item)
-{
- Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry));
- memset(Item->Current, 0, sizeof(MailQEntry));
-
- if (Item->MailQEntries == NULL)
- Item->MailQEntries = NewHash(1, Flathash);
- /* alocate big buffer so we won't get problems reallocating later. */
- Item->Current->StatusMessage = NewStrBufPlain(NULL, SIZ);
- Item->Current->n = GetCount(Item->MailQEntries);
- Put(Item->MailQEntries,
- IKEY(Item->Current->n),
- Item->Current,
- FreeMailQEntry);
-}
-
-void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos)
-{
- Item->MessageID = StrBufExtractNext_long(Line, Pos, '|');
-}
-
-void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos)
-{
- if (Item->EnvelopeFrom == NULL)
- Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line));
- StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|');
-}
-
-void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos)
-{
- if (Item->BounceTo == NULL)
- Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line));
- StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|');
-}
-
-void QItem_Handle_SenderRoom(OneQueItem *Item, StrBuf *Line, const char **Pos)
-{
- if (Item->SenderRoom == NULL)
- Item->SenderRoom = NewStrBufPlain(NULL, StrLength(Line));
- StrBufExtract_NextToken(Item->SenderRoom, Line, Pos, '|');
-}
-
-void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos)
-{
- if (Item->Current == NULL)
- NewMailQEntry(Item);
- if (Item->Current->Recipient == NULL)
- Item->Current->Recipient=NewStrBufPlain(NULL, StrLength(Line));
- StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|');
- Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|');
- StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|');
- Item->Current = NULL; // TODO: is this always right?
-}
-
-
-void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos)
-{
- Item->Retry =
- StrBufExtractNext_int(Line, Pos, '|');
- if (Item->Retry == 0)
- Item->Retry = SMTP_RETRY_INTERVAL;
- else
- Item->Retry *= 2;
-}
-
-
-void QItem_Handle_Submitted(OneQueItem *Item, StrBuf *Line, const char **Pos)
-{
- Item->Submitted = atol(*Pos);
-
-}
-
-void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos)
-{
- Item->ReattemptWhen = StrBufExtractNext_int(Line, Pos, '|');
-}
-
-
-
-/**
- * this one has to have the context for loading the message via the redirect buffer...
- */
-StrBuf *smtp_load_msg(OneQueItem *MyQItem, int n, char **Author, char **Address)
-{
- CitContext *CCC=CC;
- StrBuf *SendMsg;
-
- CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
- CtdlOutputMsg(MyQItem->MessageID,
- MT_RFC822, HEADERS_ALL,
- 0, 1, NULL,
- (ESC_DOT|SUPPRESS_ENV_TO),
- Author,
- Address,
- NULL);
-
- SendMsg = CCC->redirect_buffer;
- CCC->redirect_buffer = NULL;
- if ((StrLength(SendMsg) > 0) &&
- ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') {
- syslog(LOG_WARNING,
- "[%d] Possible problem: message did not "
- "correctly terminate. (expecting 0x10, got 0x%02x)\n",
- MsgCount, //yes uncool, but best choice here...
- ChrPtr(SendMsg)[StrLength(SendMsg) - 1] );
- StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0);
- }
- return SendMsg;
-}
-
-
-
-/*
- * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
- * instructions for "5" codes (permanent fatal errors) and produce/deliver
- * a "bounce" message (delivery status notification).
- */
-void smtpq_do_bounce(OneQueItem *MyQItem, StrBuf *OMsgTxt, ParsedURL *Relay)
-{
- static int seq = 0;
-
- struct CtdlMessage *bmsg = NULL;
- StrBuf *boundary;
- StrBuf *Msg = NULL;
- StrBuf *BounceMB;
- recptypes *valid;
- time_t now;
-
- HashPos *It;
- void *vQE;
- long len;
- const char *Key;
-
- int first_attempt = 0;
- int successful_bounce = 0;
- int num_bounces = 0;
- int give_up = 0;
-
- syslog(LOG_DEBUG, "smtp_do_bounce() called\n");
-
- if (MyQItem->SendBounceMail == 0)
- return;
-
- now = time (NULL); //ev_time();
-
- if ( (now - MyQItem->Submitted) > SMTP_GIVE_UP ) {
- give_up = 1;
- }
-
- if (MyQItem->Retry == SMTP_RETRY_INTERVAL) {
- first_attempt = 1;
- }
-
- /*
- * Now go through the instructions checking for stuff.
- */
- Msg = NewStrBufPlain(NULL, 1024);
- It = GetNewHashPos(MyQItem->MailQEntries, 0);
- while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
- {
- MailQEntry *ThisItem = vQE;
- if ((ThisItem->Active && (ThisItem->Status == 5)) || /* failed now? */
- ((give_up == 1) && (ThisItem->Status != 2)) ||
- ((first_attempt == 1) && (ThisItem->Status != 2)))
- /* giving up after failed attempts... */
- {
- ++num_bounces;
-
- StrBufAppendBufPlain(Msg, HKEY(" "), 0);
- StrBufAppendBuf(Msg, ThisItem->Recipient, 0);
- StrBufAppendBufPlain(Msg, HKEY(": "), 0);
- if (ThisItem->AllStatusMessages != NULL)
- StrBufAppendBuf(Msg, ThisItem->AllStatusMessages, 0);
- else
- StrBufAppendBuf(Msg, ThisItem->StatusMessage, 0);
- StrBufAppendBufPlain(Msg, HKEY("\r\n"), 0);
- }
- }
- DeleteHashPos(&It);
-
- /* Deliver the bounce if there's anything worth mentioning */
- syslog(LOG_DEBUG, "num_bounces = %d\n", num_bounces);
-
- if (num_bounces == 0) {
- FreeStrBuf(&Msg);
- return;
- }
-
- if ((StrLength(MyQItem->SenderRoom) == 0) && MyQItem->HaveRelay) {
- const char *RelayUrlStr = "[not found]";
- /* one message that relaying is broken is enough; no extra room error message. */
- StrBuf *RelayDetails = NewStrBuf();
-
- if (Relay != NULL)
- RelayUrlStr = ChrPtr(Relay->URL);
-
- StrBufPrintf(RelayDetails,
- "Relaying via %s failed permanently. \n Reason:\n%s\n Revalidate your relay configuration.",
- RelayUrlStr,
- ChrPtr(Msg));
- CtdlAideMessage(ChrPtr(RelayDetails), "Relaying Failed");
- FreeStrBuf(&RelayDetails);
- }
-
- boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
- StrBufAppendPrintf(boundary,
- "%s_%04x%04x",
- CtdlGetConfigStr("c_fqdn"),
- getpid(),
- ++seq);
-
- /* Start building our bounce message; go shopping for memory first. */
- BounceMB = NewStrBufPlain(
- NULL,
- 1024 + /* mime stuff.... */
- StrLength(Msg) + /* the bounce information... */
- StrLength(OMsgTxt)); /* the original message */
- if (BounceMB == NULL) {
- FreeStrBuf(&boundary);
- syslog(LOG_ERR, "Failed to alloc() bounce message.\n");
-
- return;
- }
-
- bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
- if (bmsg == NULL) {
- FreeStrBuf(&boundary);
- FreeStrBuf(&BounceMB);
- syslog(LOG_ERR, "Failed to alloc() bounce message.\n");
-
- return;
- }
- memset(bmsg, 0, sizeof(struct CtdlMessage));
-
-
- StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
- StrBufAppendBuf(BounceMB, boundary, 0);
- StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
- StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
- StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
- StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
- StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
- StrBufAppendBuf(BounceMB, boundary, 0);
- StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
- StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
-
- if (give_up)
- StrBufAppendBufPlain(
- BounceMB,
- HKEY(
- "A message you sent could not be delivered "
- "to some or all of its recipients\n"
- "due to prolonged unavailability "
- "of its destination(s).\n"
- "Giving up on the following addresses:\n\n"
- ), 0);
- else
- StrBufAppendBufPlain(
- BounceMB,
- HKEY(
- "A message you sent could not be delivered "
- "to some or all of its recipients.\n"
- "The following addresses "
- "were undeliverable:\n\n"
- ), 0);
-
- StrBufAppendBuf(BounceMB, Msg, 0);
- FreeStrBuf(&Msg);
-
- if (StrLength(MyQItem->SenderRoom) > 0)
- {
- StrBufAppendBufPlain(
- BounceMB,
- HKEY("The message was originaly posted in: "), 0);
- StrBufAppendBuf(BounceMB, MyQItem->SenderRoom, 0);
- StrBufAppendBufPlain(
- BounceMB,
- HKEY("\n"), 0);
- }
-
- /* Attach the original message */
- StrBufAppendBufPlain(BounceMB, HKEY("\r\n--"), 0);
- StrBufAppendBuf(BounceMB, boundary, 0);
- StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
- StrBufAppendBufPlain(BounceMB,
- HKEY("Content-type: message/rfc822\r\n"), 0);
- StrBufAppendBufPlain(BounceMB,
- HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
- StrBufAppendBufPlain(BounceMB,
- HKEY("Content-Disposition: inline\r\n"), 0);
- StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
- StrBufAppendBuf(BounceMB, OMsgTxt, 0);
-
- /* Close the multipart MIME scope */
- StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
- StrBufAppendBuf(BounceMB, boundary, 0);
- StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
-
- bmsg->cm_magic = CTDLMESSAGE_MAGIC;
- bmsg->cm_anon_type = MES_NORMAL;
- bmsg->cm_format_type = FMT_RFC822;
-
- CM_SetField(bmsg, eOriginalRoom, HKEY(MAILROOM));
- CM_SetField(bmsg, eAuthor, HKEY("Citadel"));
- CM_SetField(bmsg, eNodeName, CtdlGetConfigStr("c_nodename"), strlen(CtdlGetConfigStr("c_nodename")));
- CM_SetField(bmsg, eMsgSubject, HKEY("Delivery Status Notification (Failure)"));
- CM_SetAsFieldSB(bmsg, eMesageText, &BounceMB);
-
- /* First try the user who sent the message */
- if (StrLength(MyQItem->BounceTo) == 0) {
- syslog(LOG_ERR, "No bounce address specified\n");
- }
- else {
- syslog(LOG_DEBUG, "bounce to user? <%s>\n",
- ChrPtr(MyQItem->BounceTo));
- }
-
- /* Can we deliver the bounce to the original sender? */
- valid = validate_recipients(ChrPtr(MyQItem->BounceTo), NULL, 0);
- if ((valid != NULL) && (valid->num_error == 0)) {
- CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
- successful_bounce = 1;
- }
-
- /* If not, post it in the Aide> room */
- if (successful_bounce == 0) {
- CtdlSubmitMsg(bmsg, NULL, CtdlGetConfigStr("c_aideroom"), QP_EADDR);
- }
-
- /* Free up the memory we used */
- free_recipients(valid);
- FreeStrBuf(&boundary);
- CM_Free(bmsg);
- syslog(LOG_DEBUG, "Done processing bounces\n");
-}
-
-ParsedURL *LoadRelayUrls(OneQueItem *MyQItem,
- char *Author,
- char *Address)
-{
- int nRelays = 0;
- ParsedURL *RelayUrls = NULL;
- char mxbuf[SIZ];
- ParsedURL **Url = &MyQItem->URL;
-
- nRelays = get_hosts(mxbuf, "fallbackhost");
- if (nRelays > 0) {
- StrBuf *All;
- StrBuf *One;
- const char *Pos = NULL;
- All = NewStrBufPlain(mxbuf, -1);
- One = NewStrBufPlain(NULL, StrLength(All) + 1);
-
- while ((Pos != StrBufNOTNULL) &&
- ((Pos == NULL) ||
- !IsEmptyStr(Pos)))
- {
- StrBufExtract_NextToken(One, All, &Pos, '|');
- if (!ParseURL(Url, One, DefaultMXPort)) {
- syslog(LOG_DEBUG,
- "Failed to parse: %s\n",
- ChrPtr(One));
- }
- else {
- (*Url)->IsRelay = 1;
- MyQItem->HaveRelay = 1;
- }
- }
- FreeStrBuf(&All);
- FreeStrBuf(&One);
- }
- nRelays = get_hosts(mxbuf, "smarthost");
- if (nRelays > 0) {
- char *User;
- StrBuf *All;
- StrBuf *One;
- const char *Pos = NULL;
- All = NewStrBufPlain(mxbuf, -1);
- One = NewStrBufPlain(NULL, StrLength(All) + 1);
-
- while ((Pos != StrBufNOTNULL) &&
- ((Pos == NULL) ||
- !IsEmptyStr(Pos)))
- {
- StrBufExtract_NextToken(One, All, &Pos, '|');
- User = strchr(ChrPtr(One), ' ');
- if (User != NULL) {
- if (!strcmp(User + 1, Author) ||
- !strcmp(User + 1, Address))
- StrBufCutAt(One, 0, User);
- else {
- MyQItem->HaveRelay = 1;
- continue;
- }
- }
- if (!ParseURL(Url, One, DefaultMXPort)) {
- syslog(LOG_DEBUG,
- "Failed to parse: %s\n",
- ChrPtr(One));
- }
- else {
- ///if (!Url->IsIP)) // todo dupe me fork ipv6
- (*Url)->IsRelay = 1;
- MyQItem->HaveRelay = 1;
- }
- }
- FreeStrBuf(&All);
- FreeStrBuf(&One);
- }
- return RelayUrls;
-}
-/*
- * smtp_do_procmsg()
- *
- * Called by smtp_do_queue() to handle an individual message.
- */
-void smtp_do_procmsg(long msgnum, void *userdata) {
- time_t now;
- int mynumsessions = num_sessions;
- struct CtdlMessage *msg = NULL;
- char *Author = NULL;
- char *Address = NULL;
- char *instr = NULL;
- StrBuf *PlainQItem;
- OneQueItem *MyQItem;
- char *pch;
- HashPos *It;
- void *vQE;
- long len;
- const char *Key;
- int HaveBuffers = 0;
- StrBuf *Msg =NULL;
-
- if (mynumsessions > max_sessions_for_outbound_smtp) {
- syslog(LOG_INFO,
- "skipping because of num jobs %d > %d max_sessions_for_outbound_smtp",
- mynumsessions,
- max_sessions_for_outbound_smtp);
- }
-
- syslog(LOG_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
- ///strcpy(envelope_from, "");
-
- msg = CtdlFetchMessage(msgnum, 1, 1);
- if (msg == NULL) {
- syslog(LOG_ERR, "tried %ld but no such message!\n",
- msgnum);
- return;
- }
-
- pch = instr = msg->cm_fields[eMesageText];
-
- /* Strip out the headers (no not amd any other non-instruction) line */
- while (pch != NULL) {
- pch = strchr(pch, '\n');
- if ((pch != NULL) &&
- ((*(pch + 1) == '\n') ||
- (*(pch + 1) == '\r')))
- {
- instr = pch + 2;
- pch = NULL;
- }
- }
- PlainQItem = NewStrBufPlain(instr, -1);
- CM_Free(msg);
- MyQItem = DeserializeQueueItem(PlainQItem, msgnum);
- FreeStrBuf(&PlainQItem);
-
- if (MyQItem == NULL) {
- syslog(LOG_ERR,
- "Msg No %ld: already in progress!\n",
- msgnum);
- return; /* s.b. else is already processing... */
- }
-
- /*
- * Postpone delivery if we've already tried recently.
- */
- now = time(NULL);
- if ((MyQItem->ReattemptWhen != 0) &&
- (now < MyQItem->ReattemptWhen) &&
- (run_queue_now == 0))
- {
- syslog(LOG_DEBUG,
- "Retry time not yet reached. %ld seconds left.",
- MyQItem->ReattemptWhen - now);
-
- It = GetNewHashPos(MyQItem->MailQEntries, 0);
- pthread_mutex_lock(&ActiveQItemsLock);
- {
- if (GetHashPosFromKey(ActiveQItems,
- LKEY(MyQItem->MessageID),
- It))
- {
- DeleteEntryFromHash(ActiveQItems, It);
- }
- }
- pthread_mutex_unlock(&ActiveQItemsLock);
- ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
- DeleteHashPos(&It);
- return;
- }
-
- /*
- * Bail out if there's no actual message associated with this
- */
- if (MyQItem->MessageID < 0L) {
- syslog(LOG_ERR, "no 'msgid' directive found!\n");
- It = GetNewHashPos(MyQItem->MailQEntries, 0);
- pthread_mutex_lock(&ActiveQItemsLock);
- {
- if (GetHashPosFromKey(ActiveQItems,
- LKEY(MyQItem->MessageID),
- It))
- {
- DeleteEntryFromHash(ActiveQItems, It);
- }
- }
- pthread_mutex_unlock(&ActiveQItemsLock);
- DeleteHashPos(&It);
- ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
- return;
- }
-
-
- It = GetNewHashPos(MyQItem->MailQEntries, 0);
- while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))
- {
- MailQEntry *ThisItem = vQE;
- syslog(LOG_DEBUG, "SMTP Queue: Task: <%s> %d\n",
- ChrPtr(ThisItem->Recipient),
- ThisItem->Active);
- }
- DeleteHashPos(&It);
-
- MyQItem->NotYetShutdownDeliveries =
- MyQItem->ActiveDeliveries = CountActiveQueueEntries(MyQItem, 1);
-
- /* failsafe against overload:
- * will we exceed the limit set?
- */
- if ((MyQItem->ActiveDeliveries + mynumsessions > max_sessions_for_outbound_smtp) &&
- /* if yes, did we reach more than half of the quota? */
- ((mynumsessions * 2) > max_sessions_for_outbound_smtp) &&
- /* if... would we ever fit into half of the quota?? */
- (((MyQItem->ActiveDeliveries * 2) < max_sessions_for_outbound_smtp)))
- {
- /* abort delivery for another time. */
- syslog(LOG_INFO,
- "SMTP Queue: skipping because of num jobs %d + %ld > %d max_sessions_for_outbound_smtp",
- mynumsessions,
- MyQItem->ActiveDeliveries,
- max_sessions_for_outbound_smtp);
-
- It = GetNewHashPos(MyQItem->MailQEntries, 0);
- pthread_mutex_lock(&ActiveQItemsLock);
- {
- if (GetHashPosFromKey(ActiveQItems,
- LKEY(MyQItem->MessageID),
- It))
- {
- DeleteEntryFromHash(ActiveQItems, It);
- }
- }
- pthread_mutex_unlock(&ActiveQItemsLock);
-
- return;
- }
-
-
- if (MyQItem->ActiveDeliveries > 0)
- {
- ParsedURL *RelayUrls = NULL;
- int nActivated = 0;
- int n = MsgCount++;
- int m = MyQItem->ActiveDeliveries;
- int i = 1;
-
- It = GetNewHashPos(MyQItem->MailQEntries, 0);
-
- Msg = smtp_load_msg(MyQItem, n, &Author, &Address);
- RelayUrls = LoadRelayUrls(MyQItem, Author, Address);
- if ((RelayUrls == NULL) && MyQItem->HaveRelay) {
-
- while ((i <= m) &&
- (GetNextHashPos(MyQItem->MailQEntries,
- It, &len, &Key, &vQE)))
- {
- int KeepBuffers = (i == m);
- MailQEntry *ThisItem = vQE;
- StrBufPrintf(ThisItem->StatusMessage,
- "No relay configured matching %s / %s",
- (Author != NULL)? Author : "",
- (Address != NULL)? Address : "");
- ThisItem->Status = 5;
-
- nActivated++;
-
- if (i > 1) n = MsgCount++;
- syslog(LOG_INFO,
- "SMTPC: giving up on <%ld> <%s> %d / %d \n",
- MyQItem->MessageID,
- ChrPtr(ThisItem->Recipient),
- i,
- m);
- (*((int*) userdata)) ++;
- smtp_try_one_queue_entry(MyQItem,
- ThisItem,
- Msg,
- KeepBuffers,
- n,
- RelayUrls);
-
- if (KeepBuffers) HaveBuffers++;
-
- i++;
- }
- if (Author != NULL) free (Author);
- if (Address != NULL) free (Address);
- DeleteHashPos(&It);
-
- return;
- }
- if (Author != NULL) free (Author);
- if (Address != NULL) free (Address);
-
- while ((i <= m) &&
- (GetNextHashPos(MyQItem->MailQEntries,
- It, &len, &Key, &vQE)))
- {
- MailQEntry *ThisItem = vQE;
-
- if (ThisItem->Active == 1)
- {
- int KeepBuffers = (i == m);
-
- nActivated++;
- if (nActivated % ndelay_count == 0)
- usleep(delay_msec);
-
- if (i > 1) n = MsgCount++;
- syslog(LOG_DEBUG,
- "SMTPC: Trying <%ld> <%s> %d / %d \n",
- MyQItem->MessageID,
- ChrPtr(ThisItem->Recipient),
- i,
- m);
- (*((int*) userdata)) ++;
- smtp_try_one_queue_entry(MyQItem,
- ThisItem,
- Msg,
- KeepBuffers,
- n,
- RelayUrls);
-
- if (KeepBuffers) HaveBuffers++;
-
- i++;
- }
- }
- DeleteHashPos(&It);
- }
- else
- {
- It = GetNewHashPos(MyQItem->MailQEntries, 0);
- pthread_mutex_lock(&ActiveQItemsLock);
- {
- if (GetHashPosFromKey(ActiveQItems,
- LKEY(MyQItem->MessageID),
- It))
- {
- DeleteEntryFromHash(ActiveQItems, It);
- }
- else
- {
- long len;
- const char* Key;
- void *VData;
-
- syslog(LOG_WARNING,
- "unable to find QItem with ID[%ld]",
- MyQItem->MessageID);
- while (GetNextHashPos(ActiveQItems,
- It,
- &len,
- &Key,
- &VData))
- {
- syslog(LOG_WARNING,
- "have: ID[%ld]",
- ((OneQueItem *)VData)->MessageID);
- }
- }
-
- }
- pthread_mutex_unlock(&ActiveQItemsLock);
- DeleteHashPos(&It);
- ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this?
-
-// TODO: bounce & delete?
-
- }
- if (!HaveBuffers) {
- FreeStrBuf (&Msg);
-// TODO : free RelayUrls
- }
-}
-
-
-
-/*
- * smtp_queue_thread()
- *
- * Run through the queue sending out messages.
- */
-void smtp_do_queue(void) {
- int num_processed = 0;
- int num_activated = 0;
-
- pthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
- syslog(LOG_DEBUG, "processing outbound queue");
-
- if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
- syslog(LOG_ERR, "Cannot find room <%s>", SMTP_SPOOLOUT_ROOM);
- }
- else {
- num_processed = CtdlForEachMessage(MSGS_ALL,
- 0L,
- NULL,
- SPOOLMIME,
- NULL,
- smtp_do_procmsg,
- &num_activated);
- }
- if (num_activated > 0) {
- syslog(LOG_INFO,
- "queue run completed; %d messages processed %d activated",
- num_processed, num_activated);
- }
-
-}
-
-
-
-/*
- * Initialize the SMTP outbound queue
- */
-void smtp_init_spoolout(void) {
- struct ctdlroom qrbuf;
-
- /*
- * Create the room. This will silently fail if the room already
- * exists, and that's perfectly ok, because we want it to exist.
- */
- CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_QUEUE);
-
- /*
- * Make sure it's set to be a "system room" so it doesn't show up
- * in the <K>nown rooms list for Aides.
- */
- if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
- qrbuf.QRflags2 |= QR2_SYSTEM;
- CtdlPutRoomLock(&qrbuf);
- }
-}
-
-
-
-
-/*****************************************************************************/
-/* SMTP UTILITY COMMANDS */
-/*****************************************************************************/
-
-void cmd_smtp(char *argbuf) {
- char cmd[64];
- char node[256];
- char buf[1024];
- int i;
- int num_mxhosts;
-
- if (CtdlAccessCheck(ac_aide)) return;
-
- extract_token(cmd, argbuf, 0, '|', sizeof cmd);
-
- if (!strcasecmp(cmd, "mx")) {
- extract_token(node, argbuf, 1, '|', sizeof node);
- num_mxhosts = getmx(buf, node);
- cprintf("%d %d MX hosts listed for %s\n",
- LISTING_FOLLOWS, num_mxhosts, node);
- for (i=0; i<num_mxhosts; ++i) {
- extract_token(node, buf, i, '|', sizeof node);
- cprintf("%s\n", node);
- }
- cprintf("000\n");
- return;
- }
-
- else if (!strcasecmp(cmd, "runqueue")) {
- run_queue_now = 1;
- cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
- return;
- }
-
- else {
- cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
- }
-
-}
-
-int smtp_aftersave(struct CtdlMessage *msg,
- recptypes *recps)
-{
- /* For internet mail, generate delivery instructions.
- * Yes, this is recursive. Deal with it. Infinite recursion does
- * not happen because the delivery instructions message does not
- * contain a recipient.
- */
- if ((recps != NULL) && (recps->num_internet > 0)) {
- struct CtdlMessage *imsg = NULL;
- char recipient[SIZ];
- StrBuf *SpoolMsg = NewStrBuf();
- long nTokens;
- int i;
-
- syslog(LOG_DEBUG, "Generating delivery instructions\n");
-
- StrBufPrintf(SpoolMsg,
- "Content-type: "SPOOLMIME"\n"
- "\n"
- "msgid|%s\n"
- "submitted|%ld\n"
- "bounceto|%s\n",
- msg->cm_fields[eVltMsgNum],
- (long)time(NULL),
- recps->bounce_to);
-
- if (recps->envelope_from != NULL) {
- StrBufAppendBufPlain(SpoolMsg, HKEY("envelope_from|"), 0);
- StrBufAppendBufPlain(SpoolMsg, recps->envelope_from, -1, 0);
- StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
- }
- if (recps->sending_room != NULL) {
- StrBufAppendBufPlain(SpoolMsg, HKEY("source_room|"), 0);
- StrBufAppendBufPlain(SpoolMsg, recps->sending_room, -1, 0);
- StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
- }
-
- nTokens = num_tokens(recps->recp_internet, '|');
- for (i = 0; i < nTokens; i++) {
- long len;
- len = extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
- if (len > 0) {
- StrBufAppendBufPlain(SpoolMsg, HKEY("remote|"), 0);
- StrBufAppendBufPlain(SpoolMsg, recipient, len, 0);
- StrBufAppendBufPlain(SpoolMsg, HKEY("|0||\n"), 0);
- }
- }
-
- imsg = malloc(sizeof(struct CtdlMessage));
- memset(imsg, 0, sizeof(struct CtdlMessage));
- imsg->cm_magic = CTDLMESSAGE_MAGIC;
- imsg->cm_anon_type = MES_NORMAL;
- imsg->cm_format_type = FMT_RFC822;
- CM_SetField(imsg, eMsgSubject, HKEY("QMSG"));
- CM_SetField(imsg, eAuthor, HKEY("Citadel"));
- CM_SetField(imsg, eJournal, HKEY("do not journal"));
- CM_SetAsFieldSB(imsg, eMesageText, &SpoolMsg);
- CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
- CM_Free(imsg);
- }
- return 0;
-}
-
-CTDL_MODULE_INIT(smtp_queu)
-{
- char *pstr;
-
- if (!threading)
- {
- pstr = getenv("CITSERVER_n_session_max");
- if ((pstr != NULL) && (*pstr != '\0'))
- max_sessions_for_outbound_smtp = atol(pstr); /* how many sessions might be active till we stop adding more smtp jobs */
-
- pstr = getenv("CITSERVER_smtp_n_delay_count");
- if ((pstr != NULL) && (*pstr != '\0'))
- ndelay_count = atol(pstr); /* every n queued messages we will sleep... */
-
- pstr = getenv("CITSERVER_smtp_delay");
- if ((pstr != NULL) && (*pstr != '\0'))
- delay_msec = atol(pstr) * 1000; /* this many seconds. */
-
- CtdlRegisterMessageHook(smtp_aftersave, EVT_AFTERSAVE);
-
- CtdlFillSystemContext(&smtp_queue_CC, "SMTP_Send");
- ActiveQItems = NewHash(1, lFlathash);
- pthread_mutex_init(&ActiveQItemsLock, NULL);
-
- QItemHandlers = NewHash(0, NULL);
-
- RegisterQItemHandler(HKEY("msgid"), QItem_Handle_MsgID);
- RegisterQItemHandler(HKEY("envelope_from"), QItem_Handle_EnvelopeFrom);
- RegisterQItemHandler(HKEY("retry"), QItem_Handle_retry);
- RegisterQItemHandler(HKEY("attempted"), QItem_Handle_Attempted);
- RegisterQItemHandler(HKEY("remote"), QItem_Handle_Recipient);
- RegisterQItemHandler(HKEY("bounceto"), QItem_Handle_BounceTo);
- RegisterQItemHandler(HKEY("source_room"), QItem_Handle_SenderRoom);
- RegisterQItemHandler(HKEY("submitted"), QItem_Handle_Submitted);
-
- smtp_init_spoolout();
-
- CtdlRegisterEVCleanupHook(smtp_evq_cleanup);
-
- CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
- CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER, PRIO_SEND + 10);
- }
-
- /* return our Subversion id for the Log */
- return "smtpeventclient";
-}
+++ /dev/null
-/*
- * This module is an SMTP and ESMTP implementation for the Citadel system.
- * It is compliant with all of the following:
- *
- * RFC 821 - Simple Mail Transfer Protocol
- * RFC 876 - Survey of SMTP Implementations
- * RFC 1047 - Duplicate messages and SMTP
- * RFC 1652 - 8 bit MIME
- * RFC 1869 - Extended Simple Mail Transfer Protocol
- * RFC 1870 - SMTP Service Extension for Message Size Declaration
- * RFC 2033 - Local Mail Transfer Protocol
- * RFC 2197 - SMTP Service Extension for Command Pipelining
- * RFC 2476 - Message Submission
- * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
- * RFC 2554 - SMTP Service Extension for Authentication
- * RFC 2821 - Simple Mail Transfer Protocol
- * RFC 2822 - Internet Message Format
- * RFC 2920 - SMTP Service Extension for Command Pipelining
- *
- * Copyright (c) 1998-2015 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- */
-
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <termios.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <syslog.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-#include <sys/wait.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "control.h"
-#include "user_ops.h"
-#include "database.h"
-#include "msgbase.h"
-#include "internet_addressing.h"
-#include "genstamp.h"
-#include "domain.h"
-#include "clientsocket.h"
-#include "locate_host.h"
-#include "citadel_dirs.h"
-
-#include "ctdl_module.h"
-
-#include "smtp_util.h"
-#include "event_client.h"
-#include "smtpqueue.h"
-#include "smtp_clienthandlers.h"
-
-
-#define SMTP_ERROR(WHICH_ERR, ERRSTR) do { \
- Msg->MyQEntry->Status = WHICH_ERR; \
- StrBufAppendBufPlain(Msg->MyQEntry->StatusMessage, \
- HKEY(ERRSTR), 0); \
- StrBufTrim(Msg->MyQEntry->StatusMessage); \
- return eAbort; } \
- while (0)
-
-#define SMTP_VERROR(WHICH_ERR) do { \
- Msg->MyQEntry->Status = WHICH_ERR; \
- StrBufPlain(Msg->MyQEntry->StatusMessage, \
- ChrPtr(Msg->IO.IOBuf) + 4, \
- StrLength(Msg->IO.IOBuf) - 4); \
- StrBufTrim(Msg->MyQEntry->StatusMessage); \
- return eAbort; } \
- while (0)
-
-#define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(Msg->IO.IOBuf)[0] == WHICH_STATE)
-
-#define SMTP_DBG_SEND() \
- syslog(LOG_DEBUG, "> %s\n", ChrPtr(Msg->IO.SendBuf.Buf))
-
-#define SMTP_DBG_READ() \
- syslog(LOG_DEBUG, "< %s\n", ChrPtr(Msg->IO.IOBuf))
-
-/*
- * if a Read handler wants to skip to a specific part use this macro.
- * the -1 is here since the auto-forward following has to be taken into account.
- */
-#define READ_NEXT_STATE(state) Msg->State = state - 1
-
-/*****************************************************************************/
-/* SMTP CLIENT STATE CALLBACKS */
-/*****************************************************************************/
-eNextState SMTPC_read_greeting(SmtpOutMsg *Msg)
-{
- /* Process the SMTP greeting from the server */
- AsyncIO *IO = &Msg->IO;
- SMTP_DBG_READ();
- SetSMTPState(IO, eSTMPsmtp);
-
- if (!SMTP_IS_STATE('2')) {
- if (SMTP_IS_STATE('4'))
- SMTP_VERROR(4);
- else
- SMTP_VERROR(5);
- }
- return eSendReply;
-}
-
-eNextState SMTPC_send_EHLO(SmtpOutMsg *Msg)
-{
- /* At this point we know we are talking to a real SMTP server */
-
- /* Do a EHLO command. If it fails, try the HELO command. */
- StrBufPrintf(Msg->IO.SendBuf.Buf, "EHLO %s\r\n", CtdlGetConfigStr("c_fqdn"));
-
- SMTP_DBG_SEND();
- return eReadMessage;
-}
-
-eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *Msg)
-{
- SMTP_DBG_READ();
-
- if (SMTP_IS_STATE('2')) {
- READ_NEXT_STATE(eSMTPAuth);
-
- if ((Msg->pCurrRelay == NULL) ||
- (Msg->pCurrRelay->User == NULL))
- READ_NEXT_STATE(eFROM); /* Skip auth... */
- if (Msg->pCurrRelay != NULL)
- {
- if (strstr(ChrPtr(Msg->IO.IOBuf), "LOGIN") != NULL)
- Msg->SendLogin = 1;
- else if ((Msg->MultiLineBuf != NULL) &&
- strstr(ChrPtr(Msg->MultiLineBuf), "LOGIN") != NULL)
- {
- Msg->SendLogin = 1;
- }
- }
- }
- /* else we fall back to 'helo' */
- return eSendReply;
-}
-
-eNextState STMPC_send_HELO(SmtpOutMsg *Msg)
-{
- StrBufPrintf(Msg->IO.SendBuf.Buf, "HELO %s\r\n", CtdlGetConfigStr("c_fqdn"));
-
- SMTP_DBG_SEND();
- return eReadMessage;
-}
-
-eNextState SMTPC_read_HELO_reply(SmtpOutMsg *Msg)
-{
- SMTP_DBG_READ();
-
- if (!SMTP_IS_STATE('2'))
- {
- if (SMTP_IS_STATE('4'))
- SMTP_VERROR(4);
- else
- SMTP_VERROR(5);
- }
- if (Msg->pCurrRelay != NULL)
- {
- if (strstr(ChrPtr(Msg->IO.IOBuf), "LOGIN") != NULL)
- Msg->SendLogin = 1;
- }
- if ((Msg->pCurrRelay == NULL) ||
- (Msg->pCurrRelay->User == NULL))
- READ_NEXT_STATE(eFROM); /* Skip auth... */
-
- return eSendReply;
-}
-
-eNextState SMTPC_send_auth(SmtpOutMsg *Msg)
-{
- char buf[SIZ];
- char encoded[1024];
-
- if ((Msg->pCurrRelay == NULL) ||
- (Msg->pCurrRelay->User == NULL))
- READ_NEXT_STATE(eFROM); /* Skip auth, shouldn't even come here!... */
- else {
- /* Do an AUTH command if necessary */
- if (Msg->SendLogin)
- {
- StrBufPlain(Msg->IO.SendBuf.Buf,
- HKEY("AUTH LOGIN\r\n"));
- }
- else
- {
- sprintf(buf, "%s%c%s%c%s",
- Msg->pCurrRelay->User, '\0',
- Msg->pCurrRelay->User, '\0',
- Msg->pCurrRelay->Pass);
-
- size_t len = CtdlEncodeBase64(encoded, buf,
- strlen(Msg->pCurrRelay->User) * 2 +
- strlen(Msg->pCurrRelay->Pass) + 2, 0);
-
- if (buf[len - 1] == '\n') {
- buf[len - 1] = '\0';
- }
-
- StrBufPrintf(Msg->IO.SendBuf.Buf,
- "AUTH PLAIN %s\r\n",
- encoded);
- }
- }
- SMTP_DBG_SEND();
- return eReadMessage;
-}
-
-
-eNextState SMTPC_read_auth_reply(SmtpOutMsg *Msg)
-{
- /* Do an AUTH command if necessary */
-
- SMTP_DBG_READ();
-
- if (Msg->SendLogin)
- {
- if (!SMTP_IS_STATE('3'))
- SMTP_VERROR(5);
- }
- else
- {
- if (!SMTP_IS_STATE('2')) {
- if (SMTP_IS_STATE('4'))
- SMTP_VERROR(4);
- else
- SMTP_VERROR(5);
- }
- READ_NEXT_STATE(eFROM);
- }
- return eSendReply;
-}
-
-
-eNextState SMTPC_send_authplain_1(SmtpOutMsg *Msg)
-{
- char buf[SIZ];
- char encoded[1024];
- long encodedlen;
-
- sprintf(buf, "%s",
- Msg->pCurrRelay->User);
-
- encodedlen = CtdlEncodeBase64(
- encoded,
- Msg->pCurrRelay->User,
- strlen(Msg->pCurrRelay->User),
- 0);
- if (encoded[encodedlen - 1] == '\n') {
- encodedlen --;
- encoded[encodedlen] = '\0';
- }
-
- StrBufPlain(Msg->IO.SendBuf.Buf,
- encoded,
- encodedlen);
-
- StrBufAppendBufPlain(Msg->IO.SendBuf.Buf,
- HKEY("\r\n"), 0);
-
- SMTP_DBG_SEND();
-
- return eReadMessage;
-}
-eNextState SMTPC_read_auth_plain_reply_1(SmtpOutMsg *Msg)
-{
- /* Do an AUTH command if necessary */
-
- SMTP_DBG_READ();
-
- if (!SMTP_IS_STATE('3'))
- SMTP_VERROR(5);
- return eSendReply;
-}
-
-
-eNextState SMTPC_send_authplain_2(SmtpOutMsg *Msg)
-{
- char buf[SIZ];
- char encoded[1024];
- long encodedlen;
-
- sprintf(buf, "%s",
- Msg->pCurrRelay->Pass);
-
- encodedlen = CtdlEncodeBase64(
- encoded,
- Msg->pCurrRelay->Pass,
- strlen(Msg->pCurrRelay->Pass),
- 0);
-
- if (encoded[encodedlen - 1] == '\n') {
- encodedlen --;
- encoded[encodedlen] = '\0';
- }
-
- StrBufPlain(Msg->IO.SendBuf.Buf,
- encoded,
- encodedlen);
-
- StrBufAppendBufPlain(Msg->IO.SendBuf.Buf,
- HKEY("\r\n"), 0);
-
- SMTP_DBG_SEND();
-
- return eReadMessage;
-}
-eNextState SMTPC_read_auth_plain_reply_2(SmtpOutMsg *Msg)
-{
- /* Do an AUTH command if necessary */
-
- SMTP_DBG_READ();
-
- if (!SMTP_IS_STATE('2')) {
- if (SMTP_IS_STATE('4'))
- SMTP_VERROR(4);
- else
- SMTP_VERROR(5);
- }
- return eSendReply;
-}
-
-eNextState SMTPC_send_FROM(SmtpOutMsg *Msg)
-{
- /* previous command succeeded, now try the MAIL FROM: command */
- StrBufPrintf(Msg->IO.SendBuf.Buf,
- "MAIL FROM:<%s>\r\n",
- Msg->envelope_from);
-
- SMTP_DBG_SEND();
- return eReadMessage;
-}
-
-eNextState SMTPC_read_FROM_reply(SmtpOutMsg *Msg)
-{
- SMTP_DBG_READ();
-
- if (!SMTP_IS_STATE('2')) {
- if (SMTP_IS_STATE('4'))
- SMTP_VERROR(4);
- else
- SMTP_VERROR(5);
- }
- return eSendReply;
-}
-
-
-eNextState SMTPC_send_RCPT(SmtpOutMsg *Msg)
-{
- /* MAIL succeeded, now try the RCPT To: command */
- StrBufPrintf(Msg->IO.SendBuf.Buf,
- "RCPT TO:<%s@%s>\r\n",
- Msg->user,
- Msg->node);
-
- SMTP_DBG_SEND();
- return eReadMessage;
-}
-
-eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *Msg)
-{
- SMTP_DBG_READ();
-
- if (!SMTP_IS_STATE('2')) {
- if (SMTP_IS_STATE('4'))
- SMTP_VERROR(4);
- else
- SMTP_VERROR(5);
- }
- return eSendReply;
-}
-
-eNextState SMTPC_send_DATAcmd(SmtpOutMsg *Msg)
-{
- /* RCPT succeeded, now try the DATA command */
- StrBufPlain(Msg->IO.SendBuf.Buf,
- HKEY("DATA\r\n"));
-
- SMTP_DBG_SEND();
- return eReadMessage;
-}
-
-eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *Msg)
-{
- AsyncIO *IO = &Msg->IO;
- SMTP_DBG_READ();
-
- if (!SMTP_IS_STATE('3')) {
- SetSMTPState(IO, eSTMPfailOne);
- if (SMTP_IS_STATE('4'))
- SMTP_VERROR(3);
- else
- SMTP_VERROR(5);
- }
- SetSMTPState(IO, eSTMPsmtpdata);
- return eSendReply;
-}
-
-eNextState SMTPC_send_data_body(SmtpOutMsg *Msg)
-{
- StrBuf *Buf;
- /* If we reach this point, the server is expecting data.*/
-
- Buf = Msg->IO.SendBuf.Buf;
- Msg->IO.SendBuf.Buf = Msg->msgtext;
- Msg->msgtext = Buf;
- /*
- * sending the message itself doesn't use this state machine.
- * so we have to operate it here by ourselves.
- */
- Msg->State ++;
-
- return eSendMore;
-}
-
-eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *Msg)
-{
- StrBuf *Buf;
-
- Buf = Msg->IO.SendBuf.Buf;
- Msg->IO.SendBuf.Buf = Msg->msgtext;
- Msg->msgtext = Buf;
-
- StrBufPlain(Msg->IO.SendBuf.Buf,
- HKEY(".\r\n"));
-
- return eReadMessage;
-
-}
-
-eNextState SMTPC_read_data_body_reply(SmtpOutMsg *Msg)
-{
- AsyncIO *IO = &Msg->IO;
- SMTP_DBG_READ();
-
- if (!SMTP_IS_STATE('2')) {
- if (SMTP_IS_STATE('4'))
- SMTP_VERROR(4);
- else
- SMTP_VERROR(5);
- }
-
- SetSMTPState(IO, eSTMPsmtpdone);
- /* We did it! */
- StrBufPlain(Msg->MyQEntry->StatusMessage,
- &ChrPtr(Msg->IO.RecvBuf.Buf)[4],
- StrLength(Msg->IO.RecvBuf.Buf) - 4);
- StrBufTrim(Msg->MyQEntry->StatusMessage);
- Msg->MyQEntry->Status = 2;
- return eSendReply;
-}
-
-eNextState SMTPC_send_QUIT(SmtpOutMsg *Msg)
-{
- StrBufPlain(Msg->IO.SendBuf.Buf,
- HKEY("QUIT\r\n"));
-
- SMTP_DBG_SEND();
- return eReadMessage;
-}
-
-eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *Msg)
-{
- SMTP_DBG_READ();
-
- syslog(LOG_DEBUG,
- "delivery to <%s> @ <%s> (%s) succeeded\n",
- Msg->user,
- Msg->node,
- Msg->name);
-
- return eTerminateConnection;
-}
-
-eNextState SMTPC_read_dummy(SmtpOutMsg *Msg)
-{
- return eSendReply;
-}
-
-eNextState SMTPC_send_dummy(SmtpOutMsg *Msg)
-{
- return eReadMessage;
-}
-
-/*****************************************************************************/
-/* SMTP CLIENT DISPATCHER */
-/*****************************************************************************/
-SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
- SMTPC_read_greeting,
- SMTPC_read_EHLO_reply,
- SMTPC_read_HELO_reply,
- SMTPC_read_auth_reply,
- SMTPC_read_auth_plain_reply_1,
- SMTPC_read_auth_plain_reply_2,
- SMTPC_read_FROM_reply,
- SMTPC_read_RCPT_reply,
- SMTPC_read_DATAcmd_reply,
- SMTPC_read_dummy,
- SMTPC_read_data_body_reply,
- SMTPC_read_QUIT_reply
-};
-SMTPSendHandler SendHandlers[eMaxSMTPC] = {
- SMTPC_send_dummy, /* we don't send a greeting, the server does... */
- SMTPC_send_EHLO,
- STMPC_send_HELO,
- SMTPC_send_auth,
- SMTPC_send_authplain_1,
- SMTPC_send_authplain_2,
- SMTPC_send_FROM,
- SMTPC_send_RCPT,
- SMTPC_send_DATAcmd,
- SMTPC_send_data_body,
- SMTPC_send_terminate_data_body,
- SMTPC_send_QUIT
-};
-
-const double SMTP_C_ConnTimeout = 300.; /* wail 1 minute for connections... */
-
-const double SMTP_C_ReadTimeouts[eMaxSMTPC] = {
- 300., /* Greeting... */
- 30., /* EHLO */
- 30., /* HELO */
- 30., /* Auth */
- 30., /* Auth */
- 30., /* Auth */
- 30., /* From */
- 90., /* RCPT */
- 30., /* DATA */
- 90., /* DATABody */
- 90., /* end of body... */
- 30. /* QUIT */
-};
-const double SMTP_C_SendTimeouts[eMaxSMTPC] = {
- 90., /* Greeting... */
- 30., /* EHLO */
- 30., /* HELO */
- 30., /* Auth */
- 30., /* Auth */
- 30., /* Auth */
- 30., /* From */
- 30., /* RCPT */
- 30., /* DATA */
- 90., /* DATABody */
- 900., /* end of body... */
- 30. /* QUIT */
-};
-
-const ConstStr ReadErrors[eMaxSMTPC + 1] = {
- {HKEY("Connection broken during SMTP conversation")},
- {HKEY("Connection broken during SMTP EHLO")},
- {HKEY("Connection broken during SMTP HELO")},
- {HKEY("Connection broken during SMTP AUTH")},
- {HKEY("Connection broken during SMTP AUTH PLAIN I")},
- {HKEY("Connection broken during SMTP AUTH PLAIN II")},
- {HKEY("Connection broken during SMTP MAIL FROM")},
- {HKEY("Connection broken during SMTP RCPT")},
- {HKEY("Connection broken during SMTP DATA")},
- {HKEY("Connection broken during SMTP message transmit")},
- {HKEY("Connection broken during SMTP message transmit")},/* quit reply, don't care. */
- {HKEY("Connection broken during SMTP message transmit")},/* quit reply, don't care. */
- {HKEY("")}/* quit reply, don't care. */
-};
-
-
-
-
-
-int smtp_resolve_recipients(SmtpOutMsg *Msg)
-{
- const char *ptr;
- char buf[1024];
- int scan_done;
- int lp, rp;
- int i;
-
- syslog(LOG_DEBUG, "%s\n", __FUNCTION__);
-
- if ((Msg==NULL) ||
- (Msg->MyQEntry == NULL) ||
- (StrLength(Msg->MyQEntry->Recipient) == 0)) {
- return 0;
- }
-
- /* Parse out the host portion of the recipient address */
- process_rfc822_addr(ChrPtr(Msg->MyQEntry->Recipient),
- Msg->user,
- Msg->node,
- Msg->name);
-
- syslog(LOG_DEBUG,
- "Attempting delivery to <%s> @ <%s> (%s)\n",
- Msg->user,
- Msg->node,
- Msg->name);
-
- /* If no envelope_from is supplied, extract one from the message */
- Msg->envelope_from = ChrPtr(Msg->MyQItem->EnvelopeFrom);
- if ( (Msg->envelope_from == NULL) ||
- (IsEmptyStr(Msg->envelope_from)) ) {
- Msg->mailfrom[0] = '\0';
- scan_done = 0;
- ptr = ChrPtr(Msg->msgtext);
- do {
- if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0)
- {
- scan_done = 1;
- }
- if (!strncasecmp(buf, "From:", 5))
- {
- safestrncpy(Msg->mailfrom,
- &buf[5],
- sizeof Msg->mailfrom);
-
- striplt(Msg->mailfrom);
- for (i=0; Msg->mailfrom[i]; ++i) {
- if (!isprint(Msg->mailfrom[i]))
- {
- strcpy(&Msg->mailfrom[i],
- &Msg->mailfrom[i+1]);
- i=0;
- }
- }
-
- /* Strip out parenthesized names */
- lp = (-1);
- rp = (-1);
- for (i=0;
- !IsEmptyStr(Msg->mailfrom + i);
- ++i)
- {
- if (Msg->mailfrom[i] == '(') lp = i;
- if (Msg->mailfrom[i] == ')') rp = i;
- }
- if ((lp>0)&&(rp>lp))
- {
- strcpy(&Msg->mailfrom[lp-1],
- &Msg->mailfrom[rp+1]);
- }
-
- /* Prefer brokketized names */
- lp = (-1);
- rp = (-1);
- for (i=0;
- !IsEmptyStr(Msg->mailfrom + i);
- ++i)
- {
- if (Msg->mailfrom[i] == '<') lp = i;
- if (Msg->mailfrom[i] == '>') rp = i;
- }
- if ( (lp>=0) && (rp>lp) ) {
- Msg->mailfrom[rp] = 0;
- memmove(Msg->mailfrom,
- &Msg->mailfrom[lp + 1],
- rp - lp);
- }
-
- scan_done = 1;
- }
- } while (scan_done == 0);
- if (IsEmptyStr(Msg->mailfrom))
- strcpy(Msg->mailfrom, "someone@somewhere.org");
-
- stripallbut(Msg->mailfrom, '<', '>');
- Msg->envelope_from = Msg->mailfrom;
- }
-
- return 1;
-}
+++ /dev/null
-/*
- *
- * Copyright (c) 1998-2012 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-typedef enum _eSMTP_C_States {
- eConnectMX,
- eEHLO,
- eHELO,
- eSMTPAuth,
- eSMTPAuthPlain1,
- eSMTPAuthPlain2,
- eFROM,
- eRCPT,
- eDATA,
- eDATABody,
- eDATATerminateBody,
- eQUIT,
- eMaxSMTPC
-} eSMTP_C_States;
-
-
-typedef struct _stmp_out_msg {
- MailQEntry *MyQEntry;
- OneQueItem *MyQItem;
- long n;
- AsyncIO IO;
- long CXFlags;
- int IDestructQueItem;
- int nRemain;
-
- eSMTP_C_States State;
-
- struct ares_mx_reply *AllMX;
- struct ares_mx_reply *CurrMX;
- const char *mx_port;
- const char *mx_host;
- const char *LookupHostname;
- int iMX, nMX;
- int LookupWhich;
-
- DNSQueryParts MxLookup;
- DNSQueryParts HostLookup;
- struct hostent *OneMX;
- char **pIP;
-
- ParsedURL *Relay;
- ParsedURL *pCurrRelay;
- StrBuf *msgtext;
- StrBuf *QMsgData;
- StrBuf *MultiLineBuf;
- const char *envelope_from;
-
- char user[1024];
- char node[1024];
- char name[1024];
- char mailfrom[1024];
- long SendLogin;
- long Flags;
- long IsRelay;
-} SmtpOutMsg;
-
-
-typedef eNextState (*SMTPReadHandler)(SmtpOutMsg *Msg);
-typedef eNextState (*SMTPSendHandler)(SmtpOutMsg *Msg);
-
-SMTPReadHandler ReadHandlers[eMaxSMTPC];
-SMTPSendHandler SendHandlers[eMaxSMTPC];
-const ConstStr ReadErrors[eMaxSMTPC+1];
-const double SMTP_C_ReadTimeouts[eMaxSMTPC];
-const double SMTP_C_SendTimeouts[eMaxSMTPC];
-const double SMTP_C_ConnTimeout;
-
-#define F_RELAY (1<<0) /* we have a Relay host configuration */
-#define F_HAVE_FALLBACK (1<<1) /* we have a fallback host configuration */
-#define F_FALLBACK (1<<2)
-#define F_HAVE_MX (1<<3) /* we have a list of mx records to go through.*/
-#define F_DIRECT (1<<4) /* no mx record found, trying direct connect. */
-
-extern int SMTPClientDebugEnabled;
-
-int smtp_resolve_recipients(SmtpOutMsg *SendMsg);
-
-#define QID ((SmtpOutMsg*)IO->Data)->MyQItem->MessageID
-#define N ((SmtpOutMsg*)IO->Data)->n
-#define DBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (SMTPClientDebugEnabled != 0))
-
-typedef enum __smtpstate {
- eSTMPmxlookup,
- eSTMPevaluatenext,
- eSTMPalookup,
- eSTMPaaaalookup,
- eSTMPconnecting,
- eSTMPsmtp,
- eSTMPsmtpdata,
- eSTMPsmtpdone,
- eSTMPfinished,
- eSTMPfailOne,
- eSMTPFailTemporary,
- eSMTPFailTotal
-} smtpstate;
-
-void SetSMTPState(AsyncIO *IO, smtpstate State);
/*
- * This module is an SMTP and ESMTP implementation for the Citadel system.
- * It is compliant with all of the following:
- *
- * RFC 821 - Simple Mail Transfer Protocol
- * RFC 876 - Survey of SMTP Implementations
- * RFC 1047 - Duplicate messages and SMTP
- * RFC 1652 - 8 bit MIME
- * RFC 1869 - Extended Simple Mail Transfer Protocol
- * RFC 1870 - SMTP Service Extension for Message Size Declaration
- * RFC 2033 - Local Mail Transfer Protocol
- * RFC 2197 - SMTP Service Extension for Command Pipelining
- * RFC 2476 - Message Submission
- * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
- * RFC 2554 - SMTP Service Extension for Authentication
- * RFC 2821 - Simple Mail Transfer Protocol
- * RFC 2822 - Internet Message Format
- * RFC 2920 - SMTP Service Extension for Command Pipelining
- *
- * The VRFY and EXPN commands have been removed from this implementation
- * because nobody uses these commands anymore, except for spammers.
+ * Utility functions for the Citadel SMTP implementation
*
* Copyright (c) 1998-2017 by the citadel.org team
*
#include "clientsocket.h"
#include "locate_host.h"
#include "citadel_dirs.h"
-
#include "ctdl_module.h"
-
#include "smtp_util.h"
-#include "smtpqueue.h"
-#include "smtp_clienthandlers.h"
const char *smtp_get_Recipients(void)
{
/*
- * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
- * instructions for "5" codes (permanent fatal errors) and produce/deliver
- * a "bounce" message (delivery status notification).
+ * smtp_do_bounce() is caled by smtp_process_one_msg() to scan a set of delivery
+ * instructions for errors and produce/deliver a "bounce" message (delivery
+ * status notification).
+ *
+ * is_final should be set to:
+ * SDB_BOUNCE_FATALS Advise the sender of all 5XX (permanent failures)
+ * SDB_BOUNCE_ALL Advise the sender that all deliveries have failed and will not be retried
+ * SDB_WARN Warn the sender about all 4XX transient delays
*/
-void smtp_do_bounce(char *instr, StrBuf *OMsgTxt)
+void smtp_do_bounce(const char *instr, int is_final)
{
int i;
int lines;
StrBuf *boundary;
int num_bounces = 0;
int bounce_this = 0;
- time_t submitted = 0L;
struct CtdlMessage *bmsg = NULL;
- int give_up = 0;
recptypes *valid;
int successful_bounce = 0;
static int seq = 0;
StrBufAppendPrintf(boundary, "%s_%04x%04x", CtdlGetConfigStr("c_fqdn"), getpid(), ++seq);
- lines = num_tokens(instr, '\n');
-
- /* See if it's time to give up on delivery of this message */
- for (i=0; i<lines; ++i) {
- extract_token(buf, instr, i, '\n', sizeof buf);
- extract_token(key, buf, 0, '|', sizeof key);
- extract_token(addr, buf, 1, '|', sizeof addr);
- if (!strcasecmp(key, "submitted")) {
- submitted = atol(addr);
- }
- }
-
- if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
- give_up = 1;
- }
/* Start building our bounce message */
StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
-
- StrBufAppendBufPlain(
- BounceMB,
- HKEY("\r\nThis is a multipart message in MIME format."
- "\r\n\r\n"), 0);
-
+ StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
StrBufAppendBuf(BounceMB, boundary, 0);
StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
- StrBufAppendBufPlain(BounceMB,
- HKEY("Content-type: text/plain\r\n\r\n"), 0);
+ StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
- if (give_up)
+ if (is_final == SDB_BOUNCE_ALL)
+ {
+ StrBufAppendBufPlain(
+ BounceMB,
+ HKEY( "A message you sent could not be delivered "
+ "to some or all of its recipients\ndue to "
+ "prolonged unavailability of its destination(s).\n"
+ "Giving up on the following addresses:\n\n"),
+ 0);
+ }
+ else if (is_final == SDB_BOUNCE_FATALS)
{
StrBufAppendBufPlain(
BounceMB,
- HKEY("A message you sent could not be delivered "
- "to some or all of its recipients\ndue to "
- "prolonged unavailability of its destination(s).\n"
- "Giving up on the following addresses:\n\n"), 0);
+ HKEY( "A message you sent could not be delivered "
+ "to some or all of its recipients.\n"
+ "The following addresses were undeliverable:\n\n"),
+ 0);
}
- else
+ else if (is_final == SDB_WARN)
{
StrBufAppendBufPlain(
BounceMB,
- HKEY("A message you sent could not be delivered "
- "to some or all of its recipients.\n"
- "The following addresses were undeliverable:\n\n"
- ), 0);
+ HKEY("A message you sent has not been delivered "
+ "to some or all of its recipients.\n"
+ "Citadel will continue attempting delivery for five days.\n"
+ "The following addresses were undeliverable:\n\n"),
+ 0);
+ }
+ else // should never get here
+ {
+ StrBufAppendBufPlain(BounceMB, HKEY("This message should never occur.\n\n"), 0);
}
/*
* Now go through the instructions checking for stuff.
*/
+ lines = num_tokens(instr, '\n');
for (i=0; i<lines; ++i) {
long addrlen;
long dsnlen;
}
if (!strcasecmp(key, "remote")) {
- if (status == 5) bounce_this = 1;
- if (give_up) bounce_this = 1;
+ if ((is_final == SDB_BOUNCE_FATALS) && (status == 5)) bounce_this = 1;
+ if ((is_final == SDB_BOUNCE_ALL) && (status != 2)) bounce_this = 1;
+ if ((is_final == SDB_WARN) && (status == 4)) bounce_this = 1;
}
if (bounce_this) {
++num_bounces;
-
StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
-
- remove_token(instr, i, '\n');
- --i;
- --lines;
}
}
StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
StrBufAppendBuf(BounceMB, boundary, 0);
StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
-
- StrBufAppendBufPlain(
- BounceMB,
- HKEY("Content-type: message/rfc822\r\n"), 0);
-
- StrBufAppendBufPlain(
- BounceMB,
- HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
-
- StrBufAppendBufPlain(
- BounceMB,
- HKEY("Content-Disposition: inline\r\n"), 0);
-
+ StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
+ StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
+ StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
- if (OMsgTxt == NULL) {
- CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
- CtdlOutputMsg(omsgid,
- MT_RFC822,
- HEADERS_ALL,
- 0, 1, NULL, 0,
- NULL, NULL, NULL);
-
- StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
- FreeStrBuf(&CC->redirect_buffer);
- }
- else {
- StrBufAppendBuf(BounceMB, OMsgTxt, 0);
- }
+ CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
+ CtdlOutputMsg(omsgid,
+ MT_RFC822,
+ HEADERS_ALL,
+ 0, 1, NULL, 0,
+ NULL, NULL, NULL
+ );
+ StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
+ FreeStrBuf(&CC->redirect_buffer);
}
/* Close the multipart MIME scope */
syslog(LOG_DEBUG, "bounce to user <%s>", bounceto);
}
/* Can we deliver the bounce to the original sender? */
- valid = validate_recipients(bounceto,
- smtp_get_Recipients (),
- 0);
+ valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
if (valid != NULL) {
if (valid->num_error == 0) {
CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
CM_Free(bmsg);
syslog(LOG_DEBUG, "Done processing bounces");
}
+
+
+
+
+
+char *smtpcodes[][2] = {
+ { "211 - System status / system help reply" },
+ { "214", "Help message" },
+ { "220", "Domain service ready" },
+ { "221", "Domain service closing transmission channel" },
+ { "250", "Requested mail action completed and OK" },
+ { "251", "Not Local User, forward email to forward path" },
+ { "252", "Cannot Verify user, will attempt delivery later" },
+ { "253", "Pending messages for node started" },
+ { "354", "Start mail input; end with ." },
+ { "355", "Octet-offset is the transaction offset" },
+ { "421", "Domain service not available, closing transmission channel" },
+ { "432", "Domain service not available, closing transmission channel" },
+ { "450", "Requested mail action not taken: mailbox unavailable. request refused" },
+ { "451", "Requested action aborted: local error in processing Request is unable to be processed, try again" },
+ { "452", "Requested action not taken: insufficient system storage" },
+ { "453", "No mail" },
+ { "454", "TLS not available due to temporary reason. Encryption required for requested authentication mechanism" },
+ { "458", "Unable to queue messages for node" },
+ { "459", "Node not allowed: reason" },
+ { "500", "Syntax error, command unrecognized" },
+ { "501", "Syntax error in parameters or arguments" },
+ { "502", "Command not implemented" },
+ { "503", "Bad sequence of commands" },
+ { "504", "Command parameter not implemented" },
+ { "510", "Check the recipient address" },
+ { "512", "Domain can not be found. Unknown host." },
+ { "515", "Destination mailbox address invalid" },
+ { "517", "Problem with senders mail attribute, check properties" },
+ { "521", "Domain does not accept mail" },
+ { "522", "Recipient has exceeded mailbox limit" },
+ { "523", "Server limit exceeded. Message too large" },
+ { "530", "Access Denied. Authentication required" },
+ { "531", "Mail system Full" },
+ { "533", "Remote server has insufficient disk space to hold email" },
+ { "534", "Authentication mechanism is too weak. Message too big" },
+ { "535", "Multiple servers using same IP. Required Authentication" },
+ { "538", "Encryption required for requested authentication mechanism" },
+ { "540", "Email address has no DNS Server" },
+ { "541", "No response from host" },
+ { "542", "Bad Connection" },
+ { "543", "Routing server failure. No available route" },
+ { "546", "Email looping" },
+ { "547", "Delivery time-out" },
+ { "550", "Requested action not taken: mailbox unavailable or relaying denied" },
+ { "551", "User not local; please try forward path" },
+ { "552", "Requested mail action aborted: exceeded storage allocation" },
+ { "553", "Requested action not taken: mailbox name not allowed" },
+ { "554", "Transaction failed" }
+};
+
+
+
+char *smtpstatus(int code) {
+ int i;
+
+ for (i=0; i<(sizeof(smtpcodes)/sizeof(char *)/2); ++i) {
+ if (atoi(smtpcodes[i][0]) == code) {
+ return(smtpcodes[i][1]);
+ }
+ }
+
+ return("Unknown or other SMTP status");
+}
+
/*
- * This module is an SMTP and ESMTP implementation for the Citadel system.
- * It is compliant with all of the following:
- *
- * RFC 821 - Simple Mail Transfer Protocol
- * RFC 876 - Survey of SMTP Implementations
- * RFC 1047 - Duplicate messages and SMTP
- * RFC 1652 - 8 bit MIME
- * RFC 1869 - Extended Simple Mail Transfer Protocol
- * RFC 1870 - SMTP Service Extension for Message Size Declaration
- * RFC 2033 - Local Mail Transfer Protocol
- * RFC 2197 - SMTP Service Extension for Command Pipelining
- * RFC 2476 - Message Submission
- * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
- * RFC 2554 - SMTP Service Extension for Authentication
- * RFC 2821 - Simple Mail Transfer Protocol
- * RFC 2822 - Internet Message Format
- * RFC 2920 - SMTP Service Extension for Command Pipelining
- *
- * The VRFY and EXPN commands have been removed from this implementation
- * because nobody uses these commands anymore, except for spammers.
- *
- * Copyright (c) 1998-2013 by the citadel.org team
+ * Copyright (c) 1998-2017 by the citadel.org team
*
* This program is open source software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3.
#define SMTP ((citsmtp *)CC->session_specific_data)
-void smtp_do_bounce(char *instr, StrBuf *OMsgTxt);
+// These are all the values that can be passed to the is_final parameter of smtp_do_bounce()
+enum {
+ SDB_BOUNCE_FATALS,
+ SDB_BOUNCE_ALL,
+ SDB_WARN
+};
+
+void smtp_do_bounce(const char *instr, int is_final);
+char *smtpstatus(int code);
+++ /dev/null
-/*
- *
- * Copyright (c) 1998-2012 by the citadel.org team
- *
- * This program is open source software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-/*****************************************************************************/
-/* SMTP CLIENT (Queue Management) STUFF */
-/*****************************************************************************/
-
-#define MaxAttempts 15
-extern const unsigned short DefaultMXPort;
-
-typedef struct _mailq_entry {
- StrBuf *Recipient;
- StrBuf *StatusMessage;
- StrBuf *AllStatusMessages;
- int Status;
- /**<
- * 0 = No delivery has yet been attempted
- * 2 = Delivery was successful
- * 3 = Transient error like connection problem. Try next remote if available.
- * 4 = A transient error was experienced ... try again later
- * 5 = Delivery to this address failed permanently. The error message
- * should be placed in the fourth field so that a bounce message may
- * be generated.
- */
-
- int n;
- int Active;
- int StillActive;
- int nAttempt;
-}MailQEntry;
-
-typedef struct queueitem {
- long SendBounceMail;
- long MessageID;
- long QueMsgID;
- long Submitted;
- int FailNow;
- HashList *MailQEntries;
-/* copy of the currently parsed item in the MailQEntries list;
- * if null add a new one.
- */
- MailQEntry *Current;
- time_t ReattemptWhen;
- time_t Retry;
-
- long ActiveDeliveries;
- long NotYetShutdownDeliveries;
- StrBuf *EnvelopeFrom;
- StrBuf *BounceTo;
- StrBuf *SenderRoom;
- ParsedURL *URL;
- ParsedURL *FallBackHost;
- int HaveRelay;
-} OneQueItem;
-
-typedef void (*QItemHandler)(OneQueItem *Item, StrBuf *Line, const char **Pos);
-
-
-typedef struct __QItemHandlerStruct {
- QItemHandler H;
-} QItemHandlerStruct;
-int DecreaseQReference(OneQueItem *MyQItem);
-void DecreaseShutdownDeliveries(OneQueItem *MyQItem);
-int GetShutdownDeliveries(OneQueItem *MyQItem);
-void RemoveQItem(OneQueItem *MyQItem);
-int CountActiveQueueEntries(OneQueItem *MyQItem, int before);
-StrBuf *SerializeQueueItem(OneQueItem *MyQItem);
-void smtpq_do_bounce(OneQueItem *MyQItem, StrBuf *OMsgTxt, ParsedURL *Relay);
-
-int CheckQEntryIsBounce(MailQEntry *ThisItem);
* GNU General Public License for more details.
*/
-/*
- * If you want to keep a transcript of all multiuser chats that go across
- * your system, define CHATLOG to the filename to be saved to. Otherwise,
- * set CHATLOG to "/dev/null".
- */
-#define CHATLOG "/dev/null"
-
-/*
- * Logging level to use if none is specified on the command line.
- * Note that this will suppress messages before they even get to syslog().
- */
-#define DEFAULT_VERBOSITY 7
-
/*
* NLI is the string that shows up in a <W>ho's online listing for sessions
* that are active, but for which no user has yet authenticated.
#define BIGMSG 1024
/*
- * SMTP delivery retry rules (all values are in seconds)
- *
- * If delivery of a message via SMTP is unsuccessful, Citadel will try again
- * after SMTP_RETRY_INTERVAL seconds. This interval will double after each
- * unsuccessful delivery, up to a maximum of SMTP_RETRY_MAX seconds. If no
- * successful delivery has been accomplished after SMTP_GIVE_UP seconds, the
- * message will be returned to its sender.
+ * SMTP delivery timeouts (measured in seconds)
+ * If outbound SMTP deliveries cannot be completed due to transient errors
+ * within SMTP_DELIVER_WARN seconds, the sender will receive a warning message
+ * indicating that the message has not yet been delivered but Citadel will
+ * keep trying. After SMTP_DELIVER_FAIL seconds, Citadel will advise the
+ * sender that the deliveries have failed.
*/
-#define SMTP_RETRY_INTERVAL 300 /* 5 minutes */
-#define SMTP_RETRY_MAX 43200 /* 12 hours */
-#define SMTP_GIVE_UP 432000 /* 5 days */
+#define SMTP_DELIVER_WARN 14400 // warn after four hours
+#define SMTP_DELIVER_FAIL 432000 // fail after five days
/*
* Who bounced messages appear to be from