2 * This module is an SMTP and ESMTP implementation for the Citadel system.
3 * It is compliant with all of the following:
5 * RFC 821 - Simple Mail Transfer Protocol
6 * RFC 876 - Survey of SMTP Implementations
7 * RFC 1047 - Duplicate messages and SMTP
8 * RFC 1652 - 8 bit MIME
9 * RFC 1869 - Extended Simple Mail Transfer Protocol
10 * RFC 1870 - SMTP Service Extension for Message Size Declaration
11 * RFC 2033 - Local Mail Transfer Protocol
12 * RFC 2197 - SMTP Service Extension for Command Pipelining
13 * RFC 2476 - Message Submission
14 * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
15 * RFC 2554 - SMTP Service Extension for Authentication
16 * RFC 2821 - Simple Mail Transfer Protocol
17 * RFC 2822 - Internet Message Format
18 * RFC 2920 - SMTP Service Extension for Command Pipelining
20 * The VRFY and EXPN commands have been removed from this implementation
21 * because nobody uses these commands anymore, except for spammers.
23 * Copyright (c) 1998-2011 by the citadel.org team
25 * This program is open source software; you can redistribute it and/or modify
26 * it under the terms of the GNU General Public License as published by
27 * the Free Software Foundation; either version 3 of the License, or
28 * (at your option) any later version.
30 * This program is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 * GNU General Public License for more details.
35 * You should have received a copy of the GNU General Public License
36 * along with this program; if not, write to the Free Software
37 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
49 #include <sys/types.h>
52 #if TIME_WITH_SYS_TIME
53 # include <sys/time.h>
57 # include <sys/time.h>
67 #include <sys/socket.h>
68 #include <netinet/in.h>
69 #include <arpa/inet.h>
70 #include <libcitadel.h>
73 #include "citserver.h"
80 #include "internet_addressing.h"
83 #include "clientsocket.h"
84 #include "locate_host.h"
85 #include "citadel_dirs.h"
87 #include "ctdl_module.h"
89 #include "smtp_util.h"
91 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
93 /*****************************************************************************/
94 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
95 /*****************************************************************************/
102 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
105 void smtp_try(const char *key, const char *addr, int *status,
106 char *dsn, size_t n, long msgnum, char *envelope_from
113 char user[1024], node[1024], name[1024];
127 /* Parse out the host portion of the recipient address */
128 process_rfc822_addr(addr, user, node, name);
130 syslog(LOG_DEBUG, "SMTP client: Attempting delivery to <%s> @ <%s> (%s)", user, node, name);
132 /* Load the message out of the database */
133 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
134 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
135 msg_size = StrLength(CCC->redirect_buffer);
136 msgtext = SmashStrBuf(&CCC->redirect_buffer);
138 /* This check is being added on 2011apr02 as we are experiencing a problem where the msg_size
139 * is being reported as zero for every delivery after about 12h of server runtime; this seems
140 * to indicate a heap corruption of some sort? Deferring will allow the message to be delivered
141 * after a server restart instead of discarded. After we fix the real problem, this will still
142 * be a good sanity check to still have in place. --ajc
145 syslog(LOG_ALERT, "msg_size is zero -- possible data corruption");
147 strcpy(dsn, "Internal server error prevented successful delivery -- deferring");
151 /* If no envelope_from is supplied, extract one from the message */
152 if ( (envelope_from == NULL) || (IsEmptyStr(envelope_from)) ) {
153 strcpy(mailfrom, "");
157 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
160 if (!strncasecmp(buf, "From:", 5)) {
161 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
163 for (i=0; mailfrom[i]; ++i) {
164 if (!isprint(mailfrom[i])) {
165 strcpy(&mailfrom[i], &mailfrom[i+1]);
170 /* Strip out parenthesized names */
173 for (i=0; mailfrom[i]; ++i) {
174 if (mailfrom[i] == '(') lp = i;
175 if (mailfrom[i] == ')') rp = i;
177 if ((lp>0)&&(rp>lp)) {
178 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
181 /* Prefer brokketized names */
184 for (i=0; mailfrom[i]; ++i) {
185 if (mailfrom[i] == '<') lp = i;
186 if (mailfrom[i] == '>') rp = i;
188 if ( (lp>=0) && (rp>lp) ) {
190 strcpy(mailfrom, &mailfrom[lp + 1]);
195 } while (scan_done == 0);
196 if (IsEmptyStr(mailfrom)) {
197 syslog(LOG_DEBUG, "This message has no From: header. Hmm...");
199 stripallbut(mailfrom, '<', '>');
200 envelope_from = mailfrom;
203 /* Figure out what mail exchanger host we have to connect to */
204 num_mxhosts = getmx(mxhosts, node);
205 syslog(LOG_DEBUG, "Number of MX hosts for <%s> is %d [%s]", node, num_mxhosts, mxhosts);
206 if (num_mxhosts < 1) {
208 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
213 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
215 extract_token(buf, mxhosts, mx, '|', sizeof buf);
218 if (num_tokens(buf, '@') > 1) {
219 strcpy (mx_user, buf);
220 endpart = strrchr(mx_user, '@');
222 strcpy (mx_host, endpart + 1);
223 endpart = strrchr(mx_user, ':');
224 if (endpart != NULL) {
225 strcpy(mx_pass, endpart+1);
230 strcpy (mx_host, buf);
231 endpart = strrchr(mx_host, ':');
234 strcpy(mx_port, endpart + 1);
237 strcpy(mx_port, "25");
239 syslog(LOG_DEBUG, "SMTP client: connecting to %s : %s ...", mx_host, mx_port);
240 sock = sock_connect(mx_host, mx_port);
241 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
244 syslog(LOG_DEBUG, "SMTP client: connected!");
246 fdflags = fcntl(sock, F_GETFL);
249 "unable to get SMTP-Client socket flags! %s",
253 fdflags = fdflags | O_NONBLOCK;
254 if (fcntl(sock, F_SETFL, fdflags) < 0) {
256 "unable to set SMTP-Client socket nonblocking flags! %s",
263 snprintf(dsn, SIZ, "%s", strerror(errno));
266 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
272 *status = 4; /* dsn is already filled in */
276 CCC->sReadBuf = NewStrBuf();
277 CCC->sMigrateBuf = NewStrBuf();
280 /* Process the SMTP greeting from the server */
281 if (ml_sock_gets(&sock, buf, 90) < 0) {
283 strcpy(dsn, "Connection broken during SMTP conversation");
286 syslog(LOG_DEBUG, "<%s", buf);
290 safestrncpy(dsn, &buf[4], 1023);
295 safestrncpy(dsn, &buf[4], 1023);
300 /* At this point we know we are talking to a real SMTP server */
302 /* Do a EHLO command. If it fails, try the HELO command. */
303 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
304 syslog(LOG_DEBUG, ">%s", buf);
305 sock_write(&sock, buf, strlen(buf));
306 if (ml_sock_gets(&sock, buf, 30) < 0) {
308 strcpy(dsn, "Connection broken during SMTP HELO");
311 syslog(LOG_DEBUG, "<%s", buf);
313 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
314 syslog(LOG_DEBUG, ">%s", buf);
315 sock_write(&sock, buf, strlen(buf));
316 if (ml_sock_gets(&sock, buf, 30) < 0) {
318 strcpy(dsn, "Connection broken during SMTP HELO");
325 safestrncpy(dsn, &buf[4], 1023);
330 safestrncpy(dsn, &buf[4], 1023);
335 /* Do an AUTH command if necessary */
336 if (!IsEmptyStr(mx_user)) {
338 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
339 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2, 0);
340 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
341 syslog(LOG_DEBUG, ">%s", buf);
342 sock_write(&sock, buf, strlen(buf));
343 if (ml_sock_gets(&sock, buf, 30) < 0) {
345 strcpy(dsn, "Connection broken during SMTP AUTH");
348 syslog(LOG_DEBUG, "<%s", buf);
352 safestrncpy(dsn, &buf[4], 1023);
357 safestrncpy(dsn, &buf[4], 1023);
363 /* previous command succeeded, now try the MAIL FROM: command */
364 snprintf(buf, sizeof buf, "MAIL FROM:<%s>\r\n", envelope_from);
365 syslog(LOG_DEBUG, ">%s", buf);
366 sock_write(&sock, buf, strlen(buf));
367 if (ml_sock_gets(&sock, buf, 30) < 0) {
369 strcpy(dsn, "Connection broken during SMTP MAIL");
372 syslog(LOG_DEBUG, "<%s", buf);
376 safestrncpy(dsn, &buf[4], 1023);
381 safestrncpy(dsn, &buf[4], 1023);
386 /* MAIL succeeded, now try the RCPT To: command */
387 snprintf(buf, sizeof buf, "RCPT TO:<%s@%s>\r\n", user, node);
388 syslog(LOG_DEBUG, ">%s", buf);
389 sock_write(&sock, buf, strlen(buf));
390 if (ml_sock_gets(&sock, buf, 30) < 0) {
392 strcpy(dsn, "Connection broken during SMTP RCPT");
395 syslog(LOG_DEBUG, "<%s", buf);
399 safestrncpy(dsn, &buf[4], 1023);
404 safestrncpy(dsn, &buf[4], 1023);
409 /* RCPT succeeded, now try the DATA command */
410 syslog(LOG_DEBUG, ">DATA");
411 sock_write(&sock, "DATA\r\n", 6);
412 if (ml_sock_gets(&sock, buf, 30) < 0) {
414 strcpy(dsn, "Connection broken during SMTP DATA");
417 syslog(LOG_DEBUG, "<%s", buf);
421 safestrncpy(dsn, &buf[4], 1023);
426 safestrncpy(dsn, &buf[4], 1023);
431 /* If we reach this point, the server is expecting data.*/
432 sock_write_timeout(&sock,
435 (msg_size / 128) + 50);
436 if (msgtext[msg_size-1] != 10) {
437 syslog(LOG_WARNING, "Possible problem: message did not "
438 "correctly terminate. (expecting 0x10, got 0x%02x)",
440 sock_write(&sock, "\r\n", 2);
443 sock_write(&sock, ".\r\n", 3);
445 if (ml_sock_gets(&sock, buf, 90) < 0) {
447 strcpy(dsn, "Connection broken during SMTP message transmit");
450 syslog(LOG_DEBUG, "%s", buf);
454 safestrncpy(dsn, &buf[4], 1023);
459 safestrncpy(dsn, &buf[4], 1023);
465 safestrncpy(dsn, &buf[4], 1023);
468 syslog(LOG_DEBUG, ">QUIT");
469 sock_write(&sock, "QUIT\r\n", 6);
470 ml_sock_gets(&sock, buf, 30);
471 syslog(LOG_DEBUG, "<%s", buf);
472 syslog(LOG_INFO, "SMTP client: delivery to <%s> @ <%s> (%s) succeeded", user, node, name);
475 FreeStrBuf(&CCC->sReadBuf);
476 FreeStrBuf(&CCC->sMigrateBuf);
481 /* Standard practice is to write DSN to LOG_MAIL which may or may not be where the
482 * rest of the Citadel logs are going.
484 syslog((LOG_MAIL | LOG_INFO), "%ld: to=<%s>, relay=%s, stat=%s", msgnum, addr, mx_host, dsn);
491 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
492 * instructions for "5" codes (permanent fatal errors) and produce/deliver
493 * a "bounce" message (delivery status notification).
495 void smtp_do_bounce(char *instr) {
507 long bounce_msgid = (-1);
508 time_t submitted = 0L;
509 struct CtdlMessage *bmsg = NULL;
511 struct recptypes *valid;
512 int successful_bounce = 0;
517 syslog(LOG_DEBUG, "smtp_do_bounce() called");
518 strcpy(bounceto, "");
519 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
520 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
521 lines = num_tokens(instr, '\n');
523 /* See if it's time to give up on delivery of this message */
524 for (i=0; i<lines; ++i) {
525 extract_token(buf, instr, i, '\n', sizeof buf);
526 extract_token(key, buf, 0, '|', sizeof key);
527 extract_token(addr, buf, 1, '|', sizeof addr);
528 if (!strcasecmp(key, "submitted")) {
529 submitted = atol(addr);
533 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
537 /* Start building our bounce message */
539 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
540 if (bmsg == NULL) return;
541 memset(bmsg, 0, sizeof(struct CtdlMessage));
542 BounceMB = NewStrBufPlain(NULL, 1024);
544 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
545 bmsg->cm_anon_type = MES_NORMAL;
546 bmsg->cm_format_type = FMT_RFC822;
547 bmsg->cm_fields['A'] = strdup("Citadel");
548 bmsg->cm_fields['O'] = strdup(MAILROOM);
549 bmsg->cm_fields['N'] = strdup(config.c_nodename);
550 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
551 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
552 StrBufAppendBuf(BounceMB, boundary, 0);
553 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
554 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
555 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
556 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
557 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
558 StrBufAppendBuf(BounceMB, boundary, 0);
559 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
560 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
562 if (give_up) StrBufAppendBufPlain(BounceMB, HKEY(
563 "A message you sent could not be delivered to some or all of its recipients\n"
564 "due to prolonged unavailability of its destination(s).\n"
565 "Giving up on the following addresses:\n\n"
568 else StrBufAppendBufPlain(BounceMB, HKEY(
569 "A message you sent could not be delivered to some or all of its recipients.\n"
570 "The following addresses were undeliverable:\n\n"
574 * Now go through the instructions checking for stuff.
576 for (i=0; i<lines; ++i) {
579 extract_token(buf, instr, i, '\n', sizeof buf);
580 extract_token(key, buf, 0, '|', sizeof key);
581 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
582 status = extract_int(buf, 2);
583 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
586 syslog(LOG_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>", key, addr, status, dsn);
588 if (!strcasecmp(key, "bounceto")) {
589 strcpy(bounceto, addr);
592 if (!strcasecmp(key, "msgid")) {
596 if (!strcasecmp(key, "remote")) {
597 if (status == 5) bounce_this = 1;
598 if (give_up) bounce_this = 1;
604 StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
605 StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
606 StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
607 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
609 remove_token(instr, i, '\n');
615 /* Attach the original message */
617 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
618 StrBufAppendBuf(BounceMB, boundary, 0);
619 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
620 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
621 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
622 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
623 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
625 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
626 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0);
627 StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
628 FreeStrBuf(&CC->redirect_buffer);
631 /* Close the multipart MIME scope */
632 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
633 StrBufAppendBuf(BounceMB, boundary, 0);
634 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
635 if (bmsg->cm_fields['A'] != NULL)
636 free(bmsg->cm_fields['A']);
637 bmsg->cm_fields['A'] = SmashStrBuf(&BounceMB);
638 /* Deliver the bounce if there's anything worth mentioning */
639 syslog(LOG_DEBUG, "num_bounces = %d", num_bounces);
640 if (num_bounces > 0) {
642 /* First try the user who sent the message */
643 syslog(LOG_DEBUG, "bounce to user? <%s>", bounceto);
644 if (IsEmptyStr(bounceto)) {
645 syslog(LOG_ERR, "No bounce address specified");
646 bounce_msgid = (-1L);
649 /* Can we deliver the bounce to the original sender? */
650 valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
652 if (valid->num_error == 0) {
653 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
654 successful_bounce = 1;
658 /* If not, post it in the Aide> room */
659 if (successful_bounce == 0) {
660 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
663 /* Free up the memory we used */
665 free_recipients(valid);
668 FreeStrBuf(&boundary);
669 CtdlFreeMessage(bmsg);
670 syslog(LOG_DEBUG, "Done processing bounces");
675 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
676 * set of delivery instructions for completed deliveries and remove them.
678 * It returns the number of incomplete deliveries remaining.
680 int smtp_purge_completed_deliveries(char *instr) {
691 lines = num_tokens(instr, '\n');
692 for (i=0; i<lines; ++i) {
693 extract_token(buf, instr, i, '\n', sizeof buf);
694 extract_token(key, buf, 0, '|', sizeof key);
695 extract_token(addr, buf, 1, '|', sizeof addr);
696 status = extract_int(buf, 2);
697 extract_token(dsn, buf, 3, '|', sizeof dsn);
701 if (!strcasecmp(key, "remote")) {
702 if (status == 2) completed = 1;
707 remove_token(instr, i, '\n');
720 * Called by smtp_do_queue() to handle an individual message.
722 void smtp_do_procmsg(long msgnum, void *userdata) {
723 struct CtdlMessage *msg = NULL;
725 char *results = NULL;
733 char envelope_from[1024];
734 long text_msgid = (-1);
735 int incomplete_deliveries_remaining;
736 time_t attempted = 0L;
737 time_t last_attempted = 0L;
738 time_t retry = SMTP_RETRY_INTERVAL;
740 syslog(LOG_DEBUG, "SMTP client: smtp_do_procmsg(%ld)", msgnum);
741 strcpy(envelope_from, "");
743 msg = CtdlFetchMessage(msgnum, 1);
745 syslog(LOG_ERR, "SMTP client: tried %ld but no such message!", msgnum);
749 instr = strdup(msg->cm_fields['M']);
750 CtdlFreeMessage(msg);
752 /* Strip out the headers amd any other non-instruction line */
753 lines = num_tokens(instr, '\n');
754 for (i=0; i<lines; ++i) {
755 extract_token(buf, instr, i, '\n', sizeof buf);
756 if (num_tokens(buf, '|') < 2) {
757 remove_token(instr, i, '\n');
763 /* Learn the message ID and find out about recent delivery attempts */
764 lines = num_tokens(instr, '\n');
765 for (i=0; i<lines; ++i) {
766 extract_token(buf, instr, i, '\n', sizeof buf);
767 extract_token(key, buf, 0, '|', sizeof key);
768 if (!strcasecmp(key, "msgid")) {
769 text_msgid = extract_long(buf, 1);
771 if (!strcasecmp(key, "envelope_from")) {
772 extract_token(envelope_from, buf, 1, '|', sizeof envelope_from);
774 if (!strcasecmp(key, "retry")) {
775 /* double the retry interval after each attempt */
776 retry = extract_long(buf, 1) * 2L;
777 if (retry > SMTP_RETRY_MAX) {
778 retry = SMTP_RETRY_MAX;
780 remove_token(instr, i, '\n');
782 if (!strcasecmp(key, "attempted")) {
783 attempted = extract_long(buf, 1);
784 if (attempted > last_attempted)
785 last_attempted = attempted;
790 * Postpone delivery if we've already tried recently.
792 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
793 syslog(LOG_DEBUG, "SMTP client: Retry time not yet reached.");
800 * Bail out if there's no actual message associated with this
802 if (text_msgid < 0L) {
803 syslog(LOG_ERR, "SMTP client: no 'msgid' directive found!");
808 /* Plow through the instructions looking for 'remote' directives and
809 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
810 * were experienced and it's time to try again)
812 lines = num_tokens(instr, '\n');
813 for (i=0; i<lines; ++i) {
814 extract_token(buf, instr, i, '\n', sizeof buf);
815 extract_token(key, buf, 0, '|', sizeof key);
816 extract_token(addr, buf, 1, '|', sizeof addr);
817 status = extract_int(buf, 2);
818 extract_token(dsn, buf, 3, '|', sizeof dsn);
819 if ( (!strcasecmp(key, "remote"))
820 && ((status==0)||(status==3)||(status==4)) ) {
822 /* Remove this "remote" instruction from the set,
823 * but replace the set's final newline if
824 * remove_token() stripped it. It has to be there.
826 remove_token(instr, i, '\n');
827 if (instr[strlen(instr)-1] != '\n') {
833 syslog(LOG_DEBUG, "SMTP client: Trying <%s>", addr);
834 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid, envelope_from);
836 if (results == NULL) {
837 results = malloc(1024);
838 memset(results, 0, 1024);
841 results = realloc(results, strlen(results) + 1024);
843 snprintf(&results[strlen(results)], 1024,
845 key, addr, status, dsn);
850 if (results != NULL) {
851 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
852 strcat(instr, results);
857 /* Generate 'bounce' messages */
858 smtp_do_bounce(instr);
860 /* Go through the delivery list, deleting completed deliveries */
861 incomplete_deliveries_remaining =
862 smtp_purge_completed_deliveries(instr);
866 * No delivery instructions remain, so delete both the instructions
867 * message and the message message.
869 if (incomplete_deliveries_remaining <= 0) {
872 delmsgs[1] = text_msgid;
873 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
877 * Uncompleted delivery instructions remain, so delete the old
878 * instructions and replace with the updated ones.
880 if (incomplete_deliveries_remaining > 0) {
881 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
882 msg = malloc(sizeof(struct CtdlMessage));
883 memset(msg, 0, sizeof(struct CtdlMessage));
884 msg->cm_magic = CTDLMESSAGE_MAGIC;
885 msg->cm_anon_type = MES_NORMAL;
886 msg->cm_format_type = FMT_RFC822;
887 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
888 snprintf(msg->cm_fields['M'],
890 "Content-type: %s\n\n%s\n"
893 SPOOLMIME, instr, (long)time(NULL), (long)retry );
894 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
895 CtdlFreeMessage(msg);
902 /*****************************************************************************/
903 /* SMTP UTILITY COMMANDS */
904 /*****************************************************************************/
906 void cmd_smtp(char *argbuf) {
913 if (CtdlAccessCheck(ac_aide)) return;
915 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
917 if (!strcasecmp(cmd, "mx")) {
918 extract_token(node, argbuf, 1, '|', sizeof node);
919 num_mxhosts = getmx(buf, node);
920 cprintf("%d %d MX hosts listed for %s\n",
921 LISTING_FOLLOWS, num_mxhosts, node);
922 for (i=0; i<num_mxhosts; ++i) {
923 extract_token(node, buf, i, '|', sizeof node);
924 cprintf("%s\n", node);
930 else if (!strcasecmp(cmd, "runqueue")) {
932 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
937 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
944 * smtp_queue_thread()
946 * Run through the queue sending out messages.
948 void smtp_do_queue(void) {
949 static int is_running = 0;
950 int num_processed = 0;
952 if (is_running) return; /* Concurrency check - only one can run */
955 syslog(LOG_INFO, "SMTP client: processing outbound queue");
957 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
958 syslog(LOG_ERR, "Cannot find room <%s>", SMTP_SPOOLOUT_ROOM);
961 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
963 syslog(LOG_INFO, "SMTP client: queue run completed; %d messages processed", num_processed);
970 * Initialize the SMTP outbound queue
972 void smtp_init_spoolout(void) {
973 struct ctdlroom qrbuf;
976 * Create the room. This will silently fail if the room already
977 * exists, and that's perfectly ok, because we want it to exist.
979 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
982 * Make sure it's set to be a "system room" so it doesn't show up
983 * in the <K>nown rooms list for Aides.
985 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
986 qrbuf.QRflags2 |= QR2_SYSTEM;
987 CtdlPutRoomLock(&qrbuf);
994 CTDL_MODULE_INIT(smtp_client)
998 smtp_init_spoolout();
999 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1000 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1003 /* return our Subversion id for the Log */