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];
129 /* Parse out the host portion of the recipient address */
130 process_rfc822_addr(addr, user, node, name);
132 syslog(LOG_DEBUG, "SMTP client: Attempting delivery to <%s> @ <%s> (%s)\n",
135 /* Load the message out of the database */
136 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
137 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
138 msg_size = StrLength(CC->redirect_buffer);
139 msgtext = SmashStrBuf(&CC->redirect_buffer);
141 /* If no envelope_from is supplied, extract one from the message */
142 if ( (envelope_from == NULL) || (IsEmptyStr(envelope_from)) ) {
143 strcpy(mailfrom, "");
147 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
150 if (!strncasecmp(buf, "From:", 5)) {
151 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
153 for (i=0; mailfrom[i]; ++i) {
154 if (!isprint(mailfrom[i])) {
155 strcpy(&mailfrom[i], &mailfrom[i+1]);
160 /* Strip out parenthesized names */
163 for (i=0; mailfrom[i]; ++i) {
164 if (mailfrom[i] == '(') lp = i;
165 if (mailfrom[i] == ')') rp = i;
167 if ((lp>0)&&(rp>lp)) {
168 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
171 /* Prefer brokketized 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) ) {
180 strcpy(mailfrom, &mailfrom[lp + 1]);
185 } while (scan_done == 0);
186 if (IsEmptyStr(mailfrom)) {
187 char badmail_filename[128];
188 snprintf(badmail_filename, sizeof badmail_filename, "/tmp/badmail.%d.%ld",
191 FILE *badmail_fp = fopen(badmail_filename, "w");
192 fwrite(msgtext, msg_size, 1, badmail_fp);
195 stripallbut(mailfrom, '<', '>');
196 envelope_from = mailfrom;
199 /* Figure out what mail exchanger host we have to connect to */
200 num_mxhosts = getmx(mxhosts, node);
201 syslog(LOG_DEBUG, "Number of MX hosts for <%s> is %d [%s]\n", node, num_mxhosts, mxhosts);
202 if (num_mxhosts < 1) {
204 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
209 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
211 extract_token(buf, mxhosts, mx, '|', sizeof buf);
214 if (num_tokens(buf, '@') > 1) {
215 strcpy (mx_user, buf);
216 endpart = strrchr(mx_user, '@');
218 strcpy (mx_host, endpart + 1);
219 endpart = strrchr(mx_user, ':');
220 if (endpart != NULL) {
221 strcpy(mx_pass, endpart+1);
226 strcpy (mx_host, buf);
227 endpart = strrchr(mx_host, ':');
230 strcpy(mx_port, endpart + 1);
233 strcpy(mx_port, "25");
235 syslog(LOG_DEBUG, "SMTP client: connecting to %s : %s ...\n", mx_host, mx_port);
236 sock = sock_connect(mx_host, mx_port);
237 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
240 syslog(LOG_DEBUG, "SMTP client: connected!\n");
242 fdflags = fcntl(sock, F_GETFL);
245 "unable to get SMTP-Client socket flags! %s \n",
247 fdflags = fdflags | O_NONBLOCK;
248 if (fcntl(sock, F_SETFL, fdflags) < 0)
250 "unable to set SMTP-Client socket nonblocking flags! %s \n",
255 snprintf(dsn, SIZ, "%s", strerror(errno));
258 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
264 *status = 4; /* dsn is already filled in */
268 CCC->sReadBuf = NewStrBuf();
269 CCC->sMigrateBuf = NewStrBuf();
272 /* Process the SMTP greeting from the server */
273 if (ml_sock_gets(&sock, buf, 90) < 0) {
275 strcpy(dsn, "Connection broken during SMTP conversation");
278 syslog(LOG_DEBUG, "<%s\n", buf);
282 safestrncpy(dsn, &buf[4], 1023);
287 safestrncpy(dsn, &buf[4], 1023);
292 /* At this point we know we are talking to a real SMTP server */
294 /* Do a EHLO command. If it fails, try the HELO command. */
295 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
296 syslog(LOG_DEBUG, ">%s", buf);
297 sock_write(&sock, buf, strlen(buf));
298 if (ml_sock_gets(&sock, buf, 30) < 0) {
300 strcpy(dsn, "Connection broken during SMTP HELO");
303 syslog(LOG_DEBUG, "<%s\n", buf);
305 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
306 syslog(LOG_DEBUG, ">%s", buf);
307 sock_write(&sock, buf, strlen(buf));
308 if (ml_sock_gets(&sock, buf, 30) < 0) {
310 strcpy(dsn, "Connection broken during SMTP HELO");
317 safestrncpy(dsn, &buf[4], 1023);
322 safestrncpy(dsn, &buf[4], 1023);
327 /* Do an AUTH command if necessary */
328 if (!IsEmptyStr(mx_user)) {
330 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
331 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2, 0);
332 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
333 syslog(LOG_DEBUG, ">%s", buf);
334 sock_write(&sock, buf, strlen(buf));
335 if (ml_sock_gets(&sock, buf, 30) < 0) {
337 strcpy(dsn, "Connection broken during SMTP AUTH");
340 syslog(LOG_DEBUG, "<%s\n", buf);
344 safestrncpy(dsn, &buf[4], 1023);
349 safestrncpy(dsn, &buf[4], 1023);
355 /* previous command succeeded, now try the MAIL FROM: command */
356 snprintf(buf, sizeof buf, "MAIL FROM:<%s>\r\n", envelope_from);
357 syslog(LOG_DEBUG, ">%s", buf);
358 sock_write(&sock, buf, strlen(buf));
359 if (ml_sock_gets(&sock, buf, 30) < 0) {
361 strcpy(dsn, "Connection broken during SMTP MAIL");
364 syslog(LOG_DEBUG, "<%s\n", buf);
368 safestrncpy(dsn, &buf[4], 1023);
373 safestrncpy(dsn, &buf[4], 1023);
378 /* MAIL succeeded, now try the RCPT To: command */
379 snprintf(buf, sizeof buf, "RCPT TO:<%s@%s>\r\n", user, node);
380 syslog(LOG_DEBUG, ">%s", buf);
381 sock_write(&sock, buf, strlen(buf));
382 if (ml_sock_gets(&sock, buf, 30) < 0) {
384 strcpy(dsn, "Connection broken during SMTP RCPT");
387 syslog(LOG_DEBUG, "<%s\n", buf);
391 safestrncpy(dsn, &buf[4], 1023);
396 safestrncpy(dsn, &buf[4], 1023);
401 /* RCPT succeeded, now try the DATA command */
402 syslog(LOG_DEBUG, ">DATA\n");
403 sock_write(&sock, "DATA\r\n", 6);
404 if (ml_sock_gets(&sock, buf, 30) < 0) {
406 strcpy(dsn, "Connection broken during SMTP DATA");
409 syslog(LOG_DEBUG, "<%s\n", buf);
413 safestrncpy(dsn, &buf[4], 1023);
418 safestrncpy(dsn, &buf[4], 1023);
423 /* If we reach this point, the server is expecting data.*/
424 sock_write_timeout(&sock,
427 (msg_size / 128) + 50);
428 if (msgtext[msg_size-1] != 10) {
429 syslog(LOG_WARNING, "Possible problem: message did not "
430 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
432 sock_write(&sock, "\r\n", 2);
435 sock_write(&sock, ".\r\n", 3);
437 if (ml_sock_gets(&sock, buf, 90) < 0) {
439 strcpy(dsn, "Connection broken during SMTP message transmit");
442 syslog(LOG_DEBUG, "%s\n", buf);
446 safestrncpy(dsn, &buf[4], 1023);
451 safestrncpy(dsn, &buf[4], 1023);
457 safestrncpy(dsn, &buf[4], 1023);
460 syslog(LOG_DEBUG, ">QUIT\n");
461 sock_write(&sock, "QUIT\r\n", 6);
462 ml_sock_gets(&sock, buf, 30);
463 syslog(LOG_DEBUG, "<%s\n", buf);
464 syslog(LOG_INFO, "SMTP client: delivery to <%s> @ <%s> (%s) succeeded\n",
468 FreeStrBuf(&CCC->sReadBuf);
469 FreeStrBuf(&CCC->sMigrateBuf);
473 /* Write something to the syslog(which may or may not be where the
474 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
476 syslog((LOG_MAIL | LOG_INFO),
477 "%ld: to=<%s>, relay=%s, stat=%s",
490 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
491 * instructions for "5" codes (permanent fatal errors) and produce/deliver
492 * a "bounce" message (delivery status notification).
494 void smtp_do_bounce(char *instr) {
506 long bounce_msgid = (-1);
507 time_t submitted = 0L;
508 struct CtdlMessage *bmsg = NULL;
510 struct recptypes *valid;
511 int successful_bounce = 0;
516 syslog(LOG_DEBUG, "smtp_do_bounce() called\n");
517 strcpy(bounceto, "");
518 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
519 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
520 lines = num_tokens(instr, '\n');
522 /* See if it's time to give up on delivery of this message */
523 for (i=0; i<lines; ++i) {
524 extract_token(buf, instr, i, '\n', sizeof buf);
525 extract_token(key, buf, 0, '|', sizeof key);
526 extract_token(addr, buf, 1, '|', sizeof addr);
527 if (!strcasecmp(key, "submitted")) {
528 submitted = atol(addr);
532 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
536 /* Start building our bounce message */
538 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
539 if (bmsg == NULL) return;
540 memset(bmsg, 0, sizeof(struct CtdlMessage));
541 BounceMB = NewStrBufPlain(NULL, 1024);
543 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
544 bmsg->cm_anon_type = MES_NORMAL;
545 bmsg->cm_format_type = FMT_RFC822;
546 bmsg->cm_fields['A'] = strdup("Citadel");
547 bmsg->cm_fields['O'] = strdup(MAILROOM);
548 bmsg->cm_fields['N'] = strdup(config.c_nodename);
549 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
550 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
551 StrBufAppendBuf(BounceMB, boundary, 0);
552 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
553 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
554 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
555 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
556 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
557 StrBufAppendBuf(BounceMB, boundary, 0);
558 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
559 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
561 if (give_up) StrBufAppendBufPlain(BounceMB, HKEY(
562 "A message you sent could not be delivered to some or all of its recipients\n"
563 "due to prolonged unavailability of its destination(s).\n"
564 "Giving up on the following addresses:\n\n"
567 else StrBufAppendBufPlain(BounceMB, HKEY(
568 "A message you sent could not be delivered to some or all of its recipients.\n"
569 "The following addresses were undeliverable:\n\n"
573 * Now go through the instructions checking for stuff.
575 for (i=0; i<lines; ++i) {
578 extract_token(buf, instr, i, '\n', sizeof buf);
579 extract_token(key, buf, 0, '|', sizeof key);
580 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
581 status = extract_int(buf, 2);
582 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
585 syslog(LOG_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
586 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\n", num_bounces);
640 if (num_bounces > 0) {
642 /* First try the user who sent the message */
643 syslog(LOG_DEBUG, "bounce to user? <%s>\n", bounceto);
644 if (IsEmptyStr(bounceto)) {
645 syslog(LOG_ERR, "No bounce address specified\n");
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\n");
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)\n", msgnum);
741 strcpy(envelope_from, "");
743 msg = CtdlFetchMessage(msgnum, 1);
745 syslog(LOG_ERR, "SMTP client: tried %ld but no such message!\n", 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.\n");
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!\n");
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>\n", 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\n");
957 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
958 syslog(LOG_ERR, "Cannot find room <%s>\n", 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\n", num_processed);
969 * Initialize the SMTP outbound queue
971 void smtp_init_spoolout(void) {
972 struct ctdlroom qrbuf;
975 * Create the room. This will silently fail if the room already
976 * exists, and that's perfectly ok, because we want it to exist.
978 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
981 * Make sure it's set to be a "system room" so it doesn't show up
982 * in the <K>nown rooms list for Aides.
984 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
985 qrbuf.QRflags2 |= QR2_SYSTEM;
986 CtdlPutRoomLock(&qrbuf);
993 CTDL_MODULE_INIT(smtp_client)
995 #ifndef EXPERIMENTAL_SMTP_EVENT_CLIENT
998 smtp_init_spoolout();
999 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1000 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1004 /* return our Subversion id for the Log */
1005 return "smtpclient";