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)) 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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_DEBUG, "SMTP client: connected!\n");
234 fdflags = fcntl(sock, F_GETFL);
237 "unable to get SMTP-Client socket flags! %s \n",
239 fdflags = fdflags | O_NONBLOCK;
240 if (fcntl(sock, F_SETFL, fdflags) < 0)
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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_DEBUG, "<%s\n", buf);
297 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
298 syslog(LOG_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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_DEBUG, "%s\n", buf);
438 safestrncpy(dsn, &buf[4], 1023);
443 safestrncpy(dsn, &buf[4], 1023);
449 safestrncpy(dsn, &buf[4], 1023);
452 syslog(LOG_DEBUG, ">QUIT\n");
453 sock_write(&sock, "QUIT\r\n", 6);
454 ml_sock_gets(&sock, buf, 30);
455 syslog(LOG_DEBUG, "<%s\n", buf);
456 syslog(LOG_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).
468 syslog((LOG_MAIL | LOG_INFO),
469 "%ld: to=<%s>, relay=%s, stat=%s",
482 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
483 * instructions for "5" codes (permanent fatal errors) and produce/deliver
484 * a "bounce" message (delivery status notification).
486 void smtp_do_bounce(char *instr) {
498 long bounce_msgid = (-1);
499 time_t submitted = 0L;
500 struct CtdlMessage *bmsg = NULL;
502 struct recptypes *valid;
503 int successful_bounce = 0;
508 syslog(LOG_DEBUG, "smtp_do_bounce() called\n");
509 strcpy(bounceto, "");
510 boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
511 StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
512 lines = num_tokens(instr, '\n');
514 /* See if it's time to give up on delivery of this message */
515 for (i=0; i<lines; ++i) {
516 extract_token(buf, instr, i, '\n', sizeof buf);
517 extract_token(key, buf, 0, '|', sizeof key);
518 extract_token(addr, buf, 1, '|', sizeof addr);
519 if (!strcasecmp(key, "submitted")) {
520 submitted = atol(addr);
524 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
528 /* Start building our bounce message */
530 bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
531 if (bmsg == NULL) return;
532 memset(bmsg, 0, sizeof(struct CtdlMessage));
533 BounceMB = NewStrBufPlain(NULL, 1024);
535 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
536 bmsg->cm_anon_type = MES_NORMAL;
537 bmsg->cm_format_type = FMT_RFC822;
538 bmsg->cm_fields['A'] = strdup("Citadel");
539 bmsg->cm_fields['O'] = strdup(MAILROOM);
540 bmsg->cm_fields['N'] = strdup(config.c_nodename);
541 bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
542 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
543 StrBufAppendBuf(BounceMB, boundary, 0);
544 StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
545 StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
546 StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
547 StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
548 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
549 StrBufAppendBuf(BounceMB, boundary, 0);
550 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
551 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
553 if (give_up) StrBufAppendBufPlain(BounceMB, HKEY(
554 "A message you sent could not be delivered to some or all of its recipients\n"
555 "due to prolonged unavailability of its destination(s).\n"
556 "Giving up on the following addresses:\n\n"
559 else StrBufAppendBufPlain(BounceMB, HKEY(
560 "A message you sent could not be delivered to some or all of its recipients.\n"
561 "The following addresses were undeliverable:\n\n"
565 * Now go through the instructions checking for stuff.
567 for (i=0; i<lines; ++i) {
570 extract_token(buf, instr, i, '\n', sizeof buf);
571 extract_token(key, buf, 0, '|', sizeof key);
572 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
573 status = extract_int(buf, 2);
574 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
577 syslog(LOG_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
578 key, addr, status, dsn);
580 if (!strcasecmp(key, "bounceto")) {
581 strcpy(bounceto, addr);
584 if (!strcasecmp(key, "msgid")) {
588 if (!strcasecmp(key, "remote")) {
589 if (status == 5) bounce_this = 1;
590 if (give_up) bounce_this = 1;
596 StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
597 StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
598 StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
599 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
601 remove_token(instr, i, '\n');
607 /* Attach the original message */
609 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
610 StrBufAppendBuf(BounceMB, boundary, 0);
611 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
612 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
613 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
614 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
615 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
617 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
618 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0);
619 StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
620 FreeStrBuf(&CC->redirect_buffer);
623 /* Close the multipart MIME scope */
624 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
625 StrBufAppendBuf(BounceMB, boundary, 0);
626 StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
627 if (bmsg->cm_fields['A'] != NULL)
628 free(bmsg->cm_fields['A']);
629 bmsg->cm_fields['A'] = SmashStrBuf(&BounceMB);
630 /* Deliver the bounce if there's anything worth mentioning */
631 syslog(LOG_DEBUG, "num_bounces = %d\n", num_bounces);
632 if (num_bounces > 0) {
634 /* First try the user who sent the message */
635 syslog(LOG_DEBUG, "bounce to user? <%s>\n", bounceto);
636 if (IsEmptyStr(bounceto)) {
637 syslog(LOG_ERR, "No bounce address specified\n");
638 bounce_msgid = (-1L);
641 /* Can we deliver the bounce to the original sender? */
642 valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
644 if (valid->num_error == 0) {
645 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
646 successful_bounce = 1;
650 /* If not, post it in the Aide> room */
651 if (successful_bounce == 0) {
652 CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
655 /* Free up the memory we used */
657 free_recipients(valid);
660 FreeStrBuf(&boundary);
661 CtdlFreeMessage(bmsg);
662 syslog(LOG_DEBUG, "Done processing bounces\n");
667 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
668 * set of delivery instructions for completed deliveries and remove them.
670 * It returns the number of incomplete deliveries remaining.
672 int smtp_purge_completed_deliveries(char *instr) {
683 lines = num_tokens(instr, '\n');
684 for (i=0; i<lines; ++i) {
685 extract_token(buf, instr, i, '\n', sizeof buf);
686 extract_token(key, buf, 0, '|', sizeof key);
687 extract_token(addr, buf, 1, '|', sizeof addr);
688 status = extract_int(buf, 2);
689 extract_token(dsn, buf, 3, '|', sizeof dsn);
693 if (!strcasecmp(key, "remote")) {
694 if (status == 2) completed = 1;
699 remove_token(instr, i, '\n');
712 * Called by smtp_do_queue() to handle an individual message.
714 void smtp_do_procmsg(long msgnum, void *userdata) {
715 struct CtdlMessage *msg = NULL;
717 char *results = NULL;
725 char envelope_from[1024];
726 long text_msgid = (-1);
727 int incomplete_deliveries_remaining;
728 time_t attempted = 0L;
729 time_t last_attempted = 0L;
730 time_t retry = SMTP_RETRY_INTERVAL;
732 syslog(LOG_DEBUG, "SMTP client: smtp_do_procmsg(%ld)\n", msgnum);
733 strcpy(envelope_from, "");
735 msg = CtdlFetchMessage(msgnum, 1);
737 syslog(LOG_ERR, "SMTP client: tried %ld but no such message!\n", msgnum);
741 instr = strdup(msg->cm_fields['M']);
742 CtdlFreeMessage(msg);
744 /* Strip out the headers amd any other non-instruction line */
745 lines = num_tokens(instr, '\n');
746 for (i=0; i<lines; ++i) {
747 extract_token(buf, instr, i, '\n', sizeof buf);
748 if (num_tokens(buf, '|') < 2) {
749 remove_token(instr, i, '\n');
755 /* Learn the message ID and find out about recent delivery attempts */
756 lines = num_tokens(instr, '\n');
757 for (i=0; i<lines; ++i) {
758 extract_token(buf, instr, i, '\n', sizeof buf);
759 extract_token(key, buf, 0, '|', sizeof key);
760 if (!strcasecmp(key, "msgid")) {
761 text_msgid = extract_long(buf, 1);
763 if (!strcasecmp(key, "envelope_from")) {
764 extract_token(envelope_from, buf, 1, '|', sizeof envelope_from);
766 if (!strcasecmp(key, "retry")) {
767 /* double the retry interval after each attempt */
768 retry = extract_long(buf, 1) * 2L;
769 if (retry > SMTP_RETRY_MAX) {
770 retry = SMTP_RETRY_MAX;
772 remove_token(instr, i, '\n');
774 if (!strcasecmp(key, "attempted")) {
775 attempted = extract_long(buf, 1);
776 if (attempted > last_attempted)
777 last_attempted = attempted;
782 * Postpone delivery if we've already tried recently.
784 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
785 syslog(LOG_DEBUG, "SMTP client: Retry time not yet reached.\n");
792 * Bail out if there's no actual message associated with this
794 if (text_msgid < 0L) {
795 syslog(LOG_ERR, "SMTP client: no 'msgid' directive found!\n");
800 /* Plow through the instructions looking for 'remote' directives and
801 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
802 * were experienced and it's time to try again)
804 lines = num_tokens(instr, '\n');
805 for (i=0; i<lines; ++i) {
806 extract_token(buf, instr, i, '\n', sizeof buf);
807 extract_token(key, buf, 0, '|', sizeof key);
808 extract_token(addr, buf, 1, '|', sizeof addr);
809 status = extract_int(buf, 2);
810 extract_token(dsn, buf, 3, '|', sizeof dsn);
811 if ( (!strcasecmp(key, "remote"))
812 && ((status==0)||(status==3)||(status==4)) ) {
814 /* Remove this "remote" instruction from the set,
815 * but replace the set's final newline if
816 * remove_token() stripped it. It has to be there.
818 remove_token(instr, i, '\n');
819 if (instr[strlen(instr)-1] != '\n') {
825 syslog(LOG_DEBUG, "SMTP client: Trying <%s>\n", addr);
826 smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid, envelope_from);
828 if (results == NULL) {
829 results = malloc(1024);
830 memset(results, 0, 1024);
833 results = realloc(results, strlen(results) + 1024);
835 snprintf(&results[strlen(results)], 1024,
837 key, addr, status, dsn);
842 if (results != NULL) {
843 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
844 strcat(instr, results);
849 /* Generate 'bounce' messages */
850 smtp_do_bounce(instr);
852 /* Go through the delivery list, deleting completed deliveries */
853 incomplete_deliveries_remaining =
854 smtp_purge_completed_deliveries(instr);
858 * No delivery instructions remain, so delete both the instructions
859 * message and the message message.
861 if (incomplete_deliveries_remaining <= 0) {
864 delmsgs[1] = text_msgid;
865 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
869 * Uncompleted delivery instructions remain, so delete the old
870 * instructions and replace with the updated ones.
872 if (incomplete_deliveries_remaining > 0) {
873 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
874 msg = malloc(sizeof(struct CtdlMessage));
875 memset(msg, 0, sizeof(struct CtdlMessage));
876 msg->cm_magic = CTDLMESSAGE_MAGIC;
877 msg->cm_anon_type = MES_NORMAL;
878 msg->cm_format_type = FMT_RFC822;
879 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
880 snprintf(msg->cm_fields['M'],
882 "Content-type: %s\n\n%s\n"
885 SPOOLMIME, instr, (long)time(NULL), (long)retry );
886 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
887 CtdlFreeMessage(msg);
894 /*****************************************************************************/
895 /* SMTP UTILITY COMMANDS */
896 /*****************************************************************************/
898 void cmd_smtp(char *argbuf) {
905 if (CtdlAccessCheck(ac_aide)) return;
907 extract_token(cmd, argbuf, 0, '|', sizeof cmd);
909 if (!strcasecmp(cmd, "mx")) {
910 extract_token(node, argbuf, 1, '|', sizeof node);
911 num_mxhosts = getmx(buf, node);
912 cprintf("%d %d MX hosts listed for %s\n",
913 LISTING_FOLLOWS, num_mxhosts, node);
914 for (i=0; i<num_mxhosts; ++i) {
915 extract_token(node, buf, i, '|', sizeof node);
916 cprintf("%s\n", node);
922 else if (!strcasecmp(cmd, "runqueue")) {
924 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
929 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
936 * smtp_queue_thread()
938 * Run through the queue sending out messages.
940 void smtp_do_queue(void) {
941 static int is_running = 0;
942 int num_processed = 0;
944 if (is_running) return; /* Concurrency check - only one can run */
947 syslog(LOG_INFO, "SMTP client: processing outbound queue\n");
949 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
950 syslog(LOG_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
953 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
955 syslog(LOG_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
961 * Initialize the SMTP outbound queue
963 void smtp_init_spoolout(void) {
964 struct ctdlroom qrbuf;
967 * Create the room. This will silently fail if the room already
968 * exists, and that's perfectly ok, because we want it to exist.
970 CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
973 * Make sure it's set to be a "system room" so it doesn't show up
974 * in the <K>nown rooms list for Aides.
976 if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
977 qrbuf.QRflags2 |= QR2_SYSTEM;
978 CtdlPutRoomLock(&qrbuf);
985 CTDL_MODULE_INIT(smtp_client)
987 #ifndef EXPERIMENTAL_SMTP_EVENT_CLIENT
990 smtp_init_spoolout();
991 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
992 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
996 /* return our Subversion id for the Log */