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"
90 #ifndef EXPERIMENTAL_SMTP_EVENT_CLIENT
92 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
94 /*****************************************************************************/
95 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
96 /*****************************************************************************/
103 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
106 void smtp_try(const char *key, const char *addr, int *status,
107 char *dsn, size_t n, long msgnum, char *envelope_from
114 char user[1024], node[1024], name[1024];
128 /* Parse out the host portion of the recipient address */
129 process_rfc822_addr(addr, user, node, name);
131 syslog(LOG_DEBUG, "SMTP client: Attempting delivery to <%s> @ <%s> (%s)", user, node, name);
133 /* Load the message out of the database */
134 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
135 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
136 msg_size = StrLength(CCC->redirect_buffer);
137 msgtext = SmashStrBuf(&CCC->redirect_buffer);
139 /* This check is being added on 2011apr02 as we are experiencing a problem where the msg_size
140 * is being reported as zero for every delivery after about 12h of server runtime; this seems
141 * to indicate a heap corruption of some sort? Deferring will allow the message to be delivered
142 * after a server restart instead of discarded. After we fix the real problem, this will still
143 * be a good sanity check to still have in place. --ajc
146 syslog(LOG_ALERT, "msg_size is zero -- possible data corruption");
148 strcpy(dsn, "Internal server error prevented successful delivery -- deferring");
152 /* If no envelope_from is supplied, extract one from the message */
153 if ( (envelope_from == NULL) || (IsEmptyStr(envelope_from)) ) {
154 strcpy(mailfrom, "");
158 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
161 if (!strncasecmp(buf, "From:", 5)) {
162 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
164 for (i=0; mailfrom[i]; ++i) {
165 if (!isprint(mailfrom[i])) {
166 strcpy(&mailfrom[i], &mailfrom[i+1]);
171 /* Strip out parenthesized names */
174 for (i=0; mailfrom[i]; ++i) {
175 if (mailfrom[i] == '(') lp = i;
176 if (mailfrom[i] == ')') rp = i;
178 if ((lp>0)&&(rp>lp)) {
179 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
182 /* Prefer brokketized names */
185 for (i=0; mailfrom[i]; ++i) {
186 if (mailfrom[i] == '<') lp = i;
187 if (mailfrom[i] == '>') rp = i;
189 if ( (lp>=0) && (rp>lp) ) {
191 strcpy(mailfrom, &mailfrom[lp + 1]);
196 } while (scan_done == 0);
197 if (IsEmptyStr(mailfrom)) {
198 syslog(LOG_DEBUG, "This message has no From: header. Hmm...");
200 stripallbut(mailfrom, '<', '>');
201 envelope_from = mailfrom;
204 /* Figure out what mail exchanger host we have to connect to */
205 num_mxhosts = getmx(mxhosts, node);
206 syslog(LOG_DEBUG, "Number of MX hosts for <%s> is %d [%s]", node, num_mxhosts, mxhosts);
207 if (num_mxhosts < 1) {
209 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
214 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
216 extract_token(buf, mxhosts, mx, '|', sizeof buf);
219 if (num_tokens(buf, '@') > 1) {
220 strcpy (mx_user, buf);
221 endpart = strrchr(mx_user, '@');
223 strcpy (mx_host, endpart + 1);
224 endpart = strrchr(mx_user, ':');
225 if (endpart != NULL) {
226 strcpy(mx_pass, endpart+1);
231 strcpy (mx_host, buf);
232 endpart = strrchr(mx_host, ':');
235 strcpy(mx_port, endpart + 1);
238 strcpy(mx_port, "25");
240 syslog(LOG_DEBUG, "SMTP client: connecting to %s : %s ...", mx_host, mx_port);
241 sock = sock_connect(mx_host, mx_port);
242 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
245 syslog(LOG_DEBUG, "SMTP client: connected!");
247 fdflags = fcntl(sock, F_GETFL);
250 "unable to get SMTP-Client socket flags! %s",
254 fdflags = fdflags | O_NONBLOCK;
255 if (fcntl(sock, F_SETFL, fdflags) < 0) {
257 "unable to set SMTP-Client socket nonblocking flags! %s",
264 snprintf(dsn, SIZ, "%s", strerror(errno));
267 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
273 *status = 4; /* dsn is already filled in */
277 CCC->sReadBuf = NewStrBuf();
278 CCC->sMigrateBuf = NewStrBuf();
281 /* Process the SMTP greeting from the server */
282 if (ml_sock_gets(&sock, buf, 90) < 0) {
284 strcpy(dsn, "Connection broken during SMTP conversation");
287 syslog(LOG_DEBUG, "<%s", buf);
291 safestrncpy(dsn, &buf[4], 1023);
296 safestrncpy(dsn, &buf[4], 1023);
301 /* At this point we know we are talking to a real SMTP server */
303 /* Do a EHLO command. If it fails, try the HELO command. */
304 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
305 syslog(LOG_DEBUG, ">%s", buf);
306 sock_write(&sock, buf, strlen(buf));
307 if (ml_sock_gets(&sock, buf, 30) < 0) {
309 strcpy(dsn, "Connection broken during SMTP HELO");
312 syslog(LOG_DEBUG, "<%s", buf);
314 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
315 syslog(LOG_DEBUG, ">%s", buf);
316 sock_write(&sock, buf, strlen(buf));
317 if (ml_sock_gets(&sock, buf, 30) < 0) {
319 strcpy(dsn, "Connection broken during SMTP HELO");
326 safestrncpy(dsn, &buf[4], 1023);
331 safestrncpy(dsn, &buf[4], 1023);
336 /* Do an AUTH command if necessary */
337 if (!IsEmptyStr(mx_user)) {
339 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
340 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2, 0);
341 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
342 syslog(LOG_DEBUG, ">%s", buf);
343 sock_write(&sock, buf, strlen(buf));
344 if (ml_sock_gets(&sock, buf, 30) < 0) {
346 strcpy(dsn, "Connection broken during SMTP AUTH");
349 syslog(LOG_DEBUG, "<%s", buf);
353 safestrncpy(dsn, &buf[4], 1023);
358 safestrncpy(dsn, &buf[4], 1023);
364 /* previous command succeeded, now try the MAIL FROM: command */
365 snprintf(buf, sizeof buf, "MAIL FROM:<%s>\r\n", envelope_from);
366 syslog(LOG_DEBUG, ">%s", buf);
367 sock_write(&sock, buf, strlen(buf));
368 if (ml_sock_gets(&sock, buf, 30) < 0) {
370 strcpy(dsn, "Connection broken during SMTP MAIL");
373 syslog(LOG_DEBUG, "<%s", buf);
377 safestrncpy(dsn, &buf[4], 1023);
382 safestrncpy(dsn, &buf[4], 1023);
387 /* MAIL succeeded, now try the RCPT To: command */
388 snprintf(buf, sizeof buf, "RCPT TO:<%s@%s>\r\n", user, node);
389 syslog(LOG_DEBUG, ">%s", buf);
390 sock_write(&sock, buf, strlen(buf));
391 if (ml_sock_gets(&sock, buf, 30) < 0) {
393 strcpy(dsn, "Connection broken during SMTP RCPT");
396 syslog(LOG_DEBUG, "<%s", buf);
400 safestrncpy(dsn, &buf[4], 1023);
405 safestrncpy(dsn, &buf[4], 1023);
410 /* RCPT succeeded, now try the DATA command */
411 syslog(LOG_DEBUG, ">DATA");
412 sock_write(&sock, "DATA\r\n", 6);
413 if (ml_sock_gets(&sock, buf, 30) < 0) {
415 strcpy(dsn, "Connection broken during SMTP DATA");
418 syslog(LOG_DEBUG, "<%s", buf);
422 safestrncpy(dsn, &buf[4], 1023);
427 safestrncpy(dsn, &buf[4], 1023);
432 /* If we reach this point, the server is expecting data.*/
433 sock_write_timeout(&sock,
436 (msg_size / 128) + 50);
437 if (msgtext[msg_size-1] != 10) {
438 syslog(LOG_WARNING, "Possible problem: message did not "
439 "correctly terminate. (expecting 0x10, got 0x%02x)",
441 sock_write(&sock, "\r\n", 2);
444 sock_write(&sock, ".\r\n", 3);
446 if (ml_sock_gets(&sock, buf, 90) < 0) {
448 strcpy(dsn, "Connection broken during SMTP message transmit");
451 syslog(LOG_DEBUG, "%s", buf);
455 safestrncpy(dsn, &buf[4], 1023);
460 safestrncpy(dsn, &buf[4], 1023);
466 safestrncpy(dsn, &buf[4], 1023);
469 syslog(LOG_DEBUG, ">QUIT");
470 sock_write(&sock, "QUIT\r\n", 6);
471 ml_sock_gets(&sock, buf, 30);
472 syslog(LOG_DEBUG, "<%s", buf);
473 syslog(LOG_INFO, "SMTP client: delivery to <%s> @ <%s> (%s) succeeded", user, node, name);
476 FreeStrBuf(&CCC->sReadBuf);
477 FreeStrBuf(&CCC->sMigrateBuf);
482 /* Standard practice is to write DSN to LOG_MAIL which may or may not be where the
483 * rest of the Citadel logs are going.
485 syslog((LOG_MAIL | LOG_INFO), "%ld: to=<%s>, relay=%s, stat=%s", msgnum, addr, mx_host, dsn);
492 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
493 * instructions for "5" codes (permanent fatal errors) and produce/deliver
494 * a "bounce" message (delivery status notification).
496 void smtp_do_bounce(char *instr) {
508 long bounce_msgid = (-1);
509 time_t submitted = 0L;
510 struct CtdlMessage *bmsg = NULL;
512 struct recptypes *valid;
513 int successful_bounce = 0;
518 syslog(LOG_DEBUG, "smtp_do_bounce() called");
519 strcpy(bounceto, "");
520 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
521 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
522 lines = num_tokens(instr, '\n');
524 /* See if it's time to give up on delivery of this message */
525 for (i=0; i<lines; ++i) {
526 extract_token(buf, instr, i, '\n', sizeof buf);
527 extract_token(key, buf, 0, '|', sizeof key);
528 extract_token(addr, buf, 1, '|', sizeof addr);
529 if (!strcasecmp(key, "submitted")) {
530 submitted = atol(addr);
534 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
538 /* Start building our bounce message */
540 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
541 if (bmsg == NULL) return;
542 memset(bmsg, 0, sizeof(struct CtdlMessage));
543 BounceMB = NewStrBufPlain(NULL, 1024);
545 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
546 bmsg->cm_anon_type = MES_NORMAL;
547 bmsg->cm_format_type = FMT_RFC822;
548 bmsg->cm_fields['A'] = strdup("Citadel");
549 bmsg->cm_fields['O'] = strdup(MAILROOM);
550 bmsg->cm_fields['N'] = strdup(config.c_nodename);
551 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
552 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
553 StrBufAppendBuf(BounceMB, boundary, 0);
554 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
555 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
556 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
557 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
558 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
559 StrBufAppendBuf(BounceMB, boundary, 0);
560 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
561 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
563 if (give_up) StrBufAppendBufPlain(BounceMB, HKEY(
564 "A message you sent could not be delivered to some or all of its recipients\n"
565 "due to prolonged unavailability of its destination(s).\n"
566 "Giving up on the following addresses:\n\n"
569 else StrBufAppendBufPlain(BounceMB, HKEY(
570 "A message you sent could not be delivered to some or all of its recipients.\n"
571 "The following addresses were undeliverable:\n\n"
575 * Now go through the instructions checking for stuff.
577 for (i=0; i<lines; ++i) {
580 extract_token(buf, instr, i, '\n', sizeof buf);
581 extract_token(key, buf, 0, '|', sizeof key);
582 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
583 status = extract_int(buf, 2);
584 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
587 syslog(LOG_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>", key, addr, status, dsn);
589 if (!strcasecmp(key, "bounceto")) {
590 strcpy(bounceto, addr);
593 if (!strcasecmp(key, "msgid")) {
597 if (!strcasecmp(key, "remote")) {
598 if (status == 5) bounce_this = 1;
599 if (give_up) bounce_this = 1;
605 StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
606 StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
607 StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
608 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
610 remove_token(instr, i, '\n');
616 /* Attach the original message */
618 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
619 StrBufAppendBuf(BounceMB, boundary, 0);
620 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
621 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
622 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
623 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
624 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
626 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
627 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0);
628 StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
629 FreeStrBuf(&CC->redirect_buffer);
632 /* Close the multipart MIME scope */
633 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
634 StrBufAppendBuf(BounceMB, boundary, 0);
635 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
636 if (bmsg->cm_fields['A'] != NULL)
637 free(bmsg->cm_fields['A']);
638 bmsg->cm_fields['A'] = SmashStrBuf(&BounceMB);
639 /* Deliver the bounce if there's anything worth mentioning */
640 syslog(LOG_DEBUG, "num_bounces = %d", num_bounces);
641 if (num_bounces > 0) {
643 /* First try the user who sent the message */
644 syslog(LOG_DEBUG, "bounce to user? <%s>", bounceto);
645 if (IsEmptyStr(bounceto)) {
646 syslog(LOG_ERR, "No bounce address specified");
647 bounce_msgid = (-1L);
650 /* Can we deliver the bounce to the original sender? */
651 valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
653 if (valid->num_error == 0) {
654 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
655 successful_bounce = 1;
659 /* If not, post it in the Aide> room */
660 if (successful_bounce == 0) {
661 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
664 /* Free up the memory we used */
666 free_recipients(valid);
669 FreeStrBuf(&boundary);
670 CtdlFreeMessage(bmsg);
671 syslog(LOG_DEBUG, "Done processing bounces");
676 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
677 * set of delivery instructions for completed deliveries and remove them.
679 * It returns the number of incomplete deliveries remaining.
681 int smtp_purge_completed_deliveries(char *instr) {
692 lines = num_tokens(instr, '\n');
693 for (i=0; i<lines; ++i) {
694 extract_token(buf, instr, i, '\n', sizeof buf);
695 extract_token(key, buf, 0, '|', sizeof key);
696 extract_token(addr, buf, 1, '|', sizeof addr);
697 status = extract_int(buf, 2);
698 extract_token(dsn, buf, 3, '|', sizeof dsn);
702 if (!strcasecmp(key, "remote")) {
703 if (status == 2) completed = 1;
708 remove_token(instr, i, '\n');
721 * Called by smtp_do_queue() to handle an individual message.
723 void smtp_do_procmsg(long msgnum, void *userdata) {
724 struct CtdlMessage *msg = NULL;
726 char *results = NULL;
734 char envelope_from[1024];
735 long text_msgid = (-1);
736 int incomplete_deliveries_remaining;
737 time_t attempted = 0L;
738 time_t last_attempted = 0L;
739 time_t retry = SMTP_RETRY_INTERVAL;
741 syslog(LOG_DEBUG, "SMTP client: smtp_do_procmsg(%ld)", msgnum);
742 strcpy(envelope_from, "");
744 msg = CtdlFetchMessage(msgnum, 1);
746 syslog(LOG_ERR, "SMTP client: tried %ld but no such message!", msgnum);
750 instr = strdup(msg->cm_fields['M']);
751 CtdlFreeMessage(msg);
753 /* Strip out the headers amd any other non-instruction line */
754 lines = num_tokens(instr, '\n');
755 for (i=0; i<lines; ++i) {
756 extract_token(buf, instr, i, '\n', sizeof buf);
757 if (num_tokens(buf, '|') < 2) {
758 remove_token(instr, i, '\n');
764 /* Learn the message ID and find out about recent delivery attempts */
765 lines = num_tokens(instr, '\n');
766 for (i=0; i<lines; ++i) {
767 extract_token(buf, instr, i, '\n', sizeof buf);
768 extract_token(key, buf, 0, '|', sizeof key);
769 if (!strcasecmp(key, "msgid")) {
770 text_msgid = extract_long(buf, 1);
772 if (!strcasecmp(key, "envelope_from")) {
773 extract_token(envelope_from, buf, 1, '|', sizeof envelope_from);
775 if (!strcasecmp(key, "retry")) {
776 /* double the retry interval after each attempt */
777 retry = extract_long(buf, 1) * 2L;
778 if (retry > SMTP_RETRY_MAX) {
779 retry = SMTP_RETRY_MAX;
781 remove_token(instr, i, '\n');
783 if (!strcasecmp(key, "attempted")) {
784 attempted = extract_long(buf, 1);
785 if (attempted > last_attempted)
786 last_attempted = attempted;
791 * Postpone delivery if we've already tried recently.
793 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
794 syslog(LOG_DEBUG, "SMTP client: Retry time not yet reached.");
801 * Bail out if there's no actual message associated with this
803 if (text_msgid < 0L) {
804 syslog(LOG_ERR, "SMTP client: no 'msgid' directive found!");
809 /* Plow through the instructions looking for 'remote' directives and
810 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
811 * were experienced and it's time to try again)
813 lines = num_tokens(instr, '\n');
814 for (i=0; i<lines; ++i) {
815 extract_token(buf, instr, i, '\n', sizeof buf);
816 extract_token(key, buf, 0, '|', sizeof key);
817 extract_token(addr, buf, 1, '|', sizeof addr);
818 status = extract_int(buf, 2);
819 extract_token(dsn, buf, 3, '|', sizeof dsn);
820 if ( (!strcasecmp(key, "remote"))
821 && ((status==0)||(status==3)||(status==4)) ) {
823 /* Remove this "remote" instruction from the set,
824 * but replace the set's final newline if
825 * remove_token() stripped it. It has to be there.
827 remove_token(instr, i, '\n');
828 if (instr[strlen(instr)-1] != '\n') {
834 syslog(LOG_DEBUG, "SMTP client: Trying <%s>", addr);
835 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid, envelope_from);
837 if (results == NULL) {
838 results = malloc(1024);
839 memset(results, 0, 1024);
842 results = realloc(results, strlen(results) + 1024);
844 snprintf(&results[strlen(results)], 1024,
846 key, addr, status, dsn);
851 if (results != NULL) {
852 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
853 strcat(instr, results);
858 /* Generate 'bounce' messages */
859 smtp_do_bounce(instr);
861 /* Go through the delivery list, deleting completed deliveries */
862 incomplete_deliveries_remaining =
863 smtp_purge_completed_deliveries(instr);
867 * No delivery instructions remain, so delete both the instructions
868 * message and the message message.
870 if (incomplete_deliveries_remaining <= 0) {
873 delmsgs[1] = text_msgid;
874 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
878 * Uncompleted delivery instructions remain, so delete the old
879 * instructions and replace with the updated ones.
881 if (incomplete_deliveries_remaining > 0) {
882 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
883 msg = malloc(sizeof(struct CtdlMessage));
884 memset(msg, 0, sizeof(struct CtdlMessage));
885 msg->cm_magic = CTDLMESSAGE_MAGIC;
886 msg->cm_anon_type = MES_NORMAL;
887 msg->cm_format_type = FMT_RFC822;
888 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
889 snprintf(msg->cm_fields['M'],
891 "Content-type: %s\n\n%s\n"
894 SPOOLMIME, instr, (long)time(NULL), (long)retry );
895 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
896 CtdlFreeMessage(msg);
903 /*****************************************************************************/
904 /* SMTP UTILITY COMMANDS */
905 /*****************************************************************************/
907 void cmd_smtp(char *argbuf) {
914 if (CtdlAccessCheck(ac_aide)) return;
916 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
918 if (!strcasecmp(cmd, "mx")) {
919 extract_token(node, argbuf, 1, '|', sizeof node);
920 num_mxhosts = getmx(buf, node);
921 cprintf("%d %d MX hosts listed for %s\n",
922 LISTING_FOLLOWS, num_mxhosts, node);
923 for (i=0; i<num_mxhosts; ++i) {
924 extract_token(node, buf, i, '|', sizeof node);
925 cprintf("%s\n", node);
931 else if (!strcasecmp(cmd, "runqueue")) {
933 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
938 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
945 * smtp_queue_thread()
947 * Run through the queue sending out messages.
949 void smtp_do_queue(void) {
950 static int is_running = 0;
951 int num_processed = 0;
953 if (is_running) return; /* Concurrency check - only one can run */
956 syslog(LOG_INFO, "SMTP client: processing outbound queue");
958 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
959 syslog(LOG_ERR, "Cannot find room <%s>", SMTP_SPOOLOUT_ROOM);
962 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
964 syslog(LOG_INFO, "SMTP client: queue run completed; %d messages processed", num_processed);
971 * Initialize the SMTP outbound queue
973 void smtp_init_spoolout(void) {
974 struct ctdlroom qrbuf;
977 * Create the room. This will silently fail if the room already
978 * exists, and that's perfectly ok, because we want it to exist.
980 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
983 * Make sure it's set to be a "system room" so it doesn't show up
984 * in the <K>nown rooms list for Aides.
986 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
987 qrbuf.QRflags2 |= QR2_SYSTEM;
988 CtdlPutRoomLock(&qrbuf);
995 CTDL_MODULE_INIT(smtp_client)
997 #ifndef EXPERIMENTAL_SMTP_EVENT_CLIENT
1000 smtp_init_spoolout();
1001 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1002 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1006 /* return our Subversion id for the Log */
1007 return "smtpclient";