X-Git-Url: https://code.citadel.org/?p=citadel.git;a=blobdiff_plain;f=citadel%2Fmodules%2Fsmtp%2Fserv_smtpclient.c;h=dd2c7c9e9b0678f9a1a2b9148d33889e6a0b7def;hp=1a906bfd66faf3a1f511784c5c9862a9e8a3f0a5;hb=71a6a60c6b80538d3055cc8f6ab650061131189e;hpb=ad7fea2ebb39a9d89571cea08050f7b6ec7351cd diff --git a/citadel/modules/smtp/serv_smtpclient.c b/citadel/modules/smtp/serv_smtpclient.c index 1a906bfd6..dd2c7c9e9 100644 --- a/citadel/modules/smtp/serv_smtpclient.c +++ b/citadel/modules/smtp/serv_smtpclient.c @@ -1,53 +1,25 @@ /* - * This module is an SMTP and ESMTP implementation for the Citadel system. - * It is compliant with all of the following: + * Transmit outbound SMTP mail to the big wide world of the Internet * - * 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. + * This is the new, exciting, clever version that makes libcurl do all the work :) * - * Copyright (c) 1998-2011 by the citadel.org team + * 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 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 + * 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 #include #include -#include -#include -#include -#include -#include -#include -#include +#include #if TIME_WITH_SYS_TIME # include @@ -60,949 +32,486 @@ # endif #endif -#include #include #include -#include -#include -#include -#include +#include +#include +#include #include +#include #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 "ctdl_module.h" +#include "clientsocket.h" #include "msgbase.h" -#include "internet_addressing.h" -#include "genstamp.h" #include "domain.h" -#include "clientsocket.h" -#include "locate_host.h" +#include "internet_addressing.h" #include "citadel_dirs.h" +#include "modules/smtp/smtp_util.h" -#include "ctdl_module.h" - -#include "smtp_util.h" -#ifndef EXPERIMENTAL_SMTP_EVENT_CLIENT - -int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */ - -/*****************************************************************************/ -/* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */ -/*****************************************************************************/ +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 /* - * smtp_try() - * - * Called by smtp_do_procmsg() to attempt delivery to one SMTP host - * + * Initialize the SMTP outbound queue */ -void smtp_try(const char *key, const char *addr, int *status, - char *dsn, size_t n, long msgnum, char *envelope_from -) { - int sock = (-1); - char mxhosts[1024]; - int num_mxhosts; - int mx; - int i; - char user[1024], node[1024], name[1024]; - char buf[1024]; - char mailfrom[1024]; - char mx_user[256]; - char mx_pass[256]; - char mx_host[256]; - char mx_port[256]; - int lp, rp; - char *msgtext; - const char *ptr; - size_t msg_size; - int scan_done; - CitContext *CCC=CC; - - /* Parse out the host portion of the recipient address */ - process_rfc822_addr(addr, user, node, name); - - syslog(LOG_DEBUG, "SMTP client: Attempting delivery to <%s> @ <%s> (%s)", user, node, name); - - /* Load the message out of the database */ - CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ); - CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) ); - msg_size = StrLength(CCC->redirect_buffer); - msgtext = SmashStrBuf(&CCC->redirect_buffer); - - /* This check is being added on 2011apr02 as we are experiencing a problem where the msg_size - * is being reported as zero for every delivery after about 12h of server runtime; this seems - * to indicate a heap corruption of some sort? Deferring will allow the message to be delivered - * after a server restart instead of discarded. After we fix the real problem, this will still - * be a good sanity check to still have in place. --ajc +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. */ - if (msg_size <= 0) { - syslog(LOG_ALERT, "msg_size is zero -- possible data corruption"); - *status = 4; - strcpy(dsn, "Internal server error prevented successful delivery -- deferring"); - goto bail; - } + CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_QUEUE); - /* If no envelope_from is supplied, extract one from the message */ - if ( (envelope_from == NULL) || (IsEmptyStr(envelope_from)) ) { - strcpy(mailfrom, ""); - scan_done = 0; - ptr = msgtext; - do { - if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) { - scan_done = 1; - } - if (!strncasecmp(buf, "From:", 5)) { - safestrncpy(mailfrom, &buf[5], sizeof mailfrom); - striplt(mailfrom); - for (i=0; mailfrom[i]; ++i) { - if (!isprint(mailfrom[i])) { - strcpy(&mailfrom[i], &mailfrom[i+1]); - i=0; - } - } - - /* Strip out parenthesized names */ - lp = (-1); - rp = (-1); - for (i=0; mailfrom[i]; ++i) { - if (mailfrom[i] == '(') lp = i; - if (mailfrom[i] == ')') rp = i; - } - if ((lp>0)&&(rp>lp)) { - strcpy(&mailfrom[lp-1], &mailfrom[rp+1]); - } - - /* Prefer brokketized names */ - lp = (-1); - rp = (-1); - for (i=0; mailfrom[i]; ++i) { - if (mailfrom[i] == '<') lp = i; - if (mailfrom[i] == '>') rp = i; - } - if ( (lp>=0) && (rp>lp) ) { - mailfrom[rp] = 0; - strcpy(mailfrom, &mailfrom[lp + 1]); - } - - scan_done = 1; - } - } while (scan_done == 0); - if (IsEmptyStr(mailfrom)) { - syslog(LOG_DEBUG, "This message has no From: header. Hmm..."); - } - stripallbut(mailfrom, '<', '>'); - envelope_from = mailfrom; + /* + * Make sure it's set to be a "system room" so it doesn't show up + * in the nown rooms list for administrators. + */ + if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) { + qrbuf.QRflags2 |= QR2_SYSTEM; + CtdlPutRoomLock(&qrbuf); } +} - /* Figure out what mail exchanger host we have to connect to */ - num_mxhosts = getmx(mxhosts, node); - syslog(LOG_DEBUG, "Number of MX hosts for <%s> is %d [%s]", node, num_mxhosts, mxhosts); - if (num_mxhosts < 1) { - *status = 5; - snprintf(dsn, SIZ, "No MX hosts found for <%s>", node); - return; - } - sock = (-1); - for (mx=0; (mx 1) { - strcpy (mx_user, buf); - endpart = strrchr(mx_user, '@'); - *endpart = '\0'; - strcpy (mx_host, endpart + 1); - endpart = strrchr(mx_user, ':'); - if (endpart != NULL) { - strcpy(mx_pass, endpart+1); - *endpart = '\0'; - } - } - else - strcpy (mx_host, buf); - endpart = strrchr(mx_host, ':'); - if (endpart != 0){ - *endpart = '\0'; - strcpy(mx_port, endpart + 1); - } - else { - strcpy(mx_port, "25"); - } - syslog(LOG_DEBUG, "SMTP client: connecting to %s : %s ...", mx_host, mx_port); - sock = sock_connect(mx_host, mx_port); - snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno)); - if (sock >= 0) - { - syslog(LOG_DEBUG, "SMTP client: connected!"); - int fdflags; - fdflags = fcntl(sock, F_GETFL); - if (fdflags < 0) { - syslog(LOG_DEBUG, - "unable to get SMTP-Client socket flags! %s", - strerror(errno) - ); - } - fdflags = fdflags | O_NONBLOCK; - if (fcntl(sock, F_SETFL, fdflags) < 0) { - syslog(LOG_DEBUG, - "unable to set SMTP-Client socket nonblocking flags! %s", - strerror(errno) - ); - } - } - if (sock < 0) { - if (errno > 0) { - snprintf(dsn, SIZ, "%s", strerror(errno)); - } - else { - snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port); +/* 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); } } - } - - if (sock < 0) { - *status = 4; /* dsn is already filled in */ - return; - } - CCC->sReadBuf = NewStrBuf(); - CCC->sMigrateBuf = NewStrBuf(); - CCC->sPos = NULL; - - /* Process the SMTP greeting from the server */ - if (ml_sock_gets(&sock, buf, 90) < 0) { - *status = 4; - strcpy(dsn, "Connection broken during SMTP conversation"); - goto bail; - } - syslog(LOG_DEBUG, "<%s", buf); - if (buf[0] != '2') { - if (buf[0] == '4') { - *status = 4; - safestrncpy(dsn, &buf[4], 1023); - goto bail; - } - else { - *status = 5; - safestrncpy(dsn, &buf[4], 1023); - goto bail; - } - } - - /* At this point we know we are talking to a real SMTP server */ - - /* Do a EHLO command. If it fails, try the HELO command. */ - snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn); - syslog(LOG_DEBUG, ">%s", buf); - sock_write(&sock, buf, strlen(buf)); - if (ml_sock_gets(&sock, buf, 30) < 0) { - *status = 4; - strcpy(dsn, "Connection broken during SMTP HELO"); - goto bail; - } - syslog(LOG_DEBUG, "<%s", buf); - if (buf[0] != '2') { - snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn); - syslog(LOG_DEBUG, ">%s", buf); - sock_write(&sock, buf, strlen(buf)); - if (ml_sock_gets(&sock, buf, 30) < 0) { - *status = 4; - strcpy(dsn, "Connection broken during SMTP HELO"); - goto bail; - } - } - if (buf[0] != '2') { - if (buf[0] == '4') { - *status = 4; - safestrncpy(dsn, &buf[4], 1023); - goto bail; - } - else { - *status = 5; - safestrncpy(dsn, &buf[4], 1023); - goto bail; - } - } - - /* Do an AUTH command if necessary */ - if (!IsEmptyStr(mx_user)) { - char encoded[1024]; - sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass); - CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2, 0); - snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded); - syslog(LOG_DEBUG, ">%s", buf); - sock_write(&sock, buf, strlen(buf)); - if (ml_sock_gets(&sock, buf, 30) < 0) { - *status = 4; - strcpy(dsn, "Connection broken during SMTP AUTH"); - goto bail; - } - syslog(LOG_DEBUG, "<%s", buf); - if (buf[0] != '2') { - if (buf[0] == '4') { - *status = 4; - safestrncpy(dsn, &buf[4], 1023); - goto bail; - } - else { - *status = 5; - safestrncpy(dsn, &buf[4], 1023); - goto bail; - } - } + 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; +} - /* previous command succeeded, now try the MAIL FROM: command */ - snprintf(buf, sizeof buf, "MAIL FROM:<%s>\r\n", envelope_from); - syslog(LOG_DEBUG, ">%s", buf); - sock_write(&sock, buf, strlen(buf)); - if (ml_sock_gets(&sock, buf, 30) < 0) { - *status = 4; - strcpy(dsn, "Connection broken during SMTP MAIL"); - goto bail; - } - syslog(LOG_DEBUG, "<%s", buf); - if (buf[0] != '2') { - if (buf[0] == '4') { - *status = 4; - safestrncpy(dsn, &buf[4], 1023); - goto bail; - } - else { - *status = 5; - safestrncpy(dsn, &buf[4], 1023); - goto bail; - } - } - /* MAIL succeeded, now try the RCPT To: command */ - snprintf(buf, sizeof buf, "RCPT TO:<%s@%s>\r\n", user, node); - syslog(LOG_DEBUG, ">%s", buf); - sock_write(&sock, buf, strlen(buf)); - if (ml_sock_gets(&sock, buf, 30) < 0) { - *status = 4; - strcpy(dsn, "Connection broken during SMTP RCPT"); - goto bail; - } - syslog(LOG_DEBUG, "<%s", buf); - if (buf[0] != '2') { - if (buf[0] == '4') { - *status = 4; - safestrncpy(dsn, &buf[4], 1023); - goto bail; - } - else { - *status = 5; - safestrncpy(dsn, &buf[4], 1023); - goto bail; - } - } +/* + * 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; - /* RCPT succeeded, now try the DATA command */ - syslog(LOG_DEBUG, ">DATA"); - sock_write(&sock, "DATA\r\n", 6); - if (ml_sock_gets(&sock, buf, 30) < 0) { - *status = 4; - strcpy(dsn, "Connection broken during SMTP DATA"); - goto bail; - } - syslog(LOG_DEBUG, "<%s", buf); - if (buf[0] != '3') { - if (buf[0] == '4') { - *status = 3; - safestrncpy(dsn, &buf[4], 1023); - goto bail; - } - else { - *status = 5; - safestrncpy(dsn, &buf[4], 1023); - goto bail; - } - } + sendbytes = (size * nmemb); - /* If we reach this point, the server is expecting data.*/ - sock_write_timeout(&sock, - msgtext, - msg_size, - (msg_size / 128) + 50); - if (msgtext[msg_size-1] != 10) { - syslog(LOG_WARNING, "Possible problem: message did not " - "correctly terminate. (expecting 0x10, got 0x%02x)", - buf[msg_size-1]); - sock_write(&sock, "\r\n", 2); + if (s->bytes_sent >= s->bytes_total) { + return(0); // we are donez0r } - sock_write(&sock, ".\r\n", 3); - tcdrain(sock); - if (ml_sock_gets(&sock, buf, 90) < 0) { - *status = 4; - strcpy(dsn, "Connection broken during SMTP message transmit"); - goto bail; - } - syslog(LOG_DEBUG, "%s", buf); - if (buf[0] != '2') { - if (buf[0] == '4') { - *status = 4; - safestrncpy(dsn, &buf[4], 1023); - goto bail; - } - else { - *status = 5; - safestrncpy(dsn, &buf[4], 1023); - goto bail; - } + if (sendbytes > (s->bytes_total - s->bytes_sent)) { + sendbytes = s->bytes_total - s->bytes_sent; // can't send more than we have } - /* We did it! */ - safestrncpy(dsn, &buf[4], 1023); - *status = 2; - - syslog(LOG_DEBUG, ">QUIT"); - sock_write(&sock, "QUIT\r\n", 6); - ml_sock_gets(&sock, buf, 30); - syslog(LOG_DEBUG, "<%s", buf); - syslog(LOG_INFO, "SMTP client: delivery to <%s> @ <%s> (%s) succeeded", user, node, name); - -bail: free(msgtext); - FreeStrBuf(&CCC->sReadBuf); - FreeStrBuf(&CCC->sMigrateBuf); - if (sock != -1) { - sock_close(sock); - } + send_this = ChrPtr(s->TheMessage); + send_this += s->bytes_sent; // start where we last left off - /* Standard practice is to write DSN to LOG_MAIL which may or may not be where the - * rest of the Citadel logs are going. - */ - syslog((LOG_MAIL | LOG_INFO), "%ld: to=<%s>, relay=%s, stat=%s", msgnum, addr, mx_host, dsn); - return; + memcpy(ptr, send_this, sendbytes); + s->bytes_sent += sendbytes; + return(sendbytes); // return the number of bytes _actually_ copied } - /* - * 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). + * Attempt a delivery to one recipient. + * Returns a three-digit SMTP status code. */ -void smtp_do_bounce(char *instr) { +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]; + char smtp_url[512]; int i; - int lines; - int status; - char buf[1024]; - char key[1024]; - char addr[1024]; - char dsn[1024]; - char bounceto[1024]; - StrBuf *boundary; - int num_bounces = 0; - int bounce_this = 0; - long bounce_msgid = (-1); - time_t submitted = 0L; - struct CtdlMessage *bmsg = NULL; - int give_up = 0; - struct recptypes *valid; - int successful_bounce = 0; - static int seq = 0; - StrBuf *BounceMB; - long omsgid = (-1); - - syslog(LOG_DEBUG, "smtp_do_bounce() called"); - strcpy(bounceto, ""); - boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_")); - StrBufAppendPrintf(boundary, "%s_%04x%04x", config.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 SMTP_GIVE_UP ) { - give_up = 1; - } - - /* Start building our bounce message */ - - bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage)); - if (bmsg == NULL) return; - memset(bmsg, 0, sizeof(struct CtdlMessage)); - BounceMB = NewStrBufPlain(NULL, 1024); - - bmsg->cm_magic = CTDLMESSAGE_MAGIC; - bmsg->cm_anon_type = MES_NORMAL; - bmsg->cm_format_type = FMT_RFC822; - bmsg->cm_fields['A'] = strdup("Citadel"); - bmsg->cm_fields['O'] = strdup(MAILROOM); - bmsg->cm_fields['N'] = strdup(config.c_nodename); - bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)"); - 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); - - /* - * Now go through the instructions checking for stuff. - */ - for (i=0; i addr=<%s> status=%d dsn=<%s>", key, addr, status, dsn); - - if (!strcasecmp(key, "bounceto")) { - strcpy(bounceto, addr); - } - if (!strcasecmp(key, "msgid")) { - omsgid = atol(addr); - } + syslog(LOG_DEBUG, "smtpclient: smtp_attempt_delivery(%ld, %s)", msgid, recp); - if (!strcasecmp(key, "remote")) { - if (status == 5) bounce_this = 1; - if (give_up) bounce_this = 1; - } + 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); + } - if (bounce_this) { - ++num_bounces; + 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; - StrBufAppendBufPlain(BounceMB, addr, addrlen, 0); - StrBufAppendBufPlain(BounceMB, HKEY(": "), 0); - StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0); - StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0); + for (i=0; ((i= 0) { - 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("\r\n"), 0); + if (!IsEmptyStr(envelope_from)) { + curl_easy_setopt(curl, CURLOPT_MAIL_FROM, envelope_from); + } + else { + curl_easy_setopt(curl, CURLOPT_MAIL_FROM, fromaddr); + } - CC->redirect_buffer = NewStrBufPlain(NULL, SIZ); - CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0); - StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0); - FreeStrBuf(&CC->redirect_buffer); - } - - /* Close the multipart MIME scope */ - StrBufAppendBufPlain(BounceMB, HKEY("--"), 0); - StrBufAppendBuf(BounceMB, boundary, 0); - StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0); - if (bmsg->cm_fields['A'] != NULL) - free(bmsg->cm_fields['A']); - bmsg->cm_fields['A'] = SmashStrBuf(&BounceMB); - /* Deliver the bounce if there's anything worth mentioning */ - syslog(LOG_DEBUG, "num_bounces = %d", num_bounces); - if (num_bounces > 0) { - - /* First try the user who sent the message */ - syslog(LOG_DEBUG, "bounce to user? <%s>", bounceto); - if (IsEmptyStr(bounceto)) { - syslog(LOG_ERR, "No bounce address specified"); - bounce_msgid = (-1L); - } - - /* Can we deliver the bounce to the original sender? */ - valid = validate_recipients(bounceto, smtp_get_Recipients (), 0); - if (valid != NULL) { - if (valid->num_error == 0) { - CtdlSubmitMsg(bmsg, valid, "", QP_EADDR); - successful_bounce = 1; + 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 + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + // curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_error_buffer); + + // Construct an SMTP URL in the form of: + // smtp[s]://target_host/source_host + // This looks weird but libcurl uses that last part to set our name for EHLO or HELO. + // We check for "smtp://" and "smtps://" because the admin may have put those prefixes in a smart-host entry. + // If there is no prefix we add "smtp://" + extract_token(try_this_mx, mxes, i, '|', (sizeof try_this_mx - 7)); + snprintf(smtp_url, sizeof smtp_url, + "%s%s/%s", + (((!strncasecmp(try_this_mx, HKEY("smtp://"))) || (!strncasecmp(try_this_mx, HKEY("smtps://")))) ? "" : "smtp://"), + try_this_mx, + CtdlGetConfigStr("c_fqdn") + ); + curl_easy_setopt(curl, CURLOPT_URL, smtp_url); + syslog(LOG_DEBUG, "smtpclient: trying %s", smtp_url); // 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; } - } - - /* If not, post it in the Aide> room */ - if (successful_bounce == 0) { - CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR); - } - - /* Free up the memory we used */ - if (valid != NULL) { - free_recipients(valid); - } - } - FreeStrBuf(&boundary); - CtdlFreeMessage(bmsg); - syslog(LOG_DEBUG, "Done processing bounces"); -} - - -/* - * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a - * set of delivery instructions for completed deliveries and remove them. - * - * It returns the number of incomplete deliveries remaining. - */ -int smtp_purge_completed_deliveries(char *instr) { - int i; - int lines; - int status; - char buf[1024]; - char key[1024]; - char addr[1024]; - char dsn[1024]; - int completed; - int incomplete = 0; - - lines = num_tokens(instr, '\n'); - for (i=0; icm_fields['M']); - CtdlFreeMessage(msg); - - /* Strip out the headers amd any other non-instruction line */ - lines = num_tokens(instr, '\n'); - for (i=0; icm_fields[eMesageText]; + msg->cm_fields[eMesageText] = NULL; + CM_Free(msg); - /* Learn the message ID and find out about recent delivery attempts */ - lines = num_tokens(instr, '\n'); - for (i=0; i SMTP_RETRY_MAX) { - retry = SMTP_RETRY_MAX; - } - remove_token(instr, i, '\n'); - } - if (!strcasecmp(key, "attempted")) { - attempted = extract_long(buf, 1); - if (attempted > last_attempted) - last_attempted = attempted; - } + // 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); } - /* - * Postpone delivery if we've already tried recently. - */ - if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) { - syslog(LOG_DEBUG, "SMTP client: Retry time not yet reached."); - free(instr); - return; + // 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; - /* - * Bail out if there's no actual message associated with this - */ - if (text_msgid < 0L) { - syslog(LOG_ERR, "SMTP client: no 'msgid' directive found!"); - free(instr); - return; + char cfgline[SIZ]; + for (i=0; i", addr); - smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid, envelope_from); - if (status != 2) { - if (results == NULL) { - results = malloc(1024); - memset(results, 0, 1024); - } - else { - results = realloc(results, strlen(results) + 1024); + int should_try_now = 0; + 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: %ld attempting delivery now", qmsgnum); + 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 , 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) + ); + } } - snprintf(&results[strlen(results)], 1024, - "%s|%s|%d|%s\n", - key, addr, status, dsn); } } - } - if (results != NULL) { - instr = realloc(instr, strlen(instr) + strlen(results) + 2); - strcat(instr, results); - free(results); - } + 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); - /* Generate 'bounce' messages */ - smtp_do_bounce(instr); + // 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); + } - /* Go through the delivery list, deleting completed deliveries */ - incomplete_deliveries_remaining = - smtp_purge_completed_deliveries(instr); + // 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; + } - /* - * No delivery instructions remain, so delete both the instructions - * message and the message message. - */ - if (incomplete_deliveries_remaining <= 0) { - long delmsgs[2]; - delmsgs[0] = msgnum; - delmsgs[1] = text_msgid; - CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, ""); + // 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: %ld deleting", qmsgnum); + 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 queue entry with the new one + syslog(LOG_DEBUG, "smtpclient: %ld rewriting", qmsgnum); + 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, ""); + } } - - /* - * Uncompleted delivery instructions remain, so delete the old - * instructions and replace with the updated ones. - */ - if (incomplete_deliveries_remaining > 0) { - CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, ""); - 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; - msg->cm_fields['M'] = malloc(strlen(instr)+SIZ); - snprintf(msg->cm_fields['M'], - strlen(instr)+SIZ, - "Content-type: %s\n\n%s\n" - "attempted|%ld\n" - "retry|%ld\n", - SPOOLMIME, instr, (long)time(NULL), (long)retry ); - CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR); - CtdlFreeMessage(msg); + else { + syslog(LOG_DEBUG, "smtpclient: %ld retry time not reached", qmsgnum); } + if (bounceto != NULL) free(bounceto); + if (envelope_from != NULL) free(envelope_from); free(instr); } -/*****************************************************************************/ -/* 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= smtpq_count) { + smtpq_alloc += 100; + smtpq = realloc(smtpq, (smtpq_alloc * sizeof(long))); } + smtpq[smtpq_count++] = msgnum; } /* - * smtp_queue_thread() - * * Run through the queue sending out messages. */ void smtp_do_queue(void) { - static int is_running = 0; - int num_processed = 0; + int i = 0; - if (is_running) return; /* Concurrency check - only one can run */ - is_running = 1; + /* + * This is a simple concurrency check to make sure only one smtpclient + * 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_INFO, "SMTP client: processing outbound queue"); + syslog(LOG_DEBUG, "smtpclient: start queue run"); + pthread_setspecific(MyConKey, (void *)&smtp_client_CC); 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, NULL); + syslog(LOG_WARNING, "Cannot find room <%s>", SMTP_SPOOLOUT_ROOM); + doing_smtpclient = 0; + return; } - syslog(LOG_INFO, "SMTP client: queue run completed; %d messages processed", num_processed); - is_running = 0; -} + // Put the queue in memory so we can close the db cursor + CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_add_msg, NULL); -/* - * 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_MAILBOX); - - /* - * Make sure it's set to be a "system room" so it doesn't show up - * in the nown rooms list for Aides. - */ - if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) { - qrbuf.QRflags2 |= QR2_SYSTEM; - CtdlPutRoomLock(&qrbuf); + // We are ready to run through the queue now. + for (i=0; i