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-2009 by the citadel.org team
25 * This program is free 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 CtdlLogPrintf(CTDL_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]);
185 } while (scan_done == 0);
186 if (IsEmptyStr(mailfrom)) strcpy(mailfrom, "someone@somewhere.org");
187 stripallbut(mailfrom, '<', '>');
188 envelope_from = mailfrom;
191 /* Figure out what mail exchanger host we have to connect to */
192 num_mxhosts = getmx(mxhosts, node);
193 CtdlLogPrintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d [%s]\n", node, num_mxhosts, mxhosts);
194 if (num_mxhosts < 1) {
196 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
201 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
203 extract_token(buf, mxhosts, mx, '|', sizeof buf);
206 if (num_tokens(buf, '@') > 1) {
207 strcpy (mx_user, buf);
208 endpart = strrchr(mx_user, '@');
210 strcpy (mx_host, endpart + 1);
211 endpart = strrchr(mx_user, ':');
212 if (endpart != NULL) {
213 strcpy(mx_pass, endpart+1);
218 strcpy (mx_host, buf);
219 endpart = strrchr(mx_host, ':');
222 strcpy(mx_port, endpart + 1);
225 strcpy(mx_port, "25");
227 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: connecting to %s : %s ...\n", mx_host, mx_port);
228 sock = sock_connect(mx_host, mx_port);
229 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
232 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: connected!\n");
234 fdflags = fcntl(sock, F_GETFL);
236 CtdlLogPrintf(CTDL_DEBUG,
237 "unable to get SMTP-Client socket flags! %s \n",
239 fdflags = fdflags | O_NONBLOCK;
240 if (fcntl(sock, F_SETFL, fdflags) < 0)
241 CtdlLogPrintf(CTDL_DEBUG,
242 "unable to set SMTP-Client socket nonblocking flags! %s \n",
247 snprintf(dsn, SIZ, "%s", strerror(errno));
250 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
256 *status = 4; /* dsn is already filled in */
260 CCC->sReadBuf = NewStrBuf();
261 CCC->sMigrateBuf = NewStrBuf();
264 /* Process the SMTP greeting from the server */
265 if (ml_sock_gets(&sock, buf, 90) < 0) {
267 strcpy(dsn, "Connection broken during SMTP conversation");
270 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
274 safestrncpy(dsn, &buf[4], 1023);
279 safestrncpy(dsn, &buf[4], 1023);
284 /* At this point we know we are talking to a real SMTP server */
286 /* Do a EHLO command. If it fails, try the HELO command. */
287 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
288 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
289 sock_write(&sock, buf, strlen(buf));
290 if (ml_sock_gets(&sock, buf, 30) < 0) {
292 strcpy(dsn, "Connection broken during SMTP HELO");
295 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
297 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
298 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
299 sock_write(&sock, buf, strlen(buf));
300 if (ml_sock_gets(&sock, buf, 30) < 0) {
302 strcpy(dsn, "Connection broken during SMTP HELO");
309 safestrncpy(dsn, &buf[4], 1023);
314 safestrncpy(dsn, &buf[4], 1023);
319 /* Do an AUTH command if necessary */
320 if (!IsEmptyStr(mx_user)) {
322 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
323 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2, 0);
324 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
325 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
326 sock_write(&sock, buf, strlen(buf));
327 if (ml_sock_gets(&sock, buf, 30) < 0) {
329 strcpy(dsn, "Connection broken during SMTP AUTH");
332 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
336 safestrncpy(dsn, &buf[4], 1023);
341 safestrncpy(dsn, &buf[4], 1023);
347 /* previous command succeeded, now try the MAIL FROM: command */
348 snprintf(buf, sizeof buf, "MAIL FROM:<%s>\r\n", envelope_from);
349 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
350 sock_write(&sock, buf, strlen(buf));
351 if (ml_sock_gets(&sock, buf, 30) < 0) {
353 strcpy(dsn, "Connection broken during SMTP MAIL");
356 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
360 safestrncpy(dsn, &buf[4], 1023);
365 safestrncpy(dsn, &buf[4], 1023);
370 /* MAIL succeeded, now try the RCPT To: command */
371 snprintf(buf, sizeof buf, "RCPT TO:<%s@%s>\r\n", user, node);
372 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
373 sock_write(&sock, buf, strlen(buf));
374 if (ml_sock_gets(&sock, buf, 30) < 0) {
376 strcpy(dsn, "Connection broken during SMTP RCPT");
379 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
383 safestrncpy(dsn, &buf[4], 1023);
388 safestrncpy(dsn, &buf[4], 1023);
393 /* RCPT succeeded, now try the DATA command */
394 CtdlLogPrintf(CTDL_DEBUG, ">DATA\n");
395 sock_write(&sock, "DATA\r\n", 6);
396 if (ml_sock_gets(&sock, buf, 30) < 0) {
398 strcpy(dsn, "Connection broken during SMTP DATA");
401 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
405 safestrncpy(dsn, &buf[4], 1023);
410 safestrncpy(dsn, &buf[4], 1023);
415 /* If we reach this point, the server is expecting data.*/
416 sock_write_timeout(&sock,
419 (msg_size / 128) + 50);
420 if (msgtext[msg_size-1] != 10) {
421 CtdlLogPrintf(CTDL_WARNING, "Possible problem: message did not "
422 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
424 sock_write(&sock, "\r\n", 2);
427 sock_write(&sock, ".\r\n", 3);
429 if (ml_sock_gets(&sock, buf, 90) < 0) {
431 strcpy(dsn, "Connection broken during SMTP message transmit");
434 CtdlLogPrintf(CTDL_DEBUG, "%s\n", buf);
438 safestrncpy(dsn, &buf[4], 1023);
443 safestrncpy(dsn, &buf[4], 1023);
449 safestrncpy(dsn, &buf[4], 1023);
452 CtdlLogPrintf(CTDL_DEBUG, ">QUIT\n");
453 sock_write(&sock, "QUIT\r\n", 6);
454 ml_sock_gets(&sock, buf, 30);
455 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
456 CtdlLogPrintf(CTDL_INFO, "SMTP client: delivery to <%s> @ <%s> (%s) succeeded\n",
460 FreeStrBuf(&CCC->sReadBuf);
461 FreeStrBuf(&CCC->sMigrateBuf);
465 /* Write something to the syslog (which may or may not be where the
466 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
469 syslog((LOG_MAIL | LOG_INFO),
470 "%ld: to=<%s>, relay=%s, stat=%s",
484 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
485 * instructions for "5" codes (permanent fatal errors) and produce/deliver
486 * a "bounce" message (delivery status notification).
488 void smtp_do_bounce(char *instr) {
500 long bounce_msgid = (-1);
501 time_t submitted = 0L;
502 struct CtdlMessage *bmsg = NULL;
504 struct recptypes *valid;
505 int successful_bounce = 0;
510 CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
511 strcpy(bounceto, "");
512 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
513 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
514 lines = num_tokens(instr, '\n');
516 /* See if it's time to give up on delivery of this message */
517 for (i=0; i<lines; ++i) {
518 extract_token(buf, instr, i, '\n', sizeof buf);
519 extract_token(key, buf, 0, '|', sizeof key);
520 extract_token(addr, buf, 1, '|', sizeof addr);
521 if (!strcasecmp(key, "submitted")) {
522 submitted = atol(addr);
526 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
530 /* Start building our bounce message */
532 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
533 if (bmsg == NULL) return;
534 memset(bmsg, 0, sizeof(struct CtdlMessage));
535 BounceMB = NewStrBufPlain(NULL, 1024);
537 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
538 bmsg->cm_anon_type = MES_NORMAL;
539 bmsg->cm_format_type = FMT_RFC822;
540 bmsg->cm_fields['A'] = strdup("Citadel");
541 bmsg->cm_fields['O'] = strdup(MAILROOM);
542 bmsg->cm_fields['N'] = strdup(config.c_nodename);
543 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
544 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
545 StrBufAppendBuf(BounceMB, boundary, 0);
546 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
547 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
548 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
549 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
550 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
551 StrBufAppendBuf(BounceMB, boundary, 0);
552 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
553 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
555 if (give_up) StrBufAppendBufPlain(BounceMB, HKEY(
556 "A message you sent could not be delivered to some or all of its recipients\n"
557 "due to prolonged unavailability of its destination(s).\n"
558 "Giving up on the following addresses:\n\n"
561 else StrBufAppendBufPlain(BounceMB, HKEY(
562 "A message you sent could not be delivered to some or all of its recipients.\n"
563 "The following addresses were undeliverable:\n\n"
567 * Now go through the instructions checking for stuff.
569 for (i=0; i<lines; ++i) {
572 extract_token(buf, instr, i, '\n', sizeof buf);
573 extract_token(key, buf, 0, '|', sizeof key);
574 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
575 status = extract_int(buf, 2);
576 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
579 CtdlLogPrintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
580 key, addr, status, dsn);
582 if (!strcasecmp(key, "bounceto")) {
583 strcpy(bounceto, addr);
586 if (!strcasecmp(key, "msgid")) {
590 if (!strcasecmp(key, "remote")) {
591 if (status == 5) bounce_this = 1;
592 if (give_up) bounce_this = 1;
598 StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
599 StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
600 StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
601 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
603 remove_token(instr, i, '\n');
609 /* Attach the original message */
611 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
612 StrBufAppendBuf(BounceMB, boundary, 0);
613 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
614 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
615 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
616 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
617 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
619 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
620 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0);
621 StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
622 FreeStrBuf(&CC->redirect_buffer);
625 /* Close the multipart MIME scope */
626 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
627 StrBufAppendBuf(BounceMB, boundary, 0);
628 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
629 if (bmsg->cm_fields['A'] != NULL)
630 free(bmsg->cm_fields['A']);
631 bmsg->cm_fields['A'] = SmashStrBuf(&BounceMB);
632 /* Deliver the bounce if there's anything worth mentioning */
633 CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
634 if (num_bounces > 0) {
636 /* First try the user who sent the message */
637 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
638 if (IsEmptyStr(bounceto)) {
639 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
640 bounce_msgid = (-1L);
643 /* Can we deliver the bounce to the original sender? */
644 valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
646 if (valid->num_error == 0) {
647 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
648 successful_bounce = 1;
652 /* If not, post it in the Aide> room */
653 if (successful_bounce == 0) {
654 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
657 /* Free up the memory we used */
659 free_recipients(valid);
662 FreeStrBuf(&boundary);
663 CtdlFreeMessage(bmsg);
664 CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
669 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
670 * set of delivery instructions for completed deliveries and remove them.
672 * It returns the number of incomplete deliveries remaining.
674 int smtp_purge_completed_deliveries(char *instr) {
685 lines = num_tokens(instr, '\n');
686 for (i=0; i<lines; ++i) {
687 extract_token(buf, instr, i, '\n', sizeof buf);
688 extract_token(key, buf, 0, '|', sizeof key);
689 extract_token(addr, buf, 1, '|', sizeof addr);
690 status = extract_int(buf, 2);
691 extract_token(dsn, buf, 3, '|', sizeof dsn);
695 if (!strcasecmp(key, "remote")) {
696 if (status == 2) completed = 1;
701 remove_token(instr, i, '\n');
714 * Called by smtp_do_queue() to handle an individual message.
716 void smtp_do_procmsg(long msgnum, void *userdata) {
717 struct CtdlMessage *msg = NULL;
719 char *results = NULL;
727 char envelope_from[1024];
728 long text_msgid = (-1);
729 int incomplete_deliveries_remaining;
730 time_t attempted = 0L;
731 time_t last_attempted = 0L;
732 time_t retry = SMTP_RETRY_INTERVAL;
734 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: smtp_do_procmsg(%ld)\n", msgnum);
735 strcpy(envelope_from, "");
737 msg = CtdlFetchMessage(msgnum, 1);
739 CtdlLogPrintf(CTDL_ERR, "SMTP client: tried %ld but no such message!\n", msgnum);
743 instr = strdup(msg->cm_fields['M']);
744 CtdlFreeMessage(msg);
746 /* Strip out the headers amd any other non-instruction line */
747 lines = num_tokens(instr, '\n');
748 for (i=0; i<lines; ++i) {
749 extract_token(buf, instr, i, '\n', sizeof buf);
750 if (num_tokens(buf, '|') < 2) {
751 remove_token(instr, i, '\n');
757 /* Learn the message ID and find out about recent delivery attempts */
758 lines = num_tokens(instr, '\n');
759 for (i=0; i<lines; ++i) {
760 extract_token(buf, instr, i, '\n', sizeof buf);
761 extract_token(key, buf, 0, '|', sizeof key);
762 if (!strcasecmp(key, "msgid")) {
763 text_msgid = extract_long(buf, 1);
765 if (!strcasecmp(key, "envelope_from")) {
766 extract_token(envelope_from, buf, 1, '|', sizeof envelope_from);
768 if (!strcasecmp(key, "retry")) {
769 /* double the retry interval after each attempt */
770 retry = extract_long(buf, 1) * 2L;
771 if (retry > SMTP_RETRY_MAX) {
772 retry = SMTP_RETRY_MAX;
774 remove_token(instr, i, '\n');
776 if (!strcasecmp(key, "attempted")) {
777 attempted = extract_long(buf, 1);
778 if (attempted > last_attempted)
779 last_attempted = attempted;
784 * Postpone delivery if we've already tried recently.
786 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
787 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
794 * Bail out if there's no actual message associated with this
796 if (text_msgid < 0L) {
797 CtdlLogPrintf(CTDL_ERR, "SMTP client: no 'msgid' directive found!\n");
802 /* Plow through the instructions looking for 'remote' directives and
803 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
804 * were experienced and it's time to try again)
806 lines = num_tokens(instr, '\n');
807 for (i=0; i<lines; ++i) {
808 extract_token(buf, instr, i, '\n', sizeof buf);
809 extract_token(key, buf, 0, '|', sizeof key);
810 extract_token(addr, buf, 1, '|', sizeof addr);
811 status = extract_int(buf, 2);
812 extract_token(dsn, buf, 3, '|', sizeof dsn);
813 if ( (!strcasecmp(key, "remote"))
814 && ((status==0)||(status==3)||(status==4)) ) {
816 /* Remove this "remote" instruction from the set,
817 * but replace the set's final newline if
818 * remove_token() stripped it. It has to be there.
820 remove_token(instr, i, '\n');
821 if (instr[strlen(instr)-1] != '\n') {
827 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Trying <%s>\n", addr);
828 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid, envelope_from);
830 if (results == NULL) {
831 results = malloc(1024);
832 memset(results, 0, 1024);
835 results = realloc(results, strlen(results) + 1024);
837 snprintf(&results[strlen(results)], 1024,
839 key, addr, status, dsn);
844 if (results != NULL) {
845 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
846 strcat(instr, results);
851 /* Generate 'bounce' messages */
852 smtp_do_bounce(instr);
854 /* Go through the delivery list, deleting completed deliveries */
855 incomplete_deliveries_remaining =
856 smtp_purge_completed_deliveries(instr);
860 * No delivery instructions remain, so delete both the instructions
861 * message and the message message.
863 if (incomplete_deliveries_remaining <= 0) {
866 delmsgs[1] = text_msgid;
867 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
871 * Uncompleted delivery instructions remain, so delete the old
872 * instructions and replace with the updated ones.
874 if (incomplete_deliveries_remaining > 0) {
875 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
876 msg = malloc(sizeof(struct CtdlMessage));
877 memset(msg, 0, sizeof(struct CtdlMessage));
878 msg->cm_magic = CTDLMESSAGE_MAGIC;
879 msg->cm_anon_type = MES_NORMAL;
880 msg->cm_format_type = FMT_RFC822;
881 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
882 snprintf(msg->cm_fields['M'],
884 "Content-type: %s\n\n%s\n"
887 SPOOLMIME, instr, (long)time(NULL), (long)retry );
888 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
889 CtdlFreeMessage(msg);
896 /*****************************************************************************/
897 /* SMTP UTILITY COMMANDS */
898 /*****************************************************************************/
900 void cmd_smtp(char *argbuf) {
907 if (CtdlAccessCheck(ac_aide)) return;
909 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
911 if (!strcasecmp(cmd, "mx")) {
912 extract_token(node, argbuf, 1, '|', sizeof node);
913 num_mxhosts = getmx(buf, node);
914 cprintf("%d %d MX hosts listed for %s\n",
915 LISTING_FOLLOWS, num_mxhosts, node);
916 for (i=0; i<num_mxhosts; ++i) {
917 extract_token(node, buf, i, '|', sizeof node);
918 cprintf("%s\n", node);
924 else if (!strcasecmp(cmd, "runqueue")) {
926 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
931 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
938 * smtp_queue_thread()
940 * Run through the queue sending out messages.
942 void *smtp_queue_thread(void *arg) {
943 int num_processed = 0;
944 struct CitContext smtp_queue_CC;
946 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
947 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
948 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
950 while (!CtdlThreadCheckStop()) {
952 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
954 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
955 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
958 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
960 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
964 CtdlClearSystemContext();
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)
996 #ifndef EXPERIMENTAL_SMTP_EVENT_CLIENT
999 smtp_init_spoolout();
1000 CtdlThreadCreate("SMTP Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1001 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1005 /* return our Subversion id for the Log */
1006 return "smtpclient";