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"
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];
128 /* Parse out the host portion of the recipient address */
129 process_rfc822_addr(addr, user, node, name);
131 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Attempting delivery to <%s> @ <%s> (%s)\n",
134 /* Load the message out of the database */
135 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
136 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
137 msg_size = StrLength(CC->redirect_buffer);
138 msgtext = SmashStrBuf(&CC->redirect_buffer);
140 /* If no envelope_from is supplied, extract one from the message */
141 if ( (envelope_from == NULL) || (IsEmptyStr(envelope_from)) ) {
142 strcpy(mailfrom, "");
146 if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
149 if (!strncasecmp(buf, "From:", 5)) {
150 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
152 for (i=0; mailfrom[i]; ++i) {
153 if (!isprint(mailfrom[i])) {
154 strcpy(&mailfrom[i], &mailfrom[i+1]);
159 /* Strip out parenthesized names */
162 for (i=0; mailfrom[i]; ++i) {
163 if (mailfrom[i] == '(') lp = i;
164 if (mailfrom[i] == ')') rp = i;
166 if ((lp>0)&&(rp>lp)) {
167 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
170 /* Prefer brokketized 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) ) {
179 strcpy(mailfrom, &mailfrom[lp]);
184 } while (scan_done == 0);
185 if (IsEmptyStr(mailfrom)) strcpy(mailfrom, "someone@somewhere.org");
186 stripallbut(mailfrom, '<', '>');
187 envelope_from = mailfrom;
190 /* Figure out what mail exchanger host we have to connect to */
191 num_mxhosts = getmx(mxhosts, node);
192 CtdlLogPrintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d [%s]\n", node, num_mxhosts, mxhosts);
193 if (num_mxhosts < 1) {
195 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
200 for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
202 extract_token(buf, mxhosts, mx, '|', sizeof buf);
205 if (num_tokens(buf, '@') > 1) {
206 strcpy (mx_user, buf);
207 endpart = strrchr(mx_user, '@');
209 strcpy (mx_host, endpart + 1);
210 endpart = strrchr(mx_user, ':');
211 if (endpart != NULL) {
212 strcpy(mx_pass, endpart+1);
217 strcpy (mx_host, buf);
218 endpart = strrchr(mx_host, ':');
221 strcpy(mx_port, endpart + 1);
224 strcpy(mx_port, "25");
226 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: connecting to %s : %s ...\n", mx_host, mx_port);
227 sock = sock_connect(mx_host, mx_port);
228 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
231 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: connected!\n");
233 fdflags = fcntl(sock, F_GETFL);
235 CtdlLogPrintf(CTDL_DEBUG,
236 "unable to get SMTP-Client socket flags! %s \n",
238 fdflags = fdflags | O_NONBLOCK;
239 if (fcntl(sock, F_SETFL, fdflags) < 0)
240 CtdlLogPrintf(CTDL_DEBUG,
241 "unable to set SMTP-Client socket nonblocking flags! %s \n",
246 snprintf(dsn, SIZ, "%s", strerror(errno));
249 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
255 *status = 4; /* dsn is already filled in */
259 CCC->sReadBuf = NewStrBuf();
260 CCC->sMigrateBuf = NewStrBuf();
263 /* Process the SMTP greeting from the server */
264 if (ml_sock_gets(&sock, buf, 90) < 0) {
266 strcpy(dsn, "Connection broken during SMTP conversation");
269 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
273 safestrncpy(dsn, &buf[4], 1023);
278 safestrncpy(dsn, &buf[4], 1023);
283 /* At this point we know we are talking to a real SMTP server */
285 /* Do a EHLO command. If it fails, try the HELO command. */
286 snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
287 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
288 sock_write(&sock, buf, strlen(buf));
289 if (ml_sock_gets(&sock, buf, 30) < 0) {
291 strcpy(dsn, "Connection broken during SMTP HELO");
294 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
296 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
297 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
298 sock_write(&sock, buf, strlen(buf));
299 if (ml_sock_gets(&sock, buf, 30) < 0) {
301 strcpy(dsn, "Connection broken during SMTP HELO");
308 safestrncpy(dsn, &buf[4], 1023);
313 safestrncpy(dsn, &buf[4], 1023);
318 /* Do an AUTH command if necessary */
319 if (!IsEmptyStr(mx_user)) {
321 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
322 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2, 0);
323 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
324 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
325 sock_write(&sock, buf, strlen(buf));
326 if (ml_sock_gets(&sock, buf, 30) < 0) {
328 strcpy(dsn, "Connection broken during SMTP AUTH");
331 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
335 safestrncpy(dsn, &buf[4], 1023);
340 safestrncpy(dsn, &buf[4], 1023);
346 /* previous command succeeded, now try the MAIL FROM: command */
347 snprintf(buf, sizeof buf, "MAIL FROM:<%s>\r\n", envelope_from);
348 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
349 sock_write(&sock, buf, strlen(buf));
350 if (ml_sock_gets(&sock, buf, 30) < 0) {
352 strcpy(dsn, "Connection broken during SMTP MAIL");
355 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
359 safestrncpy(dsn, &buf[4], 1023);
364 safestrncpy(dsn, &buf[4], 1023);
369 /* MAIL succeeded, now try the RCPT To: command */
370 snprintf(buf, sizeof buf, "RCPT TO:<%s@%s>\r\n", user, node);
371 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
372 sock_write(&sock, buf, strlen(buf));
373 if (ml_sock_gets(&sock, buf, 30) < 0) {
375 strcpy(dsn, "Connection broken during SMTP RCPT");
378 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
382 safestrncpy(dsn, &buf[4], 1023);
387 safestrncpy(dsn, &buf[4], 1023);
392 /* RCPT succeeded, now try the DATA command */
393 CtdlLogPrintf(CTDL_DEBUG, ">DATA\n");
394 sock_write(&sock, "DATA\r\n", 6);
395 if (ml_sock_gets(&sock, buf, 30) < 0) {
397 strcpy(dsn, "Connection broken during SMTP DATA");
400 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
404 safestrncpy(dsn, &buf[4], 1023);
409 safestrncpy(dsn, &buf[4], 1023);
414 /* If we reach this point, the server is expecting data.*/
415 sock_write_timeout(&sock,
418 (msg_size / 128) + 50);
419 if (msgtext[msg_size-1] != 10) {
420 CtdlLogPrintf(CTDL_WARNING, "Possible problem: message did not "
421 "correctly terminate. (expecting 0x10, got 0x%02x)\n",
423 sock_write(&sock, "\r\n", 2);
426 sock_write(&sock, ".\r\n", 3);
428 if (ml_sock_gets(&sock, buf, 90) < 0) {
430 strcpy(dsn, "Connection broken during SMTP message transmit");
433 CtdlLogPrintf(CTDL_DEBUG, "%s\n", buf);
437 safestrncpy(dsn, &buf[4], 1023);
442 safestrncpy(dsn, &buf[4], 1023);
448 safestrncpy(dsn, &buf[4], 1023);
451 CtdlLogPrintf(CTDL_DEBUG, ">QUIT\n");
452 sock_write(&sock, "QUIT\r\n", 6);
453 ml_sock_gets(&sock, buf, 30);
454 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
455 CtdlLogPrintf(CTDL_INFO, "SMTP client: delivery to <%s> @ <%s> (%s) succeeded\n",
459 FreeStrBuf(&CCC->sReadBuf);
460 FreeStrBuf(&CCC->sMigrateBuf);
464 /* Write something to the syslog (which may or may not be where the
465 * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
468 syslog((LOG_MAIL | LOG_INFO),
469 "%ld: to=<%s>, relay=%s, stat=%s",
483 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
484 * instructions for "5" codes (permanent fatal errors) and produce/deliver
485 * a "bounce" message (delivery status notification).
487 void smtp_do_bounce(char *instr) {
499 long bounce_msgid = (-1);
500 time_t submitted = 0L;
501 struct CtdlMessage *bmsg = NULL;
503 struct recptypes *valid;
504 int successful_bounce = 0;
509 CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
510 strcpy(bounceto, "");
511 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
512 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
513 lines = num_tokens(instr, '\n');
515 /* See if it's time to give up on delivery of this message */
516 for (i=0; i<lines; ++i) {
517 extract_token(buf, instr, i, '\n', sizeof buf);
518 extract_token(key, buf, 0, '|', sizeof key);
519 extract_token(addr, buf, 1, '|', sizeof addr);
520 if (!strcasecmp(key, "submitted")) {
521 submitted = atol(addr);
525 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
529 /* Start building our bounce message */
531 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
532 if (bmsg == NULL) return;
533 memset(bmsg, 0, sizeof(struct CtdlMessage));
534 BounceMB = NewStrBufPlain(NULL, 1024);
536 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
537 bmsg->cm_anon_type = MES_NORMAL;
538 bmsg->cm_format_type = FMT_RFC822;
539 bmsg->cm_fields['A'] = strdup("Citadel");
540 bmsg->cm_fields['O'] = strdup(MAILROOM);
541 bmsg->cm_fields['N'] = strdup(config.c_nodename);
542 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
543 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
544 StrBufAppendBuf(BounceMB, boundary, 0);
545 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
546 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
547 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
548 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
549 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
550 StrBufAppendBuf(BounceMB, boundary, 0);
551 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
552 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
554 if (give_up) StrBufAppendBufPlain(BounceMB, HKEY(
555 "A message you sent could not be delivered to some or all of its recipients\n"
556 "due to prolonged unavailability of its destination(s).\n"
557 "Giving up on the following addresses:\n\n"
560 else StrBufAppendBufPlain(BounceMB, HKEY(
561 "A message you sent could not be delivered to some or all of its recipients.\n"
562 "The following addresses were undeliverable:\n\n"
566 * Now go through the instructions checking for stuff.
568 for (i=0; i<lines; ++i) {
571 extract_token(buf, instr, i, '\n', sizeof buf);
572 extract_token(key, buf, 0, '|', sizeof key);
573 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
574 status = extract_int(buf, 2);
575 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
578 CtdlLogPrintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
579 key, addr, status, dsn);
581 if (!strcasecmp(key, "bounceto")) {
582 strcpy(bounceto, addr);
585 if (!strcasecmp(key, "msgid")) {
589 if (!strcasecmp(key, "remote")) {
590 if (status == 5) bounce_this = 1;
591 if (give_up) bounce_this = 1;
597 StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
598 StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
599 StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
600 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
602 remove_token(instr, i, '\n');
608 /* Attach the original message */
610 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
611 StrBufAppendBuf(BounceMB, boundary, 0);
612 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
613 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
614 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
615 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
616 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
618 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
619 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0);
620 StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
621 FreeStrBuf(&CC->redirect_buffer);
624 /* Close the multipart MIME scope */
625 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
626 StrBufAppendBuf(BounceMB, boundary, 0);
627 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
628 if (bmsg->cm_fields['A'] != NULL)
629 free(bmsg->cm_fields['A']);
630 bmsg->cm_fields['A'] = SmashStrBuf(&BounceMB);
631 /* Deliver the bounce if there's anything worth mentioning */
632 CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
633 if (num_bounces > 0) {
635 /* First try the user who sent the message */
636 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
637 if (IsEmptyStr(bounceto)) {
638 CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
639 bounce_msgid = (-1L);
642 /* Can we deliver the bounce to the original sender? */
643 valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
645 if (valid->num_error == 0) {
646 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
647 successful_bounce = 1;
651 /* If not, post it in the Aide> room */
652 if (successful_bounce == 0) {
653 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
656 /* Free up the memory we used */
658 free_recipients(valid);
661 FreeStrBuf(&boundary);
662 CtdlFreeMessage(bmsg);
663 CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
668 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
669 * set of delivery instructions for completed deliveries and remove them.
671 * It returns the number of incomplete deliveries remaining.
673 int smtp_purge_completed_deliveries(char *instr) {
684 lines = num_tokens(instr, '\n');
685 for (i=0; i<lines; ++i) {
686 extract_token(buf, instr, i, '\n', sizeof buf);
687 extract_token(key, buf, 0, '|', sizeof key);
688 extract_token(addr, buf, 1, '|', sizeof addr);
689 status = extract_int(buf, 2);
690 extract_token(dsn, buf, 3, '|', sizeof dsn);
694 if (!strcasecmp(key, "remote")) {
695 if (status == 2) completed = 1;
700 remove_token(instr, i, '\n');
713 * Called by smtp_do_queue() to handle an individual message.
715 void smtp_do_procmsg(long msgnum, void *userdata) {
716 struct CtdlMessage *msg = NULL;
718 char *results = NULL;
726 char envelope_from[1024];
727 long text_msgid = (-1);
728 int incomplete_deliveries_remaining;
729 time_t attempted = 0L;
730 time_t last_attempted = 0L;
731 time_t retry = SMTP_RETRY_INTERVAL;
733 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: smtp_do_procmsg(%ld)\n", msgnum);
734 strcpy(envelope_from, "");
736 msg = CtdlFetchMessage(msgnum, 1);
738 CtdlLogPrintf(CTDL_ERR, "SMTP client: tried %ld but no such message!\n", msgnum);
742 instr = strdup(msg->cm_fields['M']);
743 CtdlFreeMessage(msg);
745 /* Strip out the headers amd any other non-instruction line */
746 lines = num_tokens(instr, '\n');
747 for (i=0; i<lines; ++i) {
748 extract_token(buf, instr, i, '\n', sizeof buf);
749 if (num_tokens(buf, '|') < 2) {
750 remove_token(instr, i, '\n');
756 /* Learn the message ID and find out about recent delivery attempts */
757 lines = num_tokens(instr, '\n');
758 for (i=0; i<lines; ++i) {
759 extract_token(buf, instr, i, '\n', sizeof buf);
760 extract_token(key, buf, 0, '|', sizeof key);
761 if (!strcasecmp(key, "msgid")) {
762 text_msgid = extract_long(buf, 1);
764 if (!strcasecmp(key, "envelope_from")) {
765 extract_token(envelope_from, buf, 1, '|', sizeof envelope_from);
767 if (!strcasecmp(key, "retry")) {
768 /* double the retry interval after each attempt */
769 retry = extract_long(buf, 1) * 2L;
770 if (retry > SMTP_RETRY_MAX) {
771 retry = SMTP_RETRY_MAX;
773 remove_token(instr, i, '\n');
775 if (!strcasecmp(key, "attempted")) {
776 attempted = extract_long(buf, 1);
777 if (attempted > last_attempted)
778 last_attempted = attempted;
783 * Postpone delivery if we've already tried recently.
785 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
786 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
793 * Bail out if there's no actual message associated with this
795 if (text_msgid < 0L) {
796 CtdlLogPrintf(CTDL_ERR, "SMTP client: no 'msgid' directive found!\n");
801 /* Plow through the instructions looking for 'remote' directives and
802 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
803 * were experienced and it's time to try again)
805 lines = num_tokens(instr, '\n');
806 for (i=0; i<lines; ++i) {
807 extract_token(buf, instr, i, '\n', sizeof buf);
808 extract_token(key, buf, 0, '|', sizeof key);
809 extract_token(addr, buf, 1, '|', sizeof addr);
810 status = extract_int(buf, 2);
811 extract_token(dsn, buf, 3, '|', sizeof dsn);
812 if ( (!strcasecmp(key, "remote"))
813 && ((status==0)||(status==3)||(status==4)) ) {
815 /* Remove this "remote" instruction from the set,
816 * but replace the set's final newline if
817 * remove_token() stripped it. It has to be there.
819 remove_token(instr, i, '\n');
820 if (instr[strlen(instr)-1] != '\n') {
826 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Trying <%s>\n", addr);
827 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid, envelope_from);
829 if (results == NULL) {
830 results = malloc(1024);
831 memset(results, 0, 1024);
834 results = realloc(results, strlen(results) + 1024);
836 snprintf(&results[strlen(results)], 1024,
838 key, addr, status, dsn);
843 if (results != NULL) {
844 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
845 strcat(instr, results);
850 /* Generate 'bounce' messages */
851 smtp_do_bounce(instr);
853 /* Go through the delivery list, deleting completed deliveries */
854 incomplete_deliveries_remaining =
855 smtp_purge_completed_deliveries(instr);
859 * No delivery instructions remain, so delete both the instructions
860 * message and the message message.
862 if (incomplete_deliveries_remaining <= 0) {
865 delmsgs[1] = text_msgid;
866 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
870 * Uncompleted delivery instructions remain, so delete the old
871 * instructions and replace with the updated ones.
873 if (incomplete_deliveries_remaining > 0) {
874 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
875 msg = malloc(sizeof(struct CtdlMessage));
876 memset(msg, 0, sizeof(struct CtdlMessage));
877 msg->cm_magic = CTDLMESSAGE_MAGIC;
878 msg->cm_anon_type = MES_NORMAL;
879 msg->cm_format_type = FMT_RFC822;
880 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
881 snprintf(msg->cm_fields['M'],
883 "Content-type: %s\n\n%s\n"
886 SPOOLMIME, instr, (long)time(NULL), (long)retry );
887 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
888 CtdlFreeMessage(msg);
895 /*****************************************************************************/
896 /* SMTP UTILITY COMMANDS */
897 /*****************************************************************************/
899 void cmd_smtp(char *argbuf) {
906 if (CtdlAccessCheck(ac_aide)) return;
908 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
910 if (!strcasecmp(cmd, "mx")) {
911 extract_token(node, argbuf, 1, '|', sizeof node);
912 num_mxhosts = getmx(buf, node);
913 cprintf("%d %d MX hosts listed for %s\n",
914 LISTING_FOLLOWS, num_mxhosts, node);
915 for (i=0; i<num_mxhosts; ++i) {
916 extract_token(node, buf, i, '|', sizeof node);
917 cprintf("%s\n", node);
923 else if (!strcasecmp(cmd, "runqueue")) {
925 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
930 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
937 * smtp_queue_thread()
939 * Run through the queue sending out messages.
941 void *smtp_queue_thread(void *arg) {
942 int num_processed = 0;
943 struct CitContext smtp_queue_CC;
945 CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
946 citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
947 CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
949 while (!CtdlThreadCheckStop()) {
951 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
953 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
954 CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
957 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
959 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
963 CtdlClearSystemContext();
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)
997 smtp_init_spoolout();
998 CtdlThreadCreate("SMTP Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
999 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1002 /* return our Subversion id for the Log */