X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fmodules%2Fsmtp%2Fserv_smtpclient.c;h=8e87ce4c2f4da626569a4842c602db51aaf7af51;hb=51b18018ff923284d76a36cbd421d62abf6afcf4;hp=fcf74e9ee67334a03f1dbfc66be22d722c074209;hpb=da0b049809b9ad1459a4ac0467e79bfb5c5d2719;p=citadel.git diff --git a/citadel/modules/smtp/serv_smtpclient.c b/citadel/modules/smtp/serv_smtpclient.c index fcf74e9ee..8e87ce4c2 100644 --- a/citadel/modules/smtp/serv_smtpclient.c +++ b/citadel/modules/smtp/serv_smtpclient.c @@ -3,7 +3,7 @@ * * This is the new, exciting, clever version that makes libcurl do all the work :) * - * Copyright (c) 1997-2017 by the citadel.org team + * Copyright (c) 1997-2021 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 @@ -20,18 +20,7 @@ #include #include #include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - +#include #include #include #include @@ -58,7 +47,6 @@ struct smtpmsgsrc { // Data passed in and out of libcurl for message upload 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 @@ -79,7 +67,7 @@ void smtp_init_spoolout(void) { /* * Make sure it's set to be a "system room" so it doesn't show up - * in the nown rooms list for Aides. + * in the nown rooms list for administrators. */ if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) { qrbuf.QRflags2 |= QR2_SYSTEM; @@ -93,8 +81,7 @@ void smtp_init_spoolout(void) { * not happen because the delivery instructions message does not * contain a recipient. */ -int smtp_aftersave(struct CtdlMessage *msg, recptypes *recps) -{ +int smtp_aftersave(struct CtdlMessage *msg, struct recptypes *recps) { if ((recps != NULL) && (recps->num_internet > 0)) { struct CtdlMessage *imsg = NULL; char recipient[SIZ]; @@ -105,14 +92,10 @@ int smtp_aftersave(struct CtdlMessage *msg, recptypes *recps) syslog(LOG_DEBUG, "smtpclient: generating delivery instructions"); StrBufPrintf(SpoolMsg, - "Content-type: "SPOOLMIME"\n" + "Content-type: " SPOOLMIME "\n" "\n" "msgid|%s\n" - "submitted|%ld\n" - "bounceto|%s\n", - msg->cm_fields[eVltMsgNum], - (long)time(NULL), - recps->bounce_to); + "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); @@ -126,7 +109,7 @@ int smtp_aftersave(struct CtdlMessage *msg, recptypes *recps) } nTokens = num_tokens(recps->recp_internet, '|'); - for (i = 0; i < nTokens; i++) { + for (i = 0; i < nTokens; i++) { long len; len = extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient); if (len > 0) { @@ -145,7 +128,7 @@ int smtp_aftersave(struct CtdlMessage *msg, recptypes *recps) 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); + CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM); CM_Free(imsg); } return 0; @@ -155,8 +138,7 @@ int smtp_aftersave(struct CtdlMessage *msg, recptypes *recps) /* * 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) -{ +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; @@ -164,7 +146,7 @@ static size_t upload_source(void *ptr, size_t size, size_t nmemb, void *userp) sendbytes = (size * nmemb); if (s->bytes_sent >= s->bytes_total) { - return(0); // we are donez0r + return (0); // no data remaining; we are done } if (sendbytes > (s->bytes_total - s->bytes_sent)) { @@ -172,11 +154,70 @@ static size_t upload_source(void *ptr, size_t size, size_t nmemb, void *userp) } send_this = ChrPtr(s->TheMessage); - send_this += s->bytes_sent; // start where we last left off + 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 + return (sendbytes); // return the number of bytes _actually_ copied +} + + +/* + * The libcurl API doesn't provide a way to capture the actual SMTP result message returned + * by the remote server. This is an ugly way to extract it, by capturing debug data from + * the library and filtering on the lines we want. + */ +int ctdl_libcurl_smtp_debug_callback(CURL *handle, curl_infotype type, char *data, size_t size, void *userptr) { + if (type != CURLINFO_HEADER_IN) + return 0; + if (!userptr) + return 0; + char *debugbuf = (char *) userptr; + + int len = strlen(debugbuf); + if (len + size > SIZ) + return 0; + + memcpy(&debugbuf[len], data, size); + debugbuf[len + size] = 0; + return 0; +} + + +/* + * Go through the debug output of an SMTP transaction, and boil it down to just the final success or error response message. + */ +void trim_response(long response_code, char *response) { + if ((response_code < 100) || (response_code > 999) || (IsEmptyStr(response))) { + return; + } + + char *t = malloc(strlen(response)); + if (!t) { + return; + } + t[0] = 0; + + char *p; + for (p = response; *p != 0; ++p) { + if ( (*p != '\n') && (!isprint(*p)) ) { // expunge any nonprintables except for newlines + *p = ' '; + } + } + + char response_code_str[4]; + snprintf(response_code_str, sizeof response_code_str, "%ld", response_code); + char *respstart = strstr(response, response_code_str); + if (respstart == NULL) { // If we have a response code but no response text, + strcpy(response, smtpstatus(response_code)); // use one of our canned messages. + return; + } + strcpy(response, respstart); + + p = strstr(response, "\n"); + if (p != NULL) { + *p = 0; + } } @@ -184,8 +225,7 @@ static size_t upload_source(void *ptr, size_t size, size_t nmemb, void *userp) * Attempt a delivery to one recipient. * Returns a three-digit SMTP status code. */ -int smtp_attempt_delivery(long msgid, char *recp, char *envelope_from) -{ +int smtp_attempt_delivery(long msgid, char *recp, char *envelope_from, char *response) { struct smtpmsgsrc s; char *fromaddr = NULL; CURL *curl; @@ -198,76 +238,100 @@ int smtp_attempt_delivery(long msgid, char *recp, char *envelope_from) char node[1024]; char name[1024]; char try_this_mx[256]; + char smtp_url[512]; 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 + 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); + 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_total = StrLength(CC->redirect_buffer); s.bytes_sent = 0; CC->redirect_buffer = NULL; response_code = 421; - for (i=0; ((i", i+1, num_mx, 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 - ); + 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 + if ((res != CURLE_OK) && (response_code == 0)) { // check for errors response_code = 421; } - curl_slist_free_all(recipients); - curl_easy_cleanup(curl); + curl_slist_free_all(recipients); + recipients = NULL; // this gets reused; avoid double-free + curl_easy_cleanup(curl); + curl = NULL; // this gets reused; avoid double-free + + /* Trim the error message buffer down to just the actual message */ + trim_response(response_code, response); } } FreeStrBuf(&s.TheMessage); - if (fromaddr) free(fromaddr); - return((int)response_code); + if (fromaddr) + free(fromaddr); + return ((int) response_code); } /* * Process one outbound message. */ -void smtp_process_one_msg(long qmsgnum) -{ +void smtp_process_one_msg(long qmsgnum) { struct CtdlMessage *msg = NULL; char *instr = NULL; int i; @@ -276,12 +340,11 @@ void smtp_process_one_msg(long qmsgnum) int num_delayed = 0; long deletes[2]; int delete_this_queue = 0; + char server_response[SIZ]; - syslog(LOG_DEBUG, "smtpclient: smtp_process_one_msg(%ld)", qmsgnum); - - msg = CtdlFetchMessage(qmsgnum, 1, 1); + msg = CtdlFetchMessage(qmsgnum, 1); if (msg == NULL) { - syslog(LOG_WARNING, "smtpclient: queue message %ld does not exist", qmsgnum); + syslog(LOG_WARNING, "smtpclient: %ld does not exist", qmsgnum); return; } @@ -292,13 +355,13 @@ void smtp_process_one_msg(long qmsgnum) // 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); + 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); + strcpy(instr, soi + 2); } long msgid = 0; @@ -307,74 +370,69 @@ void smtp_process_one_msg(long qmsgnum) 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 1800) { // First four hours, retry every 30 minutes + } 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 + } 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"); - + syslog(LOG_DEBUG, "smtpclient: attempting delivery of message <%ld> now", qmsgnum); StrBuf *NewInstr = NewStrBuf(); - StrBufAppendPrintf(NewInstr, "Content-type: "SPOOLMIME"\n\n"); + 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); + 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)); + new_result = smtp_attempt_delivery(msgid, recp, envelope_from, server_response); + syslog(LOG_DEBUG, + "smtpclient: recp: <%s> , result: %d (%s)", recp, new_result, server_response); if ((new_result / 100) == 2) { ++num_success; - } - else { + } else { if ((new_result / 100) == 5) { ++num_fail; - } - else { + } else { ++num_delayed; } - StrBufAppendPrintf(NewInstr, "remote|%s|%ld|%ld (%s)\n", - recp, (new_result / 100) , new_result, smtpstatus(new_result) - ); + StrBufAppendPrintf + (NewInstr, + "remote|%s|%ld|%ld (%s)\n", + recp, (new_result / 100), new_result, server_response); } } } @@ -385,7 +443,9 @@ void smtp_process_one_msg(long qmsgnum) // 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: 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. @@ -393,50 +453,47 @@ void smtp_process_one_msg(long qmsgnum) 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 + // If it's been more than five days, give up and tell the sender that delivery 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 + // If it's been more than four hours but less than five days, warn the sender that delivery is delayed // - else if ( ((attempted - submitted) < SMTP_DELIVER_WARN) && ((time(NULL) - submitted) >= SMTP_DELIVER_WARN) ) { + 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"); + 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 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); + 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); CM_Free(msg); CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &qmsgnum, 1, ""); } - } - else { - syslog(LOG_DEBUG, "smtpclient: retry time not reached"); + } else { + syslog(LOG_DEBUG, "smtpclient: %ld retry time not reached", qmsgnum); } - if (bounceto != NULL) free(bounceto); - if (envelope_from != NULL) free(envelope_from); + if (bounceto != NULL) + free(bounceto); + if (envelope_from != NULL) + free(envelope_from); free(instr); - } @@ -467,32 +524,31 @@ void smtp_do_queue(void) { int i = 0; /* - * This is a simple concurrency check to make sure only one pop3client + * 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; + 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